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..eebe0b37db0ba 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; /** @@ -1072,6 +1075,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/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, + }); +});