stages: add org.osbuild.ostree.fillvar
Pre-populate /var directory for a given deployment.
This commit is contained in:
parent
d284bc0ef2
commit
98d0a856df
1 changed files with 124 additions and 0 deletions
124
stages/org.osbuild.ostree.fillvar
Executable file
124
stages/org.osbuild.ostree.fillvar
Executable 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue