stages: add org.osbuild.ostree.fillvar

Pre-populate /var directory for a given deployment.
This commit is contained in:
Christian Kellner 2021-06-06 14:50:39 +00:00
parent d284bc0ef2
commit 98d0a856df

124
stages/org.osbuild.ostree.fillvar Executable file
View file

@ -0,0 +1,124 @@
#!/usr/bin/python3
"""
Pre-populate /var directory for a given stateroot.
"""
import contextlib
import os
import sys
import subprocess
import osbuild.api
from osbuild.util import ostree
SCHEMA = """
"additionalProperties": false,
"required": ["deployment"],
"properties": {
"deployment": {
"additionalProperties": false,
"required": ["osname", "ref"],
"properties": {
"osname": {
"description": "Name of the stateroot to be used in the deployment",
"type": "string"
},
"ref": {
"description": "OStree ref to create and use for deployment",
"type": "string"
},
"serial": {
"description": "The deployment serial (usually '0')",
"type": "number",
"default": 0
}
}
}
}
"""
class MountGuard(contextlib.AbstractContextManager):
def __init__(self):
self.mounts = []
def mount(self, source, target, bind=True, ro=False, mode="0755"):
options = []
if bind:
options += ["bind"]
if ro:
options += ["ro"]
if mode:
options += [mode]
args = ["--make-private"]
if options:
args += ["-o", ",".join(options)]
subprocess.run(["mount"] + args + [source, target], check=True)
self.mounts += [{"source": source, "target": target}]
subprocess.run(["mount"] + args + [source, target], check=True)
def unmount(self):
while self.mounts:
mount = self.mounts.pop() # FILO: get the last mount
target = mount["target"]
subprocess.run(["umount", "--lazy", target],
check=True)
def __exit__(self, exc_type, exc_val, exc_tb):
self.unmount()
def populate_var(sysroot):
# Like anaconda[1] and Fedora CoreOS dracut[2]
# [1] pyanaconda/payload/rpmostreepayload.py
# [2] ignition-ostree-populate-var.sh
for target in ('lib', 'log'):
os.makedirs(f"{sysroot}/var/{target}", exist_ok=True)
for target in ('home', 'roothome', 'lib/rpm', 'opt', 'srv',
'usrlocal', 'mnt', 'media', 'spool', 'spool/mail'):
if os.path.exists(f"{sysroot}/var/{target}"):
continue
res = subprocess.run(["systemd-tmpfiles", "--create", "--boot",
"--root=" + sysroot,
"--prefix=/var/" + target],
encoding="utf-8",
stdout=sys.stderr,
check=False)
# According to systemd-tmpfiles(8), the return values are:
# 0 → success
# 65 → so some lines had to be ignored, but no other errors
# 73 → configuration ok, but could not be created
# 1 → other error
if res.returncode not in [0, 65]:
raise RuntimeError(f"Failed to provision /var/{target}")
def main(tree, options):
dep = options["deployment"]
osname = dep["osname"]
ref = dep["ref"]
serial = dep.get("serial", 0)
deployment = ostree.deployment_path(tree, osname, ref, serial)
with MountGuard() as mgr:
mgr.mount(f"{deployment}/var", f"{deployment}/var")
populate_var(deployment)
if __name__ == '__main__':
stage_args = osbuild.api.arguments()
r = main(stage_args["tree"],
stage_args["options"])
sys.exit(r)