Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 101 additions & 55 deletions src/gardenlinux/oci/image_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
from pathlib import Path
from typing import Any, Dict

from oras.oci import Layer
from oras.defaults import annotation_title as ANNOTATION_TITLE

from ..constants import GL_DISTRIBUTION_NAME, GL_REPOSITORY_URL
from ..features import CName
from .layer import Layer
from .manifest import Manifest
from .platform import NewPlatform
from .schemas import EmptyManifestMetadata
Expand All @@ -27,6 +29,31 @@ class ImageManifest(Manifest):
Apache License, Version 2.0
"""

ANNOTATION_ARCH_KEY = "org.opencontainers.image.architecture"
"""
OCI image manifest architecture annotation
"""
ANNOTATION_CNAME_KEY = "cname"
"""
OCI image manifest GardenLinux canonical name annotation
"""
ANNOTATION_DESCRIPTION_KEY = "org.opencontainers.image.description"
"""
OCI image manifest description annotation
"""
ANNOTATION_FEATURE_SET_KEY = "feature_set"
"""
OCI image manifest GardenLinux feature set annotation
"""
ANNOTATION_SOURCE_REPO_KEY = "org.opencontainers.image.source"
"""
OCI image manifest GardenLinux source repository URL annotation
"""
ANNOTATION_TITLE_KEY = ANNOTATION_TITLE
"""
OCI image manifest title annotation
"""

@property
def arch(self) -> str:
"""
Expand All @@ -36,12 +63,12 @@ def arch(self) -> str:
:since: 0.7.0
"""

if "architecture" not in self.get("annotations", {}):
if ImageManifest.ANNOTATION_ARCH_KEY not in self.get("annotations", {}):
raise RuntimeError(
"Unexpected manifest with missing config annotation 'architecture' found"
f"Unexpected manifest with missing config annotation '{ImageManifest.ANNOTATION_ARCH_KEY}' found"
)

return self["annotations"]["architecture"] # type: ignore[no-any-return]
return self["annotations"][ImageManifest.ANNOTATION_ARCH_KEY] # type: ignore[no-any-return]

@arch.setter
def arch(self, value: str) -> None:
Expand All @@ -54,7 +81,7 @@ def arch(self, value: str) -> None:
"""

self._ensure_annotations_dict()
self["annotations"]["architecture"] = value
self["annotations"][ImageManifest.ANNOTATION_ARCH_KEY] = value

@property
def cname(self) -> str:
Expand All @@ -65,12 +92,12 @@ def cname(self) -> str:
:since: 0.7.0
"""

if "cname" not in self.get("annotations", {}):
if ImageManifest.ANNOTATION_CNAME_KEY not in self.get("annotations", {}):
raise RuntimeError(
"Unexpected manifest with missing config annotation 'cname' found"
f"Unexpected manifest with missing config annotation '{ImageManifest.ANNOTATION_CNAME_KEY}' found"
)

return self["annotations"]["cname"] # type: ignore[no-any-return]
return self["annotations"][ImageManifest.ANNOTATION_CNAME_KEY] # type: ignore[no-any-return]

@cname.setter
def cname(self, value: str) -> None:
Expand All @@ -83,7 +110,7 @@ def cname(self, value: str) -> None:
"""

self._ensure_annotations_dict()
self["annotations"]["cname"] = value
self["annotations"][ImageManifest.ANNOTATION_CNAME_KEY] = value

@property
def feature_set(self) -> str:
Expand All @@ -94,12 +121,12 @@ def feature_set(self) -> str:
:since: 0.7.0
"""

if "feature_set" not in self.get("annotations", {}):
if ImageManifest.ANNOTATION_FEATURE_SET_KEY not in self.get("annotations", {}):
raise RuntimeError(
"Unexpected manifest with missing config annotation 'feature_set' found"
f"Unexpected manifest with missing config annotation '{ImageManifest.ANNOTATION_FEATURE_SET_KEY}' found"
)

return self["annotations"]["feature_set"] # type: ignore[no-any-return]
return self["annotations"][ImageManifest.ANNOTATION_FEATURE_SET_KEY] # type: ignore[no-any-return]

@feature_set.setter
def feature_set(self, value: str) -> None:
Expand All @@ -112,7 +139,7 @@ def feature_set(self, value: str) -> None:
"""

