Skip to content

Commit

Permalink
Allow trigger to work with Lambda functions with long timeouts
Browse files Browse the repository at this point in the history
  • Loading branch information
DerkSch committed Mar 8, 2023
1 parent e39bdea commit 8e006d3
Show file tree
Hide file tree
Showing 22 changed files with 3,250 additions and 74 deletions.
27 changes: 27 additions & 0 deletions packages/@aws-cdk/triggers/README.md
Expand Up @@ -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: triggers.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
Expand Down
29 changes: 22 additions & 7 deletions packages/@aws-cdk/triggers/lib/lambda/index.ts
Expand Up @@ -3,11 +3,16 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import * as AWS from 'aws-sdk';

export type InvokeFunction = (functionName: string) => Promise<AWS.Lambda.InvocationResponse>;
export type InvokeFunction = (functionName: string, invocationType: string, timeout: number) => Promise<AWS.Lambda.InvocationResponse>;

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
Expand Down Expand Up @@ -51,9 +56,17 @@ 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 parsedTimeout = parseInt(timeout);
if (isNaN(parsedTimeout)) {
throw new Error(`The "Timeout" property with value ${timeout} is not parseable to a number`);
}

if (invokeResponse.StatusCode !== 200) {
const invokeResponse = await invoke(handlerArn, invocationType, parsedTimeout);

if (invokeResponse.StatusCode && invokeResponse.StatusCode >= 400) {
throw new Error(`Trigger handler failed with status code ${invokeResponse.StatusCode}`);
}

Expand All @@ -68,7 +81,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');
Expand Down
39 changes: 39 additions & 0 deletions packages/@aws-cdk/triggers/lib/trigger.ts
Expand Up @@ -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.
Expand Down Expand Up @@ -61,6 +62,28 @@ 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.
* 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`.
*/
Expand All @@ -69,6 +92,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?: InvocationType;

/**
* The timeout of the invocation call of the Lambda function to be triggered.
*
* @default Duration.minutes(2)
*/
readonly timeout?: Duration;
}

/**
Expand All @@ -95,6 +132,8 @@ export class Trigger extends Construct implements ITrigger {
serviceToken: provider.serviceToken,
properties: {
HandlerArn: handlerArn,
InvocationType: props.invocationType ?? 'RequestResponse',
Timeout: props.timeout?.toMilliseconds().toString() ?? Duration.minutes(2).toMilliseconds().toString(),
},
});

Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/triggers/package.json
Expand Up @@ -74,12 +74,13 @@
"license": "Apache-2.0",
"devDependencies": {
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/aws-sns": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/aws-sns": "0.0.0",
"aws-sdk": "^2.1329.0",
"@aws-cdk/pkglint": "0.0.0",
"@aws-cdk/aws-sqs": "0.0.0",
"@types/jest": "^27.5.2",
"jest": "^27.5.1"
},
Expand Down
@@ -1,28 +1,41 @@
{
"version": "29.0.0",
"version": "30.1.0",
"files": {
"ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4": {
"69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0": {
"source": {
"path": "asset.ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4",
"path": "asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0",
"packaging": "zip"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4.zip",
"objectKey": "69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0.zip",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
},
"4155391c7a2ef259603bc3a1d18d0bad9a2478b51ed773d2c06efab5a0a51c56": {
"aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1": {
"source": {
"path": "asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1",
"packaging": "zip"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1.zip",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
},
"6c8cf3b5b61b2dc9de89993ba0c228c09fc45f87c9bb09029b2d77440b0d03ae": {
"source": {
"path": "MyStack.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "4155391c7a2ef259603bc3a1d18d0bad9a2478b51ed773d2c06efab5a0a51c56.json",
"objectKey": "6c8cf3b5b61b2dc9de89993ba0c228c09fc45f87c9bb09029b2d77440b0d03ae.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down

0 comments on commit 8e006d3

Please sign in to comment.