diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip new file mode 100644 index 0000000000000..f9e887f01f840 Binary files /dev/null and b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip differ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd/index.py b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd/index.py new file mode 100644 index 0000000000000..95c458826a0b0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd/index.py @@ -0,0 +1,319 @@ +import contextlib +import json +import logging +import os +import shutil +import subprocess +import tempfile +from urllib.request import Request, urlopen +from uuid import uuid4 +from zipfile import ZipFile + +import boto3 + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +cloudfront = boto3.client('cloudfront') +s3 = boto3.client('s3') + +CFN_SUCCESS = "SUCCESS" +CFN_FAILED = "FAILED" +ENV_KEY_MOUNT_PATH = "MOUNT_PATH" +ENV_KEY_SKIP_CLEANUP = "SKIP_CLEANUP" + +AWS_CLI_CONFIG_FILE = "/tmp/aws_cli_config" +CUSTOM_RESOURCE_OWNER_TAG = "aws-cdk:cr-owned" + +os.putenv('AWS_CONFIG_FILE', AWS_CLI_CONFIG_FILE) + +def handler(event, context): + + def cfn_error(message=None): + logger.error("| cfn_error: %s" % message) + cfn_send(event, context, CFN_FAILED, reason=message, physicalResourceId=event.get('PhysicalResourceId', None)) + + + try: + # We are not logging ResponseURL as this is a pre-signed S3 URL, and could be used to tamper + # with the response CloudFormation sees from this Custom Resource execution. + logger.info({ key:value for (key, value) in event.items() if key != 'ResponseURL'}) + + # cloudformation request type (create/update/delete) + request_type = event['RequestType'] + + # extract resource properties + props = event['ResourceProperties'] + old_props = event.get('OldResourceProperties', {}) + physical_id = event.get('PhysicalResourceId', None) + + try: + source_bucket_names = props['SourceBucketNames'] + source_object_keys = props['SourceObjectKeys'] + source_markers = props.get('SourceMarkers', None) + dest_bucket_name = props['DestinationBucketName'] + dest_bucket_prefix = props.get('DestinationBucketKeyPrefix', '') + extract = props.get('Extract', 'true') == 'true' + retain_on_delete = props.get('RetainOnDelete', "true") == "true" + distribution_id = props.get('DistributionId', '') + user_metadata = props.get('UserMetadata', {}) + system_metadata = props.get('SystemMetadata', {}) + prune = props.get('Prune', 'true').lower() == 'true' + exclude = props.get('Exclude', []) + include = props.get('Include', []) + sign_content = props.get('SignContent', 'false').lower() == 'true' + + # backwards compatibility - if "SourceMarkers" is not specified, + # assume all sources have an empty market map + if source_markers is None: + source_markers = [{} for i in range(len(source_bucket_names))] + + default_distribution_path = dest_bucket_prefix + if not default_distribution_path.endswith("/"): + default_distribution_path += "/" + if not default_distribution_path.startswith("/"): + default_distribution_path = "/" + default_distribution_path + default_distribution_path += "*" + + distribution_paths = props.get('DistributionPaths', [default_distribution_path]) + except KeyError as e: + cfn_error("missing request resource property %s. props: %s" % (str(e), props)) + return + + # configure aws cli options after resetting back to the defaults for each request + if os.path.exists(AWS_CLI_CONFIG_FILE): + os.remove(AWS_CLI_CONFIG_FILE) + if sign_content: + aws_command("configure", "set", "default.s3.payload_signing_enabled", "true") + + # treat "/" as if no prefix was specified + if dest_bucket_prefix == "/": + dest_bucket_prefix = "" + + s3_source_zips = list(map(lambda name, key: "s3://%s/%s" % (name, key), source_bucket_names, source_object_keys)) + s3_dest = "s3://%s/%s" % (dest_bucket_name, dest_bucket_prefix) + old_s3_dest = "s3://%s/%s" % (old_props.get("DestinationBucketName", ""), old_props.get("DestinationBucketKeyPrefix", "")) + + + # obviously this is not + if old_s3_dest == "s3:///": + old_s3_dest = None + + logger.info("| s3_dest: %s" % s3_dest) + logger.info("| old_s3_dest: %s" % old_s3_dest) + + # if we are creating a new resource, allocate a physical id for it + # otherwise, we expect physical id to be relayed by cloudformation + if request_type == "Create": + physical_id = "aws.cdk.s3deployment.%s" % str(uuid4()) + else: + if not physical_id: + cfn_error("invalid request: request type is '%s' but 'PhysicalResourceId' is not defined" % request_type) + return + + # delete or create/update (only if "retain_on_delete" is false) + if request_type == "Delete" and not retain_on_delete: + if not bucket_owned(dest_bucket_name, dest_bucket_prefix): + aws_command("s3", "rm", s3_dest, "--recursive") + + # if we are updating without retention and the destination changed, delete first + if request_type == "Update" and not retain_on_delete and old_s3_dest != s3_dest: + if not old_s3_dest: + logger.warn("cannot delete old resource without old resource properties") + return + + aws_command("s3", "rm", old_s3_dest, "--recursive") + + if request_type == "Update" or request_type == "Create": + s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, extract) + + if distribution_id: + cloudfront_invalidate(distribution_id, distribution_paths) + + cfn_send(event, context, CFN_SUCCESS, physicalResourceId=physical_id, responseData={ + # Passing through the ARN sequences dependencees on the deployment + 'DestinationBucketArn': props.get('DestinationBucketArn'), + 'SourceObjectKeys': props.get('SourceObjectKeys'), + }) + except KeyError as e: + cfn_error("invalid request. Missing key %s" % str(e)) + except Exception as e: + logger.exception(e) + cfn_error(str(e)) + +#--------------------------------------------------------------------------------------------------- +# populate all files from s3_source_zips to a destination bucket +def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, extract): + # list lengths are equal + if len(s3_source_zips) != len(source_markers): + raise Exception("'source_markers' and 's3_source_zips' must be the same length") + + # create a temporary working directory in /tmp or if enabled an attached efs volume + if ENV_KEY_MOUNT_PATH in os.environ: + workdir = os.getenv(ENV_KEY_MOUNT_PATH) + "/" + str(uuid4()) + os.mkdir(workdir) + else: + workdir = tempfile.mkdtemp() + + logger.info("| workdir: %s" % workdir) + + # create a directory into which we extract the contents of the zip file + contents_dir=os.path.join(workdir, 'contents') + os.mkdir(contents_dir) + + try: + # download the archive from the source and extract to "contents" + for i in range(len(s3_source_zips)): + s3_source_zip = s3_source_zips[i] + markers = source_markers[i] + + if extract: + archive=os.path.join(workdir, str(uuid4())) + logger.info("archive: %s" % archive) + aws_command("s3", "cp", s3_source_zip, archive) + logger.info("| extracting archive to: %s\n" % contents_dir) + logger.info("| markers: %s" % markers) + extract_and_replace_markers(archive, contents_dir, markers) + else: + logger.info("| copying archive to: %s\n" % contents_dir) + aws_command("s3", "cp", s3_source_zip, contents_dir) + + # sync from "contents" to destination + + s3_command = ["s3", "sync"] + + if prune: + s3_command.append("--delete") + + if exclude: + for filter in exclude: + s3_command.extend(["--exclude", filter]) + + if include: + for filter in include: + s3_command.extend(["--include", filter]) + + s3_command.extend([contents_dir, s3_dest]) + s3_command.extend(create_metadata_args(user_metadata, system_metadata)) + aws_command(*s3_command) + finally: + if not os.getenv(ENV_KEY_SKIP_CLEANUP): + shutil.rmtree(workdir) + +#--------------------------------------------------------------------------------------------------- +# invalidate files in the CloudFront distribution edge caches +def cloudfront_invalidate(distribution_id, distribution_paths): + invalidation_resp = cloudfront.create_invalidation( + DistributionId=distribution_id, + InvalidationBatch={ + 'Paths': { + 'Quantity': len(distribution_paths), + 'Items': distribution_paths + }, + 'CallerReference': str(uuid4()), + }) + # by default, will wait up to 10 minutes + cloudfront.get_waiter('invalidation_completed').wait( + DistributionId=distribution_id, + Id=invalidation_resp['Invalidation']['Id']) + +#--------------------------------------------------------------------------------------------------- +# set metadata +def create_metadata_args(raw_user_metadata, raw_system_metadata): + if len(raw_user_metadata) == 0 and len(raw_system_metadata) == 0: + return [] + + format_system_metadata_key = lambda k: k.lower() + format_user_metadata_key = lambda k: k.lower() + + system_metadata = { format_system_metadata_key(k): v for k, v in raw_system_metadata.items() } + user_metadata = { format_user_metadata_key(k): v for k, v in raw_user_metadata.items() } + + flatten = lambda l: [item for sublist in l for item in sublist] + system_args = flatten([[f"--{k}", v] for k, v in system_metadata.items()]) + user_args = ["--metadata", json.dumps(user_metadata, separators=(',', ':'))] if len(user_metadata) > 0 else [] + + return system_args + user_args + ["--metadata-directive", "REPLACE"] + +#--------------------------------------------------------------------------------------------------- +# executes an "aws" cli command +def aws_command(*args): + aws="/opt/awscli/aws" # from AwsCliLayer + logger.info("| aws %s" % ' '.join(args)) + subprocess.check_call([aws] + list(args)) + +#--------------------------------------------------------------------------------------------------- +# sends a response to cloudformation +def cfn_send(event, context, responseStatus, responseData={}, physicalResourceId=None, noEcho=False, reason=None): + + responseUrl = event['ResponseURL'] + + responseBody = {} + responseBody['Status'] = responseStatus + responseBody['Reason'] = reason or ('See the details in CloudWatch Log Stream: ' + context.log_stream_name) + responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name + responseBody['StackId'] = event['StackId'] + responseBody['RequestId'] = event['RequestId'] + responseBody['LogicalResourceId'] = event['LogicalResourceId'] + responseBody['NoEcho'] = noEcho + responseBody['Data'] = responseData + + body = json.dumps(responseBody) + logger.info("| response body:\n" + body) + + headers = { + 'content-type' : '', + 'content-length' : str(len(body)) + } + + try: + request = Request(responseUrl, method='PUT', data=bytes(body.encode('utf-8')), headers=headers) + with contextlib.closing(urlopen(request)) as response: + logger.info("| status code: " + response.reason) + except Exception as e: + logger.error("| unable to send response to CloudFormation") + logger.exception(e) + + +#--------------------------------------------------------------------------------------------------- +# check if bucket is owned by a custom resource +# if it is then we don't want to delete content +def bucket_owned(bucketName, keyPrefix): + tag = CUSTOM_RESOURCE_OWNER_TAG + if keyPrefix != "": + tag = tag + ':' + keyPrefix + try: + request = s3.get_bucket_tagging( + Bucket=bucketName, + ) + return any((x["Key"].startswith(tag)) for x in request["TagSet"]) + except Exception as e: + logger.info("| error getting tags from bucket") + logger.exception(e) + return False + +# extract archive and replace markers in output files +def extract_and_replace_markers(archive, contents_dir, markers): + with ZipFile(archive, "r") as zip: + zip.extractall(contents_dir) + + # replace markers for this source + for file in zip.namelist(): + file_path = os.path.join(contents_dir, file) + if os.path.isdir(file_path): continue + replace_markers(file_path, markers) + +def replace_markers(filename, markers): + # convert the dict of string markers to binary markers + replace_tokens = dict([(k.encode('utf-8'), v.encode('utf-8')) for k, v in markers.items()]) + + outfile = filename + '.new' + with open(filename, 'rb') as fi, open(outfile, 'wb') as fo: + for line in fi: + for token in replace_tokens: + line = line.replace(token, replace_tokens[token]) + fo.write(line) + + # # delete the original file and rename the new one to the original + os.remove(filename) + os.rename(outfile, filename) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.ae370e1010629b78f494346f49ceef3ab2875718f20e6c808114e6aa770c7bf3.bundle/index.js b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.ae370e1010629b78f494346f49ceef3ab2875718f20e6c808114e6aa770c7bf3.bundle/index.js new file mode 100644 index 0000000000000..a54f75c9c3747 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.ae370e1010629b78f494346f49ceef3ab2875718f20e6c808114e6aa770c7bf3.bundle/index.js @@ -0,0 +1,1295 @@ +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 __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// ../../aws-cdk-lib/assertions/lib/matcher.ts +var matcher_exports = {}; +__export(matcher_exports, { + MatchResult: () => MatchResult, + Matcher: () => Matcher +}); +function* range(n) { + for (let i = 0; i < n; i++) { + yield i; + } +} +function* enumFirst(xs) { + let first = true; + for (const x of xs) { + yield [first, x]; + first = false; + } +} +var Matcher, MatchResult; +var init_matcher = __esm({ + "../../aws-cdk-lib/assertions/lib/matcher.ts"() { + "use strict"; + Matcher = class { + /** + * Check whether the provided object is a subtype of the `IMatcher`. + */ + static isMatcher(x) { + return x && x instanceof Matcher; + } + }; + MatchResult = class { + constructor(target) { + this.failuresHere = /* @__PURE__ */ new Map(); + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.innerMatchFailures = /* @__PURE__ */ new Map(); + this._hasFailed = false; + this._failCount = 0; + this._cost = 0; + this.target = target; + } + /** + * DEPRECATED + * @deprecated use recordFailure() + */ + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + /** + * Record a new failure into this result at a specific path. + */ + recordFailure(failure) { + const failKey = failure.path.join("."); + let list = this.failuresHere.get(failKey); + if (!list) { + list = []; + this.failuresHere.set(failKey, list); + } + this._failCount += 1; + this._cost += failure.cost ?? 1; + list.push(failure); + this._hasFailed = true; + return this; + } + /** Whether the match is a success */ + get isSuccess() { + return !this._hasFailed; + } + /** Does the result contain any failures. If not, the result is a success */ + hasFailed() { + return this._hasFailed; + } + /** The number of failures */ + get failCount() { + return this._failCount; + } + /** The cost of the failures so far */ + get failCost() { + return this._cost; + } + /** + * Compose the results of a previous match as a subtree. + * @param id the id of the parent tree. + */ + compose(id, inner) { + if (inner.hasFailed()) { + this._hasFailed = true; + this._failCount += inner.failCount; + this._cost += inner._cost; + this.innerMatchFailures.set(id, inner); + } + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + /** + * Prepare the result to be analyzed. + * This API *must* be called prior to analyzing these results. + */ + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + /** + * Render the failed match in a presentable way + * + * Prefer using `renderMismatch` over this method. It is left for backwards + * compatibility for test suites that expect it, but `renderMismatch()` will + * produce better output. + */ + toHumanStrings() { + const failures = new Array(); + debugger; + recurse(this, []); + return failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at /${r.path.join("/")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + function recurse(x, prefix) { + for (const fail of Array.from(x.failuresHere.values()).flat()) { + failures.push({ + matcher: fail.matcher, + message: fail.message, + path: [...prefix, ...fail.path] + }); + } + for (const [key, inner] of x.innerMatchFailures.entries()) { + recurse(inner, [...prefix, key]); + } + } + } + /** + * Do a deep render of the match result, showing the structure mismatches in context + */ + renderMismatch() { + if (!this.hasFailed()) { + return ""; + } + const parts = new Array(); + const indents = new Array(); + emitFailures(this, ""); + recurse(this); + return moveMarkersToFront(parts.join("").trimEnd()); + function emit(x) { + if (x === void 0) { + debugger; + } + parts.push(x.replace(/\n/g, ` +${indents.join("")}`)); + } + function emitFailures(r, path, scrapSet) { + for (const fail of r.failuresHere.get(path) ?? []) { + emit(`!! ${fail.message} +`); + } + scrapSet == null ? void 0 : scrapSet.delete(path); + } + function recurse(r) { + const remainingFailures = new Set(Array.from(r.failuresHere.keys()).filter((x) => x !== "")); + if (Array.isArray(r.target)) { + indents.push(" "); + emit("[\n"); + for (const [first, i] of enumFirst(range(r.target.length))) { + if (!first) { + emit(",\n"); + } + emitFailures(r, `${i}`, remainingFailures); + const innerMatcher = r.innerMatchFailures.get(`${i}`); + if (innerMatcher) { + emitFailures(innerMatcher, ""); + recurseComparingValues(innerMatcher, r.target[i]); + } else { + emit(renderAbridged(r.target[i])); + } + } + emitRemaining(); + indents.pop(); + emit("\n]"); + return; + } + if (r.target && typeof r.target === "object") { + indents.push(" "); + emit("{\n"); + const keys = Array.from(/* @__PURE__ */ new Set([ + ...Object.keys(r.target), + ...Array.from(remainingFailures) + ])).sort(); + for (const [first, key] of enumFirst(keys)) { + if (!first) { + emit(",\n"); + } + emitFailures(r, key, remainingFailures); + const innerMatcher = r.innerMatchFailures.get(key); + if (innerMatcher) { + emitFailures(innerMatcher, ""); + emit(`${jsonify(key)}: `); + recurseComparingValues(innerMatcher, r.target[key]); + } else { + emit(`${jsonify(key)}: `); + emit(renderAbridged(r.target[key])); + } + } + emitRemaining(); + indents.pop(); + emit("\n}"); + return; + } + emitRemaining(); + emit(jsonify(r.target)); + function emitRemaining() { + if (remainingFailures.size > 0) { + emit("\n"); + } + for (const key of remainingFailures) { + emitFailures(r, key); + } + } + } + function recurseComparingValues(inner, actualValue) { + if (inner.target === actualValue) { + return recurse(inner); + } + emit(renderAbridged(actualValue)); + emit(" <*> "); + recurse(inner); + } + function renderAbridged(x) { + if (Array.isArray(x)) { + switch (x.length) { + case 0: + return "[]"; + case 1: + return `[ ${renderAbridged(x[0])} ]`; + case 2: + if (x.every((e) => ["number", "boolean", "string"].includes(typeof e))) { + return `[ ${x.map(renderAbridged).join(", ")} ]`; + } + return "[ ... ]"; + default: + return "[ ... ]"; + } + } + if (x && typeof x === "object") { + const keys = Object.keys(x); + switch (keys.length) { + case 0: + return "{}"; + case 1: + return `{ ${JSON.stringify(keys[0])}: ${renderAbridged(x[keys[0]])} }`; + default: + return "{ ... }"; + } + } + return jsonify(x); + } + function jsonify(x) { + return JSON.stringify(x) ?? "undefined"; + } + function moveMarkersToFront(x) { + const re = /^(\s+)!!/gm; + return x.replace(re, (_, spaces) => `!!${spaces.substring(0, spaces.length - 2)}`); + } + } + /** + * Record a capture against in this match result. + */ + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/matchers/absent.ts +var AbsentMatch; +var init_absent = __esm({ + "../../aws-cdk-lib/assertions/lib/private/matchers/absent.ts"() { + "use strict"; + init_matcher(); + 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; + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/sorting.ts +function sortKeyComparator(keyFn) { + return (a, b) => { + const ak = keyFn(a); + const bk = keyFn(b); + for (let i = 0; i < ak.length && i < bk.length; i++) { + const av = ak[i]; + const bv = bk[i]; + let diff = 0; + if (typeof av === "number" && typeof bv === "number") { + diff = av - bv; + } else if (typeof av === "string" && typeof bv === "string") { + diff = av.localeCompare(bv); + } + if (diff !== 0) { + return diff; + } + } + return bk.length - ak.length; + }; +} +var init_sorting = __esm({ + "../../aws-cdk-lib/assertions/lib/private/sorting.ts"() { + "use strict"; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/sparse-matrix.ts +var SparseMatrix; +var init_sparse_matrix = __esm({ + "../../aws-cdk-lib/assertions/lib/private/sparse-matrix.ts"() { + "use strict"; + SparseMatrix = class { + constructor() { + this.matrix = /* @__PURE__ */ new Map(); + } + get(row, col) { + var _a; + return (_a = this.matrix.get(row)) == null ? void 0 : _a.get(col); + } + row(row) { + var _a; + return Array.from(((_a = this.matrix.get(row)) == null ? void 0 : _a.entries()) ?? []); + } + set(row, col, value) { + let r = this.matrix.get(row); + if (!r) { + r = /* @__PURE__ */ new Map(); + this.matrix.set(row, r); + } + r.set(col, value); + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} +var init_type = __esm({ + "../../aws-cdk-lib/assertions/lib/private/type.ts"() { + "use strict"; + } +}); + +// ../../aws-cdk-lib/assertions/lib/match.ts +var match_exports = {}; +__export(match_exports, { + Match: () => Match +}); +var Match, LiteralMatch, ArrayMatch, ObjectMatch, SerializedJson, NotMatch, AnyMatch, StringLikeRegexpMatch; +var init_match = __esm({ + "../../aws-cdk-lib/assertions/lib/match.ts"() { + "use strict"; + init_matcher(); + init_absent(); + init_sorting(); + init_sparse_matrix(); + init_type(); + Match = class { + /** + * Use this matcher in the place of a field's value, if the field must not be present. + */ + static absent() { + return new AbsentMatch("absent"); + } + /** + * Matches the specified pattern with the array found in the same relative path of the target. + * The set of elements (or matchers) must be in the same order as would be found. + * @param pattern the pattern to match + */ + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + /** + * Matches the specified pattern with the array found in the same relative path of the target. + * The set of elements (or matchers) must match exactly and in order. + * @param pattern the pattern to match + */ + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + /** + * Deep exact matching of the specified pattern to the target. + * @param pattern the pattern to match + */ + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + /** + * Matches the specified pattern to an object found in the same relative path of the target. + * The keys and their values (or matchers) must be present in the target but the target can be a superset. + * @param pattern the pattern to match + */ + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + /** + * Matches the specified pattern to an object found in the same relative path of the target. + * The keys and their values (or matchers) must match exactly with the target. + * @param pattern the pattern to match + */ + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + /** + * Matches any target which does NOT follow the specified pattern. + * @param pattern the pattern to NOT match + */ + static not(pattern) { + return new NotMatch("not", pattern); + } + /** + * Matches any string-encoded JSON and applies the specified pattern after parsing it. + * @param pattern the pattern to match after parsing the encoded JSON. + */ + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + /** + * Matches any non-null value at the target. + */ + static anyValue() { + return new AnyMatch("anyValue"); + } + /** + * Matches targets according to a regular expression + */ + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } + }; + 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; + } + }; + ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + return this.subsequence ? this.testSubsequence(actual) : this.testFullArray(actual); + } + testFullArray(actual) { + const result = new MatchResult(actual); + let i = 0; + for (; i < this.pattern.length && i < actual.length; i++) { + const patternElement = this.pattern[i]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const innerResult = matcher.test(actual[i]); + result.compose(`${i}`, innerResult); + } + if (i < this.pattern.length) { + result.recordFailure({ + matcher: this, + message: `Not enough elements in array (expecting ${this.pattern.length}, got ${actual.length})`, + path: [`${i}`] + }); + } + if (i < actual.length) { + result.recordFailure({ + matcher: this, + message: `Too many elements in array (expecting ${this.pattern.length}, got ${actual.length})`, + path: [`${i}`] + }); + } + return result; + } + testSubsequence(actual) { + const result = new MatchResult(actual); + let patternIdx = 0; + let actualIdx = 0; + const matches = new SparseMatrix(); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (matcherName == "absent" || matcherName == "anyValue") { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + matches.set(patternIdx, actualIdx, innerResult); + actualIdx++; + if (innerResult.isSuccess) { + result.compose(`${actualIdx}`, innerResult); + patternIdx++; + } + } + if (patternIdx < this.pattern.length) { + for (let spi = 0; spi < patternIdx; spi++) { + const foundMatch = matches.row(spi).find(([, r]) => r.isSuccess); + if (!foundMatch) { + continue; + } + const [index] = foundMatch; + result.compose(`${index}`, new MatchResult(actual[index]).recordFailure({ + matcher: this, + message: `arrayWith pattern ${spi} matched here`, + path: [], + cost: 0 + // This is an informational message so it would be unfair to assign it cost + })); + } + const failedMatches = matches.row(patternIdx); + failedMatches.sort(sortKeyComparator(([i, r]) => [r.failCost, i])); + if (failedMatches.length > 0) { + const [index, innerResult] = failedMatches[0]; + result.recordFailure({ + matcher: this, + message: `Could not match arrayWith pattern ${patternIdx}. This is the closest match`, + path: [`${index}`], + cost: 0 + // Informational message + }); + result.compose(`${index}`, innerResult); + } else { + result.recordFailure({ + matcher: this, + message: `Could not match arrayWith pattern ${patternIdx}. No more elements to try`, + path: [`${actual.length}`] + }); + } + } + return result; + } + }; + ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [a], + message: `Unexpected key ${a}` + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [patternKey], + message: `Missing key '${patternKey}'` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(patternKey, inner); + } + return result; + } + }; + SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + if (getType(actual) !== "string") { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + if (innerResult.hasFailed()) { + innerResult.recordFailure({ + matcher: this, + path: [], + message: "Encoded JSON value does not match" + }); + } + return innerResult; + } + }; + 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; + } + }; + 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; + } + }; + 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; + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/helpers-internal/index.js +var require_helpers_internal = __commonJS({ + "../../aws-cdk-lib/assertions/lib/helpers-internal/index.js"(exports) { + "use strict"; + var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) { + if (k2 === void 0) + k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { + return m[k]; + } }; + } + Object.defineProperty(o, k2, desc); + } : function(o, m, k, k2) { + if (k2 === void 0) + k2 = k; + o[k2] = m[k]; + }); + var __exportStar = exports && exports.__exportStar || function(m, exports2) { + for (var p in m) + if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) + __createBinding(exports2, m, p); + }; + Object.defineProperty(exports, "__esModule", { value: true }); + __exportStar((init_match(), __toCommonJS(match_exports)), exports); + __exportStar((init_matcher(), __toCommonJS(matcher_exports)), exports); + } +}); + +// 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); + +// lib/assertions/providers/lambda-handler/assertion.ts +var import_helpers_internal = __toESM(require_helpers_internal()); + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var AWS = __toESM(require("aws-sdk")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + /** + * Handles executing the custom resource event. If `stateMachineArn` is present + * in the props then trigger the waiter statemachine + */ + async handle() { + try { + if ("stateMachineArn" in this.event.ResourceProperties) { + const req = { + stateMachineArn: this.event.ResourceProperties.stateMachineArn, + name: this.event.RequestId, + input: JSON.stringify(this.event) + }; + await this.startExecution(req); + return; + } else { + const response = await this.processEvent(this.event.ResourceProperties); + return response; + } + } catch (e) { + console.log(e); + throw e; + } finally { + clearTimeout(this.timeout); + } + } + /** + * Handle async requests from the waiter state machine + */ + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + /** + * Start a step function state machine which will wait for the request + * to be successful. + */ + async startExecution(req) { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { + "content-type": "", + "content-length": Buffer.byteLength(responseBody, "utf8") + } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } finally { + clearTimeout(this.timeout); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + failed: true, + assertion: JSON.stringify({ + status: "fail", + message: matchResult.renderMismatch() + }) + }; + if (request2.failDeployment) { + throw new Error(result.assertion); + } + } else { + result = { + assertion: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + /** + * Return a Matcher that can be tested against the actual results. + * This will convert the encoded matchers into their corresponding + * assertions matcher. + * + * For example: + * + * ExpectedResult.objectLike({ + * Messages: [{ + * Body: Match.objectLike({ + * Elements: Match.arrayWith([{ Asdf: 3 }]), + * Payload: Match.serializedJson({ key: 'value' }), + * }), + * }], + * }); + * + * Will be encoded as: + * { + * $ObjectLike: { + * Messages: [{ + * Body: { + * $ObjectLike: { + * Elements: { + * $ArrayWith: [{ Asdf: 3 }], + * }, + * Payload: { + * $SerializedJson: { key: 'value' } + * } + * }, + * }, + * }], + * }, + * } + * + * Which can then be parsed by this function. For each key (recursively) + * the parser will check if the value has one of the encoded matchers as a key + * and if so, it will set the value as the Matcher. So, + * + * { + * Body: { + * $ObjectLike: { + * Elements: { + * $ArrayWith: [{ Asdf: 3 }], + * }, + * Payload: { + * $SerializedJson: { key: 'value' } + * } + * }, + * }, + * } + * + * Will be converted to + * { + * Body: Match.objectLike({ + * Elements: Match.arrayWith([{ Asdf: 3 }]), + * Payload: Match.serializedJson({ key: 'value' }), + * }), + * } + */ + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return import_helpers_internal.Match.arrayWith(v[nested]); + case "$ObjectLike": + return import_helpers_internal.Match.objectLike(v[nested]); + case "$StringLike": + return import_helpers_internal.Match.stringLikeRegexp(v[nested]); + case "$SerializedJson": + return import_helpers_internal.Match.serializedJson(v[nested]); + default: + return v; + } + }); + if (import_helpers_internal.Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return import_helpers_internal.Match.exact(final.matcher); + } catch { + return import_helpers_internal.Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + if (typeof childKey === "string") { + childKey = isJsonString(childKey); + } + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS2 = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS2.VERSION}`); + if (!Object.prototype.hasOwnProperty.call(AWS2, request2.service)) { + throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS2.VERSION}.`); + } + const service = new AWS2[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + let resp = respond; + if (request2.outputPaths) { + resp = filterKeys(flatData, request2.outputPaths); + } else if (request2.flattenResponse === "true") { + resp = flatData; + } + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +function filterKeys(object, searchStrings) { + return Object.entries(object).reduce((filteredObject, [key, value]) => { + for (const searchString of searchStrings) { + if (key.startsWith(`apiCallResponse.${searchString}`)) { + filteredObject[key] = value; + } + } + return filteredObject; + }, {}); +} +function isJsonString(value) { + try { + return JSON.parse(value); + } catch { + return value; + } +} + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + if (event.RequestType === "Delete") { + await provider.respond({ + status: "SUCCESS", + reason: "OK" + }); + return; + } + const result = await provider.handle(); + if ("stateMachineArn" in event.ResourceProperties) { + console.info('Found "stateMachineArn", waiter statemachine started'); + return; + } else if ("expected" in event.ResourceProperties) { + console.info('Found "expected", testing assertions'); + const actualPath = event.ResourceProperties.actualPath; + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + try { + const assertionResult = await assertion.handle(); + await provider.respond({ + status: "SUCCESS", + reason: "OK", + // return both the result of the API call _and_ the assertion results + data: { + ...assertionResult, + ...result + } + }); + return; + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + return; +} +async function onTimeout(timeoutEvent) { + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + const provider = createResourceHandler(isCompleteRequest, standardContext); + await provider.respond({ + status: "FAILED", + reason: "Operation timed out: " + JSON.stringify(isCompleteRequest) + }); +} +async function isComplete(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + const result = await provider.handleIsComplete(); + const actualPath = event.ResourceProperties.actualPath; + if (result) { + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + if ("expected" in event.ResourceProperties) { + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + const assertionResult = await assertion.handleIsComplete(); + if (!(assertionResult == null ? void 0 : assertionResult.failed)) { + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } else { + console.log(`Assertion Failed: ${JSON.stringify(assertionResult)}`); + throw new Error(JSON.stringify(event)); + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } else { + console.log("No result"); + throw new Error(JSON.stringify(event)); + } + return; + } catch (e) { + console.log(e); + throw new Error(JSON.stringify(event)); + } +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) { + return new AssertionHandler(event, context); + } else { + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +var standardContext = { + getRemainingTimeInMillis: () => 9e4 +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler, + isComplete, + onTimeout +}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea/56450500138e1b4809dbb5aa9bc51ccd1e0bc0044c6a453b40df7e5a17244740 b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea/56450500138e1b4809dbb5aa9bc51ccd1e0bc0044c6a453b40df7e5a17244740 new file mode 100644 index 0000000000000..18331775985bd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/asset.d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea/56450500138e1b4809dbb5aa9bc51ccd1e0bc0044c6a453b40df7e5a17244740 @@ -0,0 +1,2 @@ +substitutionStatus: substitution-successful! +lambdaArn: <> \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/cdk.out new file mode 100644 index 0000000000000..f0b901e7c06e5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"32.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.assets.json new file mode 100644 index 0000000000000..c6ba3e97ca025 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.assets.json @@ -0,0 +1,32 @@ +{ + "version": "32.0.0", + "files": { + "ae370e1010629b78f494346f49ceef3ab2875718f20e6c808114e6aa770c7bf3": { + "source": { + "path": "asset.ae370e1010629b78f494346f49ceef3ab2875718f20e6c808114e6aa770c7bf3.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "ae370e1010629b78f494346f49ceef3ab2875718f20e6c808114e6aa770c7bf3.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "2a7ec8b49a8d816a5987c212136de3dfba4e41f8b77ace9bcbb9cc2c73347483": { + "source": { + "path": "deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2a7ec8b49a8d816a5987c212136de3dfba4e41f8b77ace9bcbb9cc2c73347483.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-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.template.json new file mode 100644 index 0000000000000..b2943ff181406 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.template.json @@ -0,0 +1,158 @@ +{ + "Resources": { + "AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87": { + "Type": "Custom::DeployAssert@SdkCallS3getObject", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "S3", + "api": "getObject", + "expected": { + "Fn::Join": [ + "", + [ + "{\"$StringLike\":\"substitutionStatus: substitution-successful!\\\\nlambdaArn: ", + { + "Fn::ImportValue": "test-s3-deploy-substitution:ExportsOutputFnGetAttHello4A628BD4ArnADD0C428" + }, + "\"}" + ] + ] + }, + "actualPath": "Body", + "parameters": { + "Bucket": { + "Fn::ImportValue": "test-s3-deploy-substitution:ExportsOutputRefsubstitutionbucket13A1BF4A62538792" + }, + "Key": "56450500138e1b4809dbb5aa9bc51ccd1e0bc0044c6a453b40df7e5a17244740" + }, + "flattenResponse": "true", + "outputPaths": [ + "Body" + ], + "salt": "1686868316699" + }, + "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:GetObject" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "ae370e1010629b78f494346f49ceef3ab2875718f20e6c808114e6aa770c7bf3.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87", + "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-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/integ.json new file mode 100644 index 0000000000000..5bfc0d0325797 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "32.0.0", + "testCases": { + "deploy-time-substitution-integ-test/DefaultTest": { + "stacks": [ + "test-s3-deploy-substitution" + ], + "assertionStack": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert", + "assertionStackName": "deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/manifest.json new file mode 100644 index 0000000000000..af397f946aca4 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/manifest.json @@ -0,0 +1,285 @@ +{ + "version": "32.0.0", + "artifacts": { + "my-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "my-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "my-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "my-stack.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}/0f77a7a90c8e1fdcd1155216083fbae45ee243f307fa79cb526beb6e500ed5e9.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "my-stack.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": [ + "my-stack.assets" + ], + "metadata": { + "/my-stack/Hello/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "HelloServiceRole1E55EA16" + } + ], + "/my-stack/Hello/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Hello4A628BD4" + } + ], + "/my-stack/substitution-bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "substitutionbucket13A1BF4A" + } + ], + "/my-stack/Deployment/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DeploymentAwsCliLayerB82B26A3" + } + ], + "/my-stack/Deployment/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "DeploymentCustomResource47E8B2E6" + } + ], + "/my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ], + "/my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF" + } + ], + "/my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536" + } + ], + "/my-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/my-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "my-stack" + }, + "test-s3-deploy-substitution.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "test-s3-deploy-substitution.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "test-s3-deploy-substitution": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "test-s3-deploy-substitution.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}/ef16cc11ee41d704a2e624149adbe9c296a53e5d91d2f55cf3904043d0ba7f03.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "test-s3-deploy-substitution.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": [ + "test-s3-deploy-substitution.assets" + ], + "metadata": { + "/test-s3-deploy-substitution/Hello/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "HelloServiceRole1E55EA16" + } + ], + "/test-s3-deploy-substitution/Hello/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Hello4A628BD4" + } + ], + "/test-s3-deploy-substitution/substitution-bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "substitutionbucket13A1BF4A" + } + ], + "/test-s3-deploy-substitution/Deployment/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DeploymentAwsCliLayerB82B26A3" + } + ], + "/test-s3-deploy-substitution/Deployment/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "DeploymentCustomResource47E8B2E6" + } + ], + "/test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ], + "/test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF" + } + ], + "/test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536" + } + ], + "/test-s3-deploy-substitution/Exports/Output{\"Fn::GetAtt\":[\"Hello4A628BD4\",\"Arn\"]}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputFnGetAttHello4A628BD4ArnADD0C428" + } + ], + "/test-s3-deploy-substitution/Exports/Output{\"Ref\":\"substitutionbucket13A1BF4A\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefsubstitutionbucket13A1BF4A62538792" + } + ], + "/test-s3-deploy-substitution/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/test-s3-deploy-substitution/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "test-s3-deploy-substitution" + }, + "deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.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}/2a7ec8b49a8d816a5987c212136de3dfba4e41f8b77ace9bcbb9cc2c73347483.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.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": [ + "test-s3-deploy-substitution", + "deploytimesubstitutionintegtestDefaultTestDeployAssertCBBB427B.assets" + ], + "metadata": { + "/deploy-time-substitution-integ-test/DefaultTest/DeployAssert/AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87" + } + ], + "/deploy-time-substitution-integ-test/DefaultTest/DeployAssert/AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87" + } + ], + "/deploy-time-substitution-integ-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/deploy-time-substitution-integ-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/deploy-time-substitution-integ-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/deploy-time-substitution-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/my-stack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/my-stack.assets.json new file mode 100644 index 0000000000000..3558efae71aa4 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/my-stack.assets.json @@ -0,0 +1,58 @@ +{ + "version": "32.0.0", + "files": { + "10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83": { + "source": { + "path": "asset.10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd": { + "source": { + "path": "asset.9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea": { + "source": { + "path": "asset.d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "0f77a7a90c8e1fdcd1155216083fbae45ee243f307fa79cb526beb6e500ed5e9": { + "source": { + "path": "my-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "0f77a7a90c8e1fdcd1155216083fbae45ee243f307fa79cb526beb6e500ed5e9.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-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/my-stack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/my-stack.template.json new file mode 100644 index 0000000000000..1fe10d30c73d4 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/my-stack.template.json @@ -0,0 +1,308 @@ +{ + "Resources": { + "HelloServiceRole1E55EA16": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Hello4A628BD4": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function helloCode(_event, _context, callback) {\n return callback(undefined, {\n statusCode: 200,\n body: 'hello, world!',\n });\n}" + }, + "Role": { + "Fn::GetAtt": [ + "HelloServiceRole1E55EA16", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "HelloServiceRole1E55EA16" + ] + }, + "substitutionbucket13A1BF4A": { + "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:cr-owned:222eea1b", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "DeploymentAwsCliLayerB82B26A3": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip" + }, + "Description": "/opt/awscli/aws" + } + }, + "DeploymentCustomResource47E8B2E6": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ], + "SourceObjectKeys": [ + "d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea.zip" + ], + "SourceMarkers": [ + { + "<>": { + "Fn::GetAtt": [ + "Hello4A628BD4", + "Arn" + ] + } + } + ], + "DestinationBucketName": { + "Ref": "substitutionbucket13A1BF4A" + }, + "Extract": true, + "Prune": false + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "substitutionbucket13A1BF4A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "substitutionbucket13A1BF4A", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "Roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip" + }, + "Role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "Environment": { + "Variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "Handler": "index.handler", + "Layers": [ + { + "Ref": "DeploymentAwsCliLayerB82B26A3" + } + ], + "Runtime": "python3.9", + "Timeout": 900 + }, + "DependsOn": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + ] + } + }, + "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-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/test-s3-deploy-substitution.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/test-s3-deploy-substitution.assets.json new file mode 100644 index 0000000000000..51be1bd2e3fff --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/test-s3-deploy-substitution.assets.json @@ -0,0 +1,58 @@ +{ + "version": "32.0.0", + "files": { + "10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83": { + "source": { + "path": "asset.10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd": { + "source": { + "path": "asset.9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea": { + "source": { + "path": "asset.d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "ef16cc11ee41d704a2e624149adbe9c296a53e5d91d2f55cf3904043d0ba7f03": { + "source": { + "path": "test-s3-deploy-substitution.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "ef16cc11ee41d704a2e624149adbe9c296a53e5d91d2f55cf3904043d0ba7f03.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-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/test-s3-deploy-substitution.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/test-s3-deploy-substitution.template.json new file mode 100644 index 0000000000000..2b4b68dd85950 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/test-s3-deploy-substitution.template.json @@ -0,0 +1,329 @@ +{ + "Resources": { + "HelloServiceRole1E55EA16": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Hello4A628BD4": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function helloCode(_event, _context, callback) {\n return callback(undefined, {\n statusCode: 200,\n body: 'hello, world!',\n });\n}" + }, + "Role": { + "Fn::GetAtt": [ + "HelloServiceRole1E55EA16", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "HelloServiceRole1E55EA16" + ] + }, + "substitutionbucket13A1BF4A": { + "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:cr-owned:a95ef649", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "DeploymentAwsCliLayerB82B26A3": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip" + }, + "Description": "/opt/awscli/aws" + } + }, + "DeploymentCustomResource47E8B2E6": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ], + "SourceObjectKeys": [ + "d1ba2cb2e5cfca13267ff70863e782b91873a033a2af81afb07f38cf38ec67ea.zip" + ], + "SourceMarkers": [ + { + "<>": { + "Fn::GetAtt": [ + "Hello4A628BD4", + "Arn" + ] + } + } + ], + "DestinationBucketName": { + "Ref": "substitutionbucket13A1BF4A" + }, + "Extract": true, + "Prune": false + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "substitutionbucket13A1BF4A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "substitutionbucket13A1BF4A", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "Roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip" + }, + "Role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "Environment": { + "Variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "Handler": "index.handler", + "Layers": [ + { + "Ref": "DeploymentAwsCliLayerB82B26A3" + } + ], + "Runtime": "python3.9", + "Timeout": 900 + }, + "DependsOn": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + ] + } + }, + "Outputs": { + "ExportsOutputFnGetAttHello4A628BD4ArnADD0C428": { + "Value": { + "Fn::GetAtt": [ + "Hello4A628BD4", + "Arn" + ] + }, + "Export": { + "Name": "test-s3-deploy-substitution:ExportsOutputFnGetAttHello4A628BD4ArnADD0C428" + } + }, + "ExportsOutputRefsubstitutionbucket13A1BF4A62538792": { + "Value": { + "Ref": "substitutionbucket13A1BF4A" + }, + "Export": { + "Name": "test-s3-deploy-substitution:ExportsOutputRefsubstitutionbucket13A1BF4A62538792" + } + } + }, + "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-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/tree.json new file mode 100644 index 0000000000000..8ca074b54281b --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.js.snapshot/tree.json @@ -0,0 +1,1203 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "my-stack": { + "id": "my-stack", + "path": "my-stack", + "children": { + "Hello": { + "id": "Hello", + "path": "my-stack/Hello", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "my-stack/Hello/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "my-stack/Hello/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "my-stack/Hello/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "my-stack/Hello/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "exports.handler = function helloCode(_event, _context, callback) {\n return callback(undefined, {\n statusCode: 200,\n body: 'hello, world!',\n });\n}" + }, + "role": { + "Fn::GetAtt": [ + "HelloServiceRole1E55EA16", + "Arn" + ] + }, + "handler": "index.handler", + "runtime": "nodejs14.x" + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "substitution-bucket": { + "id": "substitution-bucket", + "path": "my-stack/substitution-bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "my-stack/substitution-bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:cr-owned:222eea1b", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Deployment": { + "id": "Deployment", + "path": "my-stack/Deployment", + "children": { + "AwsCliLayer": { + "id": "AwsCliLayer", + "path": "my-stack/Deployment/AwsCliLayer", + "children": { + "Code": { + "id": "Code", + "path": "my-stack/Deployment/AwsCliLayer/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "my-stack/Deployment/AwsCliLayer/Code/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "my-stack/Deployment/AwsCliLayer/Code/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "my-stack/Deployment/AwsCliLayer/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::LayerVersion", + "aws:cdk:cloudformation:props": { + "content": { + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip" + }, + "description": "/opt/awscli/aws" + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "CustomResourceHandler": { + "id": "CustomResourceHandler", + "path": "my-stack/Deployment/CustomResourceHandler", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Asset1": { + "id": "Asset1", + "path": "my-stack/Deployment/Asset1", + "children": { + "Stage": { + "id": "Stage", + "path": "my-stack/Deployment/Asset1/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "my-stack/Deployment/Asset1/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "CustomResource": { + "id": "CustomResource", + "path": "my-stack/Deployment/CustomResource", + "children": { + "Default": { + "id": "Default", + "path": "my-stack/Deployment/CustomResource/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": { + "id": "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "substitutionbucket13A1BF4A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "substitutionbucket13A1BF4A", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Code": { + "id": "Code", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "my-stack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip" + }, + "role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "environment": { + "variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "handler": "index.handler", + "layers": [ + { + "Ref": "DeploymentAwsCliLayerB82B26A3" + } + ], + "runtime": "python3.9", + "timeout": 900 + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "my-stack/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "my-stack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "test-s3-deploy-substitution": { + "id": "test-s3-deploy-substitution", + "path": "test-s3-deploy-substitution", + "children": { + "Hello": { + "id": "Hello", + "path": "test-s3-deploy-substitution/Hello", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "test-s3-deploy-substitution/Hello/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "test-s3-deploy-substitution/Hello/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "test-s3-deploy-substitution/Hello/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "test-s3-deploy-substitution/Hello/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "exports.handler = function helloCode(_event, _context, callback) {\n return callback(undefined, {\n statusCode: 200,\n body: 'hello, world!',\n });\n}" + }, + "role": { + "Fn::GetAtt": [ + "HelloServiceRole1E55EA16", + "Arn" + ] + }, + "handler": "index.handler", + "runtime": "nodejs14.x" + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "substitution-bucket": { + "id": "substitution-bucket", + "path": "test-s3-deploy-substitution/substitution-bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "test-s3-deploy-substitution/substitution-bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:cr-owned:a95ef649", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Deployment": { + "id": "Deployment", + "path": "test-s3-deploy-substitution/Deployment", + "children": { + "AwsCliLayer": { + "id": "AwsCliLayer", + "path": "test-s3-deploy-substitution/Deployment/AwsCliLayer", + "children": { + "Code": { + "id": "Code", + "path": "test-s3-deploy-substitution/Deployment/AwsCliLayer/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "test-s3-deploy-substitution/Deployment/AwsCliLayer/Code/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "test-s3-deploy-substitution/Deployment/AwsCliLayer/Code/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "test-s3-deploy-substitution/Deployment/AwsCliLayer/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::LayerVersion", + "aws:cdk:cloudformation:props": { + "content": { + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "10972873dd8a3bc33ae1cb91d834617be249fd84bed5bdbb6ffbb581650aea83.zip" + }, + "description": "/opt/awscli/aws" + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "CustomResourceHandler": { + "id": "CustomResourceHandler", + "path": "test-s3-deploy-substitution/Deployment/CustomResourceHandler", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Asset1": { + "id": "Asset1", + "path": "test-s3-deploy-substitution/Deployment/Asset1", + "children": { + "Stage": { + "id": "Stage", + "path": "test-s3-deploy-substitution/Deployment/Asset1/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "test-s3-deploy-substitution/Deployment/Asset1/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "CustomResource": { + "id": "CustomResource", + "path": "test-s3-deploy-substitution/Deployment/CustomResource", + "children": { + "Default": { + "id": "Default", + "path": "test-s3-deploy-substitution/Deployment/CustomResource/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": { + "id": "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "substitutionbucket13A1BF4A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "substitutionbucket13A1BF4A", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Code": { + "id": "Code", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Resource": { + "id": "Resource", + "path": "test-s3-deploy-substitution/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip" + }, + "role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "environment": { + "variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "handler": "index.handler", + "layers": [ + { + "Ref": "DeploymentAwsCliLayerB82B26A3" + } + ], + "runtime": "python3.9", + "timeout": 900 + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Exports": { + "id": "Exports", + "path": "test-s3-deploy-substitution/Exports", + "children": { + "Output{\"Fn::GetAtt\":[\"Hello4A628BD4\",\"Arn\"]}": { + "id": "Output{\"Fn::GetAtt\":[\"Hello4A628BD4\",\"Arn\"]}", + "path": "test-s3-deploy-substitution/Exports/Output{\"Fn::GetAtt\":[\"Hello4A628BD4\",\"Arn\"]}", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Output{\"Ref\":\"substitutionbucket13A1BF4A\"}": { + "id": "Output{\"Ref\":\"substitutionbucket13A1BF4A\"}", + "path": "test-s3-deploy-substitution/Exports/Output{\"Ref\":\"substitutionbucket13A1BF4A\"}", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "test-s3-deploy-substitution/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "test-s3-deploy-substitution/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "deploy-time-substitution-integ-test": { + "id": "deploy-time-substitution-integ-test", + "path": "deploy-time-substitution-integ-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "deploy-time-substitution-integ-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "deploy-time-substitution-integ-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert", + "children": { + "AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87": { + "id": "AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87/Default", + "children": { + "Default": { + "id": "Default", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87/Default/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/AwsApiCallS3getObjectd316deb61071d83221cf46c8ebac0d87/AssertionResults", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Role": { + "id": "Role", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "Handler": { + "id": "Handler", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "deploy-time-substitution-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.ts new file mode 100644 index 0000000000000..a4099532270a5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-substitution.ts @@ -0,0 +1,64 @@ +import * as path from 'path'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as cdk from 'aws-cdk-lib'; +import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests-alpha'; +import { Bucket } from 'aws-cdk-lib/aws-s3'; +import { DeployTimeSubstitutedFile } from 'aws-cdk-lib/aws-s3-deployment'; + +class Test extends cdk.Stack { + public readonly bucketName: String; + public readonly objectKey: String; + public readonly lambdaArn: String; + + constructor(scope: cdk.App, id: string) { + super(scope, id); + + const hello = new lambda.Function(this, 'Hello', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline(`exports.handler = ${helloCode}`), + }); + + const bucket = new Bucket(this, 'substitution-bucket'); + const file = new DeployTimeSubstitutedFile(this, 'Deployment', { + source: path.join(__dirname, 'sample-file.yaml'), + destinationBucket: bucket, + substitutions: { + yyyy: hello.functionArn, + xxxx: 'substitution-successful!', + }, + }); + + this.bucketName = bucket.bucketName; + this.objectKey = file.objectKey; + this.lambdaArn = hello.functionArn; + } +} + +const app = new cdk.App(); +new Test(app, 'my-stack'); + +const testCase = new Test(app, 'test-s3-deploy-substitution'); +const integ = new IntegTest(app, 'deploy-time-substitution-integ-test', { + testCases: [testCase], +}); + +const apiCall = integ.assertions.awsApiCall('S3', 'getObject', { + Bucket: testCase.bucketName, + Key: testCase.objectKey, +}); + +apiCall.provider.addToRolePolicy({ + Effect: 'Allow', + Action: ['s3:GetObject', 's3:ListBucket'], + Resource: ['*'], +}); + +apiCall.assertAtPath('Body', ExpectedResult.stringLikeRegexp(`substitutionStatus: substitution-successful!\\nlambdaArn: ${testCase.lambdaArn}`)); + +function helloCode(_event: any, _context: any, callback: any) { + return callback(undefined, { + statusCode: 200, + body: 'hello, world!', + }); +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/sample-file.yaml b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/sample-file.yaml new file mode 100644 index 0000000000000..78649e1c9fd7a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/sample-file.yaml @@ -0,0 +1,2 @@ +substitutionStatus: {{ xxxx }} +lambdaArn: {{ yyyy }} \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-s3-deployment/README.md b/packages/aws-cdk-lib/aws-s3-deployment/README.md index 78a5cd754414a..abf4b2afa48f8 100644 --- a/packages/aws-cdk-lib/aws-s3-deployment/README.md +++ b/packages/aws-cdk-lib/aws-s3-deployment/README.md @@ -355,6 +355,36 @@ The value in `topic.topicArn` is a deploy-time value. It only gets resolved during deployment by placing a marker in the generated source file and substituting it when its deployed to the destination with the actual value. +### Substitutions from Templated Files + +The `DeployTimeSubstitutedFile` construct allows you to specify substitutions +to make from placeholders in a local file which will be resolved during deployment. This +is especially useful in situations like creating an API from a spec file, where users might +want to reference other CDK resources they have created. + +The syntax for template variables is `{{ variable-name }}` in your local file. Then, you would +specify the substitutions in CDK like this: + +```ts +import * as lambda from 'aws-cdk-lib/aws-lambda'; + +declare const myLambdaFunction: lambda.Function; + +new s3deploy.DeployTimeSubstitutedFile(this, 'MyFile', { + source: 'my-file.yaml', + destinationBucket: destinationBucket, + substitutions: { + variable-name: myLambdaFunction.functionName, + }, +}); +``` + +Nested variables, like `{{ {{ foo }} }}` or `{{ foo {{ bar }} }}`, are not supported by this +construct. In the first case of a single variable being is double nested `{{ {{ foo }} }}`, only +the `{{ foo }}` would be replaced by the substitution, and the extra brackets would remain in the file. +In the second case of two nexted variables `{{ foo {{ bar }} }}`, only the `{{ bar }}` would be replaced +in the file. + ## Keep Files Zipped By default, files are zipped, then extracted into the destination bucket. diff --git a/packages/aws-cdk-lib/aws-s3-deployment/lib/bucket-deployment.ts b/packages/aws-cdk-lib/aws-s3-deployment/lib/bucket-deployment.ts index cfac8ac262fdc..b1efdcf6e7ab4 100644 --- a/packages/aws-cdk-lib/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/aws-cdk-lib/aws-s3-deployment/lib/bucket-deployment.ts @@ -1,7 +1,9 @@ + +import * as fs from 'fs'; import * as path from 'path'; import { kebab as toKebabCase } from 'case'; import { Construct } from 'constructs'; -import { ISource, SourceConfig } from './source'; +import { ISource, SourceConfig, Source } from './source'; import * as cloudfront from '../../aws-cloudfront'; import * as ec2 from '../../aws-ec2'; import * as efs from '../../aws-efs'; @@ -577,6 +579,63 @@ export class BucketDeployment extends Construct { } } +export interface DeployTimeSubstitutedFileProps { + /** + * Path to the user's local file. + */ + readonly source: string; + + /** + * The S3 bucket to sync the contents of the zip file to. + */ + readonly destinationBucket: s3.IBucket; + + /** + * User-defined substitutions to make in the file. + * Placeholders in the user's local file must be specified with double curly + * brackets and spaces. For example, if you use the key 'xxxx' in the file, + * it must be written as: {{ xxxx }} to be recognized by the construct as a + * substitution. + */ + readonly substitutions: { [key: string]: string }; +} + +/** + * `DeployTimeSubstitutedFile` is an extension of `BucketDeployment` that allows users to + * upload individual files and specify to make substitutions in the file. + */ +export class DeployTimeSubstitutedFile extends BucketDeployment { + + public readonly objectKey: string; + + constructor(scope: Construct, id: string, props: DeployTimeSubstitutedFileProps) { + if (!fs.existsSync(props.source)) { + throw new Error(`No file found at 'source' path ${props.source}`); + } + // Makes substitutions on the file + let fileData = fs.readFileSync(props.source, 'utf-8'); + fileData = fileData.replace(/{{\s*(\w+)\s*}}/g, function(match, expr) { + return props.substitutions[expr] ?? match; + }); + + const objectKey = cdk.FileSystem.fingerprint(props.source); + const fileSource = Source.data(objectKey, fileData); + const fullBucketDeploymentProps: BucketDeploymentProps = { + prune: false, + extract: true, + ...props, + sources: [fileSource], + }; + super(scope, id, fullBucketDeploymentProps); + // sets the object key + this.objectKey = objectKey; + } + + public get bucket(): s3.IBucket { + return this.deployedBucket; + } +} + /** * Metadata. * diff --git a/packages/aws-cdk-lib/aws-s3-deployment/test/bucket-deployment.test.ts b/packages/aws-cdk-lib/aws-s3-deployment/test/bucket-deployment.test.ts index aa0cf3fb2645d..803239ba4210b 100644 --- a/packages/aws-cdk-lib/aws-s3-deployment/test/bucket-deployment.test.ts +++ b/packages/aws-cdk-lib/aws-s3-deployment/test/bucket-deployment.test.ts @@ -1500,6 +1500,133 @@ test('if any source has markers then all sources have markers', () => { }); }); +test('DeployTimeSubstitutedFile can be used to add substitutions in a file', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Test'); + const bucket = new s3.Bucket(stack, 'Bucket'); + + const deployment = new s3deploy.DeployTimeSubstitutedFile(stack, 'MyFile', { + source: path.join(__dirname, 'file-substitution-test', 'sample-definition.yaml'), + destinationBucket: bucket, + substitutions: { + testMethod: 'changedTestMethodSuccess', + mock: 'changedMockTypeSuccess', + }, + }); + + const result = app.synth(); + const content = readDataFile(result, deployment.objectKey); + expect(content).not.toContain('testMethod'); + expect(content).toContain('changedTestMethodSuccess'); + expect(content).not.toContain('mock'); + expect(content).toContain('changedMockTypeSuccess'); +}); + +test('DeployTimeSubstitutedFile throws error when source file path is invalid', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Test'); + const bucket = new s3.Bucket(stack, 'Bucket'); + + expect(() => { + new s3deploy.DeployTimeSubstitutedFile(stack, 'MyFile', { + source: path.join(__dirname, 'non-existant-file.yaml'), + destinationBucket: bucket, + substitutions: { + testMethod: 'changedTestMethodSuccess', + mock: 'changedMockTypeSuccess', + }, + }); + }).toThrow(`No file found at 'source' path ${path.join(__dirname, 'non-existant-file.yaml')}`); +}); + +test('DeployTimeSubstitutedFile does not make substitutions when no substitutions are passed in', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Test'); + const bucket = new s3.Bucket(stack, 'Bucket'); + + const originalFileData = readFileSync(path.join(__dirname, 'file-substitution-test', 'sample-definition.yaml'), 'utf8'); + + const deployment = new s3deploy.DeployTimeSubstitutedFile(stack, 'MyFile', { + source: path.join(__dirname, 'file-substitution-test', 'sample-definition.yaml'), + destinationBucket: bucket, + substitutions: { }, + }); + + const result = app.synth(); + const assetFileFromOutput = readDataFile(result, deployment.objectKey); + expect(originalFileData).toStrictEqual(assetFileFromOutput); +}); + +test('DeployTimeSubstitutedFile does not substitute nested variables', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Test'); + const bucket = new s3.Bucket(stack, 'Bucket'); + + const originalFileData = readFileSync(path.join(__dirname, 'file-substitution-test', 'sample-definition-nested-vars.yaml'), 'utf8'); + + const deployment = new s3deploy.DeployTimeSubstitutedFile(stack, 'MyFile', { + source: path.join(__dirname, 'file-substitution-test', 'sample-definition-nested-vars.yaml'), + destinationBucket: bucket, + substitutions: { + foo: 'replacement1', + bar: 'replacement2', + }, + }); + + const result = app.synth(); + const assetFileFromOutput = readDataFile(result, deployment.objectKey); + expect(originalFileData).not.toStrictEqual(assetFileFromOutput); + expect(assetFileFromOutput).toContain('{{ replacement1 }}'); + expect(assetFileFromOutput).toContain('{{ foo replacement2 }}'); +}); + +test('DeployTimeSubstitutedFile does not substitute nested variables', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Test'); + const bucket = new s3.Bucket(stack, 'Bucket'); + + const originalFileData = readFileSync(path.join(__dirname, 'file-substitution-test', 'sample-definition-nested-vars.yaml'), 'utf8'); + + const deployment = new s3deploy.DeployTimeSubstitutedFile(stack, 'MyFile', { + source: path.join(__dirname, 'file-substitution-test', 'sample-definition-nested-vars.yaml'), + destinationBucket: bucket, + substitutions: { + foo: 'replacement1', + bar: 'replacement2', + }, + }); + + const result = app.synth(); + const assetFileFromOutput = readDataFile(result, deployment.objectKey); + expect(originalFileData).not.toStrictEqual(assetFileFromOutput); + expect(assetFileFromOutput).toContain('{{ replacement1 }}'); + expect(assetFileFromOutput).toContain('{{ foo replacement2 }}'); +}); + +test('DeployTimeSubstitutedFile does not double substitute already replaced variables', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Test'); + const bucket = new s3.Bucket(stack, 'Bucket'); + + const originalFileData = readFileSync(path.join(__dirname, 'file-substitution-test', 'sample-definition.yaml'), 'utf8'); + + const deployment = new s3deploy.DeployTimeSubstitutedFile(stack, 'MyFile', { + source: path.join(__dirname, 'file-substitution-test', 'sample-definition.yaml'), + destinationBucket: bucket, + substitutions: { + foo: 'bar', + bar: 'zee', + }, + }); + + const result = app.synth(); + const assetFileFromOutput = readDataFile(result, deployment.objectKey); + expect(originalFileData).not.toStrictEqual(assetFileFromOutput); + expect(assetFileFromOutput).toContain('bar'); + expect(assetFileFromOutput).not.toContain('foo'); + expect(assetFileFromOutput).not.toContain('zee'); +}); + function readDataFile(casm: cxapi.CloudAssembly, relativePath: string): string { const assetDirs = readdirSync(casm.directory).filter(f => f.startsWith('asset.')); for (const dir of assetDirs) { diff --git a/packages/aws-cdk-lib/aws-s3-deployment/test/file-substitution-test/sample-definition-nested-vars.yaml b/packages/aws-cdk-lib/aws-s3-deployment/test/file-substitution-test/sample-definition-nested-vars.yaml new file mode 100644 index 0000000000000..dbc826649a80c --- /dev/null +++ b/packages/aws-cdk-lib/aws-s3-deployment/test/file-substitution-test/sample-definition-nested-vars.yaml @@ -0,0 +1,30 @@ +openapi: "3.0.2" +info: + version: 1.0.0 + title: Test API for CDK +paths: + /pets: + get: + summary: Test Method + operationId: {{ {{ foo }} }} + responses: + "200": + description: A paged array of pets + content: + application/json: + schema: + $ref: "#/components/schemas/Empty" + x-amazon-apigateway-integration: + responses: + default: + statusCode: "200" + requestTemplates: + application/json: "{\"statusCode\": 200}" + passthroughBehavior: when_no_match + type: {{ foo {{ bar }} }} + +components: + schemas: + Empty: + title: Empty Schema + type: object \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-s3-deployment/test/file-substitution-test/sample-definition.yaml b/packages/aws-cdk-lib/aws-s3-deployment/test/file-substitution-test/sample-definition.yaml new file mode 100644 index 0000000000000..2f570aef1571a --- /dev/null +++ b/packages/aws-cdk-lib/aws-s3-deployment/test/file-substitution-test/sample-definition.yaml @@ -0,0 +1,30 @@ +openapi: "3.0.2" +info: + version: 1.0.0 + title: Test API for CDK +paths: + /pets: + get: + summary: Test Method + operationId: {{ testMethod }} + responses: + "200": + description: A paged array of pets + content: + application/json: + schema: + $ref: "#/components/schemas/Empty" + x-amazon-apigateway-integration: + responses: + default: + statusCode: "200" + requestTemplates: + application/json: "{\"statusCode\": 200}" + passthroughBehavior: when_no_match + type: {{ mock }} + +components: + schemas: + Empty: + title: {{ foo }} + type: {{ {{ foo }} }} \ No newline at end of file