From 700f9c4d465684b784f50ec74e897c9031a639c5 Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Wed, 12 Oct 2022 14:33:01 -0400 Subject: [PATCH] feat(integ-tests): add ability to `wait` for assertions to pass (#22335) This PR adds a `waitForAssertions` method that allows you to wait until an assertion passes. In order to accomplish this I've copied from the [asynchronous providers](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html#asynchronous-providers-iscomplete) implementation in the `@aws-cdk/custom-resources` module. I've updated 3 integration tests to use this functionality to demonstrate the possible use cases. ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-autoscaling-hooktargets/package.json | 2 + .../test/integ.queue-hook.ts | 78 + .../index.js | 768 +++++++++ .../test/queue-hook.integ.snapshot/cdk.out | 1 + .../integ-autoscalinghook-queue.assets.json | 19 + .../integ-autoscalinghook-queue.template.json | 663 ++++++++ .../test/queue-hook.integ.snapshot/integ.json | 12 + .../queue-hook.integ.snapshot/manifest.json | 406 +++++ ...efaultTestDeployAssertCF5493DF.assets.json | 32 + ...aultTestDeployAssertCF5493DF.template.json | 446 ++++++ .../test/queue-hook.integ.snapshot/tree.json | 1421 +++++++++++++++++ .../test/integ.pipeline-s3-deploy.ts | 41 +- .../__entrypoint__.js | 118 ++ .../index.d.ts | 1 + .../index.js | 78 + .../index.ts | 82 + .../index.js | 768 +++++++++ ...aws-cdk-codepipeline-s3-deploy.assets.json | 19 +- ...s-cdk-codepipeline-s3-deploy.template.json | 321 +++- .../pipeline-s3-deploy.integ.snapshot/cdk.out | 2 +- .../integ.json | 12 +- .../manifest.json | 202 ++- ...efaultTestDeployAssert6BC61647.assets.json | 32 + ...aultTestDeployAssert6BC61647.template.json | 437 +++++ .../tree.json | 744 ++++++++- .../test/integ.lambda-chain.ts | 29 +- ...efaultTestDeployAssertB72F10E8.assets.json | 32 + ...aultTestDeployAssertB72F10E8.template.json | 451 ++++++ .../index.js | 768 +++++++++ .../aws-cdk-lambda-chain.assets.json | 6 +- .../aws-cdk-lambda-chain.template.json | 24 +- .../test/lambda-chain.integ.snapshot/cdk.out | 2 +- .../lambda-chain.integ.snapshot/integ.json | 12 +- .../lambda-chain.integ.snapshot/manifest.json | 148 +- .../lambda-chain.integ.snapshot/tree.json | 388 ++++- packages/@aws-cdk/integ-tests/README.md | 49 + .../lib/assertions/api-call-base.ts | 23 +- .../integ-tests/lib/assertions/index.ts | 1 + .../providers/lambda-handler/base.ts | 50 +- .../providers/lambda-handler/index.ts | 91 +- .../lib/assertions/providers/provider.ts | 103 +- .../integ-tests/lib/assertions/sdk.ts | 39 +- .../lib/assertions/waiter-state-machine.ts | 182 +++ .../integ-tests/rosetta/default.ts-fixture | 1 + .../providers/lambda-handler/base.test.ts | 110 +- .../integ-tests/test/assertions/sdk.test.ts | 120 +- 46 files changed, 9213 insertions(+), 121 deletions(-) create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/integ.queue-hook.ts create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ-autoscalinghook-queue.assets.json create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ-autoscalinghook-queue.template.json create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/queuehooktestDefaultTestDeployAssertCF5493DF.assets.json create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/queuehooktestDefaultTestDeployAssertCF5493DF.template.json create mode 100644 packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/__entrypoint__.js create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.d.ts create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.js create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.assets.json create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.template.json create mode 100644 packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/LambdaDestChain3DefaultTestDeployAssertB72F10E8.assets.json create mode 100644 packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/LambdaDestChain3DefaultTestDeployAssertB72F10E8.template.json create mode 100644 packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js create mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/waiter-state-machine.ts diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json index cbe4881aa443d..c8aa0e9524665 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json @@ -74,8 +74,10 @@ "devDependencies": { "@aws-cdk/assertions": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-ssm": "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/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/integ.queue-hook.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/integ.queue-hook.ts new file mode 100644 index 0000000000000..44160510d734c --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/integ.queue-hook.ts @@ -0,0 +1,78 @@ +import * as scaling from '@aws-cdk/aws-autoscaling'; +import { Vpc, InstanceType, InstanceClass, InstanceSize, OperatingSystemType, UserData } from '@aws-cdk/aws-ec2'; +import { Queue } from '@aws-cdk/aws-sqs'; +import { StringParameter } from '@aws-cdk/aws-ssm'; +import * as cdk from '@aws-cdk/core'; +import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests'; +import { Construct } from 'constructs'; +import { QueueHook } from '../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + public readonly queueUrl: string; + public readonly groupName: string; + public readonly hookName: string; + constructor(scope: Construct, id: string) { + super(scope, id); + + const queue = new Queue(this, 'HookQueue'); + this.queueUrl = queue.queueUrl; + const group = new scaling.AutoScalingGroup(this, 'Group', { + vpc: new Vpc(this, 'Vpc'), + maxCapacity: 1, + minCapacity: 0, + instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.SMALL), + machineImage: { + getImage: () => { + return { + osType: OperatingSystemType.LINUX, + userData: UserData.forLinux(), + imageId: StringParameter.fromStringParameterName(this, 'al2022AMI', '/aws/service/ami-amazon-linux-latest/al2022-ami-kernel-default-x86_64').stringValue, + }; + }, + }, + }); + this.groupName = group.autoScalingGroupName; + const hook = group.addLifecycleHook('scaleout', { + lifecycleTransition: scaling.LifecycleTransition.INSTANCE_LAUNCHING, + notificationTarget: new QueueHook(queue), + }); + this.hookName = hook.lifecycleHookName; + + } +} + +const testCase = new TestStack(app, 'integ-autoscalinghook-queue'); +const integ = new IntegTest(app, 'queue-hook-test', { + testCases: [testCase], +}); + +const setDesired = integ.assertions.awsApiCall('AutoScaling', 'setDesiredCapacity', { + AutoScalingGroupName: testCase.groupName, + DesiredCapacity: 1, +}); + + +const message = integ.assertions.awsApiCall('SQS', 'receiveMessage', { + QueueUrl: testCase.queueUrl, +}); +message.assertAtPath( + 'Messages.0.Body.LifecycleTransition', + ExpectedResult.stringLikeRegexp('autoscaling:EC2_INSTANCE_LAUNCHING'), +).waitForAssertions(); + +const token = message.getAttString('Messages.0.Body.LifecycleActionToken'); + +const completeAction = integ.assertions.awsApiCall('AutoScaling', 'completeLifecycleAction', { + AutoScalingGroupName: testCase.groupName, + LifecycleActionResult: 'CONTINUE', + LifecycleActionToken: token, + LifecycleHookName: testCase.hookName, +}); + +setDesired.next( + message.next( + completeAction, + ), +); diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js new file mode 100644 index 0000000000000..2d6c2f0e85497 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js @@ -0,0 +1,768 @@ +"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( + 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 { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../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/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + 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)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + 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 (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + 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" + }); + } + } + } + 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}' among {${Object.keys(actual).join(",")}}` + }); + 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) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +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); + } + 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); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + 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.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + 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 + }; + } + 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]); + 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) + }; + const resp = request2.flattenResponse === "true" ? flatData : respond; + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +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", + 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/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ-autoscalinghook-queue.assets.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ-autoscalinghook-queue.assets.json new file mode 100644 index 0000000000000..55828adbc5912 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ-autoscalinghook-queue.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "512271f7a4721942ff4cf09cc4e4500d86b0e93fd0a1214307f7caf0a387bbc8": { + "source": { + "path": "integ-autoscalinghook-queue.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "512271f7a4721942ff4cf09cc4e4500d86b0e93fd0a1214307f7caf0a387bbc8.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ-autoscalinghook-queue.template.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ-autoscalinghook-queue.template.json new file mode 100644 index 0000000000000..01d2a66ea5ac7 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ-autoscalinghook-queue.template.json @@ -0,0 +1,663 @@ +{ + "Resources": { + "HookQueue3D68F56B": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PublicSubnet1" + } + ] + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1RouteTableAssociation97140677" + ] + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PublicSubnet2" + } + ] + }, + "DependsOn": [ + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTableAssociationDD5762D8" + ] + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.192.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "GroupInstanceSecurityGroupBAF83E2C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "integ-autoscalinghook-queue/Group/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Group" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "GroupInstanceRole918A2AF7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Group" + } + ] + } + }, + "GroupInstanceProfileA2F3B693": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "GroupInstanceRole918A2AF7" + } + ] + } + }, + "GroupLaunchConfigEA6271FF": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "al2022AMIParameter" + }, + "InstanceType": "t3.small", + "IamInstanceProfile": { + "Ref": "GroupInstanceProfileA2F3B693" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "GroupInstanceSecurityGroupBAF83E2C", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash" + } + }, + "DependsOn": [ + "GroupInstanceRole918A2AF7" + ] + }, + "GroupASG3C6DFE3B": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "0", + "LaunchConfigurationName": { + "Ref": "GroupLaunchConfigEA6271FF" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "integ-autoscalinghook-queue/Group" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + }, + "UpdatePolicy": { + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "GroupLifecycleHookscaleoutRoleA934DDFC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-autoscalinghook-queue/Group" + } + ] + } + }, + "GroupLifecycleHookscaleoutRoleDefaultPolicyCA2A774A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:SendMessage" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "HookQueue3D68F56B", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "GroupLifecycleHookscaleoutRoleDefaultPolicyCA2A774A", + "Roles": [ + { + "Ref": "GroupLifecycleHookscaleoutRoleA934DDFC" + } + ] + } + }, + "GroupLifecycleHookscaleoutB603BFDE": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "GroupASG3C6DFE3B" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING", + "NotificationTargetARN": { + "Fn::GetAtt": [ + "HookQueue3D68F56B", + "Arn" + ] + }, + "RoleARN": { + "Fn::GetAtt": [ + "GroupLifecycleHookscaleoutRoleA934DDFC", + "Arn" + ] + } + }, + "DependsOn": [ + "GroupLifecycleHookscaleoutRoleDefaultPolicyCA2A774A", + "GroupLifecycleHookscaleoutRoleA934DDFC" + ] + } + }, + "Parameters": { + "al2022AMIParameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/al2022-ami-kernel-default-x86_64" + }, + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Outputs": { + "ExportsOutputRefGroupASG3C6DFE3B7A0874DF": { + "Value": { + "Ref": "GroupASG3C6DFE3B" + }, + "Export": { + "Name": "integ-autoscalinghook-queue:ExportsOutputRefGroupASG3C6DFE3B7A0874DF" + } + }, + "ExportsOutputRefHookQueue3D68F56BCF6354BE": { + "Value": { + "Ref": "HookQueue3D68F56B" + }, + "Export": { + "Name": "integ-autoscalinghook-queue:ExportsOutputRefHookQueue3D68F56BCF6354BE" + } + }, + "ExportsOutputRefGroupLifecycleHookscaleoutB603BFDE12D86BC4": { + "Value": { + "Ref": "GroupLifecycleHookscaleoutB603BFDE" + }, + "Export": { + "Name": "integ-autoscalinghook-queue:ExportsOutputRefGroupLifecycleHookscaleoutB603BFDE12D86BC4" + } + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ.json new file mode 100644 index 0000000000000..50467bc25b39a --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "queue-hook-test/DefaultTest": { + "stacks": [ + "integ-autoscalinghook-queue" + ], + "assertionStack": "queue-hook-test/DefaultTest/DeployAssert", + "assertionStackName": "queuehooktestDefaultTestDeployAssertCF5493DF" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..8cfc104d523bd --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/manifest.json @@ -0,0 +1,406 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "integ-autoscalinghook-queue.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-autoscalinghook-queue.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-autoscalinghook-queue": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-autoscalinghook-queue.template.json", + "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}/512271f7a4721942ff4cf09cc4e4500d86b0e93fd0a1214307f7caf0a387bbc8.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-autoscalinghook-queue.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-autoscalinghook-queue.assets" + ], + "metadata": { + "/integ-autoscalinghook-queue/HookQueue/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "HookQueue3D68F56B" + } + ], + "/integ-autoscalinghook-queue/Vpc/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Vpc8378EB38" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1Subnet5C2D37C4" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTable6C95E38E" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTableAssociation97140677" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1DefaultRoute3DA9E72A" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1EIPD7E02669" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1NATGateway4D7517AA" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2RouteTable94F7E489" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2RouteTableAssociationDD5762D8" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2DefaultRoute97F91067" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet2/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2EIP3C605A87" + } + ], + "/integ-autoscalinghook-queue/Vpc/PublicSubnet2/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2NATGateway9182C01D" + } + ], + "/integ-autoscalinghook-queue/Vpc/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1Subnet536B997A" + } + ], + "/integ-autoscalinghook-queue/Vpc/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1RouteTableB2C5B500" + } + ], + "/integ-autoscalinghook-queue/Vpc/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1RouteTableAssociation70C59FA6" + } + ], + "/integ-autoscalinghook-queue/Vpc/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1DefaultRouteBE02A9ED" + } + ], + "/integ-autoscalinghook-queue/Vpc/PrivateSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2Subnet3788AAA1" + } + ], + "/integ-autoscalinghook-queue/Vpc/PrivateSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2RouteTableA678073B" + } + ], + "/integ-autoscalinghook-queue/Vpc/PrivateSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2RouteTableAssociationA89CAD56" + } + ], + "/integ-autoscalinghook-queue/Vpc/PrivateSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2DefaultRoute060D2087" + } + ], + "/integ-autoscalinghook-queue/Vpc/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcIGWD7BA715C" + } + ], + "/integ-autoscalinghook-queue/Vpc/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcVPCGWBF912B6E" + } + ], + "/integ-autoscalinghook-queue/Group/InstanceSecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GroupInstanceSecurityGroupBAF83E2C" + } + ], + "/integ-autoscalinghook-queue/Group/InstanceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GroupInstanceRole918A2AF7" + } + ], + "/integ-autoscalinghook-queue/Group/InstanceProfile": [ + { + "type": "aws:cdk:logicalId", + "data": "GroupInstanceProfileA2F3B693" + } + ], + "/integ-autoscalinghook-queue/Group/LaunchConfig": [ + { + "type": "aws:cdk:logicalId", + "data": "GroupLaunchConfigEA6271FF" + } + ], + "/integ-autoscalinghook-queue/Group/ASG": [ + { + "type": "aws:cdk:logicalId", + "data": "GroupASG3C6DFE3B" + } + ], + "/integ-autoscalinghook-queue/Group/LifecycleHookscaleout/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GroupLifecycleHookscaleoutRoleA934DDFC" + } + ], + "/integ-autoscalinghook-queue/Group/LifecycleHookscaleout/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GroupLifecycleHookscaleoutRoleDefaultPolicyCA2A774A" + } + ], + "/integ-autoscalinghook-queue/Group/LifecycleHookscaleout/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GroupLifecycleHookscaleoutB603BFDE" + } + ], + "/integ-autoscalinghook-queue/al2022AMI.Parameter": [ + { + "type": "aws:cdk:logicalId", + "data": "al2022AMIParameter" + } + ], + "/integ-autoscalinghook-queue/Exports/Output{\"Ref\":\"GroupASG3C6DFE3B\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefGroupASG3C6DFE3B7A0874DF" + } + ], + "/integ-autoscalinghook-queue/Exports/Output{\"Ref\":\"HookQueue3D68F56B\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefHookQueue3D68F56BCF6354BE" + } + ], + "/integ-autoscalinghook-queue/Exports/Output{\"Ref\":\"GroupLifecycleHookscaleoutB603BFDE\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefGroupLifecycleHookscaleoutB603BFDE12D86BC4" + } + ], + "/integ-autoscalinghook-queue/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-autoscalinghook-queue/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-autoscalinghook-queue" + }, + "queuehooktestDefaultTestDeployAssertCF5493DF.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "queuehooktestDefaultTestDeployAssertCF5493DF.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "queuehooktestDefaultTestDeployAssertCF5493DF": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "queuehooktestDefaultTestDeployAssertCF5493DF.template.json", + "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}/2952a5f1ee2df6894e782c86d2be1fdd7d53f82d214752f25a6a79e822dabf3e.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "queuehooktestDefaultTestDeployAssertCF5493DF.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-autoscalinghook-queue", + "queuehooktestDefaultTestDeployAssertCF5493DF.assets" + ], + "metadata": { + "/queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingsetDesiredCapacity/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallAutoScalingsetDesiredCapacity" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessage" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/IsCompleteProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessageWaitForIsCompleteProviderInvoke92C9A498" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/TimeoutProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessageWaitForTimeoutProviderInvoke88C69E59" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessageWaitForRole90A5ABAA" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSreceiveMessageWaitFor10141935" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallSQSreceiveMessage" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingcompleteLifecycleAction/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallAutoScalingcompleteLifecycleAction" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/queue-hook-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "queue-hook-test/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/queuehooktestDefaultTestDeployAssertCF5493DF.assets.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/queuehooktestDefaultTestDeployAssertCF5493DF.assets.json new file mode 100644 index 0000000000000..9298b7a00d47c --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/queuehooktestDefaultTestDeployAssertCF5493DF.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b": { + "source": { + "path": "asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "2952a5f1ee2df6894e782c86d2be1fdd7d53f82d214752f25a6a79e822dabf3e": { + "source": { + "path": "queuehooktestDefaultTestDeployAssertCF5493DF.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2952a5f1ee2df6894e782c86d2be1fdd7d53f82d214752f25a6a79e822dabf3e.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/queuehooktestDefaultTestDeployAssertCF5493DF.template.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/queuehooktestDefaultTestDeployAssertCF5493DF.template.json new file mode 100644 index 0000000000000..de0f12fea9f21 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/queuehooktestDefaultTestDeployAssertCF5493DF.template.json @@ -0,0 +1,446 @@ +{ + "Resources": { + "AwsApiCallAutoScalingsetDesiredCapacity": { + "Type": "Custom::DeployAssert@SdkCallAutoScalingsetDesiredCapacity", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "AutoScaling", + "api": "setDesiredCapacity", + "parameters": { + "AutoScalingGroupName": { + "Fn::ImportValue": "integ-autoscalinghook-queue:ExportsOutputRefGroupASG3C6DFE3B7A0874DF" + }, + "DesiredCapacity": 1 + }, + "flattenResponse": "false", + "salt": "1664821428400" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "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": [ + "autoscaling:SetDesiredCapacity" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "sqs:ReceiveMessage" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "states:StartExecution" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "autoscaling:CompleteLifecycleAction" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + }, + "AwsApiCallSQSreceiveMessage": { + "Type": "Custom::DeployAssert@SdkCallSQSreceiveMessage", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "SQS", + "api": "receiveMessage", + "expected": "{\"$StringLike\":\"autoscaling:EC2_INSTANCE_LAUNCHING\"}", + "actualPath": "Messages.0.Body.LifecycleTransition", + "stateMachineArn": { + "Ref": "AwsApiCallSQSreceiveMessageWaitFor10141935" + }, + "parameters": { + "QueueUrl": { + "Fn::ImportValue": "integ-autoscalinghook-queue:ExportsOutputRefHookQueue3D68F56BCF6354BE" + } + }, + "flattenResponse": "true", + "salt": "1664821428400" + }, + "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\":5,\"MaxAttempts\":360,\"BackoffRate\":1}],\"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" + ] + }, + "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": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.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": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.onTimeout", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE", + "Arn" + ] + } + } + }, + "AwsApiCallAutoScalingcompleteLifecycleAction": { + "Type": "Custom::DeployAssert@SdkCallAutoScalingcompleteLifecycleActi", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "AutoScaling", + "api": "completeLifecycleAction", + "parameters": { + "AutoScalingGroupName": { + "Fn::ImportValue": "integ-autoscalinghook-queue:ExportsOutputRefGroupASG3C6DFE3B7A0874DF" + }, + "LifecycleActionResult": "CONTINUE", + "LifecycleActionToken": { + "Fn::GetAtt": [ + "AwsApiCallSQSreceiveMessage", + "apiCallResponse.Messages.0.Body.LifecycleActionToken" + ] + }, + "LifecycleHookName": { + "Fn::ImportValue": "integ-autoscalinghook-queue:ExportsOutputRefGroupLifecycleHookscaleoutB603BFDE12D86BC4" + } + }, + "flattenResponse": "false", + "salt": "1664821428402" + }, + "DependsOn": [ + "AwsApiCallAutoScalingsetDesiredCapacity", + "AwsApiCallSQSreceiveMessage", + "AwsApiCallSQSreceiveMessageWaitForIsCompleteProviderInvoke92C9A498", + "AwsApiCallSQSreceiveMessageWaitFor10141935", + "AwsApiCallSQSreceiveMessageWaitForRole90A5ABAA", + "AwsApiCallSQSreceiveMessageWaitForTimeoutProviderInvoke88C69E59" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "AssertionResultsAwsApiCallSQSreceiveMessage": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallSQSreceiveMessage", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/tree.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/tree.json new file mode 100644 index 0000000000000..9218f01b8abc7 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/queue-hook.integ.snapshot/tree.json @@ -0,0 +1,1421 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "integ-autoscalinghook-queue": { + "id": "integ-autoscalinghook-queue", + "path": "integ-autoscalinghook-queue", + "children": { + "HookQueue": { + "id": "HookQueue", + "path": "integ-autoscalinghook-queue/HookQueue", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-autoscalinghook-queue/HookQueue/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-sqs.CfnQueue", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-sqs.Queue", + "version": "0.0.0" + } + }, + "Vpc": { + "id": "Vpc", + "path": "integ-autoscalinghook-queue/Vpc", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-autoscalinghook-queue/Vpc/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "allocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PublicSubnet2": { + "id": "PublicSubnet2", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.64.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "subnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet2/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "integ-autoscalinghook-queue/Vpc/PublicSubnet2/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "allocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "subnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet2": { + "id": "PrivateSubnet2", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.192.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "subnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integ-autoscalinghook-queue/Vpc/PrivateSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "integ-autoscalinghook-queue/Vpc/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "integ-autoscalinghook-queue/Vpc/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "internetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.Vpc", + "version": "0.0.0" + } + }, + "Group": { + "id": "Group", + "path": "integ-autoscalinghook-queue/Group", + "children": { + "InstanceSecurityGroup": { + "id": "InstanceSecurityGroup", + "path": "integ-autoscalinghook-queue/Group/InstanceSecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-autoscalinghook-queue/Group/InstanceSecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "integ-autoscalinghook-queue/Group/InstanceSecurityGroup", + "securityGroupEgress": [ + { + "cidrIp": "0.0.0.0/0", + "description": "Allow all outbound traffic by default", + "ipProtocol": "-1" + } + ], + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Group" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.SecurityGroup", + "version": "0.0.0" + } + }, + "InstanceRole": { + "id": "InstanceRole", + "path": "integ-autoscalinghook-queue/Group/InstanceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-autoscalinghook-queue/Group/InstanceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Group" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "InstanceProfile": { + "id": "InstanceProfile", + "path": "integ-autoscalinghook-queue/Group/InstanceProfile", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::InstanceProfile", + "aws:cdk:cloudformation:props": { + "roles": [ + { + "Ref": "GroupInstanceRole918A2AF7" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnInstanceProfile", + "version": "0.0.0" + } + }, + "LaunchConfig": { + "id": "LaunchConfig", + "path": "integ-autoscalinghook-queue/Group/LaunchConfig", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AutoScaling::LaunchConfiguration", + "aws:cdk:cloudformation:props": { + "imageId": { + "Ref": "al2022AMIParameter" + }, + "instanceType": "t3.small", + "iamInstanceProfile": { + "Ref": "GroupInstanceProfileA2F3B693" + }, + "securityGroups": [ + { + "Fn::GetAtt": [ + "GroupInstanceSecurityGroupBAF83E2C", + "GroupId" + ] + } + ], + "userData": { + "Fn::Base64": "#!/bin/bash" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.CfnLaunchConfiguration", + "version": "0.0.0" + } + }, + "ASG": { + "id": "ASG", + "path": "integ-autoscalinghook-queue/Group/ASG", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AutoScaling::AutoScalingGroup", + "aws:cdk:cloudformation:props": { + "maxSize": "1", + "minSize": "0", + "launchConfigurationName": { + "Ref": "GroupLaunchConfigEA6271FF" + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Group", + "propagateAtLaunch": true + } + ], + "vpcZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.CfnAutoScalingGroup", + "version": "0.0.0" + } + }, + "LifecycleHookscaleout": { + "id": "LifecycleHookscaleout", + "path": "integ-autoscalinghook-queue/Group/LifecycleHookscaleout", + "children": { + "Role": { + "id": "Role", + "path": "integ-autoscalinghook-queue/Group/LifecycleHookscaleout/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-autoscalinghook-queue/Group/LifecycleHookscaleout/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "tags": [ + { + "key": "Name", + "value": "integ-autoscalinghook-queue/Group" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "integ-autoscalinghook-queue/Group/LifecycleHookscaleout/Role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-autoscalinghook-queue/Group/LifecycleHookscaleout/Role/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": [ + "HookQueue3D68F56B", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "GroupLifecycleHookscaleoutRoleDefaultPolicyCA2A774A", + "roles": [ + { + "Ref": "GroupLifecycleHookscaleoutRoleA934DDFC" + } + ] + } + }, + "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" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-autoscalinghook-queue/Group/LifecycleHookscaleout/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AutoScaling::LifecycleHook", + "aws:cdk:cloudformation:props": { + "autoScalingGroupName": { + "Ref": "GroupASG3C6DFE3B" + }, + "lifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING", + "notificationTargetArn": { + "Fn::GetAtt": [ + "HookQueue3D68F56B", + "Arn" + ] + }, + "roleArn": { + "Fn::GetAtt": [ + "GroupLifecycleHookscaleoutRoleA934DDFC", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.CfnLifecycleHook", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.LifecycleHook", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.AutoScalingGroup", + "version": "0.0.0" + } + }, + "al2022AMI.Parameter": { + "id": "al2022AMI.Parameter", + "path": "integ-autoscalinghook-queue/al2022AMI.Parameter", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "al2022AMI": { + "id": "al2022AMI", + "path": "integ-autoscalinghook-queue/al2022AMI", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "integ-autoscalinghook-queue/Exports", + "children": { + "Output{\"Ref\":\"GroupASG3C6DFE3B\"}": { + "id": "Output{\"Ref\":\"GroupASG3C6DFE3B\"}", + "path": "integ-autoscalinghook-queue/Exports/Output{\"Ref\":\"GroupASG3C6DFE3B\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Output{\"Ref\":\"HookQueue3D68F56B\"}": { + "id": "Output{\"Ref\":\"HookQueue3D68F56B\"}", + "path": "integ-autoscalinghook-queue/Exports/Output{\"Ref\":\"HookQueue3D68F56B\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Output{\"Ref\":\"GroupLifecycleHookscaleoutB603BFDE\"}": { + "id": "Output{\"Ref\":\"GroupLifecycleHookscaleoutB603BFDE\"}", + "path": "integ-autoscalinghook-queue/Exports/Output{\"Ref\":\"GroupLifecycleHookscaleoutB603BFDE\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "queue-hook-test": { + "id": "queue-hook-test", + "path": "queue-hook-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "queue-hook-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "queue-hook-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "queue-hook-test/DefaultTest/DeployAssert", + "children": { + "AwsApiCallAutoScalingsetDesiredCapacity": { + "id": "AwsApiCallAutoScalingsetDesiredCapacity", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingsetDesiredCapacity", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingsetDesiredCapacity/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingsetDesiredCapacity/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingsetDesiredCapacity/Default", + "children": { + "Default": { + "id": "Default", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingsetDesiredCapacity/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/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "AwsApiCallSQSreceiveMessage": { + "id": "AwsApiCallSQSreceiveMessage", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/Default", + "children": { + "Default": { + "id": "Default", + "path": "queue-hook-test/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": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor", + "children": { + "IsCompleteProvider": { + "id": "IsCompleteProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/IsCompleteProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/IsCompleteProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "Invoke": { + "id": "Invoke", + "path": "queue-hook-test/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": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/TimeoutProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/TimeoutProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "Invoke": { + "id": "Invoke", + "path": "queue-hook-test/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": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallSQSreceiveMessage/WaitFor/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "queue-hook-test/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": "queue-hook-test/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" + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925": { + "id": "SingletonFunction76b3e830a873425f8453eddd85c86925", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925", + "children": { + "Staging": { + "id": "Staging", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a": { + "id": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a", + "children": { + "Staging": { + "id": "Staging", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "queue-hook-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "AwsApiCallAutoScalingcompleteLifecycleAction": { + "id": "AwsApiCallAutoScalingcompleteLifecycleAction", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingcompleteLifecycleAction", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingcompleteLifecycleAction/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingcompleteLifecycleAction/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingcompleteLifecycleAction/Default", + "children": { + "Default": { + "id": "Default", + "path": "queue-hook-test/DefaultTest/DeployAssert/AwsApiCallAutoScalingcompleteLifecycleAction/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/integ-tests.AwsApiCall", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts index 12560c08610fe..3a683676d29dc 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts @@ -1,6 +1,8 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; +import { IntegTest, ExpectedResult, Match } from '@aws-cdk/integ-tests'; import * as cpactions from '../lib'; const app = new cdk.App(); @@ -10,6 +12,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-s3-deploy'); const bucket = new s3.Bucket(stack, 'PipelineBucket', { versioned: true, removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, }); const sourceOutput = new codepipeline.Artifact('SourceArtifact'); const sourceAction = new cpactions.S3SourceAction({ @@ -21,13 +24,15 @@ const sourceAction = new cpactions.S3SourceAction({ const deployBucket = new s3.Bucket(stack, 'DeployBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, }); const otherDeployBucket = new s3.Bucket(stack, 'OtherDeployBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, }); -new codepipeline.Pipeline(stack, 'Pipeline', { +const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, stages: [ { @@ -39,9 +44,11 @@ new codepipeline.Pipeline(stack, 'Pipeline', { actions: [ new cpactions.S3DeployAction({ actionName: 'DeployAction', + extract: false, + objectKey: 'key', input: sourceOutput, bucket: deployBucket, - accessControl: s3.BucketAccessControl.PUBLIC_READ, + accessControl: s3.BucketAccessControl.PRIVATE, cacheControl: [ cpactions.CacheControl.setPublic(), cpactions.CacheControl.maxAge(cdk.Duration.hours(12)), @@ -63,4 +70,34 @@ new codepipeline.Pipeline(stack, 'Pipeline', { ], }); +const integ = new IntegTest(app, 's3-deploy-test', { + testCases: [stack], +}); + +integ.assertions.awsApiCall('S3', 'putObject', { + Bucket: bucket.bucketName, + Key: 'key', + Body: 'HelloWorld', +}).next( + integ.assertions.awsApiCall('CodePipeline', 'getPipelineState', { + name: pipeline.pipelineName, + }).expect(ExpectedResult.objectLike({ + stageStates: Match.arrayWith([ + Match.objectLike({ + stageName: 'Deploy', + latestExecution: Match.objectLike({ + status: 'Succeeded', + }), + }), + ]), + })).waitForAssertions({ + totalTimeout: Duration.minutes(5), + }).next( + integ.assertions.awsApiCall('S3', 'getObject', { + Bucket: deployBucket.bucketName, + Key: 'key', + }), + ), +); + app.synth(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/__entrypoint__.js b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/__entrypoint__.js new file mode 100644 index 0000000000000..9df94382cc74e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/__entrypoint__.js @@ -0,0 +1,118 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +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 }, + }; + await 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); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixnQkFBUSxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sR0FBRyxHQUFHO1FBQ1YsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1FBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtRQUNwQixNQUFNLEVBQUUsS0FBSztRQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtLQUN2RSxDQUFDO0lBRUYsTUFBTSxnQkFBUSxDQUFDLGVBQWUsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDcEQsQ0FBQztBQUVELEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxPQUE2QixFQUFFLFlBQW9CO0lBQ3ZGLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSTtZQUNGLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIGh0dHBzIGZyb20gJ2h0dHBzJztcbmltcG9ydCAqIGFzIHVybCBmcm9tICd1cmwnO1xuXG4vLyBmb3IgdW5pdCB0ZXN0c1xuZXhwb3J0IGNvbnN0IGV4dGVybmFsID0ge1xuICBzZW5kSHR0cFJlcXVlc3Q6IGRlZmF1bHRTZW5kSHR0cFJlcXVlc3QsXG4gIGxvZzogZGVmYXVsdExvZyxcbiAgaW5jbHVkZVN0YWNrVHJhY2VzOiB0cnVlLFxuICB1c2VySGFuZGxlckluZGV4OiAnLi9pbmRleCcsXG59O1xuXG5jb25zdCBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUiA9ICdBV1NDREs6OkN1c3RvbVJlc291cmNlUHJvdmlkZXJGcmFtZXdvcms6OkNSRUFURV9GQUlMRUQnO1xuY29uc3QgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpNSVNTSU5HX1BIWVNJQ0FMX0lEJztcblxuZXhwb3J0IHR5cGUgUmVzcG9uc2UgPSBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50ICYgSGFuZGxlclJlc3BvbnNlO1xuZXhwb3J0IHR5cGUgSGFuZGxlciA9IChldmVudDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCwgY29udGV4dDogQVdTTGFtYmRhLkNvbnRleHQpID0+IFByb21pc2U8SGFuZGxlclJlc3BvbnNlIHwgdm9pZD47XG5leHBvcnQgdHlwZSBIYW5kbGVyUmVzcG9uc2UgPSB1bmRlZmluZWQgfCB7XG4gIERhdGE/OiBhbnk7XG4gIFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZztcbiAgUmVhc29uPzogc3RyaW5nO1xuICBOb0VjaG8/OiBib29sZWFuO1xufTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSB7XG4gIGNvbnN0IHNhbml0aXplZEV2ZW50ID0geyAuLi5ldmVudCwgUmVzcG9uc2VVUkw6ICcuLi4nIH07XG4gIGV4dGVybmFsLmxvZyhKU09OLnN0cmluZ2lmeShzYW5pdGl6ZWRFdmVudCwgdW5kZWZpbmVkLCAyKSk7XG5cbiAgLy8gaWdub3JlIERFTEVURSBldmVudCB3aGVuIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBpcyB0aGUgbWFya2VyIHRoYXRcbiAgLy8gaW5kaWNhdGVzIHRoYXQgdGhpcyBERUxFVEUgaXMgYSBzdWJzZXF1ZW50IERFTEVURSB0byBhIGZhaWxlZCBDUkVBVEVcbiAgLy8gb3BlcmF0aW9uLlxuICBpZiAoZXZlbnQuUmVxdWVzdFR5cGUgPT09ICdEZWxldGUnICYmIGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCA9PT0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIpIHtcbiAgICBleHRlcm5hbC5sb2coJ2lnbm9yaW5nIERFTEVURSBldmVudCBjYXVzZWQgYnkgYSBmYWlsZWQgQ1JFQVRFIGV2ZW50Jyk7XG4gICAgYXdhaXQgc3VibWl0UmVzcG9uc2UoJ1NVQ0NFU1MnLCBldmVudCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICAvLyBpbnZva2UgdGhlIHVzZXIgaGFuZGxlci4gdGhpcyBpcyBpbnRlbnRpb25hbGx5IGluc2lkZSB0aGUgdHJ5LWNhdGNoIHRvXG4gICAgLy8gZW5zdXJlIHRoYXQgaWYgdGhlcmUgaXMgYW4gZXJyb3IgaXQncyByZXBvcnRlZCBhcyBhIGZhaWx1cmUgdG9cbiAgICAvLyBjbG91ZGZvcm1hdGlvbiAob3RoZXJ3aXNlIGNmbiB3YWl0cykuXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1yZXF1aXJlLWltcG9ydHNcbiAgICBjb25zdCB1c2VySGFuZGxlcjogSGFuZGxlciA9IHJlcXVpcmUoZXh0ZXJuYWwudXNlckhhbmRsZXJJbmRleCkuaGFuZGxlcjtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB1c2VySGFuZGxlcihzYW5pdGl6ZWRFdmVudCwgY29udGV4dCk7XG5cbiAgICAvLyB2YWxpZGF0ZSB1c2VyIHJlc3BvbnNlIGFuZCBjcmVhdGUgdGhlIGNvbWJpbmVkIGV2ZW50XG4gICAgY29uc3QgcmVzcG9uc2VFdmVudCA9IHJlbmRlclJlc3BvbnNlKGV2ZW50LCByZXN1bHQpO1xuXG4gICAgLy8gc3VibWl0IHRvIGNmbiBhcyBzdWNjZXNzXG4gICAgYXdhaXQgc3VibWl0UmVzcG9uc2UoJ1NVQ0NFU1MnLCByZXNwb25zZUV2ZW50KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnN0IHJlc3A6IFJlc3BvbnNlID0ge1xuICAgICAgLi4uZXZlbnQsXG4gICAgICBSZWFzb246IGV4dGVybmFsLmluY2x1ZGVTdGFja1RyYWNlcyA/IGUuc3RhY2sgOiBlLm1lc3NhZ2UsXG4gICAgfTtcblxuICAgIGlmICghcmVzcC5QaHlzaWNhbFJlc291cmNlSWQpIHtcbiAgICAgIC8vIHNwZWNpYWwgY2FzZTogaWYgQ1JFQVRFIGZhaWxzLCB3aGljaCB1c3VhbGx5IGltcGxpZXMsIHdlIHVzdWFsbHkgZG9uJ3RcbiAgICAgIC8vIGhhdmUgYSBwaHlzaWNhbCByZXNvdXJjZSBpZC4gaW4gdGhpcyBjYXNlLCB0aGUgc3Vic2VxdWVudCBERUxFVEVcbiAgICAgIC8vIG9wZXJhdGlvbiBkb2VzIG5vdCBoYXZlIGFueSBtZWFuaW5nLCBhbmQgd2lsbCBsaWtlbHkgZmFpbCBhcyB3ZWxsLiB0b1xuICAgICAgLy8gYWRkcmVzcyB0aGlzLCB3ZSB1c2UgYSBtYXJrZXIgc28gdGhlIHByb3ZpZGVyIGZyYW1ld29yayBjYW4gc2ltcGx5XG4gICAgICAvLyBpZ25vcmUgdGhlIHN1YnNlcXVlbnQgREVMRVRFLlxuICAgICAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnQ3JlYXRlJykge1xuICAgICAgICBleHRlcm5hbC5sb2coJ0NSRUFURSBmYWlsZWQsIHJlc3BvbmRpbmcgd2l0aCBhIG1hcmtlciBwaHlzaWNhbCByZXNvdXJjZSBpZCBzbyB0aGF0IHRoZSBzdWJzZXF1ZW50IERFTEVURSB3aWxsIGJlIGlnbm9yZWQnKTtcbiAgICAgICAgcmVzcC5QaHlzaWNhbFJlc291cmNlSWQgPSBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUjtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIG90aGVyd2lzZSwgaWYgUGh5c2ljYWxSZXNvdXJjZUlkIGlzIG5vdCBzcGVjaWZpZWQsIHNvbWV0aGluZyBpc1xuICAgICAgICAvLyB0ZXJyaWJseSB3cm9uZyBiZWNhdXNlIGFsbCBvdGhlciBldmVudHMgc2hvdWxkIGhhdmUgYW4gSUQuXG4gICAgICAgIGV4dGVybmFsLmxvZyhgRVJST1I6IE1hbGZvcm1lZCBldmVudC4gXCJQaHlzaWNhbFJlc291cmNlSWRcIiBpcyByZXF1aXJlZDogJHtKU09OLnN0cmluZ2lmeShldmVudCl9YCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gdGhpcyBpcyBhbiBhY3R1YWwgZXJyb3IsIGZhaWwgdGhlIGFjdGl2aXR5IGFsdG9nZXRoZXIgYW5kIGV4aXN0LlxuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdGQUlMRUQnLCByZXNwKTtcbiAgfVxufVxuXG5mdW5jdGlvbiByZW5kZXJSZXNwb25zZShcbiAgY2ZuUmVxdWVzdDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIHsgUGh5c2ljYWxSZXNvdXJjZUlkPzogc3RyaW5nIH0sXG4gIGhhbmRsZXJSZXNwb25zZTogdm9pZCB8IEhhbmRsZXJSZXNwb25zZSA9IHsgfSk6IFJlc3BvbnNlIHtcblxuICAvLyBpZiBwaHlzaWNhbCBJRCBpcyBub3QgcmV0dXJuZWQsIHdlIGhhdmUgc29tZSBkZWZhdWx0cyBmb3IgeW91IGJhc2VkXG4gIC8vIG9uIHRoZSByZXF1ZXN0IHR5cGUuXG4gIGNvbnN0IHBoeXNpY2FsUmVzb3VyY2VJZCA9IGhhbmRsZXJSZXNwb25zZS5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5SZXF1ZXN0SWQ7XG5cbiAgLy8gaWYgd2UgYXJlIGluIERFTEVURSBhbmQgcGh5c2ljYWwgSUQgd2FzIGNoYW5nZWQsIGl0J3MgYW4gZXJyb3IuXG4gIGlmIChjZm5SZXF1ZXN0LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBwaHlzaWNhbFJlc291cmNlSWQgIT09IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBERUxFVEU6IGNhbm5vdCBjaGFuZ2UgdGhlIHBoeXNpY2FsIHJlc291cmNlIElEIGZyb20gXCIke2NmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIHRvIFwiJHtoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIGR1cmluZyBkZWxldGlvbmApO1xuICB9XG5cbiAgLy8gbWVyZ2UgcmVxdWVzdCBldmVudCBhbmQgcmVzdWx0IGV2ZW50IChyZXN1bHQgcHJldmFpbHMpLlxuICByZXR1cm4ge1xuICAgIC4uLmNmblJlcXVlc3QsXG4gICAgLi4uaGFuZGxlclJlc3BvbnNlLFxuICAgIFBoeXNpY2FsUmVzb3VyY2VJZDogcGh5c2ljYWxSZXNvdXJjZUlkLFxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzdWJtaXRSZXNwb25zZShzdGF0dXM6ICdTVUNDRVNTJyB8ICdGQUlMRUQnLCBldmVudDogUmVzcG9uc2UpIHtcbiAgY29uc3QganNvbjogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VSZXNwb25zZSA9IHtcbiAgICBTdGF0dXM6IHN0YXR1cyxcbiAgICBSZWFzb246IGV2ZW50LlJlYXNvbiA/PyBzdGF0dXMsXG4gICAgU3RhY2tJZDogZXZlbnQuU3RhY2tJZCxcbiAgICBSZXF1ZXN0SWQ6IGV2ZW50LlJlcXVlc3RJZCxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCB8fCBNSVNTSU5HX1BIWVNJQ0FMX0lEX01BUktFUixcbiAgICBMb2dpY2FsUmVzb3VyY2VJZDogZXZlbnQuTG9naWNhbFJlc291cmNlSWQsXG4gICAgTm9FY2hvOiBldmVudC5Ob0VjaG8sXG4gICAgRGF0YTogZXZlbnQuRGF0YSxcbiAgfTtcblxuICBleHRlcm5hbC5sb2coJ3N1Ym1pdCByZXNwb25zZSB0byBjbG91ZGZvcm1hdGlvbicsIGpzb24pO1xuXG4gIGNvbnN0IHJlc3BvbnNlQm9keSA9IEpTT04uc3RyaW5naWZ5KGpzb24pO1xuICBjb25zdCBwYXJzZWRVcmwgPSB1cmwucGFyc2UoZXZlbnQuUmVzcG9uc2VVUkwpO1xuICBjb25zdCByZXEgPSB7XG4gICAgaG9zdG5hbWU6IHBhcnNlZFVybC5ob3N0bmFtZSxcbiAgICBwYXRoOiBwYXJzZWRVcmwucGF0aCxcbiAgICBtZXRob2Q6ICdQVVQnLFxuICAgIGhlYWRlcnM6IHsgJ2NvbnRlbnQtdHlwZSc6ICcnLCAnY29udGVudC1sZW5ndGgnOiByZXNwb25zZUJvZHkubGVuZ3RoIH0sXG4gIH07XG5cbiAgYXdhaXQgZXh0ZXJuYWwuc2VuZEh0dHBSZXF1ZXN0KHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.d.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.d.ts new file mode 100644 index 0000000000000..3554dc94d4617 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.d.ts @@ -0,0 +1 @@ +export declare function handler(event: AWSLambda.CloudFormationCustomResourceEvent): Promise; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.js b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.js new file mode 100644 index 0000000000000..7ce4156d4ba41 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.js @@ -0,0 +1,78 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +// eslint-disable-next-line import/no-extraneous-dependencies +const aws_sdk_1 = require("aws-sdk"); +const AUTO_DELETE_OBJECTS_TAG = 'aws-cdk:auto-delete-objects'; +const s3 = new aws_sdk_1.S3(); +async function handler(event) { + switch (event.RequestType) { + case 'Create': + return; + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.BucketName); + } +} +exports.handler = handler; +async function onUpdate(event) { + const updateEvent = event; + const oldBucketName = updateEvent.OldResourceProperties?.BucketName; + const newBucketName = updateEvent.ResourceProperties?.BucketName; + const bucketNameHasChanged = newBucketName != null && oldBucketName != null && newBucketName !== oldBucketName; + /* If the name of the bucket has changed, CloudFormation will try to delete the bucket + and create a new one with the new name. So we have to delete the contents of the + bucket so that this operation does not fail. */ + if (bucketNameHasChanged) { + return onDelete(oldBucketName); + } +} +/** + * Recursively delete all items in the bucket + * + * @param bucketName the bucket name + */ +async function emptyBucket(bucketName) { + const listedObjects = await s3.listObjectVersions({ Bucket: bucketName }).promise(); + const contents = [...listedObjects.Versions ?? [], ...listedObjects.DeleteMarkers ?? []]; + if (contents.length === 0) { + return; + } + const records = contents.map((record) => ({ Key: record.Key, VersionId: record.VersionId })); + await s3.deleteObjects({ Bucket: bucketName, Delete: { Objects: records } }).promise(); + if (listedObjects?.IsTruncated) { + await emptyBucket(bucketName); + } +} +async function onDelete(bucketName) { + if (!bucketName) { + throw new Error('No BucketName was provided.'); + } + if (!await isBucketTaggedForDeletion(bucketName)) { + process.stdout.write(`Bucket does not have '${AUTO_DELETE_OBJECTS_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyBucket(bucketName); + } + catch (e) { + if (e.code !== 'NoSuchBucket') { + throw e; + } + // Bucket doesn't exist. Ignoring + } +} +/** + * The bucket will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is every deleted before the bucket, it must be because + * `autoDeleteObjects` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isBucketTaggedForDeletion(bucketName) { + const response = await s3.getBucketTagging({ Bucket: bucketName }).promise(); + return response.TagSet.some(tag => tag.Key === AUTO_DELETE_OBJECTS_TAG && tag.Value === 'true'); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2REFBNkQ7QUFDN0QscUNBQTZCO0FBRTdCLE1BQU0sdUJBQXVCLEdBQUcsNkJBQTZCLENBQUM7QUFFOUQsTUFBTSxFQUFFLEdBQUcsSUFBSSxZQUFFLEVBQUUsQ0FBQztBQUViLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0Q7SUFDOUUsUUFBUSxLQUFLLENBQUMsV0FBVyxFQUFFO1FBQ3pCLEtBQUssUUFBUTtZQUNYLE9BQU87UUFDVCxLQUFLLFFBQVE7WUFDWCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN6QixLQUFLLFFBQVE7WUFDWCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsVUFBVSxDQUFDLENBQUM7S0FDekQ7QUFDSCxDQUFDO0FBVEQsMEJBU0M7QUFFRCxLQUFLLFVBQVUsUUFBUSxDQUFDLEtBQWtEO0lBQ3hFLE1BQU0sV0FBVyxHQUFHLEtBQTBELENBQUM7SUFDL0UsTUFBTSxhQUFhLEdBQUcsV0FBVyxDQUFDLHFCQUFxQixFQUFFLFVBQVUsQ0FBQztJQUNwRSxNQUFNLGFBQWEsR0FBRyxXQUFXLENBQUMsa0JBQWtCLEVBQUUsVUFBVSxDQUFDO0lBQ2pFLE1BQU0sb0JBQW9CLEdBQUcsYUFBYSxJQUFJLElBQUksSUFBSSxhQUFhLElBQUksSUFBSSxJQUFJLGFBQWEsS0FBSyxhQUFhLENBQUM7SUFFL0c7O3NEQUVrRDtJQUNsRCxJQUFJLG9CQUFvQixFQUFFO1FBQ3hCLE9BQU8sUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0tBQ2hDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxLQUFLLFVBQVUsV0FBVyxDQUFDLFVBQWtCO0lBQzNDLE1BQU0sYUFBYSxHQUFHLE1BQU0sRUFBRSxDQUFDLGtCQUFrQixDQUFDLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDcEYsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFHLGFBQWEsQ0FBQyxRQUFRLElBQUksRUFBRSxFQUFFLEdBQUcsYUFBYSxDQUFDLGFBQWEsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUN6RixJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQ3pCLE9BQU87S0FDUjtJQUVELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFXLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxHQUFHLEVBQUUsTUFBTSxDQUFDLEdBQUcsRUFBRSxTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUNsRyxNQUFNLEVBQUUsQ0FBQyxhQUFhLENBQUMsRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFFdkYsSUFBSSxhQUFhLEVBQUUsV0FBVyxFQUFFO1FBQzlCLE1BQU0sV0FBVyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0tBQy9CO0FBQ0gsQ0FBQztBQUVELEtBQUssVUFBVSxRQUFRLENBQUMsVUFBbUI7SUFDekMsSUFBSSxDQUFDLFVBQVUsRUFBRTtRQUNmLE1BQU0sSUFBSSxLQUFLLENBQUMsNkJBQTZCLENBQUMsQ0FBQztLQUNoRDtJQUNELElBQUksQ0FBQyxNQUFNLHlCQUF5QixDQUFDLFVBQVUsQ0FBQyxFQUFFO1FBQ2hELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLHlCQUF5Qix1QkFBdUIsNkJBQTZCLENBQUMsQ0FBQztRQUNwRyxPQUFPO0tBQ1I7SUFDRCxJQUFJO1FBQ0YsTUFBTSxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7S0FDL0I7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyxjQUFjLEVBQUU7WUFDN0IsTUFBTSxDQUFDLENBQUM7U0FDVDtRQUNELGlDQUFpQztLQUNsQztBQUNILENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsS0FBSyxVQUFVLHlCQUF5QixDQUFDLFVBQWtCO0lBQ3pELE1BQU0sUUFBUSxHQUFHLE1BQU0sRUFBRSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDN0UsT0FBTyxRQUFRLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssdUJBQXVCLElBQUksR0FBRyxDQUFDLEtBQUssS0FBSyxNQUFNLENBQUMsQ0FBQztBQUNsRyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llc1xuaW1wb3J0IHsgUzMgfSBmcm9tICdhd3Mtc2RrJztcblxuY29uc3QgQVVUT19ERUxFVEVfT0JKRUNUU19UQUcgPSAnYXdzLWNkazphdXRvLWRlbGV0ZS1vYmplY3RzJztcblxuY29uc3QgczMgPSBuZXcgUzMoKTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQpIHtcbiAgc3dpdGNoIChldmVudC5SZXF1ZXN0VHlwZSkge1xuICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgICByZXR1cm47XG4gICAgY2FzZSAnVXBkYXRlJzpcbiAgICAgIHJldHVybiBvblVwZGF0ZShldmVudCk7XG4gICAgY2FzZSAnRGVsZXRlJzpcbiAgICAgIHJldHVybiBvbkRlbGV0ZShldmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LkJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIG9uVXBkYXRlKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGNvbnN0IHVwZGF0ZUV2ZW50ID0gZXZlbnQgYXMgQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VVcGRhdGVFdmVudDtcbiAgY29uc3Qgb2xkQnVja2V0TmFtZSA9IHVwZGF0ZUV2ZW50Lk9sZFJlc291cmNlUHJvcGVydGllcz8uQnVja2V0TmFtZTtcbiAgY29uc3QgbmV3QnVja2V0TmFtZSA9IHVwZGF0ZUV2ZW50LlJlc291cmNlUHJvcGVydGllcz8uQnVja2V0TmFtZTtcbiAgY29uc3QgYnVja2V0TmFtZUhhc0NoYW5nZWQgPSBuZXdCdWNrZXROYW1lICE9IG51bGwgJiYgb2xkQnVja2V0TmFtZSAhPSBudWxsICYmIG5ld0J1Y2tldE5hbWUgIT09IG9sZEJ1Y2tldE5hbWU7XG5cbiAgLyogSWYgdGhlIG5hbWUgb2YgdGhlIGJ1Y2tldCBoYXMgY2hhbmdlZCwgQ2xvdWRGb3JtYXRpb24gd2lsbCB0cnkgdG8gZGVsZXRlIHRoZSBidWNrZXRcbiAgICAgYW5kIGNyZWF0ZSBhIG5ldyBvbmUgd2l0aCB0aGUgbmV3IG5hbWUuIFNvIHdlIGhhdmUgdG8gZGVsZXRlIHRoZSBjb250ZW50cyBvZiB0aGVcbiAgICAgYnVja2V0IHNvIHRoYXQgdGhpcyBvcGVyYXRpb24gZG9lcyBub3QgZmFpbC4gKi9cbiAgaWYgKGJ1Y2tldE5hbWVIYXNDaGFuZ2VkKSB7XG4gICAgcmV0dXJuIG9uRGVsZXRlKG9sZEJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbi8qKlxuICogUmVjdXJzaXZlbHkgZGVsZXRlIGFsbCBpdGVtcyBpbiB0aGUgYnVja2V0XG4gKlxuICogQHBhcmFtIGJ1Y2tldE5hbWUgdGhlIGJ1Y2tldCBuYW1lXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGVtcHR5QnVja2V0KGJ1Y2tldE5hbWU6IHN0cmluZykge1xuICBjb25zdCBsaXN0ZWRPYmplY3RzID0gYXdhaXQgczMubGlzdE9iamVjdFZlcnNpb25zKHsgQnVja2V0OiBidWNrZXROYW1lIH0pLnByb21pc2UoKTtcbiAgY29uc3QgY29udGVudHMgPSBbLi4ubGlzdGVkT2JqZWN0cy5WZXJzaW9ucyA/PyBbXSwgLi4ubGlzdGVkT2JqZWN0cy5EZWxldGVNYXJrZXJzID8/IFtdXTtcbiAgaWYgKGNvbnRlbnRzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGNvbnN0IHJlY29yZHMgPSBjb250ZW50cy5tYXAoKHJlY29yZDogYW55KSA9PiAoeyBLZXk6IHJlY29yZC5LZXksIFZlcnNpb25JZDogcmVjb3JkLlZlcnNpb25JZCB9KSk7XG4gIGF3YWl0IHMzLmRlbGV0ZU9iamVjdHMoeyBCdWNrZXQ6IGJ1Y2tldE5hbWUsIERlbGV0ZTogeyBPYmplY3RzOiByZWNvcmRzIH0gfSkucHJvbWlzZSgpO1xuXG4gIGlmIChsaXN0ZWRPYmplY3RzPy5Jc1RydW5jYXRlZCkge1xuICAgIGF3YWl0IGVtcHR5QnVja2V0KGJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIG9uRGVsZXRlKGJ1Y2tldE5hbWU/OiBzdHJpbmcpIHtcbiAgaWYgKCFidWNrZXROYW1lKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdObyBCdWNrZXROYW1lIHdhcyBwcm92aWRlZC4nKTtcbiAgfVxuICBpZiAoIWF3YWl0IGlzQnVja2V0VGFnZ2VkRm9yRGVsZXRpb24oYnVja2V0TmFtZSkpIHtcbiAgICBwcm9jZXNzLnN0ZG91dC53cml0ZShgQnVja2V0IGRvZXMgbm90IGhhdmUgJyR7QVVUT19ERUxFVEVfT0JKRUNUU19UQUd9JyB0YWcsIHNraXBwaW5nIGNsZWFuaW5nLlxcbmApO1xuICAgIHJldHVybjtcbiAgfVxuICB0cnkge1xuICAgIGF3YWl0IGVtcHR5QnVja2V0KGJ1Y2tldE5hbWUpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgaWYgKGUuY29kZSAhPT0gJ05vU3VjaEJ1Y2tldCcpIHtcbiAgICAgIHRocm93IGU7XG4gICAgfVxuICAgIC8vIEJ1Y2tldCBkb2Vzbid0IGV4aXN0LiBJZ25vcmluZ1xuICB9XG59XG5cbi8qKlxuICogVGhlIGJ1Y2tldCB3aWxsIG9ubHkgYmUgdGFnZ2VkIGZvciBkZWxldGlvbiBpZiBpdCdzIGJlaW5nIGRlbGV0ZWQgaW4gdGhlIHNhbWVcbiAqIGRlcGxveW1lbnQgYXMgdGhpcyBDdXN0b20gUmVzb3VyY2UuXG4gKlxuICogSWYgdGhlIEN1c3RvbSBSZXNvdXJjZSBpcyBldmVyeSBkZWxldGVkIGJlZm9yZSB0aGUgYnVja2V0LCBpdCBtdXN0IGJlIGJlY2F1c2VcbiAqIGBhdXRvRGVsZXRlT2JqZWN0c2AgaGFzIGJlZW4gc3dpdGNoZWQgdG8gZmFsc2UsIGluIHdoaWNoIGNhc2UgdGhlIHRhZyB3b3VsZCBoYXZlXG4gKiBiZWVuIHJlbW92ZWQgYmVmb3JlIHdlIGdldCB0byB0aGlzIERlbGV0ZSBldmVudC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gaXNCdWNrZXRUYWdnZWRGb3JEZWxldGlvbihidWNrZXROYW1lOiBzdHJpbmcpIHtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBzMy5nZXRCdWNrZXRUYWdnaW5nKHsgQnVja2V0OiBidWNrZXROYW1lIH0pLnByb21pc2UoKTtcbiAgcmV0dXJuIHJlc3BvbnNlLlRhZ1NldC5zb21lKHRhZyA9PiB0YWcuS2V5ID09PSBBVVRPX0RFTEVURV9PQkpFQ1RTX1RBRyAmJiB0YWcuVmFsdWUgPT09ICd0cnVlJyk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts new file mode 100644 index 0000000000000..2459d44ab1d18 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26/index.ts @@ -0,0 +1,82 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { S3 } from 'aws-sdk'; + +const AUTO_DELETE_OBJECTS_TAG = 'aws-cdk:auto-delete-objects'; + +const s3 = new S3(); + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) { + switch (event.RequestType) { + case 'Create': + return; + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.BucketName); + } +} + +async function onUpdate(event: AWSLambda.CloudFormationCustomResourceEvent) { + const updateEvent = event as AWSLambda.CloudFormationCustomResourceUpdateEvent; + const oldBucketName = updateEvent.OldResourceProperties?.BucketName; + const newBucketName = updateEvent.ResourceProperties?.BucketName; + const bucketNameHasChanged = newBucketName != null && oldBucketName != null && newBucketName !== oldBucketName; + + /* If the name of the bucket has changed, CloudFormation will try to delete the bucket + and create a new one with the new name. So we have to delete the contents of the + bucket so that this operation does not fail. */ + if (bucketNameHasChanged) { + return onDelete(oldBucketName); + } +} + +/** + * Recursively delete all items in the bucket + * + * @param bucketName the bucket name + */ +async function emptyBucket(bucketName: string) { + const listedObjects = await s3.listObjectVersions({ Bucket: bucketName }).promise(); + const contents = [...listedObjects.Versions ?? [], ...listedObjects.DeleteMarkers ?? []]; + if (contents.length === 0) { + return; + } + + const records = contents.map((record: any) => ({ Key: record.Key, VersionId: record.VersionId })); + await s3.deleteObjects({ Bucket: bucketName, Delete: { Objects: records } }).promise(); + + if (listedObjects?.IsTruncated) { + await emptyBucket(bucketName); + } +} + +async function onDelete(bucketName?: string) { + if (!bucketName) { + throw new Error('No BucketName was provided.'); + } + if (!await isBucketTaggedForDeletion(bucketName)) { + process.stdout.write(`Bucket does not have '${AUTO_DELETE_OBJECTS_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyBucket(bucketName); + } catch (e) { + if (e.code !== 'NoSuchBucket') { + throw e; + } + // Bucket doesn't exist. Ignoring + } +} + +/** + * The bucket will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is every deleted before the bucket, it must be because + * `autoDeleteObjects` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isBucketTaggedForDeletion(bucketName: string) { + const response = await s3.getBucketTagging({ Bucket: bucketName }).promise(); + return response.TagSet.some(tag => tag.Key === AUTO_DELETE_OBJECTS_TAG && tag.Value === 'true'); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js new file mode 100644 index 0000000000000..2d6c2f0e85497 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js @@ -0,0 +1,768 @@ +"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( + 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 { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../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/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + 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)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + 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 (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + 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" + }); + } + } + } + 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}' among {${Object.keys(actual).join(",")}}` + }); + 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) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +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); + } + 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); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + 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.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + 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 + }; + } + 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]); + 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) + }; + const resp = request2.flattenResponse === "true" ? flatData : respond; + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +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", + 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/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/aws-cdk-codepipeline-s3-deploy.assets.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/aws-cdk-codepipeline-s3-deploy.assets.json index 48842206d2938..7d8578d23976f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/aws-cdk-codepipeline-s3-deploy.assets.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/aws-cdk-codepipeline-s3-deploy.assets.json @@ -1,7 +1,20 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "9e39911fe11ea10dedd2b61c405eb9ca7b1c0be636c5b293c2ce1371f6b05221": { + "60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26": { + "source": { + "path": "asset.60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "ac693470bc6a09b25a5b2b79ba362bbe25323eb8c9eb4f90627a74dce0b075ff": { "source": { "path": "aws-cdk-codepipeline-s3-deploy.template.json", "packaging": "file" @@ -9,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "9e39911fe11ea10dedd2b61c405eb9ca7b1c0be636c5b293c2ce1371f6b05221.json", + "objectKey": "ac693470bc6a09b25a5b2b79ba362bbe25323eb8c9eb4f90627a74dce0b075ff.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/aws-cdk-codepipeline-s3-deploy.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/aws-cdk-codepipeline-s3-deploy.template.json index d919feceaa05f..680b8c34fae6c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/aws-cdk-codepipeline-s3-deploy.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/aws-cdk-codepipeline-s3-deploy.template.json @@ -3,6 +3,12 @@ "PipelineBucketB967BD35": { "Type": "AWS::S3::Bucket", "Properties": { + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + } + ], "VersioningConfiguration": { "Status": "Enabled" } @@ -10,13 +16,297 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "PipelineBucketPolicyD65CDEF5": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "PipelineBucketB967BD35" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineBucketAutoDeleteObjectsCustomResource5F37E165": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "PipelineBucketB967BD35" + } + }, + "DependsOn": [ + "PipelineBucketPolicyD65CDEF5" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { + "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" + } + ] + } + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "60767da3831353fede3cfe92efef10580a600592dec8ccbb06c051e95b9c1b26.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Description": { + "Fn::Join": [ + "", + [ + "Lambda function for auto-deleting objects in ", + { + "Ref": "PipelineBucketB967BD35" + }, + " S3 bucket." + ] + ] + } + }, + "DependsOn": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + ] + }, "DeployBucket67E2C076": { "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "DeployBucketPolicyCED076FF": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "DeployBucket67E2C076" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "DeployBucket67E2C076", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "DeployBucket67E2C076", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "DeployBucketAutoDeleteObjectsCustomResourceC751D9D5": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "DeployBucket67E2C076" + } + }, + "DependsOn": [ + "DeployBucketPolicyCED076FF" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, "OtherDeployBucket7B0CCE57": { "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "OtherDeployBucketPolicy4B707068": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "OtherDeployBucket7B0CCE57" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "OtherDeployBucket7B0CCE57", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "OtherDeployBucket7B0CCE57", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "OtherDeployBucketAutoDeleteObjectsCustomResourceDB063131": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "OtherDeployBucket7B0CCE57" + } + }, + "DependsOn": [ + "OtherDeployBucketPolicy4B707068" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -169,8 +459,9 @@ "BucketName": { "Ref": "DeployBucket67E2C076" }, - "Extract": "true", - "CannedACL": "public-read", + "Extract": "false", + "ObjectKey": "key", + "CannedACL": "private", "CacheControl": "public, max-age=43200" }, "InputArtifacts": [ @@ -599,6 +890,32 @@ } } }, + "Outputs": { + "ExportsOutputRefPipelineBucketB967BD35BAE6E881": { + "Value": { + "Ref": "PipelineBucketB967BD35" + }, + "Export": { + "Name": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefPipelineBucketB967BD35BAE6E881" + } + }, + "ExportsOutputRefPipelineC660917DEB540586": { + "Value": { + "Ref": "PipelineC660917D" + }, + "Export": { + "Name": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefPipelineC660917DEB540586" + } + }, + "ExportsOutputRefDeployBucket67E2C076D8DEC04D": { + "Value": { + "Ref": "DeployBucket67E2C076" + }, + "Export": { + "Name": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefDeployBucket67E2C076D8DEC04D" + } + } + }, "Parameters": { "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/integ.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/integ.json index 98766a11ef97c..b0e62ca8be9c7 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/integ.json @@ -1,14 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { - "integ.pipeline-s3-deploy": { + "s3-deploy-test/DefaultTest": { "stacks": [ "aws-cdk-codepipeline-s3-deploy" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "s3-deploy-test/DefaultTest/DeployAssert", + "assertionStackName": "s3deploytestDefaultTestDeployAssert6BC61647" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/manifest.json index 245a97ebe2b1d..508a52961d662 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,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}/9e39911fe11ea10dedd2b61c405eb9ca7b1c0be636c5b293c2ce1371f6b05221.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/ac693470bc6a09b25a5b2b79ba362bbe25323eb8c9eb4f90627a74dce0b075ff.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -45,18 +45,66 @@ "data": "PipelineBucketB967BD35" } ], + "/aws-cdk-codepipeline-s3-deploy/PipelineBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineBucketPolicyD65CDEF5" + } + ], + "/aws-cdk-codepipeline-s3-deploy/PipelineBucket/AutoDeleteObjectsCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineBucketAutoDeleteObjectsCustomResource5F37E165" + } + ], + "/aws-cdk-codepipeline-s3-deploy/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + } + ], + "/aws-cdk-codepipeline-s3-deploy/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F" + } + ], "/aws-cdk-codepipeline-s3-deploy/DeployBucket/Resource": [ { "type": "aws:cdk:logicalId", "data": "DeployBucket67E2C076" } ], + "/aws-cdk-codepipeline-s3-deploy/DeployBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DeployBucketPolicyCED076FF" + } + ], + "/aws-cdk-codepipeline-s3-deploy/DeployBucket/AutoDeleteObjectsCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "DeployBucketAutoDeleteObjectsCustomResourceC751D9D5" + } + ], "/aws-cdk-codepipeline-s3-deploy/OtherDeployBucket/Resource": [ { "type": "aws:cdk:logicalId", "data": "OtherDeployBucket7B0CCE57" } ], + "/aws-cdk-codepipeline-s3-deploy/OtherDeployBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "OtherDeployBucketPolicy4B707068" + } + ], + "/aws-cdk-codepipeline-s3-deploy/OtherDeployBucket/AutoDeleteObjectsCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "OtherDeployBucketAutoDeleteObjectsCustomResourceDB063131" + } + ], "/aws-cdk-codepipeline-s3-deploy/Pipeline/Role/Resource": [ { "type": "aws:cdk:logicalId", @@ -111,6 +159,24 @@ "data": "PipelineDisabledDisabledDeployActionCodePipelineActionRoleDefaultPolicyB1AF629C" } ], + "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineBucketB967BD35\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefPipelineBucketB967BD35BAE6E881" + } + ], + "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineC660917D\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefPipelineC660917DEB540586" + } + ], + "/aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"DeployBucket67E2C076\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefDeployBucket67E2C076D8DEC04D" + } + ], "/aws-cdk-codepipeline-s3-deploy/BootstrapVersion": [ { "type": "aws:cdk:logicalId", @@ -125,6 +191,138 @@ ] }, "displayName": "aws-cdk-codepipeline-s3-deploy" + }, + "s3deploytestDefaultTestDeployAssert6BC61647.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "s3deploytestDefaultTestDeployAssert6BC61647.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "s3deploytestDefaultTestDeployAssert6BC61647": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "s3deploytestDefaultTestDeployAssert6BC61647.template.json", + "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}/3597c38c72dbafa8cc67f06b9244e00f72e1db97c1cd2850b39a91f900991569.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "s3deploytestDefaultTestDeployAssert6BC61647.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-codepipeline-s3-deploy", + "s3deploytestDefaultTestDeployAssert6BC61647.assets" + ], + "metadata": { + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallS3putObject" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCodePipelinegetPipelineState" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/IsCompleteProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCodePipelinegetPipelineStateWaitForIsCompleteProviderInvokeB83E9F2C" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/TimeoutProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCodePipelinegetPipelineStateWaitForTimeoutProviderInvoke96D2C126" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCodePipelinegetPipelineStateWaitFor68BABF78" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallCodePipelinegetPipelineState" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallS3getObject" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/s3-deploy-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "s3-deploy-test/DefaultTest/DeployAssert" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.assets.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.assets.json new file mode 100644 index 0000000000000..2bfe3b5e111e5 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b": { + "source": { + "path": "asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "3597c38c72dbafa8cc67f06b9244e00f72e1db97c1cd2850b39a91f900991569": { + "source": { + "path": "s3deploytestDefaultTestDeployAssert6BC61647.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3597c38c72dbafa8cc67f06b9244e00f72e1db97c1cd2850b39a91f900991569.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.template.json new file mode 100644 index 0000000000000..32300db47b1f7 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/s3deploytestDefaultTestDeployAssert6BC61647.template.json @@ -0,0 +1,437 @@ +{ + "Resources": { + "AwsApiCallS3putObject": { + "Type": "Custom::DeployAssert@SdkCallS3putObject", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "S3", + "api": "putObject", + "parameters": { + "Bucket": { + "Fn::ImportValue": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefPipelineBucketB967BD35BAE6E881" + }, + "Key": "key", + "Body": "HelloWorld" + }, + "flattenResponse": "false", + "salt": "1664823975013" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "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": [ + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "codepipeline:GetPipelineState" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "states:StartExecution" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + }, + "AwsApiCallCodePipelinegetPipelineState": { + "Type": "Custom::DeployAssert@SdkCallCodePipelinegetPipelineState", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "CodePipeline", + "api": "getPipelineState", + "expected": "{\"$ObjectLike\":{\"stageStates\":{\"$ArrayWith\":[{\"$ObjectLike\":{\"stageName\":\"Deploy\",\"latestExecution\":{\"$ObjectLike\":{\"status\":\"Succeeded\"}}}}]}}}", + "stateMachineArn": { + "Ref": "AwsApiCallCodePipelinegetPipelineStateWaitFor68BABF78" + }, + "parameters": { + "name": { + "Fn::ImportValue": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefPipelineC660917DEB540586" + } + }, + "flattenResponse": "false", + "salt": "1664823975013" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallCodePipelinegetPipelineStateWaitForIsCompleteProviderInvokeB83E9F2C": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE", + "Arn" + ] + }, + "Principal": { + "Fn::GetAtt": [ + "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47", + "Arn" + ] + } + } + }, + "AwsApiCallCodePipelinegetPipelineStateWaitForTimeoutProviderInvoke96D2C126": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA", + "Arn" + ] + }, + "Principal": { + "Fn::GetAtt": [ + "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47", + "Arn" + ] + } + } + }, + "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47": { + "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" + ] + } + ] + } + ] + } + } + ] + } + }, + "AwsApiCallCodePipelinegetPipelineStateWaitFor68BABF78": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"framework-isComplete-task\",\"States\":{\"framework-isComplete-task\":{\"End\":true,\"Retry\":[{\"ErrorEquals\":[\"States.ALL\"],\"IntervalSeconds\":5,\"MaxAttempts\":60,\"BackoffRate\":1}],\"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": [ + "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47", + "Arn" + ] + } + }, + "DependsOn": [ + "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47" + ] + }, + "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": [ + "codepipeline:GetPipelineState" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.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": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.onTimeout", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE", + "Arn" + ] + } + } + }, + "AwsApiCallS3getObject": { + "Type": "Custom::DeployAssert@SdkCallS3getObject", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "S3", + "api": "getObject", + "parameters": { + "Bucket": { + "Fn::ImportValue": "aws-cdk-codepipeline-s3-deploy:ExportsOutputRefDeployBucket67E2C076D8DEC04D" + }, + "Key": "key" + }, + "flattenResponse": "false", + "salt": "1664823975015" + }, + "DependsOn": [ + "AwsApiCallCodePipelinegetPipelineState", + "AwsApiCallCodePipelinegetPipelineStateWaitForIsCompleteProviderInvokeB83E9F2C", + "AwsApiCallCodePipelinegetPipelineStateWaitFor68BABF78", + "AwsApiCallCodePipelinegetPipelineStateWaitForRoleDF2D0D47", + "AwsApiCallCodePipelinegetPipelineStateWaitForTimeoutProviderInvoke96D2C126", + "AwsApiCallS3putObject" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "AssertionResultsAwsApiCallCodePipelinegetPipelineState": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallCodePipelinegetPipelineState", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/tree.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/tree.json index 1a6ce2940b194..d7b2200c3dedb 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-s3-deploy.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "aws-cdk-codepipeline-s3-deploy": { @@ -26,6 +26,12 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::S3::Bucket", "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:auto-delete-objects", + "value": "true" + } + ], "versioningConfiguration": { "status": "Enabled" } @@ -35,6 +41,93 @@ "fqn": "@aws-cdk/aws-s3.CfnBucket", "version": "0.0.0" } + }, + "Policy": { + "id": "Policy", + "path": "aws-cdk-codepipeline-s3-deploy/PipelineBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codepipeline-s3-deploy/PipelineBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "PipelineBucketB967BD35" + }, + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + }, + "AutoDeleteObjectsCustomResource": { + "id": "AutoDeleteObjectsCustomResource", + "path": "aws-cdk-codepipeline-s3-deploy/PipelineBucket/AutoDeleteObjectsCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "aws-cdk-codepipeline-s3-deploy/PipelineBucket/AutoDeleteObjectsCustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } } }, "constructInfo": { @@ -42,6 +135,40 @@ "version": "0.0.0" } }, + "Custom::S3AutoDeleteObjectsCustomResourceProvider": { + "id": "Custom::S3AutoDeleteObjectsCustomResourceProvider", + "path": "aws-cdk-codepipeline-s3-deploy/Custom::S3AutoDeleteObjectsCustomResourceProvider", + "children": { + "Staging": { + "id": "Staging", + "path": "aws-cdk-codepipeline-s3-deploy/Custom::S3AutoDeleteObjectsCustomResourceProvider/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "aws-cdk-codepipeline-s3-deploy/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "aws-cdk-codepipeline-s3-deploy/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResourceProvider", + "version": "0.0.0" + } + }, "DeployBucket": { "id": "DeployBucket", "path": "aws-cdk-codepipeline-s3-deploy/DeployBucket", @@ -51,12 +178,106 @@ "path": "aws-cdk-codepipeline-s3-deploy/DeployBucket/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::S3::Bucket", - "aws:cdk:cloudformation:props": {} + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:auto-delete-objects", + "value": "true" + } + ] + } }, "constructInfo": { "fqn": "@aws-cdk/aws-s3.CfnBucket", "version": "0.0.0" } + }, + "Policy": { + "id": "Policy", + "path": "aws-cdk-codepipeline-s3-deploy/DeployBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codepipeline-s3-deploy/DeployBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "DeployBucket67E2C076" + }, + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "DeployBucket67E2C076", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "DeployBucket67E2C076", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + }, + "AutoDeleteObjectsCustomResource": { + "id": "AutoDeleteObjectsCustomResource", + "path": "aws-cdk-codepipeline-s3-deploy/DeployBucket/AutoDeleteObjectsCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "aws-cdk-codepipeline-s3-deploy/DeployBucket/AutoDeleteObjectsCustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } } }, "constructInfo": { @@ -73,12 +294,106 @@ "path": "aws-cdk-codepipeline-s3-deploy/OtherDeployBucket/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::S3::Bucket", - "aws:cdk:cloudformation:props": {} + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:auto-delete-objects", + "value": "true" + } + ] + } }, "constructInfo": { "fqn": "@aws-cdk/aws-s3.CfnBucket", "version": "0.0.0" } + }, + "Policy": { + "id": "Policy", + "path": "aws-cdk-codepipeline-s3-deploy/OtherDeployBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codepipeline-s3-deploy/OtherDeployBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "OtherDeployBucket7B0CCE57" + }, + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "OtherDeployBucket7B0CCE57", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "OtherDeployBucket7B0CCE57", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + }, + "AutoDeleteObjectsCustomResource": { + "id": "AutoDeleteObjectsCustomResource", + "path": "aws-cdk-codepipeline-s3-deploy/OtherDeployBucket/AutoDeleteObjectsCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "aws-cdk-codepipeline-s3-deploy/OtherDeployBucket/AutoDeleteObjectsCustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } } }, "constructInfo": { @@ -285,8 +600,9 @@ "BucketName": { "Ref": "DeployBucket67E2C076" }, - "Extract": "true", - "CannedACL": "public-read", + "Extract": "false", + "ObjectKey": "key", + "CannedACL": "private", "CacheControl": "public, max-age=43200" }, "runOrder": 1, @@ -510,13 +826,13 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "Deploy": { @@ -699,13 +1015,13 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "Disabled": { @@ -867,13 +1183,13 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } } }, @@ -881,17 +1197,415 @@ "fqn": "@aws-cdk/aws-codepipeline.Pipeline", "version": "0.0.0" } + }, + "Exports": { + "id": "Exports", + "path": "aws-cdk-codepipeline-s3-deploy/Exports", + "children": { + "Output{\"Ref\":\"PipelineBucketB967BD35\"}": { + "id": "Output{\"Ref\":\"PipelineBucketB967BD35\"}", + "path": "aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineBucketB967BD35\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Output{\"Ref\":\"PipelineC660917D\"}": { + "id": "Output{\"Ref\":\"PipelineC660917D\"}", + "path": "aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"PipelineC660917D\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Output{\"Ref\":\"DeployBucket67E2C076\"}": { + "id": "Output{\"Ref\":\"DeployBucket67E2C076\"}", + "path": "aws-cdk-codepipeline-s3-deploy/Exports/Output{\"Ref\":\"DeployBucket67E2C076\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "s3-deploy-test": { + "id": "s3-deploy-test", + "path": "s3-deploy-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "s3-deploy-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "s3-deploy-test/DefaultTest/DeployAssert", + "children": { + "AwsApiCallS3putObject": { + "id": "AwsApiCallS3putObject", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/Default", + "children": { + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3putObject/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/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "AwsApiCallCodePipelinegetPipelineState": { + "id": "AwsApiCallCodePipelinegetPipelineState", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/Default", + "children": { + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/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": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor", + "children": { + "IsCompleteProvider": { + "id": "IsCompleteProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/IsCompleteProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/IsCompleteProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "Invoke": { + "id": "Invoke", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/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": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/TimeoutProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/TimeoutProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "Invoke": { + "id": "Invoke", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/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": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/WaitFor/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/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": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallCodePipelinegetPipelineState/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925": { + "id": "SingletonFunction76b3e830a873425f8453eddd85c86925", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925", + "children": { + "Staging": { + "id": "Staging", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a": { + "id": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a", + "children": { + "Staging": { + "id": "Staging", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "s3-deploy-test/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "AwsApiCallS3getObject": { + "id": "AwsApiCallS3getObject", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/Default", + "children": { + "Default": { + "id": "Default", + "path": "s3-deploy-test/DefaultTest/DeployAssert/AwsApiCallS3getObject/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/integ-tests.AwsApiCall", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.ts b/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.ts index 61eb686491112..cb6ebe3a21847 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.ts +++ b/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.ts @@ -1,5 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; +import { IntegTest, ExpectedResult, InvocationType, Match } from '@aws-cdk/integ-tests'; import { Construct } from 'constructs'; import * as destinations from '../lib'; @@ -16,6 +17,8 @@ import * as destinations from '../lib'; // aws logs filter-log-events --log-group-name /aws/lambda/ class TestStack extends Stack { + public readonly firstFunctionName: string; + public readonly thirdFunctionName: string; constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); @@ -24,7 +27,7 @@ class TestStack extends Stack { handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = async (event) => { console.log('Event: %j', event); - if (event === 'error') throw new Error('UnkownError'); + if (event.status === 'error') throw new Error('UnkownError'); return event; };`), }; @@ -33,6 +36,8 @@ class TestStack extends Stack { const second = new lambda.Function(this, 'Second', lambdaProps); const third = new lambda.Function(this, 'Third', lambdaProps); const error = new lambda.Function(this, 'Error', lambdaProps); + this.firstFunctionName = first.functionName; + this.thirdFunctionName = third.functionName; first.configureAsyncInvoke({ onSuccess: new destinations.LambdaDestination(second, { responseOnly: true }), @@ -52,6 +57,26 @@ class TestStack extends Stack { const app = new App(); -new TestStack(app, 'aws-cdk-lambda-chain'); +const stack = new TestStack(app, 'aws-cdk-lambda-chain'); +const integ = new IntegTest(app, 'LambdaDestChain3', { + testCases: [stack], +}); +integ.assertions.invokeFunction({ + functionName: stack.firstFunctionName, + invocationType: InvocationType.EVENT, + payload: JSON.stringify({ + status: 'success', + }), +}); + +integ.assertions.awsApiCall('CloudWatchLogs', 'filterLogEvents', { + logGroupName: `/aws/lambda/${stack.thirdFunctionName}`, +}).expect(ExpectedResult.objectLike({ + events: Match.arrayWith([ + Match.objectLike({ + message: Match.stringLikeRegexp('success'), + }), + ]), +})).waitForAssertions(); app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/LambdaDestChain3DefaultTestDeployAssertB72F10E8.assets.json b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/LambdaDestChain3DefaultTestDeployAssertB72F10E8.assets.json new file mode 100644 index 0000000000000..8985bd3ba3c79 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/LambdaDestChain3DefaultTestDeployAssertB72F10E8.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b": { + "source": { + "path": "asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "36ba0923aa26f9b7c5a2bea94bbcfb168d2cef9f3285309a32c1d8e4a98101aa": { + "source": { + "path": "LambdaDestChain3DefaultTestDeployAssertB72F10E8.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "36ba0923aa26f9b7c5a2bea94bbcfb168d2cef9f3285309a32c1d8e4a98101aa.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/LambdaDestChain3DefaultTestDeployAssertB72F10E8.template.json b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/LambdaDestChain3DefaultTestDeployAssertB72F10E8.template.json new file mode 100644 index 0000000000000..3fe7a0973327e --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/LambdaDestChain3DefaultTestDeployAssertB72F10E8.template.json @@ -0,0 +1,451 @@ +{ + "Resources": { + "LambdaInvoke56ff6291852e7537dce00fa6e862a00b": { + "Type": "Custom::DeployAssert@SdkCallLambdainvoke", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "Lambda", + "api": "invoke", + "parameters": { + "FunctionName": { + "Fn::ImportValue": "aws-cdk-lambda-chain:ExportsOutputRefFirst8D4707F1B3E37FDF" + }, + "InvocationType": "Event", + "Payload": "{\"status\":\"success\"}" + }, + "flattenResponse": "false", + "salt": "1664821652422" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "LambdaInvoke56ff6291852e7537dce00fa6e862a00bInvokeA6B3FDE4": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::ImportValue": "aws-cdk-lambda-chain:ExportsOutputRefFirst8D4707F1B3E37FDF" + }, + "Principal": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + }, + "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": [ + "lambda:Invoke" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "lambda:InvokeFunction" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":lambda:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":function:", + { + "Fn::ImportValue": "aws-cdk-lambda-chain:ExportsOutputRefFirst8D4707F1B3E37FDF" + } + ] + ] + } + ] + }, + { + "Action": [ + "logs:FilterLogEvents" + ], + "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": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + }, + "AwsApiCallCloudWatchLogsfilterLogEvents": { + "Type": "Custom::DeployAssert@SdkCallCloudWatchLogsfilterLogEvents", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "CloudWatchLogs", + "api": "filterLogEvents", + "expected": "{\"$ObjectLike\":{\"events\":{\"$ArrayWith\":[{\"$ObjectLike\":{\"message\":{\"$StringLike\":\"success\"}}}]}}}", + "stateMachineArn": { + "Ref": "AwsApiCallCloudWatchLogsfilterLogEventsWaitFor64019274" + }, + "parameters": { + "logGroupName": { + "Fn::Join": [ + "", + [ + "/aws/lambda/", + { + "Fn::ImportValue": "aws-cdk-lambda-chain:ExportsOutputRefThird1125870FF05390B4" + } + ] + ] + } + }, + "flattenResponse": "false", + "salt": "1664821652422" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallCloudWatchLogsfilterLogEventsWaitForIsCompleteProviderInvoke00D72D57": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE", + "Arn" + ] + }, + "Principal": { + "Fn::GetAtt": [ + "AwsApiCallCloudWatchLogsfilterLogEventsWaitForRole9F1B78BD", + "Arn" + ] + } + } + }, + "AwsApiCallCloudWatchLogsfilterLogEventsWaitForTimeoutProviderInvoke3950C224": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA", + "Arn" + ] + }, + "Principal": { + "Fn::GetAtt": [ + "AwsApiCallCloudWatchLogsfilterLogEventsWaitForRole9F1B78BD", + "Arn" + ] + } + } + }, + "AwsApiCallCloudWatchLogsfilterLogEventsWaitForRole9F1B78BD": { + "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" + ] + } + ] + } + ] + } + } + ] + } + }, + "AwsApiCallCloudWatchLogsfilterLogEventsWaitFor64019274": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"framework-isComplete-task\",\"States\":{\"framework-isComplete-task\":{\"End\":true,\"Retry\":[{\"ErrorEquals\":[\"States.ALL\"],\"IntervalSeconds\":5,\"MaxAttempts\":360,\"BackoffRate\":1}],\"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": [ + "AwsApiCallCloudWatchLogsfilterLogEventsWaitForRole9F1B78BD", + "Arn" + ] + } + }, + "DependsOn": [ + "AwsApiCallCloudWatchLogsfilterLogEventsWaitForRole9F1B78BD" + ] + }, + "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": [ + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.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": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.onTimeout", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallCloudWatchLogsfilterLogEvents": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallCloudWatchLogsfilterLogEvents", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js new file mode 100644 index 0000000000000..2d6c2f0e85497 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js @@ -0,0 +1,768 @@ +"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( + 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 { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../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/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + 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)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + 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 (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + 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" + }); + } + } + } + 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}' among {${Object.keys(actual).join(",")}}` + }); + 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) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +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); + } + 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); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + 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.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + 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 + }; + } + 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]); + 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) + }; + const resp = request2.flattenResponse === "true" ? flatData : respond; + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +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", + 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/aws-lambda-destinations/test/lambda-chain.integ.snapshot/aws-cdk-lambda-chain.assets.json b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/aws-cdk-lambda-chain.assets.json index f155d94e62f44..dee35629b182d 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/aws-cdk-lambda-chain.assets.json +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/aws-cdk-lambda-chain.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "ac184deab5698b832a3af2ad34818e9296611ee91c6c8ca9b4fe1399e8ab0551": { + "346b0f31aef9dba7bae2a5c33f01d10e8f1ca774a3fc50386c0c901037757662": { "source": { "path": "aws-cdk-lambda-chain.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "ac184deab5698b832a3af2ad34818e9296611ee91c6c8ca9b4fe1399e8ab0551.json", + "objectKey": "346b0f31aef9dba7bae2a5c33f01d10e8f1ca774a3fc50386c0c901037757662.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/aws-cdk-lambda-chain.template.json b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/aws-cdk-lambda-chain.template.json index dfd89bf1fca46..178cdae2b90dc 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/aws-cdk-lambda-chain.template.json +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/aws-cdk-lambda-chain.template.json @@ -75,7 +75,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event === 'error') throw new Error('UnkownError');\n return event;\n };" + "ZipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event.status === 'error') throw new Error('UnkownError');\n return event;\n };" }, "Role": { "Fn::GetAtt": [ @@ -344,7 +344,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event === 'error') throw new Error('UnkownError');\n return event;\n };" + "ZipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event.status === 'error') throw new Error('UnkownError');\n return event;\n };" }, "Role": { "Fn::GetAtt": [ @@ -489,7 +489,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event === 'error') throw new Error('UnkownError');\n return event;\n };" + "ZipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event.status === 'error') throw new Error('UnkownError');\n return event;\n };" }, "Role": { "Fn::GetAtt": [ @@ -539,7 +539,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event === 'error') throw new Error('UnkownError');\n return event;\n };" + "ZipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event.status === 'error') throw new Error('UnkownError');\n return event;\n };" }, "Role": { "Fn::GetAtt": [ @@ -570,6 +570,22 @@ "Value": { "Ref": "ErrorD9F0B79D" } + }, + "ExportsOutputRefFirst8D4707F1B3E37FDF": { + "Value": { + "Ref": "First8D4707F1" + }, + "Export": { + "Name": "aws-cdk-lambda-chain:ExportsOutputRefFirst8D4707F1B3E37FDF" + } + }, + "ExportsOutputRefThird1125870FF05390B4": { + "Value": { + "Ref": "Third1125870F" + }, + "Export": { + "Name": "aws-cdk-lambda-chain:ExportsOutputRefThird1125870FF05390B4" + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/integ.json b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/integ.json index 8b14dd46ecb30..d067884f6485f 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/integ.json @@ -1,14 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { - "integ.lambda-chain": { + "LambdaDestChain3/DefaultTest": { "stacks": [ "aws-cdk-lambda-chain" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "LambdaDestChain3/DefaultTest/DeployAssert", + "assertionStackName": "LambdaDestChain3DefaultTestDeployAssertB72F10E8" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/manifest.json index cee488dbc634b..066d1fc9cb636 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,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}/ac184deab5698b832a3af2ad34818e9296611ee91c6c8ca9b4fe1399e8ab0551.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/346b0f31aef9dba7bae2a5c33f01d10e8f1ca774a3fc50386c0c901037757662.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -165,6 +165,18 @@ "data": "ErrorFunctionName" } ], + "/aws-cdk-lambda-chain/Exports/Output{\"Ref\":\"First8D4707F1\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefFirst8D4707F1B3E37FDF" + } + ], + "/aws-cdk-lambda-chain/Exports/Output{\"Ref\":\"Third1125870F\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefThird1125870FF05390B4" + } + ], "/aws-cdk-lambda-chain/BootstrapVersion": [ { "type": "aws:cdk:logicalId", @@ -179,6 +191,138 @@ ] }, "displayName": "aws-cdk-lambda-chain" + }, + "LambdaDestChain3DefaultTestDeployAssertB72F10E8.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "LambdaDestChain3DefaultTestDeployAssertB72F10E8.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "LambdaDestChain3DefaultTestDeployAssertB72F10E8": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "LambdaDestChain3DefaultTestDeployAssertB72F10E8.template.json", + "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}/36ba0923aa26f9b7c5a2bea94bbcfb168d2cef9f3285309a32c1d8e4a98101aa.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "LambdaDestChain3DefaultTestDeployAssertB72F10E8.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-lambda-chain", + "LambdaDestChain3DefaultTestDeployAssertB72F10E8.assets" + ], + "metadata": { + "/LambdaDestChain3/DefaultTest/DeployAssert/LambdaInvoke56ff6291852e7537dce00fa6e862a00b/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "LambdaInvoke56ff6291852e7537dce00fa6e862a00b" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/LambdaInvoke56ff6291852e7537dce00fa6e862a00b/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "LambdaInvoke56ff6291852e7537dce00fa6e862a00bInvokeA6B3FDE4" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudWatchLogsfilterLogEvents" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/IsCompleteProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudWatchLogsfilterLogEventsWaitForIsCompleteProviderInvoke00D72D57" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/TimeoutProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudWatchLogsfilterLogEventsWaitForTimeoutProviderInvoke3950C224" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudWatchLogsfilterLogEventsWaitForRole9F1B78BD" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudWatchLogsfilterLogEventsWaitFor64019274" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallCloudWatchLogsfilterLogEvents" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/LambdaDestChain3/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "LambdaDestChain3/DefaultTest/DeployAssert" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/tree.json b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/tree.json index 26b5188dcbd16..c36831b5f95b6 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-lambda-destinations/test/lambda-chain.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } }, "aws-cdk-lambda-chain": { @@ -134,7 +134,7 @@ "aws:cdk:cloudformation:type": "AWS::Lambda::Function", "aws:cdk:cloudformation:props": { "code": { - "zipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event === 'error') throw new Error('UnkownError');\n return event;\n };" + "zipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event.status === 'error') throw new Error('UnkownError');\n return event;\n };" }, "role": { "Fn::GetAtt": [ @@ -246,8 +246,8 @@ "id": "DefaultEventBus", "path": "aws-cdk-lambda-chain/First/EventInvokeConfig/DefaultEventBus", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "Success": { @@ -531,7 +531,7 @@ "aws:cdk:cloudformation:type": "AWS::Lambda::Function", "aws:cdk:cloudformation:props": { "code": { - "zipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event === 'error') throw new Error('UnkownError');\n return event;\n };" + "zipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event.status === 'error') throw new Error('UnkownError');\n return event;\n };" }, "role": { "Fn::GetAtt": [ @@ -643,8 +643,8 @@ "id": "DefaultEventBus", "path": "aws-cdk-lambda-chain/Second/EventInvokeConfig/DefaultEventBus", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "Resource": { @@ -760,7 +760,7 @@ "aws:cdk:cloudformation:type": "AWS::Lambda::Function", "aws:cdk:cloudformation:props": { "code": { - "zipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event === 'error') throw new Error('UnkownError');\n return event;\n };" + "zipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event.status === 'error') throw new Error('UnkownError');\n return event;\n };" }, "role": { "Fn::GetAtt": [ @@ -843,7 +843,7 @@ "aws:cdk:cloudformation:type": "AWS::Lambda::Function", "aws:cdk:cloudformation:props": { "code": { - "zipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event === 'error') throw new Error('UnkownError');\n return event;\n };" + "zipFile": "exports.handler = async (event) => {\n console.log('Event: %j', event);\n if (event.status === 'error') throw new Error('UnkownError');\n return event;\n };" }, "role": { "Fn::GetAtt": [ @@ -870,36 +870,388 @@ "id": "FirstFunctionName", "path": "aws-cdk-lambda-chain/FirstFunctionName", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } }, "ThirdFunctionName": { "id": "ThirdFunctionName", "path": "aws-cdk-lambda-chain/ThirdFunctionName", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } }, "ErrorFunctionName": { "id": "ErrorFunctionName", "path": "aws-cdk-lambda-chain/ErrorFunctionName", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "aws-cdk-lambda-chain/Exports", + "children": { + "Output{\"Ref\":\"First8D4707F1\"}": { + "id": "Output{\"Ref\":\"First8D4707F1\"}", + "path": "aws-cdk-lambda-chain/Exports/Output{\"Ref\":\"First8D4707F1\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Output{\"Ref\":\"Third1125870F\"}": { + "id": "Output{\"Ref\":\"Third1125870F\"}", + "path": "aws-cdk-lambda-chain/Exports/Output{\"Ref\":\"Third1125870F\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.102" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "LambdaDestChain3": { + "id": "LambdaDestChain3", + "path": "LambdaDestChain3", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "LambdaDestChain3/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "LambdaDestChain3/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "LambdaDestChain3/DefaultTest/DeployAssert", + "children": { + "LambdaInvoke56ff6291852e7537dce00fa6e862a00b": { + "id": "LambdaInvoke56ff6291852e7537dce00fa6e862a00b", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/LambdaInvoke56ff6291852e7537dce00fa6e862a00b", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/LambdaInvoke56ff6291852e7537dce00fa6e862a00b/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/LambdaInvoke56ff6291852e7537dce00fa6e862a00b/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/LambdaInvoke56ff6291852e7537dce00fa6e862a00b/Default", + "children": { + "Default": { + "id": "Default", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/LambdaInvoke56ff6291852e7537dce00fa6e862a00b/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "Invoke": { + "id": "Invoke", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/LambdaInvoke56ff6291852e7537dce00fa6e862a00b/Invoke", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.LambdaInvokeFunction", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "AwsApiCallCloudWatchLogsfilterLogEvents": { + "id": "AwsApiCallCloudWatchLogsfilterLogEvents", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/Default", + "children": { + "Default": { + "id": "Default", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/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": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor", + "children": { + "IsCompleteProvider": { + "id": "IsCompleteProvider", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/IsCompleteProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/IsCompleteProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "Invoke": { + "id": "Invoke", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/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": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/TimeoutProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/TimeoutProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "Invoke": { + "id": "Invoke", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/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": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/WaitFor/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/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": "LambdaDestChain3/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925": { + "id": "SingletonFunction76b3e830a873425f8453eddd85c86925", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925", + "children": { + "Staging": { + "id": "Staging", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a": { + "id": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a", + "children": { + "Staging": { + "id": "Staging", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "LambdaDestChain3/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/integ-tests/README.md b/packages/@aws-cdk/integ-tests/README.md index 08c2e70fa811b..5714b7be8795c 100644 --- a/packages/@aws-cdk/integ-tests/README.md +++ b/packages/@aws-cdk/integ-tests/README.md @@ -439,3 +439,52 @@ integ.assertions.awsApiCall('S3', 'putObject', { })); ``` +#### Wait for results + +A common use case when performing assertions is to wait for a condition to pass. Sometimes the thing +that you are asserting against is not done provisioning by the time the assertion runs. In these +cases it is possible to run the assertion asynchronously by calling the `waitForAssertions()` method. + +Taking the example above of executing a StepFunctions state machine, depending on the complexity of +the state machine, it might take a while for it to complete. + +```ts +declare const app: App; +declare const stack: Stack; +declare const sm: IStateMachine; + +const testCase = new IntegTest(app, 'IntegTest', { + testCases: [stack], +}); + +// Start an execution +const start = testCase.assertions.awsApiCall('StepFunctions', 'startExecution', { + stateMachineArn: sm.stateMachineArn, +}); + +// describe the results of the execution +const describe = testCase.assertions.awsApiCall('StepFunctions', 'describeExecution', { + executionArn: start.getAttString('executionArn'), +}).expect(ExpectedResult.objectLike({ + status: 'SUCCEEDED', +})).waitForAssertions(); +``` + +When you call `waitForAssertions()` the assertion provider will continuously make the `awsApiCall` until the +`ExpectedResult` is met. You can also control the parameters for waiting, for example: + +```ts +declare const testCase: IntegTest; +declare const start: IApiCall; + +const describe = testCase.assertions.awsApiCall('StepFunctions', 'describeExecution', { + executionArn: start.getAttString('executionArn'), +}).expect(ExpectedResult.objectLike({ + status: 'SUCCEEDED', +})).waitForAssertions({ + totalTimeout: Duration.minutes(5), + interval: Duration.seconds(15), + backoffRate: 3, +}); +``` + diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/api-call-base.ts b/packages/@aws-cdk/integ-tests/lib/assertions/api-call-base.ts index a0b67ad587f39..88ac967545307 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/api-call-base.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/api-call-base.ts @@ -2,6 +2,7 @@ import { CustomResource, Reference } from '@aws-cdk/core'; import { Construct, IConstruct } from 'constructs'; import { ExpectedResult } from './common'; import { AssertionsProvider } from './providers'; +import { WaiterStateMachineOptions } from './waiter-state-machine'; /** * Represents an ApiCall @@ -53,7 +54,7 @@ export interface IApiCall extends IConstruct { * }); * invoke.expect(ExpectedResult.objectLike({ Payload: 'OK' })); */ - expect(expected: ExpectedResult): void; + expect(expected: ExpectedResult): IApiCall; /** * Assert that the ExpectedResult is equal @@ -98,6 +99,20 @@ export interface IApiCall extends IConstruct { * first.next(second); */ next(next: IApiCall): IApiCall; + + /** + * Wait for the IApiCall to return the expected response. + * If no expected response is specified then it will wait for + * the IApiCall to return a success + * + * @example + * declare const integ: IntegTest; + * declare const executionArn: string; + * integ.assertions.awsApiCall('StepFunctions', 'describeExecution', { + * executionArn, + * }).waitForAssertions(); + */ + waitForAssertions(options?: WaiterStateMachineOptions): IApiCall; } /** @@ -107,6 +122,7 @@ export abstract class ApiCallBase extends Construct implements IApiCall { protected abstract readonly apiCallResource: CustomResource; protected expectedResult?: string; protected flattenResponse: string = 'false'; + protected stateMachineArn?: string; public abstract readonly provider: AssertionsProvider; @@ -125,8 +141,9 @@ export abstract class ApiCallBase extends Construct implements IApiCall { return this.apiCallResource.getAttString(`apiCallResponse.${attributeName}`); } - public expect(expected: ExpectedResult): void { + public expect(expected: ExpectedResult): IApiCall { this.expectedResult = expected.result; + return this; } public abstract assertAtPath(path: string, expected: ExpectedResult): IApiCall; @@ -135,4 +152,6 @@ export abstract class ApiCallBase extends Construct implements IApiCall { next.node.addDependency(this); return next; } + + public abstract waitForAssertions(options?: WaiterStateMachineOptions): IApiCall } diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/index.ts b/packages/@aws-cdk/integ-tests/lib/assertions/index.ts index b81ba41add625..fdb65ff0d2b9c 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/index.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/index.ts @@ -5,3 +5,4 @@ export * from './providers'; export * from './common'; export * from './match'; export * from './api-call-base'; +export * from './waiter-state-machine'; diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/base.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/base.ts index 7191e24ea8876..47dbdd3e30f47 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/base.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/base.ts @@ -1,6 +1,8 @@ /* eslint-disable no-console */ import * as https from 'https'; import * as url from 'url'; +// eslint-disable-next-line import/no-extraneous-dependencies +import * as AWS from 'aws-sdk'; interface HandlerResponse { readonly status: 'SUCCESS' | 'FAILED'; @@ -27,10 +29,24 @@ export abstract class CustomResourceHandler { try { - const response = await this.processEvent(this.event.ResourceProperties as unknown as Request); - return response; + if ('stateMachineArn' in this.event.ResourceProperties) { + const req: AWS.StepFunctions.StartExecutionInput = { + 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 as unknown as Request); + return response; + } } catch (e) { console.log(e); throw e; @@ -39,6 +55,34 @@ export abstract class CustomResourceHandler { + try { + const result = await this.processEvent(this.event.ResourceProperties as unknown as Request); + 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. + */ + private async startExecution(req: AWS.StepFunctions.StartExecutionInput): Promise { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + protected abstract processEvent(request: Request): Promise; public respond(response: HandlerResponse) { @@ -75,6 +119,8 @@ export abstract class CustomResourceHandler 90000, +}; diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/provider.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/provider.ts index 5a5e3378fa4fa..79c5b514b1d63 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/providers/provider.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/provider.ts @@ -4,6 +4,17 @@ import { Construct } from 'constructs'; let SDK_METADATA: any = undefined; +/** + * Properties for a lambda function provider + */ +export interface LambdaFunctionProviderProps { + /** + * The handler to use for the lambda function + * + * @default index.handler + */ + readonly handler?: string; +} /** * integ-tests can only depend on '@aws-cdk/core' so @@ -24,7 +35,7 @@ class LambdaFunctionProvider extends Construct { private readonly policies: any[] = []; - constructor(scope: Construct, id: string/*, props?: LambdaFunctionProviderProps*/) { + constructor(scope: Construct, id: string, props?: LambdaFunctionProviderProps) { super(scope, id); const staging = new AssetStaging(this, 'Staging', { @@ -48,15 +59,20 @@ class LambdaFunctionProvider extends Construct { ManagedPolicyArns: [ { 'Fn::Sub': 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' }, ], - Policies: [ - { - PolicyName: 'Inline', - PolicyDocument: { - Version: '2012-10-17', - Statement: Lazy.list({ produce: () => this.policies }), - }, + Policies: Lazy.any({ + produce: () => { + const policies = this.policies.length > 0 ? [ + { + PolicyName: 'Inline', + PolicyDocument: { + Version: '2012-10-17', + Statement: this.policies, + }, + }, + ] : undefined; + return policies; }, - ], + }), }, }); @@ -69,7 +85,7 @@ class LambdaFunctionProvider extends Construct { S3Key: asset.objectKey, }, Timeout: Duration.minutes(2).toSeconds(), - Handler: 'index.handler', + Handler: props?.handler ?? 'index.handler', Role: role.getAtt('Arn'), }, }); @@ -84,7 +100,7 @@ class LambdaFunctionProvider extends Construct { } -interface SingletonFunctionProps { +interface SingletonFunctionProps extends LambdaFunctionProviderProps { /** * A unique identifier to identify this lambda * @@ -92,12 +108,6 @@ interface SingletonFunctionProps { * We recommend generating a UUID per provider. */ readonly uuid: string; - - /** - * A list of IAM policies to add to the lambdaFunction - * execution role - */ - readonly policies: any[]; } /** @@ -107,21 +117,10 @@ class SingletonFunction extends Construct { public readonly serviceToken: string; public readonly lambdaFunction: LambdaFunctionProvider; - private readonly policies: any[] = []; constructor(scope: Construct, id: string, props: SingletonFunctionProps) { super(scope, id); this.lambdaFunction = this.ensureFunction(props); this.serviceToken = this.lambdaFunction.serviceToken; - - /** - * The policies can be added by different constructs - */ - this.node.addValidation({ - validate: () => { - this.lambdaFunction.addPolicies(this.policies); - return []; - }, - }); } private ensureFunction(props: SingletonFunctionProps): LambdaFunctionProvider { @@ -131,7 +130,9 @@ class SingletonFunction extends Construct { return existing as LambdaFunctionProvider; } - return new LambdaFunctionProvider(Stack.of(this), constructName); + return new LambdaFunctionProvider(Stack.of(this), constructName, { + handler: props.handler, + }); } /** @@ -149,7 +150,7 @@ class SingletonFunction extends Construct { * }); */ public addToRolePolicy(statement: any): void { - this.policies.push(statement); + this.lambdaFunction.addPolicies([statement]); } /** @@ -163,13 +164,26 @@ class SingletonFunction extends Construct { const srv = service.toLowerCase(); const iamService = (SDK_METADATA[srv] && SDK_METADATA[srv].prefix) || srv; const iamAction = api.charAt(0).toUpperCase() + api.slice(1); - this.policies.push({ + this.lambdaFunction.addPolicies([{ Action: [`${iamService}:${iamAction}`], Effect: 'Allow', Resource: resources || ['*'], - }); + }]); } +} +/** + * Properties for defining an AssertionsProvider + */ +export interface AssertionsProviderProps extends LambdaFunctionProviderProps { + /** + * This determines the uniqueness of each AssertionsProvider. + * You should only need to provide something different here if you + * _know_ that you need a separate provider + * + * @default - the default uuid is used + */ + readonly uuid?: string; } /** @@ -190,15 +204,14 @@ export class AssertionsProvider extends Construct { */ public readonly handlerRoleArn: Reference; - private readonly policies: any[] = []; private readonly handler: SingletonFunction; - constructor(scope: Construct, id: string) { + constructor(scope: Construct, id: string, props?: AssertionsProviderProps) { super(scope, id); this.handler = new SingletonFunction(this, 'AssertionsProvider', { - uuid: '1488541a-7b23-4664-81b6-9b4408076b81', - policies: Lazy.list({ produce: () => this.policies }), + handler: props?.handler, + uuid: props?.uuid ?? '1488541a-7b23-4664-81b6-9b4408076b81', }); this.handlerRoleArn = this.handler.lambdaFunction.roleArn; @@ -254,6 +267,24 @@ export class AssertionsProvider extends Construct { public addToRolePolicy(statement: any): void { this.handler.addToRolePolicy(statement); } + + /** + * Grant a principal access to invoke the assertion provider + * lambda function + * + * @param principalArn the ARN of the principal that should be given + * permission to invoke the assertion provider + */ + public grantInvoke(principalArn: string): void { + new CfnResource(this, 'Invoke', { + type: 'AWS::Lambda::Permission', + properties: { + Action: 'lambda:InvokeFunction', + FunctionName: this.serviceToken, + Principal: principalArn, + }, + }); + } } function slugify(x: string): string { diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts index 7f107f3ecd8d3..96a994a8fc0a8 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts @@ -3,6 +3,7 @@ import { Construct, IConstruct } from 'constructs'; import { ApiCallBase, IApiCall } from './api-call-base'; import { ExpectedResult } from './common'; import { AssertionsProvider, SDK_RESOURCE_TYPE_PREFIX } from './providers'; +import { WaiterStateMachine, WaiterStateMachineOptions } from './waiter-state-machine'; /** * Options to perform an AWS JavaScript V2 API call @@ -27,7 +28,8 @@ export interface AwsApiCallOptions { } /** - * Options for creating an SDKQuery provider + * Construct that creates a custom resource that will perform + * a query using the AWS SDK */ export interface AwsApiCallProps extends AwsApiCallOptions { } @@ -36,11 +38,29 @@ export interface AwsApiCallProps extends AwsApiCallOptions { } * a query using the AWS SDK */ export class AwsApiCall extends ApiCallBase { + public readonly provider: AssertionsProvider; + + /** + * access the AssertionsProvider for the waiter state machine. + * This can be used to add additional IAM policies + * the the provider role policy + * + * @example + * declare const apiCall: AwsApiCall; + * apiCall.waiterProvider?.addToRolePolicy({ + * Effect: 'Allow', + * Action: ['s3:GetObject'], + * Resource: ['*'], + * }); + */ + public waiterProvider?: AssertionsProvider; + protected readonly apiCallResource: CustomResource; private readonly name: string; - public readonly provider: AssertionsProvider; private _assertAtPath?: string; + private readonly api: string; + private readonly service: string; constructor(scope: Construct, id: string, props: AwsApiCallProps) { super(scope, id); @@ -48,6 +68,8 @@ export class AwsApiCall extends ApiCallBase { this.provider = new AssertionsProvider(this, 'SdkProvider'); this.provider.addPolicyStatementFromSdkCall(props.service, props.api); this.name = `${props.service}${props.api}`; + this.api = props.api; + this.service = props.service; this.apiCallResource = new CustomResource(this, 'Default', { serviceToken: this.provider.serviceToken, @@ -56,13 +78,13 @@ export class AwsApiCall extends ApiCallBase { api: props.api, expected: Lazy.any({ produce: () => this.expectedResult }), actualPath: Lazy.string({ produce: () => this._assertAtPath }), + stateMachineArn: Lazy.string({ produce: () => this.stateMachineArn }), parameters: this.provider.encode(props.parameters), flattenResponse: Lazy.string({ produce: () => this.flattenResponse }), salt: Date.now().toString(), }, resourceType: `${SDK_RESOURCE_TYPE_PREFIX}${this.name}`.substring(0, 60), }); - // Needed so that all the policies set up by the provider should be available before the custom resource is provisioned. this.apiCallResource.node.addDependency(this.provider); @@ -89,6 +111,17 @@ export class AwsApiCall extends ApiCallBase { this.flattenResponse = 'true'; return this; } + + public waitForAssertions(options?: WaiterStateMachineOptions): IApiCall { + const waiter = new WaiterStateMachine(this, 'WaitFor', { + ...options, + }); + this.stateMachineArn = waiter.stateMachineArn; + this.provider.addPolicyStatementFromSdkCall('states', 'StartExecution'); + waiter.isCompleteProvider.addPolicyStatementFromSdkCall(this.service, this.api); + this.waiterProvider = waiter.isCompleteProvider; + return this; + } } /** diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/waiter-state-machine.ts b/packages/@aws-cdk/integ-tests/lib/assertions/waiter-state-machine.ts new file mode 100644 index 0000000000000..a2a63df342a7e --- /dev/null +++ b/packages/@aws-cdk/integ-tests/lib/assertions/waiter-state-machine.ts @@ -0,0 +1,182 @@ +import { CfnResource, Duration, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { AssertionsProvider } from './providers'; + +/** + * Options for creating a WaiterStateMachine + */ +export interface WaiterStateMachineOptions { + /** + * The total time that the state machine will wait + * for a successful response + * + * @default Duration.minutes(30) + */ + readonly totalTimeout?: Duration; + + /** + * The interval (number of seconds) to wait between attempts. + * + * @default Duration.seconds(5) + */ + readonly interval?: Duration; + + /** + * Backoff between attempts. + * + * This is the multiplier by which the retry interval increases + * after each retry attempt. + * + * By default there is no backoff. Each retry will wait the amount of time + * specified by `interval`. + * + * @default 1 (no backoff) + */ + readonly backoffRate?: number; +} + +/** + * Props for creating a WaiterStateMachine + */ +export interface WaiterStateMachineProps extends WaiterStateMachineOptions {} + +/** + * A very simple StateMachine construct highly customized to the provider framework. + * This is so that this package does not need to depend on aws-stepfunctions module. + * + * The state machine continuously calls the isCompleteHandler, until it succeeds or times out. + * The handler is called `maxAttempts` times with an `interval` duration and a `backoffRate` rate. + * + * For example with: + * - maxAttempts = 360 (30 minutes) + * - interval = 5 + * - backoffRate = 1 (no backoff) + * + * it will make the API Call every 5 seconds and fail after 360 failures. + * + * If the backoff rate is changed to 2 (for example), it will + * - make the first call + * - wait 5 seconds + * - make the second call + * - wait 15 seconds + * - etc. + */ +export class WaiterStateMachine extends Construct { + /** + * The ARN of the statemachine + */ + public readonly stateMachineArn: string; + + /** + * The IAM Role ARN of the role used by the state machine + */ + public readonly roleArn: string; + + /** + * The AssertionsProvide that handles async requests + */ + public readonly isCompleteProvider: AssertionsProvider; + + constructor(scope: Construct, id: string, props: WaiterStateMachineProps = {}) { + super(scope, id); + const interval = props.interval || Duration.seconds(5); + const totalTimeout = props.totalTimeout || Duration.minutes(30); + const maxAttempts = calculateMaxRetries(totalTimeout.toSeconds(), interval.toSeconds(), props.backoffRate ?? 1); + + if (Math.round(maxAttempts) !== maxAttempts) { + throw new Error(`Cannot determine retry count since totalTimeout=${totalTimeout.toSeconds()}s is not integrally dividable by queryInterval=${interval.toSeconds()}s`); + } + + this.isCompleteProvider = new AssertionsProvider(this, 'IsCompleteProvider', { + handler: 'index.isComplete', + uuid: '76b3e830-a873-425f-8453-eddd85c86925', + }); + + const timeoutProvider = new AssertionsProvider(this, 'TimeoutProvider', { + handler: 'index.onTimeout', + uuid: '5c1898e0-96fb-4e3e-95d5-f6c67f3ce41a', + }); + + const role = new CfnResource(this, 'Role', { + 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: [ + this.isCompleteProvider.serviceToken, + timeoutProvider.serviceToken, + ], + }], + }, + }, + ], + }, + }); + + const definition = Stack.of(this).toJsonString({ + StartAt: 'framework-isComplete-task', + States: { + 'framework-isComplete-task': { + End: true, + Retry: [{ + ErrorEquals: ['States.ALL'], + IntervalSeconds: interval.toSeconds(), + MaxAttempts: maxAttempts, + BackoffRate: props.backoffRate ?? 1, + }], + Catch: [{ + ErrorEquals: ['States.ALL'], + Next: 'framework-onTimeout-task', + }], + Type: 'Task', + Resource: this.isCompleteProvider.serviceToken, + }, + 'framework-onTimeout-task': { + End: true, + Type: 'Task', + Resource: timeoutProvider.serviceToken, + }, + }, + }); + + const resource = new CfnResource(this, 'Resource', { + type: 'AWS::StepFunctions::StateMachine', + properties: { + DefinitionString: definition, + RoleArn: role.getAtt('Arn'), + }, + }); + resource.node.addDependency(role); + + this.stateMachineArn = resource.ref; + this.roleArn = role.getAtt('Arn').toString(); + this.isCompleteProvider.grantInvoke(this.roleArn); + timeoutProvider.grantInvoke(this.roleArn); + } +} + +/** + * Calculate the max number of retries + */ +function calculateMaxRetries(maxSeconds: number, intervalSeconds: number, backoff: number): number { + let retries = 1; + let nextInterval = intervalSeconds; + let i = 0; + while (i < maxSeconds) { + nextInterval = nextInterval+nextInterval*backoff; + i+=nextInterval; + if (i >= maxSeconds) break; + retries++; + } + return retries; +} diff --git a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture index 168caf8aaf2af..672c09156597f 100644 --- a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture @@ -20,6 +20,7 @@ import { Stack, StackProps, CustomResource, + Duration, } from '@aws-cdk/core'; import * as path from 'path'; import * as sqs from '@aws-cdk/aws-sqs'; diff --git a/packages/@aws-cdk/integ-tests/test/assertions/providers/lambda-handler/base.test.ts b/packages/@aws-cdk/integ-tests/test/assertions/providers/lambda-handler/base.test.ts index 8722eea5e3837..80c176e9e874a 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/providers/lambda-handler/base.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/providers/lambda-handler/base.test.ts @@ -2,7 +2,7 @@ import * as SDK from 'aws-sdk'; import * as AWS from 'aws-sdk-mock'; import * as nock from 'nock'; import * as sinon from 'sinon'; -import { handler as lambda_handler } from '../../../../lib/assertions/providers/lambda-handler'; +import { handler as lambda_handler, isComplete, onTimeout } from '../../../../lib/assertions/providers/lambda-handler'; import { CustomResourceHandler } from '../../../../lib/assertions/providers/lambda-handler/base'; interface MyHandlerRequest { @@ -32,6 +32,7 @@ describe('CustomResourceHandler', () => { AWS.mock('S3', 'listBuckets', mockMyApi); AWS.mock('StepFunctions', 'startExecution', mockStartExecution); jest.spyOn(console, 'log').mockImplementation(() => { return true; }); + jest.spyOn(console, 'info').mockImplementation(() => { return true; }); }); afterEach(() => { @@ -42,6 +43,30 @@ describe('CustomResourceHandler', () => { }); describe('lambda handler', () => { + test('create async request', async () => { + const nocked = nockUp((body) => { + return body.Status === 'SUCCESS' + && body.Reason === 'OK' + && body.Data === {} + && body.StackId === 'MyStackId' + && body.RequestId === 'MyRequestId' + && body.NoEcho === false; + }); + const event = createEvent({ + stateMachineArn: 'arn', + service: 'MyService', + api: 'myApi', + }, 'Custom::DeployAssert@SdkCall'); + await lambda_handler(event, standardContext); + + sinon.assert.calledOnce(mockStartExecution); + sinon.assert.notCalled(mockMyApi); + + // THEN + // started async workflow so no response to CFN + expect(nocked.isDone()).toEqual(false); + }); + test('create sync request', async () => { const nocked = nockUp((body) => { return body.Status === 'SUCCESS' @@ -108,6 +133,89 @@ describe('CustomResourceHandler', () => { }); }); + describe('lambda isCompleteHandler', () => { + test('basic', async () => { + const nocked = nockUp((body) => { + return body.Status === 'SUCCESS' + && body.Reason === 'OK' + && 'apiCallResponse' in body.Data + && body.StackId === 'MyStackId' + && body.RequestId === 'MyRequestId' + && body.NoEcho === false; + }); + const event = createEvent({ + stateMachineArn: 'arn', + service: 'S3', + api: 'listBuckets', + }, 'Custom::DeployAssert@SdkCall'); + await isComplete(event, standardContext); + + sinon.assert.calledOnce(mockMyApi); + + // THEN + expect(nocked.isDone()).toEqual(true); + }); + + test('create request with assertions', async () => { + const nocked = nockUp((body) => { + return body.Status === 'SUCCESS' + && body.Reason === 'OK' + && body.Data.assertion === '{"status":"success"}' + && body.StackId === 'MyStackId' + && body.RequestId === 'MyRequestId' + && body.NoEcho === false; + }); + const event = createEvent({ + service: 'S3', + api: 'listBuckets', + expected: JSON.stringify({ $ObjectLike: { Buckets: [{ Name: 'somebucket' }] } }), + }, 'Custom::DeployAssert@SdkCall'); + await isComplete(event, standardContext); + + sinon.assert.calledOnce(mockMyApi); + + // THEN + expect(nocked.isDone()).toEqual(true); + }); + + test('create request with assertions fails', async () => { + const nocked = nockUp((body) => { + return body.Status === 'FAILED' + && (body.Reason?.match(/Expected someotherbucket/) ?? []).length === 1; + }); + const event = createEvent({ + service: 'S3', + api: 'listBuckets', + expected: JSON.stringify({ $ObjectLike: { Buckets: [{ Name: 'someotherbucket' }] } }), + }, 'Custom::DeployAssert@SdkCall'); + await expect(isComplete(event, standardContext)).rejects.toThrow(); + + sinon.assert.calledOnce(mockMyApi); + sinon.assert.notCalled(mockStartExecution); + + // THEN + expect(nocked.isDone()).toEqual(false); + }); + }); + + describe('onTimeout', () => { + test('timeout', async () => { + const nocked = nockUp((body) => { + return body.Status === 'FAILED' + && (body.Reason?.match(/Operation timed out/) ?? []).length === 1; + }); + await onTimeout({ + Cause: JSON.stringify({ + errorMessage: JSON.stringify(createEvent({ + service: 'S3', + api: 'listBuckets', + }, 'Custom::DeployAssert@SdkCall')), + }), + }); + expect(nocked.isDone()).toEqual(true); + }); + }); + test('timeout kicks in', async () => { // GIVEN class MyHandler extends CustomResourceHandler { diff --git a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts index 3d0cbaa57f13b..8a0123ca8e96b 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts @@ -1,6 +1,6 @@ -import { Match, Template } from '@aws-cdk/assertions'; -import { App, CfnOutput } from '@aws-cdk/core'; -import { ExpectedResult, InvocationType, LogType } from '../../lib/assertions'; +import { Template, Match } from '@aws-cdk/assertions'; +import { App, CfnOutput, Duration } from '@aws-cdk/core'; +import { LogType, InvocationType, ExpectedResult } from '../../lib/assertions'; import { DeployAssert } from '../../lib/assertions/private/deploy-assert'; describe('AwsApiCall', () => { @@ -95,6 +95,120 @@ describe('AwsApiCall', () => { }); }); + test('waitFor', () => { + // GIVEN + const app = new App(); + const deplossert = new DeployAssert(app); + + // WHEN + const apiCall = deplossert.awsApiCall('MyService', 'MyApi', { + param1: 'val1', + param2: 2, + }).expect(ExpectedResult.objectLike({ + Key: 'Value', + })).waitForAssertions(); + apiCall.provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['s3:GetObject'], + Resource: ['*'], + }); + + // THEN + Template.fromStack(deplossert.scope).hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { + service: 'MyService', + api: 'MyApi', + parameters: { + param1: 'val1', + param2: 2, + }, + expected: JSON.stringify({ $ObjectLike: { Key: 'Value' } }), + }); + Template.fromStack(deplossert.scope).findResources('AWS::IAM::Role', { + SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73: { + Properties: { + Policies: [ + { + PolicyName: 'Inline', + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: [ + 'myservice:MyApi', + ], + Effect: 'Allow', + Resource: [ + '*', + ], + }, + { + Action: [ + 's3:GetObject', + ], + Effect: 'Allow', + Resource: [ + '*', + ], + }, + { + Action: [ + 'states:StartExecution', + ], + Effect: 'Allow', + Resource: ['*'], + }, + ], + }, + }, + ], + }, + }, + }); + }); + test('waitFor with options', () => { + // GIVEN + const app = new App(); + const deplossert = new DeployAssert(app); + + // WHEN + deplossert.awsApiCall('MyService', 'MyApi', { + param1: 'val1', + param2: 2, + }).expect(ExpectedResult.objectLike({ + Key: 'Value', + })).waitForAssertions({ + interval: Duration.seconds(10), + backoffRate: 2, + totalTimeout: Duration.minutes(10), + }); + + // THEN + Template.fromStack(deplossert.scope).hasResourceProperties('AWS::StepFunctions::StateMachine', { + DefinitionString: { + 'Fn::Join': [ + '', + [ + '{"StartAt":"framework-isComplete-task","States":{"framework-isComplete-task":{"End":true,"Retry":[{"ErrorEquals":["States.ALL"],"IntervalSeconds":10,"MaxAttempts":4,"BackoffRate":2}],"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', + ], + }, + '"}}}', + ], + ], + }, + }); + }); + describe('get attribute', () => { test('getAttString', () => { // GIVEN