debian-forge/osbuild.py
Tom Gundersen e9363f96ed osbuild.py: give assemblers a full devtmpfs
This follows what is done for the stages. We intend to look into
making this unnecessary, but for now allow full access so we can
work on the individual assemblers.

Signed-off-by: Tom Gundersen <teg@jklm.no>
2019-06-19 19:49:53 +02:00

164 lines
5.3 KiB
Python

import json
import os
import subprocess
import sys
import tempfile
__all__ = [
"StageFailed",
"BuildRoot",
"tmpfs"
]
libdir = os.path.dirname(__file__)
if not os.path.exists(f"{libdir}/stages"):
libdir = f"{sys.prefix}/lib"
class StageFailed(Exception):
def __init__(self, stage, returncode):
self.stage = stage
self.returncode = returncode
class tmpfs:
def __init__(self, path="/run/osbuild"):
self.root = tempfile.mkdtemp(prefix="osbuild-tmpfs-", dir=path)
self.mounted = False
try:
subprocess.run(["mount", "-t", "tmpfs", "-o", "mode=0755", "tmpfs", self.root], check=True)
self.mounted = True
except subprocess.CalledProcessError:
self.unmount()
def unmount(self):
if not self.root:
return
if self.mounted:
subprocess.run(["umount", "--lazy", self.root], check=True)
os.rmdir(self.root)
self.root = None
def __del__(self):
self.unmount()
def __enter__(self):
return self.root
def __exit__(self, exc_type, exc_value, exc_tb):
self.unmount()
class BuildRoot:
def __init__(self, path="/run/osbuild"):
self.root = tempfile.mkdtemp(prefix="osbuild-buildroot-", dir=path)
self.mounted = False
try:
subprocess.run(["mount", "-o", "bind,ro", "/", self.root], check=True)
self.mounted = True
except subprocess.CalledProcessError:
self.unmount()
raise
# systemd-nspawn silently removes some characters when choosing a
# machine name from the directory name. The only one relevant for
# us is '_', because all other characters used by
# TemporaryDirectory() are allowed. Replace it with 'L's
# (TemporaryDirectory() only uses lower-case characters)
self.machine_name = os.path.basename(self.root).replace("_", "L")
def unmount(self):
if not self.root:
return
if self.mounted:
subprocess.run(["umount", "--lazy", self.root], check=True)
os.rmdir(self.root)
self.root = None
def run(self, argv, binds=[], readonly_binds=[], *args, **kwargs):
return subprocess.run([
"systemd-nspawn",
"--quiet",
"--as-pid2",
"--link-journal=no",
"--volatile=yes",
"--property=DeviceAllow=/dev/loop-control rw",
"--property=DeviceAllow=block-loop rw",
"--property=DeviceAllow=block-blkext rw",
f"--machine={self.machine_name}",
f"--directory={self.root}",
f"--bind={libdir}/osbuild-run:/run/osbuild/osbuild-run",
*[f"--bind={b}" for b in binds],
*[f"--bind-ro={b}" for b in readonly_binds],
"/run/osbuild/osbuild-run",
] + argv, *args, **kwargs)
def _get_system_resources_from_etc(self, stage_or_assembler):
resources = stage_or_assembler.get("systemResourcesFromEtc", [])
for r in resources:
if not r.startswith("/etc"):
raise ValueError(f"{r} is not a resource in /etc/")
if ":" in r:
raise ValueError(f"{r} tries to bind to a different location")
return resources
def run_stage(self, stage, tree, input_dir=None):
name = stage["name"]
args = {
"tree": "/run/osbuild/tree",
"options": stage.get("options", {})
}
robinds = [f"{libdir}/stages/{name}:/run/osbuild/{name}"]
robinds.extend(self._get_system_resources_from_etc(stage))
binds = [f"{tree}:/run/osbuild/tree", "/dev:/dev"]
if input_dir:
robinds.append(f"{input_dir}:/run/osbuild/input")
args["input_dir"] = "/run/osbuild/input"
try:
self.run([f"/run/osbuild/{name}"], binds=binds, readonly_binds=robinds, input=json.dumps(args), encoding="utf-8", check=True)
except subprocess.CalledProcessError as error:
raise StageFailed(name, error.returncode)
def run_assembler(self, assembler, tree, input_dir=None, output_dir=None):
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
name = assembler["name"]
args = {
"tree": "/run/osbuild/tree",
"options": assembler.get("options", {}),
}
robinds = [
f"{tree}:/run/osbuild/tree",
f"{libdir}/assemblers/{name}:/run/osbuild/{name}"
]
robinds.extend(self._get_system_resources_from_etc(assembler))
binds = ["/dev:/dev"]
if input_dir:
robinds.append(f"{input_dir}:/run/osbuild/input")
args["input_dir"] = "/run/osbuild/input"
if output_dir:
binds.append(f"{output_dir}:/run/osbuild/output")
args["output_dir"] = "/run/osbuild/output"
try:
self.run([f"/run/osbuild/{name}"], binds=binds, readonly_binds=robinds, input=json.dumps(args), encoding="utf-8", check=True)
except subprocess.CalledProcessError as error:
raise StageFailed(name, error.returncode)
def __del__(self):
self.unmount()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.unmount()