@@ -105,54 +105,100 @@ def get_oci_cmd(repository, version):
105105 private_registry = re .match (private_ecr_pattern , repository ).groupdict ()
106106 public_registry = re .match (public_ecr_pattern , repository ).groupdict ()
107107
108+ # Build helm pull command as array
109+ helm_cmd = ['helm' , 'pull' , repository , '--version' , version , '--untar' ]
110+
108111 if private_registry ['registry' ] is not None :
109112 logger .info ("Found AWS private repository" )
110- cmnd = [
111- f"aws ecr get-login-password --region { private_registry ['region' ]} | " \
112- f"helm registry login --username AWS --password-stdin { private_registry ['registry' ]} ; helm pull { repository } --version { version } --untar"
113- ]
113+ ecr_login = ['aws' , 'ecr' , 'get-login-password' , '--region' , private_registry ['region' ]]
114+ helm_registry_login = ['helm' , 'registry' , 'login' , '--username' , 'AWS' , '--password-stdin' , private_registry ['registry' ]]
115+ return {'ecr_login' : ecr_login , 'registry_login' : helm_registry_login , 'helm' : helm_cmd }
114116 elif public_registry ['registry' ] is not None :
115117 logger .info ("Found AWS public repository, will use default region as deployment" )
116118 region = os .environ .get ('AWS_REGION' , 'us-east-1' )
117119
118120 if is_ecr_public_available (region ):
119- cmnd = [
120- f" aws ecr-public get-login-password --region us-east-1 | " \
121- f" helm registry login --username AWS --password-stdin { public_registry ['registry' ]} ; helm pull { repository } --version { version } --untar"
122- ]
121+ # Public ECR auth is always in us-east-1: https://docs.aws.amazon.com/AmazonECR/latest/public/public-registry-auth.html
122+ ecr_login = [ ' aws' , ' ecr-public' , ' get-login-password' , ' --region' , ' us-east-1' ]
123+ helm_registry_login = [ ' helm' , ' registry' , ' login' , ' --username' , ' AWS' , ' --password-stdin' , public_registry ['registry' ]]
124+ return { 'ecr_login' : ecr_login , 'helm_registry_login' : helm_registry_login , 'helm' : helm_cmd }
123125 else :
124- # `aws ecr-public get- login-password` and `helm registry login` not required as ecr public is not available in current region
126+ # No login required for public ECR in non-aws regions
125127 # see https://helm.sh/docs/helm/helm_registry_login/
126- cmnd = [ f" helm pull { repository } --version { version } --untar" ]
128+ return { ' helm' : helm_cmd }
127129 else :
128130 logger .error ("OCI repository format not recognized, falling back to helm pull" )
129- cmnd = [f"helm pull { repository } --version { version } --untar" ]
130-
131- return cmnd
131+ return {'helm' : helm_cmd }
132132
133133
134- def get_chart_from_oci (tmpdir , repository = None , version = None ):
134+ def get_chart_from_oci (tmpdir , repository = None , version = None ):
135+ import subprocess
136+ from subprocess import Popen , PIPE
135137
136- cmnd = get_oci_cmd (repository , version )
138+ commands = get_oci_cmd (repository , version )
137139
138140 maxAttempts = 3
139141 retry = maxAttempts
140142 while retry > 0 :
141143 try :
142- logger .info (cmnd )
143- output = subprocess .check_output (cmnd , stderr = subprocess .STDOUT , cwd = tmpdir , shell = True )
144+ # Execute login commands if needed
145+ if 'ecr_login' in commands and 'helm_registry_login' in commands :
146+ logger .info ("Running login command: %s" , commands ['ecr_login' ])
147+ logger .info ("Running registry login command: %s" , commands ['helm_registry_login' ])
148+
149+ # Start first process: aws ecr get-login-password
150+ # NOTE: We do NOT call p1.wait() here before starting p2.
151+ # Doing so could deadlock if p1's output fills the pipe buffer
152+ # before p2 starts reading. Instead, start p2 immediately so it
153+ # can consume p1's stdout as it's produced.
154+ p1 = Popen (commands ['ecr_login' ], stdout = PIPE , stderr = PIPE , cwd = tmpdir )
155+
156+ # Start second process: helm registry login
157+ p2 = Popen (commands ['helm_registry_login' ], stdin = p1 .stdout , stdout = PIPE , stderr = PIPE , cwd = tmpdir )
158+ p1 .stdout .close () # Allow p1 to receive SIGPIPE if p2 exits early
159+
160+ # Wait for p2 to finish first (ensures full pipeline completes)
161+ _ , p2_err = p2 .communicate ()
162+
163+ # Now wait for p1 so we have a complete stderr and an exit code
164+ p1 .wait ()
165+
166+ # Handle p1 failure
167+ if p1 .returncode != 0 :
168+ p1_err = p1 .stderr .read ().decode ('utf-8' , errors = 'replace' ) if p1 .stderr else ''
169+ logger .error (
170+ "ECR get-login-password failed for repository %s. Error: %s" ,
171+ repository ,
172+ p1_err or "No error details"
173+ )
174+ raise subprocess .CalledProcessError (p1 .returncode , commands ['ecr_login' ], p1_err .encode ())
175+
176+ # Handle p2 failure
177+ if p2 .returncode != 0 :
178+ logger .error (
179+ "Helm registry authentication failed for repository %s. Error: %s" ,
180+ repository ,
181+ p2_err .decode ('utf-8' , errors = 'replace' ) or "No error details"
182+ )
183+ raise subprocess .CalledProcessError (p2 .returncode , commands ['helm_registry_login' ], p2_err )
184+
185+ # Execute helm pull command
186+ logger .info ("Running helm command: %s" , commands ['helm' ])
187+ output = subprocess .check_output (commands ['helm' ], stderr = subprocess .STDOUT , cwd = tmpdir )
144188 logger .info (output )
145189
146190 # effectively returns "$tmpDir/$lastPartOfOCIUrl", because this is how helm pull saves OCI artifact.
147191 # Eg. if we have oci://9999999999.dkr.ecr.us-east-1.amazonaws.com/foo/bar/pet-service repository, helm saves artifact under $tmpDir/pet-service
148192 return os .path .join (tmpdir , repository .rpartition ('/' )[- 1 ])
193+
149194 except subprocess .CalledProcessError as exc :
150195 output = exc .output
151196 if b'Broken pipe' in output :
152197 retry = retry - 1
153198 logger .info ("Broken pipe, retries left: %s" % retry )
154199 else :
155200 raise Exception (output )
201+
156202 raise Exception (f'Operation failed after { maxAttempts } attempts: { output } ' )
157203
158204
0 commit comments