1616from typing import Optional , Tuple
1717
1818import jsonschema
19+ import opencontainers .distribution .reggie as reggie
1920import oras .auth
2021import oras .client
2122import oras .defaults
2930from oras .schemas import manifest as oras_manifest_schema
3031
3132from gardenlinux .features import Parser
32- from ..constants import OCI_ANNOTATION_SIGNATURE_KEY , OCI_ANNOTATION_SIGNED_STRING_KEY
33+ from ..constants import (
34+ OCI_ANNOTATION_SIGNATURE_KEY ,
35+ OCI_ANNOTATION_SIGNED_STRING_KEY ,
36+ GL_USER_AGENT_REGISTRY ,
37+ )
3338from .checksum import (
3439 calculate_sha256 ,
3540 verify_sha256 ,
@@ -653,40 +658,59 @@ def push_from_dir(
653658 cname : str ,
654659 directory : str ,
655660 manifest_file : str ,
656- commit : Optional [ str ] = None ,
661+ additional_tags : list = None ,
657662 ):
658- # Step 1 scan and extract nested artifacts:
659- for file in os .listdir (directory ):
660- try :
661- if file .endswith (".pxe.tar.gz" ):
662- logger .info (f"Found nested artifact { file } " )
663- nested_tar_obj = tarfile .open (f"{ directory } /{ file } " )
664- nested_tar_obj .extractall (filter = "data" , path = directory )
665- nested_tar_obj .close ()
666- except (OSError , tarfile .FilterError , tarfile .TarError ) as e :
667- print (f"Failed to extract nested artifact { file } " , e )
668- exit (1 )
663+ """
664+ Push artifacts from a directory to a registry
665+
666+ Args:
667+ architecture: Target architecture of the image
668+ version: Version tag for the image
669+ cname: Canonical name of the image
670+ directory: Directory containing the artifacts
671+ manifest_file: File to write the manifest index entry to
672+ additional_tags: Additional tags to push the manifest with
673+
674+ Returns:
675+ The digest of the pushed manifest
676+ """
677+ if additional_tags is None :
678+ additional_tags = []
669679
670680 try :
681+ # Step 1: scan and extract nested artifacts
682+ for file in os .listdir (directory ):
683+ try :
684+ if file .endswith (".pxe.tar.gz" ):
685+ logger .info (f"Found nested artifact { file } " )
686+ nested_tar_obj = tarfile .open (f"{ directory } /{ file } " )
687+ nested_tar_obj .extractall (filter = "data" , path = directory )
688+ nested_tar_obj .close ()
689+ except (OSError , tarfile .FilterError , tarfile .TarError ) as e :
690+ print (f"Failed to extract nested artifact { file } " , e )
691+ exit (1 )
692+
693+ # Step 2: Get metadata from files
671694 oci_metadata = get_oci_metadata_from_fileset (
672695 os .listdir (directory ), architecture
673696 )
674697
675698 features = ""
699+ commit = ""
676700 for artifact in oci_metadata :
677701 if artifact ["media_type" ] == "application/io.gardenlinux.release" :
678- file = open (f"{ directory } /{ artifact ["file_name" ]} " , "r" )
679- lines = file . readlines ()
680- for line in lines :
681- if line . strip () .startswith ("GARDENLINUX_FEATURES=" ):
682- features = line .strip (). removeprefix (
683- "GARDENLINUX_FEATURES="
684- )
685- break
686- file . close ()
687-
688- flavor = Parser . get_flavor_from_cname ( cname , get_arch = True )
689-
702+ with open (f"{ directory } /{ artifact ["file_name" ]} " , "r" ) as file :
703+ for line in file :
704+ line = line . strip ()
705+ if line .startswith ("GARDENLINUX_FEATURES=" ):
706+ features = line .removeprefix ("GARDENLINUX_FEATURES=" )
707+ elif line . startswith ( "GARDENLINUX_COMMIT_ID=" ):
708+ commit = line . removeprefix ( "GARDENLINUX_COMMIT_ID=" )
709+ if features and commit : # Break if both values are found
710+ break
711+ break # Break after processing the release file
712+
713+ # Step 3: Push the image manifest
690714 digest = self .push_image_manifest (
691715 architecture ,
692716 cname ,
@@ -697,7 +721,169 @@ def push_from_dir(
697721 manifest_file ,
698722 commit = commit ,
699723 )
724+
725+ # Step 4: Process additional tags if provided
726+ if additional_tags and len (additional_tags ) > 0 :
727+ print (f"DEBUG: Processing { len (additional_tags )} additional tags" )
728+ logger .info (f"Processing { len (additional_tags )} additional tags" )
729+
730+ # Get repository and registry information from container
731+ repo_name = self .container .repository
732+ registry_url = self .container .registry
733+
734+ # Call push_additional_tags_manifest with repository information
735+ self .push_additional_tags_manifest (
736+ architecture ,
737+ cname ,
738+ version ,
739+ additional_tags ,
740+ container = self .container ,
741+ )
742+
743+ return digest
700744 except Exception as e :
701745 print ("Error: " , e )
702746 exit (1 )
703- return digest
747+
748+ def push_additional_tags_manifest (
749+ self , architecture , cname , version , additional_tags , container
750+ ):
751+ """
752+ Push additional tags for an existing manifest using Reggie client
753+
754+ Args:
755+ architecture: Target architecture of the image
756+ cname: Canonical name of the image
757+ version: Version tag for the image
758+ additional_tags: List of additional tags to push
759+ container: Container object
760+ """
761+ try :
762+ print (
763+ f"DEBUG: Processing { len (additional_tags )} additional tags for manifest"
764+ )
765+ print (f"DEBUG: Container: { container } " )
766+ print (f"DEBUG: Container api_prefix: { container .api_prefix } " )
767+ print (f"DEBUG: Container uri: { container .uri } " )
768+ print (f"DEBUG: Container tag: { container .tag } " )
769+ print (f"DEBUG: Container registry: { container .registry } " )
770+ print (f"DEBUG: Container repository: { container .repository } " )
771+
772+ # Source tag is version-cname-architecture as pushed by push_image_manifest
773+ source_tag = f"{ version } -{ cname } -{ architecture } "
774+ manifest_url = container .manifest_url ()
775+
776+ repo_name = manifest_url [manifest_url .find ('/v2/' )+ 3 :manifest_url .find ('/manifests/' )]
777+ if repo_name .startswith ('/' ):
778+ repo_name = repo_name [1 :]
779+
780+ # Ensure registry_url and manifest_url have protocol prefix
781+ use_insecure = getattr (
782+ self , "insecure" , True
783+ ) # Default to True if attribute doesn't exist
784+ protocol = "http" if use_insecure else "https"
785+ registry_url = f"{ protocol } ://{ container .registry } "
786+
787+ print (f"DEBUG: Using source tag: { container .tag } " )
788+ print (f"DEBUG: Using registry URL: { registry_url } " )
789+ print (f"DEBUG: Using repository name: { repo_name } " )
790+ logger .info (f"Using source tag: { container .tag } " )
791+ logger .info (f"Using registry URL: { registry_url } " )
792+ logger .info (f"Using repository name: { repo_name } " )
793+
794+ # Configure client options
795+ client_options = [reggie .WithUserAgent (GL_USER_AGENT_REGISTRY )]
796+
797+ # Initialize reggie client
798+ client = reggie .NewClient (registry_url , * client_options )
799+
800+ # Set authentication token if provided
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+ # Step 1: Get the manifest from the source tag using Reggie
806+ get_manifest_req = client .NewRequest (
807+ "GET" , f"/v2/{ repo_name } /manifests/{ source_tag } "
808+ )
809+
810+ # Set authentication for the GET request
811+ if token :
812+ get_manifest_req .SetAuthToken (token )
813+ elif username and password :
814+ get_manifest_req .SetBasicAuth (username , password )
815+
816+ get_manifest_resp = client .Do (get_manifest_req )
817+
818+ if get_manifest_resp .status_code != 200 :
819+ print (
820+ f"DEBUG: Failed to get source manifest: { get_manifest_resp .status_code } "
821+ f"DEBUG: Failed to get source manifest: { get_manifest_resp .text } "
822+ )
823+ logger .error (
824+ f"Failed to get source manifest: { get_manifest_resp .status_code } "
825+ )
826+ return
827+
828+ # Get the manifest content and content-type
829+ manifest_content = get_manifest_resp .content
830+ content_type = get_manifest_resp .headers .get ("Content-Type" )
831+
832+ print (
833+ f"DEBUG: Successfully retrieved manifest with content: { manifest_content } "
834+ f"DEBUG: Successfully retrieved manifest with content type: { content_type } "
835+
836+ )
837+ logger .info (
838+ f"Successfully retrieved manifest with content: { manifest_content } "
839+ f"Successfully retrieved manifest with content type: { content_type } "
840+ )
841+
842+ # Step 2: For each additional tag, push the manifest using Reggie
843+ for tag in additional_tags :
844+ try :
845+ print (f"DEBUG: Pushing additional tag: { tag } " )
846+
847+ # Create a new PUT request for the tag
848+ put_manifest_req = client .NewRequest (
849+ "PUT" , f"/v2/{ repo_name } /manifests/{ tag } "
850+ )
851+
852+ # Set the content type header to match the original manifest
853+ put_manifest_req .SetHeader ("Content-Type" , content_type )
854+
855+ # Set authentication for the PUT request
856+ if token :
857+ put_manifest_req .SetAuthToken (token )
858+ elif username and password :
859+ put_manifest_req .SetBasicAuth (username , password )
860+
861+ # Set the body to the manifest content
862+ put_manifest_req .SetBody (manifest_content )
863+
864+ print (f"DEBUG: put_manifest_req: { put_manifest_req } " )
865+ print (f"DEBUG: put_manifest_req.body: { put_manifest_req .body } " )
866+ print (f"DEBUG: put_manifest_req.headers: { put_manifest_req .headers } " )
867+
868+ # Use the reggie client to make the request
869+ put_manifest_resp = client .Do (put_manifest_req )
870+
871+ if put_manifest_resp .status_code in [200 , 201 ]:
872+ print (f"DEBUG: Successfully pushed tag { tag } for manifest" )
873+ logger .info (f"Successfully pushed tag { tag } for manifest" )
874+ else :
875+ print (
876+ f"DEBUG: Failed to push tag { tag } for manifest: { put_manifest_resp .status_code } "
877+ f"DEBUG: Failed to push tag { tag } for manifest: { put_manifest_resp .text } "
878+ )
879+ logger .error (
880+ f"Failed to push tag { tag } for manifest: { put_manifest_resp .status_code } "
881+ )
882+
883+ except Exception as e :
884+ print (f"DEBUG: Error pushing tag { tag } for manifest: { str (e )} " )
885+ logger .error (f"Error pushing tag { tag } for manifest: { str (e )} " )
886+
887+ except Exception as e :
888+ print (f"DEBUG: Error in push_additional_tags_manifest: { str (e )} " )
889+ logger .error (f"Error in push_additional_tags_manifest: { str (e )} " )
0 commit comments