|
| 1 | +From 701fa40dfce9a70c1dafdee32907a154463a1a44 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Dusty Mabe < [email protected]> |
| 3 | +Date: Thu, 9 Oct 2025 22:05:44 -0400 |
| 4 | +Subject: [PATCH 1/5] osbuild/util/containers.py: add container_mount() |
| 5 | + functionality |
| 6 | + |
| 7 | +As prep for a later patch this moves the container image mounting code |
| 8 | +from stages/org.osbuild.container-deploy into the containers library. |
| 9 | + |
| 10 | +This commit also removes the associated unit test since it wasn't doing |
| 11 | +much and also there doesn't seem to be a place for unit testing |
| 12 | +osbuild/util/containers.py. |
| 13 | +--- |
| 14 | + osbuild/util/containers.py | 42 ++++++++++++++++++++++++++- |
| 15 | + stages/org.osbuild.container-deploy | 45 ++--------------------------- |
| 16 | + 2 files changed, 43 insertions(+), 44 deletions(-) |
| 17 | + |
| 18 | +diff --git a/osbuild/util/containers.py b/osbuild/util/containers.py |
| 19 | +index 49eceee2..352c5c8e 100644 |
| 20 | +--- a/osbuild/util/containers.py |
| 21 | ++++ b/osbuild/util/containers.py |
| 22 | +@@ -1,8 +1,10 @@ |
| 23 | + import json |
| 24 | + import os |
| 25 | ++import random |
| 26 | ++import string |
| 27 | + import subprocess |
| 28 | + import tempfile |
| 29 | +-from contextlib import contextmanager |
| 30 | ++from contextlib import ExitStack, contextmanager |
| 31 | + |
| 32 | + from osbuild.util.mnt import MountGuard, MountPermissions |
| 33 | + |
| 34 | +@@ -184,3 +186,41 @@ def container_source(image): |
| 35 | + # that the inner ctx manager won't be cleaned up until the execution returns to this ctx manager. |
| 36 | + with container_source_fn(image, image_filepath, container_format) as image_source: |
| 37 | + yield image_name, image_source |
| 38 | ++ |
| 39 | ++ |
| 40 | ++@contextmanager |
| 41 | ++def container_mount(image, remove_signatures=False): |
| 42 | ++ # Helper function for doing the `podman image mount` |
| 43 | ++ @contextmanager |
| 44 | ++ def _mount_container(image_tag): |
| 45 | ++ cmd = ["podman", "image", "mount", image_tag], |
| 46 | ++ result = subprocess.run(cmd, encoding="utf-8", check=False, |
| 47 | ++ stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 48 | ++ if result.returncode != 0: |
| 49 | ++ code = result.returncode |
| 50 | ++ msg = result.stderr.strip() |
| 51 | ++ raise RuntimeError(f"Failed to mount image ({code}): {msg}") |
| 52 | ++ try: |
| 53 | ++ yield result.stdout.strip() |
| 54 | ++ finally: |
| 55 | ++ cmd = ["podman", "image", "umount", image_tag], |
| 56 | ++ subprocess.run(cmd, check=True) |
| 57 | ++ |
| 58 | ++ # We cannot use a tmpdir as storage here because of |
| 59 | ++ # https://github.com/containers/storage/issues/1779 so instead |
| 60 | ++ # just pick a random suffix. This runs inside bwrap which gives a |
| 61 | ++ # tmp /var so it does not really matter much. |
| 62 | ++ tmp_image_tag = "tmp-container-mount-" + "".join(random.choices(string.digits, k=14)) |
| 63 | ++ with container_source(image) as (_, source): |
| 64 | ++ with ExitStack() as cm: |
| 65 | ++ cm.callback(subprocess.run, ["podman", "rmi", tmp_image_tag], check=True) |
| 66 | ++ # skopeo needs /var/tmp but the bwrap env is minimal and may not have it |
| 67 | ++ os.makedirs("/var/tmp", mode=0o1777, exist_ok=True) |
| 68 | ++ cmd = ["skopeo", "copy"] |
| 69 | ++ if remove_signatures: |
| 70 | ++ cmd.append("--remove-signatures") |
| 71 | ++ cmd.extend([source, f"containers-storage:{tmp_image_tag}"]) |
| 72 | ++ subprocess.run(cmd, check=True) |
| 73 | ++ |
| 74 | ++ with _mount_container(tmp_image_tag) as container_mountpoint: |
| 75 | ++ yield container_mountpoint |
| 76 | +diff --git a/stages/org.osbuild.container-deploy b/stages/org.osbuild.container-deploy |
| 77 | +index 29fe7b97..f933ac73 100755 |
| 78 | +--- a/stages/org.osbuild.container-deploy |
| 79 | ++++ b/stages/org.osbuild.container-deploy |
| 80 | +@@ -1,8 +1,4 @@ |
| 81 | + #!/usr/bin/python3 |
| 82 | +-import contextlib |
| 83 | +-import os |
| 84 | +-import random |
| 85 | +-import string |
| 86 | + import subprocess |
| 87 | + import sys |
| 88 | + |
| 89 | +@@ -10,51 +6,14 @@ import osbuild.api |
| 90 | + from osbuild.util import containers |
| 91 | + |
| 92 | + |
| 93 | +-@contextlib.contextmanager |
| 94 | +-def mount_container(image_tag): |
| 95 | +- result = subprocess.run( |
| 96 | +- ["podman", "image", "mount", image_tag], |
| 97 | +- stdout=subprocess.PIPE, |
| 98 | +- stderr=subprocess.PIPE, |
| 99 | +- encoding="utf-8", |
| 100 | +- check=False, |
| 101 | +- ) |
| 102 | +- if result.returncode != 0: |
| 103 | +- code = result.returncode |
| 104 | +- msg = result.stderr.strip() |
| 105 | +- raise RuntimeError(f"Failed to mount image ({code}): {msg}") |
| 106 | +- try: |
| 107 | +- yield result.stdout.strip() |
| 108 | +- finally: |
| 109 | +- subprocess.run( |
| 110 | +- ["podman", "image", "umount", image_tag], |
| 111 | +- check=True, |
| 112 | +- ) |
| 113 | +- |
| 114 | +- |
| 115 | + def main(inputs, tree, options): |
| 116 | + images = containers.parse_containers_input(inputs) |
| 117 | + assert len(images) == 1 |
| 118 | + image = list(images.values())[0] |
| 119 | + remove_signatures = options.get("remove-signatures") |
| 120 | + |
| 121 | +- # skopeo needs /var/tmp but the bwrap env is minimal and may not have it |
| 122 | +- os.makedirs("/var/tmp", mode=0o1777, exist_ok=True) |
| 123 | +- # We cannot use a tmpdir as storage here because of |
| 124 | +- # https://github.com/containers/storage/issues/1779 so instead |
| 125 | +- # just pick a random suffix. This runs inside bwrap which gives a |
| 126 | +- # tmp /var so it does not really matter much. |
| 127 | +- image_tag = "tmp-container-deploy-" + "".join(random.choices(string.digits, k=14)) |
| 128 | +- with contextlib.ExitStack() as cm: |
| 129 | +- cm.callback(subprocess.run, ["podman", "rmi", image_tag], check=True) |
| 130 | +- with containers.container_source(image) as (_, source): |
| 131 | +- cmd = ["skopeo", "copy"] |
| 132 | +- if remove_signatures: |
| 133 | +- cmd.append("--remove-signatures") |
| 134 | +- cmd.extend([source, f"containers-storage:{image_tag}"]) |
| 135 | +- subprocess.run(cmd, check=True) |
| 136 | +- with mount_container(image_tag) as img: |
| 137 | +- subprocess.run(["cp", "-a", f"{img}/.", f"{tree}/"], check=True) |
| 138 | ++ with containers.container_mount(image, remove_signatures) as container_mountpoint: |
| 139 | ++ subprocess.run(["cp", "-a", f"{container_mountpoint}/.", f"{tree}/"], check=True) |
| 140 | + # postprocess the tree, would be nicer to filter before already |
| 141 | + for exclude in options.get("exclude", []): |
| 142 | + subprocess.run(["rm", "-rf", f"{tree}/{exclude}"], check=True) |
| 143 | +-- |
| 144 | +2.51.0 |
| 145 | + |
0 commit comments