Add blueprint customization support for hostname and ssh key
This adds support for the optional blueprint section [customizations]. Use it like this: [customizations] hostname = yourhostnamehere [[customizations.sshkey]] user = root key = root user key
This commit is contained in:
		
							parent
							
								
									ffc3195d77
								
							
						
					
					
						commit
						ccafa76019
					
				| @ -91,6 +91,33 @@ def repo_to_ks(r, url="url"): | ||||
| 
 | ||||
|     return cmd | ||||
| 
 | ||||
| 
 | ||||
| def add_customizations(f, recipe): | ||||
|     """ Add customizations to the kickstart file | ||||
| 
 | ||||
|     :param f: kickstart file object | ||||
|     :type f: open file object | ||||
|     :param recipe: | ||||
|     :type recipe: Recipe object | ||||
|     :returns: None | ||||
|     :raises: RuntimeError if there was a problem writing to the kickstart | ||||
|     """ | ||||
|     if "customizations" not in recipe: | ||||
|         return | ||||
|     customizations = recipe["customizations"] | ||||
| 
 | ||||
|     if "hostname" in customizations: | ||||
|         f.write("network --hostname=%s\n" % customizations["hostname"]) | ||||
| 
 | ||||
|     if "sshkey" in customizations: | ||||
|         # This is a list of entries | ||||
|         for sshkey in customizations["sshkey"]: | ||||
|             if "user" not in sshkey or "key" not in sshkey: | ||||
|                 log.error("%s is incorrect, skipping", sshkey) | ||||
|                 continue | ||||
|             f.write('sshkey --user %s "%s"' % (sshkey["user"], sshkey["key"])) | ||||
| 
 | ||||
| 
 | ||||
