debian-forge/sources/org.osbuild.containers-storage
Simon de Vlieger cde6e1e114 source/containers-storage: error message
An error message has changed in skopeo [1] (or one of it's underlying
libraries). This new version is now in our CI so let's fix our tests.

[1]: f423f01d1b

Signed-off-by: Simon de Vlieger <supakeen@redhat.com>
2025-06-16 09:22:28 -07:00

105 lines
3.8 KiB
Python
Executable file

#!/usr/bin/python3
"""Provide access to an image in the host system's container storage.
This stage differs from other sources in that the source checks to see if the
container is available in the host's container storage. This puts a requirement
on the user to ensure that the container is copied into local storage before
trying to build an image. The starts by reading the host's `storage.conf`
file and then using the config to check if the container has been imported.
Unlike all other sources, this source relies on external storage not controlled
by osbuild itself.
Buildhost commands used: `skopeo`.
"""
import hashlib
import subprocess as sp
import sys
from osbuild import sources
from osbuild.util import host
SCHEMA = """
"additionalProperties": false,
"properties": {
"items": {
"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
}
}
}
}
"""
class ContainersStorageSource(sources.SourceService):
content_type = "org.osbuild.containers-storage"
storage_conf = None
def local_image_name(self, imagename):
"""
Construct the full image name that references an image with a given checksum in the local storage.
"""
if self.storage_conf is None:
self.storage_conf = host.get_container_storage()
driver = self.storage_conf["storage"]["driver"]
graphroot = self.storage_conf["storage"]["graphroot"]
runroot = self.storage_conf["storage"]["runroot"]
return f"containers-storage:[{driver}@{graphroot}+{runroot}]{imagename}"
def fetch_one(self, checksum, desc) -> None:
# Instead of fetching anything, just check that it exists.
#
# Note that there's an obvious TOCTOU issue here, but it's unavoidable without copying the storage or a
# container out of it, which is exactly what we want to avoid with this source.
# Unlike all other sources, this source relies on external storage not controlled by osbuild itself.
self.exists(checksum, desc)
def fetch_all(self, items) -> None:
for checksum in items:
self.fetch_one(checksum, None)
def exists(self, checksum, _) -> bool:
image_id = checksum.split(":")[1]
source = self.local_image_name(image_id)
res = sp.run(["skopeo", "inspect", "--raw", "--config", source],
check=False, stdout=sp.PIPE, stderr=sp.PIPE, universal_newlines=True)
# fail early if the user hasn't imported the container into
# containers-storage
if res.returncode != 0:
# string not matching not ideal - this is ErrNotAnImage
# which is unchanged since 2016 (added in ee99172905 in
# containers/storage)
# update: 2025, the message did change!
if "identifier is not an image" in res.stderr or "does not resolve to an image ID" in res.stderr:
return False
raise RuntimeError(f"unknown skopeo error: {res.stderr}")
# NOTE: this is a bit redundant because we're checking the content digest of the thing we retrieved via its
# id (which is the content digest), but let's keep it in case we change to retrieving local containers by name
# See also https://github.com/containers/skopeo/pull/2236
local_id = "sha256:" + hashlib.sha256(res.stdout.encode()).hexdigest()
if local_id != checksum:
raise RuntimeError(
f"Local container image id of {local_id}, but expected {checksum}")
return True
def main():
service = ContainersStorageSource.from_args(sys.argv[1:])
service.main()
if __name__ == '__main__':
main()