Skip to content

Commit a96d69c

Browse files
authored
fix(triggers): unable to trigger two lambda functions (#22124)
Closes #22110 This PR updates the custom resource provider in the Trigger construct to use `addToRolePolicy` to add new statements to the IAM policy, rather than the constructor, so it can be used to trigger more than one lambda function. Also adds a one-minute retry in the custom resource provider lambda function as IAM policy changes take some time to propagate. Also refactored tests to increase coverage. ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 320cc25 commit a96d69c

File tree

17 files changed

+621
-289
lines changed

17 files changed

+621
-289
lines changed

Diff for: packages/@aws-cdk/triggers/lib/lambda/index.ts

+27-3
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,35 @@ import * as AWS from 'aws-sdk';
55

66
export type InvokeFunction = (functionName: string) => Promise<AWS.Lambda.InvocationResponse>;
77

8-
export const invoke: InvokeFunction = async functionName => {
8+
export const invoke: InvokeFunction = async (functionName) => {
99
const lambda = new AWS.Lambda();
1010
const invokeRequest = { FunctionName: functionName };
1111
console.log({ invokeRequest });
12-
const invokeResponse = await lambda.invoke(invokeRequest).promise();
12+
13+
// IAM policy changes can take some time to fully propagate
14+
// Therefore, retry for up to one minute
15+
16+
let retryCount = 0;
17+
const delay = 5000;
18+
19+
let invokeResponse;
20+
while (true) {
21+
try {
22+
invokeResponse = await lambda.invoke(invokeRequest).promise();
23+
break;
24+
} catch (error) {
25+
if (error instanceof Error && (error as AWS.AWSError).code === 'AccessDeniedException' && retryCount < 12) {
26+
retryCount++;
27+
await new Promise((resolve) => {
28+
setTimeout(resolve, delay);
29+
});
30+
continue;
31+
}
32+
33+
throw error;
34+
}
35+
}
36+
1337
console.log({ invokeResponse });
1438
return invokeResponse;
1539
};
@@ -37,7 +61,7 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent
3761
if (invokeResponse.FunctionError) {
3862
throw new Error(parseError(invokeResponse.Payload?.toString()));
3963
}
40-
};
64+
}
4165

4266
/**
4367
* Parse the error message from the lambda function.

Diff for: packages/@aws-cdk/triggers/lib/trigger.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,12 @@ export class Trigger extends Construct implements ITrigger {
8282
const provider = CustomResourceProvider.getOrCreateProvider(this, 'AWSCDK.TriggerCustomResourceProvider', {
8383
runtime: CustomResourceProviderRuntime.NODEJS_14_X,
8484
codeDirectory: join(__dirname, 'lambda'),
85-
policyStatements: [
86-
{
87-
Effect: 'Allow',
88-
Action: ['lambda:InvokeFunction'],
89-
Resource: [`${props.handler.functionArn}:*`],
90-
},
91-
],
85+
});
86+
87+
provider.addToRolePolicy({
88+
Effect: 'Allow',
89+
Action: ['lambda:InvokeFunction'],
90+
Resource: [`${props.handler.functionArn}:*`],
9291
});
9392

9493
new CustomResource(this, 'Default', {
@@ -133,4 +132,4 @@ export enum TriggerInvalidation {
133132
* of the AWS Lambda function, which gets recreated every time the handler changes.
134133
*/
135134
HANDLER_CHANGE = 'WHEN_FUNCTION_CHANGES',
136-
}
135+
}

