assemblers/qcow2: move from the old stage and make it work again
Minor ajustments needed after we changed the internal API. Signed-off-by: Tom Gundersen <teg@jklm.no>
This commit is contained in:
parent
4742ae1e72
commit
a428ff05ad
2 changed files with 82 additions and 78 deletions
82
assemblers/io.weldr.qcow2
Executable file
82
assemblers/io.weldr.qcow2
Executable file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
def tree_size(tree):
|
||||
size = 0
|
||||
for root, dirs, files in os.walk(tree):
|
||||
for entry in files + dirs:
|
||||
path = os.path.join(root, entry)
|
||||
size += os.stat(path, follow_symlinks=False).st_size
|
||||
return size
|
||||
|
||||
@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(image):
|
||||
r = subprocess.run(["losetup", "-f"], stdout=subprocess.PIPE, encoding="utf-8", check=True)
|
||||
loop = r.stdout.strip()
|
||||
r = subprocess.run(["losetup", loop, "-P", image], check=True)
|
||||
try:
|
||||
yield loop
|
||||
finally:
|
||||
subprocess.run(["losetup", "-d", loop], check=True)
|
||||
|
||||
def main(tree, input_dir, options):
|
||||
filename = options["filename"]
|
||||
partition_table_id = options["partition_table_id"]
|
||||
root_fs_uuid = options["root_fs_uuid"]
|
||||
|
||||
# Create a working directory on a tmpfs, maybe we should implicitly
|
||||
# always do this.
|
||||
with tempfile.TemporaryDirectory() as workdir:
|
||||
image = f"{workdir}/image.raw"
|
||||
mountpoint = f"{workdir}/mnt"
|
||||
|
||||
# Create an empty image file of the right size
|
||||
size = int(tree_size(tree) * 1.2)
|
||||
subprocess.run(["truncate", "--size", str(size), image], check=True)
|
||||
|
||||
# Set up the partition table of the image
|
||||
partition_table = "label: mbr\nlabel-id: {partition_table_id}\nbootable, type=83"
|
||||
subprocess.run(["sfdisk", "-q", image], input=partition_table, encoding='utf-8', check=True)
|
||||
|
||||
# Mount the created image as a loopback device
|
||||
with loop_device(image) as loop:
|
||||
# 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", "-d", tree, "-U", root_fs_uuid, f"{loop}p1"], check=True)
|
||||
|
||||
# Install grub2 into the boot sector of the image
|
||||
# It is unclear why chrooting into the image is required.
|
||||
with mount(f"{loop}p1", mountpoint):
|
||||
with mount_api(mountpoint):
|
||||
subprocess.run(["chroot", mountpoint, "grub2-install", "--no-floppy", "--target", "i386-pc", loop], check=True)
|
||||
|
||||
subprocess.run(["qemu-img", "convert", "-O" "qcow2", "-c", image, filename], check=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = json.load(sys.stdin)
|
||||
r = main(args["tree"], args["input_dir"], args["options"])
|
||||
sys.exit(r)
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
def tree_size(tree):
|
||||
size = 0
|
||||
for root, dirs, files in os.walk(tree):
|
||||
for entry in files + dirs:
|
||||
path = os.path.join(root, entry)
|
||||
size += os.stat(path, follow_symlinks=False).st_size
|
||||
return size
|
||||
|
||||
@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", dest], check=True)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mount_image(device, dest):
|
||||
with mount(device, dest), \
|
||||
mount("/dev", f"{dest}/dev", "--bind"), \
|
||||
mount("/proc", f"{dest}/proc", "--bind"), \
|
||||
mount("/sys", f"{dest}/sys", "--bind"):
|
||||
yield
|
||||
|
||||
@contextlib.contextmanager
|
||||
def loop_device(image):
|
||||
r = subprocess.run(["losetup", "--show", "-f", "-P", image], stdout=subprocess.PIPE, encoding="utf-8", check=True)
|
||||
loop = r.stdout.strip()
|
||||
try:
|
||||
yield loop
|
||||
finally:
|
||||
subprocess.run(["losetup", "-d", loop], check=True)
|
||||
|
||||
def main(tree, options):
|
||||
target = options["target"])
|
||||
|
||||
size = tree_size(tree)
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=os.getcwd()) as workdir:
|
||||
image = f"{workdir}/image.raw"
|
||||
mountpoint = f"{workdir}/mnt"
|
||||
|
||||
subprocess.run(["truncate", "--size", str(int(size * 1.1)), image], check=True)
|
||||
|
||||
partition_table = "label: mbr\nbootable, type=83"
|
||||
subprocess.run(["sfdisk", "-q", image], input=partition_table, encoding='utf-8', check=True)
|
||||
|
||||
with loop_device(image) as loop:
|
||||
subprocess.run(["mkfs.ext4", "-d", tree, f"{loop}p1"], check=True)
|
||||
|
||||
# grub2-mkconfig and grub2-install only seem to work reliably when
|
||||
# called from a chroot
|
||||
with mount_image(f"{loop}p1", mountpoint):
|
||||
subprocess.run(["chroot", mountpoint, "grub2-mkconfig", "-o", "/boot/grub2/grub.cfg"], check=True)
|
||||
subprocess.run(["chroot", mountpoint, "grub2-install", "--no-floppy", "--target", "i386-pc", loop], check=True)
|
||||
|
||||
subprocess.run(["qemu-img", "convert", "-O" "qcow2", "-c", image, target], check=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = json.load(sys.stdin)
|
||||
|
||||
try:
|
||||
r = main(args["tree"], args["options"])
|
||||
except CalledProcessError as e:
|
||||
print(e, file=sys.stderr)
|
||||
r = 1
|
||||
|
||||
sys.exit(r)
|
||||
Loading…
Add table
Add a link
Reference in a new issue