def generate_recipes(facts, prefs): """Generate the selected types of recipes. Args: facts: A continually-updated dictionary containing all the information we know so far about the app associated with the input path. prefs: The dictionary containing a key/value pair for each preference. """ recipes = facts["recipes"] if "app_name" in facts: if not facts["args"].ignore_existing: create_existing_recipe_list(facts) else: raise RoboError("I wasn't able to determine the name of this app, so I " "can't make any recipes.") preferred = [recipe for recipe in recipes if recipe["preferred"]] raise_if_recipes_cannot_be_generated(facts, preferred) # We have enough information to create a recipe set, but with assumptions. # TODO(Elliot): This code may not be necessary if inspections do their job. if "codesign_reqs" not in facts and "codesign_authorities" not in facts: facts["reminders"].append( "I can't tell whether this app is codesigned or not, so I'm " "going to assume it's not. You may want to verify that yourself " "and add the CodeSignatureVerifier processor if necessary." ) facts["codesign_reqs"] = "" facts["codesign_authorities"] = [] if "version_key" not in facts: facts["reminders"].append( "I can't tell whether to use CFBundleShortVersionString or " "CFBundleVersion for the version key of this app. Most apps use " "CFBundleShortVersionString, so that's what I'll use. You may " "want to verify that and modify the recipes if necessary." ) facts["version_key"] = "CFBundleShortVersionString" # TODO(Elliot): Run `autopkg repo-list` once and store the resulting value # for future use when detecting missing required repos, rather than running # `autopkg repo-list` separately during each check. (For example, the # FileWaveImporter repo must be present to run created filewave recipes.) # Prepare the destination directory. # TODO (Shea): This JSS Recipe format code is repeated all over. # Smells like a refactor. if "developer" in facts and prefs.get("FollowOfficialJSSRecipesFormat", False) is not True: recipe_dest_dir = robo_join(prefs["RecipeCreateLocation"], facts["developer"].replace("/", "-")) else: recipe_dest_dir = robo_join(prefs["RecipeCreateLocation"], facts["app_name"].replace("/", "-")) facts["recipe_dest_dir"] = recipe_dest_dir create_dest_dirs(recipe_dest_dir) build_recipes(facts, preferred, prefs) # TODO (Shea): As far as I can tell, the only pref that changes is the # recipe created count. Move out from here! # Save preferences to disk for next time. FoundationPlist.writePlist(prefs, PREFS_FILE)
def write(self, path): """Write the recipe to disk.""" FoundationPlist.writePlist(self["keys"], path)
def write_report(report, report_file): FoundationPlist.writePlist(report, report_file)
def generate_recipes(facts, prefs, recipes): """Generate the selected types of recipes. Args: facts: A continually-updated dictionary containing all the information we know so far about the app associated with the input path. prefs: The dictionary containing a key/value pair for each preference. recipes: The list of known recipe types, created by init_recipes(). """ preferred = [recipe for recipe in recipes if recipe["preferred"]] # No recipe types are preferred. if not preferred: robo_print("Sorry, no recipes available to generate.", LogLevel.ERROR) # We don't have enough information to create a recipe set. if (facts["is_from_app_store"] is False and "sparkle_feed" not in facts and "github_repo" not in facts and "sourceforge_id" not in facts and "download_url" not in facts): robo_print("Sorry, I don't know how to download this app. " "Maybe try another angle? If you provided an app, try " "providing the Sparkle feed for the app instead. Or maybe " "the app's developers offer a direct download URL on their " "website.", LogLevel.ERROR) if (facts["is_from_app_store"] is False and "download_format" not in facts): robo_print("Sorry, I can't tell what format to download this app in. " "Maybe try another angle? If you provided an app, try " "providing the Sparkle feed for the app instead. Or maybe " "the app's developers offer a direct download URL on their " "website.", LogLevel.ERROR) # We have enough information to create a recipe set, but with assumptions. # TODO(Elliot): This code may not be necessary if inspections do their job. if "codesign_reqs" not in facts and "codesign_authorities" not in facts: robo_print("I can't tell whether this app is codesigned or not, so " "I'm going to assume it's not. You may want to verify that " "yourself and add the CodeSignatureVerifier processor if " "necessary.", LogLevel.REMINDER) facts["codesign_reqs"] = "" facts["codesign_authorities"] = [] if "version_key" not in facts: robo_print("I can't tell whether to use CFBundleShortVersionString or " "CFBundleVersion for the version key of this app. Most " "apps use CFBundleShortVersionString, so that's what I'll " "use. You may want to verify that and modify the recipes " "if necessary.", LogLevel.REMINDER) facts["version_key"] = "CFBundleShortVersionString" # TODO(Elliot): Run `autopkg repo-list` once and store the resulting value for # future use when detecting missing required repos, rather than running # `autopkg repo-list` separately during each check. (For example, the # FileWaveImporter repo must be present to run created filewave recipes.) # Prepare the destination directory. if "developer" in facts and prefs.get("FollowOfficialJSSRecipesFormat", False) is not True: recipe_dest_dir = os.path.join(os.path.expanduser(prefs["RecipeCreateLocation"]), facts["developer"].replace("/", "-")) else: recipe_dest_dir = os.path.join(os.path.expanduser(prefs["RecipeCreateLocation"]), facts["app_name"].replace("/", "-")) create_dest_dirs(recipe_dest_dir) # Create a recipe for each preferred type we know about. for recipe in preferred: # TODO (Shea): This could be a global constant. Well, maybe. # Construct the default keys common to all recipes. recipe["keys"] = { "Identifier": "", "MinimumVersion": "0.5.0", "Input": { "NAME": facts["app_name"] }, "Process": [], "Comment": "Created with Recipe Robot v%s " "(https://github.com/homebysix/recipe-robot)" % __version__ } keys = recipe["keys"] # Set the recipe filename (spaces are OK). recipe["filename"] = "%s.%s.recipe" % (facts["app_name"], recipe["type"]) # Set the recipe identifier. keys["Identifier"] = "%s.%s.%s" % (prefs["RecipeIdentifierPrefix"], recipe["type"], facts["app_name"].replace(" ", "")) # If the name of the app bundle differs from the name of the app # itself, we need another input variable for that. if "app_file" in facts: keys["Input"]["APP_FILENAME"] = facts["app_file"] facts["app_name_key"] = "%APP_FILENAME%" else: facts["app_name_key"] = "%NAME%" # Set keys specific to download recipes. if recipe["type"] == "download": generate_download_recipe(facts, prefs, recipe) # Set keys specific to App Store munki overrides. elif recipe["type"] == "munki" and facts["is_from_app_store"] is True: generate_app_store_munki_recipe(facts, prefs, recipe) # Set keys specific to non-App Store munki recipes. elif recipe["type"] == "munki" and facts["is_from_app_store"] is False: generate_munki_recipe(facts, prefs, recipe) # Set keys specific to App Store pkg overrides. elif recipe["type"] == "pkg" and facts["is_from_app_store"] is True: generate_app_store_pkg_recipe(facts, prefs, recipe) # Set keys specific to non-App Store pkg recipes. elif recipe["type"] == "pkg" and facts["is_from_app_store"] is False: generate_pkg_recipe(facts, prefs, recipe) # Set keys specific to install recipes. elif recipe["type"] == "install": generate_install_recipe(facts, prefs, recipe) # Set keys specific to jss recipes. elif recipe["type"] == "jss": generate_jss_recipe(facts, prefs, recipe) # Set keys specific to absolute recipes. elif recipe["type"] == "absolute": generate_absolute_recipe(facts, prefs, recipe) # Set keys specific to sccm recipes. elif recipe["type"] == "sccm": generate_sccm_recipe(facts, prefs, recipe) # Set keys specific to ds recipes. elif recipe["type"] == "ds": generate_ds_recipe(facts, prefs, recipe) # Set keys specific to filewave recipes. elif recipe["type"] == "filewave": generate_filewave_recipe(facts, prefs, recipe) else: # This shouldn't happen, if all the right recipe types are # specified in init_recipes() and also specified above. robo_print("Oops, I think my programmer messed up. I don't " "yet know how to generate a %s recipe. Sorry about " "that." % recipe["type"], LogLevel.WARNING) # Write the recipe to disk. if len(recipe["keys"]["Process"]) > 0: dest_path = os.path.join(recipe_dest_dir, recipe["filename"]) if not os.path.exists(dest_path): # Keep track of the total number of unique recipes we've created. prefs["RecipeCreateCount"] += 1 FoundationPlist.writePlist(recipe["keys"], dest_path) robo_print("%s" % os.path.join(recipe_dest_dir, recipe["filename"]), LogLevel.LOG, 4) facts["recipes"].append(dest_path) # Save preferences to disk for next time. FoundationPlist.writePlist(prefs, prefs_file)