| def start_build(cfg, yumlock, gitlock, branch, recipe_name, compose_type, test_mode=0): | ||||
|     """ Start the build | ||||
| 
 | ||||
| @ -201,9 +228,10 @@ def start_build(cfg, yumlock, gitlock, branch, recipe_name, compose_type, test_m | ||||
| 
 | ||||
|         for d in deps: | ||||
|             f.write(dep_nevra(d)+"\n") | ||||
| 
 | ||||
|         f.write("%end\n") | ||||
| 
 | ||||
|         add_customizations(f, recipe) | ||||
| 
 | ||||
|     # Setup the config to pass to novirt_install | ||||
|     log_dir = joinpaths(results_dir, "logs/") | ||||
|     cfg_args = compose_args(compose_type) | ||||
|  | ||||
| @ -47,7 +47,7 @@ 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): | ||||
|     def __init__(self, name, description, version, modules, packages, customizations=None): | ||||
|         # Check that version is empty or semver compatible | ||||
|         if version: | ||||
|             semver.Version(version) | ||||
| @ -61,7 +61,12 @@ class Recipe(dict): | ||||
|                             description=description, | ||||
|                             version=version, | ||||
|                             modules=modules, | ||||
|                             packages=packages) | ||||
|                             packages=packages, | ||||
|                             customizations=customizations) | ||||
| 
 | ||||
|         # We don't want customizations=None to show up in the TOML so remove it | ||||
|         if customizations is None: | ||||
|             del self["customizations"] | ||||
| 
 | ||||
|     @property | ||||
|     def package_names(self): | ||||
| @ -137,9 +142,13 @@ class Recipe(dict): | ||||
|                 new_packages.append(RecipePackage(dep["name"], dep_evra(dep))) | ||||
|             elif dep["name"] in module_names: | ||||
|                 new_modules.append(RecipeModule(dep["name"], dep_evra(dep))) | ||||
|         if "customizations" in self: | ||||
|             customizations = self["customizations"] | ||||
|         else: | ||||
|             customizations = None | ||||
| 
 | ||||
|         return Recipe(self["name"], self["description"], self["version"], | ||||
|                       new_modules, new_packages) | ||||
|                       new_modules, new_packages, customizations) | ||||
| 
 | ||||
| class RecipeModule(dict): | ||||
|     def __init__(self, name, version): | ||||
| @ -194,10 +203,11 @@ def recipe_from_dict(recipe_dict): | ||||
|         name = recipe_dict["name"] | ||||
|         description = recipe_dict["description"] | ||||
|         version = recipe_dict.get("version", None) | ||||
|         customizations = recipe_dict.get("customizations", None) | ||||
|     except KeyError as e: | ||||
|         raise RecipeError("There was a problem parsing the recipe: %s" % str(e)) | ||||
| 
 | ||||
|     return Recipe(name, description, version, modules, packages) | ||||
|     return Recipe(name, description, version, modules, packages, customizations) | ||||
| 
 | ||||
| def gfile(path): | ||||
|     """Convert a string path to GFile for use with Git""" | ||||
|  | ||||
							
								
								
									
										14
									
								
								tests/pylorax/blueprints/custom-base.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tests/pylorax/blueprints/custom-base.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| name = "custom-base" | ||||
| description = "A base system with customizations" | ||||
| version = "0.0.1" | ||||
| 
 | ||||
| [[packages]] | ||||
| name = "bash" | ||||
| version = "4.4.*" | ||||
| 
 | ||||
| [customizations] | ||||
| hostname = "custombase" | ||||
| 
 | ||||
| [[customizations.sshkey]] | ||||
| user = "root" | ||||
| key = "A SSH KEY FOR ROOT" | ||||
							
								
								
									
										1
									
								
								tests/pylorax/results/custom-base.dict
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/pylorax/results/custom-base.dict
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +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'}]}} | ||||
							
								
								
									
										14
									
								
								tests/pylorax/results/custom-base.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tests/pylorax/results/custom-base.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| name = "custom-base" | ||||
| description = "A base system with customizations" | ||||
| version = "0.0.1" | ||||
| 
 | ||||
| [[packages]] | ||||
| name = "bash" | ||||
| version = "4.4.*" | ||||
| 
 | ||||
| [customizations] | ||||
| hostname = "custombase" | ||||
| 
 | ||||
| [[customizations.sshkey]] | ||||
| user = "root" | ||||
| key = "A SSH KEY FOR ROOT" | ||||
| @ -31,7 +31,8 @@ class BasicRecipeTest(unittest.TestCase): | ||||
|         input_recipes = [("full-recipe.toml", "full-recipe.dict"), | ||||
|                          ("minimal.toml", "minimal.dict"), | ||||
|                          ("modules-only.toml", "modules-only.dict"), | ||||
|                          ("packages-only.toml", "packages-only.dict")] | ||||
|                          ("packages-only.toml", "packages-only.dict"), | ||||
|                          ("custom-base.toml", "custom-base.dict")] | ||||
|         results_path = "./tests/pylorax/results/" | ||||
|         self.input_toml = [] | ||||
|         for (recipe_toml, recipe_dict) in input_recipes: | ||||
|  | ||||
| @ -52,6 +52,7 @@ class ServerTestCase(unittest.TestCase): | ||||
| 
 | ||||
|         server.config['TESTING'] = True | ||||
|         self.server = server.test_client() | ||||
|         self.repo_dir = repo_dir | ||||
| 
 | ||||
|         self.examples_path = "./tests/pylorax/blueprints/" | ||||
| 
 | ||||
| @ -79,8 +80,8 @@ class ServerTestCase(unittest.TestCase): | ||||
| 
 | ||||
|     def test_02_blueprints_list(self): | ||||
|         """Test the /api/v0/blueprints/list route""" | ||||
|         list_dict = {"blueprints":["atlas", "development", "glusterfs", "http-server", "jboss", "kubernetes"], | ||||
|                      "limit":20, "offset":0, "total":6} | ||||
|         list_dict = {"blueprints":["atlas", "custom-base", "development", "glusterfs", "http-server", | ||||
|                      "jboss", "kubernetes"], "limit":20, "offset":0, "total":7} | ||||
|         resp = self.server.get("/api/v0/blueprints/list") | ||||
|         data = json.loads(resp.data) | ||||
|         self.assertEqual(data, list_dict) | ||||
| @ -728,7 +729,7 @@ class ServerTestCase(unittest.TestCase): | ||||
| 
 | ||||
|     def test_compose_12_create_finished(self): | ||||
|         """Test the /api/v0/compose routes with a finished test compose""" | ||||
|         test_compose = {"blueprint_name": "glusterfs", | ||||
|         test_compose = {"blueprint_name": "custom-base", | ||||
|                         "compose_type": "tar", | ||||
|                         "branch": "master"} | ||||
| 
 | ||||
| @ -795,6 +796,14 @@ class ServerTestCase(unittest.TestCase): | ||||
|         self.assertEqual(len(resp.data) > 0, True) | ||||
|         self.assertEqual(resp.data, "TEST IMAGE") | ||||
| 
 | ||||
|         # Examine the final-kickstart.ks for the customizations | ||||
|         # A bit kludgy since it examines the filesystem directly, but that's better than unpacking the metadata | ||||
|         final_ks = open(joinpaths(self.repo_dir, "var/lib/lorax/composer/results/", build_id, "final-kickstart.ks")).read() | ||||
| 
 | ||||
|         # Check for the expected customizations in the kickstart | ||||
|         self.assertTrue("network --hostname=" in final_ks) | ||||
|         self.assertTrue("sshkey --user root" in final_ks) | ||||
| 
 | ||||
|         # Delete the finished build | ||||
|         # Test the /api/v0/compose/delete/<uuid> route | ||||
|         resp = self.server.delete("/api/v0/compose/delete/%s" % build_id) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user