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.
111 lines
3.3 KiB
Python
Executable file
111 lines
3.3 KiB
Python
Executable file
#!/usr/bin/python3
|
|
"""
|
|
Assemble tree into a raw filesystem image
|
|
|
|
Assemble the tree into a raw filesystem image named `filename`, with the UUID
|
|
`root_fs_uuid`.
|
|
|
|
The image is a sparse file of the given `size`, which is created using the
|
|
`truncate(1)` command. The `size` is an integer with an optional suffix:
|
|
K,M,G,T,... (for powers of 1024) or KB,MB,GB,TB,... (powers of 1000).
|
|
|
|
NOTE: If the tree contents are larger than `size`, this assembler will fail.
|
|
On the other hand, since the image is a sparse file, the unused parts of the
|
|
image take up almost no disk space - so a 1GB tree in a 20GB image should not
|
|
use much more than 1GB disk space.
|
|
|
|
The filesystem UUID should be a standard (RFC4122) UUID, which you can
|
|
generate with uuid.uuid4() in Python, `uuidgen(1)` in a shell script, or
|
|
read from `/proc/sys/kernel/random/uuid` if your kernel provides it.
|
|
"""
|
|
|
|
|
|
import contextlib
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
import osbuild.api
|
|
import osbuild.remoteloop as remoteloop
|
|
|
|
|
|
SCHEMA = """
|
|
"additionalProperties": false,
|
|
"required": ["filename", "root_fs_uuid", "size"],
|
|
"properties": {
|
|
"filename": {
|
|
"description": "Raw filesystem image filename",
|
|
"type": "string"
|
|
},
|
|
"root_fs_uuid": {
|
|
"description": "UUID for the filesystem",
|
|
"type": "string",
|
|
"pattern": "^[0-9A-Za-z]{8}(-[0-9A-Za-z]{4}){3}-[0-9A-Za-z]{12}$",
|
|
"examples": ["9c6ae55b-cf88-45b8-84e8-64990759f39d"]
|
|
},
|
|
"size": {
|
|
"description": "Maximum size of the filesystem",
|
|
"type": "integer"
|
|
},
|
|
"fs_type": {
|
|
"description": "Filesystem type",
|
|
"type": "string",
|
|
"enum": ["ext4", "xfs", "btrfs"],
|
|
"default": "ext4"
|
|
}
|
|
}
|
|
"""
|
|
|
|
@contextlib.contextmanager
|
|
def mount(source, dest, *options):
|
|
os.makedirs(dest, 0o755, True)
|
|
subprocess.run(["mount", *options, source, dest], check=True)
|
|
try:
|
|
yield
|
|
finally:
|
|
subprocess.run(["umount", "-R", dest], check=True)
|
|
|
|
|
|
def mkfs_ext4(device, uuid):
|
|
subprocess.run(["mkfs.ext4", "-U", uuid, device], input="y", encoding='utf-8', check=True)
|
|
|
|
|
|
def mkfs_xfs(device, uuid):
|
|
subprocess.run(["mkfs.xfs", "-m", f"uuid={uuid}", device], encoding='utf-8', check=True)
|
|
|
|
|
|
def mkfs_btrfs(device, uuid):
|
|
subprocess.run(["mkfs.btrfs", "-U", uuid, device], encoding='utf-8', check=True)
|
|
|
|
|
|
def main(tree, output_dir, options, loop_client):
|
|
filename = options["filename"]
|
|
root_fs_uuid = options["root_fs_uuid"]
|
|
size = options["size"]
|
|
fs_type = options.get("fs_type", "ext4")
|
|
|
|
image = "/var/tmp/osbuild-image.raw"
|
|
mountpoint = "/tmp/osbuild-mnt"
|
|
|
|
subprocess.run(["truncate", "--size", str(size), image], check=True)
|
|
|
|
if fs_type == "ext4":
|
|
mkfs_ext4(image, root_fs_uuid)
|
|
elif fs_type == "xfs":
|
|
mkfs_xfs(image, root_fs_uuid)
|
|
elif fs_type == "btrfs":
|
|
mkfs_btrfs(image, root_fs_uuid)
|
|
else:
|
|
raise ValueError("`fs_type` must be ext4, xfs or btrfs")
|
|
|
|
# Copy the tree into the target image
|
|
with loop_client.device(image) as loop, mount(loop, mountpoint):
|
|
subprocess.run(["cp", "-a", f"{tree}/.", mountpoint], check=True)
|
|
|
|
subprocess.run(["mv", image, f"{output_dir}/{filename}"], check=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = osbuild.api.arguments()
|
|
r = main(args["tree"], args["output_dir"], args["options"], remoteloop.LoopClient("/run/osbuild/api/remoteloop"))
|
|
sys.exit(r)
|