AWSTemplateFormatVersion: 2010-09-09 Description: This CloudFormation Template invokes another template responsible for copying files to a local region S3 Bucket for AWS Lambda. Parameters: QSS3BucketName: AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$ ConstraintDescription: Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Default: aws-quickstart Description: S3 bucket name for the Quick Start assets. This string can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Type: String QSS3KeyPrefix: AllowedPattern: ^[0-9a-zA-Z-/]*$ ConstraintDescription: Quick Start key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). Description: S3 key prefix for the Quick Start assets. Quick Start key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). Type: String QSS3BucketRegion: Default: "us-east-1" Description: "The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. When using your own bucket, you must specify this value." Type: String DestinationBucket: Type: String Default: "" SourceObjects: Type: CommaDelimitedList Default: "" Conditions: CreateDestBucket: !Equals [!Ref DestinationBucket, ""] UsingDefaultBucket: !Equals [!Ref QSS3BucketName, "aws-quickstart"] Resources: LambdaZipsBucket: Condition: CreateDestBucket Type: "AWS::S3::Bucket" CopyRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: ConfigPolicy PolicyDocument: Version: 2012-10-17 Statement: - Sid: Logging Effect: Allow Action: logs:* Resource: "*" - Sid: S3Get Effect: Allow Action: - s3:GetObject Resource: !Sub - arn:${AWS::Partition}:s3:::${S3Bucket}/${QSS3KeyPrefix}* - S3Bucket: !If [ UsingDefaultBucket, !Sub "${QSS3BucketName}-${AWS::Region}", !Ref QSS3BucketName, ] - Sid: S3Put Effect: Allow Action: - s3:PutObject - s3:DeleteObject Resource: !Sub - "arn:${AWS::Partition}:s3:::${DestBucket}/*" - DestBucket: !If [ CreateDestBucket, !Ref LambdaZipsBucket, !Ref DestinationBucket, ] CopyZips: Type: AWS::CloudFormation::CustomResource Properties: ServiceToken: !GetAtt CopyZipsFunction.Arn DestRegion: !Ref "AWS::Region" DestBucket: !If [CreateDestBucket, !Ref LambdaZipsBucket, !Ref DestinationBucket] SourceBucket: !If [ UsingDefaultBucket, !Sub "${QSS3BucketName}-${AWS::Region}", !Ref QSS3BucketName, ] Prefix: !Ref QSS3KeyPrefix Objects: !Ref SourceObjects CopyZipsFunction: Type: AWS::Lambda::Function Properties: Description: Copies objects from a source S3 bucket to a destination Handler: index.handler Runtime: python3.7 Role: !GetAtt CopyRole.Arn Timeout: 240 Code: ZipFile: | import json import logging import threading import boto3 import cfnresponse def copy_objects(source_bucket, dest_bucket, prefix, objects): s3 = boto3.client('s3') for o in objects: key = prefix + o copy_source = { 'Bucket': source_bucket, 'Key': key } s3.copy_object(CopySource=copy_source, Bucket=dest_bucket, Key=key) def delete_objects(bucket, prefix, objects): s3 = boto3.client('s3') objects = {'Objects': [{'Key': prefix + o} for o in objects]} s3.delete_objects(Bucket=bucket, Delete=objects) def timeout(event, context): logging.error('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): # make sure we send a failure to CloudFormation if the function is going to timeout timer = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5, timeout, args=[event, context]) timer.start() print('Received event: %s' % json.dumps(event)) status = cfnresponse.SUCCESS try: source_bucket = event['ResourceProperties']['SourceBucket'] dest_bucket = event['ResourceProperties']['DestBucket'] prefix = event['ResourceProperties']['Prefix'] objects = event['ResourceProperties']['Objects'] if event['RequestType'] == 'Delete': delete_objects(dest_bucket, prefix, objects) else: copy_objects(source_bucket, dest_bucket, prefix, objects) except Exception as e: logging.error('Exception: %s' % e, exc_info=True) status = cfnresponse.FAILED finally: timer.cancel() cfnresponse.send(event, context, status, {}, None) Outputs: LambdaZipsBucket: Description: S3 Bucket for the Lambda Function Code Value: !If [CreateDestBucket, !Ref LambdaZipsBucket, !Ref DestinationBucket]