125 lines
3.9 KiB
Python
Executable file
125 lines
3.9 KiB
Python
Executable file
#!/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()
|