Skip to content

Commit c1be7a1

Browse files
committed
sources/skopeo: fetch index manifest for container
With the local format being `dir` now, we can copy the manifest specified in the source digest. We can then merge the list manifest into the image directory when requested by the user. 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 56ac5b8 commit c1be7a1

File tree

1 file changed

+28
-4
lines changed

1 file changed

+28
-4
lines changed

sources/org.osbuild.skopeo

+28-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ Buildhost commands used: `skopeo`.
66

77
import errno
88
import hashlib
9+
import json
910
import os
1011
import subprocess
1112
import sys
1213
import tempfile
1314

1415
from osbuild import sources
15-
from osbuild.util import ctx
16+
from osbuild.util import containers, ctx
1617

1718
SCHEMA = """
1819
"additionalProperties": false,
@@ -86,6 +87,8 @@ class SkopeoSource(sources.SourceService):
8687
# direct serialisation of the registry data.
8788
dir_name = "image"
8889
destination = f"dir:{archive_dir}/{dir_name}"
90+
image_path = os.path.join(archive_dir, dir_name)
91+
destination = f"dir:{image_path}"
8992

9093
extra_args = []
9194
if not tls_verify:
@@ -104,13 +107,34 @@ class SkopeoSource(sources.SourceService):
104107
raise RuntimeError(
105108
f"Downloaded image {imagename}@{digest} has a id of {downloaded_id}, but expected {image_id}")
106109

107-
# Atomically move download archive into place on successful download
110+
# Move image directory into place on successful download
108111
with ctx.suppress_oserror(errno.ENOTEMPTY, errno.EEXIST):
109112
os.makedirs(f"{self.cache}/{image_id}", exist_ok=True)
110113
os.rename(f"{archive_dir}/{dir_name}", f"{self.cache}/{image_id}/{dir_name}")
111114

112-
def exists(self, checksum, _desc):
113-
return os.path.exists(f"{self.cache}/{checksum}/image")
115+
if self.is_manifest_list(source):
116+
# the digest points to a manifest-list: download it and store it separately in the archive_dir, next to
117+
# the image directory
118+
manifest_path = os.path.join(archive_dir, digest)
119+
subprocess.run(["skopeo", "copy", "--multi-arch=index-only", *extra_args,
120+
source, f"dir:{manifest_path}"],
121+
encoding="utf-8", check=True)
122+
123+
# Move manifest directory into place on successful download
124+
with ctx.suppress_oserror(errno.ENOTEMPTY, errno.EEXIST):
125+
os.rename(f"{archive_dir}/{digest}", f"{self.cache}/{image_id}/{digest}")
126+
127+
def exists(self, checksum, desc):
128+
digest = desc["image"]["digest"]
129+
# check that both the image and the digest exist
130+
return (os.path.exists(f"{self.cache}/{checksum}/image") and
131+
os.path.exists(f"{self.cache}/{checksum}/{digest}"))
132+
133+
@staticmethod
134+
def is_manifest_list(source):
135+
"""Inspect the manifest at the source and determine if it's a multi-image manifest-list."""
136+
res = subprocess.check_output(["skopeo", "inspect", "--raw", source])
137+
return containers.is_manifest_list(json.loads(res))
114138

115139

116140
def main():

0 commit comments

Comments
 (0)