diff --git a/docs/docs/administration/images.md b/docs/docs/administration/images.md index ac09288..7ef6942 100644 --- a/docs/docs/administration/images.md +++ b/docs/docs/administration/images.md @@ -24,6 +24,7 @@ The following parameters can be used to define a recipe: | package | A string or list of strings of package names to install | | file | Copy a file from the management node into the image. A string specifies a common source and destination. A mapping with `src` and `dst` can specify different paths | | command | Run a command inside the image | +| osrelease | Write `IMAGE_ID` (recipe name) and `IMAGE_VERSION` (build tag) to `/etc/os-release`. Accepts a boolean to enable `IMAGE_ID` and `IMAGE_VERSION`, or a dictionary of additional os-release fields. `IMAGE_ID` and `IMAGE_VERSION` is always added. | ### Artifact Types @@ -57,6 +58,7 @@ steps: src: /root/hosts.mycluster dst: /etc/hosts - command: systemd-firstboot --timezone=America/New_York --locale=en_US.UTF-8 --locale-messages=en_US.UTF-8 + - osrelease: true artifacts: - squashfs: diff --git a/lib/phoenix/recipe.py b/lib/phoenix/recipe.py index 9e7e64a..80a36d7 100755 --- a/lib/phoenix/recipe.py +++ b/lib/phoenix/recipe.py @@ -148,6 +148,8 @@ def load_recipe(self, name): self.steps.append(StepPackage(step['package'])) elif steptype == 'file': self.steps.append(StepFile(step['file'])) + elif steptype == 'osrelease': + self.steps.append(StepOsRelease(step['osrelease'])) else: self.steps.append(step) elif key == "artifacts": @@ -443,6 +445,49 @@ def run(self, recipe): logging.error("Could not copy file %s to %s", self.src, self.dst) raise RuntimeError +class StepOsRelease(Step): + name = 'OsRelease' + + def __init__(self, params): + # Optional mapping of additional os-release fields to set. IMAGE_ID + # and IMAGE_VERSION are always set from the recipe name and tag. + self.extra = dict() + if type(params) is dict: + self.extra = dict(params) + + def __str__(self): + result = "IMAGE_ID= IMAGE_VERSION=" + if self.extra: + result += " " + " ".join("%s=%s" % (k, v) for k, v in self.extra.items()) + return result + + def run(self, recipe): + osrelease = Path(recipe.root) / 'etc' / 'os-release' + fields = dict(self.extra) + fields['IMAGE_ID'] = recipe.name + fields['IMAGE_VERSION'] = recipe.tag + logging.info("Setting %s in %s", + " ".join("%s=%s" % (k, v) for k, v in fields.items()), + osrelease) + + lines = [] + if osrelease.is_file(): + lines = osrelease.read_text().splitlines() + + # Drop any pre-existing lines for the fields we are about to set + lines = [l for l in lines + if not any(l.startswith("%s=" % key) for key in fields)] + + for key, value in fields.items(): + lines.append('%s="%s"' % (key, value)) + + try: + osrelease.parent.mkdir(parents=True, exist_ok=True) + osrelease.write_text('\n'.join(lines) + '\n') + except OSError as e: + logging.error("Could not write %s: %s", osrelease, e) + raise RuntimeError + class Artifact(object): pass