Skip to content

Commit f67654e

Browse files
committed
add gardenlinux.oci.registry.push_additional_tags_manifest
enables pushing additional tags to OCI manifest
1 parent 1f14abe commit f67654e

File tree

7 files changed

+513
-168
lines changed

7 files changed

+513
-168
lines changed

poetry.lock

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

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 145 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@
2929
from oras.schemas import manifest as oras_manifest_schema
3030

3131
from gardenlinux.features import Parser
32-
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+
)
3337
from .checksum import (
3438
calculate_sha256,
3539
verify_sha256,
@@ -670,40 +674,59 @@ def push_from_dir(
670674
cname: str,
671675
directory: str,
672676
manifest_file: str,
673-
commit: Optional[str] = None,
677+
additional_tags: list = None,
674678
):
675-
# Step 1 scan and extract nested artifacts:
676-
for file in os.listdir(directory):
677-
try:
678-
if file.endswith(".pxe.tar.gz"):
679-
logger.info(f"Found nested artifact {file}")
680-
nested_tar_obj = tarfile.open(f"{directory}/{file}")
681-
nested_tar_obj.extractall(filter="data", path=directory)
682-
nested_tar_obj.close()
683-
except (OSError, tarfile.FilterError, tarfile.TarError) as e:
684-
print(f"Failed to extract nested artifact {file}", e)
685-
exit(1)
679+
"""
680+
Push artifacts from a directory to a registry
681+
682+
Args:
683+
architecture: Target architecture of the image
684+
version: Version tag for the image
685+
cname: Canonical name of the image
686+
directory: Directory containing the artifacts
687+
manifest_file: File to write the manifest index entry to
688+
additional_tags: Additional tags to push the manifest with
689+
690+
Returns:
691+
The digest of the pushed manifest
692+
"""
693+
if additional_tags is None:
694+
additional_tags = []
686695

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

692714
features = ""
715+
commit = ""
693716
for artifact in oci_metadata:
694717
if artifact["media_type"] == "application/io.gardenlinux.release":
695-
file = open(f"{directory}/{artifact["file_name"]}", "r")
696-
lines = file.readlines()
697-
for line in lines:
698-
if line.strip().startswith("GARDENLINUX_FEATURES="):
699-
features = line.strip().removeprefix(
700-
"GARDENLINUX_FEATURES="
701-
)
702-
break
703-
file.close()
704-
705-
flavor = Parser.get_flavor_from_cname(cname, get_arch=True)
706-
718+
with open(f"{directory}/{artifact["file_name"]}", "r") as file:
719+
for line in file:
720+
line = line.strip()
721+
if line.startswith("GARDENLINUX_FEATURES="):
722+
features = line.removeprefix("GARDENLINUX_FEATURES=")
723+
elif line.startswith("GARDENLINUX_COMMIT_ID="):
724+
commit = line.removeprefix("GARDENLINUX_COMMIT_ID=")
725+
if features and commit: # Break if both values are found
726+
break
727+
break # Break after processing the release file
728+
729+
# Push the image manifest
707730
digest = self.push_image_manifest(
708731
architecture,
709732
cname,
@@ -714,7 +737,103 @@ def push_from_dir(
714737
manifest_file,
715738
commit=commit,
716739
)
740+
741+
# Process additional tags if provided
742+
if additional_tags and len(additional_tags) > 0:
743+
print(f"DEBUG: Processing {len(additional_tags)} additional tags")
744+
logger.info(f"Processing {len(additional_tags)} additional tags")
745+
746+
self.push_additional_tags_manifest(
747+
architecture,
748+
cname,
749+
version,
750+
additional_tags,
751+
container=self.container,
752+
)
753+
754+
return digest
717755
except Exception as e:
718756
print("Error: ", e)
719757
exit(1)
720-
return digest
758+
759+
def push_additional_tags_manifest(
760+
self, architecture, cname, version, additional_tags, container
761+
):
762+
"""
763+
Push additional tags for an existing manifest using ORAS Registry methods
764+
765+
Args:
766+
architecture: Target architecture of the image
767+
cname: Canonical name of the image
768+
version: Version tag for the image
769+
additional_tags: List of additional tags to push
770+
container: Container object
771+
"""
772+
try:
773+
# Source tag is the tag containing the version-cname-architecture combination
774+
source_tag = f"{version}-{cname}-{architecture}"
775+
source_container = copy.deepcopy(container)
776+
source_container.tag = source_tag
777+
778+
# Authentication credentials from environment
779+
token = os.getenv("GL_CLI_REGISTRY_TOKEN")
780+
username = os.getenv("GL_CLI_REGISTRY_USERNAME")
781+
password = os.getenv("GL_CLI_REGISTRY_PASSWORD")
782+
783+
# Login to registry if credentials are provided
784+
if username and password:
785+
logger.debug(f"Logging in with username/password")
786+
try:
787+
self.login(username, password)
788+
except Exception as login_error:
789+
logger.error(f"Login error: {str(login_error)}")
790+
elif token:
791+
# If token is provided, set it directly on the Registry instance
792+
logger.debug(f"Using token authentication")
793+
self.token = base64.b64encode(token.encode("utf-8")).decode("utf-8")
794+
self.auth.set_token_auth(self.token)
795+
796+
# Get the manifest from the source container
797+
try:
798+
logger.debug(f"Getting manifest from {source_container}")
799+
manifest = self.get_manifest(source_container)
800+
if not manifest:
801+
logger.error(f"Failed to get manifest for {source_container}")
802+
return
803+
logger.info(
804+
f"Successfully retrieved manifest: {manifest['mediaType'] if 'mediaType' in manifest else 'unknown'}"
805+
)
806+
except Exception as get_error:
807+
logger.error(f"Error getting manifest: {str(get_error)}")
808+
return
809+
810+
# For each additional tag, push the manifest using Registry.upload_manifest
811+
for tag in additional_tags:
812+
try:
813+
logger.debug(f"Pushing additional tag: {tag}")
814+
815+
# Create a new container for this tag
816+
tag_container = copy.deepcopy(container)
817+
tag_container.tag = tag
818+
819+
logger.debug(f"Pushing to container: {tag_container}")
820+
821+
# Upload the manifest to the new tag
822+
response = self.upload_manifest(manifest, tag_container)
823+
824+
if response and response.status_code in [200, 201]:
825+
logger.info(f"Successfully pushed tag {tag} for manifest")
826+
else:
827+
status_code = getattr(response, "status_code", "unknown")
828+
response_text = getattr(response, "text", "No response text")
829+
logger.error(
830+
f"Failed to push tag {tag} for manifest: {status_code}"
831+
)
832+
833+
except Exception as tag_error:
834+
logger.error(
835+
f"Error pushing tag {tag} for manifest: {str(tag_error)}"
836+
)
837+
838+
except Exception as e:
839+
logger.error(f"Error in push_additional_tags_manifest: {str(e)}")

0 commit comments

Comments
 (0)