Skip to content

Commit 891b510

Browse files
committed
fix(eks-v2-alpha): remove usage of shell=True in helm commands
1 parent 4eda24b commit 891b510

File tree

12 files changed

+110
-49
lines changed

12 files changed

+110
-49
lines changed

packages/@aws-cdk/aws-eks-v2-alpha/lib/kubectl-handler/helm/__init__.py

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -105,60 +105,103 @@ 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+
from subprocess import Popen, PIPE
135136

136-
cmnd = get_oci_cmd(repository, version)
137+
commands = get_oci_cmd(repository, version)
137138

138139
maxAttempts = 3
139140
retry = maxAttempts
140141
while retry > 0:
141142
try:
142-
logger.info(cmnd)
143-
output = subprocess.check_output(cmnd, stderr=subprocess.STDOUT, cwd=tmpdir, shell=True)
143+
# Execute login commands if needed
144+
if 'ecr_login' in commands and 'helm_registry_login' in commands:
145+
logger.info("Running login command: %s", commands['ecr_login'])
146+
logger.info("Running registry login command: %s", commands['helm_registry_login'])
147+
148+
# Start first process: aws ecr get-login-password
149+
# NOTE: We do NOT call p1.wait() here before starting p2.
150+
# Doing so could deadlock if p1's output fills the pipe buffer
151+
# before p2 starts reading. Instead, start p2 immediately so it
152+
# can consume p1's stdout as it's produced.
153+
p1 = Popen(commands['ecr_login'], stdout=PIPE, stderr=PIPE, cwd=tmpdir)
154+
155+
# Start second process: helm registry login
156+
p2 = Popen(commands['helm_registry_login'], stdin=p1.stdout, stdout=PIPE, stderr=PIPE, cwd=tmpdir)
157+
p1.stdout.close() # Allow p1 to receive SIGPIPE if p2 exits early
158+
159+
# Wait for p2 to finish first (ensures full pipeline completes)
160+
_, p2_err = p2.communicate()
161+
162+
# Now wait for p1 so we have a complete stderr and an exit code
163+
p1.wait()
164+
165+
# Handle p1 failure
166+
if p1.returncode != 0:
167+
p1_err = p1.stderr.read().decode('utf-8', errors='replace') if p1.stderr else ''
168+
logger.error(
169+
"ECR get-login-password failed for repository %s. Error: %s",
170+
repository,
171+
p1_err or "No error details"
172+
)
173+
raise subprocess.CalledProcessError(p1.returncode, commands['ecr_login'], p1_err.encode())
174+
175+
# Handle p2 failure
176+
if p2.returncode != 0:
177+
logger.error(
178+
"Helm registry authentication failed for repository %s. Error: %s",
179+
repository,
180+
p2_err.decode('utf-8', errors='replace') or "No error details"
181+
)
182+
raise subprocess.CalledProcessError(p2.returncode, commands['helm_registry_login'], p2_err)
183+
184+
# Execute helm pull command
185+
logger.info("Running helm command: %s", commands['helm'])
186+
output = subprocess.check_output(commands['helm'], stderr=subprocess.STDOUT, cwd=tmpdir)
144187
logger.info(output)
145188

146189
# effectively returns "$tmpDir/$lastPartOfOCIUrl", because this is how helm pull saves OCI artifact.
147190
# 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
148191
return os.path.join(tmpdir, repository.rpartition('/')[-1])
192+
149193
except subprocess.CalledProcessError as exc:
150194
output = exc.output
151195
if b'Broken pipe' in output:
152196
retry = retry - 1
153197
logger.info("Broken pipe, retries left: %s" % retry)
154198
else:
155199
raise Exception(output)
200+
156201
raise Exception(f'Operation failed after {maxAttempts} attempts: {output}')
157202

158203

159204
def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False, timeout = None, create_namespace = None, skip_crds = False, atomic = False):
160-
import subprocess
161-
162205
cmnd = ['helm', verb, release]
163206
if not chart is None:
164207
cmnd.append(chart)
Lines changed: 34 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-eks-v2-alpha/test/integ.helm-chart-logging.js.snapshot/aws-cdk-eks-v2-alpha-helm-logging-test.assets.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-eks-v2-alpha/test/integ.helm-chart-logging.js.snapshot/aws-cdk-eks-v2-alpha-helm-logging-test.template.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@
767767
"S3Bucket": {
768768
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
769769
},
770-
"S3Key": "872f17c2bcde7a48a7d2a0dfc5e58a15d25a49c136492a93249a39ce93062069.zip"
770+
"S3Key": "cffedcef6c55857394770138a02cbf72f8f237510a9dc98633bd500b911fe7b6.zip"
771771
},
772772
"Description": "onEvent handler for EKS kubectl resource provider",
773773
"Environment": {

0 commit comments

Comments
 (0)