From 76d376fe1807edadcf2d388ae6835190c3ceb18d Mon Sep 17 00:00:00 2001 From: Chris Lumens Date: Tue, 10 Jul 2018 16:59:41 -0400 Subject: [PATCH] Add support for groups to blueprints. Nothing is currently being done with this information, but it will be soon. (cherry picked from commit 0f69d2084cdac3917a0b68515cdad612bf8ff95a) --- docs/lorax-composer.rst | 12 ++++++++++ src/pylorax/api/recipes.py | 29 ++++++++++++++++++++---- tests/pylorax/results/custom-base.dict | 2 +- tests/pylorax/results/full-recipe.dict | 2 +- tests/pylorax/results/minimal.dict | 2 +- tests/pylorax/results/modules-only.dict | 2 +- tests/pylorax/results/packages-only.dict | 2 +- tests/pylorax/test_recipes.py | 16 ++++++------- 8 files changed, 50 insertions(+), 17 deletions(-) diff --git a/docs/lorax-composer.rst b/docs/lorax-composer.rst index b107073d..c219213d 100644 --- a/docs/lorax-composer.rst +++ b/docs/lorax-composer.rst @@ -137,6 +137,18 @@ NOTE: As of lorax-composer-29.2-1 the versions are not used for depsolving, that is planned for a future release. And currently there are no differences between ``packages`` and ``modules`` in ``lorax-composer``. +[[groups]] +~~~~~~~~~~ + +These entries describe a group of packages to be installed into the image. Package groups are +defined in the repository metadata. Each group has a descriptive name used primarily for display +in user interfaces and an ID more commonly used in kickstart files. Here, the ID is the expected +way of listing a group. + +Groups have three different ways of categorizing their packages: mandatory, default, and optional. +For purposes of blueprints, mandatory and default packages will be installed. There is no mechanism +for selecting optional packages. + Customizations ~~~~~~~~~~~~~~ diff --git a/src/pylorax/api/recipes.py b/src/pylorax/api/recipes.py index a14ccbbd..617044f5 100644 --- a/src/pylorax/api/recipes.py +++ b/src/pylorax/api/recipes.py @@ -47,21 +47,24 @@ class Recipe(dict): and adds a .filename property to return the recipe's filename, and a .toml() function to return the recipe as a TOML string. """ - def __init__(self, name, description, version, modules, packages, customizations=None): + def __init__(self, name, description, version, modules, packages, groups, customizations=None): # Check that version is empty or semver compatible if version: semver.Version(version) - # Make sure modules and packages are listed by their case-insensitive names + # Make sure modules, packages, and groups are listed by their case-insensitive names if modules is not None: modules = sorted(modules, key=lambda m: m["name"].lower()) if packages is not None: packages = sorted(packages, key=lambda p: p["name"].lower()) + if groups is not None: + groups = sorted(groups, key=lambda g: g["name"].lower()) dict.__init__(self, name=name, description=description, version=version, modules=modules, packages=packages, + groups=groups, customizations=customizations) # We don't want customizations=None to show up in the TOML so remove it @@ -88,6 +91,11 @@ class Recipe(dict): """Return the names and version globs of the modules""" return [(m["name"], m["version"]) for m in self["modules"] or []] + @property + def group_names(self): + """Return the names of the groups. Groups do not have versions.""" + return map(lambda g: g["name"], self["groups"] or []) + @property def filename(self): """Return the Recipe's filename @@ -144,21 +152,25 @@ class Recipe(dict): """ module_names = self.module_names package_names = self.package_names + group_names = self.group_names new_modules = [] new_packages = [] + new_groups = [] for dep in deps: if dep["name"] in package_names: new_packages.append(RecipePackage(dep["name"], dep_evra(dep))) elif dep["name"] in module_names: new_modules.append(RecipeModule(dep["name"], dep_evra(dep))) + elif dep["name"] in group_names: + new_groups.append(RecipeGroup(dep["name"])) if "customizations" in self: customizations = self["customizations"] else: customizations = None return Recipe(self["name"], self["description"], self["version"], - new_modules, new_packages, customizations) + new_modules, new_packages, new_groups, customizations) class RecipeModule(dict): def __init__(self, name, version): @@ -167,6 +179,10 @@ class RecipeModule(dict): class RecipePackage(RecipeModule): pass +class RecipeGroup(dict): + def __init__(self, name): + dict.__init__(self, name=name) + def recipe_from_file(recipe_path): """Return a recipe file as a Recipe object @@ -210,6 +226,10 @@ def recipe_from_dict(recipe_dict): packages = [RecipePackage(p.get("name"), p.get("version")) for p in recipe_dict["packages"]] else: packages = [] + if recipe_dict.get("groups"): + groups = [RecipeGroup(g.get("name")) for g in recipe_dict["groups"]] + else: + groups = [] name = recipe_dict["name"] description = recipe_dict["description"] version = recipe_dict.get("version", None) @@ -217,7 +237,7 @@ def recipe_from_dict(recipe_dict): except KeyError as e: raise RecipeError("There was a problem parsing the recipe: %s" % str(e)) - return Recipe(name, description, version, modules, packages, customizations) + return Recipe(name, description, version, modules, packages, groups, customizations) def gfile(path): """Convert a string path to GFile for use with Git""" @@ -897,5 +917,6 @@ def recipe_diff(old_recipe, new_recipe): diffs.extend(diff_items("Module", old_recipe["modules"], new_recipe["modules"])) diffs.extend(diff_items("Package", old_recipe["packages"], new_recipe["packages"])) + diffs.extend(diff_items("Group", old_recipe["groups"], new_recipe["groups"])) return diffs diff --git a/tests/pylorax/results/custom-base.dict b/tests/pylorax/results/custom-base.dict index 0a39c626..2604cf34 100644 --- a/tests/pylorax/results/custom-base.dict +++ b/tests/pylorax/results/custom-base.dict @@ -1 +1 @@ -{'name': 'custom-base', 'description': 'A base system with customizations', 'version': '0.0.1', 'modules': [], 'packages': [{'name': 'bash', 'version': '4.4.*'}], 'customizations': {'hostname': 'custombase', 'sshkey': [{'user': 'root', 'key': 'A SSH KEY FOR ROOT'}]}} +{'name': 'custom-base', 'description': 'A base system with customizations', 'version': '0.0.1', 'groups': [], 'modules': [], 'packages': [{'name': 'bash', 'version': '4.4.*'}], 'customizations': {'hostname': 'custombase', 'sshkey': [{'user': 'root', 'key': 'A SSH KEY FOR ROOT'}]}} diff --git a/tests/pylorax/results/full-recipe.dict b/tests/pylorax/results/full-recipe.dict index 082d6de4..23a0ee4f 100644 --- a/tests/pylorax/results/full-recipe.dict +++ b/tests/pylorax/results/full-recipe.dict @@ -1 +1 @@ -{'description': u'An example http server with PHP and MySQL support.', 'packages': [{'version': u'6.6.*', 'name': u'openssh-server'}, {'version': u'3.0.*', 'name': u'rsync'}, {'version': u'2.2', 'name': u'tmux'}], 'modules': [{'version': u'2.4.*', 'name': u'httpd'}, {'version': u'5.4', 'name': u'mod_auth_kerb'}, {'version': u'2.4.*', 'name': u'mod_ssl'}, {'version': u'5.4.*', 'name': u'php'}, {'version': u'5.4.*', 'name': u'php-mysql'}], 'version': u'0.0.1', 'name': u'http-server'} +{'description': u'An example http server with PHP and MySQL support.', 'packages': [{'version': u'6.6.*', 'name': u'openssh-server'}, {'version': u'3.0.*', 'name': u'rsync'}, {'version': u'2.2', 'name': u'tmux'}], 'groups': [], 'modules': [{'version': u'2.4.*', 'name': u'httpd'}, {'version': u'5.4', 'name': u'mod_auth_kerb'}, {'version': u'2.4.*', 'name': u'mod_ssl'}, {'version': u'5.4.*', 'name': u'php'}, {'version': u'5.4.*', 'name': u'php-mysql'}], 'version': u'0.0.1', 'name': u'http-server'} diff --git a/tests/pylorax/results/minimal.dict b/tests/pylorax/results/minimal.dict index 4af1c108..81178d27 100644 --- a/tests/pylorax/results/minimal.dict +++ b/tests/pylorax/results/minimal.dict @@ -1 +1 @@ -{'description': u'An example http server with PHP and MySQL support.', 'packages': [], 'modules': [], 'version': u'0.0.1', 'name': u'http-server'} +{'description': u'An example http server with PHP and MySQL support.', 'packages': [], 'groups': [], 'modules': [], 'version': u'0.0.1', 'name': u'http-server'} diff --git a/tests/pylorax/results/modules-only.dict b/tests/pylorax/results/modules-only.dict index 64b74b31..81185b8c 100644 --- a/tests/pylorax/results/modules-only.dict +++ b/tests/pylorax/results/modules-only.dict @@ -1 +1 @@ -{'description': u'An example http server with PHP and MySQL support.', 'packages': [], 'modules': [{'version': u'2.4.*', 'name': u'httpd'}, {'version': u'5.4', 'name': u'mod_auth_kerb'}, {'version': u'2.4.*', 'name': u'mod_ssl'}, {'version': u'5.4.*', 'name': u'php'}, {'version': u'5.4.*', 'name': u'php-mysql'}], 'version': u'0.0.1', 'name': u'http-server'} +{'description': u'An example http server with PHP and MySQL support.', 'packages': [], 'groups': [], 'modules': [{'version': u'2.4.*', 'name': u'httpd'}, {'version': u'5.4', 'name': u'mod_auth_kerb'}, {'version': u'2.4.*', 'name': u'mod_ssl'}, {'version': u'5.4.*', 'name': u'php'}, {'version': u'5.4.*', 'name': u'php-mysql'}], 'version': u'0.0.1', 'name': u'http-server'} diff --git a/tests/pylorax/results/packages-only.dict b/tests/pylorax/results/packages-only.dict index 8ad5412c..9c542a53 100644 --- a/tests/pylorax/results/packages-only.dict +++ b/tests/pylorax/results/packages-only.dict @@ -1 +1 @@ -{'description': u'An example http server with PHP and MySQL support.', 'packages': [{'version': u'6.6.*', 'name': u'openssh-server'}, {'version': u'3.0.*', 'name': u'rsync'}, {'version': u'2.2', 'name': u'tmux'}], 'modules': [], 'version': u'0.0.1', 'name': u'http-server'} +{'description': u'An example http server with PHP and MySQL support.', 'packages': [{'version': u'6.6.*', 'name': u'openssh-server'}, {'version': u'3.0.*', 'name': u'rsync'}, {'version': u'2.2', 'name': u'tmux'}], 'groups': [], 'modules': [], 'version': u'0.0.1', 'name': u'http-server'} diff --git a/tests/pylorax/test_recipes.py b/tests/pylorax/test_recipes.py index 1b77c800..3d5622f8 100644 --- a/tests/pylorax/test_recipes.py +++ b/tests/pylorax/test_recipes.py @@ -95,27 +95,27 @@ class BasicRecipeTest(unittest.TestCase): """Test the Recipe's version bump function""" # Neither have a version - recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None) + recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None, None) new_version = recipe.bump_version(None) self.assertEqual(new_version, "0.0.1") # Original has a version, new does not - recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None) + recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None, None) new_version = recipe.bump_version("0.0.1") self.assertEqual(new_version, "0.0.2") # Original has no version, new does - recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.0", None, None) + recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.0", None, None, None) new_version = recipe.bump_version(None) self.assertEqual(new_version, "0.1.0") # New and Original are the same - recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.0.1", None, None) + recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.0.1", None, None, None) new_version = recipe.bump_version("0.0.1") self.assertEqual(new_version, "0.0.2") # New is different from Original - recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.1", None, None) + recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.1", None, None, None) new_version = recipe.bump_version("0.0.1") self.assertEqual(new_version, "0.1.1") @@ -136,8 +136,8 @@ class BasicRecipeTest(unittest.TestCase): def recipe_diff_test(self): """Test the recipe_diff function""" - old_recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.1", self.old_modules, self.old_packages) - new_recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.3.1", self.new_modules, self.new_packages) + old_recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.1", self.old_modules, self.old_packages, []) + new_recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.3.1", self.new_modules, self.new_packages, []) result = [{'new': {'Version': '0.3.1'}, 'old': {'Version': '0.1.1'}}, {'new': {'Module': {'name': 'openssh', 'version': '2.8.1'}}, 'old': None}, {'new': None, 'old': {'Module': {'name': 'bash', 'version': '4.*'}}}, @@ -182,7 +182,7 @@ version = "2.7.*" def test_02_commit_recipe(self): """Test committing a Recipe object""" - recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None) + recipe = recipes.Recipe("test-recipe", "A recipe used for testing", None, None, None, None) oid = recipes.commit_recipe(self.repo, "master", recipe) self.assertNotEqual(oid, None)