From 8e006d3c577d58e2b39e81aa01c5cd083e0156d4 Mon Sep 17 00:00:00 2001 From: Derk Schooltink Date: Mon, 23 Jan 2023 10:34:41 +0100 Subject: [PATCH] Allow trigger to work with Lambda functions with long timeouts --- packages/@aws-cdk/triggers/README.md | 27 + .../@aws-cdk/triggers/lib/lambda/index.ts | 29 +- packages/@aws-cdk/triggers/lib/trigger.ts | 39 + packages/@aws-cdk/triggers/package.json | 3 +- .../MyStack.assets.json | 25 +- .../MyStack.template.json | 336 ++++- ...efaultTestDeployAssert61636546.assets.json | 19 +- ...aultTestDeployAssert61636546.template.json | 333 +++++ .../__entrypoint__.js | 144 ++ .../index.js | 83 ++ .../index.js | 1205 +++++++++++++++++ .../index.d.ts | 1 + .../index.js | 20 + .../index.ts | 17 + .../test/integ.triggers.js.snapshot/cdk.out | 2 +- .../integ.triggers.js.snapshot/integ.json | 3 +- .../integ.triggers.js.snapshot/manifest.json | 167 ++- .../test/integ.triggers.js.snapshot/tree.json | 714 +++++++++- .../@aws-cdk/triggers/test/integ.triggers.ts | 62 +- packages/@aws-cdk/triggers/test/lib/index.ts | 17 + .../triggers/test/trigger-handler.test.ts | 25 +- .../@aws-cdk/triggers/test/triggers.test.ts | 53 +- 22 files changed, 3250 insertions(+), 74 deletions(-) create mode 100644 packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0/__entrypoint__.js create mode 100644 packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0/index.js create mode 100644 packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.bundle/index.js create mode 100644 packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.d.ts create mode 100644 packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.js create mode 100644 packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.ts create mode 100644 packages/@aws-cdk/triggers/test/lib/index.ts diff --git a/packages/@aws-cdk/triggers/README.md b/packages/@aws-cdk/triggers/README.md index 2c86e946c1941..d859fe1430f34 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: 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 diff --git a/packages/@aws-cdk/triggers/lib/lambda/index.ts b/packages/@aws-cdk/triggers/lib/lambda/index.ts index 8fb4db66a3cc3..d9b61f8cf7e7b 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,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}`); } @@ -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'); diff --git a/packages/@aws-cdk/triggers/lib/trigger.ts b/packages/@aws-cdk/triggers/lib/trigger.ts index 6febb482966cf..b3199cc267226 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. @@ -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`. */ @@ -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; } /** @@ -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(), }, }); diff --git a/packages/@aws-cdk/triggers/package.json b/packages/@aws-cdk/triggers/package.json index 3d4221bd14726..7eb6b6b1fba54 100644 --- a/packages/@aws-cdk/triggers/package.json +++ b/packages/@aws-cdk/triggers/package.json @@ -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" }, diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.assets.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.assets.json index 773d8e4b077df..b5dea8a979613 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.assets.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/MyStack.assets.json @@ -1,20 +1,33 @@ { - "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" @@ -22,7 +35,7 @@ "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}" } } 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..41ea88012c7c5 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,18 +48,18 @@ }, "Role": { "Fn::GetAtt": [ - "MyFunctionServiceRole3C357FF2", + "MyTriggerFunctionServiceRole1BB78C29", "Arn" ] }, "Handler": "index.handler", - "Runtime": "nodejs14.x" + "Runtime": "nodejs16.x" }, "DependsOn": [ - "MyFunctionServiceRole3C357FF2" + "MyTriggerFunctionServiceRole1BB78C29" ] }, - "MyFunctionTriggerDB129D7B": { + "MyTriggerFunctionTrigger5424E7A7": { "Type": "Custom::Trigger", "Properties": { "ServiceToken": { @@ -69,8 +69,10 @@ ] }, "HandlerArn": { - "Ref": "MyFunctionCurrentVersion197490AF2cb2bc11080c1ef11d3b49c1f1603957" - } + "Ref": "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777" + }, + "InvocationType": "RequestResponse", + "Timeout": "120000" }, "DependsOn": [ "Topic269377B75" @@ -78,11 +80,11 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "MyFunctionCurrentVersion197490AF2cb2bc11080c1ef11d3b49c1f1603957": { + "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { - "Ref": "MyFunction3BAA72D1" + "Ref": "MyTriggerFunction056842F6" } } }, @@ -124,7 +126,51 @@ [ { "Fn::GetAtt": [ - "MyFunction3BAA72D1", + "MyTriggerFunction056842F6", + "Arn" + ] + }, + ":*" + ] + ] + } + ] + }, + { + "Effect": "Allow", + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyLambdaFunction67CCA873", + "Arn" + ] + }, + ":*" + ] + ] + } + ] + }, + { + "Effect": "Allow", + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyAssertionLambdaFunction7E77172F", "Arn" ] }, @@ -155,6 +201,28 @@ ] } ] + }, + { + "Effect": "Allow", + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyLambdaFunction67CCA873", + "Arn" + ] + }, + ":*" + ] + ] + } + ] } ] } @@ -169,7 +237,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "ae344ba0df770ab1ea166e5b55e0ff5681c951c0a34d8e724e430b88957c50d4.zip" + "S3Key": "69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0.zip" }, "Timeout": 900, "MemorySize": 128, @@ -186,6 +254,216 @@ "AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A" ] }, + "TestQueue6F0069AA": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": "trigger-assertion-queue" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "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 = async function() { await setTimeout(() => {console.log(\"hi\")}, 3*60*1000); };" + }, + "Role": { + "Fn::GetAtt": [ + "MyLambdaFunctionServiceRole313A4D46", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs16.x", + "Timeout": 900 + }, + "DependsOn": [ + "MyLambdaFunctionServiceRole313A4D46" + ] + }, + "MyLambdaFunctionCurrentVersion4FAB80EC75f2df347bcc21ea05f818cb68778d9f": { + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyLambdaFunction67CCA873" + } + } + }, + "MyTrigger": { + "Type": "Custom::Trigger", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91", + "Arn" + ] + }, + "HandlerArn": { + "Ref": "MyLambdaFunctionCurrentVersion4FAB80EC75f2df347bcc21ea05f818cb68778d9f" + }, + "InvocationType": "Event", + "Timeout": "60000" + }, + "DependsOn": [ + "Topic198E71B3E", + "Topic269377B75" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyAssertionLambdaFunctionServiceRole36146F3B": { + "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" + ] + ] + } + ] + } + }, + "MyAssertionLambdaFunctionServiceRoleDefaultPolicyFC44077F": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:SendMessage" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TestQueue6F0069AA", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyAssertionLambdaFunctionServiceRoleDefaultPolicyFC44077F", + "Roles": [ + { + "Ref": "MyAssertionLambdaFunctionServiceRole36146F3B" + } + ] + } + }, + "MyAssertionLambdaFunction7E77172F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1.zip" + }, + "Role": { + "Fn::GetAtt": [ + "MyAssertionLambdaFunctionServiceRole36146F3B", + "Arn" + ] + }, + "Environment": { + "Variables": { + "QUEUE_URL": { + "Ref": "TestQueue6F0069AA" + } + } + }, + "Handler": "index.handler", + "Runtime": "nodejs16.x", + "Timeout": 900 + }, + "DependsOn": [ + "MyAssertionLambdaFunctionServiceRoleDefaultPolicyFC44077F", + "MyAssertionLambdaFunctionServiceRole36146F3B" + ] + }, + "MyAssertionLambdaFunctionCurrentVersionF4FA4C80e2c1d671ec67fab13e46054563e433a6": { + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyAssertionLambdaFunction7E77172F" + } + } + }, + "MyAssertionTrigger": { + "Type": "Custom::Trigger", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91", + "Arn" + ] + }, + "HandlerArn": { + "Ref": "MyAssertionLambdaFunctionCurrentVersionF4FA4C80e2c1d671ec67fab13e46054563e433a6" + }, + "InvocationType": "RequestResponse", + "Timeout": "60000" + }, + "DependsOn": [ + "TestQueue6F0069AA" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "MySecondFunctionServiceRole5B930841": { "Type": "AWS::IAM::Role", "Properties": { @@ -247,7 +525,9 @@ }, "HandlerArn": { "Ref": "MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362" - } + }, + "InvocationType": "RequestResponse", + "Timeout": "120000" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -259,6 +539,34 @@ "Ref": "MySecondFunction0F0B51EB" } } + }, + "MyDefaultPropTrigger": { + "Type": "Custom::Trigger", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91", + "Arn" + ] + }, + "HandlerArn": { + "Ref": "MyLambdaFunctionCurrentVersion4FAB80EC75f2df347bcc21ea05f818cb68778d9f" + }, + "InvocationType": "RequestResponse", + "Timeout": "120000" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "ExportsOutputRefTestQueue6F0069AA4C7E94E2": { + "Value": { + "Ref": "TestQueue6F0069AA" + }, + "Export": { + "Name": "MyStack:ExportsOutputRefTestQueue6F0069AA4C7E94E2" + } } }, "Parameters": { diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/TriggerTestDefaultTestDeployAssert61636546.assets.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/TriggerTestDefaultTestDeployAssert61636546.assets.json index 88392139295c3..96e816917d75d 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/TriggerTestDefaultTestDeployAssert61636546.assets.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/TriggerTestDefaultTestDeployAssert61636546.assets.json @@ -1,7 +1,20 @@ { - "version": "29.0.0", + "version": "30.1.0", "files": { - "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4": { + "source": { + "path": "asset.73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "8c0f4f60c3986653a24ca8464d7682bf57a7a9749e916d73da2184ea3c4e3d31": { "source": { "path": "TriggerTestDefaultTestDeployAssert61636546.template.json", "packaging": "file" @@ -9,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "objectKey": "8c0f4f60c3986653a24ca8464d7682bf57a7a9749e916d73da2184ea3c4e3d31.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/TriggerTestDefaultTestDeployAssert61636546.template.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/TriggerTestDefaultTestDeployAssert61636546.template.json index ad9d0fb73d1dd..6e08818f8997c 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/TriggerTestDefaultTestDeployAssert61636546.template.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/TriggerTestDefaultTestDeployAssert61636546.template.json @@ -1,4 +1,337 @@ { + "Resources": { + "AwsApiCallSQSreceiveMessage": { + "Type": "Custom::DeployAssert@SdkCallSQSreceiveMessage", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "SQS", + "api": "receiveMessage", + "expected": "{\"$StringLike\":\"^hello world!$\"}", + "actualPath": "Messages.0.Body", + "stateMachineArn": { + "Ref": "AwsApiCallSQSreceiveMessageWaitFor10141935" + }, + "parameters": { + "QueueUrl": { + "Fn::ImportValue": "MyStack:ExportsOutputRefTestQueue6F0069AA4C7E94E2" + }, + "WaitTimeSeconds": 20 + }, + "flattenResponse": "true", + "outputPaths": [ + "Messages.0.Body" + ], + "salt": "1677835727233" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallSQSreceiveMessageWaitForIsCompleteProviderInvoke92C9A498": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE", + "Arn" + ] + }, + "Principal": { + "Fn::GetAtt": [ + "AwsApiCallSQSreceiveMessageWaitForRole90A5ABAA", + "Arn" + ] + } + } + }, + "AwsApiCallSQSreceiveMessageWaitForTimeoutProviderInvoke88C69E59": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA", + "Arn" + ] + }, + "Principal": { + "Fn::GetAtt": [ + "AwsApiCallSQSreceiveMessageWaitForRole90A5ABAA", + "Arn" + ] + } + } + }, + "AwsApiCallSQSreceiveMessageWaitForRole90A5ABAA": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "states.amazonaws.com" + } + } + ] + }, + "Policies": [ + { + "PolicyName": "InlineInvokeFunctions", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA", + "Arn" + ] + } + ] + } + ] + } + } + ] + } + }, + "AwsApiCallSQSreceiveMessageWaitFor10141935": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"framework-isComplete-task\",\"States\":{\"framework-isComplete-task\":{\"End\":true,\"Retry\":[{\"ErrorEquals\":[\"States.ALL\"],\"IntervalSeconds\":15,\"MaxAttempts\":2,\"BackoffRate\":3}],\"Catch\":[{\"ErrorEquals\":[\"States.ALL\"],\"Next\":\"framework-onTimeout-task\"}],\"Type\":\"Task\",\"Resource\":\"", + { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE", + "Arn" + ] + }, + "\"},\"framework-onTimeout-task\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"", + { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA", + "Arn" + ] + }, + "\"}}}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "AwsApiCallSQSreceiveMessageWaitForRole90A5ABAA", + "Arn" + ] + } + }, + "DependsOn": [ + "AwsApiCallSQSreceiveMessageWaitForRole90A5ABAA" + ] + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sqs:ReceiveMessage" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "states:StartExecution" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sqs:ReceiveMessage" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.zip" + }, + "Timeout": 120, + "Handler": "index.isComplete", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB", + "Arn" + ] + } + } + }, + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.zip" + }, + "Timeout": 120, + "Handler": "index.onTimeout", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallSQSreceiveMessage": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallSQSreceiveMessage", + "assertion" + ] + } + } + }, "Parameters": { "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0/__entrypoint__.js b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0/__entrypoint__.js new file mode 100644 index 0000000000000..1e3a3093c1706 --- /dev/null +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0/__entrypoint__.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixnQkFBUSxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sR0FBRyxHQUFHO1FBQ1YsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1FBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtRQUNwQixNQUFNLEVBQUUsS0FBSztRQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtLQUN2RSxDQUFDO0lBRUYsTUFBTSxZQUFZLEdBQUc7UUFDbkIsUUFBUSxFQUFFLENBQUM7UUFDWCxLQUFLLEVBQUUsSUFBSTtLQUNaLENBQUM7SUFDRixNQUFNLFdBQVcsQ0FBQyxZQUFZLEVBQUUsZ0JBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDL0UsQ0FBQztBQUVELEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxPQUE2QixFQUFFLFlBQW9CO0lBQ3ZGLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSTtZQUNGLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQztBQVNELFNBQWdCLFdBQVcsQ0FBMEIsT0FBcUIsRUFBRSxFQUE0QjtJQUN0RyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUssRUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCByZXNwOiBSZXNwb25zZSA9IHtcbiAgICAgIC4uLmV2ZW50LFxuICAgICAgUmVhc29uOiBleHRlcm5hbC5pbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgIH07XG5cbiAgICBpZiAoIXJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAvLyBzcGVjaWFsIGNhc2U6IGlmIENSRUFURSBmYWlscywgd2hpY2ggdXN1YWxseSBpbXBsaWVzLCB3ZSB1c3VhbGx5IGRvbid0XG4gICAgICAvLyBoYXZlIGEgcGh5c2ljYWwgcmVzb3VyY2UgaWQuIGluIHRoaXMgY2FzZSwgdGhlIHN1YnNlcXVlbnQgREVMRVRFXG4gICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgIC8vIGFkZHJlc3MgdGhpcywgd2UgdXNlIGEgbWFya2VyIHNvIHRoZSBwcm92aWRlciBmcmFtZXdvcmsgY2FuIHNpbXBseVxuICAgICAgLy8gaWdub3JlIHRoZSBzdWJzZXF1ZW50IERFTEVURS5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgZXh0ZXJuYWwubG9nKCdDUkVBVEUgZmFpbGVkLCByZXNwb25kaW5nIHdpdGggYSBtYXJrZXIgcGh5c2ljYWwgcmVzb3VyY2UgaWQgc28gdGhhdCB0aGUgc3Vic2VxdWVudCBERUxFVEUgd2lsbCBiZSBpZ25vcmVkJyk7XG4gICAgICAgIHJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkID0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVI7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgLy8gdGVycmlibHkgd3JvbmcgYmVjYXVzZSBhbGwgb3RoZXIgZXZlbnRzIHNob3VsZCBoYXZlIGFuIElELlxuICAgICAgICBleHRlcm5hbC5sb2coYEVSUk9SOiBNYWxmb3JtZWQgZXZlbnQuIFwiUGh5c2ljYWxSZXNvdXJjZUlkXCIgaXMgcmVxdWlyZWQ6ICR7SlNPTi5zdHJpbmdpZnkoZXZlbnQpfWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHRoaXMgaXMgYW4gYWN0dWFsIGVycm9yLCBmYWlsIHRoZSBhY3Rpdml0eSBhbHRvZ2V0aGVyIGFuZCBleGlzdC5cbiAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgcmVzcCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyUmVzcG9uc2UoXG4gIGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiB7IFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZyB9LFxuICBoYW5kbGVyUmVzcG9uc2U6IHZvaWQgfCBIYW5kbGVyUmVzcG9uc2UgPSB7IH0pOiBSZXNwb25zZSB7XG5cbiAgLy8gaWYgcGh5c2ljYWwgSUQgaXMgbm90IHJldHVybmVkLCB3ZSBoYXZlIHNvbWUgZGVmYXVsdHMgZm9yIHlvdSBiYXNlZFxuICAvLyBvbiB0aGUgcmVxdWVzdCB0eXBlLlxuICBjb25zdCBwaHlzaWNhbFJlc291cmNlSWQgPSBoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUmVxdWVzdElkO1xuXG4gIC8vIGlmIHdlIGFyZSBpbiBERUxFVEUgYW5kIHBoeXNpY2FsIElEIHdhcyBjaGFuZ2VkLCBpdCdzIGFuIGVycm9yLlxuICBpZiAoY2ZuUmVxdWVzdC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScgJiYgcGh5c2ljYWxSZXNvdXJjZUlkICE9PSBjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgREVMRVRFOiBjYW5ub3QgY2hhbmdlIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBmcm9tIFwiJHtjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZH1cIiB0byBcIiR7aGFuZGxlclJlc3BvbnNlLlBoeXNpY2FsUmVzb3VyY2VJZH1cIiBkdXJpbmcgZGVsZXRpb25gKTtcbiAgfVxuXG4gIC8vIG1lcmdlIHJlcXVlc3QgZXZlbnQgYW5kIHJlc3VsdCBldmVudCAocmVzdWx0IHByZXZhaWxzKS5cbiAgcmV0dXJuIHtcbiAgICAuLi5jZm5SZXF1ZXN0LFxuICAgIC4uLmhhbmRsZXJSZXNwb25zZSxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3VibWl0UmVzcG9uc2Uoc3RhdHVzOiAnU1VDQ0VTUycgfCAnRkFJTEVEJywgZXZlbnQ6IFJlc3BvbnNlKSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBldmVudC5SZWFzb24gPz8gc3RhdHVzLFxuICAgIFN0YWNrSWQ6IGV2ZW50LlN0YWNrSWQsXG4gICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgfHwgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIsXG4gICAgTG9naWNhbFJlc291cmNlSWQ6IGV2ZW50LkxvZ2ljYWxSZXNvdXJjZUlkLFxuICAgIE5vRWNobzogZXZlbnQuTm9FY2hvLFxuICAgIERhdGE6IGV2ZW50LkRhdGEsXG4gIH07XG5cbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBqc29uKTtcblxuICBjb25zdCByZXNwb25zZUJvZHkgPSBKU09OLnN0cmluZ2lmeShqc29uKTtcbiAgY29uc3QgcGFyc2VkVXJsID0gdXJsLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgY29uc3QgcmVxID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0/index.js b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0/index.js new file mode 100644 index 0000000000000..1937352035727 --- /dev/null +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.69c304a3f7ac849a04d2998eb55063905ceeae0c50a408f6f9b98eff6832b5b0/index.js @@ -0,0 +1,83 @@ +"use strict"; +/* eslint-disable no-console */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = exports.invoke = void 0; +// eslint-disable-next-line import/no-extraneous-dependencies +const AWS = require("aws-sdk"); +exports.invoke = 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 + // Therefore, retry for up to one minute + let retryCount = 0; + const delay = 5000; + let invokeResponse; + while (true) { + try { + invokeResponse = await lambda.invoke(invokeRequest).promise(); + break; + } + catch (error) { + if (error instanceof Error && error.code === 'AccessDeniedException' && retryCount < 12) { + retryCount++; + await new Promise((resolve) => { + setTimeout(resolve, delay); + }); + continue; + } + throw error; + } + } + console.log({ invokeResponse }); + return invokeResponse; +}; +async function handler(event) { + console.log({ ...event, ResponseURL: '...' }); + if (event.RequestType === 'Delete') { + console.log('not calling trigger on DELETE'); + return; + } + const handlerArn = event.ResourceProperties.HandlerArn; + if (!handlerArn) { + throw new Error('The "HandlerArn" property is required'); + } + 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`); + } + const invokeResponse = await exports.invoke(handlerArn, invocationType, parsedTimeout); + if (invokeResponse.StatusCode && invokeResponse.StatusCode >= 400) { + throw new Error(`Trigger handler failed with status code ${invokeResponse.StatusCode}`); + } + // if the lambda function throws an error, parse the error message and fail + if (invokeResponse.FunctionError) { + throw new Error(parseError(invokeResponse.Payload?.toString())); + } +} +exports.handler = handler; +/** + * Parse the error message from the lambda function. + */ +function parseError(payload) { + console.log(`Error payload: ${payload}`); + if (!payload) { + return 'unknown handler error'; + } + try { + const error = JSON.parse(payload); + const concat = [error.errorMessage, error.trace].filter(x => x).join('\n'); + return concat.length > 0 ? concat : payload; + } + catch (e) { + // fall back to just returning the payload + return payload; + } +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0JBQStCOzs7QUFFL0IsNkRBQTZEO0FBQzdELCtCQUErQjtBQUlsQixRQUFBLE1BQU0sR0FBbUIsS0FBSyxFQUFFLFlBQVksRUFBRSxjQUFjLEVBQUUsT0FBTyxFQUFFLEVBQUU7SUFDcEYsTUFBTSxNQUFNLEdBQUcsSUFBSSxHQUFHLENBQUMsTUFBTSxDQUFDO1FBQzVCLFdBQVcsRUFBRTtZQUNYLE9BQU87U0FDUjtLQUNGLENBQUMsQ0FBQztJQUVILE1BQU0sYUFBYSxHQUFHLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxjQUFjLEVBQUUsY0FBYyxFQUFFLENBQUM7SUFDckYsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFLGFBQWEsRUFBRSxDQUFDLENBQUM7SUFFL0IsMkRBQTJEO0lBQzNELHdDQUF3QztJQUV4QyxJQUFJLFVBQVUsR0FBRyxDQUFDLENBQUM7SUFDbkIsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDO0lBRW5CLElBQUksY0FBYyxDQUFDO0lBQ25CLE9BQU8sSUFBSSxFQUFFO1FBQ1gsSUFBSTtZQUNGLGNBQWMsR0FBRyxNQUFNLE1BQU0sQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDOUQsTUFBTTtTQUNQO1FBQUMsT0FBTyxLQUFLLEVBQUU7WUFDZCxJQUFJLEtBQUssWUFBWSxLQUFLLElBQUssS0FBc0IsQ0FBQyxJQUFJLEtBQUssdUJBQXVCLElBQUksVUFBVSxHQUFHLEVBQUUsRUFBRTtnQkFDekcsVUFBVSxFQUFFLENBQUM7Z0JBQ2IsTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFO29CQUM1QixVQUFVLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO2dCQUM3QixDQUFDLENBQUMsQ0FBQztnQkFDSCxTQUFTO2FBQ1Y7WUFFRCxNQUFNLEtBQUssQ0FBQztTQUNiO0tBQ0Y7SUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsY0FBYyxFQUFFLENBQUMsQ0FBQztJQUNoQyxPQUFPLGNBQWMsQ0FBQztBQUN4QixDQUFDLENBQUM7QUFFSyxLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtEO0lBQzlFLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztJQUU5QyxJQUFJLEtBQUssQ0FBQyxXQUFXLEtBQUssUUFBUSxFQUFFO1FBQ2xDLE9BQU8sQ0FBQyxHQUFHLENBQUMsK0JBQStCLENBQUMsQ0FBQztRQUM3QyxPQUFPO0tBQ1I7SUFFRCxNQUFNLFVBQVUsR0FBRyxLQUFLLENBQUMsa0JBQWtCLENBQUMsVUFBVSxDQUFDO0lBQ3ZELElBQUksQ0FBQyxVQUFVLEVBQUU7UUFDZixNQUFNLElBQUksS0FBSyxDQUFDLHVDQUF1QyxDQUFDLENBQUM7S0FDMUQ7SUFFRCxNQUFNLGNBQWMsR0FBRyxLQUFLLENBQUMsa0JBQWtCLENBQUMsY0FBYyxDQUFDO0lBQy9ELE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLENBQUM7SUFFakQsTUFBTSxhQUFhLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3hDLElBQUksS0FBSyxDQUFDLGFBQWEsQ0FBQyxFQUFFO1FBQ3hCLE1BQU0sSUFBSSxLQUFLLENBQUMscUNBQXFDLE9BQU8sK0JBQStCLENBQUMsQ0FBQztLQUM5RjtJQUVELE1BQU0sY0FBYyxHQUFHLE1BQU0sY0FBTSxDQUFDLFVBQVUsRUFBRSxjQUFjLEVBQUUsYUFBYSxDQUFDLENBQUM7SUFFL0UsSUFBSSxjQUFjLENBQUMsVUFBVSxJQUFJLGNBQWMsQ0FBQyxVQUFVLElBQUksR0FBRyxFQUFFO1FBQ2pFLE1BQU0sSUFBSSxLQUFLLENBQUMsMkNBQTJDLGNBQWMsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO0tBQ3pGO0lBRUQsMkVBQTJFO0lBQzNFLElBQUksY0FBYyxDQUFDLGFBQWEsRUFBRTtRQUNoQyxNQUFNLElBQUksS0FBSyxDQUFDLFVBQVUsQ0FBQyxjQUFjLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNqRTtBQUNILENBQUM7QUEvQkQsMEJBK0JDO0FBRUQ7O0dBRUc7QUFDSCxTQUFTLFVBQVUsQ0FBQyxPQUEyQjtJQUM3QyxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixPQUFPLEVBQUUsQ0FBQyxDQUFDO0lBQ3pDLElBQUksQ0FBQyxPQUFPLEVBQUU7UUFDWixPQUFPLHVCQUF1QixDQUFDO0tBQ2hDO0lBQ0QsSUFBSTtRQUNGLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDbEMsTUFBTSxNQUFNLEdBQUcsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUFFLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDM0UsT0FBTyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUM7S0FDN0M7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLDBDQUEwQztRQUMxQyxPQUFPLE9BQU8sQ0FBQztLQUNoQjtBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKiBlc2xpbnQtZGlzYWJsZSBuby1jb25zb2xlICovXG5cbi8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBpbXBvcnQvbm8tZXh0cmFuZW91cy1kZXBlbmRlbmNpZXNcbmltcG9ydCAqIGFzIEFXUyBmcm9tICdhd3Mtc2RrJztcblxuZXhwb3J0IHR5cGUgSW52b2tlRnVuY3Rpb24gPSAoZnVuY3Rpb25OYW1lOiBzdHJpbmcsIGludm9jYXRpb25UeXBlOiBzdHJpbmcsIHRpbWVvdXQ6IG51bWJlcikgPT4gUHJvbWlzZTxBV1MuTGFtYmRhLkludm9jYXRpb25SZXNwb25zZT47XG5cbmV4cG9ydCBjb25zdCBpbnZva2U6IEludm9rZUZ1bmN0aW9uID0gYXN5bmMgKGZ1bmN0aW9uTmFtZSwgaW52b2NhdGlvblR5cGUsIHRpbWVvdXQpID0+IHtcbiAgY29uc3QgbGFtYmRhID0gbmV3IEFXUy5MYW1iZGEoe1xuICAgIGh0dHBPcHRpb25zOiB7XG4gICAgICB0aW1lb3V0LFxuICAgIH0sXG4gIH0pO1xuXG4gIGNvbnN0IGludm9rZVJlcXVlc3QgPSB7IEZ1bmN0aW9uTmFtZTogZnVuY3Rpb25OYW1lLCBJbnZvY2F0aW9uVHlwZTogaW52b2NhdGlvblR5cGUgfTtcbiAgY29uc29sZS5sb2coeyBpbnZva2VSZXF1ZXN0IH0pO1xuXG4gIC8vIElBTSBwb2xpY3kgY2hhbmdlcyBjYW4gdGFrZSBzb21lIHRpbWUgdG8gZnVsbHkgcHJvcGFnYXRlXG4gIC8vIFRoZXJlZm9yZSwgcmV0cnkgZm9yIHVwIHRvIG9uZSBtaW51dGVcblxuICBsZXQgcmV0cnlDb3VudCA9IDA7XG4gIGNvbnN0IGRlbGF5ID0gNTAwMDtcblxuICBsZXQgaW52b2tlUmVzcG9uc2U7XG4gIHdoaWxlICh0cnVlKSB7XG4gICAgdHJ5IHtcbiAgICAgIGludm9rZVJlc3BvbnNlID0gYXdhaXQgbGFtYmRhLmludm9rZShpbnZva2VSZXF1ZXN0KS5wcm9taXNlKCk7XG4gICAgICBicmVhaztcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRXJyb3IgJiYgKGVycm9yIGFzIEFXUy5BV1NFcnJvcikuY29kZSA9PT0gJ0FjY2Vzc0RlbmllZEV4Y2VwdGlvbicgJiYgcmV0cnlDb3VudCA8IDEyKSB7XG4gICAgICAgIHJldHJ5Q291bnQrKztcbiAgICAgICAgYXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHtcbiAgICAgICAgICBzZXRUaW1lb3V0KHJlc29sdmUsIGRlbGF5KTtcbiAgICAgICAgfSk7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICB0aHJvdyBlcnJvcjtcbiAgICB9XG4gIH1cblxuICBjb25zb2xlLmxvZyh7IGludm9rZVJlc3BvbnNlIH0pO1xuICByZXR1cm4gaW52b2tlUmVzcG9uc2U7XG59O1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaGFuZGxlcihldmVudDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCkge1xuICBjb25zb2xlLmxvZyh7IC4uLmV2ZW50LCBSZXNwb25zZVVSTDogJy4uLicgfSk7XG5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJykge1xuICAgIGNvbnNvbGUubG9nKCdub3QgY2FsbGluZyB0cmlnZ2VyIG9uIERFTEVURScpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGNvbnN0IGhhbmRsZXJBcm4gPSBldmVudC5SZXNvdXJjZVByb3BlcnRpZXMuSGFuZGxlckFybjtcbiAgaWYgKCFoYW5kbGVyQXJuKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdUaGUgXCJIYW5kbGVyQXJuXCIgcHJvcGVydHkgaXMgcmVxdWlyZWQnKTtcbiAgfVxuXG4gIGNvbnN0IGludm9jYXRpb25UeXBlID0gZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzLkludm9jYXRpb25UeXBlO1xuICBjb25zdCB0aW1lb3V0ID0gZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzLlRpbWVvdXQ7XG5cbiAgY29uc3QgcGFyc2VkVGltZW91dCA9IHBhcnNlSW50KHRpbWVvdXQpO1xuICBpZiAoaXNOYU4ocGFyc2VkVGltZW91dCkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYFRoZSBcIlRpbWVvdXRcIiBwcm9wZXJ0eSB3aXRoIHZhbHVlICR7dGltZW91dH0gaXMgbm90IHBhcnNlYWJsZSB0byBhIG51bWJlcmApO1xuICB9XG5cbiAgY29uc3QgaW52b2tlUmVzcG9uc2UgPSBhd2FpdCBpbnZva2UoaGFuZGxlckFybiwgaW52b2NhdGlvblR5cGUsIHBhcnNlZFRpbWVvdXQpO1xuXG4gIGlmIChpbnZva2VSZXNwb25zZS5TdGF0dXNDb2RlICYmIGludm9rZVJlc3BvbnNlLlN0YXR1c0NvZGUgPj0gNDAwKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBUcmlnZ2VyIGhhbmRsZXIgZmFpbGVkIHdpdGggc3RhdHVzIGNvZGUgJHtpbnZva2VSZXNwb25zZS5TdGF0dXNDb2RlfWApO1xuICB9XG5cbiAgLy8gaWYgdGhlIGxhbWJkYSBmdW5jdGlvbiB0aHJvd3MgYW4gZXJyb3IsIHBhcnNlIHRoZSBlcnJvciBtZXNzYWdlIGFuZCBmYWlsXG4gIGlmIChpbnZva2VSZXNwb25zZS5GdW5jdGlvbkVycm9yKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKHBhcnNlRXJyb3IoaW52b2tlUmVzcG9uc2UuUGF5bG9hZD8udG9TdHJpbmcoKSkpO1xuICB9XG59XG5cbi8qKlxuICogUGFyc2UgdGhlIGVycm9yIG1lc3NhZ2UgZnJvbSB0aGUgbGFtYmRhIGZ1bmN0aW9uLlxuICovXG5mdW5jdGlvbiBwYXJzZUVycm9yKHBheWxvYWQ6IHN0cmluZyB8IHVuZGVmaW5lZCk6IHN0cmluZyB7XG4gIGNvbnNvbGUubG9nKGBFcnJvciBwYXlsb2FkOiAke3BheWxvYWR9YCk7XG4gIGlmICghcGF5bG9hZCkge1xuICAgIHJldHVybiAndW5rbm93biBoYW5kbGVyIGVycm9yJztcbiAgfVxuICB0cnkge1xuICAgIGNvbnN0IGVycm9yID0gSlNPTi5wYXJzZShwYXlsb2FkKTtcbiAgICBjb25zdCBjb25jYXQgPSBbZXJyb3IuZXJyb3JNZXNzYWdlLCBlcnJvci50cmFjZV0uZmlsdGVyKHggPT4geCkuam9pbignXFxuJyk7XG4gICAgcmV0dXJuIGNvbmNhdC5sZW5ndGggPiAwID8gY29uY2F0IDogcGF5bG9hZDtcbiAgfSBjYXRjaCAoZSkge1xuICAgIC8vIGZhbGwgYmFjayB0byBqdXN0IHJldHVybmluZyB0aGUgcGF5bG9hZFxuICAgIHJldHVybiBwYXlsb2FkO1xuICB9XG59XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.bundle/index.js b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.bundle/index.js new file mode 100644 index 0000000000000..58bcb1ef7f38e --- /dev/null +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.73c20a669c041469f7fc3fc03d574b093b5b97e7c716f76c1e8117e6163e4dc4.bundle/index.js @@ -0,0 +1,1205 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler, + isComplete: () => isComplete, + onTimeout: () => onTimeout +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + /** + * Check whether the provided object is a subtype of the `IMatcher`. + */ + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failuresHere = /* @__PURE__ */ new Map(); + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.innerMatchFailures = /* @__PURE__ */ new Map(); + this._hasFailed = false; + this._failCount = 0; + this._cost = 0; + this.target = target; + } + /** + * DEPRECATED + * @deprecated use recordFailure() + */ + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + /** + * Record a new failure into this result at a specific path. + */ + recordFailure(failure) { + const failKey = failure.path.join("."); + let list = this.failuresHere.get(failKey); + if (!list) { + list = []; + this.failuresHere.set(failKey, list); + } + this._failCount += 1; + this._cost += failure.cost ?? 1; + list.push(failure); + this._hasFailed = true; + return this; + } + /** Whether the match is a success */ + get isSuccess() { + return !this._hasFailed; + } + /** Does the result contain any failures. If not, the result is a success */ + hasFailed() { + return this._hasFailed; + } + /** The number of failures */ + get failCount() { + return this._failCount; + } + /** The cost of the failures so far */ + get failCost() { + return this._cost; + } + /** + * Compose the results of a previous match as a subtree. + * @param id the id of the parent tree. + */ + compose(id, inner) { + if (inner.hasFailed()) { + this._hasFailed = true; + this._failCount += inner.failCount; + this._cost += inner._cost; + this.innerMatchFailures.set(id, inner); + } + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + /** + * Prepare the result to be analyzed. + * This API *must* be called prior to analyzing these results. + */ + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + /** + * Render the failed match in a presentable way + * + * Prefer using `renderMismatch` over this method. It is left for backwards + * compatibility for test suites that expect it, but `renderMismatch()` will + * produce better output. + */ + toHumanStrings() { + const failures = new Array(); + debugger; + recurse(this, []); + return failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at /${r.path.join("/")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + function recurse(x, prefix) { + for (const fail of Array.from(x.failuresHere.values()).flat()) { + failures.push({ + matcher: fail.matcher, + message: fail.message, + path: [...prefix, ...fail.path] + }); + } + for (const [key, inner] of x.innerMatchFailures.entries()) { + recurse(inner, [...prefix, key]); + } + } + } + /** + * Do a deep render of the match result, showing the structure mismatches in context + */ + renderMismatch() { + if (!this.hasFailed()) { + return ""; + } + const parts = new Array(); + const indents = new Array(); + emitFailures(this, ""); + recurse(this); + return moveMarkersToFront(parts.join("").trimEnd()); + function emit(x) { + if (x === void 0) { + debugger; + } + parts.push(x.replace(/\n/g, ` +${indents.join("")}`)); + } + function emitFailures(r, path, scrapSet) { + for (const fail of r.failuresHere.get(path) ?? []) { + emit(`!! ${fail.message} +`); + } + scrapSet == null ? void 0 : scrapSet.delete(path); + } + function recurse(r) { + const remainingFailures = new Set(Array.from(r.failuresHere.keys()).filter((x) => x !== "")); + if (Array.isArray(r.target)) { + indents.push(" "); + emit("[\n"); + for (const [first, i] of enumFirst(range(r.target.length))) { + if (!first) { + emit(",\n"); + } + emitFailures(r, `${i}`, remainingFailures); + const innerMatcher = r.innerMatchFailures.get(`${i}`); + if (innerMatcher) { + emitFailures(innerMatcher, ""); + recurseComparingValues(innerMatcher, r.target[i]); + } else { + emit(renderAbridged(r.target[i])); + } + } + emitRemaining(); + indents.pop(); + emit("\n]"); + return; + } + if (r.target && typeof r.target === "object") { + indents.push(" "); + emit("{\n"); + const keys = Array.from(/* @__PURE__ */ new Set([ + ...Object.keys(r.target), + ...Array.from(remainingFailures) + ])).sort(); + for (const [first, key] of enumFirst(keys)) { + if (!first) { + emit(",\n"); + } + emitFailures(r, key, remainingFailures); + const innerMatcher = r.innerMatchFailures.get(key); + if (innerMatcher) { + emitFailures(innerMatcher, ""); + emit(`${jsonify(key)}: `); + recurseComparingValues(innerMatcher, r.target[key]); + } else { + emit(`${jsonify(key)}: `); + emit(renderAbridged(r.target[key])); + } + } + emitRemaining(); + indents.pop(); + emit("\n}"); + return; + } + emitRemaining(); + emit(jsonify(r.target)); + function emitRemaining() { + if (remainingFailures.size > 0) { + emit("\n"); + } + for (const key of remainingFailures) { + emitFailures(r, key); + } + } + } + function recurseComparingValues(inner, actualValue) { + if (inner.target === actualValue) { + return recurse(inner); + } + emit(renderAbridged(actualValue)); + emit(" <*> "); + recurse(inner); + } + function renderAbridged(x) { + if (Array.isArray(x)) { + switch (x.length) { + case 0: + return "[]"; + case 1: + return `[ ${renderAbridged(x[0])} ]`; + case 2: + if (x.every((e) => ["number", "boolean", "string"].includes(typeof e))) { + return `[ ${x.map(renderAbridged).join(", ")} ]`; + } + return "[ ... ]"; + default: + return "[ ... ]"; + } + } + if (x && typeof x === "object") { + const keys = Object.keys(x); + switch (keys.length) { + case 0: + return "{}"; + case 1: + return `{ ${JSON.stringify(keys[0])}: ${renderAbridged(x[keys[0]])} }`; + default: + return "{ ... }"; + } + } + return jsonify(x); + } + function jsonify(x) { + return JSON.stringify(x) ?? "undefined"; + } + function moveMarkersToFront(x) { + const re = /^(\s+)!!/gm; + return x.replace(re, (_, spaces) => `!!${spaces.substring(0, spaces.length - 2)}`); + } + } + /** + * Record a capture against in this match result. + */ + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; +function* range(n) { + for (let i = 0; i < n; i++) { + yield i; + } +} +function* enumFirst(xs) { + let first = true; + for (const x of xs) { + yield [first, x]; + first = false; + } +} + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/sorting.ts +function sortKeyComparator(keyFn) { + return (a, b) => { + const ak = keyFn(a); + const bk = keyFn(b); + for (let i = 0; i < ak.length && i < bk.length; i++) { + const av = ak[i]; + const bv = bk[i]; + let diff = 0; + if (typeof av === "number" && typeof bv === "number") { + diff = av - bv; + } else if (typeof av === "string" && typeof bv === "string") { + diff = av.localeCompare(bv); + } + if (diff !== 0) { + return diff; + } + } + return bk.length - ak.length; + }; +} + +// ../assertions/lib/private/sparse-matrix.ts +var SparseMatrix = class { + constructor() { + this.matrix = /* @__PURE__ */ new Map(); + } + get(row, col) { + var _a; + return (_a = this.matrix.get(row)) == null ? void 0 : _a.get(col); + } + row(row) { + var _a; + return Array.from(((_a = this.matrix.get(row)) == null ? void 0 : _a.entries()) ?? []); + } + set(row, col, value) { + let r = this.matrix.get(row); + if (!r) { + r = /* @__PURE__ */ new Map(); + this.matrix.set(row, r); + } + r.set(col, value); + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + /** + * Use this matcher in the place of a field's value, if the field must not be present. + */ + static absent() { + return new AbsentMatch("absent"); + } + /** + * Matches the specified pattern with the array found in the same relative path of the target. + * The set of elements (or matchers) must be in the same order as would be found. + * @param pattern the pattern to match + */ + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + /** + * Matches the specified pattern with the array found in the same relative path of the target. + * The set of elements (or matchers) must match exactly and in order. + * @param pattern the pattern to match + */ + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + /** + * Deep exact matching of the specified pattern to the target. + * @param pattern the pattern to match + */ + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + /** + * Matches the specified pattern to an object found in the same relative path of the target. + * The keys and their values (or matchers) must be present in the target but the target can be a superset. + * @param pattern the pattern to match + */ + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + /** + * Matches the specified pattern to an object found in the same relative path of the target. + * The keys and their values (or matchers) must match exactly with the target. + * @param pattern the pattern to match + */ + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + /** + * Matches any target which does NOT follow the specified pattern. + * @param pattern the pattern to NOT match + */ + static not(pattern) { + return new NotMatch("not", pattern); + } + /** + * Matches any string-encoded JSON and applies the specified pattern after parsing it. + * @param pattern the pattern to match after parsing the encoded JSON. + */ + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + /** + * Matches any non-null value at the target. + */ + static anyValue() { + return new AnyMatch("anyValue"); + } + /** + * Matches targets according to a regular expression + */ + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + return this.subsequence ? this.testSubsequence(actual) : this.testFullArray(actual); + } + testFullArray(actual) { + const result = new MatchResult(actual); + let i = 0; + for (; i < this.pattern.length && i < actual.length; i++) { + const patternElement = this.pattern[i]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const innerResult = matcher.test(actual[i]); + result.compose(`${i}`, innerResult); + } + if (i < this.pattern.length) { + result.recordFailure({ + matcher: this, + message: `Not enough elements in array (expecting ${this.pattern.length}, got ${actual.length})`, + path: [`${i}`] + }); + } + if (i < actual.length) { + result.recordFailure({ + matcher: this, + message: `Too many elements in array (expecting ${this.pattern.length}, got ${actual.length})`, + path: [`${i}`] + }); + } + return result; + } + testSubsequence(actual) { + const result = new MatchResult(actual); + let patternIdx = 0; + let actualIdx = 0; + const matches = new SparseMatrix(); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (matcherName == "absent" || matcherName == "anyValue") { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + matches.set(patternIdx, actualIdx, innerResult); + actualIdx++; + if (innerResult.isSuccess) { + result.compose(`${actualIdx}`, innerResult); + patternIdx++; + } + } + if (patternIdx < this.pattern.length) { + for (let spi = 0; spi < patternIdx; spi++) { + const foundMatch = matches.row(spi).find(([, r]) => r.isSuccess); + if (!foundMatch) { + continue; + } + const [index] = foundMatch; + result.compose(`${index}`, new MatchResult(actual[index]).recordFailure({ + matcher: this, + message: `arrayWith pattern ${spi} matched here`, + path: [], + cost: 0 + // This is an informational message so it would be unfair to assign it cost + })); + } + const failedMatches = matches.row(patternIdx); + failedMatches.sort(sortKeyComparator(([i, r]) => [r.failCost, i])); + if (failedMatches.length > 0) { + const [index, innerResult] = failedMatches[0]; + result.recordFailure({ + matcher: this, + message: `Could not match arrayWith pattern ${patternIdx}. This is the closest match`, + path: [`${index}`], + cost: 0 + // Informational message + }); + result.compose(`${index}`, innerResult); + } else { + result.recordFailure({ + matcher: this, + message: `Could not match arrayWith pattern ${patternIdx}. No more elements to try`, + path: [`${actual.length}`] + }); + } + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [a], + message: `Unexpected key ${a}` + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [patternKey], + message: `Missing key '${patternKey}'` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(patternKey, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + if (getType(actual) !== "string") { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + if (innerResult.hasFailed()) { + innerResult.recordFailure({ + matcher: this, + path: [], + message: "Encoded JSON value does not match" + }); + } + return innerResult; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var AWS = __toESM(require("aws-sdk")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + /** + * Handles executing the custom resource event. If `stateMachineArn` is present + * in the props then trigger the waiter statemachine + */ + async handle() { + try { + if ("stateMachineArn" in this.event.ResourceProperties) { + const req = { + stateMachineArn: this.event.ResourceProperties.stateMachineArn, + name: this.event.RequestId, + input: JSON.stringify(this.event) + }; + await this.startExecution(req); + return; + } else { + const response = await this.processEvent(this.event.ResourceProperties); + return response; + } + } catch (e) { + console.log(e); + throw e; + } finally { + clearTimeout(this.timeout); + } + } + /** + * Handle async requests from the waiter state machine + */ + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + /** + * Start a step function state machine which will wait for the request + * to be successful. + */ + async startExecution(req) { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } finally { + clearTimeout(this.timeout); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + failed: true, + assertion: JSON.stringify({ + status: "fail", + message: matchResult.renderMismatch() + }) + }; + if (request2.failDeployment) { + throw new Error(result.assertion); + } + } else { + result = { + assertion: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + /** + * Return a Matcher that can be tested against the actual results. + * This will convert the encoded matchers into their corresponding + * assertions matcher. + * + * For example: + * + * ExpectedResult.objectLike({ + * Messages: [{ + * Body: Match.objectLike({ + * Elements: Match.arrayWith([{ Asdf: 3 }]), + * Payload: Match.serializedJson({ key: 'value' }), + * }), + * }], + * }); + * + * Will be encoded as: + * { + * $ObjectLike: { + * Messages: [{ + * Body: { + * $ObjectLike: { + * Elements: { + * $ArrayWith: [{ Asdf: 3 }], + * }, + * Payload: { + * $SerializedJson: { key: 'value' } + * } + * }, + * }, + * }], + * }, + * } + * + * Which can then be parsed by this function. For each key (recursively) + * the parser will check if the value has one of the encoded matchers as a key + * and if so, it will set the value as the Matcher. So, + * + * { + * Body: { + * $ObjectLike: { + * Elements: { + * $ArrayWith: [{ Asdf: 3 }], + * }, + * Payload: { + * $SerializedJson: { key: 'value' } + * } + * }, + * }, + * } + * + * Will be converted to + * { + * Body: Match.objectLike({ + * Elements: Match.arrayWith([{ Asdf: 3 }]), + * Payload: Match.serializedJson({ key: 'value' }), + * }), + * } + */ + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + case "$SerializedJson": + return Match.serializedJson(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + if (typeof childKey === "string") { + childKey = isJsonString(childKey); + } + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS2 = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS2.VERSION}`); + if (!Object.prototype.hasOwnProperty.call(AWS2, request2.service)) { + throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS2.VERSION}.`); + } + const service = new AWS2[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + let resp = respond; + if (request2.outputPaths) { + resp = filterKeys(flatData, request2.outputPaths); + } else if (request2.flattenResponse === "true") { + resp = flatData; + } + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +function filterKeys(object, searchStrings) { + return Object.entries(object).reduce((filteredObject, [key, value]) => { + for (const searchString of searchStrings) { + if (key.startsWith(`apiCallResponse.${searchString}`)) { + filteredObject[key] = value; + } + } + return filteredObject; + }, {}); +} +function isJsonString(value) { + try { + return JSON.parse(value); + } catch { + return value; + } +} + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + if (event.RequestType === "Delete") { + await provider.respond({ + status: "SUCCESS", + reason: "OK" + }); + return; + } + const result = await provider.handle(); + if ("stateMachineArn" in event.ResourceProperties) { + console.info('Found "stateMachineArn", waiter statemachine started'); + return; + } else if ("expected" in event.ResourceProperties) { + console.info('Found "expected", testing assertions'); + const actualPath = event.ResourceProperties.actualPath; + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + try { + const assertionResult = await assertion.handle(); + await provider.respond({ + status: "SUCCESS", + reason: "OK", + // return both the result of the API call _and_ the assertion results + data: { + ...assertionResult, + ...result + } + }); + return; + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + return; +} +async function onTimeout(timeoutEvent) { + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + const provider = createResourceHandler(isCompleteRequest, standardContext); + await provider.respond({ + status: "FAILED", + reason: "Operation timed out: " + JSON.stringify(isCompleteRequest) + }); +} +async function isComplete(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + const result = await provider.handleIsComplete(); + const actualPath = event.ResourceProperties.actualPath; + if (result) { + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + if ("expected" in event.ResourceProperties) { + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + const assertionResult = await assertion.handleIsComplete(); + if (!(assertionResult == null ? void 0 : assertionResult.failed)) { + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } else { + console.log(`Assertion Failed: ${JSON.stringify(assertionResult)}`); + throw new Error(JSON.stringify(event)); + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } else { + console.log("No result"); + throw new Error(JSON.stringify(event)); + } + return; + } catch (e) { + console.log(e); + throw new Error(JSON.stringify(event)); + } +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) { + return new AssertionHandler(event, context); + } else { + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +var standardContext = { + getRemainingTimeInMillis: () => 9e4 +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler, + isComplete, + onTimeout +}); diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.d.ts b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.d.ts new file mode 100644 index 0000000000000..cb0ff5c3b541f --- /dev/null +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.js b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.js new file mode 100644 index 0000000000000..7431fc951f282 --- /dev/null +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/* eslint-disable */ +const aws_sdk_1 = require("aws-sdk"); +exports.handler = () => { + const sqs = new aws_sdk_1.SQS(); + sqs.sendMessage({ + MessageBody: 'hello world!', + QueueUrl: process.env.QUEUE_URL, + DelaySeconds: 60, + }, (err, data) => { + if (err) { + console.log(err, err.stack); + } + else { + console.log(data); + } + }); +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLG9CQUFvQjtBQUNwQixxQ0FBNEI7QUFFNUIsT0FBTyxDQUFDLE9BQU8sR0FBRyxHQUFHLEVBQUU7SUFDbkIsTUFBTSxHQUFHLEdBQUcsSUFBSSxhQUFHLEVBQUUsQ0FBQTtJQUNyQixHQUFHLENBQUMsV0FBVyxDQUFDO1FBQ1osV0FBVyxFQUFFLGNBQWM7UUFDM0IsUUFBUSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBVztRQUNqQyxZQUFZLEVBQUUsRUFBRTtLQUNuQixFQUFFLENBQUMsR0FBRyxFQUFFLElBQUksRUFBRSxFQUFFO1FBQ2IsSUFBSSxHQUFHLEVBQUU7WUFDTCxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUE7U0FDOUI7YUFBTTtZQUNILE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDckI7SUFDTCxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlICovXG5pbXBvcnQge1NRU30gZnJvbSBcImF3cy1zZGtcIjtcblxuZXhwb3J0cy5oYW5kbGVyID0gKCkgPT4ge1xuICAgIGNvbnN0IHNxcyA9IG5ldyBTUVMoKVxuICAgIHNxcy5zZW5kTWVzc2FnZSh7XG4gICAgICAgIE1lc3NhZ2VCb2R5OiAnaGVsbG8gd29ybGQhJyxcbiAgICAgICAgUXVldWVVcmw6IHByb2Nlc3MuZW52LlFVRVVFX1VSTCEhLFxuICAgICAgICBEZWxheVNlY29uZHM6IDYwLFxuICAgIH0sIChlcnIsIGRhdGEpID0+IHtcbiAgICAgICAgaWYgKGVycikge1xuICAgICAgICAgICAgY29uc29sZS5sb2coZXJyLCBlcnIuc3RhY2spXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZyhkYXRhKTtcbiAgICAgICAgfVxuICAgIH0pO1xufTsiXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.ts b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.ts new file mode 100644 index 0000000000000..f1805b589826b --- /dev/null +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/asset.aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1/index.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ +import {SQS} from "aws-sdk"; + +exports.handler = () => { + const sqs = new SQS() + sqs.sendMessage({ + MessageBody: 'hello world!', + QueueUrl: process.env.QUEUE_URL!!, + DelaySeconds: 60, + }, (err, data) => { + if (err) { + console.log(err, err.stack) + } else { + console.log(data); + } + }); +}; \ No newline at end of file diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/cdk.out b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/cdk.out index d8b441d447f8a..b72fef144f05c 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/cdk.out +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"29.0.0"} \ No newline at end of file +{"version":"30.1.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/integ.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/integ.json index c3f252d62f24a..c69465b8a48e5 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/integ.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/integ.json @@ -1,11 +1,10 @@ { - "version": "29.0.0", + "version": "30.1.0", "testCases": { "TriggerTest/DefaultTest": { "stacks": [ "MyStack" ], - "stackUpdateWorkflow": false, "assertionStack": "TriggerTest/DefaultTest/DeployAssert", "assertionStackName": "TriggerTestDefaultTestDeployAssert61636546" } diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/manifest.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/manifest.json index af20c114f2b3a..eab642fddeec6 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/manifest.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "29.0.0", + "version": "30.1.0", "artifacts": { "MyStack.assets": { "type": "cdk:asset-manifest", @@ -17,7 +17,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4155391c7a2ef259603bc3a1d18d0bad9a2478b51ed773d2c06efab5a0a51c56.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/6c8cf3b5b61b2dc9de89993ba0c228c09fc45f87c9bb09029b2d77440b0d03ae.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -45,28 +45,28 @@ "data": "Topic269377B75" } ], - "/MyStack/MyFunction/ServiceRole/Resource": [ + "/MyStack/MyTriggerFunction/ServiceRole/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyFunctionServiceRole3C357FF2" + "data": "MyTriggerFunctionServiceRole1BB78C29" } ], - "/MyStack/MyFunction/Resource": [ + "/MyStack/MyTriggerFunction/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyFunction3BAA72D1" + "data": "MyTriggerFunction056842F6" } ], - "/MyStack/MyFunction/Trigger/Default/Default": [ + "/MyStack/MyTriggerFunction/Trigger/Default/Default": [ { "type": "aws:cdk:logicalId", - "data": "MyFunctionTriggerDB129D7B" + "data": "MyTriggerFunctionTrigger5424E7A7" } ], - "/MyStack/MyFunction/CurrentVersion/Resource": [ + "/MyStack/MyTriggerFunction/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyFunctionCurrentVersion197490AF2cb2bc11080c1ef11d3b49c1f1603957" + "data": "MyTriggerFunctionCurrentVersion61957CE160cd5b4c06c4d00191dc10a647ea0777" } ], "/MyStack/AWSCDK.TriggerCustomResourceProviderCustomResourceProvider/Role": [ @@ -81,6 +81,66 @@ "data": "AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91" } ], + "/MyStack/TestQueue/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TestQueue6F0069AA" + } + ], + "/MyStack/MyLambdaFunction/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaFunctionServiceRole313A4D46" + } + ], + "/MyStack/MyLambdaFunction/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaFunction67CCA873" + } + ], + "/MyStack/MyLambdaFunction/CurrentVersion/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaFunctionCurrentVersion4FAB80EC75f2df347bcc21ea05f818cb68778d9f" + } + ], + "/MyStack/MyTrigger/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "MyTrigger" + } + ], + "/MyStack/MyAssertionLambdaFunction/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyAssertionLambdaFunctionServiceRole36146F3B" + } + ], + "/MyStack/MyAssertionLambdaFunction/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyAssertionLambdaFunctionServiceRoleDefaultPolicyFC44077F" + } + ], + "/MyStack/MyAssertionLambdaFunction/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyAssertionLambdaFunction7E77172F" + } + ], + "/MyStack/MyAssertionLambdaFunction/CurrentVersion/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyAssertionLambdaFunctionCurrentVersionF4FA4C80e2c1d671ec67fab13e46054563e433a6" + } + ], + "/MyStack/MyAssertionTrigger/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "MyAssertionTrigger" + } + ], "/MyStack/MySecondFunction/ServiceRole/Resource": [ { "type": "aws:cdk:logicalId", @@ -105,6 +165,18 @@ "data": "MySecondFunctionCurrentVersion7D497B5D173a4bb1f758991022ea97d651403362" } ], + "/MyStack/MyDefaultPropTrigger/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "MyDefaultPropTrigger" + } + ], + "/MyStack/Exports/Output{\"Ref\":\"TestQueue6F0069AA\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefTestQueue6F0069AA4C7E94E2" + } + ], "/MyStack/BootstrapVersion": [ { "type": "aws:cdk:logicalId", @@ -136,7 +208,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8c0f4f60c3986653a24ca8464d7682bf57a7a9749e916d73da2184ea3c4e3d31.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -149,9 +221,82 @@ } }, "dependencies": [ + "MyStack", "TriggerTestDefaultTestDeployAssert61636546.assets" ], "metadata": { + "/TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessage" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/IsCompleteProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessageWaitForIsCompleteProviderInvoke92C9A498" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/TimeoutProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessageWaitForTimeoutProviderInvoke88C69E59" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessageWaitForRole90A5ABAA" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessageWaitFor10141935" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallSQSreceiveMessage" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE" + } + ], + "/TriggerTest/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA" + } + ], "/TriggerTest/DefaultTest/DeployAssert/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/tree.json b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/tree.json index fe95d9122da51..244e6e330becf 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/tree.json +++ b/packages/@aws-cdk/triggers/test/integ.triggers.js.snapshot/tree.json @@ -52,17 +52,17 @@ "version": "0.0.0" } }, - "MyFunction": { - "id": "MyFunction", - "path": "MyStack/MyFunction", + "MyTriggerFunction": { + "id": "MyTriggerFunction", + "path": "MyStack/MyTriggerFunction", "children": { "ServiceRole": { "id": "ServiceRole", - "path": "MyStack/MyFunction/ServiceRole", + "path": "MyStack/MyTriggerFunction/ServiceRole", "children": { "ImportServiceRole": { "id": "ImportServiceRole", - "path": "MyStack/MyFunction/ServiceRole/ImportServiceRole", + "path": "MyStack/MyTriggerFunction/ServiceRole/ImportServiceRole", "constructInfo": { "fqn": "@aws-cdk/core.Resource", "version": "0.0.0" @@ -70,7 +70,7 @@ }, "Resource": { "id": "Resource", - "path": "MyStack/MyFunction/ServiceRole/Resource", + "path": "MyStack/MyTriggerFunction/ServiceRole/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::IAM::Role", "aws:cdk:cloudformation:props": { @@ -115,7 +115,7 @@ }, "Resource": { "id": "Resource", - "path": "MyStack/MyFunction/Resource", + "path": "MyStack/MyTriggerFunction/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::Lambda::Function", "aws:cdk:cloudformation:props": { @@ -124,12 +124,12 @@ }, "role": { "Fn::GetAtt": [ - "MyFunctionServiceRole3C357FF2", + "MyTriggerFunctionServiceRole1BB78C29", "Arn" ] }, "handler": "index.handler", - "runtime": "nodejs14.x" + "runtime": "nodejs16.x" } }, "constructInfo": { @@ -139,15 +139,15 @@ }, "Trigger": { "id": "Trigger", - "path": "MyStack/MyFunction/Trigger", + "path": "MyStack/MyTriggerFunction/Trigger", "children": { "Default": { "id": "Default", - "path": "MyStack/MyFunction/Trigger/Default", + "path": "MyStack/MyTriggerFunction/Trigger/Default", "children": { "Default": { "id": "Default", - "path": "MyStack/MyFunction/Trigger/Default/Default", + "path": "MyStack/MyTriggerFunction/Trigger/Default/Default", "constructInfo": { "fqn": "@aws-cdk/core.CfnResource", "version": "0.0.0" @@ -167,16 +167,16 @@ }, "CurrentVersion": { "id": "CurrentVersion", - "path": "MyStack/MyFunction/CurrentVersion", + "path": "MyStack/MyTriggerFunction/CurrentVersion", "children": { "Resource": { "id": "Resource", - "path": "MyStack/MyFunction/CurrentVersion/Resource", + "path": "MyStack/MyTriggerFunction/CurrentVersion/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::Lambda::Version", "aws:cdk:cloudformation:props": { "functionName": { - "Ref": "MyFunction3BAA72D1" + "Ref": "MyTriggerFunction056842F6" } } }, @@ -231,6 +231,406 @@ "version": "0.0.0" } }, + "TestQueue": { + "id": "TestQueue", + "path": "MyStack/TestQueue", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/TestQueue/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": { + "queueName": "trigger-assertion-queue" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-sqs.CfnQueue", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-sqs.Queue", + "version": "0.0.0" + } + }, + "MyLambdaFunction": { + "id": "MyLambdaFunction", + "path": "MyStack/MyLambdaFunction", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "MyStack/MyLambdaFunction/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "MyStack/MyLambdaFunction/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/MyLambdaFunction/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "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" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/MyLambdaFunction/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "exports.handler = async function() { await setTimeout(() => {console.log(\"hi\")}, 3*60*1000); };" + }, + "role": { + "Fn::GetAtt": [ + "MyLambdaFunctionServiceRole313A4D46", + "Arn" + ] + }, + "handler": "index.handler", + "runtime": "nodejs16.x", + "timeout": 900 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnFunction", + "version": "0.0.0" + } + }, + "CurrentVersion": { + "id": "CurrentVersion", + "path": "MyStack/MyLambdaFunction/CurrentVersion", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/MyLambdaFunction/CurrentVersion/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Version", + "aws:cdk:cloudformation:props": { + "functionName": { + "Ref": "MyLambdaFunction67CCA873" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnVersion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Version", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Function", + "version": "0.0.0" + } + }, + "MyTrigger": { + "id": "MyTrigger", + "path": "MyStack/MyTrigger", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/MyTrigger/Default", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/MyTrigger/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/triggers.Trigger", + "version": "0.0.0" + } + }, + "MyAssertionLambdaFunction": { + "id": "MyAssertionLambdaFunction", + "path": "MyStack/MyAssertionLambdaFunction", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "MyStack/MyAssertionLambdaFunction/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "MyStack/MyAssertionLambdaFunction/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/MyAssertionLambdaFunction/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "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" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "MyStack/MyAssertionLambdaFunction/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/MyAssertionLambdaFunction/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:SendMessage" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TestQueue6F0069AA", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "MyAssertionLambdaFunctionServiceRoleDefaultPolicyFC44077F", + "roles": [ + { + "Ref": "MyAssertionLambdaFunctionServiceRole36146F3B" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Code": { + "id": "Code", + "path": "MyStack/MyAssertionLambdaFunction/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/MyAssertionLambdaFunction/Code/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/MyAssertionLambdaFunction/Code/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/MyAssertionLambdaFunction/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "aa1959bb6ee212354a5363c6469bb9dc0d3096ccc4b66f277b3d5e171a9462d1.zip" + }, + "role": { + "Fn::GetAtt": [ + "MyAssertionLambdaFunctionServiceRole36146F3B", + "Arn" + ] + }, + "environment": { + "variables": { + "QUEUE_URL": { + "Ref": "TestQueue6F0069AA" + } + } + }, + "handler": "index.handler", + "runtime": "nodejs16.x", + "timeout": 900 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnFunction", + "version": "0.0.0" + } + }, + "CurrentVersion": { + "id": "CurrentVersion", + "path": "MyStack/MyAssertionLambdaFunction/CurrentVersion", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/MyAssertionLambdaFunction/CurrentVersion/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Version", + "aws:cdk:cloudformation:props": { + "functionName": { + "Ref": "MyAssertionLambdaFunction7E77172F" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnVersion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Version", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Function", + "version": "0.0.0" + } + }, + "MyAssertionTrigger": { + "id": "MyAssertionTrigger", + "path": "MyStack/MyAssertionTrigger", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/MyAssertionTrigger/Default", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/MyAssertionTrigger/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/triggers.Trigger", + "version": "0.0.0" + } + }, "MySecondFunction": { "id": "MySecondFunction", "path": "MyStack/MySecondFunction", @@ -376,6 +776,52 @@ "version": "0.0.0" } }, + "MyDefaultPropTrigger": { + "id": "MyDefaultPropTrigger", + "path": "MyStack/MyDefaultPropTrigger", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/MyDefaultPropTrigger/Default", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/MyDefaultPropTrigger/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/triggers.Trigger", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "MyStack/Exports", + "children": { + "Output{\"Ref\":\"TestQueue6F0069AA\"}": { + "id": "Output{\"Ref\":\"TestQueue6F0069AA\"}", + "path": "MyStack/Exports/Output{\"Ref\":\"TestQueue6F0069AA\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.264" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "MyStack/BootstrapVersion", @@ -411,13 +857,247 @@ "path": "TriggerTest/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.216" + "version": "10.1.264" } }, "DeployAssert": { "id": "DeployAssert", "path": "TriggerTest/DefaultTest/DeployAssert", "children": { + "AwsApiCallSQSreceiveMessage": { + "id": "AwsApiCallSQSreceiveMessage", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.264" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/Default", + "children": { + "Default": { + "id": "Default", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "WaitFor": { + "id": "WaitFor", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor", + "children": { + "IsCompleteProvider": { + "id": "IsCompleteProvider", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/IsCompleteProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/IsCompleteProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.264" + } + }, + "Invoke": { + "id": "Invoke", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/IsCompleteProvider/Invoke", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "TimeoutProvider": { + "id": "TimeoutProvider", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/TimeoutProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/TimeoutProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.264" + } + }, + "Invoke": { + "id": "Invoke", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/TimeoutProvider/Invoke", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/Resource", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.WaiterStateMachine", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "TriggerTest/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.264" + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925": { + "id": "SingletonFunction76b3e830a873425f8453eddd85c86925", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925", + "children": { + "Staging": { + "id": "Staging", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.264" + } + }, + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a": { + "id": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a", + "children": { + "Staging": { + "id": "Staging", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "TriggerTest/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.264" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "TriggerTest/DefaultTest/DeployAssert/BootstrapVersion", @@ -457,7 +1137,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.216" + "version": "10.1.264" } } }, diff --git a/packages/@aws-cdk/triggers/test/integ.triggers.ts b/packages/@aws-cdk/triggers/test/integ.triggers.ts index e59101eaf53b4..4cf504f239d10 100644 --- a/packages/@aws-cdk/triggers/test/integ.triggers.ts +++ b/packages/@aws-cdk/triggers/test/integ.triggers.ts @@ -1,8 +1,11 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; -import { App, Stack } from '@aws-cdk/core'; +import * as sqs from '@aws-cdk/aws-sqs'; +import { App, Duration, Stack } from '@aws-cdk/core'; import * as integ from '@aws-cdk/integ-tests'; +import { ExpectedResult } from '@aws-cdk/integ-tests'; import * as triggers from '../lib'; +import { InvocationType } from '../lib'; const app = new App(); const stack = new Stack(app, 'MyStack'); @@ -10,13 +13,51 @@ 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', { - runtime: lambda.Runtime.NODEJS_14_X, +const triggerFunction = new triggers.TriggerFunction(stack, 'MyTriggerFunction', { + runtime: lambda.Runtime.NODEJS_16_X, handler: 'index.handler', code: lambda.Code.fromInline('exports.handler = function() { console.log("hi"); };'), executeBefore: [topic1], }); +const assertionQueue = new sqs.Queue(stack, 'TestQueue', { + queueName: 'trigger-assertion-queue', +}); + +const func = new lambda.Function(stack, 'MyLambdaFunction', { + runtime: lambda.Runtime.NODEJS_16_X, + handler: 'index.handler', + timeout: Duration.minutes(15), + code: lambda.Code.fromInline('exports.handler = async function() { await setTimeout(() => {console.log("hi")}, 3*60*1000); };'), +}); + +const trigger = new triggers.Trigger(stack, 'MyTrigger', { + handler: func, + invocationType: InvocationType.EVENT, + timeout: Duration.minutes(1), + executeAfter: [topic1], +}); + +const funcWithAssertion = new lambda.Function(stack, 'MyAssertionLambdaFunction', { + runtime: lambda.Runtime.NODEJS_16_X, + handler: 'index.handler', + timeout: Duration.minutes(15), + code: lambda.Code.fromAsset('lib'), + environment: { + QUEUE_URL: assertionQueue.queueUrl, + }, +}); + +assertionQueue.grantSendMessages(funcWithAssertion); + +new triggers.Trigger(stack, 'MyAssertionTrigger', { + handler: funcWithAssertion, + invocationType: InvocationType.REQUEST_RESPONSE, + timeout: Duration.minutes(1), + executeAfter: [assertionQueue], +}); + +triggerFunction.executeAfter(topic2); trigger.executeAfter(topic2); new triggers.TriggerFunction(stack, 'MySecondFunction', { @@ -25,8 +66,21 @@ new triggers.TriggerFunction(stack, 'MySecondFunction', { code: lambda.Code.fromInline('exports.handler = function() { console.log("hello"); };'), }); -new integ.IntegTest(app, 'TriggerTest', { +new triggers.Trigger(stack, 'MyDefaultPropTrigger', { + handler: func, +}); + +const testCase = new integ.IntegTest(app, 'TriggerTest', { testCases: [stack], }); +testCase.assertions.awsApiCall('SQS', 'receiveMessage', { + QueueUrl: assertionQueue.queueUrl, + WaitTimeSeconds: 20, +}).assertAtPath('Messages.0.Body', ExpectedResult.stringLikeRegexp('^hello world!$')).waitForAssertions({ + totalTimeout: Duration.minutes(5), + interval: Duration.seconds(15), + backoffRate: 3, +}); + app.synth(); diff --git a/packages/@aws-cdk/triggers/test/lib/index.ts b/packages/@aws-cdk/triggers/test/lib/index.ts new file mode 100644 index 0000000000000..f1805b589826b --- /dev/null +++ b/packages/@aws-cdk/triggers/test/lib/index.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ +import {SQS} from "aws-sdk"; + +exports.handler = () => { + const sqs = new SQS() + sqs.sendMessage({ + MessageBody: 'hello world!', + QueueUrl: process.env.QUEUE_URL!!, + DelaySeconds: 60, + }, (err, data) => { + if (err) { + console.log(err, err.stack) + } else { + console.log(data); + } + }); +}; \ No newline at end of file diff --git a/packages/@aws-cdk/triggers/test/trigger-handler.test.ts b/packages/@aws-cdk/triggers/test/trigger-handler.test.ts index 18eea6a881ceb..bd1c54bebbb86 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', @@ -39,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 () => { @@ -64,7 +66,18 @@ 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('202 status code success', async () => { + promiseMock.mockResolvedValueOnce({ + StatusCode: 202, + }); + + await lambda.handler(({ RequestType: 'Create', ...mockRequest })); + + expect(invokeMock).toBeCalledTimes(1); + expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn, InvocationType: 'Event' }); }); test('retry with access denied exception', async () => { @@ -81,7 +94,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 () => { @@ -94,7 +107,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', () => { @@ -111,7 +124,7 @@ describe('function error', () => { .toMatchObject({ message: expectedError }); expect(invokeMock).toBeCalledTimes(1); - expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn }); + expect(invokeMock).toBeCalledWith({ FunctionName: handlerArn, InvocationType: 'Event' }); }; }; diff --git a/packages/@aws-cdk/triggers/test/triggers.test.ts b/packages/@aws-cdk/triggers/test/triggers.test.ts index cc4edb610bade..2cad53a2ae590 100644 --- a/packages/@aws-cdk/triggers/test/triggers.test.ts +++ b/packages/@aws-cdk/triggers/test/triggers.test.ts @@ -1,10 +1,11 @@ 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'; +import { InvocationType } from '../lib'; -test('minimal', () => { +test('minimal trigger function', () => { // GIVEN const stack = new Stack(); @@ -89,3 +90,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: InvocationType.EVENT, + }); + + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Lambda::Function', {}); + template.hasResourceProperties('Custom::Trigger', { + HandlerArn: { Ref: 'MyFunctionCurrentVersion197490AF2e4e06d52af2bb609d8c23243d665966' }, + Timeout: '600000', + InvocationType: 'Event', + }); +});