Instead of reading the arguments from sys.stdin, which requires that stdin is setup properly for that in the runner, use the new api.arguments() method to directly fetch the arguments. Also fix missing newlines between imports and methods to be more PEP-8 complaint, where needed.
189 lines
5 KiB
Python
Executable file
189 lines
5 KiB
Python
Executable file
#!/usr/bin/python3
|
|
"""
|
|
Assemble a file system tree into a ostree commit
|
|
|
|
Takes a file system tree that is already conforming to the ostree
|
|
system layout[1] and commits it to an archive repository.
|
|
|
|
The repository is located at the `/repo` directory and additional
|
|
metadata is stored in `/compose.json` which contains the commit
|
|
compose information.
|
|
|
|
Alternatively, if the `tar` option is supplied, the repository and
|
|
the `compose.json` will be archived in a file named via the
|
|
`tar.filename` option. This supports auto-compression via the file
|
|
extension (see the tar man page). Requires the `tar` command to be
|
|
in the build root.
|
|
|
|
[1] https://ostree.readthedocs.io/en/stable/manual/adapting-existing/
|
|
"""
|
|
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
from osbuild import api
|
|
from osbuild.util import ostree
|
|
|
|
|
|
SCHEMA = """
|
|
"additionalProperties": false,
|
|
"required": ["ref"],
|
|
"properties": {
|
|
"ref": {
|
|
"description": "OStree ref to create for the commit",
|
|
"type": "string",
|
|
"default": ""
|
|
},
|
|
"os_version": {
|
|
"description": "Set the version of the OS as commit metadata",
|
|
"type": "string"
|
|
},
|
|
"tmp-is-dir": {
|
|
"description": "Create a regular directory for /tmp",
|
|
"type": "boolean",
|
|
"default": true
|
|
},
|
|
"parent": {
|
|
"description": "commit id of the parent commit",
|
|
"type": "string"
|
|
},
|
|
"tar": {
|
|
"description": "Emit a tarball as the result",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["filename"],
|
|
"properties": {
|
|
"filename": {
|
|
"description": "File-name of the tarball to create. Compression is determined by the extension.",
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
|
|
TOPLEVEL_DIRS = ["dev", "proc", "run", "sys", "sysroot", "var"]
|
|
TOPLEVEL_LINKS = {
|
|
"home": "var/home",
|
|
"media": "run/media",
|
|
"mnt": "var/mnt",
|
|
"opt": "var/opt",
|
|
"ostree": "sysroot/ostree",
|
|
"root": "var/roothome",
|
|
"srv": "var/srv",
|
|
}
|
|
|
|
|
|
def copy(name, source, dest):
|
|
subprocess.run(["cp", "--reflink=auto", "-a",
|
|
os.path.join(source, name),
|
|
os.path.join(dest, name)],
|
|
check=True)
|
|
|
|
|
|
def init_rootfs(root, tmp_is_dir):
|
|
"""Initialize a pristine root file-system"""
|
|
|
|
fd = os.open(root, os.O_DIRECTORY)
|
|
|
|
os.fchmod(fd, 0o755)
|
|
|
|
for d in TOPLEVEL_DIRS:
|
|
os.mkdir(d, mode=0o755, dir_fd=fd)
|
|
os.chmod(d, mode=0o755, dir_fd=fd)
|
|
|
|
for l, t in TOPLEVEL_LINKS.items():
|
|
# <dir_fd>/l -> t
|
|
os.symlink(t, l, dir_fd=fd)
|
|
|
|
if tmp_is_dir:
|
|
os.mkdir("tmp", mode=0o1777, dir_fd=fd)
|
|
os.chmod("tmp", mode=0o1777, dir_fd=fd)
|
|
else:
|
|
os.symlink("tmp", "sysroot/tmp", dir_fd=fd)
|
|
|
|
|
|
def main(tree, output_dir, options, meta):
|
|
ref = options["ref"]
|
|
os_version = options.get("os_version", None)
|
|
tmp_is_dir = options.get("tmp-is-dir", True)
|
|
parent = options.get("parent", None)
|
|
tar = options.get("tar", None)
|
|
|
|
with tempfile.TemporaryDirectory(dir=output_dir) as root:
|
|
print("Initializing root filesystem", file=sys.stderr)
|
|
init_rootfs(root, tmp_is_dir)
|
|
|
|
print("Copying data", file=sys.stderr)
|
|
copy("usr", tree, root)
|
|
copy("boot", tree, root)
|
|
copy("var", tree, root)
|
|
|
|
for name in ["bin", "lib", "lib32", "lib64", "sbin"]:
|
|
if os.path.lexists(f"{tree}/{name}"):
|
|
copy(name, tree, root)
|
|
|
|
repo = os.path.join(output_dir, "repo")
|
|
|
|
subprocess.run(["ostree",
|
|
"init",
|
|
"--mode=archive",
|
|
f"--repo={repo}"],
|
|
stdout=sys.stderr,
|
|
check=True)
|
|
|
|
treefile = ostree.Treefile()
|
|
treefile["ref"] = ref
|
|
|
|
argv = ["rpm-ostree", "compose", "commit"]
|
|
argv += [f"--repo={repo}"]
|
|
|
|
if parent:
|
|
argv += [f"--parent={parent}"]
|
|
|
|
if os_version:
|
|
argv += [
|
|
f"--add-metadata-string=version={os_version}",
|
|
]
|
|
|
|
argv += [
|
|
f"--add-metadata-string=rpmostree.inputhash={meta['id']}",
|
|
f"--write-composejson-to={output_dir}/compose.json"
|
|
]
|
|
|
|
with treefile.as_tmp_file() as path:
|
|
argv += [path, root]
|
|
|
|
subprocess.run(argv,
|
|
stdout=sys.stderr,
|
|
check=True)
|
|
|
|
with open(os.path.join(output_dir, "compose.json"), "r") as f:
|
|
compose = json.load(f)
|
|
|
|
api.metadata({"compose": compose})
|
|
|
|
if tar:
|
|
filename = tar["filename"]
|
|
command = [
|
|
"tar",
|
|
"--remove-files",
|
|
"--auto-compress",
|
|
"-cf", os.path.join(output_dir, filename),
|
|
"-C", output_dir,
|
|
"repo", "compose.json"
|
|
]
|
|
subprocess.run(command,
|
|
stdout=sys.stderr,
|
|
check=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = api.arguments()
|
|
r = main(args["tree"], args["output_dir"], args["options"], args["meta"])
|
|
sys.exit(r)
|