diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts index f323110eecfa4..609cf50d297c6 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts @@ -102,6 +102,12 @@ integTest('cdk synth', withDefaultFixture(async (fixture) => { }, })); + expect(await fixture.cdkSynth({ + options: [fixture.fullStackName('test-1')], + })).not.toEqual(expect.stringContaining(` +Rules: + CheckBootstrapVersion:`)); + await fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false }); expect(fixture.template('test-2')).toEqual(expect.objectContaining({ Resources: { diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/MyStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/MyStack.assets.json new file mode 100644 index 0000000000000..5a02d75ebe385 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/MyStack.assets.json @@ -0,0 +1,58 @@ +{ + "version": "36.0.5", + "files": { + "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961": { + "source": { + "path": "asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c": { + "source": { + "path": "asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8": { + "source": { + "path": "asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "e18944d0be1b04c77ec42f6036bf87609348dbd787c375da85e7bdbd979094a1": { + "source": { + "path": "MyStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "e18944d0be1b04c77ec42f6036bf87609348dbd787c375da85e7bdbd979094a1.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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/MyStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/MyStack.template.json new file mode 100644 index 0000000000000..3be885e7b3501 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/MyStack.template.json @@ -0,0 +1,263 @@ +{ + "Resources": { + "WebsiteBucket75C24D94": { + "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:cr-owned:156aa6de", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "LogGroupF5B46931": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 3653 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "s3deployAwsCliLayerD0CD1E6B": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip" + }, + "Description": "/opt/awscli/aws" + } + }, + "s3deployCustomResourceDB97D82D": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ], + "SourceObjectKeys": [ + "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8.zip" + ], + "SourceMarkers": [ + {} + ], + "DestinationBucketName": { + "Ref": "WebsiteBucket75C24D94" + }, + "Prune": true + }, + "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": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "WebsiteBucket75C24D94", + "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": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip" + }, + "Environment": { + "Variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "Handler": "index.handler", + "Layers": [ + { + "Ref": "s3deployAwsCliLayerD0CD1E6B" + } + ], + "LoggingConfig": { + "LogGroup": { + "Ref": "LogGroupF5B46931" + } + }, + "Role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py new file mode 100644 index 0000000000000..e4d3920e40c02 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py @@ -0,0 +1,336 @@ +import contextlib +import json +import logging +import os +import shutil +import subprocess +import tempfile +import urllib.parse +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): + if message: + logger.error("| cfn_error: %s" % message.encode()) + 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" % sanitize_message(s3_dest)) + logger.info("| old_s3_dest: %s" % sanitize_message(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)) + +#--------------------------------------------------------------------------------------------------- +# Sanitize the message to mitigate CWE-117 and CWE-93 vulnerabilities +def sanitize_message(message): + if not message: + return message + + # Sanitize the message to prevent log injection and HTTP response splitting + sanitized_message = message.replace('\n', '').replace('\r', '') + + # Encode the message to handle special characters + encoded_message = urllib.parse.quote(sanitized_message) + + return encoded_message + +#--------------------------------------------------------------------------------------------------- +# 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) + \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip new file mode 100644 index 0000000000000..f624b92c63849 Binary files /dev/null and b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip differ diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json new file mode 100644 index 0000000000000..7f228f99e61ae --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json @@ -0,0 +1 @@ +{"a":"b"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/cdk.out new file mode 100644 index 0000000000000..bd5311dc372de --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.5"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integ.json new file mode 100644 index 0000000000000..5a82b067ed84c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "36.0.5", + "testCases": { + "integ-test-custom-resource-config-logGroup/DefaultTest": { + "stacks": [ + "MyStack" + ], + "diffAssets": false, + "assertionStack": "integ-test-custom-resource-config-logGroup/DefaultTest/DeployAssert", + "assertionStackName": "integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.assets.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.assets.json new file mode 100644 index 0000000000000..a229a89acb5f6 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.5", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.template.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.template.json @@ -0,0 +1,36 @@ +{ + "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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/manifest.json new file mode 100644 index 0000000000000..912db8404779d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/manifest.json @@ -0,0 +1,161 @@ +{ + "version": "36.0.5", + "artifacts": { + "MyStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "MyStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "MyStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "MyStack.template.json", + "terminationProtection": false, + "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}/e18944d0be1b04c77ec42f6036bf87609348dbd787c375da85e7bdbd979094a1.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "MyStack.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": [ + "MyStack.assets" + ], + "metadata": { + "/MyStack/WebsiteBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebsiteBucket75C24D94" + } + ], + "/MyStack/LogGroup": [ + { + "type": "aws:cdk:is-custom-resource-handler-logGroup", + "data": true + } + ], + "/MyStack/LogGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogGroupF5B46931" + } + ], + "/MyStack/s3deploy/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "s3deployAwsCliLayerD0CD1E6B" + } + ], + "/MyStack/s3deploy/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "s3deployCustomResourceDB97D82D" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": [ + { + "type": "aws:cdk:is-custom-resource-handler-singleton", + "data": true + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536" + } + ], + "/MyStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/MyStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "MyStack" + }, + "integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.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": [ + "integtestcustomresourceconfiglogGroupDefaultTestDeployAssert04698011.assets" + ], + "metadata": { + "/integ-test-custom-resource-config-logGroup/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-test-custom-resource-config-logGroup/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-test-custom-resource-config-logGroup/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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/tree.json new file mode 100644 index 0000000000000..77ee7d7819bff --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.js.snapshot/tree.json @@ -0,0 +1,520 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "MyStack": { + "id": "MyStack", + "path": "MyStack", + "children": { + "WebsiteBucket": { + "id": "WebsiteBucket", + "path": "MyStack/WebsiteBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/WebsiteBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:cr-owned:156aa6de", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.Bucket", + "version": "0.0.0" + } + }, + "LogGroup": { + "id": "LogGroup", + "path": "MyStack/LogGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/LogGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": { + "retentionInDays": 7 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.LogGroup", + "version": "0.0.0" + } + }, + "s3deploy": { + "id": "s3deploy", + "path": "MyStack/s3deploy", + "children": { + "AwsCliLayer": { + "id": "AwsCliLayer", + "path": "MyStack/s3deploy/AwsCliLayer", + "children": { + "Code": { + "id": "Code", + "path": "MyStack/s3deploy/AwsCliLayer/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/s3deploy/AwsCliLayer/Code/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/s3deploy/AwsCliLayer/Code/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/s3deploy/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": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip" + }, + "description": "/opt/awscli/aws" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnLayerVersion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.lambda_layer_awscli.AwsCliLayer", + "version": "0.0.0" + } + }, + "CustomResourceHandler": { + "id": "CustomResourceHandler", + "path": "MyStack/s3deploy/CustomResourceHandler", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.SingletonFunction", + "version": "0.0.0" + } + }, + "Asset1": { + "id": "Asset1", + "path": "MyStack/s3deploy/Asset1", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/s3deploy/Asset1/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/s3deploy/Asset1/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, + "CustomResource": { + "id": "CustomResource", + "path": "MyStack/s3deploy/CustomResource", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/s3deploy/CustomResource/Default", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_deployment.BucketDeployment", + "version": "0.0.0" + } + }, + "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": { + "id": "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/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": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/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": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Code": { + "id": "Code", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/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": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip" + }, + "environment": { + "variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "handler": "index.handler", + "layers": [ + { + "Ref": "s3deployAwsCliLayerD0CD1E6B" + } + ], + "loggingConfig": { + "logGroup": { + "Ref": "LogGroupF5B46931" + } + }, + "role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "runtime": "python3.9", + "timeout": 900 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "MyStack/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "MyStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "integ-test-custom-resource-config-logGroup": { + "id": "integ-test-custom-resource-config-logGroup", + "path": "integ-test-custom-resource-config-logGroup", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integ-test-custom-resource-config-logGroup/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "integ-test-custom-resource-config-logGroup/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "integ-test-custom-resource-config-logGroup/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-test-custom-resource-config-logGroup/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-test-custom-resource-config-logGroup/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "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.3.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.ts b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.ts new file mode 100644 index 0000000000000..0b728a6940a6a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logGroup.ts @@ -0,0 +1,27 @@ +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; +import { CustomResourceConfig } from 'aws-cdk-lib/custom-resources'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'MyStack'); + +let websiteBucket = new s3.Bucket(stack, 'WebsiteBucket', {}); + +new s3deploy.BucketDeployment(stack, 's3deploy', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket, + logGroup: new logs.LogGroup(stack, 'LogGroup', { + retention: logs.RetentionDays.ONE_WEEK, + }), +}); + +const logRetentionDays = logs.RetentionDays.TEN_YEARS; +CustomResourceConfig.of(app).addLogRetentionLifetime(logRetentionDays); + +new integ.IntegTest(app, 'integ-test-custom-resource-config-logGroup', { + testCases: [stack], + diffAssets: false, +}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/MyStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/MyStack.assets.json new file mode 100644 index 0000000000000..42d1f634bcab0 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/MyStack.assets.json @@ -0,0 +1,71 @@ +{ + "version": "36.0.5", + "files": { + "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961": { + "source": { + "path": "asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c": { + "source": { + "path": "asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "e4afb15788ec44ed9ff3377e1d131ba2768d7b2e2931bc000d1f2005879b3035": { + "source": { + "path": "asset.e4afb15788ec44ed9ff3377e1d131ba2768d7b2e2931bc000d1f2005879b3035", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "e4afb15788ec44ed9ff3377e1d131ba2768d7b2e2931bc000d1f2005879b3035.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8": { + "source": { + "path": "asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "21b4d2ecb7821580fa7275de5e71e79a3134f1d31c696d13a198c4357127c530": { + "source": { + "path": "MyStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21b4d2ecb7821580fa7275de5e71e79a3134f1d31c696d13a198c4357127c530.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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/MyStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/MyStack.template.json new file mode 100644 index 0000000000000..c0b4fe3d0766f --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/MyStack.template.json @@ -0,0 +1,501 @@ +{ + "Resources": { + "WebsiteBucket75C24D94": { + "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:cr-owned:156aa6de", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "s3deployAwsCliLayerD0CD1E6B": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip" + }, + "Description": "/opt/awscli/aws" + } + }, + "s3deployCustomResourceDB97D82D": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ], + "SourceObjectKeys": [ + "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8.zip" + ], + "SourceMarkers": [ + {} + ], + "DestinationBucketName": { + "Ref": "WebsiteBucket75C24D94" + }, + "Prune": true + }, + "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": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "WebsiteBucket75C24D94", + "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": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip" + }, + "Environment": { + "Variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "Handler": "index.handler", + "Layers": [ + { + "Ref": "s3deployAwsCliLayerD0CD1E6B" + } + ], + "LoggingConfig": { + "LogGroup": { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756ClogGroupD6937F08" + } + }, + "Role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "Runtime": "python3.9", + "Timeout": 900 + }, + "DependsOn": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + ] + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756ClogGroupD6937F08": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 3653 + }, + "DependsOn": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + ], + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CLogRetention1948627D": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/lambda/", + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536" + } + ] + ] + }, + "RetentionInDays": 3653 + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB": { + "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" + ] + ] + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:DeleteRetentionPolicy", + "logs:PutRetentionPolicy" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "Roles": [ + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Runtime": { + "Fn::FindInMap": [ + "LatestNodeRuntimeMap", + { + "Ref": "AWS::Region" + }, + "value" + ] + }, + "Timeout": 900, + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "e4afb15788ec44ed9ff3377e1d131ba2768d7b2e2931bc000d1f2005879b3035.zip" + }, + "Role": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB", + "Arn" + ] + } + }, + "DependsOn": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + ] + } + }, + "Mappings": { + "LatestNodeRuntimeMap": { + "af-south-1": { + "value": "nodejs20.x" + }, + "ap-east-1": { + "value": "nodejs20.x" + }, + "ap-northeast-1": { + "value": "nodejs20.x" + }, + "ap-northeast-2": { + "value": "nodejs20.x" + }, + "ap-northeast-3": { + "value": "nodejs20.x" + }, + "ap-south-1": { + "value": "nodejs20.x" + }, + "ap-south-2": { + "value": "nodejs20.x" + }, + "ap-southeast-1": { + "value": "nodejs20.x" + }, + "ap-southeast-2": { + "value": "nodejs20.x" + }, + "ap-southeast-3": { + "value": "nodejs20.x" + }, + "ap-southeast-4": { + "value": "nodejs20.x" + }, + "ap-southeast-5": { + "value": "nodejs20.x" + }, + "ap-southeast-7": { + "value": "nodejs20.x" + }, + "ca-central-1": { + "value": "nodejs20.x" + }, + "ca-west-1": { + "value": "nodejs20.x" + }, + "cn-north-1": { + "value": "nodejs18.x" + }, + "cn-northwest-1": { + "value": "nodejs18.x" + }, + "eu-central-1": { + "value": "nodejs20.x" + }, + "eu-central-2": { + "value": "nodejs20.x" + }, + "eu-isoe-west-1": { + "value": "nodejs18.x" + }, + "eu-north-1": { + "value": "nodejs20.x" + }, + "eu-south-1": { + "value": "nodejs20.x" + }, + "eu-south-2": { + "value": "nodejs20.x" + }, + "eu-west-1": { + "value": "nodejs20.x" + }, + "eu-west-2": { + "value": "nodejs20.x" + }, + "eu-west-3": { + "value": "nodejs20.x" + }, + "il-central-1": { + "value": "nodejs20.x" + }, + "me-central-1": { + "value": "nodejs20.x" + }, + "me-south-1": { + "value": "nodejs20.x" + }, + "mx-central-1": { + "value": "nodejs20.x" + }, + "sa-east-1": { + "value": "nodejs20.x" + }, + "us-east-1": { + "value": "nodejs20.x" + }, + "us-east-2": { + "value": "nodejs20.x" + }, + "us-gov-east-1": { + "value": "nodejs18.x" + }, + "us-gov-west-1": { + "value": "nodejs18.x" + }, + "us-iso-east-1": { + "value": "nodejs18.x" + }, + "us-iso-west-1": { + "value": "nodejs18.x" + }, + "us-isob-east-1": { + "value": "nodejs18.x" + }, + "us-west-1": { + "value": "nodejs20.x" + }, + "us-west-2": { + "value": "nodejs20.x" + } + } + }, + "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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py new file mode 100644 index 0000000000000..e4d3920e40c02 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py @@ -0,0 +1,336 @@ +import contextlib +import json +import logging +import os +import shutil +import subprocess +import tempfile +import urllib.parse +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): + if message: + logger.error("| cfn_error: %s" % message.encode()) + 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" % sanitize_message(s3_dest)) + logger.info("| old_s3_dest: %s" % sanitize_message(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)) + +#--------------------------------------------------------------------------------------------------- +# Sanitize the message to mitigate CWE-117 and CWE-93 vulnerabilities +def sanitize_message(message): + if not message: + return message + + # Sanitize the message to prevent log injection and HTTP response splitting + sanitized_message = message.replace('\n', '').replace('\r', '') + + # Encode the message to handle special characters + encoded_message = urllib.parse.quote(sanitized_message) + + return encoded_message + +#--------------------------------------------------------------------------------------------------- +# 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) + \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip new file mode 100644 index 0000000000000..f624b92c63849 Binary files /dev/null and b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip differ diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json new file mode 100644 index 0000000000000..7f228f99e61ae --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json @@ -0,0 +1 @@ +{"a":"b"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.e4afb15788ec44ed9ff3377e1d131ba2768d7b2e2931bc000d1f2005879b3035/index.js b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.e4afb15788ec44ed9ff3377e1d131ba2768d7b2e2931bc000d1f2005879b3035/index.js new file mode 100644 index 0000000000000..ae6165a46ea1e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/asset.e4afb15788ec44ed9ff3377e1d131ba2768d7b2e2931bc000d1f2005879b3035/index.js @@ -0,0 +1 @@ +"use strict";var h=Object.create;var d=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var C=Object.getPrototypeOf,P=Object.prototype.hasOwnProperty;var b=(e,o)=>{for(var n in o)d(e,n,{get:o[n],enumerable:!0})},p=(e,o,n,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of f(o))!P.call(e,r)&&r!==n&&d(e,r,{get:()=>o[r],enumerable:!(t=w(o,r))||t.enumerable});return e};var S=(e,o,n)=>(n=e!=null?h(C(e)):{},p(o||!e||!e.__esModule?d(n,"default",{value:e,enumerable:!0}):n,e)),G=e=>p(d({},"__esModule",{value:!0}),e);var q={};b(q,{handler:()=>E});module.exports=G(q);var i=S(require("@aws-sdk/client-cloudwatch-logs"));async function R(e,o,n){await n(async()=>{try{let t={logGroupName:e},r=new i.CreateLogGroupCommand(t);await o.send(r)}catch(t){if(t.name==="ResourceAlreadyExistsException")return;throw t}})}async function x(e,o,n){await n(async()=>{try{let t={logGroupName:e},r=new i.DeleteLogGroupCommand(t);await o.send(r)}catch(t){if(t.name==="ResourceNotFoundException")return;throw t}})}async function y(e,o,n,t){await n(async()=>{if(t){let r={logGroupName:e,retentionInDays:t},s=new i.PutRetentionPolicyCommand(r);await o.send(s)}else{let r={logGroupName:e},s=new i.DeleteRetentionPolicyCommand(r);await o.send(s)}})}async function E(e,o){try{console.log(JSON.stringify({...e,ResponseURL:"..."}));let t=e.ResourceProperties.LogGroupName,r=e.ResourceProperties.LogGroupRegion,s=L(e.ResourceProperties.SdkRetry?.maxRetries)??5,a=I(s),m={logger:console,region:r,maxAttempts:Math.max(5,s)},c=new i.CloudWatchLogsClient(m);if((e.RequestType==="Create"||e.RequestType==="Update")&&(await R(t,c,a),await y(t,c,a,L(e.ResourceProperties.RetentionInDays)),e.RequestType==="Create")){let g=new i.CloudWatchLogsClient({logger:console,region:process.env.AWS_REGION});await R(`/aws/lambda/${o.functionName}`,g,a),await y(`/aws/lambda/${o.functionName}`,g,a,1)}e.RequestType==="Delete"&&e.ResourceProperties.RemovalPolicy==="destroy"&&await x(t,c,a),await n("SUCCESS","OK",t)}catch(t){console.log(t),await n("FAILED",t.message,e.ResourceProperties.LogGroupName)}function n(t,r,s){let a=JSON.stringify({Status:t,Reason:r,PhysicalResourceId:s,StackId:e.StackId,RequestId:e.RequestId,LogicalResourceId:e.LogicalResourceId,Data:{LogGroupName:e.ResourceProperties.LogGroupName}});console.log("Responding",a);let m=require("url").parse(e.ResponseURL),c={hostname:m.hostname,path:m.path,method:"PUT",headers:{"content-type":"","content-length":Buffer.byteLength(a,"utf8")}};return new Promise((g,l)=>{try{let u=require("https").request(c,g);u.on("error",l),u.write(a),u.end()}catch(u){l(u)}})}}function L(e,o=10){if(e!==void 0)return parseInt(e,o)}function I(e,o=100,n=10*1e3){return async t=>{let r=0;do try{return await t()}catch(s){if(s.name==="OperationAbortedException"||s.name==="ThrottlingException")if(rsetTimeout(a,k(r,o,n)));continue}else throw new Error("Out of attempts to change log group");throw s}while(!0)}}function k(e,o,n){return Math.round(Math.random()*Math.min(n,o*2**e))}0&&(module.exports={handler}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/cdk.out new file mode 100644 index 0000000000000..bd5311dc372de --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.5"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integ.json new file mode 100644 index 0000000000000..99fa9f80212ca --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "36.0.5", + "testCases": { + "integ-test-custom-resource-config-logRetention/DefaultTest": { + "stacks": [ + "MyStack" + ], + "diffAssets": false, + "assertionStack": "integ-test-custom-resource-config-logRetention/DefaultTest/DeployAssert", + "assertionStackName": "integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.assets.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.assets.json new file mode 100644 index 0000000000000..7cafdede4642c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.5", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.template.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.template.json @@ -0,0 +1,36 @@ +{ + "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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/manifest.json new file mode 100644 index 0000000000000..358e3816cd639 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/manifest.json @@ -0,0 +1,197 @@ +{ + "version": "36.0.5", + "artifacts": { + "MyStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "MyStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "MyStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "MyStack.template.json", + "terminationProtection": false, + "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}/21b4d2ecb7821580fa7275de5e71e79a3134f1d31c696d13a198c4357127c530.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "MyStack.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": [ + "MyStack.assets" + ], + "metadata": { + "/MyStack/WebsiteBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebsiteBucket75C24D94" + } + ], + "/MyStack/s3deploy/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "s3deployAwsCliLayerD0CD1E6B" + } + ], + "/MyStack/s3deploy/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "s3deployCustomResourceDB97D82D" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": [ + { + "type": "aws:cdk:is-custom-resource-handler-singleton", + "data": true + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource/logGroup": [ + { + "type": "aws:cdk:is-custom-resource-handler-logGroup", + "data": true + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource/logGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756ClogGroupD6937F08" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/LogRetention": [ + { + "type": "aws:cdk:is-custom-resource-handler-logRetention", + "data": true + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/LogRetention/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CLogRetention1948627D" + } + ], + "/MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ], + "/MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB" + } + ], + "/MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A" + } + ], + "/MyStack/LatestNodeRuntimeMap": [ + { + "type": "aws:cdk:logicalId", + "data": "LatestNodeRuntimeMap" + } + ], + "/MyStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/MyStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "MyStack" + }, + "integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.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": [ + "integtestcustomresourceconfiglogRetentionDefaultTestDeployAssert2A59B769.assets" + ], + "metadata": { + "/integ-test-custom-resource-config-logRetention/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-test-custom-resource-config-logRetention/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-test-custom-resource-config-logRetention/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/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/tree.json new file mode 100644 index 0000000000000..ef2ff66cf017a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.js.snapshot/tree.json @@ -0,0 +1,694 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "MyStack": { + "id": "MyStack", + "path": "MyStack", + "children": { + "WebsiteBucket": { + "id": "WebsiteBucket", + "path": "MyStack/WebsiteBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/WebsiteBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:cr-owned:156aa6de", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.Bucket", + "version": "0.0.0" + } + }, + "s3deploy": { + "id": "s3deploy", + "path": "MyStack/s3deploy", + "children": { + "AwsCliLayer": { + "id": "AwsCliLayer", + "path": "MyStack/s3deploy/AwsCliLayer", + "children": { + "Code": { + "id": "Code", + "path": "MyStack/s3deploy/AwsCliLayer/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/s3deploy/AwsCliLayer/Code/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/s3deploy/AwsCliLayer/Code/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/s3deploy/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": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip" + }, + "description": "/opt/awscli/aws" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnLayerVersion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.lambda_layer_awscli.AwsCliLayer", + "version": "0.0.0" + } + }, + "CustomResourceHandler": { + "id": "CustomResourceHandler", + "path": "MyStack/s3deploy/CustomResourceHandler", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.SingletonFunction", + "version": "0.0.0" + } + }, + "Asset1": { + "id": "Asset1", + "path": "MyStack/s3deploy/Asset1", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/s3deploy/Asset1/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/s3deploy/Asset1/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, + "CustomResource": { + "id": "CustomResource", + "path": "MyStack/s3deploy/CustomResource", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/s3deploy/CustomResource/Default", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_deployment.BucketDeployment", + "version": "0.0.0" + } + }, + "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": { + "id": "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/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": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/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": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Code": { + "id": "Code", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource", + "children": { + "logGroup": { + "id": "logGroup", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource/logGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource/logGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": { + "retentionInDays": 3653 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.LogGroup", + "version": "0.0.0" + } + } + }, + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip" + }, + "environment": { + "variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "handler": "index.handler", + "layers": [ + { + "Ref": "s3deployAwsCliLayerD0CD1E6B" + } + ], + "role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "runtime": "python3.9", + "timeout": 900 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + }, + "LogRetention": { + "id": "LogRetention", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/LogRetention", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/LogRetention/Resource", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.LogRetention", + "version": "0.0.0" + } + }, + "LogGroup": { + "id": "LogGroup", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/LogGroup", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a": { + "id": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a", + "children": { + "Code": { + "id": "Code", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, + "ServiceRole": { + "id": "ServiceRole", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "logs:DeleteRetentionPolicy", + "logs:PutRetentionPolicy" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "policyName": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "roles": [ + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Resource", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "LatestNodeRuntimeMap": { + "id": "LatestNodeRuntimeMap", + "path": "MyStack/LatestNodeRuntimeMap", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnMapping", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "MyStack/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "MyStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "integ-test-custom-resource-config-logRetention": { + "id": "integ-test-custom-resource-config-logRetention", + "path": "integ-test-custom-resource-config-logRetention", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integ-test-custom-resource-config-logRetention/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "integ-test-custom-resource-config-logRetention/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "integ-test-custom-resource-config-logRetention/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-test-custom-resource-config-logRetention/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-test-custom-resource-config-logRetention/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "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.3.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.ts b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.ts new file mode 100644 index 0000000000000..83cdfa62a31bc --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-logRetention.ts @@ -0,0 +1,25 @@ +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; +import { CustomResourceConfig } from 'aws-cdk-lib/custom-resources'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'MyStack'); + +let websiteBucket = new s3.Bucket(stack, 'WebsiteBucket', {}); +new s3deploy.BucketDeployment(stack, 's3deploy', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket, + logRetention: logs.RetentionDays.ONE_WEEK, +}); + +const logRetentionDays = logs.RetentionDays.TEN_YEARS; +CustomResourceConfig.of(app).addLogRetentionLifetime(logRetentionDays); + +new integ.IntegTest(app, 'integ-test-custom-resource-config-logRetention', { + testCases: [stack], + diffAssets: false, +}); + diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/MyStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/MyStack.assets.json new file mode 100644 index 0000000000000..5bb13f8a8f326 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/MyStack.assets.json @@ -0,0 +1,58 @@ +{ + "version": "36.0.0", + "files": { + "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961": { + "source": { + "path": "asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c": { + "source": { + "path": "asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8": { + "source": { + "path": "asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "ce0a6bec1c9e10557d8436e3c5dc0e5f06542681dd67981a5fdc4daa2d9475ae": { + "source": { + "path": "MyStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "ce0a6bec1c9e10557d8436e3c5dc0e5f06542681dd67981a5fdc4daa2d9475ae.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/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/MyStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/MyStack.template.json new file mode 100644 index 0000000000000..5bfc4c6a2e5b7 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/MyStack.template.json @@ -0,0 +1,267 @@ +{ + "Resources": { + "WebsiteBucket75C24D94": { + "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:cr-owned:c19d3033", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "s3deployNoneAwsCliLayer8E653504": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip" + }, + "Description": "/opt/awscli/aws" + } + }, + "s3deployNoneCustomResource0EA66D66": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + } + ], + "SourceObjectKeys": [ + "561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8.zip" + ], + "SourceMarkers": [ + {} + ], + "DestinationBucketName": { + "Ref": "WebsiteBucket75C24D94" + }, + "Prune": true + }, + "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": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "WebsiteBucket75C24D94", + "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": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip" + }, + "Environment": { + "Variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "Handler": "index.handler", + "Layers": [ + { + "Ref": "s3deployNoneAwsCliLayer8E653504" + } + ], + "LoggingConfig": { + "LogGroup": { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756ClogGroupD6937F08" + } + }, + "Role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "Runtime": "python3.9", + "Timeout": 900 + }, + "DependsOn": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + ] + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756ClogGroupD6937F08": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 3653 + }, + "DependsOn": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + ], + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "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/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py new file mode 100644 index 0000000000000..e4d3920e40c02 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c/index.py @@ -0,0 +1,336 @@ +import contextlib +import json +import logging +import os +import shutil +import subprocess +import tempfile +import urllib.parse +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): + if message: + logger.error("| cfn_error: %s" % message.encode()) + 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" % sanitize_message(s3_dest)) + logger.info("| old_s3_dest: %s" % sanitize_message(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)) + +#--------------------------------------------------------------------------------------------------- +# Sanitize the message to mitigate CWE-117 and CWE-93 vulnerabilities +def sanitize_message(message): + if not message: + return message + + # Sanitize the message to prevent log injection and HTTP response splitting + sanitized_message = message.replace('\n', '').replace('\r', '') + + # Encode the message to handle special characters + encoded_message = urllib.parse.quote(sanitized_message) + + return encoded_message + +#--------------------------------------------------------------------------------------------------- +# 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) + \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip new file mode 100644 index 0000000000000..f624b92c63849 Binary files /dev/null and b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip differ diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json new file mode 100644 index 0000000000000..7f228f99e61ae --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/asset.561a8b95d6d62d87513e3607a2de271376251555ee83cf7b93534b0e85c500c8/file.json @@ -0,0 +1 @@ +{"a":"b"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integ.json new file mode 100644 index 0000000000000..fd6c4260d6b28 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "36.0.0", + "testCases": { + "integ-test-custom-resource-config-undefined-log/DefaultTest": { + "stacks": [ + "MyStack" + ], + "diffAssets": false, + "assertionStack": "integ-test-custom-resource-config-undefined-log/DefaultTest/DeployAssert", + "assertionStackName": "integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.assets.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.assets.json new file mode 100644 index 0000000000000..ff46d58217c91 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.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/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.template.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.template.json @@ -0,0 +1,36 @@ +{ + "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/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/manifest.json new file mode 100644 index 0000000000000..6eb7867c0c4e9 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/manifest.json @@ -0,0 +1,161 @@ +{ + "version": "36.0.0", + "artifacts": { + "MyStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "MyStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "MyStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "MyStack.template.json", + "terminationProtection": false, + "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}/ce0a6bec1c9e10557d8436e3c5dc0e5f06542681dd67981a5fdc4daa2d9475ae.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "MyStack.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": [ + "MyStack.assets" + ], + "metadata": { + "/MyStack/WebsiteBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WebsiteBucket75C24D94" + } + ], + "/MyStack/s3deployNone/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "s3deployNoneAwsCliLayer8E653504" + } + ], + "/MyStack/s3deployNone/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "s3deployNoneCustomResource0EA66D66" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": [ + { + "type": "aws:cdk:is-custom-resource-handler-singleton", + "data": true + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536" + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource/logGroup": [ + { + "type": "aws:cdk:is-custom-resource-handler-logGroup", + "data": true + } + ], + "/MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource/logGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756ClogGroupD6937F08" + } + ], + "/MyStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/MyStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "MyStack" + }, + "integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.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": [ + "integtestcustomresourceconfigundefinedlogDefaultTestDeployAssertE1DBC36D.assets" + ], + "metadata": { + "/integ-test-custom-resource-config-undefined-log/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-test-custom-resource-config-undefined-log/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-test-custom-resource-config-undefined-log/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/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/tree.json new file mode 100644 index 0000000000000..352aac9ca4313 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.js.snapshot/tree.json @@ -0,0 +1,517 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "MyStack": { + "id": "MyStack", + "path": "MyStack", + "children": { + "WebsiteBucket": { + "id": "WebsiteBucket", + "path": "MyStack/WebsiteBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/WebsiteBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "aws-cdk:cr-owned:c19d3033", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "s3deployNone": { + "id": "s3deployNone", + "path": "MyStack/s3deployNone", + "children": { + "AwsCliLayer": { + "id": "AwsCliLayer", + "path": "MyStack/s3deployNone/AwsCliLayer", + "children": { + "Code": { + "id": "Code", + "path": "MyStack/s3deployNone/AwsCliLayer/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/s3deployNone/AwsCliLayer/Code/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/s3deployNone/AwsCliLayer/Code/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/s3deployNone/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": "3322b7049fb0ed2b7cbb644a2ada8d1116ff80c32dca89e6ada846b5de26f961.zip" + }, + "description": "/opt/awscli/aws" + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CustomResourceHandler": { + "id": "CustomResourceHandler", + "path": "MyStack/s3deployNone/CustomResourceHandler", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Asset1": { + "id": "Asset1", + "path": "MyStack/s3deployNone/Asset1", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/s3deployNone/Asset1/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/s3deployNone/Asset1/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CustomResource": { + "id": "CustomResource", + "path": "MyStack/s3deployNone/CustomResource", + "children": { + "Default": { + "id": "Default", + "path": "MyStack/s3deployNone/CustomResource/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": { + "id": "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/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.3.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/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": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "WebsiteBucket75C24D94", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Code": { + "id": "Code", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/Stage", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/AssetBucket", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Resource": { + "id": "Resource", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource", + "children": { + "logGroup": { + "id": "logGroup", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource/logGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "MyStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource/logGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": { + "retentionInDays": 3653 + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "0158f40002a8c211635388a87874fd4dcc3d68f525fe08a0fe0f014069ae539c.zip" + }, + "environment": { + "variables": { + "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" + } + }, + "handler": "index.handler", + "layers": [ + { + "Ref": "s3deployNoneAwsCliLayer8E653504" + } + ], + "role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "runtime": "python3.9", + "timeout": 900 + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "MyStack/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "MyStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "integ-test-custom-resource-config-undefined-log": { + "id": "integ-test-custom-resource-config-undefined-log", + "path": "integ-test-custom-resource-config-undefined-log", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integ-test-custom-resource-config-undefined-log/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "integ-test-custom-resource-config-undefined-log/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "integ-test-custom-resource-config-undefined-log/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-test-custom-resource-config-undefined-log/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-test-custom-resource-config-undefined-log/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "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.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.ts b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.ts new file mode 100644 index 0000000000000..ef5c21e693273 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/custom-resources/test/custom-resource-config/integ.custom-resource-config-undefined-log.ts @@ -0,0 +1,24 @@ +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; +import { CustomResourceConfig } from 'aws-cdk-lib/custom-resources'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'MyStack'); + +let websiteBucket = new s3.Bucket(stack, 'WebsiteBucket', {}); +new s3deploy.BucketDeployment(stack, 's3deployNone', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket, +}); + +const logRetentionDays = logs.RetentionDays.TEN_YEARS; +CustomResourceConfig.of(app).addLogRetentionLifetime(logRetentionDays); + +new integ.IntegTest(app, 'integ-test-custom-resource-config-undefined-log', { + testCases: [stack], + diffAssets: false, +}); + diff --git a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts index bee5e57cb3f35..eee0e8c12e467 100644 --- a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts +++ b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts @@ -14,8 +14,15 @@ import { Expression, ClassSpec, $T, + Statement, } from '@cdklabs/typewriter'; -import { Runtime } from './config'; +import { + Runtime, + CUSTOM_RESOURCE_PROVIDER, + CUSTOM_RESOURCE_SINGLETON, + CUSTOM_RESOURCE_SINGLETON_LOG_GROUP, + CUSTOM_RESOURCE_SINGLETON_LOG_RETENTION, + } from './config'; import { HandlerFrameworkModule } from './framework'; import { PATH_MODULE, @@ -56,6 +63,13 @@ interface ConstructorBuildProps { * @default MemberVisbility.Public */ readonly constructorVisbility?: MemberVisibility; + + /** + * These statements are added to the constructor body in the order they appear in this property. + * + * @default undefined + */ + readonly statements?: Statement[]; } /** @@ -206,10 +220,17 @@ export abstract class HandlerFrameworkClass extends ClassType { ['handler', expr.lit(props.handler)], ['runtime', this.buildRuntimeProperty(scope, { runtime: props.runtime, isEvalNodejsProvider })], ]); + const metadataStatements: Statement[] = [ + expr.directCode(`this.addMetadata('${CUSTOM_RESOURCE_SINGLETON}', true)`), + expr.directCode(`if (props?.logGroup) { this.logGroup.node.addMetadata('${CUSTOM_RESOURCE_SINGLETON_LOG_GROUP}', true) }`), + // We need to access the private `_logRetention` custom resource, the only public property - `logGroup` - provides an ARN reference to the resource, instead of the resource itself. + expr.directCode(`if (props?.logRetention) { ((this as any).lambdaFunction as lambda.Function)._logRetention?.node.addMetadata('${CUSTOM_RESOURCE_SINGLETON_LOG_RETENTION}', true) }`), + ]; this.buildConstructor({ constructorPropsType: _interface.type, superProps, constructorVisbility: MemberVisibility.Public, + statements: metadataStatements, }); } })(); @@ -310,11 +331,13 @@ export abstract class HandlerFrameworkClass extends ClassType { isCustomResourceProvider: true, })], ]); + const metadataStatements: Statement[] = [expr.directCode(`this.node.addMetadata('${CUSTOM_RESOURCE_PROVIDER}', true)`)]; this.buildConstructor({ constructorPropsType: CORE_MODULE.CustomResourceProviderOptions, superProps, constructorVisbility: MemberVisibility.Private, optionalConstructorProps: true, + statements: metadataStatements, }); } })(); @@ -359,6 +382,11 @@ export abstract class HandlerFrameworkClass extends ClassType { const superInitializerArgs: Expression[] = [scope, id, props.superProps]; init.addBody(new SuperInitializer(...superInitializerArgs)); + if (props.statements){ + for (const statement of props.statements) { + init.addBody(statement); + } + } } private buildRuntimeProperty(scope: HandlerFrameworkModule, options: BuildRuntimePropertyOptions = {}) { diff --git a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/config.ts b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/config.ts index 41f18c6d6a1aa..7dd71a88fb595 100644 --- a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/config.ts +++ b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/config.ts @@ -367,3 +367,9 @@ export const config: HandlerFrameworkConfig = { ], }, }; + +/* This is duplicated in aws-cdk-lib/custom-resources/lib/custom-resource-config/custom-resource-config.ts */ +export const CUSTOM_RESOURCE_PROVIDER = 'aws:cdk:is-custom-resource-handler-customResourceProvider'; +export const CUSTOM_RESOURCE_SINGLETON = 'aws:cdk:is-custom-resource-handler-singleton'; +export const CUSTOM_RESOURCE_SINGLETON_LOG_GROUP = 'aws:cdk:is-custom-resource-handler-logGroup'; +export const CUSTOM_RESOURCE_SINGLETON_LOG_RETENTION = 'aws:cdk:is-custom-resource-handler-logRetention'; diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts index 7d233b55451bd..942d465ea35bd 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts @@ -28,5 +28,6 @@ export class TestProvider extends CustomResourceProviderBase { "codeDirectory": path.join(__dirname, 'my-handler'), "runtimeName": determineLatestNodeRuntimeName(scope) }); + this.node.addMetadata('aws:cdk:is-custom-resource-handler-customResourceProvider', true); } } \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts index 8c45d66c40e93..6168a528fb5e9 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts @@ -27,5 +27,6 @@ export class TestProvider extends CustomResourceProviderBase { "codeDirectory": path.join(__dirname, 'my-handler'), "runtimeName": determineLatestNodeRuntimeName(scope) }); + this.node.addMetadata('aws:cdk:is-custom-resource-handler-customResourceProvider', true); } } \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/singleton-function.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/singleton-function.ts index 6f5f5e4897fa6..2830ce24110ac 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/singleton-function.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/singleton-function.ts @@ -11,6 +11,9 @@ export class TestSingletonFunction extends lambda.SingletonFunction { "handler": "index.handler", "runtime": lambda.determineLatestNodeRuntime(scope) }); + this.addMetadata('aws:cdk:is-custom-resource-handler-singleton', true); + if (props?.logGroup) { this.logGroup.node.addMetadata('aws:cdk:is-custom-resource-handler-logGroup', true) }; + if (props?.logRetention) { ((this as any).lambdaFunction as lambda.Function)._logRetention?.node.addMetadata('aws:cdk:is-custom-resource-handler-logRetention', true) }; } } diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts index 46b3b7bdf532c..adb473ddac2de 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts @@ -28,5 +28,6 @@ export class TestProvider extends CustomResourceProviderBase { "codeDirectory": path.join(__dirname, 'my-handler'), "runtimeName": "python3.10" }); + this.node.addMetadata('aws:cdk:is-custom-resource-handler-customResourceProvider', true); } } \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts index 0ab4377ec0ebd..f16d83520d172 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts @@ -27,5 +27,6 @@ export class TestProvider extends CustomResourceProviderBase { "codeDirectory": path.join(__dirname, 'my-handler'), "runtimeName": "python3.10" }); + this.node.addMetadata('aws:cdk:is-custom-resource-handler-customResourceProvider', true); } } \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/singleton-function.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/singleton-function.ts index c8326cbeece52..bd16b8208c24c 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/singleton-function.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/singleton-function.ts @@ -11,6 +11,9 @@ export class TestSingletonFunction extends lambda.SingletonFunction { "handler": "index.handler", "runtime": lambda.Runtime.PYTHON_3_10 }); + this.addMetadata('aws:cdk:is-custom-resource-handler-singleton', true); + if (props?.logGroup) { this.logGroup.node.addMetadata('aws:cdk:is-custom-resource-handler-logGroup', true) }; + if (props?.logRetention) { ((this as any).lambdaFunction as lambda.Function)._logRetention?.node.addMetadata('aws:cdk:is-custom-resource-handler-logRetention', true) }; } } diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/singleton-function-eval-nodejs.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/singleton-function-eval-nodejs.ts index a5f790c300641..d8f29a94c6e4b 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/singleton-function-eval-nodejs.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/singleton-function-eval-nodejs.ts @@ -11,6 +11,9 @@ export class EvalNodejsSingletonFunction extends lambda.SingletonFunction { "handler": "index.handler", "runtime": (props.runtime ? props.runtime : lambda.determineLatestNodeRuntime(scope)) }); + this.addMetadata('aws:cdk:is-custom-resource-handler-singleton', true); + if (props?.logGroup) { this.logGroup.node.addMetadata('aws:cdk:is-custom-resource-handler-logGroup', true) }; + if (props?.logRetention) { ((this as any).lambdaFunction as lambda.Function)._logRetention?.node.addMetadata('aws:cdk:is-custom-resource-handler-logRetention', true) }; } } diff --git a/packages/aws-cdk-lib/aws-lambda/lib/function.ts b/packages/aws-cdk-lib/aws-lambda/lib/function.ts index 7d15500da259c..2f804ca0d4f06 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/function.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/function.ts @@ -861,6 +861,9 @@ export class Function extends FunctionBase { /** @internal */ public readonly _layers: ILayerVersion[] = []; + /** @internal */ + public _logRetention?: logs.LogRetention; + private _logGroup?: logs.ILogGroup; /** @@ -913,6 +916,14 @@ export class Function extends FunctionBase { // add additional managed policies when necessary if (props.filesystem) { const config = props.filesystem.config; + if (!Token.isUnresolved(config.localMountPath)) { + if (!/^\/mnt\/[a-zA-Z0-9-_.]+$/.test(config.localMountPath)) { + throw new Error(`Local mount path should match with ^/mnt/[a-zA-Z0-9-_.]+$ but given ${config.localMountPath}.`); + } + if (config.localMountPath.length > 160) { + throw new Error(`Local mount path can not be longer than 160 characters but has ${config.localMountPath.length} characters.`); + } + } if (config.policies) { config.policies.forEach(p => { this.role?.addToPrincipalPolicy(p); @@ -1072,6 +1083,7 @@ export class Function extends FunctionBase { logRetentionRetryOptions: props.logRetentionRetryOptions as logs.LogRetentionRetryOptions, }); this._logGroup = logs.LogGroup.fromLogGroupArn(this, 'LogGroup', logRetention.logGroupArn); + this._logRetention = logRetention; } props.code.bindToResource(resource); diff --git a/packages/aws-cdk-lib/aws-lambda/lib/singleton-lambda.ts b/packages/aws-cdk-lib/aws-lambda/lib/singleton-lambda.ts index 7c1d0ee0b48de..197e7e005d985 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/singleton-lambda.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/singleton-lambda.ts @@ -1,4 +1,4 @@ -import { Construct, IConstruct, IDependable, Node } from 'constructs'; +import { Construct, IConstruct, IDependable, Node, MetadataOptions } from 'constructs'; import { Architecture } from './architecture'; import { Function as LambdaFunction, FunctionProps, EnvironmentOptions } from './function'; import { FunctionBase } from './function-base'; @@ -155,6 +155,14 @@ export class SingletonFunction extends FunctionBase { this.lambdaFunction.node.addDependency(...up); } + /** + * Use this method to write to the construct tree. + * The metadata entries are written to the Cloud Assembly Manifest if the `treeMetadata` property is specified in the props of the App that contains this Construct. + */ + public addMetadata(type: string, data: any, options?: MetadataOptions) { + this.lambdaFunction.node.addMetadata(type, data, options); + } + /** * The SingletonFunction construct cannot be added as a dependency of another construct using * node.addDependency(). Use this method instead to declare this as a dependency of another construct. diff --git a/packages/aws-cdk-lib/aws-lambda/test/function.test.ts b/packages/aws-cdk-lib/aws-lambda/test/function.test.ts index c5228bb2cd82c..a6d982ddf5bdf 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/function.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/function.test.ts @@ -2923,6 +2923,99 @@ describe('function', () => { }); }); + test('validate localMountPath format when mounting efs', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + const securityGroup = new ec2.SecurityGroup(stack, 'LambdaSG', { + vpc, + allowAllOutbound: false, + }); + + const fs = new efs.FileSystem(stack, 'Efs', { + vpc, + }); + const accessPoint = fs.addAccessPoint('AccessPoint'); + + // THEN + expect(() => { + new lambda.Function(stack, 'MyFunction', { + vpc, + handler: 'foo', + securityGroups: [securityGroup], + runtime: lambda.Runtime.NODEJS_LATEST, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, '/not-mnt/foo-bar'), + }); + }).toThrow('Local mount path should match with ^/mnt/[a-zA-Z0-9-_.]+$ but given /not-mnt/foo-bar'); + }); + + test('validate localMountPath length when mounting efs', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + const securityGroup = new ec2.SecurityGroup(stack, 'LambdaSG', { + vpc, + allowAllOutbound: false, + }); + + const fs = new efs.FileSystem(stack, 'Efs', { + vpc, + }); + const accessPoint = fs.addAccessPoint('AccessPoint'); + + // THEN + expect(() => { + new lambda.Function(stack, 'MyFunction', { + vpc, + handler: 'foo', + securityGroups: [securityGroup], + runtime: lambda.Runtime.NODEJS_LATEST, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, `/mnt/${'a'.repeat(160)}`), + }); + }).toThrow('Local mount path can not be longer than 160 characters but has 165 characters'); + }); + + test('No error when local mount path is Tokenized and Unresolved', () => { + // GIVEN + const realLocalMountPath = '/not-mnt/foo-bar'; + const tokenizedLocalMountPath = cdk.Token.asString(new cdk.Intrinsic(realLocalMountPath)); + + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + const securityGroup = new ec2.SecurityGroup(stack, 'LambdaSG', { + vpc, + allowAllOutbound: false, + }); + + const fs = new efs.FileSystem(stack, 'Efs', { + vpc, + }); + const accessPoint = fs.addAccessPoint('AccessPoint'); + + // THEN + expect(() => { + new lambda.Function(stack, 'MyFunction', { + vpc, + handler: 'foo', + securityGroups: [securityGroup], + runtime: lambda.Runtime.NODEJS_LATEST, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, tokenizedLocalMountPath), + }); + }).not.toThrow(); + }); + test('correct security group is created when deployed in separate stacks', () => { const app = new cdk.App(); diff --git a/packages/aws-cdk-lib/custom-resources/README.md b/packages/aws-cdk-lib/custom-resources/README.md index d1ab7c5a87d05..fec9d7ca1df23 100644 --- a/packages/aws-cdk-lib/custom-resources/README.md +++ b/packages/aws-cdk-lib/custom-resources/README.md @@ -824,3 +824,83 @@ new cr.AwsCustomResource(this, 'CrossAccount', { })]), }); ``` + +#### Custom Resource Config + +You can configure every CDK-vended custom resource in a given scope with `CustomResourceConfig`. + +Note that `CustomResourceConfig` uses Aspects to modify your constructs. There is no guarantee in the order in which Aspects modify the construct tree, which means that adding the same Aspect more than once to a given scope produces undefined behavior. This example guarantees that every affected resource will have a log retention of ten years or one day, but you cannot know which: +CustomResourceConfig.of(App).addLogRetentionLifetime(logs.RetentionDays.TEN_YEARS); +CustomResourceConfig.of(App).addLogRetentionLifetime(logs.RetentionDays.ONE_DAY); + +The following example configures every custom resource in this CDK app to retain its logs for ten years: + +```ts +import * as cdk from 'aws-cdk-lib'; +import { CustomResourceConfig } from 'aws-cdk-lib/custom-resources'; +import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; + +const app = new cdk.App(); +CustomResourceConfig.of(app).addLogRetentionLifetime(logs.RetentionDays.TEN_YEARS); +const stack = new cdk.Stack(app, 'Stack'); + +let websiteBucket = new s3.Bucket(stack, 'WebsiteBucket', {}); +new s3deploy.BucketDeployment(stack, 's3deploy', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket, +}); +``` + +The following example configures every custom resource in two top-level stacks to retain its log for ten years: +```ts +import * as cdk from 'aws-cdk-lib'; +import { CustomResourceConfig } from 'aws-cdk-lib/custom-resources'; +import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; + +const app = new cdk.App(); +CustomResourceConfig.of(app).addLogRetentionLifetime(logs.RetentionDays.TEN_YEARS); + +const stackA = new cdk.Stack(app, 'stackA'); +let websiteBucketA = new s3.Bucket(stackA, "WebsiteBucketA", {}); +new s3deploy.BucketDeployment(stackA, "s3deployA", { + sources: [s3deploy.Source.jsonData("file.json", { a: "b" })], + destinationBucket: websiteBucketA, + logRetention: logs.RetentionDays.ONE_DAY, // overridden by the `TEN_YEARS` set by `CustomResourceConfig`. +}); + +const stackB = new cdk.Stack(app, 'stackB'); +let websiteBucketB = new s3.Bucket(stackB, "WebsiteBucketB", {}); +new s3deploy.BucketDeployment(stackB, "s3deployB", { + sources: [s3deploy.Source.jsonData("file.json", { a: "b" })], + destinationBucket: websiteBucketB, + logRetention: logs.RetentionDays.ONE_DAY, // overridden by the `TEN_YEARS` set by `CustomResourceConfig`. +}); + +``` + +This also applies to nested stacks: +```ts +import * as cdk from 'aws-cdk-lib'; +import { CustomResourceConfig } from 'aws-cdk-lib/custom-resources'; +import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'Stack'); +CustomResourceConfig.of(app).addLogRetentionLifetime(logs.RetentionDays.TEN_YEARS); + +const nestedStackA = new cdk.NestedStack(stack, 'NestedStackA'); +let websiteBucketA = new s3.Bucket(nestedStackA, "WebsiteBucketA", {}); +new s3deploy.BucketDeployment(nestedStackA, "s3deployA", { + sources: [s3deploy.Source.jsonData("file.json", { a: "b" })], + destinationBucket: websiteBucketA, + logRetention: logs.RetentionDays.ONE_DAY, // overridden by the `TEN_YEARS` set by `CustomResourceConfig`. +}); + +const nestedStackB = new cdk.NestedStack(stack, 'NestedStackB'); +let websiteBucketB = new s3.Bucket(nestedStackB, "WebsiteBucketB", {}); +new s3deploy.BucketDeployment(nestedStackB, "s3deployB", { + sources: [s3deploy.Source.jsonData("file.json", { a: "b" })], + destinationBucket: websiteBucketB, + logRetention: logs.RetentionDays.ONE_DAY, // overridden by the `TEN_YEARS` set by `CustomResourceConfig`. +}); +``` diff --git a/packages/aws-cdk-lib/custom-resources/lib/custom-resource-config/custom-resource-config.ts b/packages/aws-cdk-lib/custom-resources/lib/custom-resource-config/custom-resource-config.ts new file mode 100644 index 0000000000000..40bea2713ff0a --- /dev/null +++ b/packages/aws-cdk-lib/custom-resources/lib/custom-resource-config/custom-resource-config.ts @@ -0,0 +1,81 @@ +import { IConstruct, MetadataEntry } from 'constructs'; +import * as cloudformation from '../../../aws-cloudformation'; +import * as lambda from '../../../aws-lambda'; +import * as logs from '../../../aws-logs'; +import { IAspect, Aspects } from '../../../core/lib'; + +/* This is duplicated in @aws-cdk/custom-resource-handlers/lib/custom-resources-framework/config.ts */ +export const CUSTOM_RESOURCE_PROVIDER = 'aws:cdk:is-custom-resource-handler-customResourceProvider'; +export const CUSTOM_RESOURCE_SINGLETON = 'aws:cdk:is-custom-resource-handler-singleton'; +export const CUSTOM_RESOURCE_SINGLETON_LOG_GROUP = 'aws:cdk:is-custom-resource-handler-logGroup'; +export const CUSTOM_RESOURCE_SINGLETON_LOG_RETENTION = 'aws:cdk:is-custom-resource-handler-logRetention'; + +/** + * Manages AWS vended Custom Resources + */ +export class CustomResourceConfig { + /** + * Returns the CustomResourceConfig for this scope. + */ + public static of(scope: IConstruct): CustomResourceConfig { + return new CustomResourceConfig(scope); + } + + private constructor(private readonly scope: IConstruct) { } + + /** + * Set the log retention of AWS-vended custom resource lambdas. + */ + public addLogRetentionLifetime(rentention: logs.RetentionDays) { + Aspects.of(this.scope).add(new CustomResourceLogRetention(rentention)); + } + +} + +/** + * Manages log retention for AWS vended custom resources. + */ +export class CustomResourceLogRetention implements IAspect { + private readonly logRetention: logs.RetentionDays; + + constructor(setLogRetention: logs.RetentionDays) { + this.logRetention = setLogRetention; + } + visit(node: IConstruct) { + for (const metadataEntry of node.node.metadata as MetadataEntry[]) { + if (metadataEntry.type == CUSTOM_RESOURCE_SINGLETON_LOG_GROUP) { + const localNode = node.node.defaultChild as logs.CfnLogGroup; + localNode.addPropertyOverride('RetentionInDays', this.logRetention); + } + + if (metadataEntry.type == CUSTOM_RESOURCE_SINGLETON) { + const localNode = node.node.defaultChild as lambda.CfnFunction; + + if (localNode && !localNode.loggingConfig) { + const newLogGroup = this.createLogGroup(localNode); + localNode.addPropertyOverride('LoggingConfig', { + LogGroup: newLogGroup.logGroupName, + }); + } + } + + if (metadataEntry.type == CUSTOM_RESOURCE_SINGLETON_LOG_RETENTION) { + let localNode = node.node.defaultChild as cloudformation.CfnCustomResource; + localNode.addPropertyOverride('RetentionInDays', this.logRetention); + } + } + } + + /* + * Creates a new logGroup and associates with the singletonLambda + * Returns a Cloudwatch LogGroup + */ + private createLogGroup(scope: lambda.CfnFunction): logs.ILogGroup { + const newLogGroup = new logs.LogGroup(scope, 'logGroup', { + retention: this.logRetention, + }); + newLogGroup.node.addMetadata(`${CUSTOM_RESOURCE_SINGLETON_LOG_GROUP}`, true); + return newLogGroup; + } +} + diff --git a/packages/aws-cdk-lib/custom-resources/lib/custom-resource-config/index.ts b/packages/aws-cdk-lib/custom-resources/lib/custom-resource-config/index.ts new file mode 100644 index 0000000000000..7958f673ee697 --- /dev/null +++ b/packages/aws-cdk-lib/custom-resources/lib/custom-resource-config/index.ts @@ -0,0 +1 @@ +export * from './custom-resource-config'; \ No newline at end of file diff --git a/packages/aws-cdk-lib/custom-resources/lib/index.ts b/packages/aws-cdk-lib/custom-resources/lib/index.ts index b3e6ae4cfb7d5..5b230377f70bb 100644 --- a/packages/aws-cdk-lib/custom-resources/lib/index.ts +++ b/packages/aws-cdk-lib/custom-resources/lib/index.ts @@ -1,2 +1,3 @@ export * from './aws-custom-resource'; -export * from './provider-framework'; \ No newline at end of file +export * from './provider-framework'; +export * from './custom-resource-config'; \ No newline at end of file diff --git a/packages/aws-cdk-lib/custom-resources/test/custom-resource-config/custom-resource-config.test.ts b/packages/aws-cdk-lib/custom-resources/test/custom-resource-config/custom-resource-config.test.ts new file mode 100644 index 0000000000000..17225b585c9c3 --- /dev/null +++ b/packages/aws-cdk-lib/custom-resources/test/custom-resource-config/custom-resource-config.test.ts @@ -0,0 +1,202 @@ +import { Template } from '../../../assertions'; +import * as logs from '../../../aws-logs'; +import * as s3 from '../../../aws-s3'; +import * as s3deploy from '../../../aws-s3-deployment'; +import * as cdk from '../../../core'; +import { CustomResourceConfig } from '../../lib/custom-resource-config/custom-resource-config'; + +describe('when a singleton-backed custom resource does not have logging defined', () => { + test('addLogRetentionLifetime creates a new log group with the correct retention period if one does not already exist', () => { + // GIVEN + const customResourceLogRetention = logs.RetentionDays.TEN_YEARS; + const app = new cdk.App(); + const stack = new cdk.Stack(app); + let websiteBucket = new s3.Bucket(stack, 'WebsiteBucket', {}); + new s3deploy.BucketDeployment(stack, 'BucketDeployment', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket, + }); + + // WHEN + CustomResourceConfig.of(app).addLogRetentionLifetime(customResourceLogRetention); + + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Lambda::Function', { + LoggingConfig: { + LogGroup: { + Ref: 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756ClogGroupD6937F08', + }, + }, + }); + template.resourceCountIs('AWS::Logs::LogGroup', 1); + template.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: customResourceLogRetention, + }); + }); + + test('addLogRetentionLifetime only modifies custom resource log groups', () => { + // GIVEN + const customResourceLogRetention = logs.RetentionDays.TEN_YEARS; + const nonCustomResourceLogRetention = logs.RetentionDays.TWO_YEARS; + const app = new cdk.App(); + const stack = new cdk.Stack(app); + new logs.LogGroup(stack, 'ignored', {}); + let websiteBucket = new s3.Bucket(stack, 'WebsiteBucket', {}); + new s3deploy.BucketDeployment(stack, 'BucketDeployment', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket, + }); + + // WHEN + CustomResourceConfig.of(app).addLogRetentionLifetime(customResourceLogRetention); + + // THEN + const template = Template.fromStack(stack); + template.resourceCountIs('AWS::Logs::LogGroup', 2); + template.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: customResourceLogRetention, + }); + template.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: nonCustomResourceLogRetention, + }); + }); +}); + +describe('when a singleton-backed custom resource logRetention is specified', () => { + test('addLogRetentionLifetime overrides log retention', () => { + // GIVEN + const customResourceLogRetention = logs.RetentionDays.TEN_YEARS; + const app = new cdk.App(); + const stack = new cdk.Stack(app); + const websiteBucket = new s3.Bucket(stack, 'WebsiteBucket', {}); + new s3deploy.BucketDeployment(stack, 'BucketDeployment', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket, + logRetention: logs.RetentionDays.ONE_WEEK, + }); + + // WHEN + CustomResourceConfig.of(app).addLogRetentionLifetime(customResourceLogRetention); + + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('Custom::LogRetention', { + RetentionInDays: customResourceLogRetention, + }); + template.resourceCountIs('AWS::Logs::LogGroup', 1); + template.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: customResourceLogRetention, + }); + }); +}); + +describe('when a singleton-backed custom resource log group is specified', () => { + test('addLogRetentionLifetime modifies the retention period of a singleton-backed custom resource log group.', () => { + // GIVEN + const customResourceLogRetention = logs.RetentionDays.TEN_YEARS; + const app = new cdk.App(); + const stack = new cdk.Stack(app); + const websiteBucket = new s3.Bucket(stack, 'WebsiteBucket', {}); + new s3deploy.BucketDeployment(stack, 'BucketDeployment', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket, + logGroup: new logs.LogGroup(stack, 'LogGroup', { + retention: logs.RetentionDays.ONE_WEEK, + }), + }); + + // WHEN + CustomResourceConfig.of(app).addLogRetentionLifetime(customResourceLogRetention); + + // THEN + const template = Template.fromStack(stack); + template.resourceCountIs('AWS::Logs::LogGroup', 1); + template.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: customResourceLogRetention, + }); + }); +}); + +test('addLogRetentionLifetime modifies the retention period of the custom resources in two top-level stacks', () => { + // GIVEN + const customResourceLogRetention = logs.RetentionDays.TEN_YEARS; + const app = new cdk.App(); + const stack1 = new cdk.Stack(app, 'stack1'); + let websiteBucket1 = new s3.Bucket(stack1, 'WebsiteBucket1', {}); + new s3deploy.BucketDeployment(stack1, 'BucketDeployment1', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket1, + logRetention: logs.RetentionDays.ONE_DAY, + }); + const stack2 = new cdk.Stack(app, 'stack2'); + let websiteBucket2 = new s3.Bucket(stack2, 'WebsiteBucket2', {}); + new s3deploy.BucketDeployment(stack2, 'BucketDeployment2', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucket2, + logRetention: logs.RetentionDays.ONE_DAY, + }); + + // WHEN + CustomResourceConfig.of(app).addLogRetentionLifetime(customResourceLogRetention); + + // THEN + const template1 = Template.fromStack(stack1); + template1.hasResourceProperties('Custom::LogRetention', { + RetentionInDays: customResourceLogRetention, + }); + template1.resourceCountIs('AWS::Logs::LogGroup', 1); + template1.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: customResourceLogRetention, + }); + const template2 = Template.fromStack(stack2); + template2.hasResourceProperties('Custom::LogRetention', { + RetentionInDays: customResourceLogRetention, + }); + template2.resourceCountIs('AWS::Logs::LogGroup', 1); + template2.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: customResourceLogRetention, + }); +}); + +test('addLogRetentionLifetime modifies the retention period of the custom resources in the nested stack', () => { + // GIVEN + const customResourceLogRetention = logs.RetentionDays.TEN_YEARS; + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const nestedStack1 = new cdk.NestedStack(stack, 'nestedStack1'); + let websiteBucketA = new s3.Bucket(nestedStack1, 'WebsiteBucketA', {}); + new s3deploy.BucketDeployment(nestedStack1, 's3deployA', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucketA, + logRetention: logs.RetentionDays.ONE_DAY, + }); + const nestedStack2 = new cdk.NestedStack(stack, 'nestedStack2'); + let websiteBucketB = new s3.Bucket(nestedStack2, 'WebsiteBucketB', {}); + new s3deploy.BucketDeployment(nestedStack2, 's3deployB', { + sources: [s3deploy.Source.jsonData('file.json', { a: 'b' })], + destinationBucket: websiteBucketB, + logRetention: logs.RetentionDays.ONE_DAY, + }); + + // WHEN + CustomResourceConfig.of(app).addLogRetentionLifetime(customResourceLogRetention); + + // THEN + const templateA = Template.fromStack(nestedStack1); + templateA.hasResourceProperties('Custom::LogRetention', { + RetentionInDays: customResourceLogRetention, + }); + templateA.resourceCountIs('AWS::Logs::LogGroup', 1); + templateA.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: customResourceLogRetention, + }); + const templateB = Template.fromStack(nestedStack2); + templateB.hasResourceProperties('Custom::LogRetention', { + RetentionInDays: customResourceLogRetention, + }); + templateB.resourceCountIs('AWS::Logs::LogGroup', 1); + templateB.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: customResourceLogRetention, + }); +}); diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index 2b06a12c6b2d4..25a058a2fcbd1 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -165,6 +165,8 @@ $ # Diff against the currently deployed stack with quiet parameter enabled $ cdk diff --quiet --app='node bin/main.js' MyStackName ``` +Note that the CDK::Metadata resource and the `CheckBootstrapVersion` Rule are excluded from `cdk diff` by default. You can force `cdk diff` to display them by passing the `--strict` flag. + The `change-set` flag will make `diff` create a change set and extract resource replacement data from it. This is a bit slower, but will provide no false positives for resource replacement. The `--no-change-set` mode will consider any change to a property that requires replacement to be a resource replacement, even if the change is purely cosmetic (like replacing a resource reference with a hardcoded arn). diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 854b7ec6419c2..af64056e2fc29 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -105,7 +105,7 @@ export class CdkToolkit { public async metadata(stackName: string, json: boolean) { const stacks = await this.selectSingleStackByName(stackName); - data(serializeStructure(stacks.firstStack.manifest.metadata ?? {}, json)); + printSerializedObject(stacks.firstStack.manifest.metadata ?? {}, json); } public async acknowledge(noticeId: string) { @@ -632,7 +632,7 @@ export class CdkToolkit { }); if (options.long && options.showDeps) { - data(serializeStructure(stacks, options.json ?? false)); + printSerializedObject(stacks, options.json ?? false); return 0; } @@ -646,7 +646,7 @@ export class CdkToolkit { }); } - data(serializeStructure(stackDeps, options.json ?? false)); + printSerializedObject(stackDeps, options.json ?? false); return 0; } @@ -660,7 +660,7 @@ export class CdkToolkit { environment: stack.environment, }); } - data(serializeStructure(long, options.json ?? false)); + printSerializedObject(long, options.json ?? false); return 0; } @@ -687,7 +687,7 @@ export class CdkToolkit { // if we have a single stack, print it to STDOUT if (stacks.stackCount === 1) { if (!quiet) { - data(serializeStructure(stacks.firstStack.template, json ?? false)); + printSerializedObject(obscureTemplate(stacks.firstStack.template), json ?? false); } return undefined; } @@ -701,7 +701,7 @@ export class CdkToolkit { // behind an environment variable. const isIntegMode = process.env.CDK_INTEG_MODE === '1'; if (isIntegMode) { - data(serializeStructure(stacks.stackArtifacts.map(s => s.template), json ?? false)); + printSerializedObject(stacks.stackArtifacts.map(s => obscureTemplate(s.template)), json ?? false); } // not outputting template to stdout, let's explain things to the user a little bit... @@ -1045,6 +1045,13 @@ export class CdkToolkit { } } +/** + * Print a serialized object (YAML or JSON) to stdout. + */ +function printSerializedObject(obj: any, json: boolean) { + data(serializeStructure(obj, json)); +} + export interface DiffOptions { /** * Stack names to diff @@ -1526,3 +1533,21 @@ function buildParameterMap(parameters: { return parameterMap; } + +/** + * Remove any template elements that we don't want to show users. + */ +function obscureTemplate(template: any = {}) { + if (template.Rules) { + // see https://github.com/aws/aws-cdk/issues/17942 + if (template.Rules.CheckBootstrapVersion) { + if (Object.keys(template.Rules).length > 1) { + delete template.Rules.CheckBootstrapVersion; + } else { + delete template.Rules; + } + } + } + + return template; +} diff --git a/packages/aws-cdk/lib/cli.ts b/packages/aws-cdk/lib/cli.ts index 2c15bb7b9949f..a05a0cbb4625c 100644 --- a/packages/aws-cdk/lib/cli.ts +++ b/packages/aws-cdk/lib/cli.ts @@ -260,7 +260,7 @@ async function parseCommandLineArguments(args: string[]) { .option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only diff requested stacks, don\'t include dependencies' }) .option('context-lines', { type: 'number', desc: 'Number of context lines to include in arbitrary JSON diff rendering', default: 3, requiresArg: true }) .option('template', { type: 'string', desc: 'The path to the CloudFormation template to compare with', requiresArg: true }) - .option('strict', { type: 'boolean', desc: 'Do not filter out AWS::CDK::Metadata resources or mangled non-ASCII characters', default: false }) + .option('strict', { type: 'boolean', desc: 'Do not filter out AWS::CDK::Metadata resources, mangled non-ASCII characters, or the CheckBootstrapVersionRule', default: false }) .option('security-only', { type: 'boolean', desc: 'Only diff for broadened security changes', default: false }) .option('fail', { type: 'boolean', desc: 'Fail with exit code 1 in case of diff' }) .option('processed', { type: 'boolean', desc: 'Whether to compare against the template with Transforms already processed', default: false }) diff --git a/packages/aws-cdk/lib/diff.ts b/packages/aws-cdk/lib/diff.ts index 2fc043fcd38a3..0e9f1c15543dc 100644 --- a/packages/aws-cdk/lib/diff.ts +++ b/packages/aws-cdk/lib/diff.ts @@ -19,7 +19,7 @@ import { print, warning } from './logging'; * * @param oldTemplate the old/current state of the stack. * @param newTemplate the new/target state of the stack. - * @param strict do not filter out AWS::CDK::Metadata + * @param strict do not filter out AWS::CDK::Metadata or Rules * @param context lines of context to use in arbitrary JSON diff * @param quiet silences \'There were no differences\' messages * @@ -50,13 +50,9 @@ export function printStackDiff( } // filter out 'AWS::CDK::Metadata' resources from the template - if (diff.resources && !strict) { - diff.resources = diff.resources.filter(change => { - if (!change) { return true; } - if (change.newResourceType === 'AWS::CDK::Metadata') { return false; } - if (change.oldResourceType === 'AWS::CDK::Metadata') { return false; } - return true; - }); + // filter out 'CheckBootstrapVersion' rules from the template + if (!strict) { + obscureDiff(diff); } let stackDiffCount = 0; @@ -165,3 +161,30 @@ function logicalIdMapFromTemplate(template: any) { } return ret; } + +/** + * Remove any template elements that we don't want to show users. + * This is currently: + * - AWS::CDK::Metadata resource + * - CheckBootstrapVersion Rule + */ +function obscureDiff(diff: TemplateDiff) { + if (diff.unknown) { + // see https://github.com/aws/aws-cdk/issues/17942 + diff.unknown = diff.unknown.filter(change => { + if (!change) { return true; } + if (change.newValue?.CheckBootstrapVersion) { return false; } + if (change.oldValue?.CheckBootstrapVersion) { return false; } + return true; + }); + } + + if (diff.resources) { + diff.resources = diff.resources.filter(change => { + if (!change) { return true; } + if (change.newResourceType === 'AWS::CDK::Metadata') { return false; } + if (change.oldResourceType === 'AWS::CDK::Metadata') { return false; } + return true; + }); + } +} diff --git a/packages/aws-cdk/test/diff.test.ts b/packages/aws-cdk/test/diff.test.ts index 0155f74dd192d..ffaa157e5fc20 100644 --- a/packages/aws-cdk/test/diff.test.ts +++ b/packages/aws-cdk/test/diff.test.ts @@ -855,6 +855,95 @@ Resources }); }); +describe('--strict', () => { + const templatePath = 'oldTemplate.json'; + beforeEach(() => { + const oldTemplate = {}; + + cloudExecutable = new MockCloudExecutable({ + stacks: [{ + stackName: 'A', + template: { + Resources: { + MetadataResource: { + Type: 'AWS::CDK::Metadata', + Properties: { + newMeta: 'newData', + }, + }, + SomeOtherResource: { + Type: 'AWS::Something::Amazing', + }, + }, + Rules: { + CheckBootstrapVersion: { + newCheck: 'newBootstrapVersion', + }, + }, + }, + }], + }); + + toolkit = new CdkToolkit({ + cloudExecutable, + deployments: cloudFormation, + configuration: cloudExecutable.configuration, + sdkProvider: cloudExecutable.sdkProvider, + }); + + fs.writeFileSync(templatePath, JSON.stringify(oldTemplate)); + }); + + afterEach(() => fs.rmSync(templatePath)); + + test('--strict does not obscure CDK::Metadata or CheckBootstrapVersion', async () => { + // GIVEN + const buffer = new StringWritable(); + + // WHEN + const exitCode = await toolkit.diff({ + stackNames: ['A'], + stream: buffer, + strict: true, + }); + + // THEN + const plainTextOutput = buffer.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, ''); + expect(plainTextOutput.trim()).toEqual(`Stack A +Resources +[+] AWS::CDK::Metadata MetadataResource +[+] AWS::Something::Amazing SomeOtherResource + +Other Changes +[+] Unknown Rules: {\"CheckBootstrapVersion\":{\"newCheck\":\"newBootstrapVersion\"}} + + +✨ Number of stacks with differences: 1`); + expect(exitCode).toBe(0); + }); + + test('--no-strict obscures CDK::Metadata and CheckBootstrapVersion', async () => { + // GIVEN + const buffer = new StringWritable(); + + // WHEN + const exitCode = await toolkit.diff({ + stackNames: ['A'], + stream: buffer, + }); + + // THEN + const plainTextOutput = buffer.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, ''); + expect(plainTextOutput.trim()).toEqual(`Stack A +Resources +[+] AWS::Something::Amazing SomeOtherResource + + +✨ Number of stacks with differences: 1`); + expect(exitCode).toBe(0); + }); +}); + class StringWritable extends Writable { public data: string; private readonly _decoder: StringDecoder;