#!/usr/bin/python3 """Fetch container image from a registry using skopeo Buildhost commands used: `skopeo`. """ import errno import hashlib import os import subprocess import sys import tempfile from osbuild import sources from osbuild.util import ctx SCHEMA = """ "additionalProperties": false, "definitions": { "item": { "description": "The container image to fetch indexed by the container image id", "type": "object", "additionalProperties": false, "patternProperties": { "sha256:[0-9a-f]{64}": { "type": "object", "additionalProperties": false, "required": ["image"], "properties": { "image": { "type": "object", "additionalProperties": false, "required": ["name", "digest"], "properties": { "name": { "type": "string", "description": "Name of the image (including registry)." }, "digest": { "type": "string", "description": "Digest of image in registry.", "pattern": "sha256:[0-9a-f]{64}" }, "tls-verify": { "type": "boolean", "description": "Require https (default true)." } } } } } } } }, "properties": { "items": {"$ref": "#/definitions/item"}, "digests": {"$ref": "#/definitions/item"} }, "oneOf": [{ "required": ["items"] }, { "required": ["digests"] }] """ class SkopeoSource(sources.SourceService): content_type = "org.osbuild.containers" def fetch_one(self, checksum, desc): image_id = checksum image = desc["image"] imagename = image["name"] digest = image["digest"] tls_verify = image.get("tls-verify", True) with tempfile.TemporaryDirectory(prefix="tmp-download-", dir=self.cache) as tmpdir: archive_dir = os.path.join(tmpdir, "container-archive") os.makedirs(archive_dir) os.chmod(archive_dir, 0o755) archive_path = os.path.join(archive_dir, "container-image.tar") source = f"docker://{imagename}@{digest}" # We use the docker format, not oci, because that is the # default return image type of real world registries, # allowing the image to get the same image id as if you # did "podman pull" (rather than converting the image to # oci format, changing the id) destination = f"docker-archive:{archive_path}" extra_args = [] # The archive format can't store signatures, but we still verify them during download extra_args.append("--remove-signatures") if not tls_verify: extra_args.append("--src-tls-verify=false") subprocess.run(["skopeo", "copy"] + extra_args + [source, destination], encoding="utf-8", check=True) # Verify that the digest supplied downloaded the correct container image id. # The image id is the digest of the config, but skopeo can' currently # get the config id, only the full config, so we checksum it ourselves. res = subprocess.check_output(["skopeo", "inspect", "--raw", "--config", destination]) downloaded_id = "sha256:" + hashlib.sha256(res).hexdigest() if downloaded_id != image_id: raise RuntimeError( f"Downloaded image {imagename}@{digest} has a id of {downloaded_id}, but expected {image_id}") # Atomically move download dir into place on successful download with ctx.suppress_oserror(errno.ENOTEMPTY, errno.EEXIST): os.rename(archive_dir, f"{self.cache}/{image_id}") def main(): service = SkopeoSource.from_args(sys.argv[1:]) service.main() if __name__ == '__main__': main()