# https://github.com/awslabs/cid-data-collection-framework/blob/main/data-exports/deploy/cur-aggregation.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS Legacy CUR Aggregation Stack v0.2.0 - AWS Solution SO9011
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "Common Parameters Configuration"
        Parameters:
          - DestinationAccountId
          - ResourcePrefix
          - CreateCUR
          - AddSCAD
      -
        Label:
          default: "Parameters needed in Destination (Data Collection) Account only"
        Parameters:
          - SourceAccountIds

    ParameterLabels:
      DestinationAccountId:
        default: "Destination (Data Collection) Account Account Id"
      ResourcePrefix:
        default: "Prefix used for all named resources, including S3 Bucket. Must be the same in destination and source stacks"
      SourceAccountIds:
        default: "Source Account Ids (Comma separated list)"
      AddSCAD:
        default: "Add Split Cost Allocation Data (SCAD) to CUR."


Parameters:

  ##
  # Common params
  ##
  DestinationAccountId:
    Type: String
    Description: "AWS Account Id where CID Quicksight Dashboard will be deployed"
    AllowedPattern: '\d{12}'
  ResourcePrefix:
    Type: String
    Default: "cid"
    Description: "Resource prefix. Will be used as a prefix for bucket and a name of a CUR"
    AllowedPattern: "^[a-z0-9]+[a-z0-9-]{1,61}[a-z0-9]+$"
  CreateCUR:
    Type: String
    Description: Whether to create a local CUR in the destination account or not. Set this to False only if the current account is covered in the CUR of its Management (Payer) Account.
    AllowedValues: ["True", "False"]
    Default: "True"
  AddSCAD:
    Type: String
    Description: Add Split Cost Allocation Data (SCAD) to CUR
    AllowedValues: ['yes', 'no']
    Default: "no"

  ##
  # Destination specific params
  ##

  SourceAccountIds:
    Type: String
    AllowedPattern: "^((\\d{12})\\,?)*$"
    Default: ''
    Description: "A comma separated list of all Source accounts that will replicate CUR Data. Ex:  12345678912,98745612312,...."
  PermissionsBoundary:
    Type: String
    Default: ''
    Description: Define Permission Boundary for Roles if required by SCP

Conditions:
  NeedSCAD: !Equals [!Ref AddSCAD, 'yes']
  IsDestinationAccount: !Equals [!Ref DestinationAccountId, !Ref 'AWS::AccountId']
  IsSourceAccount: !Not [!Condition IsDestinationAccount]
  RegionSupportsCURviaCFN: # CFN supports CUR only in us-east-1 and cn-northwest-1. Other regions must use lambda
    Fn::Or:
      - !Equals [!Ref 'AWS::Region', 'us-east-1']
      - !Equals [!Ref 'AWS::Region', 'cn-northwest-1']
  CUREnable: !Equals [!Ref CreateCUR, 'True']
  DeployCURViaCFNInSource: !And [!Condition CUREnable, !Condition IsSourceAccount, !Condition RegionSupportsCURviaCFN]
  DeployCURViaCFNInDestination: !And [!Condition CUREnable, !Condition IsDestinationAccount, !Condition RegionSupportsCURviaCFN]
  DeployCURViaLambda: !And [!Condition CUREnable, !Not [!Condition RegionSupportsCURviaCFN]]
  EmptySourceAccountIds: !Equals [ !Ref SourceAccountIds, '']
  NeedPermissionsBoundary: !Not [!Equals [ !Ref PermissionsBoundary, "" ]]

Resources:

###########################################################################
# Destination Account Resources
###########################################################################

