# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 # # Copies S3 objects to local S3 bucket. This is usefule if you have lambda functions from a different region # AWSTemplateFormatVersion: 2010-09-09 Description: Copy S3 object to local S3 bucket Parameters: S3BucketSources: Type: String Description: S3 bucket with sources MaxLength: 63 MinLength: 3 Default: my-test-bucket S3SourcesPrefix: Type: String Description: S3 prefix with sources WITH ending slash MaxLength: 128 MinLength: 3 Default: prefix S3Objects: Type: CommaDelimitedList Description: S3 Object to be copied Default: test1.json, test2.json Resources: S3BucketRegionSources: Type: AWS::S3::Bucket Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 DeletionPolicy: Delete CopyZips: Type: AWS::CloudFormation::CustomResource Properties: ServiceToken: !GetAtt 'CopyZipsFunction.Arn' DestBucket: !Ref 'S3BucketRegionSources' SourceBucket: !Ref 'S3BucketSources' SourcePrefix: !Ref 'S3SourcesPrefix' Counter: "1" Objects: !Ref S3Objects CopyZipsRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Path: / Policies: - PolicyName: lambda-copier PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectTagging Resource: - !Sub 'arn:aws:s3:::${S3BucketSources}/*' - Effect: Allow Action: - s3:ListBucket Resource: - !Sub 'arn:aws:s3:::${S3BucketSources}' - Effect: Allow Action: - s3:ListBucket Resource: - !Sub 'arn:aws:s3:::${S3BucketRegionSources}' - Effect: Allow Action: - s3:PutObject - s3:DeleteObject - s3:PutObjectTagging - s3:GetObject Resource: - !Sub 'arn:aws:s3:::${S3BucketRegionSources}/*' CopyZipsFunction: Type: AWS::Lambda::Function Properties: FunctionName: "CopyZipsFunction" Description: Copies objects from a source S3 bucket to a destination Handler: index.handler Runtime: python3.6 Role: !GetAtt 'CopyZipsRole.Arn' Timeout: 240 Code: ZipFile: | import json import logging import threading import boto3 import cfnresponse import botocore import uuid logger = logging.getLogger() def copy_objects(s3, source_bucket, dest_bucket, objects, prefix): for o in objects: key = prefix + o copy_source = { 'Bucket': source_bucket, 'Key': key } logger.info('copy source_bucket:' + source_bucket + ' destination_bucket: '+ dest_bucket + ' key: ' + key) try: s3.head_object(Bucket=dest_bucket, Key=key) except botocore.exceptions.ClientError as error: if error.response['Error']['Message']=="Not Found": s3.copy_object(CopySource=copy_source, Bucket=dest_bucket, Key=key) else: raise Exception("ObjectAlreadyExists") def delete_objects(s3, bucket, objects, prefix): objects = {'Objects': [{'Key': prefix + o} for o in objects]} s3.delete_objects(Bucket=bucket, Delete=objects) def timeout(event, context): logger.error('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): s3 = boto3.client('s3') # 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() status = cfnresponse.SUCCESS try: source_bucket = event['ResourceProperties']['SourceBucket'] source_prefix = event['ResourceProperties']['SourcePrefix'] dest_bucket = event['ResourceProperties']['DestBucket'] if source_bucket != dest_bucket: objects = event['ResourceProperties']['Objects'] if event['RequestType'] == 'Delete': delete_objects(s3, dest_bucket, objects, source_prefix) else: copy_objects(s3, source_bucket, dest_bucket, objects, source_prefix) except Exception as e: logger.error('Exception: %s' % e, exc_info=True) status = cfnresponse.FAILED finally: timer.cancel() # return the same physical ID to prevent delete on stack clenup try: physicalID=event["PhysicalResourceId"] logger.info(f"Found physical ID {physicalID}") # "f except Exception as e: physicalID=str(uuid.uuid1()) logger.info(f"Set physical ID {physicalID}") # "f cfnresponse.send(event, context, status, {}, physicalID) Outputs: RegionalS3Bucket: Description: Regional S3 bucket Value: !Ref S3BucketRegionSources