# https://github.com/awslabs/cid-data-collection-framework/blob/main/data-collection/deploy/deploy-data-collection.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: CID Data Collection Stack v3.14.1 - AWS Solution SO9011
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: 'Deployment parameters'
        Parameters:
          - ManagementAccountID
          - RegionsInScope
          - ResourcePrefix
          - ManagementAccountRole
          - MultiAccountRoleName
          - DestinationBucket
          - DatabaseName
          - Schedule
          - ScheduleFrequent
          - CFNSourceBucket
          - DataBucketsKmsKeysArns
      - Label:
          default: 'Available modules'
        Parameters:
          - IncludeBackupModule
          - IncludeBudgetsModule
          - IncludeComputeOptimizerModule
          - IncludeCostAnomalyModule
          - IncludeSupportCasesModule
          - IncludeECSChargebackModule
          - IncludeHealthEventsModule
          - IncludeInventoryCollectorModule
          - IncludeOrgDataModule
          - IncludeRDSUtilizationModule
          - IncludeRightsizingModule
          - IncludeTAModule
          - IncludeTransitGatewayModule
          - IncludeAWSFeedsModule
          - IncludeISVFeedsModule
          - IncludeLicenseManagerModule
          - IncludeQuickSightModule
          - IncludeServiceQuotasModule
          - IncludeEUCUtilizationModule
          - IncludeResilienceHubModule
          - IncludeMarketplaceModule
          - IncludeReferenceModule          
      - Label:
          default: 'EUC (End User Compute) Module Configuration'
        Parameters:
          - EUCAccountIDs
    ParameterLabels:
      DestinationBucket:
        default: 'Destination S3 bucket prefix'
      ManagementAccountRole:
        default: 'Management account role'
      ManagementAccountID:
        default: 'Comma Delimited list of Account IDs for all Management Account IDs'
      MultiAccountRoleName:
        default: 'Multi Account Role Name'
      Schedule:
        default: "Schedule can be swapped to cron, for example: cron(0 8 1,15 * ? *) for 08:00 the 1st and 15th days of the month"
      ScheduleFrequent:
        default: "Schedule can be swapped to cron, for example: cron(0 8 1,15 * ? *) for 08:00 the 1st and 15th days of the month"
      RegionsInScope:
        default: "Comma Delimited list of AWS regions from which data about resources will be collected. Example: us-east-1,eu-west-1,ap-northeast-1"
      DatabaseName:
        default: "Name of the Athena database to be created to hold collected data"
      ResourcePrefix:
        default: "Role Prefix"
      CFNSourceBucket:
        default: "DO NOT CHANGE - A bucket that contains WA-Labs CloudFormation templates. Must be always 'aws-managed-cost-intelligence-dashboards'"
      DataBucketsKmsKeysArns:
        default: ""
      IncludeTAModule:
        default: 'Include AWS Trusted Advisor Data Collection Module'
      IncludeRightsizingModule:
        default: 'Include Rightsizing Recommendations Data Collection Module'
      IncludeCostAnomalyModule:
        default: 'Include Cost Anomalies Data Collection Module'
      IncludeSupportCasesModule:
        default: 'Include Support Cases Data Collection Module'
      IncludeInventoryCollectorModule:
        default: 'Include Inventory Collector Module'
      IncludeComputeOptimizerModule:
        default: 'Include AWS Compute Optimizer Data Collection Module'
      IncludeECSChargebackModule:
        default: 'Include ECS Chargeback Data Collection Module'
      IncludeRDSUtilizationModule:
        default: 'Include RDS Utilization Data Collection Module'
      IncludeEUCUtilizationModule:
        default: 'Include WorkSpaces Utilization Data Collection Module'
      EUCAccountIDs:
        default: 'WorkSpaces Account IDs (optional)'
      IncludeOrgDataModule:
        default: 'Include AWS Organization Data Collection Module'
      IncludeBudgetsModule:
        default: 'Include AWS Budgets Collection Module'
      IncludeTransitGatewayModule:
        default: 'Include AWS TransitGateway Collection Module'
      IncludeBackupModule:
        default: 'Include AWS Backup Collection Module'
      IncludeAWSFeedsModule:
        default: 'Include AWS Feeds Module'
      IncludeISVFeedsModule:
        default: 'Include ISV Feeds Module'
      IncludeHealthEventsModule:
        default: 'Include AWS Health Events Module'
      IncludeLicenseManagerModule:
        default: 'Include Marketplace Licensing Collection'
      IncludeServiceQuotasModule:
        default: 'Include Service Quota Data Collection'
      IncludeQuickSightModule:
        default: 'Include QuickSight User Collection Module'
      IncludeResilienceHubModule:
        default: 'Include Resilience Hub Data Collection Module'
      IncludeMarketplaceModule:
        default: 'Include Marketplace Data Collection Module'