####
# S3 Bucket which store all CUR from Accounts
####

  DestinationS3:
    Type: AWS::S3::Bucket
    Condition: IsDestinationAccount
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName:
        Fn::Sub: "${ResourcePrefix}-${AWS::AccountId}-shared"

      ## Uncomment following lines to enable bucket logging if needed. Please be careful with the cost of logging.
      # LoggingConfiguration:
      #   DestinationBucketName: REPLACE_WITH_YOUR_LOGGING_BUCKET
      #   LogFilePrefix: REPLACE_WITH_YOUR_PREFIX

      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256 ## Use AWS managed KMS
              ## If you need Customer managed KMS key, yoy can do that using following parameters:
              # SSEAlgorithm: aws:kms
              # KMSMasterKeyID: "REPLACE_WITH_YOUR_KEY_ARN"
      AccessControl: BucketOwnerFullControl
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: Object&Version Expiration
            Status: Enabled
            NoncurrentVersionExpirationInDays: 1
          - Id: DeleteIncompleteMultipartUploadsAndExpiredDeleteMarkers
            Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            ExpiredObjectDeleteMarker: true
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: 'W35'
            reason: "Data buckets would generate too much logs"
      cfn-lint:
        config:
          ignore_checks:
            - W3045 # Need to use AccessControl for replication

  DestinationS3BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Condition: IsDestinationAccount
    DeletionPolicy: Retain
    UpdateReplacePolicy: Delete
    Properties:
      Bucket:
        Ref: DestinationS3
      PolicyDocument:
        Id: CrossAccessPolicy
        Version: "2012-10-17"
        Statement:
          - Sid: AllowTLS12Only
            Effect: Deny
            Principal: "*"
            Action: s3:*
            Resource:
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}'
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}/*'
            Condition:
              NumericLessThan:
                s3:TlsVersion: 1.2
          - Sid: AllowOnlyHTTPS
            Effect: Deny
            Principal: "*"
            Action: s3:*
            Resource:
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}'
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}/*'
            Condition:
              Bool:
                aws:SecureTransport: false
          - Sid: AllowReadBilling
            Effect: Allow
            Principal:
              Service: billingreports.amazonaws.com
            Action:
              - s3:GetBucketAcl
              - s3:GetBucketPolicy
            Resource:
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}'
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}/*'
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
          - Sid: AllowWriteBilling
            Effect: Allow
            Principal:
              Service: billingreports.amazonaws.com
            Action:
              - s3:PutObject
            Resource:
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}/*'
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
          - Sid: AllowReplicationWrite
            Effect: Allow
            Principal:
              AWS:
                Fn::If:
                  - EmptySourceAccountIds
                  - !Ref AWS::AccountId
                  - !Split [',', !Ref SourceAccountIds]
            Action:
              - s3:ReplicateDelete
              - s3:ReplicateObject
            Resource:
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}/*'
          - Sid: AllowReplicationRead
            Effect: Allow
            Principal:
              AWS:
                Fn::If:
                  - EmptySourceAccountIds
                  - !Ref AWS::AccountId
                  - !Split [',', !Ref SourceAccountIds]
            Action:
              - s3:ListBucket
              - s3:ListBucketVersions
              - s3:GetBucketVersioning
              - s3:PutBucketVersioning
            Resource:
              - Fn::Sub: 'arn:${AWS::Partition}:s3:::${DestinationS3}'


###########################################################################
# Source Account Resources
###########################################################################

####
# S3 Bucket with replication
####

  SourceS3:
    Type: AWS::S3::Bucket
    Condition: IsSourceAccount
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      BucketName:
        Fn::Sub: ${ResourcePrefix}-${AWS::AccountId}-local

      ## Uncomment following lines to enable bucket logging if needed. Please be careful with the cost of logging.
      # LoggingConfiguration:
      #   DestinationBucketName: REPLACE_WITH_YOUR_LOGGING_BUCKET
      #   LogFilePrefix: REPLACE_WITH_YOUR_PREFIX

      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256 ## Use AWS managed KMS
              ## If you need Customer managed KMS key, yoy can do that using following parameters:
              # SSEAlgorithm: aws:kms
              # KMSMasterKeyID: "REPLACE_WITH_YOUR_KEY_ARN"
      AccessControl: BucketOwnerFullControl
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      ReplicationConfiguration:
        Role: !GetAtt CrossRegionReplicationRole.Arn
        Rules:
          -
            Destination:
              Bucket:
                Fn::Sub: "arn:${AWS::Partition}:s3:::${ResourcePrefix}-${DestinationAccountId}-shared"
              StorageClass: STANDARD
            Id: ReplicationRule1
            Prefix: "cur"
            Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: Object&Version Expiration
            Status: Enabled
            NoncurrentVersionExpirationInDays: 1
            ExpirationInDays: 7
          - Id: DeleteIncompleteMultipartUploadsAndExpiredDeleteMarkers
            Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            ExpiredObjectDeleteMarker: true
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: 'W35'
            reason: "Data buckets would generate too much logs"
      cfn-lint:
        config:
          ignore_checks:
            - W3045 # Need to use AccessControl for replication

  SourceS3BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Condition: IsSourceAccount
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      Bucket: !Ref SourceS3
      PolicyDocument:
        Id: CrossAccessPolicy
        Version: "2012-10-17"
        Statement:
          - Sid: AllowTLS12Only
            Effect: Deny
            Principal: "*"
            Action: s3:*
            Resource:
              - !Sub 'arn:${AWS::Partition}:s3:::${SourceS3}'
              - !Sub 'arn:${AWS::Partition}:s3:::${SourceS3}/*'
            Condition:
              NumericLessThan:
                s3:TlsVersion: 1.2
          - Sid: AllowOnlyHTTPS
            Effect: Deny
            Principal: "*"
            Action: s3:*
            Resource:
              - !Sub 'arn:${AWS::Partition}:s3:::${SourceS3}'
              - !Sub 'arn:${AWS::Partition}:s3:::${SourceS3}/*'
            Condition:
              Bool:
                aws:SecureTransport: false
          - Sid: AllowReadBilling
            Effect: Allow
            Principal:
              Service: billingreports.amazonaws.com
            Action:
              - s3:GetBucketAcl
              - s3:GetBucketPolicy
            Resource:
              - !Sub 'arn:${AWS::Partition}:s3:::${SourceS3}'
              - !Sub 'arn:${AWS::Partition}:s3:::${SourceS3}/*'
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
          - Sid: AllowWriteBilling
            Effect: Allow
            Principal:
              Service: billingreports.amazonaws.com
            Action:
              - s3:PutObject
            Resource:
              - !Sub 'arn:${AWS::Partition}:s3:::${SourceS3}/*'
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId

  CrossRegionReplicationRole:
    Condition: IsSourceAccount
    Type: AWS::IAM::Role
    Properties:
      Path:
        Fn::Sub: /${ResourcePrefix}/
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - "s3.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      PermissionsBoundary: !If [NeedPermissionsBoundary, !Ref PermissionsBoundary, !Ref "AWS::NoValue"]
      Policies:
        - PolicyName: CrossRegionPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetReplicationConfiguration
                  - s3:ListBucket
                Resource: !Sub "arn:${AWS::Partition}:s3:::${ResourcePrefix}-${AWS::AccountId}-local"
              - Effect: Allow
                Action:
                  - s3:GetObjectVersionForReplication
                  - s3:GetObjectVersionAcl
                Resource: !Sub "arn:${AWS::Partition}:s3:::${ResourcePrefix}-${AWS::AccountId}-local/*"
              - Effect: Allow
                Action:
                  - s3:ReplicateObject
                  - s3:ReplicateDelete
                  - s3:ReplicateTags
                  - s3:GetObjectVersionTagging
                Resource:
                  Fn::Sub: "arn:${AWS::Partition}:s3:::${ResourcePrefix}-${DestinationAccountId}-shared/cur/${AWS::AccountId}/*"

####
# Local CUR
####

  ## Deploy CUR natively via CFN resource if we are in a region that supports it
  LocalCurInSource:
    Type: AWS::CUR::ReportDefinition
    Condition: DeployCURViaCFNInSource
    DependsOn:
      - SourceS3BucketPolicy
    Properties:
      AdditionalArtifacts:
        - ATHENA
      AdditionalSchemaElements:
        - RESOURCES
        - !If [ NeedSCAD, SPLIT_COST_ALLOCATION_DATA, !Ref "AWS::NoValue"]
      Compression: Parquet
      Format: Parquet
      RefreshClosedReports: True
      ReportName: !Ref ResourcePrefix
      ReportVersioning: OVERWRITE_REPORT
      S3Bucket: !Ref SourceS3
      S3Prefix: !Sub "cur/${AWS::AccountId}"
      S3Region: !Ref AWS::Region
      TimeUnit: HOURLY

  LocalCurInDestination:
    Type: AWS::CUR::ReportDefinition
    Condition: DeployCURViaCFNInDestination
    DependsOn:
      - DestinationS3BucketPolicy # Conditional DependsOn is not supported, so we need 2 resources
    Properties:
      AdditionalArtifacts:
        - ATHENA
      AdditionalSchemaElements:
        - RESOURCES
        - !If [ NeedSCAD, SPLIT_COST_ALLOCATION_DATA, !Ref "AWS::NoValue"]
      Compression: Parquet
      Format: Parquet
      RefreshClosedReports: True
      ReportName: !Ref ResourcePrefix
      ReportVersioning: OVERWRITE_REPORT
      S3Bucket: !Ref DestinationS3
      S3Prefix: !Sub "cur/${AWS::AccountId}"
      S3Region: !Ref AWS::Region
      TimeUnit: HOURLY


  # Deploy CUR via lambda due to missing cfn resource definition
  # AWS::CUR::ReportDefinition outside us-east-1
  CURinUSEAST1:
    Type: Custom::CURCreator
    Condition: DeployCURViaLambda
    Properties:
      ServiceToken: !GetAtt CIDLambdaCURCreator.Arn
      BucketPolicyWait: !If [ IsDestinationAccount, !Ref DestinationS3BucketPolicy, !Ref SourceS3BucketPolicy ]
      ReportDefinition:
        AdditionalArtifacts:
          - ATHENA
        AdditionalSchemaElements:
          - RESOURCES
          - !If [ NeedSCAD, SPLIT_COST_ALLOCATION_DATA, !Ref "AWS::NoValue"]
        Compression: Parquet
        Format: Parquet
        RefreshClosedReports: True
        ReportName: !Ref ResourcePrefix
        ReportVersioning: OVERWRITE_REPORT
        S3Bucket: !If [ IsDestinationAccount, !Ref DestinationS3, !Ref SourceS3 ]
        S3Prefix: !Sub "cur/${AWS::AccountId}"
        S3Region: !Ref AWS::Region
        TimeUnit: HOURLY

###########################################################################
# Lambda CUR Creator: used to create cur from outside us-east-1
###########################################################################

  CIDLambdaCURCreatorRole: #Execution role for the custom resource CIDLambdaAnalyticsExecutor
    Type: AWS::IAM::Role
    Condition: DeployCURViaLambda
    Properties:
      Path:
        Fn::Sub: /${ResourcePrefix}/
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      PermissionsBoundary: !If [NeedPermissionsBoundary, !Ref PermissionsBoundary, !Ref "AWS::NoValue"]
      Policies:
        - PolicyName: "ExecutionDefault"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
              - logs:CreateLogGroup
              Resource:
              - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${ResourcePrefix}-CID-CURCreator"
              - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${ResourcePrefix}-CID-CURCreator:*"
              - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${ResourcePrefix}-CID-CURCreator:*:*"
        - PolicyName: "ExecutionSpecific"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - cur:PutReportDefinition
              - cur:ModifyReportDefinition
              - cur:DeleteReportDefinition
              Resource:
                - Fn::Sub: arn:${AWS::Partition}:cur:*:${AWS::AccountId}:definition/*

  CIDLambdaCURCreator:
    Type: AWS::Lambda::Function
    Condition: DeployCURViaLambda
    Properties:
      Runtime: python3.13
      FunctionName:
        Fn::Sub: ${ResourcePrefix}-CID-CURCreator
      Handler: index.lambda_handler
      MemorySize: 128
      Role:
        Fn::GetAtt: CIDLambdaCURCreatorRole.Arn
      Timeout: 15
      Code:
        ZipFile: |
          import os
          import json
          import uuid

          import boto3
          import cfnresponse

          region = os.environ['AWS_REGION']

          # CUR only exists in us-east-1 and cn-northwest-1 regions
          if region.startswith('cn-'):
              region = 'cn-northwest-1'
          else:
              region = 'us-east-1'

          client = boto3.client('cur', region_name=region)

          def lambda_handler(event, context):

            print(json.dumps(event))
            reason = ""

            try:
              report = event['ResourceProperties']['ReportDefinition']
              report_name = event['ResourceProperties']['ReportDefinition']['ReportName']

              refresh_closed_report = event['ResourceProperties']['ReportDefinition']["RefreshClosedReports"]
              if refresh_closed_report in ["True", "true"]:
                  report["RefreshClosedReports"] = True
              elif refresh_closed_report in ["False", "false"]:
                  report["RefreshClosedReports"] = False
              else:
                  raise Exception("RefreshClosedReports is not a boolean")

              if event['RequestType'] == 'Create':
                  res = client.put_report_definition(
                      ReportDefinition=report
                  )
                  print(json.dumps(res))
              elif event['RequestType'] == 'Update':
                  old_report_name = event['OldResourceProperties']['ReportDefinition']['ReportName']
                  if report["ReportName"] != old_report_name:
                      res = client.put_report_definition(
                          ReportDefinition=report
                      )
                      print(json.dumps(res))
                  else:
                      res = client.modify_report_definition(
                          ReportName=old_report_name,
                          ReportDefinition=report
                      )
                      print(json.dumps(res))
              elif event['RequestType'] == 'Delete':
                  try:
                      res = client.delete_report_definition(
                          ReportName=report_name
                      )
                      print(json.dumps(res))
                  except:
                      pass # Do not block deletion
              else:
                  raise Exception("Unknown operation: " + event['RequestType'])

            except Exception as e:
                reason = str(e)
                print(e)
            finally:
                physicalResourceId = event.get('ResourceProperties',{}).get('ReportDefinition').get('ReportName', None) or str(uuid.uuid1())
                if reason:
                    print("FAILURE")
                    cfnresponse.send(event, context, cfnresponse.FAILED, {"Data": reason }, physicalResourceId)
                else:
                    print("SUCCESS")
                    cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, physicalResourceId)
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: 'W89'
            reason: "This Lambda does not require VPC"
          - id: 'W92'
            reason: "One Time execution. No need for ReservedConcurrentExecutions"


###########################################################################
# Analytics: used by CID team to track adoption, by retrieving AWS AccountId
###########################################################################

  CIDLambdaAnalyticsRole: #Execution role for the custom resource CIDLambdaAnalyticsExecutor
    Type: AWS::IAM::Role
    Properties:
      Path:
        Fn::Sub: /${ResourcePrefix}/
      #RoleName: CID-Analytics
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      PermissionsBoundary: !If [NeedPermissionsBoundary, !Ref PermissionsBoundary, !Ref "AWS::NoValue"]
      Policies:
        - PolicyName: "ExecutionDefault"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
              - logs:CreateLogGroup
              Resource:
              - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${ResourcePrefix}-CID-Analytics"
              - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${ResourcePrefix}-CID-Analytics:*"
              - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${ResourcePrefix}-CID-Analytics:*:*"

  CIDLambdaAnalytics:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.13 # before updating 
      FunctionName:
        Fn::Sub: ${ResourcePrefix}-CID-Analytics
      Handler: index.lambda_handler
      MemorySize: 128
      Role:
        Fn::GetAtt: CIDLambdaAnalyticsRole.Arn
      Timeout: 15
      Environment:
        Variables:
          API_ENDPOINT: https://okakvoavfg.execute-api.eu-west-1.amazonaws.com/
      Code:
        ZipFile: |
          import json
          import boto3
          import os
          import cfnresponse
          import urllib3
          import uuid

          http = urllib3.PoolManager()
          endpoint=os.environ["API_ENDPOINT"]
          account_id=boto3.client("sts").get_caller_identity()["Account"]

          def execute_request(action,dashboard_id,via_key):
              try:
                  message=None
                  payload={'dashboard_id': dashboard_id, 'account_id': account_id, via_key: 'CFN'}
                  encoded_data = json.dumps(payload).encode('utf-8')
                  r = http.request(action,endpoint,body=encoded_data,headers={'Content-Type': 'application/json'})
                  if r.status!=200:
                      message=f"This will not fail the deployment. There has been an issue logging action {action}  for dashboard {dashboard_id} and account {account_id}, server did not respond with a 200 response,actual  status: {r.status}, response data {r.data.decode('utf-8')}. This issue will be ignored"
              except urllib3.exceptions.HTTPError as e:
                message=f"Issue logging action {action}  for dashboard {dashboard_id} and account {account_id}, due to a urllib3 exception {str(e)} . This issue will be ignored"
              return message


          def register_deployment(action,dashboards):
              message=f"Successfully logged  {action} for {dashboards}"
              for dashboard_id in dashboards:
                  if action == 'CREATE':
                      message=execute_request('PUT',dashboard_id,'created_via')
                  elif action == 'UPDATE':
                      message=execute_request('PATCH',dashboard_id,'updated_via')
                  elif action == 'DELETE':
                      message=execute_request('DELETE',dashboard_id,'deleted_via')
                  if message is None:
                      message=f"Successfully logged  {action} for {dashboards} "
              #Do not stop deployment if we're not able to successfully record this deployment, still return true
              return ("True",message)


          def lambda_handler(event, context):
              if event['RequestType'] == 'Create':
                  res, reason = register_deployment('CREATE',event['ResourceProperties']['DeploymentType'])
              elif event['RequestType'] == 'Update':
                  res, reason = register_deployment('UPDATE',event['ResourceProperties']['DeploymentType'])
              elif event['RequestType'] == 'Delete':
                  res, reason = register_deployment('DELETE',event['ResourceProperties']['DeploymentType'])
              else:
                  res = False
                  reason = "Unknown operation: " + event['RequestType']

              response_data = {'Reason': reason}
              print(response_data)
              if 'PhysicalResourceId' in event.keys() and event['PhysicalResourceId'] is not None:
                  physicalResourceId=event['PhysicalResourceId']
              else:
                  physicalResourceId=str(uuid.uuid1())
              if res:
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data, physicalResourceId )
              else:
                  cfnresponse.send(event, context, cfnresponse.FAILED, response_data, physicalResourceId )
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: 'W89'
            reason: "This Lambda does not require VPC"
          - id: 'W92'
            reason: "One Time execution. No need for ReservedConcurrentExecutions"

  CIDLambdaAnalyticsExecutor:
    Type: Custom::CIDLambdaAnalyticsExecutor
    Properties:
      ServiceToken: !GetAtt CIDLambdaAnalytics.Arn
      DeploymentType:
        - Fn::If:
          - IsDestinationAccount
          - "cid-cur-aggregator"
          - "cid-cur-replication"

Outputs:
  GovernanceBucketName:
    Description: Bucket where to store and aggregate CUR
    Value:
      Fn::Sub: ${ResourcePrefix}-${DestinationAccountId}-shared
  LocalAccountBucket:
    Condition: IsSourceAccount
    Description: Local Bucket Name which replicate objects to centralized bucket
    Value:
      Fn::Sub: ${ResourcePrefix}-${AWS::AccountId}-local
