Skip to content

Commit 4ce25b1

Browse files
committed
add gardenlinux.oci.registry.push_additional_tags_manifest
enables pushing additional tags to OCI manifest
1 parent 91db17e commit 4ce25b1

File tree

7 files changed

+859
-556
lines changed

7 files changed

+859
-556
lines changed

poetry.lock

Lines changed: 426 additions & 413 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ readme = "README.md"
88
packages = [{include = "gardenlinux", from="src"}, {include = "python_gardenlinux_lib", from="src"}]
99

1010
[tool.poetry.dependencies]
11-
python = "^3.10"
11+
python = "^3.13"
1212
networkx = "^3.3"
1313
PyYAML = "^6.0.2"
1414
pytest = "^8.3.2"
@@ -19,6 +19,9 @@ oras = { git = "https://github.com/oras-project/oras-py.git", rev="caf8db5b2793
1919
python-dotenv = "^1.0.1"
2020
cryptography = "^44.0.0"
2121
boto3 = "*"
22+
click = "^8.2.0"
23+
pygments = "^2.19.1"
24+
opencontainers = "^0.0.14"
2225

2326
[tool.poetry.group.dev.dependencies]
2427
bandit = "^1.8.3"

src/gardenlinux/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,5 @@
166166

167167
OCI_ANNOTATION_SIGNATURE_KEY = "io.gardenlinux.oci.signature"
168168
OCI_ANNOTATION_SIGNED_STRING_KEY = "io.gardenlinux.oci.signed-string"
169+
170+
GL_USER_AGENT_REGISTRY = "gardenlinux.oci.registry/1.0"

src/gardenlinux/oci/__main__.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,6 @@ def cli():
2626
type=click.Path(),
2727
help="Version of image",
2828
)
29-
@click.option(
30-
"--commit",
31-
required=False,
32-
type=click.Path(),
33-
default=None,
34-
help="Commit of image",
35-
)
3629
@click.option(
3730
"--arch",
3831
required=True,
@@ -58,16 +51,22 @@ def cli():
5851
default=False,
5952
help="Use HTTP to communicate with the registry",
6053
)
54+
@click.option(
55+
"--additional_tag",
56+
required=False,
57+
multiple=True,
58+
help="Additional tag to push the manifest with",
59+
)
6160
def push_manifest(
6261
container,
6362
version,
64-
commit,
6563
arch,
6664
cname,
6765
directory,
6866
cosign_file,
6967
manifest_file,
7068
insecure,
69+
additional_tag,
7170
):
7271
"""push artifacts from a dir to a registry, get the index-entry for the manifest in return"""
7372
container_name = f"{container}:{version}"
@@ -77,7 +76,7 @@ def push_manifest(
7776
insecure=insecure,
7877
)
7978
digest = registry.push_from_dir(
80-
arch, version, cname, directory, manifest_file, commit=commit
79+
arch, version, cname, directory, manifest_file, additional_tag
8180
)
8281
if cosign_file:
8382
print(digest, file=open(cosign_file, "w"))

src/gardenlinux/oci/registry.py

Lines changed: 158 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
import base64
4+
import configparser
45
import copy
56
import hashlib
67
import json
@@ -28,9 +29,12 @@
2829
from oras.provider import Registry
2930
from oras.schemas import manifest as oras_manifest_schema
3031

