Skip to content

Modules

Define and parse package manifests.

Manifest

Bases: BaseModel

Represent the metadata and file lists defined by a package manifest.

Source code in src/mkp_forge/manifest.py
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
class Manifest(BaseModel):
    """Represent the metadata and file lists defined by a package manifest."""

    title: str
    name: PackageName
    description: str
    version: PackageVersion
    version_packaged: str = Field(alias="version.packaged")
    version_min_required: str = Field(alias="version.min_required")
    version_usable_until: str | None = Field(None, alias="version.usable_until")
    author: str
    download_url: str
    files: Mapping[PackagePart, Sequence[Path]]

    model_config = ConfigDict(
        validate_by_name=True,
        frozen=True,
        extra="allow",  # we used to have 'num_files' :-(
    )

    def file_content(self) -> str:
        """Return the manifest as a pretty-printed Python dictionary.

        Returns:
            str: The manifest content rendered with alias names and file paths as
                strings in a human-readable Python literal format.
        """
        raw = {
            **self.model_dump(by_alias=True),
            "files": {p.ident: [str(f) for f in files] for p, files in self.files.items()},
        }
        return f"{pprint.pformat(raw)}\n"

    def json_file_content(self) -> str:
        """Return the manifest serialized as JSON using alias field names.

        Returns:
            str: The manifest content as a JSON string.
        """
        return self.model_dump_json(by_alias=True)

    @classmethod
    def parse_python_string(cls, raw: str) -> Self:
        """Parse a manifest definition from a Python literal string.

        Args:
            raw (str): A Python dictionary literal describing the manifest.

        Returns:
            Self: A validated Manifest instance created from the parsed data.
        """
        return cls.model_validate(ast.literal_eval(raw))

    @property
    def id(self) -> PackageID:
        """Return the package identifier for this manifest.

        Returns:
            PackageID: The canonical package identifier composed from the package
                name and version.
        """
        return PackageID(name=self.name, version=self.version)

id property

Return the package identifier for this manifest.

Returns:

Name Type Description
PackageID PackageID

The canonical package identifier composed from the package name and version.

file_content()

Return the manifest as a pretty-printed Python dictionary.

Returns:

Name Type Description
str str

The manifest content rendered with alias names and file paths as strings in a human-readable Python literal format.

Source code in src/mkp_forge/manifest.py
73
74
75
76
77
78
79
80
81
82
83
84
def file_content(self) -> str:
    """Return the manifest as a pretty-printed Python dictionary.

    Returns:
        str: The manifest content rendered with alias names and file paths as
            strings in a human-readable Python literal format.
    """
    raw = {
        **self.model_dump(by_alias=True),
        "files": {p.ident: [str(f) for f in files] for p, files in self.files.items()},
    }
    return f"{pprint.pformat(raw)}\n"

json_file_content()

Return the manifest serialized as JSON using alias field names.

Returns:

Name Type Description
str str

The manifest content as a JSON string.

Source code in src/mkp_forge/manifest.py
86
87
88
89
90
91
92
def json_file_content(self) -> str:
    """Return the manifest serialized as JSON using alias field names.

    Returns:
        str: The manifest content as a JSON string.
    """
    return self.model_dump_json(by_alias=True)

parse_python_string(raw) classmethod

Parse a manifest definition from a Python literal string.

Parameters:

Name Type Description Default
raw str

A Python dictionary literal describing the manifest.

required

Returns:

Name Type Description
Self Self

A validated Manifest instance created from the parsed data.

Source code in src/mkp_forge/manifest.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
@classmethod
def parse_python_string(cls, raw: str) -> Self:
    """Parse a manifest definition from a Python literal string.

    Args:
        raw (str): A Python dictionary literal describing the manifest.

    Returns:
        Self: A validated Manifest instance created from the parsed data.
    """
    return cls.model_validate(ast.literal_eval(raw))

PackagePart

Bases: StrEnum

A category of files included in a package manifest.

Source code in src/mkp_forge/manifest.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@enum.unique
class PackagePart(enum.StrEnum):
    """A category of files included in a package manifest."""

    # We have to inherit str to make the (de)serialization work as expected.
    # It's a shame, but other approaches don't work or are worse.
    CMK_PLUGINS = "cmk_plugins"
    CMK_ADDONS_PLUGINS = "cmk_addons_plugins"
    EC_RULE_PACKS = "ec_rule_packs"
    AGENT_BASED = "agent_based"
    CHECKS = "checks"
    HASI = "inventory"
    CHECKMAN = "checkman"
    AGENTS = "agents"
    NOTIFICATIONS = "notifications"
    GUI = "gui"
    WEB = "web"
    PNP_TEMPLATES = "pnp-templates"
    DOC = "doc"
    LOCALES = "locales"
    BIN = "bin"
    LIB = "lib"
    MIBS = "mibs"
    ALERT_HANDLERS = "alert_handlers"

    @property
    def ident(self) -> str:
        """Return the manifest identifier string for this package part.

        Returns:
            str: The canonical string value used in manifest files.
        """
        return self.value

ident property

Return the manifest identifier string for this package part.

Returns:

Name Type Description
str str

The canonical string value used in manifest files.

read_manifest(manifest_path)

Load and validate a package manifest from a file.

