This commit is contained in:
Lars Karlitski 2019-01-15 15:54:52 +01:00
commit ae1afef209
3 changed files with 156 additions and 0 deletions

70
osbuild Executable file
View file

@ -0,0 +1,70 @@
#!/usr/bin/python3
import argparse
import contextlib
import json
import os
import subprocess
import sys
import tempfile
RESET = "\033[0m"
BOLD = "\033[1m"
RED = "\033[31m"
@contextlib.contextmanager
def tmpfs():
"""A contextmanager that mounts a tmpfs and returns its location."""
with tempfile.TemporaryDirectory(prefix="osbuild-tree-", dir=os.getcwd()) as path:
subprocess.run(["mount", "-t", "tmpfs", "tmpfs", path], check=True)
try:
yield path
finally:
subprocess.run(["umount", path], check=True)
def main(pipeline_path):
with open(pipeline_path) as f:
pipeline = json.load(f)
with tmpfs() as tree:
for i, stage in enumerate(pipeline["stages"], start=1):
name = stage["name"]
options = stage.get("options", {})
options["tree"] = os.path.abspath(tree)
options_str = json.dumps(options, indent=2)
print(f"{RESET}{BOLD}{i}. {name}{RESET} {options_str}")
print()
script = f"""
set -e
mount -t tmpfs tmpfs /tmp
mount -t tmpfs tmpfs /var/tmp
stages/{name}
"""
try:
subprocess.run(["unshare", "--pid", "--kill-child", "--mount", "sh", "-c", script],
input=options_str, encoding="utf-8", check=True)
except KeyboardInterrupt:
print()
print(f"{RESET}{BOLD}{RED}Aborted{RESET}")
return 1
except subprocess.CalledProcessError as error:
print()
print(f"{RESET}{BOLD}{RED}{name} failed with code {error.returncode}{RESET}")
return 1
print()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Build operating system images")
parser.add_argument("pipeline_path", metavar="pipeline", help="json file containing the pipeline that should be built")
args = parser.parse_args()
sys.exit(main(**vars(args)))

39
stages/io.weldr.anaconda Executable file
View file

@ -0,0 +1,39 @@
#!/usr/bin/python3
import json
import os
import subprocess
import sys
import tempfile
def main(tree, kickstart, skip_package_install=False):
with open("/tmp/kickstart.ks", "w") as f:
if skip_package_install:
subprocess.run(["tar", "cvf", "/tmp/empty.tar", "--files-from", "/dev/null"])
f.write(f"liveimg --url=file:///tmp/empty.tar\n")
f.write(kickstart)
cmd = [
"anaconda",
"--cmdline",
"--loglevel", "debug",
"--kickstart", "/tmp/kickstart.ks",
"--dirinstall", tree
]
print(" ".join(cmd), flush=True)
returncode = subprocess.run(cmd).returncode
if returncode != 0:
print("\n=== anaconda.log" + "=" * 50)
with open("/tmp/anaconda.log") as f:
print(f.read())
if skip_package_install:
os.unlink("/tmp/empty.tar")
os.unlink("/tmp/kickstart.ks")
return returncode
if __name__ == '__main__':
options = json.load(sys.stdin)
sys.exit(main(**options))

47
stages/io.weldr.dnf Executable file
View file

@ -0,0 +1,47 @@
#!/usr/bin/python3
import atexit
import configparser
import json
import os
import subprocess
import sys
import tempfile
def bindmount(source, dest):
os.makedirs(source, 0o755, True)
os.makedirs(dest, 0o755, True)
subprocess.run(["mount", "--bind", source, dest], check=True)
atexit.register(lambda: subprocess.run(["umount", dest]))
def main(tree, repos, packages, releasever, cache=None):
with tempfile.NamedTemporaryFile(mode="w", prefix="dnf-", suffix=".conf", delete=False) as dnfconfig:
p = configparser.ConfigParser()
p.read_dict(repos)
p.write(dnfconfig)
atexit.register(lambda: os.unlink(dnfconfig.name))
bindmount("/proc", f"{tree}/proc")
bindmount("/sys", f"{tree}/sys")
bindmount("/dev", f"{tree}/dev")
if cache:
bindmount(cache, f"{tree}/var/cache/dnf")
cmd = [
"dnf", "-yv",
"--installroot", tree,
"--setopt", "reposdir=",
"--setopt", "keepcache=True",
"--setopt", "install_weak_deps=False",
"--releasever", releasever,
"--config", dnfconfig.name,
"install"
] + packages
print(" ".join(cmd), flush=True)
return subprocess.run(cmd).returncode
if __name__ == '__main__':
options = json.load(sys.stdin)
sys.exit(main(**options))