Created
March 1, 2021 18:03
-
-
Save homebysix/9ac9f1fc0e48b36c1367f6ab1b561efe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/autopkg/python | |
import os | |
import plistlib | |
from glob import glob | |
from Foundation import CFPreferencesCopyAppValue | |
# Input keys we don't care about comparing. | |
EXCLUDED_KEYS = ( | |
"MUNKI_CATEGORY", | |
"MUNKI_REPO_SUBDIR", | |
"NAME", | |
) | |
EXCLUDED_OVERRIDES = () | |
def get_identifier(recipe): | |
"""Return identifier from recipe dict. Tries the Identifier | |
top-level key and falls back to the legacy key location. | |
Source: https://github.com/autopkg/autopkg/blob/8b8a008bdc5d4b309050394a84ebb505d10dc68c/Code/autopkglib/__init__.py#L281 | |
""" | |
try: | |
return recipe["Identifier"] | |
except (KeyError, AttributeError): | |
try: | |
return recipe["Input"]["IDENTIFIER"] | |
except (KeyError, AttributeError): | |
return None | |
except TypeError: | |
return None | |
def get_recipe_index(): | |
"""Return a tuple of tuples representing the recipe paths and their identifiers on this Mac.""" | |
# Determine search paths for recipes. | |
recipe_search_dirs = CFPreferencesCopyAppValue( | |
"RECIPE_SEARCH_DIRS", "com.github.autopkg" | |
) | |
# Glob the search paths and add any found recipes to a recipe list. | |
recipes = [] | |
for search_dir in recipe_search_dirs: | |
recipes.extend(glob(os.path.join(search_dir, "*.recipe"))) | |
recipes.extend(glob(os.path.join(search_dir, "*/*.recipe"))) | |
recipes = list(set(recipes)) | |
# For each recipe in the list, determine an identifier and build an index. | |
recipe_index = {} | |
for recipe in recipes: | |
with open(recipe, "rb") as openfile: | |
recipe_data = plistlib.load(openfile) | |
recipe_index[get_identifier(recipe_data)] = recipe | |
return recipe_index | |
def get_override_list(): | |
override_dirs = CFPreferencesCopyAppValue( | |
"RECIPE_OVERRIDE_DIRS", "com.github.autopkg" | |
) | |
if not override_dirs: | |
override_dirs = ["~/Library/AutoPkg/RecipeOverrides"] | |
elif isinstance(override_dirs, str): | |
override_dirs = [override_dirs] | |
overrides = [] | |
for override_dir in override_dirs: | |
overrides.extend( | |
glob(os.path.join(override_dir, "*.recipe")) | |
+ glob(os.path.join(override_dir, "*/*.recipe")) | |
) | |
return overrides | |
def detect_differences(override, recipe_index): | |
# Skip excluded filenames. | |
filename = os.path.split(override)[-1].replace(".recipe", "") | |
if filename in EXCLUDED_OVERRIDES: | |
return | |
with open(override, "rb") as openfile: | |
override_data = plistlib.load(openfile) | |
parent_recipe = override_data.get("ParentRecipe") | |
if not recipe_index.get(parent_recipe) or not os.path.isfile( | |
recipe_index[parent_recipe] | |
): | |
print( | |
"%s: Could not find parent recipe with identifier %s. Skipping." | |
% (override_data["Identifier"], parent_recipe) | |
) | |
return | |
with open(recipe_index[parent_recipe], "rb") as openfile: | |
parent_data = plistlib.load(openfile) | |
announced = False | |
pkginfo = False | |
for key in override_data.get("Input"): | |
# Pkginfos must be processed in their own special way, below. | |
if key == "pkginfo": | |
# pkginfo = True | |
continue | |
# Skip any key we don't care about. | |
if key in EXCLUDED_KEYS: | |
continue | |
# Gather override and parent input key values. | |
o_value = override_data["Input"].get(key) | |
p_value = parent_data["Input"].get(key) | |
# If there's no parent value, check the parent's parent. | |
if not p_value: | |
gparent = parent_data.get("ParentRecipe") | |
if gparent and os.path.isfile(recipe_index[gparent]): | |
with open(recipe_index[gparent], "rb") as openfile: | |
gparent_data = plistlib.load(openfile) | |
p_value = gparent_data["Input"].get(key) | |
if not p_value: | |
ggparent = parent_data.get("ParentRecipe") | |
if ggparent and os.path.isfile(recipe_index[ggparent]): | |
with open(recipe_index[ggparent], "rb") as openfile: | |
ggparent_data = plistlib.load(openfile) | |
p_value = ggparent_data["Input"].get(key) | |
# TODO: Does this actually work? | |
# Skip any Adobe CC description placeholder. | |
if p_value == "Some description.": | |
continue | |
# Compare the values. | |
if o_value != p_value: | |
if not announced: | |
print("\n%s\nOverride file: %s" % ("-" * 80, override)) | |
announced = True | |
print("\n %s differs:" % key) | |
print(" Override value: %s" % o_value) | |
print(" Parent value: %s" % p_value) | |
# Process pkginfo dicts similarly to the above. | |
if pkginfo: | |
for key in override_data["Input"]["pkginfo"]: | |
if key in EXCLUDED_KEYS: | |
continue | |
o_value = override_data["Input"]["pkginfo"].get(key) | |
p_value = parent_data["Input"]["pkginfo"].get(key) | |
if o_value != p_value: | |
if not announced: | |
print("\nOverride file: %s" % override) | |
announced = True | |
print(" pkginfo/%s differs:" % key) | |
print(" Override value: %s" % o_value) | |
print(" Parent value: %s" % p_value) | |
def main(): | |
"""Main process.""" | |
recipe_index = get_recipe_index() | |
overrides = sorted(get_override_list()) | |
for override in overrides: | |
detect_differences(override, recipe_index) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment