11# -*- coding: utf-8 -*-
22
33import base64
4+ import configparser
45import copy
56import hashlib
67import json
2829from oras .provider import Registry
2930from 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+ )
3237from ..features import CName
33-
3438from .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