debian-forge/assemblers/org.osbuild.qemu
Tom Gundersen f470c3f3a3 assemblers/qemu: fix the partition UUID in the pipeline
Otherwise, sfdik would pick one at random. We want our images to be
reproducible to the extent possible, so we must move all randomness
out of the assemblers when we can.

Signed-off-by: Tom Gundersen <teg@jklm.no>
2019-10-02 15:10:37 +02:00

92 lines
3.6 KiB
Python
Executable file

#!/usr/bin/python3
import contextlib
import json
import os
import socket
import subprocess
import sys
import osbuild.remoteloop as remoteloop
@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)
@contextlib.contextmanager
def mount_api(dest):
with mount("/dev", f"{dest}/dev", "-o", "rbind"), \
mount("/proc", f"{dest}/proc", "-o", "rbind"), \
mount("/sys", f"{dest}/sys", "-o", "rbind"), \
mount("none", f"{dest}/run", "-t", "tmpfs"):
yield
@contextlib.contextmanager
def loop_device(loop_client, image, size, offset=0):
fd = os.open(image, os.O_RDWR)
devname = loop_client.create_device(fd, offset=offset, sizelimit=size)
os.close(fd)
path = f"/dev/{devname}"
try:
yield path
finally:
os.unlink(path)
def main(tree, output_dir, options, loop_client):
fmt = options["format"]
filename = options["filename"]
ptuuid = options["ptuuid"]
root_fs_uuid = options["root_fs_uuid"]
size = options["size"]
# sfdisk works on sectors of 512 bytes and ignores excess space - be explicit about this
if size % 512 != 0:
raise ValueError("`size` must be a multiple of sector size (512)")
if fmt not in ["raw", "qcow2", "vdi", "vmdk"]:
raise ValueError("`format` must be one of raw, qcow, vdi, vmdk")
image = f"/var/tmp/osbuild-image.raw"
mountpoint = f"/tmp/osbuild-mnt"
# Create an empty image file
subprocess.run(["truncate", "--size", str(size), image], check=True)
# Set up the partition table of the image
partition_table = f"label: mbr\nlabel-id: {ptuuid}\nbootable, type=83"
subprocess.run(["sfdisk", "-q", image], input=partition_table, encoding='utf-8', check=True)
r = subprocess.run(["sfdisk", "--json", image], stdout=subprocess.PIPE, encoding='utf-8', check=True)
partition_table = json.loads(r.stdout)
partition = partition_table["partitiontable"]["partitions"][0]
partition_offset = partition["start"] * 512
partition_size = partition["size"] * 512
# Populate the first partition of the image with an ext4 fs and fill it with the contents of the
# tree we are operating on.
subprocess.run(["mkfs.ext4", "-U", root_fs_uuid, "-E", f"offset={partition_offset}", image,
f"{int(partition_size / 1024)}k"], input="y", encoding='utf-8', check=True)
# Mount the created image as a loopback device
with loop_device(loop_client, image, partition_offset) as loop_block, \
loop_device(loop_client, image, partition_size, partition_offset) as loop_part, \
mount(loop_part, mountpoint):
# Copy the tree into the target image
subprocess.run(["cp", "-a", f"{tree}/.", mountpoint], check=True)
# Install grub2 into the boot sector of the image, and copy the grub2 imagise into /boot/grub2
with mount_api(mountpoint):
subprocess.run(["chroot", mountpoint, "grub2-install", "--no-floppy",
"--modules=part_msdos", "--target=i386-pc", loop_block], check=True)
subprocess.run(["qemu-img", "convert", "-O", fmt, "-c", image, f"{output_dir}/{filename}"], check=True)
if __name__ == '__main__':
args = json.load(sys.stdin)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
sock.connect("/run/osbuild/api/remoteloop")
ret = main(args["tree"], args["output_dir"], args["options"], remoteloop.LoopClient(sock))
sys.exit(ret)