debian-forge/assemblers/org.osbuild.rawfs
Christian Kellner 8ccc73d1c3 pipeline assemblers are stages now
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.
2021-01-18 17:44:46 +01:00

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)