Diff for: packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.assets.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
{
2-
"version": "20.0.0",
2+
"version": "21.0.0",
33
"files": {
4-
"f942cf8dea09e8b74bc8da73a643a8b2639fe7f93c6eb60e338c56224decd68a": {
4+
"ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4": {
55
"source": {
6-
"path": "asset.f942cf8dea09e8b74bc8da73a643a8b2639fe7f93c6eb60e338c56224decd68a",
6+
"path": "asset.ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4",
77
"packaging": "zip"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "f942cf8dea09e8b74bc8da73a643a8b2639fe7f93c6eb60e338c56224decd68a.zip",
12+
"objectKey": "ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4.zip",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}
1616
},
17-
"e1b247eb3c41fa99451971925f9a24d2d7e5dec4d36ed03c43640d0b477a41ce": {
17+
"4155391c7a2ef259603bc3a1d18d0bad9a2478b51ed773d2c06efab5a0a51c56": {
1818
"source": {
1919
"path": "MyStack.template.json",
2020
"packaging": "file"
2121
},
2222
"destinations": {
2323
"current_account-current_region": {
2424
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
25-
"objectKey": "e1b247eb3c41fa99451971925f9a24d2d7e5dec4d36ed03c43640d0b477a41ce.json",
25+
"objectKey": "4155391c7a2ef259603bc3a1d18d0bad9a2478b51ed773d2c06efab5a0a51c56.json",
2626
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
2727
}
2828
}

Diff for: packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json

+97-1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,28 @@
133133
]
134134
}
135135
]
136+
},
137+
{
138+
"Effect": "Allow",
139+
"Action": [
140+
"lambda:InvokeFunction"
141+
],
142+
"Resource": [
143+
{
144+
"Fn::Join": [
145+
"",
146+
[
147+
{
148+
"Fn::GetAtt": [
149+
"MySecondFunction0F0B51EB",
150+
"Arn"
151+
]
152+
},
153+
":*"
154+
]
155+
]
156+
}
157+
]
136158
}
137159
]
138160
}
@@ -147,7 +169,7 @@
147169
"S3Bucket": {
148170
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
149171
},
150-
"S3Key": "f942cf8dea09e8b74bc8da73a643a8b2639fe7f93c6eb60e338c56224decd68a.zip"
172+
"S3Key": "ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4.zip"
151173
},
152174
"Timeout": 900,
153175
"MemorySize": 128,
@@ -163,6 +185,80 @@
163185
"DependsOn": [
164186
"AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A"
165187
]
188+
},
189+
"MySecondFunctionServiceRole5B930841": {
190+
"Type": "AWS::IAM::Role",
191+
"Properties": {
192+
"AssumeRolePolicyDocument": {
193+
"Statement": [
194+
{
195+
"Action": "sts:AssumeRole",
196+
"Effect": "Allow",
197+
"Principal": {
198+
"Service": "lambda.amazonaws.com"
199+
}
200+
}
201+
],
202+
"Version": "2012-10-17"
203+
},
204+
"ManagedPolicyArns": [
205+
{
206+
"Fn::Join": [
207+
"",
208+
[
209+
"arn:",
210+
{
211+
"Ref": "AWS::Partition"
212+
},
213+
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
214+
]
215+
]
216+
}
217+
]
218+
}
219+
},
220+
"MySecondFunction0F0B51EB": {
221+
"Type": "AWS::Lambda::Function",
222+
"Properties": {
223+
"Code": {
224+
"ZipFile": "exports.handler = function() { console.log(\"hello\"); };"
225+
},
226+
"Role": {
227+
"Fn::GetAtt": [
228+
"MySecondFunctionServiceRole5B930841",
229+
"Arn"
230+
]
231+
},
232+
"Handler": "index.handler",
233+
"Runtime": "nodejs16.x"
234+
},
235+
"DependsOn": [
236+
"MySecondFunctionServiceRole5B930841"
237+
]
238+
},
239+
"MySecondFunctionTrigger8C61EC28": {
240+
"Type": "Custom::Trigger",
241+
"Properties": {
242+
"ServiceToken": {
243+
"Fn::GetAtt": [
244+
"AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91",
245+
"Arn"
246+
]
247+
},
248+
"HandlerArn": {
249+
"Ref": "MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362"
250+
}
251+
},
252+
"UpdateReplacePolicy": "Delete",
253+
"DeletionPolicy": "Delete"
254+
},
255+
"MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362": {
256+
"Type": "AWS::Lambda::Version",
257+
"Properties": {
258+
"FunctionName": {
259+
"Ref": "MySecondFunction0F0B51EB"
260+
}
261+
}
166262
}
167263
},
168264
"Parameters": {

Diff for: packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4/__entrypoint__.js

+144
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4/index.js

+73
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)