Skip to content

Commit abf603d

Browse files
committed
sources/skopeo: fetch index manifest for dir
When the format is specified as "dir", 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
1 parent b34ba4a commit abf603d

File tree

1 file changed

+25
-2
lines changed

1 file changed

+25
-2
lines changed

sources/org.osbuild.skopeo

+25-2
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ class SkopeoSource(sources.SourceService):
9393
os.chmod(archive_dir, 0o755)
9494

9595
source = f"docker://{imagename}@{digest}"
96-
9796
archive_name = containers.archive_name(archive_format)
98-
destination = f"{archive_format}:{archive_dir}/{archive_name}"
97+
dest_path = os.path.join(archive_dir, archive_name)
98+
destination = f"{archive_format}:{dest_path}"
9999

100100
extra_args = []
101101

@@ -120,6 +120,10 @@ class SkopeoSource(sources.SourceService):
120120
raise RuntimeError(
121121
f"Downloaded image {imagename}@{digest} has a id of {downloaded_id}, but expected {image_id}")
122122

123+
if archive_format == "dir":
124+
# fetch the manifest and merge it into the archive
125+
self.merge_manifest(source, dest_path)
126+
123127
# Atomically move download archive into place on successful download
124128
with ctx.suppress_oserror(errno.ENOTEMPTY, errno.EEXIST):
125129
os.makedirs(f"{self.cache}/{image_id}", exist_ok=True)
@@ -129,6 +133,25 @@ class SkopeoSource(sources.SourceService):
129133
archive_name = containers.archive_name(desc["image"].get("format", "docker-archive"))
130134
return os.path.exists(f"{self.cache}/{checksum}/{archive_name}")
131135

136+
def merge_manifest(self, source, destination):
137+
with tempfile.TemporaryDirectory(prefix="tmp-download-", dir=self.cache) as indexdir:
138+
# download the manifest(s) to a temporary directory
139+
subprocess.run(["skopeo", "copy", "--multi-arch=index-only", source, f"dir:{indexdir}"],
140+
encoding="utf-8", check=True)
141+
142+
# calculate the checksum of the manifest of the container image in the destination
143+
manifest_path = os.path.join(destination, "manifest.json")
144+
manifest_checksum = subprocess.check_output(["skopeo", "manifest-digest", manifest_path]).decode().strip()
145+
parts = manifest_checksum.split(":")
146+
assert len(parts) == 2, f"unexpected output for skopeo manifest-digest: {manifest_checksum}"
147+
manifest_checksum = parts[1]
148+
149+
# rename the manifest to its checksum
150+
os.rename(manifest_path, os.path.join(destination, manifest_checksum + ".manifest.json"))
151+
152+
# move the index manifest into the destination
153+
os.rename(os.path.join(indexdir, "manifest.json"), manifest_path)
154+
132155

133156
def main():
134157
service = SkopeoSource.from_args(sys.argv[1:])

0 commit comments

Comments
 (0)