Skip to content

Commit

Permalink
sources/skopeo: fetch index manifest for container
Browse files Browse the repository at this point in the history
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 containers/skopeo#1935
  • Loading branch information
achilleas-k committed Mar 14, 2023
1 parent 565dab3 commit 166a023
Showing 1 changed file with 24 additions and 0 deletions.
24 changes: 24 additions & 0 deletions sources/org.osbuild.skopeo
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -112,6 +117,25 @@ 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:
# 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:])
Expand Down

0 comments on commit 166a023

Please sign in to comment.