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,165 @@ 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+ # Call push_additional_tags_manifest with repository information
731+ self .push_additional_tags_manifest (
732+ architecture ,
733+ cname ,
734+ version ,
735+ additional_tags ,
736+ container = self .container ,
737+ )
738+
739+ return digest
700740 except Exception as e :
701741 print ("Error: " , e )
702742 exit (1 )
703- return digest
743+
744+ def push_additional_tags_manifest (
745+ self , architecture , cname , version , additional_tags , container
746+ ):
747+ """
748+ Push additional tags for an existing manifest using Reggie client
749+
750+ Args:
751+ architecture: Target architecture of the image
752+ cname: Canonical name of the image
753+ version: Version tag for the image
754+ additional_tags: List of additional tags to push
755+ container: Container object
756+ """
757+ try :
758+ print (
759+ f"DEBUG: Processing { len (additional_tags )} additional tags for manifest"
760+ )
761+ print (f"DEBUG: Container: { container } " )
762+ print (f"DEBUG: Container api_prefix: { container .api_prefix } " )
763+ print (f"DEBUG: Container uri: { container .uri } " )
764+ print (f"DEBUG: Container tag: { container .tag } " )
765+ print (f"DEBUG: Container registry: { container .registry } " )
766+ print (f"DEBUG: Container repository: { container .repository } " )
767+
768+ # Source tag is version-cname-architecture as pushed by push_image_manifest
769+ source_tag = f"{ version } -{ cname } -{ architecture } "
770+ manifest_url = container .manifest_url ()
771+
772+ repo_name = manifest_url [manifest_url .find ('/v2/' )+ 3 :manifest_url .find ('/manifests/' )]
773+ if repo_name .startswith ('/' ):
774+ repo_name = repo_name [1 :]
775+
776+ # Ensure registry_url and manifest_url have protocol prefix
777+ use_insecure = getattr (
778+ self , "insecure" , True
779+ ) # Default to True if attribute doesn't exist
780+ protocol = "http" if use_insecure else "https"
781+ registry_url = f"{ protocol } ://{ container .registry } "
782+
783+ print (f"DEBUG: Using source tag: { container .tag } " )
784+ print (f"DEBUG: Using registry URL: { registry_url } " )
785+ print (f"DEBUG: Using repository name: { repo_name } " )
786+ logger .info (f"Using source tag: { container .tag } " )
787+ logger .info (f"Using registry URL: { registry_url } " )
788+ logger .info (f"Using repository name: { repo_name } " )
789+
790+ # Configure client options
791+ client_options = [reggie .WithUserAgent (GL_USER_AGENT_REGISTRY )]
792+
793+ # Initialize reggie client
794+ client = reggie .NewClient (f"{ registry_url } /{ repo_name } " , * client_options )
795+
796+ # Set authentication token if provided
797+ token = os .getenv ("GL_CLI_REGISTRY_TOKEN" )
798+ username = os .getenv ("GL_CLI_REGISTRY_USERNAME" )
799+ password = os .getenv ("GL_CLI_REGISTRY_PASSWORD" )
800+
801+ # Step 1: Get the manifest from the source tag using Reggie
802+ get_manifest_req = client .NewRequest (
803+ "GET" , f"/v2/{ repo_name } /manifests/{ source_tag } "
804+ )
805+
806+ # Set authentication for the GET request
807+ if token :
808+ get_manifest_req .SetAuthToken (token )
809+ elif username and password :
810+ get_manifest_req .SetBasicAuth (username , password )
811+
812+ get_manifest_resp = client .Do (get_manifest_req )
813+
814+ if get_manifest_resp .status_code != 200 :
815+ print (
816+ f"DEBUG: Failed to get source manifest: { get_manifest_resp .status_code } "
817+ f"DEBUG: Failed to get source manifest: { get_manifest_resp .text } "
818+ )
819+ logger .error (
820+ f"Failed to get source manifest: { get_manifest_resp .status_code } "
821+ )
822+ return
823+
824+ # Get the manifest content and content-type
825+ manifest_content = get_manifest_resp .content
826+ content_type = get_manifest_resp .headers .get ("Content-Type" )
827+
828+ print (
829+ f"DEBUG: Successfully retrieved manifest with content: { manifest_content } "
830+ f"DEBUG: Successfully retrieved manifest with content type: { content_type } "
831+
832+ )
833+ logger .info (
834+ f"Successfully retrieved manifest with content: { manifest_content } "
835+ f"Successfully retrieved manifest with content type: { content_type } "
836+ )
837+
838+ # Step 2: For each additional tag, push the manifest using Reggie
839+ for tag in additional_tags :
840+ try :
841+ print (f"DEBUG: Pushing additional tag: { tag } " )
842+
843+ # Create a new PUT request for the tag
844+ put_manifest_req = client .NewRequest (
845+ "PUT" , f"/v2/{ repo_name } /manifests/{ tag } "
846+ )
847+
848+ # Set the content type header to match the original manifest
849+ put_manifest_req .SetHeader ("Content-Type" , content_type )
850+
851+ # Set authentication for the PUT request
852+ if token :
853+ put_manifest_req .SetAuthToken (token )
854+ elif username and password :
855+ put_manifest_req .SetBasicAuth (username , password )
856+
857+ # Set the body to the manifest content
858+ put_manifest_req .SetBody (manifest_content )
859+
860+ print (f"DEBUG: put_manifest_req: { put_manifest_req } " )
861+ print (f"DEBUG: put_manifest_req.body: { put_manifest_req .body } " )
862+ print (f"DEBUG: put_manifest_req.headers: { put_manifest_req .headers } " )
863+
864+ # Use the reggie client to make the request
865+ put_manifest_resp = client .Do (put_manifest_req )
866+
867+ if put_manifest_resp .status_code in [200 , 201 ]:
868+ print (f"DEBUG: Successfully pushed tag { tag } for manifest" )
869+ logger .info (f"Successfully pushed tag { tag } for manifest" )
870+ else :
871+ print (
872+ f"DEBUG: Failed to push tag { tag } for manifest: { put_manifest_resp .status_code } "
873+ f"DEBUG: Failed to push tag { tag } for manifest: { put_manifest_resp .text } "
874+ )
875+ logger .error (
876+ f"Failed to push tag { tag } for manifest: { put_manifest_resp .status_code } "
877+ )
878+
879+ except Exception as e :
880+ print (f"DEBUG: Error pushing tag { tag } for manifest: { str (e )} " )
881+ logger .error (f"Error pushing tag { tag } for manifest: { str (e )} " )
882+
883+ except Exception as e :
884+ print (f"DEBUG: Error in push_additional_tags_manifest: { str (e )} " )
885+ logger .error (f"Error in push_additional_tags_manifest: { str (e )} " )
0 commit comments