From ae3df8a538207674cda7ab536573de983796050f Mon Sep 17 00:00:00 2001 From: Achilleas Koutsou Date: Fri, 10 Mar 2023 17:18:37 +0100 Subject: [PATCH] sources/skopeo: fetch index manifest for container With the format is specified as `dir` now, we can copy the manifest specified in the source digest and merge it into the final image directory. The effect of this is that the digest of the final image in the container registry will match the manifest digest. This enables users to specify an image's manifest digest or a multi-image manifest list digest which will be preserved in the container store on the final OS. Then, they can run the container using the same digest that was specified in the input. This may become a feature of Skopeo (or other tooling) in the future. See https://github.com/containers/skopeo/issues/1935 --- sources/org.osbuild.skopeo | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sources/org.osbuild.skopeo b/sources/org.osbuild.skopeo index e474181006..ea316b2390 100755 --- a/sources/org.osbuild.skopeo +++ b/sources/org.osbuild.skopeo @@ -86,6 +86,8 @@ class SkopeoSource(sources.SourceService): # direct serialisation of the registry data. dir_name = "container-dir" destination = f"dir:{archive_dir}/{dir_name}" + dest_path = os.path.join(archive_dir, dir_name) + destination = f"dir:{dest_path}" extra_args = [] if not tls_verify: @@ -104,6 +106,9 @@ class SkopeoSource(sources.SourceService): raise RuntimeError( f"Downloaded image {imagename}@{digest} has a id of {downloaded_id}, but expected {image_id}") + # fetch the manifest and merge it into the archive + self.merge_manifest(source, dest_path, extra_args) + # Atomically move download archive into place on successful download with ctx.suppress_oserror(errno.ENOTEMPTY, errno.EEXIST): os.makedirs(f"{self.cache}/{image_id}", exist_ok=True) @@ -112,6 +117,26 @@ class SkopeoSource(sources.SourceService): def exists(self, checksum, desc): return os.path.exists(f"{self.cache}/{checksum}/container-dir") + def merge_manifest(self, source, destination, extra_args): + with tempfile.TemporaryDirectory(prefix="tmp-download-", dir=self.cache) as indexdir: + subprocess.run(["skopeo", "inspect", "--raw", "--config", source]) + # download the manifest(s) to a temporary directory + subprocess.run(["skopeo", "copy", "--multi-arch=index-only", *extra_args, source, f"dir:{indexdir}"], + encoding="utf-8", check=True) + + # calculate the checksum of the manifest of the container image in the destination + manifest_path = os.path.join(destination, "manifest.json") + manifest_checksum = subprocess.check_output(["skopeo", "manifest-digest", manifest_path]).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(manifest_path, os.path.join(destination, manifest_checksum + ".manifest.json")) + + # move the index manifest into the destination + os.rename(os.path.join(indexdir, "manifest.json"), manifest_path) + def main(): service = SkopeoSource.from_args(sys.argv[1:])