Skip to content

Commit a1e5a8e

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 and allow them to 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 d2b4c3b commit a1e5a8e

File tree

1 file changed

+23
-2
lines changed

1 file changed

+23
-2
lines changed

sources/org.osbuild.skopeo

+23-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

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

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

135+
def merge_manifest(self, source, destination):
136+
with tempfile.TemporaryDirectory(prefix="tmp-download-", dir=self.cache) as indexdir:
137+
# download the manifest(s) to a temporary directory
138+
subprocess.run(["skopeo", "copy", "--multi-arch=index-only", source, f"dir:{indexdir}"],
139+
encoding="utf-8", check=True)
140+
141+
# calculate the checksum of the manifest of the container image in the destination
142+
manifest_path = os.path.join(destination, "manifest.json")
143+
with open(manifest_path, encoding="utf-8") as manifest:
144+
manifest_checksum = hashlib.sha256(manifest.read().encode()).hexdigest()
145+
146+
# rename the manifest to its checksum
147+
os.rename(manifest_path, os.path.join(destination, manifest_checksum + ".manifest.json"))
148+
149+
# move the index manifest into the destination
150+
os.rename(os.path.join(indexdir, "manifest.json"), manifest_path)
151+
131152

132153
def main():
133154
service = SkopeoSource.from_args(sys.argv[1:])

0 commit comments

Comments
 (0)