AWSTemplateFormatVersion: 2010-09-09
Description: Dynatrace Data Acquisition AWS monitoring activation - enable metrics, smartscape, logs and events (v0.8.3)

Metadata:
  Version:
    Number: v0.8.3

  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Dynatrace tokens"
        Parameters:
          - pDtApiToken
          - pDtIngestToken
      - Label:
          default: "Logs ingest"
        Parameters:
          - pDtLogsIngestEnabled
          - pDtLogsIngestRegions
      - Label:
          default: "Events ingest"
        Parameters:
          - pDtEventsIngestEnabled
          - pDtEventsIngestRegions
          - pEventBridgeBusName
          - pEventSources
      - Label:
          default: "Dynatrace environment"
        Parameters:
          - pDynatraceUrl
          - pMonitoringConfigId
      - Label:
          default: "Encryption"
        Parameters:
          - pUseCMK
      - Label:
          default: "Deprecated"
        Parameters:
          - pExternalId
    ParameterLabels:
      pDtApiToken:
        default: Dynatrace settings platform token
      pDtIngestToken:
        default: Dynatrace ingest platform token
      pDtLogsIngestEnabled:
        default: Enable logs ingest
      pDtLogsIngestRegions:
        default: Regions to ingest logs from
      pDtEventsIngestEnabled:
        default: Enable events ingest
      pDtEventsIngestRegions:
        default: Regions to ingest events from
      pEventBridgeBusName:
        default: Amazon EventBridge bus name
      pEventSources:
        default: EventBridge sources list
      pDynatraceUrl:
        default: Dynatrace environment URL
      pMonitoringConfigId:
        default: Dynatrace monitoring configuration ID - do not modify
      pUseCMK:
        default: Use Customer Managed Key (CMK)
      pExternalId:
        default: Deprecated Dynatrace monitoring configuration ID

Parameters:
  pDynatraceUrl:
    Type: String
    AllowedPattern: "^https:\\/\\/.+[^\\/]$"
  pMonitoringConfigId:
    Description: AWS monitoring extension configuration ID
    Type: String
  pDtApiToken:
    Description: "Required scopes: settings:objects:write, extensions:configurations:write, extensions:configurations:read"
    Type: String
    NoEcho: true
    MinLength: 1
  pDtIngestToken:
    Description: "Required scopes: data-acquisition:logs:ingest, data-acquisition:events:ingest"
    Type: String
    Default: ""
    NoEcho: true
  pDtLogsIngestEnabled:
    Default: 'FALSE'
    Type: String
    AllowedValues: ['TRUE', 'FALSE']
  pDtLogsIngestRegions:
    Description: Comma-separated list of regions e.g. us-east-1,eu-central-1
    Type: CommaDelimitedList
    Default: ""
  pDtEventsIngestEnabled:
    Default: 'FALSE'
    Type: String
    AllowedValues: ['TRUE', 'FALSE']
  pDtEventsIngestRegions:
    Description: Comma-separated list of regions e.g. us-west-2,eu-north-1
    Type: CommaDelimitedList
    Default: ""
  pEventBridgeBusName:
    Type: String
    Description: Name of the Amazon EventBridge bus from which to consume the events from
    Default: default
    MinLength: 1
    MaxLength: 256
    AllowedPattern: "^[a-zA-Z0-9._-]*$"
  pEventSources:
    Type: CommaDelimitedList
    Description: Comma-separated list of values of event sources to forward to Dynatrace (e.g. aws.health)
    Default: aws.health
  pUseCMK:
    Default: 'FALSE'
    Type: String
    AllowedValues: ['TRUE', 'FALSE']
    Description: Create and use a Customer Managed Key (CMK) for encrypting secrets, logs, and S3 bucket
  pExternalId:
    Description: Deprecated AWS monitoring extension configuration ID
    Type: String
    Default: ""