31-
from ..constants import OCI_ANNOTATION_SIGNATURE_KEY, OCI_ANNOTATION_SIGNED_STRING_KEY
32+
from ..constants import (
33+
OCI_ANNOTATION_SIGNATURE_KEY,
34+
OCI_ANNOTATION_SIGNED_STRING_KEY,
35+
GL_USER_AGENT_REGISTRY,
36+
)
3237
from ..features import CName
33-
3438
from .checksum import (
3539
calculate_sha256,
3640
verify_sha256,
@@ -673,38 +677,73 @@ def push_from_dir(
673677
cname: str,
674678
directory: str,
675679
manifest_file: str,
676-
commit: Optional[str] = None,
680+
additional_tags: list = None,
677681
):
678-
# Step 1 scan and extract nested artifacts:
679-
for file in os.listdir(directory):
680-
try:
681-
if file.endswith(".pxe.tar.gz"):
682-
logger.info(f"Found nested artifact {file}")
683-
nested_tar_obj = tarfile.open(f"{directory}/{file}")
684-
nested_tar_obj.extractall(filter="data", path=directory)
685-
nested_tar_obj.close()
686-
except (OSError, tarfile.FilterError, tarfile.TarError) as e:
687-
print(f"Failed to extract nested artifact {file}", e)
688-
exit(1)
682+
"""
683+
Push artifacts from a directory to a registry
684+
685+
Args:
686+
architecture: Target architecture of the image
687+
version: Version tag for the image
688+
cname: Canonical name of the image
689+
directory: Directory containing the artifacts
690+
manifest_file: File to write the manifest index entry to
691+
additional_tags: Additional tags to push the manifest with
692+
693+
Returns:
694+
The digest of the pushed manifest
695+
"""
696+
if additional_tags is None:
697+
additional_tags = []
689698

690699
try:
700+
# scan and extract nested artifacts
701+
for file in os.listdir(directory):
702+
try:
703+
if file.endswith(".pxe.tar.gz"):
704+
logger.info(f"Found nested artifact {file}")
705+
nested_tar_obj = tarfile.open(f"{directory}/{file}")
706+
nested_tar_obj.extractall(filter="data", path=directory)
707+
nested_tar_obj.close()
708+
except (OSError, tarfile.FilterError, tarfile.TarError) as e:
709+
print(f"Failed to extract nested artifact {file}", e)
710+
exit(1)
711+
712+
# Get metadata from files
691713
oci_metadata = get_oci_metadata_from_fileset(
692714
os.listdir(directory), architecture
693715
)
694716

695717
features = ""
718+
commit = ""
696719
for artifact in oci_metadata:
697720
if artifact["media_type"] == "application/io.gardenlinux.release":
698-
file = open(f"{directory}/{artifact["file_name"]}", "r")
699-
lines = file.readlines()
700-
for line in lines:
701-
if line.strip().startswith("GARDENLINUX_FEATURES="):
702-
features = line.strip().removeprefix(
703-
"GARDENLINUX_FEATURES="
721+
try:
722+
file_path = f"{directory}/{artifact['file_name']}"
723+
724+
config = configparser.ConfigParser(allow_unnamed_section=True)
725+
config.read(file_path)
726+
727+
if config.has_option(
728+
configparser.UNNAMED_SECTION, "GARDENLINUX_FEATURES"
729+
):
730+
features = config.get(
731+
configparser.UNNAMED_SECTION, "GARDENLINUX_FEATURES"
732+
)
733+
if config.has_option(
734+
configparser.UNNAMED_SECTION, "GARDENLINUX_COMMIT_ID"
735+
):
736+
commit = config.get(
737+
configparser.UNNAMED_SECTION, "GARDENLINUX_COMMIT_ID"
704738
)
705-
break
706-
file.close()
707739

740+
except (configparser.Error, IOError) as e:
741+
logger.error(
742+
f"Error reading config file {artifact['file_name']}: {e}"
743+
)
744+
break
745+
746+
# Push the image manifest
708747
digest = self.push_image_manifest(
709748
architecture,
710749
cname,
@@ -715,7 +754,103 @@ def push_from_dir(
715754
manifest_file,
716755
commit=commit,
717756
)
757+
758+
# Process additional tags if provided
759+
if additional_tags and len(additional_tags) > 0:
760+
print(f"DEBUG: Processing {len(additional_tags)} additional tags")
761+
logger.info(f"Processing {len(additional_tags)} additional tags")
762+
763+
self.push_additional_tags_manifest(
764+
architecture,
765+
cname,
766+
version,
767+
additional_tags,
768+
container=self.container,
769+
)
770+
771+
return digest
718772
except Exception as e:
719773
print("Error: ", e)
720774
exit(1)
721-
return digest
775+
776+
def push_additional_tags_manifest(
777+
self, architecture, cname, version, additional_tags, container
778+
):
779+
"""
780+
Push additional tags for an existing manifest using ORAS Registry methods
781+
782+
Args:
783+
architecture: Target architecture of the image
784+
cname: Canonical name of the image
785+
version: Version tag for the image
786+
additional_tags: List of additional tags to push
787+
container: Container object
788+
"""
789+
try:
790+
# Source tag is the tag containing the version-cname-architecture combination
791+
source_tag = f"{version}-{cname}-{architecture}"
792+
source_container = copy.deepcopy(container)
793+
source_container.tag = source_tag
794+
795+
# Authentication credentials from environment
796+
token = os.getenv("GL_CLI_REGISTRY_TOKEN")
797+
username = os.getenv("GL_CLI_REGISTRY_USERNAME")
798+
password = os.getenv("GL_CLI_REGISTRY_PASSWORD")
799+
800+
# Login to registry if credentials are provided
801+
if username and password:
802+
logger.debug(f"Logging in with username/password")
803+
try:
804+
self.login(username, password)
805+
except Exception as login_error:
806+
logger.error(f"Login error: {str(login_error)}")
807+
elif token:
808+
# If token is provided, set it directly on the Registry instance
809+
logger.debug(f"Using token authentication")
810+
self.token = base64.b64encode(token.encode("utf-8")).decode("utf-8")
811+
self.auth.set_token_auth(self.token)
812+
813+
# Get the manifest from the source container
814+
try:
815+
logger.debug(f"Getting manifest from {source_container}")
816+
manifest = self.get_manifest(source_container)
817+
if not manifest:
818+
logger.error(f"Failed to get manifest for {source_container}")
819+
return
820+
logger.info(
821+
f"Successfully retrieved manifest: {manifest['mediaType'] if 'mediaType' in manifest else 'unknown'}"
822+
)
823+
except Exception as get_error:
824+
logger.error(f"Error getting manifest: {str(get_error)}")
825+
return
826+
827+
# For each additional tag, push the manifest using Registry.upload_manifest
828+
for tag in additional_tags:
829+
try:
830+
logger.debug(f"Pushing additional tag: {tag}")
831+
832+
# Create a new container for this tag
833+
tag_container = copy.deepcopy(container)
834+
tag_container.tag = tag
835+
836+
logger.debug(f"Pushing to container: {tag_container}")
837+
838+
# Upload the manifest to the new tag
839+
response = self.upload_manifest(manifest, tag_container)
840+
841+
if response and response.status_code in [200, 201]:
842+
logger.info(f"Successfully pushed tag {tag} for manifest")
843+
else:
844+
status_code = getattr(response, "status_code", "unknown")
845+
response_text = getattr(response, "text", "No response text")
846+
logger.error(
847+
f"Failed to push tag {tag} for manifest: {status_code}"
848+
)
849+
850+
except Exception as tag_error:
851+
logger.error(
852+
f"Error pushing tag {tag} for manifest: {str(tag_error)}"
853+
)
854+
855+
except Exception as e:
856+
logger.error(f"Error in push_additional_tags_manifest: {str(e)}")

0 commit comments

Comments
 (0)