self._ensure_annotations_dict()
self["annotations"]["feature_set"] = value
self["annotations"][ImageManifest.ANNOTATION_FEATURE_SET_KEY] = value

@property
def flavor(self) -> str:
Expand All @@ -126,56 +153,75 @@ def flavor(self) -> str:
return CName(self.cname).flavor

@property
def layers_as_dict(self) -> Dict[str, Any]:
def _final_dict(self) -> Dict[str, Any]:
"""
Returns the OCI image manifest layers as a dictionary.
Returns the final parsed and extended OCI manifest dictionary

:return: (dict) OCI image manifest layers with title as key
:since: 0.7.0
:return: (dict) OCI manifest dictionary
:since: 1.0.0
"""

layers = {}
manifest_dict = Manifest(self)._final_dict
manifest_annotations = manifest_dict["annotations"]

for layer in self["layers"]:
if "org.opencontainers.image.title" not in layer.get("annotations", {}):
raise RuntimeError(
"Unexpected layer with missing annotation 'org.opencontainers.image.title' found"
)
if ImageManifest.ANNOTATION_TITLE_KEY not in manifest_annotations:
manifest_annotations[ImageManifest.ANNOTATION_TITLE_KEY] = (
GL_DISTRIBUTION_NAME
)

layers[layer["annotations"]["org.opencontainers.image.title"]] = layer
manifest_description = manifest_annotations[ImageManifest.ANNOTATION_TITLE_KEY]

return layers
if ImageManifest.ANNOTATION_SOURCE_REPO_KEY not in manifest_annotations:
manifest_annotations[ImageManifest.ANNOTATION_SOURCE_REPO_KEY] = (
GL_REPOSITORY_URL
)

@property
def version(self) -> str:
"""
Returns the GardenLinux version of the OCI image manifest.
if ImageManifest.ANNOTATION_ARCH_KEY in manifest_annotations:
manifest_annotations["architecture"] = self.arch
manifest_description += f" ({self.arch})"

:return: (str) OCI image GardenLinux version
:since: 0.7.0
"""
if ImageManifest.ANNOTATION_VERSION_KEY in manifest_annotations:
manifest_annotations["org.opencontainers.image.version"] = self.version
manifest_description += " " + self.version

if "version" not in self.get("annotations", {}):
raise RuntimeError(
"Unexpected manifest with missing config annotation 'version' found"
if ImageManifest.ANNOTATION_COMMIT_KEY in manifest_annotations:
manifest_annotations["org.opencontainers.image.revision"] = self.commit
manifest_description += f" ({self.commit})"

if ImageManifest.ANNOTATION_FEATURE_SET_KEY in manifest_annotations:
manifest_description += (
" - " + manifest_annotations[ImageManifest.ANNOTATION_FEATURE_SET_KEY]
)

if ImageManifest.ANNOTATION_DESCRIPTION_KEY not in manifest_annotations:
manifest_annotations[ImageManifest.ANNOTATION_DESCRIPTION_KEY] = (
manifest_description
)

return self["annotations"]["version"] # type: ignore[no-any-return]
return manifest_dict

@version.setter
def version(self, value: str) -> None:
@property
def layers_as_dict(self) -> Dict[str, Any]:
"""
Sets the GardenLinux version of the OCI image manifest.

:param value: OCI image GardenLinux version
Returns the OCI image manifest layers as a dictionary.

:since: 0.7.0
:return: (dict) OCI image manifest layers with title as key
:since: 0.7.0
"""

self._ensure_annotations_dict()
self["annotations"]["version"] = value
layers = {}

for layer in self["layers"]:
if ImageManifest.ANNOTATION_TITLE_KEY not in layer.get("annotations", {}):
raise RuntimeError(
f"Unexpected layer with missing annotation '{ImageManifest.ANNOTATION_TITLE_KEY}' found"
)

layers[layer["annotations"][ImageManifest.ANNOTATION_TITLE_KEY]] = layer

def append_layer(self, layer: Layer) -> None:
return layers

def append_layer(self, layer: Layer | Dict[str, Any]) -> None:
"""
Appends the given OCI image manifest layer to the manifest

Expand All @@ -184,30 +230,30 @@ def append_layer(self, layer: Layer) -> None:
:since: 0.7.0
"""

if not isinstance(layer, Layer):
raise RuntimeError("Unexpected layer type given")

layer_dict = layer.dict
if isinstance(layer, Layer):
layer_dict = layer.dict
else:
layer_dict = layer

if "org.opencontainers.image.title" not in layer_dict.get("annotations", {}):
if ImageManifest.ANNOTATION_TITLE_KEY not in layer_dict.get("annotations", {}):
raise RuntimeError(
"Unexpected layer with missing annotation 'org.opencontainers.image.title' found"
f"Unexpected layer with missing annotation '{ImageManifest.ANNOTATION_TITLE_KEY}' found"
)

image_title = layer_dict["annotations"]["org.opencontainers.image.title"]
image_title = layer_dict["annotations"][ImageManifest.ANNOTATION_TITLE_KEY]
existing_layer_index = 0

for existing_layer in self["layers"]:
if "org.opencontainers.image.title" not in existing_layer.get(
if ImageManifest.ANNOTATION_TITLE_KEY not in existing_layer.get(
"annotations", {}
):
raise RuntimeError(
"Unexpected layer with missing annotation 'org.opencontainers.image.title' found"
f"Unexpected layer with missing annotation '{ImageManifest.ANNOTATION_TITLE_KEY}' found"
)

if (
image_title
== existing_layer["annotations"]["org.opencontainers.image.title"]
== existing_layer["annotations"][ImageManifest.ANNOTATION_TITLE_KEY]
):
break

Expand Down
14 changes: 11 additions & 3 deletions src/gardenlinux/oci/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from pathlib import Path
from typing import Any, Dict, Iterator, Optional

from oras.defaults import annotation_title as ANNOTATION_TITLE
from oras.oci import Layer as _Layer

from ..constants import GL_MEDIA_TYPE_LOOKUP, GL_MEDIA_TYPES
Expand All @@ -26,6 +25,15 @@ class Layer(_Layer, Mapping): # type: ignore[misc, type-arg]
Apache License, Version 2.0
"""

ANNOTATION_ARCH_KEY = "io.gardenlinux.image.layer.architecture"
"""
OCI image layer architecture annotation
"""
ANNOTATION_TITLE_KEY = "org.opencontainers.image.title"
"""
OCI image layer title annotation
"""

def __init__(
self,
blob_path: PathLike[str] | str,
Expand All @@ -48,7 +56,7 @@ def __init__(
_Layer.__init__(self, blob_path, media_type, is_dir)

self._annotations = {
ANNOTATION_TITLE: blob_path.name, # type: ignore[attr-defined]
Layer.ANNOTATION_TITLE_KEY: blob_path.name, # type: ignore[attr-defined]
}

@property
Expand Down Expand Up @@ -157,7 +165,7 @@ def generate_metadata_from_file_name(
return {
"file_name": file_name.name, # type: ignore[attr-defined]
"media_type": media_type,
"annotations": {"io.gardenlinux.image.layer.architecture": arch},
"annotations": {Layer.ANNOTATION_ARCH_KEY: arch},
}

@staticmethod
Expand Down
Loading
Loading