From b823a874baf2875234ea2ccd6f2d416864409d9e Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Wed, 23 Nov 2022 16:58:26 +0100 Subject: [PATCH 1/8] feat(trigger): Allow trigger to work with Lambda functions with long timeouts --- .../@aws-cdk/triggers/lib/lambda/index.ts | 22 +++++--- packages/@aws-cdk/triggers/lib/trigger.ts | 17 ++++++ .../triggers/test/trigger-handler.test.ts | 2 + .../@aws-cdk/triggers/test/triggers.test.ts | 52 ++++++++++++++++++- 4 files changed, 85 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/triggers/lib/lambda/index.ts b/packages/@aws-cdk/triggers/lib/lambda/index.ts index 8fb4db66a3cc3..6f3bd38bcc42e 100644 --- a/packages/@aws-cdk/triggers/lib/lambda/index.ts +++ b/packages/@aws-cdk/triggers/lib/lambda/index.ts @@ -3,11 +3,16 @@ // eslint-disable-next-line import/no-extraneous-dependencies import * as AWS from 'aws-sdk'; -export type InvokeFunction = (functionName: string) => Promise; +export type InvokeFunction = (functionName: string, invocationType: string, timeout: number) => Promise; -export const invoke: InvokeFunction = async (functionName) => { - const lambda = new AWS.Lambda(); - const invokeRequest = { FunctionName: functionName }; +export const invoke: InvokeFunction = async (functionName, invocationType, timeout) => { + const lambda = new AWS.Lambda({ + httpOptions: { + timeout, + }, + }); + + const invokeRequest = { FunctionName: functionName, InvocationType: invocationType }; console.log({ invokeRequest }); // IAM policy changes can take some time to fully propagate @@ -51,7 +56,10 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent throw new Error('The "HandlerArn" property is required'); } - const invokeResponse = await invoke(handlerArn); + const invocationType = event.ResourceProperties.InvocationType; + const timeout = event.ResourceProperties.Timeout; + + const invokeResponse = await invoke(handlerArn, invocationType, timeout); if (invokeResponse.StatusCode !== 200) { throw new Error(`Trigger handler failed with status code ${invokeResponse.StatusCode}`); @@ -68,7 +76,9 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent */ function parseError(payload: string | undefined): string { console.log(`Error payload: ${payload}`); - if (!payload) { return 'unknown handler error'; } + if (!payload) { + return 'unknown handler error'; + } try { const error = JSON.parse(payload); const concat = [error.errorMessage, error.trace].filter(x => x).join('\n'); diff --git a/packages/@aws-cdk/triggers/lib/trigger.ts b/packages/@aws-cdk/triggers/lib/trigger.ts index 6febb482966cf..38478dc415434 100644 --- a/packages/@aws-cdk/triggers/lib/trigger.ts +++ b/packages/@aws-cdk/triggers/lib/trigger.ts @@ -2,6 +2,7 @@ import { join } from 'path'; import * as lambda from '@aws-cdk/aws-lambda'; import { CustomResource, CustomResourceProvider, CustomResourceProviderRuntime } from '@aws-cdk/core'; import { Construct, IConstruct, Node } from 'constructs'; +import { Duration } from '../../core'; /** * Interface for triggers. @@ -69,6 +70,20 @@ export interface TriggerProps extends TriggerOptions { * The AWS Lambda function of the handler to execute. */ readonly handler: lambda.Function; + + /** + * The invocation type to invoke the Lambda function with. + * + * @default RequestResponse + */ + readonly invocationType?: 'RequestResponse' | 'Event' | 'DryRun'; + + /** + * The timeout of the invocation call of the Lambda function to be triggered. + * + * @default Duration.minutes(2) + */ + readonly timeout?: Duration; } /** @@ -95,6 +110,8 @@ export class Trigger extends Construct implements ITrigger { serviceToken: provider.serviceToken, properties: { HandlerArn: handlerArn, + InvocationType: props.invocationType ?? 'RequestResponse', + Timeout: props.timeout?.toMilliseconds() ?? Duration.minutes(2), }, }); diff --git a/packages/@aws-cdk/triggers/test/trigger-handler.test.ts b/packages/@aws-cdk/triggers/test/trigger-handler.test.ts index 18eea6a881ceb..bbfe4f2062748 100644 --- a/packages/@aws-cdk/triggers/test/trigger-handler.test.ts +++ b/packages/@aws-cdk/triggers/test/trigger-handler.test.ts @@ -29,6 +29,8 @@ const mockRequest = { ResourceProperties: { ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:MyFunction', HandlerArn: handlerArn, + Timeout: 600, + InvocationType: 'Event', }, RequestId: 'MyRequestId', ResourceType: 'Custom::Trigger', diff --git a/packages/@aws-cdk/triggers/test/triggers.test.ts b/packages/@aws-cdk/triggers/test/triggers.test.ts index cc4edb610bade..1653978519016 100644 --- a/packages/@aws-cdk/triggers/test/triggers.test.ts +++ b/packages/@aws-cdk/triggers/test/triggers.test.ts @@ -1,10 +1,10 @@ import { Template } from '@aws-cdk/assertions'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; -import { Stack } from '@aws-cdk/core'; +import { Duration, Stack } from '@aws-cdk/core'; import * as triggers from '../lib'; -test('minimal', () => { +test('minimal trigger function', () => { // GIVEN const stack = new Stack(); @@ -89,3 +89,51 @@ test('multiple functions', () => { const triggerIamRole = roles.AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A; expect(triggerIamRole.Properties.Policies[0].PolicyDocument.Statement.length).toBe(2); }); + +test('minimal trigger', () => { + // GIVEN + const stack = new Stack(); + const func = new lambda.Function(stack, 'MyFunction', { + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_14_X, + code: lambda.Code.fromInline('foo'), + }); + + // WHEN + new triggers.Trigger(stack, 'MyTrigger', { + handler: func, + }); + + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Lambda::Function', {}); + template.hasResourceProperties('Custom::Trigger', { + HandlerArn: { Ref: 'MyFunctionCurrentVersion197490AF2e4e06d52af2bb609d8c23243d665966' }, + }); +}); + +test('trigger with optional properties', () => { + // GIVEN + const stack = new Stack(); + const func = new lambda.Function(stack, 'MyFunction', { + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_14_X, + code: lambda.Code.fromInline('foo'), + }); + + // WHEN + new triggers.Trigger(stack, 'MyTrigger', { + handler: func, + timeout: Duration.minutes(10), + invocationType: 'Event', + }); + + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Lambda::Function', {}); + template.hasResourceProperties('Custom::Trigger', { + HandlerArn: { Ref: 'MyFunctionCurrentVersion197490AF2e4e06d52af2bb609d8c23243d665966' }, + Timeout: 600000, + InvocationType: 'Event', + }); +}); From 0c3b70fdc7928e83c9961c5f21f3b6d1ac2e02df Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Wed, 23 Nov 2022 17:23:15 +0100 Subject: [PATCH 2/8] add documentation & update integration test --- packages/@aws-cdk/triggers/README.md | 27 +++++++++++++++++++ .../MyStack.template.json | 9 +++++++ 2 files changed, 36 insertions(+) diff --git a/packages/@aws-cdk/triggers/README.md b/packages/@aws-cdk/triggers/README.md index 2c86e946c1941..ca9825635bbc7 100644 --- a/packages/@aws-cdk/triggers/README.md +++ b/packages/@aws-cdk/triggers/README.md @@ -39,6 +39,33 @@ new triggers.TriggerFunction(stack, 'MyTrigger', { In the above example, the AWS Lambda function defined in `myLambdaFunction` will be invoked when the stack is deployed. +It is also possible to trigger a predefined Lambda function by using the `Trigger` construct: + +```ts +import * as lambda from '@aws-cdk/aws-lambda'; +import * as triggers from '@aws-cdk/triggers'; +import { Stack } from '@aws-cdk/core'; + +declare const stack: Stack; + +const func = new lambda.Function(stack, 'MyFunction', { + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_14_X, + code: lambda.Code.fromInline('foo'), +}); + +new triggers.Trigger(stack, 'MyTrigger', { + handler: func, + timeout: Duration.minutes(10), + invocationType: 'Event', +}); +``` + +Addition properties can be used to fine-tune the behaviour of the trigger. +The `timeout` property can be used to determine how long the invocation of the function should take. +The `invocationType` property can be used to change the invocation type of the function. +This might be useful in scenarios where a fire-and-forget strategy for invoking the function is sufficient. + ## Trigger Failures If the trigger handler fails (e.g. an exception is raised), the CloudFormation diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json index fde860ed9ad4b..fc79f09e5b7f0 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json @@ -70,6 +70,15 @@ }, "HandlerArn": { "Ref": "MyFunctionCurrentVersion197490AF2cb2bc11080c1ef11d3b49c1f1603957" + }, + "InvocationType": "RequestResponse", + "Timeout": { + "amount": 2, + "unit": { + "label": "minutes", + "isoLabel": "M", + "inMillis": 60000 + } } }, "DependsOn": [ From 814ec8b7810f08c40dd6a3f83107ac3ee59a01e3 Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Wed, 23 Nov 2022 17:36:22 +0100 Subject: [PATCH 3/8] add trigger consruct with optional parameters to integration test --- .../MyStack.template.json | 100 ++++++++++++++++-- .../@aws-cdk/triggers/test/integ.triggers.ts | 18 +++- 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json index fc79f09e5b7f0..153cc6d89ac89 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json @@ -3,13 +3,13 @@ "Topic198E71B3E": { "Type": "AWS::SNS::Topic", "DependsOn": [ - "MyFunctionTriggerDB129D7B" + "MyTriggerFunctionTrigger5424E7A7" ] }, "Topic269377B75": { "Type": "AWS::SNS::Topic" }, - "MyFunctionServiceRole3C357FF2": { + "MyTriggerFunctionServiceRole1BB78C29": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -40,7 +40,7 @@ ] } }, - "MyFunction3BAA72D1": { + "MyTriggerFunction056842F6": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { @@ -48,7 +48,7 @@ }, "Role": { "Fn::GetAtt": [ - "MyFunctionServiceRole3C357FF2", + "MyTriggerFunctionServiceRole1BB78C29", "Arn" ] }, @@ -56,10 +56,10 @@ "Runtime": "nodejs14.x" }, "DependsOn": [ - "MyFunctionServiceRole3C357FF2" + "MyTriggerFunctionServiceRole1BB78C29" ] }, - "MyFunctionTriggerDB129D7B": { + "MyTriggerFunctionTrigger5424E7A7": { "Type": "Custom::Trigger", "Properties": { "ServiceToken": { @@ -69,7 +69,7 @@ ] }, "HandlerArn": { - "Ref": "MyFunctionCurrentVersion197490AF2cb2bc11080c1ef11d3b49c1f1603957" + "Ref": "MyTriggerFunctionCurrentVersion61957CE15e7e73cc617a38bc55257878985a5e83" }, "InvocationType": "RequestResponse", "Timeout": { @@ -87,11 +87,11 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "MyFunctionCurrentVersion197490AF2cb2bc11080c1ef11d3b49c1f1603957": { + "MyTriggerFunctionCurrentVersion61957CE15e7e73cc617a38bc55257878985a5e83": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { - "Ref": "MyFunction3BAA72D1" + "Ref": "MyTriggerFunction056842F6" } } }, @@ -133,7 +133,7 @@ [ { "Fn::GetAtt": [ - "MyFunction3BAA72D1", + "MyTriggerFunction056842F6", "Arn" ] }, @@ -268,6 +268,86 @@ "Ref": "MySecondFunction0F0B51EB" } } + }, + "MyLambdaFunctionServiceRole313A4D46": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "MyLambdaFunction67CCA873": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function() { console.log(\"hi\"); };" + }, + "Role": { + "Fn::GetAtt": [ + "MyLambdaFunctionServiceRole313A4D46", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "MyLambdaFunctionServiceRole313A4D46" + ] + }, + "MyLambdaFunctionCurrentVersion4FAB80EC18bee38800123c16fbc03ff5bdcda0f9": { + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyLambdaFunction67CCA873" + } + } + }, + "MyTrigger": { + "Type": "Custom::Trigger", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91", + "Arn" + ] + }, + "HandlerArn": { + "Ref": "MyLambdaFunctionCurrentVersion4FAB80EC18bee38800123c16fbc03ff5bdcda0f9" + }, + "InvocationType": "Event", + "Timeout": 60000 + }, + "DependsOn": [ + "Topic198E71B3E", + "Topic269377B75" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } }, "Parameters": { diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.ts b/packages/@aws-cdk/triggers/test/integ.triggers.ts index 9e24277509c58..f6e18668b66d9 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.ts +++ b/packages/@aws-cdk/triggers/test/integ.triggers.ts @@ -1,6 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; -import { App, Stack } from '@aws-cdk/core'; +import { App, Duration, Stack } from '@aws-cdk/core'; import * as triggers from '../lib'; const app = new App(); @@ -9,13 +9,27 @@ const stack = new Stack(app, 'MyStack'); const topic1 = new sns.Topic(stack, 'Topic1'); const topic2 = new sns.Topic(stack, 'Topic2'); -const trigger = new triggers.TriggerFunction(stack, 'MyFunction', { +const triggerFunction = new triggers.TriggerFunction(stack, 'MyTriggerFunction', { runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline('exports.handler = function() { console.log("hi"); };'), executeBefore: [topic1], }); +const func = new lambda.Function(stack, 'MyLambdaFunction', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function() { console.log("hi"); };'), +}); + +const trigger = new triggers.Trigger(stack, 'MyTrigger', { + handler: func, + invocationType: 'Event', + timeout: Duration.minutes(1), + executeAfter: [topic1], +}); + +triggerFunction.executeAfter(topic2); trigger.executeAfter(topic2); new triggers.TriggerFunction(stack, 'MySecondFunction', { From 3b72debd5b39fed111a07f33822787546c07b89b Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Thu, 1 Dec 2022 14:47:49 +0100 Subject: [PATCH 4/8] add invocation type enum & add test scenario --- packages/@aws-cdk/triggers/README.md | 2 +- packages/@aws-cdk/triggers/lib/trigger.ts | 21 ++++++++++++++++++- .../MyStack.template.json | 15 ++++++------- .../@aws-cdk/triggers/test/integ.triggers.ts | 3 ++- .../@aws-cdk/triggers/test/triggers.test.ts | 3 ++- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/packages/@aws-cdk/triggers/README.md b/packages/@aws-cdk/triggers/README.md index ca9825635bbc7..d859fe1430f34 100644 --- a/packages/@aws-cdk/triggers/README.md +++ b/packages/@aws-cdk/triggers/README.md @@ -57,7 +57,7 @@ const func = new lambda.Function(stack, 'MyFunction', { new triggers.Trigger(stack, 'MyTrigger', { handler: func, timeout: Duration.minutes(10), - invocationType: 'Event', + invocationType: triggers.InvocationType.EVENT, }); ``` diff --git a/packages/@aws-cdk/triggers/lib/trigger.ts b/packages/@aws-cdk/triggers/lib/trigger.ts index 38478dc415434..644d9959d5302 100644 --- a/packages/@aws-cdk/triggers/lib/trigger.ts +++ b/packages/@aws-cdk/triggers/lib/trigger.ts @@ -62,6 +62,25 @@ export interface TriggerOptions { readonly executeOnHandlerChange?: boolean; } +export enum InvocationType { + /** + * Invoke the function synchronously. Keep the connection open until the function returns a response or times out. + * The API response includes the function response and additional data. + */ + EVENT = 'Event', + + /** + * Invoke the function asynchronously. Send events that fail multiple times to the function's dead-letter queue (if one is configured). + * The API response only includes a status code. + */ + REQUEST_RESPONSE = 'RequestResponse', + + /** + * Validate parameter values and verify that the user or role has permission to invoke the function. + */ + DRY_RUN = 'DryRun' +} + /** * Props for `Trigger`. */ @@ -76,7 +95,7 @@ export interface TriggerProps extends TriggerOptions { * * @default RequestResponse */ - readonly invocationType?: 'RequestResponse' | 'Event' | 'DryRun'; + readonly invocationType?: InvocationType; /** * The timeout of the invocation call of the Lambda function to be triggered. diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json index 153cc6d89ac89..093720853cf8c 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json @@ -53,7 +53,7 @@ ] }, "Handler": "index.handler", - "Runtime": "nodejs14.x" + "Runtime": "nodejs16.x" }, "DependsOn": [ "MyTriggerFunctionServiceRole1BB78C29" @@ -69,7 +69,7 @@ ] }, "HandlerArn": { - "Ref": "MyTriggerFunctionCurrentVersion61957CE15e7e73cc617a38bc55257878985a5e83" + "Ref": "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777" }, "InvocationType": "RequestResponse", "Timeout": { @@ -87,7 +87,7 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "MyTriggerFunctionCurrentVersion61957CE15e7e73cc617a38bc55257878985a5e83": { + "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -304,7 +304,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = function() { console.log(\"hi\"); };" + "ZipFile": "exports.handler = function() { await setTimeout(3*60*1000, \"hi\"); };" }, "Role": { "Fn::GetAtt": [ @@ -313,13 +313,14 @@ ] }, "Handler": "index.handler", - "Runtime": "nodejs14.x" + "Runtime": "nodejs16.x", + "Timeout": 900 }, "DependsOn": [ "MyLambdaFunctionServiceRole313A4D46" ] }, - "MyLambdaFunctionCurrentVersion4FAB80EC18bee38800123c16fbc03ff5bdcda0f9": { + "MyLambdaFunctionCurrentVersion4FAB80ECdc4d4e257bb2b44c9c4b9231f0d16f4c": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -337,7 +338,7 @@ ] }, "HandlerArn": { - "Ref": "MyLambdaFunctionCurrentVersion4FAB80EC18bee38800123c16fbc03ff5bdcda0f9" + "Ref": "MyLambdaFunctionCurrentVersion4FAB80ECdc4d4e257bb2b44c9c4b9231f0d16f4c" }, "InvocationType": "Event", "Timeout": 60000 diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.ts b/packages/@aws-cdk/triggers/test/integ.triggers.ts index f6e18668b66d9..859a46910c7db 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.ts +++ b/packages/@aws-cdk/triggers/test/integ.triggers.ts @@ -2,6 +2,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import { App, Duration, Stack } from '@aws-cdk/core'; import * as triggers from '../lib'; +import { InvocationType } from '../lib'; const app = new App(); const stack = new Stack(app, 'MyStack'); @@ -24,7 +25,7 @@ const func = new lambda.Function(stack, 'MyLambdaFunction', { const trigger = new triggers.Trigger(stack, 'MyTrigger', { handler: func, - invocationType: 'Event', + invocationType: InvocationType.EVENT, timeout: Duration.minutes(1), executeAfter: [topic1], }); diff --git a/packages/@aws-cdk/triggers/test/triggers.test.ts b/packages/@aws-cdk/triggers/test/triggers.test.ts index 1653978519016..8c3b4d05a29e9 100644 --- a/packages/@aws-cdk/triggers/test/triggers.test.ts +++ b/packages/@aws-cdk/triggers/test/triggers.test.ts @@ -3,6 +3,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import { Duration, Stack } from '@aws-cdk/core'; import * as triggers from '../lib'; +import { InvocationType } from '../lib'; test('minimal trigger function', () => { // GIVEN @@ -125,7 +126,7 @@ test('trigger with optional properties', () => { new triggers.Trigger(stack, 'MyTrigger', { handler: func, timeout: Duration.minutes(10), - invocationType: 'Event', + invocationType: InvocationType.EVENT, }); // THEN From 6ff80c8bcde5b23318573c69020522736b9e783a Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Thu, 1 Dec 2022 17:44:35 +0100 Subject: [PATCH 5/8] Add missing changes --- packages/@aws-cdk/triggers/lib/trigger.ts | 3 +++ packages/@aws-cdk/triggers/test/integ.triggers.ts | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/triggers/lib/trigger.ts b/packages/@aws-cdk/triggers/lib/trigger.ts index 644d9959d5302..53a8b98c614b8 100644 --- a/packages/@aws-cdk/triggers/lib/trigger.ts +++ b/packages/@aws-cdk/triggers/lib/trigger.ts @@ -62,6 +62,9 @@ export interface TriggerOptions { readonly executeOnHandlerChange?: boolean; } +/** + * The invocation type to apply to a trigger. This determines whether the trigger function should await the result of the to be triggered function or not. + */ export enum InvocationType { /** * Invoke the function synchronously. Keep the connection open until the function returns a response or times out. diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.ts b/packages/@aws-cdk/triggers/test/integ.triggers.ts index 859a46910c7db..de5023461ed2e 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.ts +++ b/packages/@aws-cdk/triggers/test/integ.triggers.ts @@ -11,16 +11,17 @@ const topic1 = new sns.Topic(stack, 'Topic1'); const topic2 = new sns.Topic(stack, 'Topic2'); const triggerFunction = new triggers.TriggerFunction(stack, 'MyTriggerFunction', { - runtime: lambda.Runtime.NODEJS_14_X, + runtime: lambda.Runtime.NODEJS_16_X, handler: 'index.handler', code: lambda.Code.fromInline('exports.handler = function() { console.log("hi"); };'), executeBefore: [topic1], }); const func = new lambda.Function(stack, 'MyLambdaFunction', { - runtime: lambda.Runtime.NODEJS_14_X, + runtime: lambda.Runtime.NODEJS_16_X, handler: 'index.handler', - code: lambda.Code.fromInline('exports.handler = function() { console.log("hi"); };'), + timeout: Duration.minutes(15), + code: lambda.Code.fromInline('exports.handler = function() { await setTimeout(3*60*1000, "hi"); };'), }); const trigger = new triggers.Trigger(stack, 'MyTrigger', { From 3e97e069bc0c29efb1b5ce6198940a59d0b10e3e Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Mon, 5 Dec 2022 17:10:50 +0100 Subject: [PATCH 6/8] resolve conflicts --- .../MyStack.template.json | 115 +++++++++++------- .../triggers/test/trigger-handler.test.ts | 12 +- 2 files changed, 79 insertions(+), 48 deletions(-) diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json index 093720853cf8c..4923d463c8cca 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json @@ -69,7 +69,7 @@ ] }, "HandlerArn": { - "Ref": "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777" + "Ref": "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777" }, "InvocationType": "RequestResponse", "Timeout": { @@ -87,7 +87,7 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777": { + "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -143,6 +143,28 @@ } ] }, + { + "Effect": "Allow", + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyLambdaFunction67CCA873", + "Arn" + ] + }, + ":*" + ] + ] + } + ] + }, { "Effect": "Allow", "Action": [ @@ -195,7 +217,7 @@ "AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A" ] }, - "MySecondFunctionServiceRole5B930841": { + "MyLambdaFunctionServiceRole313A4D46": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -226,26 +248,35 @@ ] } }, - "MySecondFunction0F0B51EB": { + "MyLambdaFunction67CCA873": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = function() { console.log(\"hello\"); };" + "ZipFile": "exports.handler = function() { await setTimeout(3*60*1000, \"hi\"); };" }, "Role": { "Fn::GetAtt": [ - "MySecondFunctionServiceRole5B930841", + "MyLambdaFunctionServiceRole313A4D46", "Arn" ] }, "Handler": "index.handler", - "Runtime": "nodejs16.x" + "Runtime": "nodejs16.x", + "Timeout": 900 }, "DependsOn": [ - "MySecondFunctionServiceRole5B930841" + "MyLambdaFunctionServiceRole313A4D46" ] }, - "MySecondFunctionTrigger8C61EC28": { + "MyLambdaFunctionCurrentVersion4FAB80ECdc4d4e257bb2b44c9c4b9231f0d16f4c": { + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyLambdaFunction67CCA873" + } + } + }, + "MyTrigger": { "Type": "Custom::Trigger", "Properties": { "ServiceToken": { @@ -255,21 +286,19 @@ ] }, "HandlerArn": { - "Ref": "MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362" - } + "Ref": "MyLambdaFunctionCurrentVersion4FAB80ECdc4d4e257bb2b44c9c4b9231f0d16f4c" + }, + "InvocationType": "Event", + "Timeout": 60000 }, + "DependsOn": [ + "Topic198E71B3E", + "Topic269377B75" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362": { - "Type": "AWS::Lambda::Version", - "Properties": { - "FunctionName": { - "Ref": "MySecondFunction0F0B51EB" - } - } - }, - "MyLambdaFunctionServiceRole313A4D46": { + "MySecondFunctionServiceRole5B930841": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -300,35 +329,26 @@ ] } }, - "MyLambdaFunction67CCA873": { + "MySecondFunction0F0B51EB": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = function() { await setTimeout(3*60*1000, \"hi\"); };" + "ZipFile": "exports.handler = function() { console.log(\"hello\"); };" }, "Role": { "Fn::GetAtt": [ - "MyLambdaFunctionServiceRole313A4D46", + "MySecondFunctionServiceRole5B930841", "Arn" ] }, "Handler": "index.handler", - "Runtime": "nodejs16.x", - "Timeout": 900 + "Runtime": "nodejs16.x" }, "DependsOn": [ - "MyLambdaFunctionServiceRole313A4D46" + "MySecondFunctionServiceRole5B930841" ] }, - "MyLambdaFunctionCurrentVersion4FAB80ECdc4d4e257bb2b44c9c4b9231f0d16f4c": { - "Type": "AWS::Lambda::Version", - "Properties": { - "FunctionName": { - "Ref": "MyLambdaFunction67CCA873" - } - } - }, - "MyTrigger": { + "MySecondFunctionTrigger8C61EC28": { "Type": "Custom::Trigger", "Properties": { "ServiceToken": { @@ -338,17 +358,28 @@ ] }, "HandlerArn": { - "Ref": "MyLambdaFunctionCurrentVersion4FAB80ECdc4d4e257bb2b44c9c4b9231f0d16f4c" + "Ref": "MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362" }, - "InvocationType": "Event", - "Timeout": 60000 + "InvocationType": "RequestResponse", + "Timeout": { + "amount": 2, + "unit": { + "label": "minutes", + "isoLabel": "M", + "inMillis": 60000 + } + } }, - "DependsOn": [ - "Topic198E71B3E", - "Topic269377B75" - ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" + }, + "MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362": { + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MySecondFunction0F0B51EB" + } + } } }, "Parameters": { diff --git a/packages/@aws-cdk/triggers/test/trigger-handler.test.ts b/packages/@aws-cdk/triggers/test/trigger-handler.test.ts index bbfe4f2062748..09a4d77a657b5 100644 --- a/packages/@aws-cdk/triggers/test/trigger-handler.test.ts +++ b/packages/@aws-cdk/triggers/test/trigger-handler.test.ts @@ -41,14 +41,14 @@ test('Create', async () => { await lambda.handler({ RequestType: 'Create', ...mockRequest }); expect(invokeMock).toBeCalledTimes(1); - expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn }); + expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn, InvocationType: 'Event' }); }); test('Update', async () => { await lambda.handler({ RequestType: 'Update', PhysicalResourceId: 'PRID', OldResourceProperties: {}, ...mockRequest }); expect(invokeMock).toBeCalledTimes(1); - expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn }); + expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn, InvocationType: 'Event' }); }); test('Delete - handler not called', async () => { @@ -66,7 +66,7 @@ test('non-200 status code throws an error', async () => { .toMatchObject({ message: 'Trigger handler failed with status code 500' }); expect(invokeMock).toBeCalledTimes(1); - expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn }); + expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn, InvocationType: 'Event' }); }); test('retry with access denied exception', async () => { @@ -83,7 +83,7 @@ test('retry with access denied exception', async () => { await response; expect(invokeMock).toBeCalledTimes(2); - expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn }); + expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn, InvocationType: 'Event' }); }); test('throws an error for other exceptions', async () => { @@ -96,7 +96,7 @@ test('throws an error for other exceptions', async () => { .toThrow(); expect(invokeMock).toBeCalledTimes(1); - expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn }); + expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn, InvocationType: 'Event' }); }); describe('function error', () => { @@ -113,7 +113,7 @@ describe('function error', () => { .toMatchObject({ message: expectedError }); expect(invokeMock).toBeCalledTimes(1); - expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn }); + expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn, InvocationType: 'Event' }); }; }; From 5eb89d62bbe50445d9bea59d7983a349b8b08441 Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Tue, 13 Dec 2022 10:53:38 +0100 Subject: [PATCH 7/8] apply toMillis for default timeout suggestion Co-authored-by: Cory Hall <43035978+corymhall@users.noreply.github.com> --- packages/@aws-cdk/triggers/lib/trigger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/triggers/lib/trigger.ts b/packages/@aws-cdk/triggers/lib/trigger.ts index 53a8b98c614b8..2a74a0a6fd874 100644 --- a/packages/@aws-cdk/triggers/lib/trigger.ts +++ b/packages/@aws-cdk/triggers/lib/trigger.ts @@ -133,7 +133,7 @@ export class Trigger extends Construct implements ITrigger { properties: { HandlerArn: handlerArn, InvocationType: props.invocationType ?? 'RequestResponse', - Timeout: props.timeout?.toMilliseconds() ?? Duration.minutes(2), + Timeout: props.timeout?.toMilliseconds() ?? Duration.minutes(2).toMilliseconds(), }, }); From 7809c626b1ec9a585ecd1f2c338d8e858753e07f Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Tue, 13 Dec 2022 13:36:55 +0100 Subject: [PATCH 8/8] apply duration > milliseconds change to integration test --- .../MyStack.template.json | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json index 4923d463c8cca..31625a36f0bee 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.template.json @@ -72,14 +72,7 @@ "Ref": "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777" }, "InvocationType": "RequestResponse", - "Timeout": { - "amount": 2, - "unit": { - "label": "minutes", - "isoLabel": "M", - "inMillis": 60000 - } - } + "Timeout": 120000 }, "DependsOn": [ "Topic269377B75" @@ -361,14 +354,7 @@ "Ref": "MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362" }, "InvocationType": "RequestResponse", - "Timeout": { - "amount": 2, - "unit": { - "label": "minutes", - "isoLabel": "M", - "inMillis": 60000 - } - } + "Timeout": 120000 }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete"