osbuild
This commit is contained in:
commit
ae1afef209
3 changed files with 156 additions and 0 deletions
70
osbuild
Executable file
70
osbuild
Executable 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
39
stages/io.weldr.anaconda
Executable 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
47
stages/io.weldr.dnf
Executable 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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue