From cbca935b87b4606534fc08d2cae80d864af51c7c Mon Sep 17 00:00:00 2001 From: Piotr Milewski Date: Tue, 23 Dec 2025 10:30:02 +0100 Subject: Add OCI standard labels to container images Currently, Kolla images lack standard metadata, making it difficult to identify their build source, creation time, or exact version solely by inspecting the image artifact. This patch introduces the addition of Open Container Initiative (OCI) standard labels to all generated images. The build process now detects metadata (utilizing `direct_url.json` for pip-installed packages to avoid subprocess calls) and injects the following labels into the container runtime config: * org.opencontainers.image.authors * org.opencontainers.image.created * org.opencontainers.image.description * org.opencontainers.image.licenses * org.opencontainers.image.revision * org.opencontainers.image.source * org.opencontainers.image.title * org.opencontainers.image.url * org.opencontainers.image.version Example output: "Labels": { "org.opencontainers.image.authors": "Kolla Project (https://launchpad.net/kolla)", "org.opencontainers.image.created": "2025-12-22T14:02:47.015819", "org.opencontainers.image.description": "OpenStack Kolla keystone-ssh image", "org.opencontainers.image.licenses": "Apache-2.0", "org.opencontainers.image.revision": "96b54022857ccef8dc6bb11789abe32119827109", "org.opencontainers.image.source": "https://opendev.org/openstack/kolla", "org.opencontainers.image.title": "keystone-ssh", "org.opencontainers.image.url": "https://opendev.org/openstack/kolla", "org.opencontainers.image.version": "master-ubuntu-noble" } This enhancement improves the interoperability of Kolla images with standard container tools and registries that display OCI metadata. Change-Id: I3451940c90180ba91759c839987434e94251e32e Signed-off-by: Piotr Milewski --- kolla/image/kolla_worker.py | 74 +++++++++++++++++++++++++++++++++++++++++++-- kolla/image/tasks.py | 3 ++ 2 files changed, 74 insertions(+), 3 deletions(-) (limited to 'kolla') diff --git a/kolla/image/kolla_worker.py b/kolla/image/kolla_worker.py index 723914f5a..680595f86 100644 --- a/kolla/image/kolla_worker.py +++ b/kolla/image/kolla_worker.py @@ -11,6 +11,8 @@ # limitations under the License. import datetime +import git +import importlib.metadata import json import os import queue @@ -42,7 +44,7 @@ PROJECT_ROOT = os.path.abspath(os.path.join( class Image(object): def __init__(self, name, canonical_name, path, parent_name='', status=Status.UNPROCESSED, parent=None, - source=None, logger=None, engine_client=None): + source=None, logger=None, engine_client=None, labels=None): self.name = name self.canonical_name = canonical_name self.path = path @@ -57,11 +59,12 @@ class Image(object): self.plugins = [] self.additions = [] self.engine_client = engine_client + self.labels = labels or {} def copy(self): c = Image(self.name, self.canonical_name, self.path, logger=self.logger, parent_name=self.parent_name, - status=self.status, parent=self.parent) + status=self.status, parent=self.parent, labels=self.labels) if self.source: c.source = self.source.copy() if self.children: @@ -143,6 +146,9 @@ class KollaWorker(object): self.image_statuses_allowed_to_fail = dict() self.maintainer = conf.maintainer self.patches_path = conf.patches_path + self.scm_labels = self._get_scm_labels() + self.build_created = (datetime.datetime.now(datetime.timezone.utc) + .strftime('%Y-%m-%dT%H:%M:%SZ')) try: self.engine_client = engine.getEngineClient(self.conf) @@ -157,6 +163,53 @@ class KollaWorker(object): LOG.info("Exception caught: {0}".format(e)) sys.exit(1) + def _get_scm_labels(self): + labels = {} + + # Method 1: Install via PIP (direct_url.json) + try: + dist = importlib.metadata.distribution('kolla') + direct_url_file = [ + p for p in dist.files if p.name == 'direct_url.json' + ] + + if direct_url_file: + content = dist.read_text('direct_url.json') + if content: + data = json.loads(content) + if 'url' in data: + labels['org.opencontainers.image.source'] = data['url'] + if 'vcs_info' in data and 'commit_id' in data['vcs_info']: + labels['org.opencontainers.image.revision'] = ( + data['vcs_info']['commit_id']) + except Exception as e: + LOG.debug(f"Could not retrieve PIP direct_url info: {e}") + + # Method 2: Fallback to Git (if PIP info is local or missing) + source = labels.get('org.opencontainers.image.source', '') + if not labels.get('org.opencontainers.image.revision') or \ + source.startswith('file:'): + try: + repo = git.Repo(PROJECT_ROOT, search_parent_directories=True) + + labels['org.opencontainers.image.revision'] = ( + repo.head.object.hexsha) + + if source.startswith('file:') or not source: + labels['org.opencontainers.image.source'] = ( + 'https://opendev.org/openstack/kolla') + + LOG.debug("Detected SCM info via Git fallback: %s", labels) + except Exception as e: + LOG.debug("Git fallback failed: %s", e) + + source_url = labels.get('org.opencontainers.image.source', '') + if source_url.startswith('file:'): + labels['org.opencontainers.image.source'] = ( + 'https://opendev.org/openstack/kolla') + + return labels + def _get_images_dir(self): possible_paths = ( PROJECT_ROOT, @@ -694,10 +747,25 @@ class KollaWorker(object): else: parent_name = '' del match + + oci_labels = self.scm_labels.copy() + oci_labels.update({ + "org.opencontainers.image.title": image_name, + "org.opencontainers.image.description": ( + "OpenStack Kolla {} image".format(image_name)), + "org.opencontainers.image.version": self.tag, + "org.opencontainers.image.created": self.build_created, + "org.opencontainers.image.authors": self.maintainer, + "org.opencontainers.image.url": ( + "https://opendev.org/openstack/kolla"), + "org.opencontainers.image.licenses": "Apache-2.0" + }) + image = Image(image_name, canonical_name, path, parent_name=parent_name, logger=utils.make_a_logger(self.conf, image_name), - engine_client=self.engine_client) + engine_client=self.engine_client, + labels=oci_labels) # NOTE(jeffrey4l): register the opts if the section didn't # register in the kolla/common/config.py file diff --git a/kolla/image/tasks.py b/kolla/image/tasks.py index 39d759390..6bee905f7 100644 --- a/kolla/image/tasks.py +++ b/kolla/image/tasks.py @@ -407,6 +407,9 @@ class BuildTask(EngineTask): buildargs = self.update_buildargs() kwargs = {} + if hasattr(image, 'labels') and image.labels: + kwargs['labels'] = image.labels + if self.conf.engine == engine.Engine.PODMAN.value: # TODO(kevko): dockerfile path is a workaround, # should be removed as soon as it will be fixed in podman-py -- cgit v1.2.3