Description: "(uksb-1tcfnc0g5)"
Resources:
  seseventsdestinationEA24EF5F:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
      BucketName: !Sub '${AWS::AccountId}-${AWS::Region}-ses-events-destination'
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
  seseventsdestinationPolicyAC1B01B4:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket:
        Ref: seseventsdestinationEA24EF5F
      PolicyDocument:
        Statement:
          - Action: s3:*
            Condition:
              Bool:
                aws:SecureTransport: "false"
            Effect: Deny
            Principal:
              AWS: "*"
            Resource:
              - Fn::GetAtt:
                  - seseventsdestinationEA24EF5F
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - seseventsdestinationEA24EF5F
                        - Arn
                    - /*
        Version: "2012-10-17"
  seseventsdestinationNotificationsDC46E6DF:
    Type: Custom::S3BucketNotifications
    Properties:
      ServiceToken:
        Fn::GetAtt:
          - BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691
          - Arn
      BucketName:
        Ref: seseventsdestinationEA24EF5F
      NotificationConfiguration:
        LambdaFunctionConfigurations:
          - Events:
              - s3:ObjectCreated:*
            Filter:
              Key:
                FilterRules:
                  - Name: prefix
                    Value: partitioned/
            LambdaFunctionArn:
              Fn::GetAtt:
                - SESPartitionedObjectReplicationFunction413D8164
                - Arn
      Managed: true
    DependsOn:
      - seseventsdestinationAllowBucketNotificationsToSesBlogSolutionStackSESPartitionedObjectReplicationFunctionFC59373D512A5B8B
  seseventsdestinationAllowBucketNotificationsToSesBlogSolutionStackSESPartitionedObjectReplicationFunctionFC59373D512A5B8B:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName:
        Fn::GetAtt:
          - SESPartitionedObjectReplicationFunction413D8164
          - Arn
      Principal: s3.amazonaws.com
      SourceAccount: !Sub '${AWS::AccountId}'
      SourceArn:
        Fn::GetAtt:
          - seseventsdestinationEA24EF5F
          - Arn
  seseventsdestinationaggregatedD1CA1006:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
      BucketName: !Sub '${AWS::AccountId}-${AWS::Region}-ses-events-destination-aggregated'
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
  seseventsdestinationaggregatedPolicy42D9EBC4:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket:
        Ref: seseventsdestinationaggregatedD1CA1006
      PolicyDocument:
        Statement:
          - Action: s3:*
            Condition:
              Bool:
                aws:SecureTransport: "false"
            Effect: Deny
            Principal:
              AWS: "*"
            Resource:
              - Fn::GetAtt:
                  - seseventsdestinationaggregatedD1CA1006
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - seseventsdestinationaggregatedD1CA1006
                        - Arn
                    - /*
        Version: "2012-10-17"
  SESPartitionedObjectReplicationFunctionServiceRole7A64D76B:
    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
  SESPartitionedObjectReplicationFunctionServiceRoleDefaultPolicy265F3C9E:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - s3:GetBucket*
              - s3:GetObject*
              - s3:List*
            Effect: Allow
            Resource:
              - Fn::GetAtt:
                  - seseventsdestinationEA24EF5F
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - seseventsdestinationEA24EF5F
                        - Arn
                    - /*
          - Action:
              - s3:Abort*
              - s3:DeleteObject*
              - s3:PutObject
              - s3:PutObjectLegalHold
              - s3:PutObjectRetention
              - s3:PutObjectTagging
              - s3:PutObjectVersionTagging
            Effect: Allow
            Resource:
              - Fn::GetAtt:
                  - seseventsdestinationaggregatedD1CA1006
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - seseventsdestinationaggregatedD1CA1006
                        - Arn
                    - /*
        Version: "2012-10-17"
      PolicyName: SESPartitionedObjectReplicationFunctionServiceRoleDefaultPolicy265F3C9E
      Roles:
        - Ref: SESPartitionedObjectReplicationFunctionServiceRole7A64D76B
  SESPartitionedObjectReplicationFunction413D8164:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |+
          const aws = require("aws-sdk");
          const s3 = new aws.S3({ apiVersion: "2006-03-01" });

          const sourcePrefix = process.env.SRC_PREFIX;
          const destinationBucket = process.env.DEST_BUCKET;

          exports.handler = async (event, context) => {
            //console.log("Received event:", JSON.stringify(event, null, 2));

            // Get the object from the event
            const bucket = event.Records[0].s3.bucket.name;
            const key = decodeURIComponent(
              event.Records[0].s3.object.key.replace(/\+/g, " ")
            );

            if (!key.startsWith(`${sourcePrefix}year=`)) {
              //console.log(`Key ${key} does not start with '${sourcePrefix}year='`);
              return;
            }

            var copyParams = {
              Bucket: destinationBucket,
              CopySource: encodeURI(`/${bucket}/${key}`),
              Key: encodeURI(key),
            };

            //console.log(`Copying object to ${copyParams.Bucket}/${copyParams.Key}`);

            try {
              await s3.copyObject(copyParams, function (err, data) {
                if (err) console.log(err, err.stack); // an error occurred
                //else console.log('Response: ' + JSON.stringify(data, null, 2)); // successful response
              }).promise();
            } catch (err) {
              console.log(err);
              throw new Error(err);
            }
          };

      Role:
        Fn::GetAtt:
          - SESPartitionedObjectReplicationFunctionServiceRole7A64D76B
          - Arn
      Environment:
        Variables:
          SRC_PREFIX: partitioned/
          DEST_BUCKET:
            Ref: seseventsdestinationaggregatedD1CA1006
      FunctionName: SESPartitionedObjectReplicationFunction
      Handler: index.handler
      Runtime: nodejs12.x
    DependsOn:
      - SESPartitionedObjectReplicationFunctionServiceRoleDefaultPolicy265F3C9E
      - SESPartitionedObjectReplicationFunctionServiceRole7A64D76B
  BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC:
    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
  BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action: s3:PutBucketNotification
            Effect: Allow
            Resource: "*"
        Version: "2012-10-17"
      PolicyName: BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36
      Roles:
        - Ref: BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC
  BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691:
    Type: AWS::Lambda::Function
    Properties:
      Description: AWS CloudFormation handler for "Custom::S3BucketNotifications" resources (@aws-cdk/aws-s3)
      Code:
        ZipFile: |
          import boto3  # type: ignore
          import json
          import logging
          import urllib.request

          s3 = boto3.client("s3")

          CONFIGURATION_TYPES = ["TopicConfigurations", "QueueConfigurations", "LambdaFunctionConfigurations"]

          def handler(event: dict, context):
              response_status = "SUCCESS"
              error_message = ""
              try:
                  props = event["ResourceProperties"]
                  bucket = props["BucketName"]
                  notification_configuration = props["NotificationConfiguration"]
                  request_type = event["RequestType"]
                  managed = props.get('Managed', 'true').lower() == 'true'
                  stack_id = event['StackId']

                  if managed:
                    config = handle_managed(request_type, notification_configuration)
                  else:
                    config = handle_unmanaged(bucket, stack_id, request_type, notification_configuration)

                  put_bucket_notification_configuration(bucket, config)
              except Exception as e:
                  logging.exception("Failed to put bucket notification configuration")
                  response_status = "FAILED"
                  error_message = f"Error: {str(e)}. "
              finally:
                  submit_response(event, context, response_status, error_message)


          def handle_managed(request_type, notification_configuration):
            if request_type == 'Delete':
              return {}
            return notification_configuration


          def handle_unmanaged(bucket, stack_id, request_type, notification_configuration):

            # find external notifications
            external_notifications = find_external_notifications(bucket, stack_id)

            # if delete, that's all we need
            if request_type == 'Delete':
              return external_notifications

            def with_id(notification):
              notification['Id'] = f"{stack_id}-{hash(json.dumps(notification, sort_keys=True))}"
              return notification

            # otherwise, merge external with incoming config and augment with id
            notifications = {}
            for t in CONFIGURATION_TYPES:
              external = external_notifications.get(t, [])
              incoming = [with_id(n) for n in notification_configuration.get(t, [])]
              notifications[t] = external + incoming
            return notifications


          def find_external_notifications(bucket, stack_id):
            existing_notifications = get_bucket_notification_configuration(bucket)
            external_notifications = {}
            for t in CONFIGURATION_TYPES:
              # if the notification was created by us, we know what id to expect
              # so we can filter by it.
              external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f"{stack_id}-")]

            return external_notifications


          def get_bucket_notification_configuration(bucket):
            return s3.get_bucket_notification_configuration(Bucket=bucket)


          def put_bucket_notification_configuration(bucket, notification_configuration):
            s3.put_bucket_notification_configuration(Bucket=bucket, NotificationConfiguration=notification_configuration)


          def submit_response(event: dict, context, response_status: str, error_message: str):
              response_body = json.dumps(
                  {
                      "Status": response_status,
                      "Reason": f"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}",
                      "PhysicalResourceId": event.get("PhysicalResourceId") or event["LogicalResourceId"],
                      "StackId": event["StackId"],
                      "RequestId": event["RequestId"],
                      "LogicalResourceId": event["LogicalResourceId"],
                      "NoEcho": False,
                  }
              ).encode("utf-8")
              headers = {"content-type": "", "content-length": str(len(response_body))}
              try:
                  req = urllib.request.Request(url=event["ResponseURL"], headers=headers, data=response_body, method="PUT")
                  with urllib.request.urlopen(req) as response:
                      print(response.read().decode("utf-8"))
                  print("Status code: " + response.reason)
              except Exception as e:
                  print("send(..) failed executing request.urlopen(..): " + str(e))
      Handler: index.handler
      Role:
        Fn::GetAtt:
          - BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC
          - Arn
      Runtime: python3.7
      Timeout: 300
    DependsOn:
      - BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36
      - BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC
  KinesisFirehoseStreamServiceRole8F041D47:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: firehose.amazonaws.com
        Version: "2012-10-17"
  KinesisFirehoseStreamS3DestinationRoleC8B52E87:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: firehose.amazonaws.com
        Version: "2012-10-17"
  KinesisFirehoseStreamS3DestinationRoleDefaultPolicy8AEFFD95:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - 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:
                  - seseventsdestinationEA24EF5F
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - seseventsdestinationEA24EF5F
                        - Arn
                    - /*
          - Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
            Resource:
              Fn::GetAtt:
                - KinesisFirehoseStreamLogGroup75847921
                - Arn
        Version: "2012-10-17"
      PolicyName: KinesisFirehoseStreamS3DestinationRoleDefaultPolicy8AEFFD95
      Roles:
        - Ref: KinesisFirehoseStreamS3DestinationRoleC8B52E87
  KinesisFirehoseStreamLogGroup75847921:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays: 731
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
  KinesisFirehoseStreamLogGroupS3Destination78556782:
    Type: AWS::Logs::LogStream
    Properties:
      LogGroupName:
        Ref: KinesisFirehoseStreamLogGroup75847921
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
  KinesisFirehoseStream6F9ED265:
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties:
      DeliveryStreamEncryptionConfigurationInput:
        KeyType: AWS_OWNED_CMK
      DeliveryStreamType: DirectPut
      ExtendedS3DestinationConfiguration:
        BucketARN:
          Fn::GetAtt:
            - seseventsdestinationEA24EF5F
            - Arn
        BufferingHints:
          IntervalInSeconds: 60
          SizeInMBs: 5
        CloudWatchLoggingOptions:
          Enabled: true
          LogGroupName:
            Ref: KinesisFirehoseStreamLogGroup75847921
          LogStreamName:
            Ref: KinesisFirehoseStreamLogGroupS3Destination78556782
        CompressionFormat: GZIP
        Prefix: raw/
        RoleARN:
          Fn::GetAtt:
            - KinesisFirehoseStreamS3DestinationRoleC8B52E87
            - Arn
    DependsOn:
      - KinesisFirehoseStreamS3DestinationRoleDefaultPolicy8AEFFD95
  cfnConfigurationSet:
    Type: AWS::SES::ConfigurationSet
    Properties:
      Name: SESConfigurationSet
  KinesisPutRecordBatchPolicyA42C6A09:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: firehose:PutRecordBatch
            Effect: Allow
            Resource:
              Fn::GetAtt:
                - KinesisFirehoseStream6F9ED265
                - Arn
            Sid: ""
        Version: "2012-10-17"
      Description: AllowsFirehosePutRecordBatch
      Path: /
  SESRole7CABDF48:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Condition:
              StringEquals:
                AWS:SourceAccount: !Sub '${AWS::AccountId}'
                AWS:SourceArn: !Sub 'arn:aws:ses:${AWS::Region}:${AWS::AccountId}:configuration-set/SESConfigurationSet'
            Effect: Allow
            Principal:
              Service: ses.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - Ref: KinesisPutRecordBatchPolicyA42C6A09
  cfnConfigurationSetEventDestination:
    Type: AWS::SES::ConfigurationSetEventDestination
    Properties:
      ConfigurationSetName: SESConfigurationSet
      EventDestination:
        Enabled: true
        KinesisFirehoseDestination:
          DeliveryStreamARN:
            Fn::GetAtt:
              - KinesisFirehoseStream6F9ED265
              - Arn
          IAMRoleARN:
            Fn::GetAtt:
              - SESRole7CABDF48
              - Arn
        MatchingEventTypes:
          - send
          - reject
          - bounce
          - complaint
          - delivery
          - open
          - click
          - renderingFailure
          - subscription
          - deliveryDelay
        Name: SesBlogSolutionStack-ConfigurationSetDestination
  dataBrewDataset:
    Type: AWS::DataBrew::Dataset
    Properties:
      Input:
        S3InputDefinition:
          Bucket:
            Ref: seseventsdestinationEA24EF5F
          Key: raw/{year}/{month}/{day}/{hour}/
      Name: SESDataBrewDataset
      Format: JSON
      PathOptions:
        LastModifiedDateCondition:
          Expression: relative_after :timeframe
          ValuesMap:
            - Value: -1H
              ValueReference: :timeframe
        Parameters:
          - DatasetParameter:
              CreateColumn: true
              Name: year
              Type: Number
            PathParameterName: year
          - DatasetParameter:
              CreateColumn: true
              Name: month
              Type: Number
            PathParameterName: month
          - DatasetParameter:
              CreateColumn: true
              Name: day
              Type: Number
            PathParameterName: day
          - DatasetParameter:
              CreateColumn: true
              Name: hour
              Type: Number
            PathParameterName: hour
  SESEventDataDatabase:
    Type: AWS::Glue::Database
    Properties:
      CatalogId: !Sub '${AWS::AccountId}'
      DatabaseInput:
        Name: ses_event_data_database
  crawlerServiceRoleF4172CF9:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: glue.amazonaws.com
        Version: "2012-10-17"
      Description: A role used by Glue Crawler for data processing
      ManagedPolicyArns:
        - Fn::Join:
            - ""
            - - "arn:"
              - Ref: AWS::Partition
              - :iam::aws:policy/service-role/AWSGlueServiceRole
  crawlerServiceRoleDefaultPolicyB617A954:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - 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:
                  - seseventsdestinationaggregatedD1CA1006
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - seseventsdestinationaggregatedD1CA1006
                        - Arn
                    - /*
        Version: "2012-10-17"
      PolicyName: crawlerServiceRoleDefaultPolicyB617A954
      Roles:
        - Ref: crawlerServiceRoleF4172CF9
  SESEventDataCrawler:
    Type: AWS::Glue::Crawler
    Properties:
      Role:
        Fn::GetAtt:
          - crawlerServiceRoleF4172CF9
          - Arn
      Targets:
        S3Targets:
          - Path:
              Fn::Join:
                - ""
                - - s3://
                  - Ref: seseventsdestinationaggregatedD1CA1006
                  - /partitioned/
      DatabaseName: ses_event_data_database
      Name: SESEventDataCrawler
      RecrawlPolicy:
        RecrawlBehavior: CRAWL_EVERYTHING
      Schedule:
        ScheduleExpression: cron(30 0/1 * * ? *)
      SchemaChangePolicy:
        UpdateBehavior: UPDATE_IN_DATABASE