Mappings:
  RegionMap:
    # Only support regions that have QuickSight
    ap-northeast-1: {CodeBucket: aws-managed-cost-intelligence-dashboards-ap-northeast-1 }
    ap-northeast-2: {CodeBucket: aws-managed-cost-intelligence-dashboards-ap-northeast-2 }
    ap-south-1:     {CodeBucket: aws-managed-cost-intelligence-dashboards-ap-south-1 }
    ap-southeast-1: {CodeBucket: aws-managed-cost-intelligence-dashboards-ap-southeast-1 }
    ap-southeast-2: {CodeBucket: aws-managed-cost-intelligence-dashboards-ap-southeast-2 }
    ca-central-1:   {CodeBucket: aws-managed-cost-intelligence-dashboards-ca-central-1 }
    eu-central-1:   {CodeBucket: aws-managed-cost-intelligence-dashboards-eu-central-1 }
    eu-north-1:     {CodeBucket: aws-managed-cost-intelligence-dashboards-eu-north-1 }
    eu-west-1:      {CodeBucket: aws-managed-cost-intelligence-dashboards-eu-west-1 }
    eu-west-2:      {CodeBucket: aws-managed-cost-intelligence-dashboards-eu-west-2 }
    eu-west-3:      {CodeBucket: aws-managed-cost-intelligence-dashboards-eu-west-3 }
    sa-east-1:      {CodeBucket: aws-managed-cost-intelligence-dashboards-sa-east-1 }
    us-east-1:      {CodeBucket: aws-managed-cost-intelligence-dashboards-us-east-1 }
    us-east-2:      {CodeBucket: aws-managed-cost-intelligence-dashboards-us-east-2 }
    us-west-1:      {CodeBucket: aws-managed-cost-intelligence-dashboards-us-west-1 }
    us-west-2:      {CodeBucket: aws-managed-cost-intelligence-dashboards-us-west-2 }
  StepFunctionCode:
    main-state-machine:           {TemplatePath: cfn/data-collection/v3.14.1/source/step-functions/main-state-machine.json}
    crawler-state-machine:        {TemplatePath: cfn/data-collection/v3.14.1/source/step-functions/crawler-state-machine.json}
    standalone-state-machine:     {TemplatePath: cfn/data-collection/v3.14.1/source/step-functions/standalone-state-machine.json}
    health-detail-state-machine:  {TemplatePath: cfn/data-collection/v3.14.1/source/step-functions/health-detail-state-machine.json}

Parameters:
  DestinationBucket:
    Type: String
    Description: "A Prefix of S3 Bucket name that will hold information. A Bucket name will be concatenated with account_id automatically (ex: cid-data-123456123456). You can keep this parameter as is."
    AllowedPattern: (?=^.{3,36}$)(?!^(\d+\.)+\d+$)(^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9\-])$)
    Default: cid-data-
  ManagementAccountRole:
    Type: String
    Description: The name of the IAM role that will be deployed in the management account which can retrieve AWS Organization data. KEEP THE SAME AS WHAT IS DEPLOYED INTO MANAGEMENT ACCOUNT
    Default: Lambda-Assume-Role-Management-Account
  ManagementAccountID:
    Type: String
    AllowedPattern: ^(\d{12})(,\d{12})*$
    Description: "(Ex: 123456789,098654321,789054312) List of Payer IDs you wish to collect data for. Can just be one Accounts"
  MultiAccountRoleName:
    Type: String
    Description: The name of the IAM role that will be deployed from the management account to linked accounts as a read only role. KEEP THE SAME AS WHAT IS DEPLOYED INTO MANAGEMENT ACCOUNT
    Default: "Optimization-Data-Multi-Account-Role"
  Schedule:
    Type: String
    Description: "EventBridge schedule to trigger data collection for Trusted Advisor, Compute Optimizer, Organizations Data, Rightsizing, RDS Utilization, Inventory Collector, Transit Gateway, Backup, and ECS Chargeback modules (see docs for tailoring the schedule for each module). Recommended value: rate(14 days). Increasing data collection frequency will trigger additional cost, avoid setting the data collection schedule to more than once per day."
    Default: "rate(14 days)"
  ScheduleFrequent:
    Type: String
    Description: "EventBridge schedule to trigger data collection for Cost Anomalies, Budgets, Support Cases and Health Events modules (see docs for tailoring the schedule for each module). Recommended value: rate(1 day).  Increasing data collection frequency will trigger additional cost, avoid setting the data collection schedule to more than once per day."
    Default: "rate(1 day)"
  RegionsInScope:
    Type: String
    Description: "Ex: us-east-1,us-east-2,us-west-1,us-west-2,eu-central-1,eu-west-1,eu-west-2,eu-west-3  if empty, the current region will be used. You can add regions later by updating the stack."
    Default: ""
    AllowedPattern: (^(([a-z]{2}-([a-z]-?){4,10}-[1-9]{1}|[a-z]{2}-([a-z]-?){4,10}-[1-9]{1},?)+|)$)
  DatabaseName:
    Type: String
    Description: Name of the Athena database to be created to hold lambda information
    AllowedPattern: ([a-z0-9_]*?$)
    Default: optimization_data
  ResourcePrefix:
    Type: String
    Description: This prefix will be placed in front of all resources created. Note you may wish to add a dash at the end to make more readable (e.g. 'prefix-'). This parameter CANNOT BE UPDATED. Delete and re-create stack if needed an update.
    Default: "CID-DC-"
  CFNSourceBucket:
    Type: String
    Description: "DO NOT CHANGE - A bucket that contains WA-Labs CloudFormation templates. Must be always 'aws-managed-cost-intelligence-dashboards'"
    Default: "aws-managed-cost-intelligence-dashboards"
  DataBucketsKmsKeysArns:
    Type: String
    Description: "ARNs of KMS Keys for data buckets and/or Glue Catalog. Comma separated list, no spaces. Keep empty if data Buckets and Glue Catalog are not Encrypted with KMS. You can also set it to '*' to grant decrypt permission for all the keys."
    Default: ""
  IncludeTAModule:
    Type: String
    Description: Collects AWS Trusted Advisor recommendations data
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeRightsizingModule:
    Type: String
    Description: "Collects AWS Cost Explorer Rightsizing Recommendations"
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeCostAnomalyModule:
    Type: String
    Description: "Collects AWS Cost Explorer Cost Anomalies Recommendations"
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeSupportCasesModule:
    Type: String
    Description: "Collects AWS Support Cases Data"
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeInventoryCollectorModule:
    Type: String
    Description: Collects data about AMIs, EBS volumes and snapshots
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeComputeOptimizerModule:
    Type: String
    Description: Collects AWS Compute Optimizer service recommendations
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeECSChargebackModule:
    Type: String
    Description: Collects data which shows costs associated with ECS Tasks leveraging EC2 instances within a Cluster
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeRDSUtilizationModule:
    Type: String
    Description: Collects RDS CloudWatch metrics from your accounts
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeEUCUtilizationModule:
    Type: String
    Description: Collects WorkSpaces CloudWatch metrics from your accounts
    AllowedValues: ['yes', 'no']
    Default: 'no'
  EUCAccountIDs:
    Type: String
    Description: "Optional, If you enable EUC Utilization or Inventory module and you use Amazon WorkSpaces, please provide a comma-separated list of account IDs where WorkSpaces are deployed. Or you can set * to collect from all linked accounts in the Organization."
    Default: "*"
  IncludeOrgDataModule:
    Type: String
    Description: Collects AWS Organizations data such as account Id, account name, organization parent and specified tags
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeBudgetsModule:
    Type: String
    Description: Collects AWS Budgets
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeTransitGatewayModule:
    Type: String
    Description: Collects AWS TransitGateway data
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeBackupModule:
    Type: String
    Description: Collects AWS Backup data
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeAWSFeedsModule:
    Type: String
    Description: Collects AWS Feeds data
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeISVFeedsModule:
    Type: String
    Description: Collects ISV Feeds data
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeHealthEventsModule:
    Type: String
    Description: Collects AWS Health Events data
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeLicenseManagerModule:
    Type: String
    Description: Collects Marketplace Licenses and Grants
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeQuickSightModule:
    Type: String
    Description: Collects Amazon QuickSight users and gropus details
    AllowedValues: ['yes', 'no']
    Default: 'no'
  IncludeServiceQuotasModule:
   Type: String
   Description: Collects AWS Service Quotas data
   AllowedValues: ['yes', 'no']
   Default: 'no'
  IncludeResilienceHubModule:
   Type: String
   Description: Collects AWS Resilience Hub data
   AllowedValues: ['yes', 'no']
   Default: 'no'
  IncludeMarketplaceModule:
   Type: String
   Description: Collects AWS Marketplace data
   AllowedValues: ['yes', 'no']
   Default: 'no'
  IncludeReferenceModule:
    Type: String
    Description: Collects Reference data for other modules
    AllowedValues: ['yes', 'no']
    Default: 'no'
Conditions:
  DeployTAModule: !Equals [ !Ref IncludeTAModule, "yes"]
  DeployRightsizingModule: !Equals [ !Ref IncludeRightsizingModule, "yes"]
  DeployCostAnomalyModule: !Equals [ !Ref IncludeCostAnomalyModule, "yes"]
  DeploySupportCasesModule: !Equals [ !Ref IncludeSupportCasesModule, "yes"]
  DeployInventoryCollectorModule: !Equals [ !Ref IncludeInventoryCollectorModule, "yes"]
  DeployComputeOptimizerModule: !Equals [ !Ref IncludeComputeOptimizerModule, "yes"]
  DeployEcsChargebackModule: !Equals [ !Ref IncludeECSChargebackModule, "yes"]
  DeployRDSUtilizationModule: !Equals [ !Ref IncludeRDSUtilizationModule, "yes"]
  DeployEUCUtilizationModule: !Equals [ !Ref IncludeEUCUtilizationModule, "yes"]
  DeployOrgDataModule: !Equals [ !Ref IncludeOrgDataModule, "yes"]
  DeployBudgetsModule: !Equals [ !Ref IncludeBudgetsModule, "yes"]
  DeployTransitGatewayModule: !Equals [ !Ref IncludeTransitGatewayModule, "yes"]
  DeployBackupModule: !Equals [ !Ref IncludeBackupModule, "yes"]
  DeployAWSFeedsModule: !Equals [ !Ref IncludeAWSFeedsModule, "yes"]
  DeployISVFeedsModule: !Equals [ !Ref IncludeISVFeedsModule, "yes"]
  DeployHealthEventsModule: !Equals [ !Ref IncludeHealthEventsModule, "yes"]
  DeployLicenseManagerModule: !Equals [ !Ref IncludeLicenseManagerModule, "yes"]
  DeployQuickSightModule: !Equals [ !Ref IncludeQuickSightModule, "yes"]
  DeployServiceQuotasModule: !Equals [ !Ref IncludeServiceQuotasModule, "yes"]
  DeployResilienceHubModule: !Equals [ !Ref IncludeResilienceHubModule, "yes"]
  DeployMarketplaceModule: !Equals [ !Ref IncludeMarketplaceModule, "yes"]
  DeployPricingModule: !Or
    - !Condition DeployInventoryCollectorModule
    - !Condition DeployRDSUtilizationModule
    - !Condition DeployEUCUtilizationModule
  DeployAccountCollector: !Or
    - !Or
      - !Condition DeployTAModule
      - !Condition DeployRightsizingModule
      - !Condition DeployCostAnomalyModule
      - !Condition DeploySupportCasesModule
      - !Condition DeployInventoryCollectorModule
      - !Condition DeployEcsChargebackModule
      - !Condition DeployRDSUtilizationModule
      - !Condition DeployOrgDataModule
      - !Condition DeployBudgetsModule
      - !Condition DeployResilienceHubModule
    - !Or
      - !Condition DeployMarketplaceModule
      - !Condition DeployBackupModule
      - !Condition DeployTransitGatewayModule
      - !Condition DeployHealthEventsModule
      - !Condition DeployLicenseManagerModule
      - !Condition DeployQuickSightModule
      - !Condition DeployServiceQuotasModule
      - !Condition DeployEUCUtilizationModule
      - !Condition DeployComputeOptimizerModule
  RegionsInScopeIsEmpty: !Equals
    - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces
    - ""
  ProdCFNTemplateUsed: !Equals [ !Ref CFNSourceBucket,  'aws-managed-cost-intelligence-dashboards' ]
  NeedDataBucketsKms: !Not [ !Equals [ !Ref DataBucketsKmsKeysArns, "" ] ]
  DeployIncludeReferenceModule: !Equals [ !Ref IncludeReferenceModule, "yes"]
  
Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Sub "${DestinationBucket}${AWS::AccountId}"
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
            BlockPublicAcls : true
            BlockPublicPolicy : true
            IgnorePublicAcls : true
            RestrictPublicBuckets : true
      LifecycleConfiguration:
        Rules:
          - Id: RemoveNonCurrentVersions
            Status: Enabled
            NoncurrentVersionExpiration:
              NoncurrentDays: 7
              NewerNoncurrentVersions: 1
            # ExpirationInDays: 365 # Set Expiration of all objects here (not recommended but can be useful in case of big workloads) 
          - Id: DeleteIncompleteMultipartUploadsAndExpiredDeleteMarkers
            Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            ExpiredObjectDeleteMarker: true
          - Id: CleanupOldPricingFiles
            Status: Enabled
            Prefix: pricing/pricing-
            ExpirationInDays: 7
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W51 #S3 bucket should likely have a bucket policy
            reason: "No bucket policy required by default"
          - id: W35 #S3 Bucket should have access logging configured
            reason: "The bucket is used with Athena and logging can be extensive."

  S3DataBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: AllowSSLOnly
            Action: s3:*
            Effect: Deny
            Principal: "*"
            Resource:
              - !Sub "${S3Bucket.Arn}"
              - !Sub "${S3Bucket.Arn}/*"
            Condition:
              Bool:
                aws:SecureTransport: false
          - Sid: AllowTLS12Only
            Action: s3:*
            Effect: Deny
            Principal: "*"
            Resource:
              - !Sub "${S3Bucket.Arn}"
              - !Sub "${S3Bucket.Arn}/*"
            Condition:
              NumericLessThan:
                s3:TlsVersion: 1.2

  # GlueDatabase: # GlueDatabase can be already created by crawler in previous versions. So we cannot create it here. See Init CustomResource
  #   Type: "AWS::Glue::Database"
  #   Properties:
  #     CatalogId: !Ref AWS::AccountId
  #     DatabaseInput:
  #       Description: "CID Data Collection"
  #       Name: !Ref DatabaseName

  GlueRole:
    Type: AWS::IAM::Role
    DependsOn:
      - InitExecutor # Custom resource that creates Glue database
    Properties:
      RoleName: !Sub "${ResourcePrefix}Glue-Crawler"
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - !Sub "glue.${AWS::URLSuffix}"
        Version: 2012-10-17
      Path: /
      Policies:
        - PolicyName: S3Read
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListBucket
                Resource:
                 - !Sub "arn:${AWS::Partition}:s3:::${DestinationBucket}${AWS::AccountId}"
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource:
                 - !Sub "arn:${AWS::Partition}:s3:::${DestinationBucket}${AWS::AccountId}/*"
        - !If
          - NeedDataBucketsKms
          - PolicyName: "KMS"
            PolicyDocument:
              Version: "2012-10-17"
              Statement:
                - Effect: "Allow"
                  Action:
                    - "kms:Decrypt"
                  Resource: !Split [ ',', !Ref DataBucketsKmsKeysArns ]
          - !Ref AWS::NoValue
        - PolicyName: "Glue"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - glue:GetDatabase
                  - glue:GetDatabases
                  - glue:CreateTable
                  - glue:GetTable
                  - glue:GetTables
                  - glue:UpdateTable
                  - glue:GetTableVersion
                  - glue:GetTableVersions
                  - glue:DeleteTableVersion
                  - glue:CreatePartition
                  - glue:BatchCreatePartition
                  - glue:GetPartition
                  - glue:GetPartitions
                  - glue:BatchGetPartition
                  - glue:UpdatePartition
                  - glue:DeletePartition
                  - glue:TagResource
                Resource:
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:catalog"
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/${DatabaseName}"
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:table/${DatabaseName}/*"
        - PolicyName: "CloudWatch"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub "arn:${AWS::Partition}:logs:*:*:/aws-glue/*"
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W28 # Resource found with an explicit name, this disallows updates that require replacement of this resource
            reason: "Need explicit name to identify role actions"

  LambdaAnalyticsRole: #Execution role for the custom resource for Analytics Lambda
    Type: AWS::IAM::Role
    Properties:
      Path:
        Fn::Sub: /${ResourcePrefix}/
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !Sub "lambda.${AWS::URLSuffix}"
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"

  LambdaAnalytics:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.13
      FunctionName: !Sub ${ResourcePrefix}analytics-Lambda
      Description: "Lambda function to collect general deployment metrics"
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt LambdaAnalyticsRole.Arn
      Timeout: 15
      Environment:
        Variables:
          CID_ANALYTICS_ENDPOINT: https://cid.workshops.aws.dev/adoption-tracking
      Code:
        ZipFile: |
          import os
          import json
          import urllib3
          import cfnresponse
          
          def lambda_handler(event, context):
              print(json.dumps(event))
              try:
                  urllib3.PoolManager().request(
                      method={'CREATE': 'PUT', 'UPDATE': 'PATCH', 'DELETE': 'DELETE'}[event['RequestType'].upper()],
                      url=os.environ['CID_ANALYTICS_ENDPOINT'],
                      body=json.dumps({
                          'id': 'data-collection-lab/' + event['ResourceProperties'].get('Name'),
                          'account_id': context.invoked_function_arn.split(':')[4],
                          'via': 'CFN'
                      }).encode('utf-8'),
                      headers={'Content-Type': 'application/json'}
                  )
              except Exception as exc:
                  print(f"Exception occurred: {exc}")
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Reason': 'success'})

  LambdaInitRole: #Execution role for the custom resource for Init Lambda
    Type: AWS::IAM::Role
    Properties:
      Path:
        Fn::Sub: /${ResourcePrefix}/
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !Sub "lambda.${AWS::URLSuffix}"
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      Policies:
        - PolicyName: "Glue"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - glue:GetDatabase
                  - glue:CreateDatabase
                  - glue:DeleteDatabase
                  - glue:GetTable
                  - glue:GetTables
                  - glue:CreateTable
                  - glue:DeleteTable
                  - glue:TagResource
                Resource:
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:catalog"
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/${DatabaseName}"
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:table/${DatabaseName}/*"
        - !If
          - NeedDataBucketsKms
          - PolicyName: "KMS"
            PolicyDocument:
              Version: "2012-10-17"
              Statement:
                - Effect: "Allow"
                  Action:
                    - "kms:Decrypt"
                  Resource: !Split [ ',', !Ref DataBucketsKmsKeysArns ]
          - !Ref AWS::NoValue

  KmsPolicyForCidResources:
    Type: AWS::IAM::Policy
    Condition: NeedDataBucketsKms
    Properties:
      PolicyName: !Sub "${ResourcePrefix}AwsDataCollectionKmsDecryption"
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - 'kms:Decrypt'
            Resource: !Split [ ',', !Ref DataBucketsKmsKeysArns ]
      Roles:
        - !Ref StepFunctionExecutionRole
        - !Ref LambdaManageGlueTableRole
        - !Ref GlueRole

  LambdaInit:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.13
      FunctionName: !Sub ${ResourcePrefix}init-Lambda
      Description: "Lambda function to initialize the stack"
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt LambdaInitRole.Arn
      Timeout: 300
      Environment:
        Variables:
          DATABASE_NAME: !Ref DatabaseName
          RESOURCE_PREFIX: !Ref ResourcePrefix
          BUCKET_NAME: !Sub "${DestinationBucket}${AWS::AccountId}"
      Code:
        ZipFile: |
          import os
          import json
          import uuid
          import urllib3
          import boto3

          database_name = os.environ['DATABASE_NAME']
          resource_prefix = os.environ['RESOURCE_PREFIX']

          def lambda_handler(event, context): #pylint: disable=unused-argument
              print(json.dumps(event))
              try:
                  action = event.get('RequestType').upper()
                  if action not in ['CREATE', 'UPDATE', 'DELETE']:
                      raise Exception(f"Unknown RequestType {action}") #pylint: disable=broad-exception-raised
                  func = {'CREATE': create, 'DELETE': delete, 'UPDATE': update}.get(action)
                  res, reason = func()
              except Exception as exc: #pylint: disable=broad-exception-caught
                  if 'Insufficient Lake Formation permission' in str(exc):
                      res, reason = 'FAILED', 'Lake Formation is not supported yet. Please use account without Lake Formation.'
                  else:
                      res, reason = 'FAILED', str(exc)
              body = {
                  'Status': res,
                  'Reason': reason,
                  'PhysicalResourceId': event.get('PhysicalResourceId', str(uuid.uuid1())),
                  'StackId': event.get('StackId'),
                  'RequestId': event.get('RequestId'),
                  'LogicalResourceId': event.get('LogicalResourceId'),
                  'NoEcho': False,
                  'Data':  {'Reason': reason},
              }
              json_body=json.dumps(body)
              print(json_body)
              url = event.get('ResponseURL')
              if not url:
                  return
              try:
                  response = urllib3.PoolManager().request('PUT', url, body=json_body, headers={'content-type' : '', 'content-length' : str(len(json_body))}, retries=False)
                  print(f"Status code: {response}")
              except Exception as exc: #pylint: disable=broad-exception-caught
                  print("Failed sending PUT to CFN: " + str(exc))

          def create():
              create_glue_database()
              return  'SUCCESS', 'success'

          def update():
              return  'SUCCESS', 'nothing to do'

          def delete():
              return  'SUCCESS', 'nothing to do'

          def create_glue_database():
              glue_client = boto3.client('glue')
              try:
                  glue_client.get_database(Name=database_name)
              except glue_client.exceptions.EntityNotFoundException:
                  glue_client.create_database(DatabaseInput={'Name': database_name})
                  print(f"Created database '{database_name}'")
              else:
                  # Delete all tables updated by previous versions of crawlers.
                  # If not crawler will not be able to update the table and will create a new one with a random name.
                  # The exception to this is if a table was created using a date project for which no crawler will touch it
                  for table in glue_client.get_paginator('get_tables').paginate(DatabaseName=database_name).search('TableList'):
                      table_name = table.get('Name')
                      updated_by = table.get('Parameters', {}).get('UPDATED_BY_CRAWLER', '')
                      is_projection = table.get('Parameters', {}).get('projection.enabled', '').lower()
                      if (not updated_by.startswith(resource_prefix)) and (is_projection != 'true'):
                          glue_client.delete_table(DatabaseName=database_name, Name=table_name)
                          print(f'table {table_name} was deleted to avoid crawler confusion')
              return 'SUCCESS', 'success'

  LambdaManageGlueTableRole:
    Type: AWS::IAM::Role
    Properties:
      Path:
        Fn::Sub: /${ResourcePrefix}/
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !Sub "lambda.${AWS::URLSuffix}"
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      Policies:
        - PolicyName: "Glue"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - glue:GetDatabase
                  - glue:CreateDatabase
                  - glue:DeleteDatabase
                  - glue:GetTable
                  - glue:GetTables
                  - glue:CreateTable
                  - glue:DeleteTable
                  - glue:TagResource
                  - glue:UpdateTable
                Resource:
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:catalog"
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/${DatabaseName}"
                  - !Sub "arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:table/${DatabaseName}/*"

  LambdaManageGlueTable:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.13
      FunctionName: !Sub ${ResourcePrefix}ManageGlueTable-Lambda
      Description: "Lambda function to manage glue tables"
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt LambdaManageGlueTableRole.Arn
      Timeout: 300
      Environment:
        Variables:
          DATABASE_NAME: !Ref DatabaseName
          RESOURCE_PREFIX: !Ref ResourcePrefix
          BUCKET_NAME: !Sub "${DestinationBucket}${AWS::AccountId}"
      Code:
        ZipFile: |
          import os
          import json
          import uuid
          import urllib3
          import boto3

          database_name = os.environ['DATABASE_NAME']
          resource_prefix = os.environ['RESOURCE_PREFIX']
          glue_client = boto3.client('glue')

          def lambda_handler(event, context): #pylint: disable=unused-argument
              print(json.dumps(event))
              try:
                  action = event.get('RequestType').upper()
                  if action not in ['CREATE', 'UPDATE', 'DELETE']:
                      raise Exception(f"Unknown RequestType {action}") #pylint: disable=broad-exception-raised
                  func = {'CREATE': create_or_update, 'DELETE': delete, 'UPDATE': create_or_update}.get(action)
                  table_input = event['ResourceProperties']['TableInput']
                  res, reason = func(table_input)
              except Exception as exc: #pylint: disable=broad-exception-caught
                  if 'Insufficient Lake Formation permission' in str(exc):
                      res, reason = 'FAILED', 'Lake Formation is not supported yet. Please use account without Lake Formation.'
                  else:
                      res, reason = 'FAILED', str(exc)
              body = {
                  'Status': res,
                  'Reason': reason,
                  'PhysicalResourceId': event.get('PhysicalResourceId', str(uuid.uuid1())),
                  'StackId': event.get('StackId'),
                  'RequestId': event.get('RequestId'),
                  'LogicalResourceId': event.get('LogicalResourceId'),
                  'NoEcho': False,
                  'Data':  {'Reason': reason},
              }
              json_body=json.dumps(body)
              print(json_body)
              url = event.get('ResponseURL')
              if not url:
                  return
              try:
                  response = urllib3.PoolManager().request('PUT', url, body=json_body, headers={'content-type' : '', 'content-length' : str(len(json_body))}, retries=False)
                  print(f"Status code: {response}")
              except Exception as exc: #pylint: disable=broad-exception-caught
                  print("Failed sending PUT to CFN: " + str(exc))

          def create_or_update(table_input):
              try:
                  glue_client.create_table(DatabaseName=database_name, TableInput=table_input)
                  return  'SUCCESS', 'created'
              except glue_client.exceptions.AlreadyExistsException:
                  glue_client.update_table(DatabaseName=database_name, TableInput=table_input)
                  return  'SUCCESS', 'updated'

          def delete(table_input):
              try:
                  glue_client.delete_table(DatabaseName=database_name, Name=table_input['Name'])
                  return  'SUCCESS', 'deleted'
              except glue_client.exceptions.EntityNotFoundException:
                  return  'SUCCESS', 'not found'

  InitExecutor:
    Type: Custom::LambdaAnalyticsExecutor
    Properties:
      ServiceToken: !GetAtt LambdaInit.Arn

  CrawlerExecutionStepFunction:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      StateMachineName: !Sub '${ResourcePrefix}CrawlerExecution-StateMachine'
      StateMachineType: STANDARD
      RoleArn: !GetAtt StepFunctionExecutionRole.Arn
      DefinitionS3Location:
        Bucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        Key: !FindInMap [StepFunctionCode, crawler-state-machine, TemplatePath]

  StepFunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      RoleName: !Sub "${ResourcePrefix}StepFunctionExecutionRole"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !Sub "states.${AWS::URLSuffix}"
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: "GlueExecution"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - glue:StartCrawler
                  - glue:GetCrawler
                Resource: !Sub 'arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:crawler/${ResourcePrefix}*Crawler*'
        - PolicyName: InvokeCollectionLambda
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  - !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${ResourcePrefix}*Lambda*'
        - PolicyName: PolicyForSyncronousExecution
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - events:PutTargets
                  - events:DescribeRule
                  - events:PutRule
                Resource:
                  - !Sub 'arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule'
              - Effect: Allow
                Action:
                  - states:StartExecution
                Resource:
                  - !Sub 'arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}*-StateMachine'
              - Effect: Allow
                Action:
                  - states:DescribeExecution
                  - states:StopExecution
                Resource:
                  - !Sub 'arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:execution:*:*'
                  - !Sub 'arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:express:*:*:*'
        - PolicyName: "S3-ReadOnlyAccess" #Used for getting summary record list for map iterations to retrieve detail API calls
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "s3:GetObject"
                Resource: !Sub '${S3Bucket.Arn}/*'
        - PolicyName: "LoggingToS3andCloudWatch" #Used for logging/instrumentation
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - s3:PutObject
                Resource: !Sub '${S3Bucket.Arn}/*'
              - Effect: "Allow"
                Action:
                  - cloudwatch:PutMetricData
                Resource: '*' # Wildcard required because actions do not support resource-level permissions
                Condition:
                  StringEquals:
                    cloudwatch:namespace: CID-DataCollection

  StepFunctionExecutionRoleInvokeAccountCollectorPolicy:
    Type: 'AWS::IAM::Policy'
    Condition: DeployAccountCollector
    Properties:
      PolicyName: InvokeAccountCollector
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - lambda:InvokeFunction
            Resource:
              - !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
      Roles:
        - Ref: StepFunctionExecutionRole

  SchedulerExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      RoleName: !Sub "${ResourcePrefix}SchedulerExecutionRole"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !Sub "scheduler.${AWS::URLSuffix}"
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: "ExecuteStateMachine"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - states:StartExecution
                Resource:
                  - !Sub "arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}*StateMachine"
        - PolicyName: "ExecuteLambda"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  - !Sub "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${ResourcePrefix}*"
  LogTable:
    Type: AWS::Glue::Table
    DependsOn:
      - InitExecutor
    Properties:
      CatalogId: !Ref "AWS::AccountId"
      DatabaseName: !Ref DatabaseName
      TableInput:
        Name: dc_execution_log
        TableType: EXTERNAL_TABLE
        PartitionKeys:
        - { Name: logdate,    Type: string }
        StorageDescriptor:
          Columns:
          - { Name: timestamp,                   Type: timestamp }
          - { Name: datacollectionregion,        Type: string }
          - { Name: datacollectionaccountid,     Type: string }
          - { Name: module,                      Type: string }
          - { Name: modulefunction,              Type: string }
          - { Name: params,                      Type: string }
          - { Name: payerid,                     Type: string }
          - { Name: accountid,                   Type: string }
          - { Name: region,                      Type: string }
          - { Name: statuscode,                  Type: int }
          - { Name: subcode,                     Type: string }
          - { Name: recordcount,                 Type: int }
          - { Name: description,                 Type: string }
          - { Name: datalocation,                Type: string }
          - { Name: mainexeuuid,                 Type: string }
          - { Name: subuuid,                     Type: string }
          - { Name: service,                     Type: string }
          - { Name: stackversion,                Type: string }
          - { Name: subversion,                  Type: string }
          InputFormat: org.apache.hadoop.mapred.TextInputFormat
          OutputFormat: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
          Location: !Sub "s3://${DestinationBucket}${AWS::AccountId}/logs/modules/"
          Parameters:
            UPDATED_BY_CRAWLER: CID-DC-no-crawler-needed
          SerdeInfo:
            SerializationLibrary: org.openx.data.jsonserde.JsonSerDe
            Parameters:
              serialization.format: '1'
        Parameters:
          EXTERNAL: 'TRUE'
          projection.logdate.format: yyyy/MM/dd
          projection.logdate.interval: '1'
          projection.logdate.interval.unit: DAYS
          projection.logdate.range: 2025/01/01,NOW
          projection.logdate.type: date
          projection.enabled: 'true'
          storage.location.template: !Sub "s3://${DestinationBucket}${AWS::AccountId}/logs/modules/${!logdate}"

  TrustedAdvisorModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployTAModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-trusted-advisor.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref Schedule
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        LambdaManageGlueTableARN: !GetAtt LambdaManageGlueTable.Arn

  RightsizeModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployRightsizingModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-cost-explorer-rightsizing.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        ManagementRoleName: !Sub "${ResourcePrefix}${ManagementAccountRole}"
        Schedule: !Ref Schedule
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  CostAnomalyModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployCostAnomalyModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-cost-anomaly.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        ManagementRoleName: !Sub "${ResourcePrefix}${ManagementAccountRole}"
        Schedule: !Ref ScheduleFrequent
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        LambdaManageGlueTableARN: !GetAtt LambdaManageGlueTable.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  SupportCasesModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeploySupportCasesModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-support-cases.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref ScheduleFrequent
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  BackupModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployBackupModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-backup.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        ManagementRoleName: !Sub "${ResourcePrefix}${ManagementAccountRole}"
        Schedule: !Ref Schedule
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  InventoryCollectorModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployInventoryCollectorModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-inventory.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        GlueRoleARN: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref Schedule
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        LambdaManageGlueTableARN: !GetAtt LambdaManageGlueTable.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces

  PricingModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployPricingModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-pricing.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        GlueRoleARN: !GetAtt GlueRole.Arn
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        Schedule: !Ref Schedule
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces

  ComputeOptimizerModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployComputeOptimizerModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-compute-optimizer.yaml"
      Parameters:
        DestinationBucket: !Ref S3Bucket
        ManagementRoleName: !Sub "${ResourcePrefix}${ManagementAccountRole}"
        ManagementAccountID: !Ref ManagementAccountID
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        Schedule: !Ref Schedule
        ResourcePrefix: !Ref ResourcePrefix
        BucketPrefix:  !Ref DestinationBucket
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  EcsChargebackModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployEcsChargebackModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-ecs-chargeback.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref Schedule
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces

  RDSUsageModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployRDSUtilizationModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-rds-usage.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref Schedule
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces

  EUCUsageModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployEUCUtilizationModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-workspaces-metrics.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref Schedule
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces

  OrgDataModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployOrgDataModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-organization.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        Schedule: !Ref Schedule
        ManagementRoleName: !Sub "${ResourcePrefix}${ManagementAccountRole}"
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  BudgetsModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployBudgetsModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-budgets.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref ScheduleFrequent
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  TransitGatewayModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployTransitGatewayModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-transit-gateway.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref Schedule
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces

  AWSFeedsModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployAWSFeedsModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-aws-feeds.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        Schedule: !Ref ScheduleFrequent
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, standalone-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  ISVFeedsModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployISVFeedsModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-isv-feeds.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        Schedule: !Ref ScheduleFrequent
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, standalone-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  HealthEventsModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployHealthEventsModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-health-events.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        Schedule: !Ref ScheduleFrequent
        ManagementRoleName: !Sub "${ResourcePrefix}${ManagementAccountRole}"
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        DetailStepFunctionTemplate: !FindInMap [StepFunctionCode, health-detail-state-machine, TemplatePath]

  LicenseManagerModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployLicenseManagerModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-license-manager.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        ManagementRoleName: !Sub "${ResourcePrefix}${ManagementAccountRole}"
        Schedule: !Ref ScheduleFrequent
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  ServiceQuotasModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployServiceQuotasModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-service-quotas.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        Schedule: !Ref ScheduleFrequent
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces

  QuickSightModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployQuickSightModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-quicksight.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        Schedule: !Ref ScheduleFrequent
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, standalone-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  ResilienceHubModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployResilienceHubModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-resilience-hub.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DestinationBucket: !Ref S3Bucket
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        GlueRoleARN: !GetAtt GlueRole.Arn
        Schedule: !Ref ScheduleFrequent
        ResourcePrefix: !Ref ResourcePrefix
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  MarketplaceModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployMarketplaceModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-marketplace.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        MultiAccountRoleName: !Sub "${ResourcePrefix}${MultiAccountRoleName}"
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        GlueRoleARN: !GetAtt GlueRole.Arn
        Schedule: !Ref ScheduleFrequent
        ResourcePrefix: !Ref ResourcePrefix
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        AccountCollectorLambdaARN: !Sub "${AccountCollector.Outputs.LambdaFunctionARN}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, main-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn

  ReferenceModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployIncludeReferenceModule
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/module-reference.yaml"
      Parameters:
        DatabaseName: !Ref DatabaseName
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        Schedule: !Ref ScheduleFrequent
        GlueRoleARN: !GetAtt GlueRole.Arn
        ResourcePrefix: !Ref ResourcePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNSourceBucket ]
        StepFunctionTemplate: !FindInMap [StepFunctionCode, standalone-state-machine, TemplatePath]
        StepFunctionExecutionRoleARN: !GetAtt StepFunctionExecutionRole.Arn
        SchedulerExecutionRoleARN: !GetAtt SchedulerExecutionRole.Arn
        RegionsInScope:
          Fn::If:
            - RegionsInScopeIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref RegionsInScope  ] ] # remove spaces

  AccountCollector:
    Type: AWS::CloudFormation::Stack
    Condition: DeployAccountCollector
    Properties:
      TemplateURL: !Sub "https://${CFNSourceBucket}.s3.${AWS::URLSuffix}/cfn/data-collection/v3.14.1/account-collector.yaml"
      Parameters:
        ManagementRoleName: !Sub "${ResourcePrefix}${ManagementAccountRole}"
        ManagementAccountID: !Ref ManagementAccountID
        ResourcePrefix: !Ref ResourcePrefix
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        DataBucketsKmsKeysArns: !Ref DataBucketsKmsKeysArns
        EUCAccountIDs: !Ref EUCAccountIDs

  DataCollectionReadAccess:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub ${ResourcePrefix}DataCollectionReadAccess
      Description: 'Policy for QuickSight to allow DataCollection access'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowGlue
            Effect: Allow
            Action:
              - glue:GetPartition
              - glue:GetPartitions
              - glue:GetDatabase
              - glue:GetDatabases
              - glue:GetTable
              - glue:GetTables
            Resource:
              - !Sub arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:catalog
              - !Sub arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:database/${DatabaseName}
              - !Sub arn:${AWS::Partition}:glue:${AWS::Region}:${AWS::AccountId}:table/${DatabaseName}/*
          - Sid: AllowListBucket
            Effect: Allow
            Action: s3:ListBucket
            Resource:
              - !Sub ${S3Bucket.Arn}
          - Sid: AllowReadBucket
            Effect: Allow
            Action:
              - s3:GetObject
              - s3:GetObjectVersion
            Resource:
              - !Sub ${S3Bucket.Arn}/*
          - !If
            - NeedDataBucketsKms
            - Sid: AllowKmsDecrypt
              Effect: "Allow"
              Action:
                - "kms:Decrypt"
              Resource: !Split [ ',', !Ref DataBucketsKmsKeysArns ]
            - !Ref AWS::NoValue

Outputs:
  Bucket:
    Description: CID Data Collection - Name of S3 Bucket which will store collected data
    Value: !Ref S3Bucket
    Export: { Name: "cid-DataCollection-Bucket" }
  Database:
    Description: "Glue Database for CID Data Collection"
    Value: !Ref DatabaseName
    Export: { Name: "cid-DataCollection-Database" }
  ReadAccessPolicyARN:
    Description: "Access Policy for CID Data Collection"
    Value: !Ref DataCollectionReadAccess
    Export: { Name: "cid-DataCollection-ReadAccessPolicyARN" }
