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
df63cfddc5
commit
8839182f43
@ -83,6 +83,33 @@ def repo_to_ks(r, url="url"):
|
|||||||
|
|
||||||
return cmd
|
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, dnflock, gitlock, branch, recipe_name, compose_type, test_mode=0):
|
def start_build(cfg, dnflock, gitlock, branch, recipe_name, compose_type, test_mode=0):
|
||||||
""" Start the build
|
""" Start the build
|
||||||
|
|
||||||
@ -193,9 +220,10 @@ def start_build(cfg, dnflock, gitlock, branch, recipe_name, compose_type, test_m
|
|||||||
|
|
||||||
for d in deps:
|
for d in deps:
|
||||||
f.write(dep_nevra(d)+"\n")
|
f.write(dep_nevra(d)+"\n")
|
||||||
|
|
||||||
f.write("%end\n")
|
f.write("%end\n")
|
||||||
|
|
||||||
|
add_customizations(f, recipe)
|
||||||
|
|
||||||
# Setup the config to pass to novirt_install
|
# Setup the config to pass to novirt_install
|
||||||
log_dir = joinpaths(results_dir, "logs/")
|
log_dir = joinpaths(results_dir, "logs/")
|
||||||
cfg_args = compose_args(compose_type)
|
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 adds a .filename property to return the recipe's filename,
|
||||||
and a .toml() function to return the recipe as a TOML string.
|
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
|
# Check that version is empty or semver compatible
|
||||||
if version:
|
if version:
|
||||||
semver.Version(version)
|
semver.Version(version)
|
||||||
@ -61,7 +61,12 @@ class Recipe(dict):
|
|||||||
description=description,
|
description=description,
|
||||||
version=version,
|
version=version,
|
||||||
modules=modules,
|
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
|
@property
|
||||||
def package_names(self):
|
def package_names(self):
|
||||||
@ -137,9 +142,13 @@ class Recipe(dict):
|
|||||||
new_packages.append(RecipePackage(dep["name"], dep_evra(dep)))
|
new_packages.append(RecipePackage(dep["name"], dep_evra(dep)))
|
||||||
elif dep["name"] in module_names:
|
elif dep["name"] in module_names:
|
||||||
new_modules.append(RecipeModule(dep["name"], dep_evra(dep)))
|
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"],
|
return Recipe(self["name"], self["description"], self["version"],
|
||||||
new_modules, new_packages)
|
new_modules, new_packages, customizations)
|
||||||
|
|
||||||
class RecipeModule(dict):
|
class RecipeModule(dict):
|
||||||
def __init__(self, name, version):
|
def __init__(self, name, version):
|
||||||
@ -194,10 +203,11 @@ def recipe_from_dict(recipe_dict):
|
|||||||
name = recipe_dict["name"]
|
name = recipe_dict["name"]
|
||||||
description = recipe_dict["description"]
|
description = recipe_dict["description"]
|
||||||
version = recipe_dict.get("version", None)
|
version = recipe_dict.get("version", None)
|
||||||
|
customizations = recipe_dict.get("customizations", None)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise RecipeError("There was a problem parsing the recipe: %s" % str(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):
|
def gfile(path):
|
||||||
"""Convert a string path to GFile for use with Git"""
|
"""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"),
|
input_recipes = [("full-recipe.toml", "full-recipe.dict"),
|
||||||
("minimal.toml", "minimal.dict"),
|
("minimal.toml", "minimal.dict"),
|
||||||
("modules-only.toml", "modules-only.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/"
|
results_path = "./tests/pylorax/results/"
|
||||||
self.input_toml = []
|
self.input_toml = []
|
||||||
for (recipe_toml, recipe_dict) in input_recipes:
|
for (recipe_toml, recipe_dict) in input_recipes:
|
||||||
|
@ -54,6 +54,7 @@ class ServerTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
server.config['TESTING'] = True
|
server.config['TESTING'] = True
|
||||||
self.server = server.test_client()
|
self.server = server.test_client()
|
||||||
|
self.repo_dir = repo_dir
|
||||||
|
|
||||||
self.examples_path = "./tests/pylorax/blueprints/"
|
self.examples_path = "./tests/pylorax/blueprints/"
|
||||||
|
|
||||||
@ -81,8 +82,8 @@ class ServerTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def test_02_blueprints_list(self):
|
def test_02_blueprints_list(self):
|
||||||
"""Test the /api/v0/blueprints/list route"""
|
"""Test the /api/v0/blueprints/list route"""
|
||||||
list_dict = {"blueprints":["atlas", "development", "glusterfs", "http-server", "jboss", "kubernetes"],
|
list_dict = {"blueprints":["atlas", "custom-base", "development", "glusterfs", "http-server",
|
||||||
"limit":20, "offset":0, "total":6}
|
"jboss", "kubernetes"], "limit":20, "offset":0, "total":7}
|
||||||
resp = self.server.get("/api/v0/blueprints/list")
|
resp = self.server.get("/api/v0/blueprints/list")
|
||||||
data = json.loads(resp.data)
|
data = json.loads(resp.data)
|
||||||
self.assertEqual(data, list_dict)
|
self.assertEqual(data, list_dict)
|
||||||
@ -738,7 +739,7 @@ class ServerTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def test_compose_12_create_finished(self):
|
def test_compose_12_create_finished(self):
|
||||||
"""Test the /api/v0/compose routes with a finished test compose"""
|
"""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",
|
"compose_type": "tar",
|
||||||
"branch": "master"}
|
"branch": "master"}
|
||||||
|
|
||||||
@ -805,6 +806,14 @@ class ServerTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(len(resp.data) > 0, True)
|
self.assertEqual(len(resp.data) > 0, True)
|
||||||
self.assertEqual(resp.data, b"TEST IMAGE")
|
self.assertEqual(resp.data, b"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
|
# Delete the finished build
|
||||||
# Test the /api/v0/compose/delete/<uuid> route
|
# Test the /api/v0/compose/delete/<uuid> route
|
||||||
resp = self.server.delete("/api/v0/compose/delete/%s" % build_id)
|
resp = self.server.delete("/api/v0/compose/delete/%s" % build_id)
|
||||||
|
Loading…
Reference in New Issue
Block a user