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,160 @@ 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+ repo_name = repo_name ,
741+ registry_url = registry_url ,
742+ )
743+
744+ return digest
700745 except Exception as e :
701746 print ("Error: " , e )
702747 exit (1 )
703- return digest
748+
749+ def push_additional_tags_manifest (
750+ self , architecture , cname , version , additional_tags , repo_name , registry_url
751+ ):
752+ """
753+ Push additional tags for an existing manifest using Reggie client
754+
755+ Args:
756+ architecture: Target architecture of the image
757+ cname: Canonical name of the image
758+ version: Version tag for the image
759+ additional_tags: List of additional tags to push
760+ repo_name: Repository name
761+ registry_url: Registry URL
762+ """
763+ try :
764+ print (
765+ f"DEBUG: Processing { len (additional_tags )} additional tags for manifest"
766+ )
767+
768+ # Source tag is version-cname-architecture as pushed by push_image_manifest
769+ source_tag = f"{ version } -{ cname } -{ architecture } "
770+
771+ # Ensure registry_url has protocol prefix
772+ if not registry_url .startswith ("http://" ) and not registry_url .startswith (
773+ "https://"
774+ ):
775+ use_insecure = getattr (
776+ self , "insecure" , True
777+ ) # Default to True if attribute doesn't exist
778+ protocol = "http" if use_insecure else "https"
779+ registry_url = f"{ protocol } ://{ registry_url } "
780+
781+ print (f"DEBUG: Using source tag: { source_tag } " )
782+ print (f"DEBUG: Using repository: { repo_name } " )
783+ print (f"DEBUG: Using registry URL: { registry_url } " )
784+ logger .info (f"Using source tag: { source_tag } " )
785+ logger .info (f"Using repository: { repo_name } " )
786+ logger .info (f"Using registry URL: { registry_url } " )
787+
788+ # Configure client options
789+ client_options = [reggie .WithUserAgent (GL_USER_AGENT_REGISTRY )]
790+
791+ # Initialize reggie client
792+ client = reggie .NewClient (registry_url , * client_options )
793+
794+ # Set authentication token if provided
795+ token = os .getenv ("GL_CLI_REGISTRY_TOKEN" )
796+ if token :
797+ client .SetAuthToken (token )
798+
799+ # Step 1: Get the manifest from the source tag using Reggie
800+ get_manifest_req = client .NewRequest (
801+ "GET" , f"/v2/{ repo_name } /manifests/{ source_tag } "
802+ )
803+ get_manifest_req .headers .update (
804+ {
805+ "Accept" : "application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.v2+json"
806+ }
807+ )
808+
809+ get_manifest_resp = client .Do (get_manifest_req )
810+
811+ if get_manifest_resp .status_code != 200 :
812+ print (
813+ f"DEBUG: Failed to get source manifest: { get_manifest_resp .status_code } "
814+ )
815+ logger .error (
816+ f"Failed to get source manifest: { get_manifest_resp .status_code } "
817+ )
818+ return
819+
820+ # Get the manifest content and content-type
821+ manifest_content = get_manifest_resp .text
822+ content_type = get_manifest_resp .headers .get (
823+ "Content-Type" , "application/vnd.oci.image.manifest.v1+json"
824+ )
825+
826+ print (
827+ f"DEBUG: Successfully retrieved manifest with content type: { content_type } "
828+ )
829+ logger .info (
830+ f"Successfully retrieved manifest with content type: { content_type } "
831+ )
832+
833+ # Step 2: For each additional tag, push the manifest
834+ for tag in additional_tags :
835+ try :
836+ print (f"DEBUG: Pushing additional tag: { tag } " )
837+
838+ # Create a new PUT request for the tag
839+ put_manifest_req = client .NewRequest (
840+ "PUT" , f"/v2/{ repo_name } /manifests/{ tag } "
841+ )
842+
843+ # Set the content type header to match the original manifest
844+ put_manifest_req .headers .update ({"Content-Type" : content_type })
845+
846+ # Use requests for the PUT operation since reggie has issues with the data parameter
847+ import requests
848+
849+ put_url = f"{ registry_url } /v2/{ repo_name } /manifests/{ tag } "
850+
851+ # Use getattr to safely access insecure flag
852+ use_insecure = getattr (self , "insecure" , True )
853+ verify = not use_insecure
854+
855+ # Use requests for the PUT operation
856+ tag_resp = requests .put (
857+ put_url ,
858+ data = manifest_content ,
859+ headers = dict (put_manifest_req .headers ),
860+ verify = verify ,
861+ )
862+
863+ if tag_resp .status_code in [200 , 201 ]:
864+ print (f"DEBUG: Successfully pushed tag { tag } for manifest" )
865+ logger .info (f"Successfully pushed tag { tag } for manifest" )
866+ else :
867+ print (
868+ f"DEBUG: Failed to push tag { tag } for manifest: { tag_resp .status_code } "
869+ )
870+ logger .error (
871+ f"Failed to push tag { tag } for manifest: { tag_resp .status_code } "
872+ )
873+
874+ except Exception as e :
875+ print (f"DEBUG: Error pushing tag { tag } for manifest: { str (e )} " )
876+ logger .error (f"Error pushing tag { tag } for manifest: { str (e )} " )
877+
878+ except Exception as e :
879+ print (f"DEBUG: Error in push_additional_tags_manifest: { str (e )} " )
880+ logger .error (f"Error in push_additional_tags_manifest: { str (e )} " )
0 commit comments