11# -*- coding: utf-8 -*-
22
33import base64
4+ import configparser
45import copy
56import hashlib
67import json
3031
3132from ..constants import OCI_ANNOTATION_SIGNATURE_KEY , OCI_ANNOTATION_SIGNED_STRING_KEY
3233from ..features import CName
33-
3434from .checksum import (
3535 calculate_sha256 ,
3636 verify_sha256 ,
3737)
38+ from .wrapper import retry_on_error
3839from python_gardenlinux_lib .features .parse_features import get_oci_metadata_from_fileset
3940from .schemas import (
4041 EmptyIndex ,
@@ -599,9 +600,12 @@ def push_image_manifest(
599600
600601 return local_digest
601602
602- def update_index (self , manifest_folder ):
603+ def update_index (self , manifest_folder , additional_tags : list = None ):
603604 """
604605 replaces an old manifest entry with a new manifest entry
606+
607+ :param str manifest_folder: the folder where the manifest entries are read from
608+ :param list additional_tags: the additional tags to push the index with
605609 """
606610 index = self .get_index ()
607611 # Ensure mediaType is set for existing indices
@@ -634,6 +638,12 @@ def update_index(self, manifest_folder):
634638 self ._check_200_response (self .upload_index (index ))
635639 logger .info (f"Index pushed with { new_entries } new entries" )
636640
641+ for tag in additional_tags :
642+ self .container .digest = None
643+ self .container .tag = tag
644+ self .upload_index (index )
645+ logger .info (f"Index pushed with additional tag { tag } " )
646+
637647 def create_layer (
638648 self ,
639649 file_path : str ,
@@ -649,45 +659,96 @@ def create_layer(
649659 }
650660 return layer
651661
662+ @retry_on_error (max_retries = 3 , initial_delay = 2 , backoff_factor = 2 )
663+ def upload_blob (self , file_path , container , metadata = None ):
664+ """
665+ Upload a blob to the registry with retry logic for network errors.
666+
667+ Args:
668+ file_path: Path to the file to upload
669+ container: Container object
670+ metadata: Optional metadata for the blob
671+
672+ Returns:
673+ Response from the upload
674+ """
675+ # Call the parent class's upload_blob method
676+ return super ().upload_blob (file_path , container , metadata )
677+
652678 def push_from_dir (
653679 self ,
654680 architecture : str ,
655681 version : str ,
656682 cname : str ,
657683 directory : str ,
658684 manifest_file : str ,
659- commit : Optional [ str ] = None ,
685+ additional_tags : list = None ,
660686 ):
661- # Step 1 scan and extract nested artifacts:
662- for file in os .listdir (directory ):
663- try :
664- if file .endswith (".pxe.tar.gz" ):
665- logger .info (f"Found nested artifact { file } " )
666- nested_tar_obj = tarfile .open (f"{ directory } /{ file } " )
667- nested_tar_obj .extractall (filter = "data" , path = directory )
668- nested_tar_obj .close ()
669- except (OSError , tarfile .FilterError , tarfile .TarError ) as e :
670- print (f"Failed to extract nested artifact { file } " , e )
671- exit (1 )
687+ """
688+ Push artifacts from a directory to a registry
689+
690+ Args:
691+ architecture: Target architecture of the image
692+ version: Version tag for the image
693+ cname: Canonical name of the image
694+ directory: Directory containing the artifacts
695+ manifest_file: File to write the manifest index entry to
696+ additional_tags: Additional tags to push the manifest with
697+
698+ Returns:
699+ The digest of the pushed manifest
700+ """
701+ if additional_tags is None :
702+ additional_tags = []
672703
673704 try :
705+ # scan and extract nested artifacts
706+ for file in os .listdir (directory ):
707+ try :
708+ if file .endswith (".pxe.tar.gz" ):
709+ logger .info (f"Found nested artifact { file } " )
710+ nested_tar_obj = tarfile .open (f"{ directory } /{ file } " )
711+ nested_tar_obj .extractall (filter = "data" , path = directory )
712+ nested_tar_obj .close ()
713+ except (OSError , tarfile .FilterError , tarfile .TarError ) as e :
714+ print (f"Failed to extract nested artifact { file } " , e )
715+ exit (1 )
716+
717+ # Get metadata from files
674718 oci_metadata = get_oci_metadata_from_fileset (
675719 os .listdir (directory ), architecture
676720 )
677721
678722 features = ""
723+ commit = ""
679724 for artifact in oci_metadata :
680725 if artifact ["media_type" ] == "application/io.gardenlinux.release" :
681- file = open (f"{ directory } /{ artifact ["file_name" ]} " , "r" )
682- lines = file .readlines ()
683- for line in lines :
684- if line .strip ().startswith ("GARDENLINUX_FEATURES=" ):
685- features = line .strip ().removeprefix (
686- "GARDENLINUX_FEATURES="
726+ try :
727+ file_path = f"{ directory } /{ artifact ['file_name' ]} "
728+
729+ config = configparser .ConfigParser (allow_unnamed_section = True )
730+ config .read (file_path )
731+
732+ if config .has_option (
733+ configparser .UNNAMED_SECTION , "GARDENLINUX_FEATURES"
734+ ):
735+ features = config .get (
736+ configparser .UNNAMED_SECTION , "GARDENLINUX_FEATURES"
737+ )
738+ if config .has_option (
739+ configparser .UNNAMED_SECTION , "GARDENLINUX_COMMIT_ID"
740+ ):
741+ commit = config .get (
742+ configparser .UNNAMED_SECTION , "GARDENLINUX_COMMIT_ID"
687743 )
688- break
689- file .close ()
690744
745+ except (configparser .Error , IOError ) as e :
746+ logger .error (
747+ f"Error reading config file { artifact ['file_name' ]} : { e } "
748+ )
749+ break
750+
751+ # Push the image manifest
691752 digest = self .push_image_manifest (
692753 architecture ,
693754 cname ,
@@ -698,7 +759,103 @@ def push_from_dir(
698759 manifest_file ,
699760 commit = commit ,
700761 )
762+
763+ # Process additional tags if provided
764+ if additional_tags and len (additional_tags ) > 0 :
765+ print (f"DEBUG: Processing { len (additional_tags )} additional tags" )
766+ logger .info (f"Processing { len (additional_tags )} additional tags" )
767+
768+ self .push_additional_tags_manifest (
769+ architecture ,
770+ cname ,
771+ version ,
772+ additional_tags ,
773+ container = self .container ,
774+ )
775+
776+ return digest
701777 except Exception as e :
702778 print ("Error: " , e )
703779 exit (1 )
704- return digest
780+
781+ def push_additional_tags_manifest (
782+ self , architecture , cname , version , additional_tags , container
783+ ):
784+ """
785+ Push additional tags for an existing manifest using ORAS Registry methods
786+
787+ Args:
788+ architecture: Target architecture of the image
789+ cname: Canonical name of the image
790+ version: Version tag for the image
791+ additional_tags: List of additional tags to push
792+ container: Container object
793+ """
794+ try :
795+ # Source tag is the tag containing the version-cname-architecture combination
796+ source_tag = f"{ version } -{ cname } -{ architecture } "
797+ source_container = copy .deepcopy (container )
798+ source_container .tag = source_tag
799+
800+ # Authentication credentials from environment
801+ token = os .getenv ("GL_CLI_REGISTRY_TOKEN" )
802+ username = os .getenv ("GL_CLI_REGISTRY_USERNAME" )
803+ password = os .getenv ("GL_CLI_REGISTRY_PASSWORD" )
804+
805+ # Login to registry if credentials are provided
806+ if username and password :
807+ logger .debug (f"Logging in with username/password" )
808+ try :
809+ self .login (username , password )
810+ except Exception as login_error :
811+ logger .error (f"Login error: { str (login_error )} " )
812+ elif token :
813+ # If token is provided, set it directly on the Registry instance
814+ logger .debug (f"Using token authentication" )
815+ self .token = base64 .b64encode (token .encode ("utf-8" )).decode ("utf-8" )
816+ self .auth .set_token_auth (self .token )
817+
818+ # Get the manifest from the source container
819+ try :
820+ logger .debug (f"Getting manifest from { source_container } " )
821+ manifest = self .get_manifest (source_container )
822+ if not manifest :
823+ logger .error (f"Failed to get manifest for { source_container } " )
824+ return
825+ logger .info (
826+ f"Successfully retrieved manifest: { manifest ['mediaType' ] if 'mediaType' in manifest else 'unknown' } "
827+ )
828+ except Exception as get_error :
829+ logger .error (f"Error getting manifest: { str (get_error )} " )
830+ return
831+
832+ # For each additional tag, push the manifest using Registry.upload_manifest
833+ for tag in additional_tags :
834+ try :
835+ logger .debug (f"Pushing additional tag: { tag } " )
836+
837+ # Create a new container for this tag
838+ tag_container = copy .deepcopy (container )
839+ tag_container .tag = tag
840+
841+ logger .debug (f"Pushing to container: { tag_container } " )
842+
843+ # Upload the manifest to the new tag
844+ response = self .upload_manifest (manifest , tag_container )
845+
846+ if response and response .status_code in [200 , 201 ]:
847+ logger .info (f"Successfully pushed tag { tag } for manifest" )
848+ else :
849+ status_code = getattr (response , "status_code" , "unknown" )
850+ response_text = getattr (response , "text" , "No response text" )
851+ logger .error (
852+ f"Failed to push tag { tag } for manifest: { status_code } "
853+ )
854+
855+ except Exception as tag_error :
856+ logger .error (
857+ f"Error pushing tag { tag } for manifest: { str (tag_error )} "
858+ )
859+
860+ except Exception as e :
861+ logger .error (f"Error in push_additional_tags_manifest: { str (e )} " )
0 commit comments