From ac2f140d4c2ed0c3784c019bc5c4d24986afaa1f Mon Sep 17 00:00:00 2001 From: Achilleas Koutsou Date: Thu, 16 Mar 2023 15:50:41 +0100 Subject: [PATCH] stages/skopeo: merge manifest into image directory When a manifest list is matched with a container image, the skopeo stage will merge the specified manifest into the container image dir before copying it to the registry in the OS tree. If there is no manifest to merge, we maintain the old behaviour of symlinking the source to work around the ":" in filename issue. Otherwise, we copy the container directory so that we can merge the manifest in the new location. --- stages/org.osbuild.skopeo | 46 +++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/stages/org.osbuild.skopeo b/stages/org.osbuild.skopeo index a26fa111..6454ea75 100755 --- a/stages/org.osbuild.skopeo +++ b/stages/org.osbuild.skopeo @@ -121,6 +121,27 @@ def parse_input(inputs): return res +def merge_manifest(list_manifest, destination): + """ + Merge the list manifest into the image directory. This preserves the manifest list with the image in the registry so + that users can run or inspect a container using the original manifest list digest used to pull the container. + + See https://github.com/containers/skopeo/issues/1935 + """ + # calculate the checksum of the manifest of the container image in the destination + dest_manifest = os.path.join(destination, "manifest.json") + manifest_checksum = subprocess.check_output(["skopeo", "manifest-digest", dest_manifest]).decode().strip() + parts = manifest_checksum.split(":") + assert len(parts) == 2, f"unexpected output for skopeo manifest-digest: {manifest_checksum}" + manifest_checksum = parts[1] + + # rename the manifest to its checksum + os.rename(dest_manifest, os.path.join(destination, manifest_checksum + ".manifest.json")) + + # copy the index manifest into the destination + subprocess.run(["cp", "--reflink=auto", "-a", list_manifest, dest_manifest], check=True) + + def main(inputs, output, options): images = parse_input(inputs) @@ -136,24 +157,25 @@ def main(inputs, output, options): container_format = source_data["format"] image_name = source_data["name"] - # We can't have special characters like ":" in the source names because containers/image - # treats them special, like e.g. /some/path:tag, so we make a symlink to the real name - # and pass the symlink name to skopeo to make it work with anything with tempfile.TemporaryDirectory() as tmpdir: - linkname = os.path.join(tmpdir, "image") - os.symlink(source, linkname) + tmp_source = os.path.join(tmpdir, "image") - if container_format == "dir": - source = f"dir:{linkname}" - elif container_format == "oci-archive": - source = f"oci-archive:{linkname}" + if container_format == "dir" and image["manifest-list"]: + # copy the source container to the tmp source so we can merge the manifest into it + subprocess.run(["cp", "-a", "--reflink=auto", source, tmp_source], check=True) + merge_manifest(image["manifest-list"], tmp_source) else: + # We can't have special characters like ":" in the source names because containers/image + # treats them special, like e.g. /some/path:tag, so we make a symlink to the real name + # and pass the symlink name to skopeo to make it work with anything + os.symlink(source, tmp_source) + + if container_format not in ("dir", "oci-archive"): raise RuntimeError(f"Unknown container format {container_format}") + source = f"{container_format}:{tmp_source}" dest = f"containers-storage:[{storage_driver}@{output}{storage_root}+/run/containers/storage]{image_name}" - - subprocess.run(["skopeo", "copy", source, dest], - check=True) + subprocess.run(["skopeo", "copy", source, dest], check=True) if storage_driver == "overlay": # Each time the overlay backend runs on an xfs fs it creates this file: