Instead of using the `Assemblers` class to represent assemblers, use the `Stage` class: The `Pipeline.add_assembler` method will now instantiate and `Stage` instead of an `Assembler`. The tree that the pipeline built is converted to an Input (while loading the manifest description in `format/v1.py`) and all existing assemblers are converted to use that input as the tree input. The assembler run test is removed as the Assembler class itself is not used (i.e. run) anymore.
113 lines
3.4 KiB
Python
Executable file
113 lines
3.4 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()
|
|
args_input = args["inputs"]["tree"]["path"]
|
|
args_output = args["tree"]
|
|
r = main(args_input, args_output, args["options"], remoteloop.LoopClient("/run/osbuild/api/remoteloop"))
|
|
sys.exit(r)
|