Parameters:

Name Type Description Default
manifest_path Path

Path to the manifest file.

required

Returns:

Name Type Description
Manifest Manifest

The validated manifest parsed from the file contents.

Source code in src/mkp_forge/manifest.py
117
118
119
120
121
122
123
124
125
126
def read_manifest(manifest_path: Path) -> Manifest:
    """Load and validate a package manifest from a file.

    Args:
        manifest_path (Path): Path to the manifest file.

    Returns:
        Manifest: The validated manifest parsed from the file contents.
    """
    return Manifest.parse_python_string(manifest_path.read_text())

Create and write MKP package archives.

create_mkp(manifest, project_path)

Combines project_path with manifest to create mkp byte

Parameters:

Name Type Description Default
manifest Manifest

Manifest from plugin info file.

required
project_path Path

Project path of Plugin Project.

required

Returns:

Name Type Description
bytes bytes

description

Source code in src/mkp_forge/packaging.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def create_mkp(
    manifest: Manifest,
    project_path: Path,
) -> bytes:
    """Combines project_path with manifest to create mkp byte

    Args:
        manifest (Manifest): Manifest from plugin info file.
        project_path (Path): Project path of Plugin Project.

    Returns:
        bytes: _description_
    """

    return create_tgz((
        ("info", manifest.file_content().encode()),
        ("info.json", manifest.json_file_content().encode()),
        *(
            create_tar(part.ident, project_path / "src", filenames)
            for part, filenames in manifest.files.items()
            if filenames
        ),
    ))

create_tar(name, loc, filenames)

Creates a list of Bytes which represent the content of filenames.

Parameters:

Name Type Description Default
name str

name of tar file

required
loc Path

location of file in project dir

required
filenames Iterable[Path]

names of files from manifest

required

Returns:

Type Description
tuple[str, bytes]

tuple[str, bytes]: group of bytes per filename

Source code in src/mkp_forge/packaging.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def create_tar(name: str, loc: Path, filenames: Iterable[Path]) -> tuple[str, bytes]:
    """Creates a list of Bytes which represent the content of filenames.

    Args:
        name (str): name of tar file
        loc (Path): location of file in project dir
        filenames (Iterable[Path]): names of files from manifest

    Returns:
        tuple[str, bytes]: group of bytes per filename
    """
    tarname = f"{name}.tar"
    _logger.debug("  Packing %(tarname)s:", {"tarname": tarname})

    buffer = BytesIO()
    with tarfile.open(
        fileobj=buffer,
        mode="w",
        format=tarfile.GNU_FORMAT,
    ) as tar:
        tar.dereference = True
        for f in filenames:
            _logger.debug("    %(file)s", {"file": f})
            tar.add(loc / f, arcname=f.as_posix())
    return tarname, buffer.getvalue()

create_tar_info(filename, size)

Creates tar GNU file information

Parameters:

Name Type Description Default
filename str

filename of tarinfo

required
size int

Size of the filename

required

Returns:

Type Description
TarInfo

tarfile.TarInfo: minimalized tar info for tar packaging.

Source code in src/mkp_forge/packaging.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def create_tar_info(filename: str, size: int) -> tarfile.TarInfo:
    """Creates tar GNU file information

    Args:
        filename (str): filename of tarinfo
        size (int): Size of the filename

    Returns:
        tarfile.TarInfo: minimalized tar info for tar packaging.
    """
    info = tarfile.TarInfo()
    info.mtime = int(time.time())
    info.uid = 0
    info.gid = 0
    info.size = size
    info.mode = 0o644
    info.type = tarfile.REGTYPE
    info.name = filename
    return info

create_tgz(files)

Creates tar.gz bytes based on given files.

Parameters:

Name Type Description Default
files Iterable[tuple[str, bytes]]

files which will be included into tar.gz

required

Returns:

Name Type Description
bytes bytes

tar.gz bytes object.

Source code in src/mkp_forge/packaging.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def create_tgz(files: Iterable[tuple[str, bytes]]) -> bytes:
    """Creates tar.gz bytes based on given files.

    Args:
        files (Iterable[tuple[str, bytes]]): files which will be included into tar.gz

    Returns:
        bytes: tar.gz bytes object.
    """
    buffer = BytesIO()
    with tarfile.open(fileobj=buffer, mode="w:gz") as tar:
        for name, content in files:
            tar.addfile(create_tar_info(name, len(content)), BytesIO(content))

    return buffer.getvalue()

write_mkp(manifest, mkp_bytes)

Writes given mkp_bytes to mkp files based on manifest information.

Parameters:

Name Type Description Default
manifest Manifest

Manifest from plugin info file.

required
mkp_bytes bytes

mkp bytes created from manifest.

required
Source code in src/mkp_forge/packaging.py
15
16
17
18
19
20
21
22
23
24
def write_mkp(manifest: Manifest, mkp_bytes: bytes) -> None:
    """Writes given mkp_bytes to mkp files based on manifest information.

    Args:
        manifest (Manifest): Manifest from plugin info file.
        mkp_bytes (bytes): mkp bytes created from manifest.
    """
    mkp_file = Path(f"{manifest.name}-{manifest.version}.mkp")
    with mkp_file.open("wb") as mkp:
        mkp.write(mkp_bytes)