-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathcheck_munkipkg_buildinfo.py
More file actions
executable file
·135 lines (111 loc) · 4.59 KB
/
Copy pathcheck_munkipkg_buildinfo.py
File metadata and controls
executable file
·135 lines (111 loc) · 4.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
51
52
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/python
"""Check MunkiPkg build-info files to ensure they are valid."""
import argparse
import json
import plistlib
from typing import Any
from xml.parsers.expat import ExpatError
import ruamel.yaml
from pre_commit_macadmin_hooks.util import validate_required_keys
yaml = ruamel.yaml.YAML(typ="safe")
def build_argument_parser() -> argparse.ArgumentParser:
"""Build and return the argument parser."""
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"--identifier-prefix",
default="",
help="Expected prefix for package bundle identifiers.",
)
parser.add_argument("filenames", nargs="*", help="Filenames to check.")
return parser
def validate_buildinfo_key_types(buildinfo: dict[str, Any], filename: str) -> bool:
"""Ensure build-info files contain the proper types."""
# Pkginfo keys and their known types. Omitted keys are left unvalidated.
# Source: https://github.com/munki/munki-pkg
# Last updated 2019-06-27.
buildinfo_types = {
"distribution_style": bool,
"identifier": str,
"install_location": str,
"name": str,
"ownership": str,
"postinstall_action": str,
"preserve_xattr": bool,
"product id": str,
"signing_info": dict,
"suppress_bundle_relocation": bool,
"version": str,
}
passed = True
for buildinfo_key, expected_type in buildinfo_types.items():
if buildinfo_key in buildinfo:
if not isinstance(buildinfo[buildinfo_key], expected_type):
print(
f"{filename}: buildinfo key {buildinfo_key} should be type "
f"{expected_type}, not type {type(buildinfo[buildinfo_key])}"
)
passed = False
return passed
def main(argv: list[str] | None = None) -> int:
"""Main process."""
# Parse command line arguments.
argparser = build_argument_parser()
args = argparser.parse_args(argv)
retval = 0
buildinfo = {}
for filename in args.filenames:
if filename.endswith(".plist"):
try:
with open(filename, "rb") as openfile:
buildinfo = plistlib.load(openfile)
except (ExpatError, ValueError) as err:
print(f"{filename}: plist parsing error: {err}")
retval = 1
break # no need to continue testing this file
elif filename.endswith((".yaml", ".yml")):
try:
with open(filename, encoding="utf-8") as openfile:
buildinfo = yaml.load(openfile)
except Exception as err:
print(f"{filename}: yaml parsing error: {err}")
retval = 1
break # no need to continue testing this file
elif filename.endswith(".json"):
try:
with open(filename, encoding="utf-8") as openfile:
buildinfo = json.load(openfile)
except Exception as err:
print(f"{filename}: json parsing error: {err}")
retval = 1
break # no need to continue testing this file
if not buildinfo or not isinstance(buildinfo, dict):
print(f"{filename}: cannot parse build-info file")
retval = 1
break
# Top level keys that all build-info files should contain.
# NOTE: Even though other keys are listed as non-"optional" in the documentation,
# name and version appear to be the only ones that are actually required.
required_keys = ["name", "version"]
if not validate_required_keys(buildinfo, filename, required_keys):
retval = 1
break # No need to continue checking this file
if args.identifier_prefix:
# Warn if the identifier does not start with the expected prefix.
if not buildinfo.get("identifier", "").startswith(args.identifier_prefix):
print(
f"{filename}: identifier does not start with {args.identifier_prefix}."
)
retval = 1
# Ensure buildinfo keys have expected types.
if not validate_buildinfo_key_types(buildinfo, filename):
retval = 1
# Warn if install_location is not the startup disk.
if buildinfo.get("install_location") != "/":
print(
f"{filename}: WARNING: install_location is not set to the startup disk."
)
return retval
if __name__ == "__main__":
exit(main())