Rules:
  LogsIngestParameters:
    RuleCondition: !Equals [ !Ref pDtLogsIngestEnabled, 'TRUE' ]
    Assertions:
      - Assert:
          Fn::EachMemberIn:
            - !Ref pDtLogsIngestRegions
            - [ "af-south-1", "ap-east-1", "ap-east-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-south-1", "ap-south-2", "ap-southeast-1", "ap-southeast-2", "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", "ap-southeast-6", "ap-southeast-7", "ca-central-1", "ca-west-1", "eu-central-1", "eu-central-2", "eu-north-1", "eu-south-1", "eu-south-2", "eu-west-1", "eu-west-2", "eu-west-3", "il-central-1", "me-central-1", "me-south-1", "mx-central-1", "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2" ]
        AssertDescription: "Logs ingest regions parameter needs to contain comma-separated list of valid AWS regions"
      - Assert: !Not [ !Equals [ !Ref pDtIngestToken, '' ] ]
        AssertDescription: "Dynatrace ingest platform token must be set"

  EventsIngestParameters:
    RuleCondition: !Equals [ !Ref pDtEventsIngestEnabled, 'TRUE' ]
    Assertions:
      - Assert:
          Fn::EachMemberIn:
            - !Ref pDtEventsIngestRegions
            - [ "af-south-1", "ap-east-1", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-south-1", "ap-south-2", "ap-southeast-1", "ap-southeast-2", "ap-southeast-3", "ca-central-1", "eu-central-1", "eu-central-2", "eu-north-1", "eu-south-1", "eu-south-2", "eu-west-1", "eu-west-2", "eu-west-3", "me-central-1", "me-south-1", "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2" ]
        AssertDescription: "Events ingest regions parameter needs to contain comma-separated list of valid AWS regions"
      - Assert: !Not [ !Equals [ !Ref pDtIngestToken, '' ] ]
        AssertDescription: "Dynatrace ingest platform token must be set"

  MonitoringConfigValidation:
    Assertions:
      - Assert: !Or
          - !Not [ !Equals [ !Ref pMonitoringConfigId, '' ] ]
          - !Not [ !Equals [ !Ref pExternalId, '' ] ]
        AssertDescription: "Specify 'AWS monitoring extension configuration ID' (pMonitoringConfigId)"

Mappings:
  StageMap:
    dev:
      DacUrl: "dev.da.dynatracelabs.com"
    sprint:
      DacUrl: "sprint.da.dynatracelabs.com"
    live:
      DacUrl: "da.dynatrace.com"
    apps:
      DacUrl: "da.dynatrace.com"

Conditions:
  IngestEnabled: !Or
    - !Condition LogsIngestEnabled
    - !Condition EventsIngestEnabled
  LogsIngestEnabled: !And
    - !Equals [ !Ref pDtLogsIngestEnabled, 'TRUE' ]
    - !Not [ !Condition EmptyLogsRegions ]
  EventsIngestEnabled: !And
    - !Equals [ !Ref pDtEventsIngestEnabled, 'TRUE' ]
    - !Not [ !Condition EmptyEventsRegions ]
  EmptyLogsRegions: !Equals
    - !Join [ '', !Ref pDtLogsIngestRegions ]
    - ''
  EmptyEventsRegions: !Equals
    - !Join [ '', !Ref pDtEventsIngestRegions ]
    - ''
  UseCMK: !Equals [ !Ref pUseCMK, 'TRUE' ]
  EmptyMonitoringConfigId: !Equals [ !Ref pMonitoringConfigId, '' ]

Resources:
  DynatraceApiClientStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://dynatrace-data-acquisition.s3.amazonaws.com/aws/deployment/cfn/v0.8.3/da-aws-nested-dt-api-function.yaml
      Parameters:
        DynatraceUrl: !Ref pDynatraceUrl
        DynatraceApiToken: !Ref pDtApiToken
        ParentStackId: !Ref AWS::StackId
        UseCMK: !Ref pUseCMK
      Tags:
        - Key: 'CreatedBy'
          Value: 'Dynatrace'
        - Key: 'EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  ReportStatusResource:
    Type: "Custom::DynatraceApiAccessFunction"
    DependsOn: DynatraceApiClientStack
    Properties:
      ServiceToken: !GetAtt DynatraceApiClientStack.Outputs.DynatraceApiClientFunctionArn
      ServiceTimeout: 120
      Step: REPORT_STATUS
      DtApiKeySecret: !GetAtt DynatraceApiClientStack.Outputs.DynatraceApiTokenSecretArn
      MonitoringConfigurationId: !If [ EmptyMonitoringConfigId, !Ref pExternalId, !Ref pMonitoringConfigId ]
      # Pass rest of stack parameters (ignored) to trigger status reporting on every stack update
      pDynatraceUrl: !Ref pDynatraceUrl
      pDtLogsIngestEnabled: !Ref pDtLogsIngestEnabled
      pDtLogsIngestRegions: !Ref pDtLogsIngestRegions
      pDtEventsIngestEnabled: !Ref pDtEventsIngestEnabled
      pDtEventsIngestRegions: !Ref pDtEventsIngestRegions
      pEventBridgeBusName: !Ref pEventBridgeBusName
      pEventSources: !Ref pEventSources
      TemplateVersion: v0.8.3

  DynatraceIntegrationStack:
    Type: AWS::CloudFormation::Stack
    DependsOn: ReportStatusResource
    Properties:
      TemplateURL: https://dynatrace-data-acquisition.s3.amazonaws.com/aws/deployment/cfn/v0.8.3/da-aws-nested-integration.yaml
      Parameters:
        DynatraceUrl: !Ref pDynatraceUrl
        MonitoringConfigurationId: !If [ EmptyMonitoringConfigId, !Ref pExternalId, !Ref pMonitoringConfigId ]
        DynatraceApiClientFunctionArn: !GetAtt DynatraceApiClientStack.Outputs.DynatraceApiClientFunctionArn
        DynatraceApiTokenSecretArn: !GetAtt DynatraceApiClientStack.Outputs.DynatraceApiTokenSecretArn

      Tags:
        - Key: 'CreatedBy'
          Value: 'Dynatrace'
        - Key: 'EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  StackSetRoleStack:
    Type: AWS::CloudFormation::Stack
    DependsOn: DynatraceIntegrationStack
    Condition: IngestEnabled
    Properties:
      TemplateURL: https://dynatrace-data-acquisition.s3.amazonaws.com/aws/deployment/cfn/v0.8.3/da-aws-nested-stackset-role.yaml
      Parameters:
        DynatraceUrl: !Ref pDynatraceUrl
      Tags:
        - Key: 'CreatedBy'
          Value: 'Dynatrace'
        - Key: 'EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  # Logs ingest StackSet
  DTLogsIngestStackSet:
    DependsOn:
      - DynatraceIntegrationStack
    Condition: LogsIngestEnabled
    Type: AWS::CloudFormation::StackSet
    Properties:
      Description: StackSet
      StackSetName: !Join
        - "-"
        - - "DynatraceLogsIngest"
          - !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
          - !Select [ 2, !Split [ "/", !Ref AWS::StackId ] ]
      StackInstancesGroup:
        - DeploymentTargets:
            Accounts:
              - !Ref AWS::AccountId
          Regions: !Ref pDtLogsIngestRegions
      OperationPreferences:
        FailureToleranceCount: 0
        MaxConcurrentCount: 1
        RegionConcurrencyType: PARALLEL
      Parameters:
        - ParameterKey: DtEnvironmentId
          ParameterValue: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
        - ParameterKey: DtIngestToken
          ParameterValue: !Ref pDtIngestToken
        - ParameterKey: DacUrl
          ParameterValue: !FindInMap
            - StageMap
            - !Select [ 1, !Split [ ".", !Ref pDynatraceUrl ] ]
            - DacUrl
        - ParameterKey: UseCMK
          ParameterValue: !Ref pUseCMK
      PermissionModel: SELF_MANAGED
      AdministrationRoleARN: !GetAtt StackSetRoleStack.Outputs.StackSetAdministrationRoleArn
      ExecutionRoleName: !GetAtt StackSetRoleStack.Outputs.StackSetExecutionRoleName
      Capabilities:
        - CAPABILITY_NAMED_IAM
      TemplateURL: https://dynatrace-data-acquisition.s3.amazonaws.com/aws/deployment/cfn/v0.8.3/da-aws-stack-logs.yaml
      Tags:
        - Key: 'CreatedBy'
          Value: 'Dynatrace'
        - Key: 'EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  # Events ingest StackSet
  DTEventsIngestStackSet:
    DependsOn:
      - DynatraceIntegrationStack
    Condition: EventsIngestEnabled
    Type: AWS::CloudFormation::StackSet
    Properties:
      Description: StackSet
      StackSetName: !Join
        - "-"
        - - "DynatraceEventsIngest"
          - !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
          - !Select [ 2, !Split [ "/", !Ref AWS::StackId ] ]
      StackInstancesGroup:
        - DeploymentTargets:
            Accounts:
              - !Ref AWS::AccountId
          Regions: !Ref pDtEventsIngestRegions
      OperationPreferences:
        FailureToleranceCount: 0
        MaxConcurrentCount: 1
        RegionConcurrencyType: PARALLEL
      Parameters:
        - ParameterKey: DtEnvironmentId
          ParameterValue: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
        - ParameterKey: DtIngestToken
          ParameterValue: !Ref pDtIngestToken
        - ParameterKey: DacUrl
          ParameterValue: !FindInMap
            - StageMap
            - !Select [ 1, !Split [ ".", !Ref pDynatraceUrl ] ]
            - DacUrl
        - ParameterKey: EventBridgeBusName
          ParameterValue: !Ref pEventBridgeBusName
        - ParameterKey: EventSources
          ParameterValue: !Join [ ",", !Ref pEventSources ]
      PermissionModel: SELF_MANAGED
      AdministrationRoleARN: !GetAtt StackSetRoleStack.Outputs.StackSetAdministrationRoleArn
      ExecutionRoleName: !GetAtt StackSetRoleStack.Outputs.StackSetExecutionRoleName
      Capabilities:
        - CAPABILITY_NAMED_IAM
      TemplateURL: https://dynatrace-data-acquisition.s3.amazonaws.com/aws/deployment/cfn/v0.8.3/da-aws-stack-events.yaml
      Tags:
        - Key: 'CreatedBy'
          Value: 'Dynatrace'
        - Key: 'EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  ReportStatusCompleteResource:
    Type: "Custom::DynatraceApiAccessFunction"
    DependsOn: DynatraceIntegrationStack
    Metadata:
      ConditionalLogsStackSetDependency: !If [ LogsIngestEnabled, !Ref DTLogsIngestStackSet, !Ref AWS::NoValue ]
      ConditionalEventsStackSetDependency: !If [ EventsIngestEnabled, !Ref DTEventsIngestStackSet, !Ref AWS::NoValue ]
    Properties:
      ServiceToken: !GetAtt DynatraceApiClientStack.Outputs.DynatraceApiClientFunctionArn
      ServiceTimeout: 120
      Step: REPORT_COMPLETE
      DtApiKeySecret: !GetAtt DynatraceApiClientStack.Outputs.DynatraceApiTokenSecretArn
      MonitoringConfigurationId: !If [ EmptyMonitoringConfigId, !Ref pExternalId, !Ref pMonitoringConfigId ]
      # Pass rest of stack parameters (ignored) to trigger status reporting on every stack update
      pDynatraceUrl: !Ref pDynatraceUrl
      pDtLogsIngestEnabled: !Ref pDtLogsIngestEnabled
      pDtLogsIngestRegions: !Ref pDtLogsIngestRegions
      pDtEventsIngestEnabled: !Ref pDtEventsIngestEnabled
      pDtEventsIngestRegions: !Ref pDtEventsIngestRegions
      pEventBridgeBusName: !Ref pEventBridgeBusName
      pEventSources: !Ref pEventSources
      TemplateVersion: v0.8.3

Outputs:
  MonitoringConfigUuid:
    Value: !GetAtt DynatraceIntegrationStack.Outputs.MonitoringConfigUuid
  HasSettingsObjectId:
    Value: !GetAtt DynatraceIntegrationStack.Outputs.HasSettingsObjectId
  DynatraceApiTokenSecret:
    Value: !GetAtt DynatraceApiClientStack.Outputs.DynatraceApiTokenSecretArn
    Description: ARN of SecretsManager secret storing Dynatrace settings platform token
  DTLogsIngestStackSetName:
    Condition: LogsIngestEnabled
    Value: !Join
      - "-"
      - - "DynatraceLogsIngest"
        - !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
        - !Select [ 2, !Split [ "/", !Ref AWS::StackId ] ]
  DTEventsIngestStackSetName:
    Condition: EventsIngestEnabled
    Value: !Join
      - "-"
      - - "DynatraceEventsIngest"
        - !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
        - !Select [ 2, !Split [ "/", !Ref AWS::StackId ] ]
