AWSTemplateFormatVersion: '2010-09-09'
Description: 'Dynatrace Data Acquisition for AWS Organizations - Monitoring Activation (v1.0.8 2026-06-11 12:05)'

Metadata:
  Version:
    Number: v1.0.8
    ReleaseDate: 2026-06-11 12:05

  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: 'AWS Organizations'
        Parameters:
          - pAwsOrganizationId
          - pSecretsAccountId
          - pManagementRegion
      - Label:
          default : 'Dynatrace deployment settings'
        Parameters:
          - pDynatraceUrl
          - pMonitoringConfigId
          - pDeploymentInstanceIdentifier
          - pDtLogsIngestEnabled
          - pDtEventsIngestEnabled
          - pEventSources
          - pEventBridgeBusName
      - Label:
          default: 'Encryption'
        Parameters:
          - pUseCMK
    ParameterLabels:
      pAwsOrganizationId:
        default: 'AWS Organization ID'
      pSecretsAccountId:
        default: 'Dynatrace foundational account ID'
      pManagementRegion:
        default: 'AWS management region'
      pDynatraceUrl:
        default: 'Dynatrace environment url'
      pMonitoringConfigId:
        default: 'Dynatrace monitoring configuration ID'
      pDeploymentInstanceIdentifier:
        default: 'Deployment instance identifier'
      pDtLogsIngestEnabled:
        default: 'Deploy log ingestion resources'
      pDtEventsIngestEnabled:
        default: 'Deploy event ingestion resources'
      pEventSources:
        default: 'AWS EventBridge event sources'
      pEventBridgeBusName:
        default: 'AWS EventBridge bus name'
      pUseCMK:
        default: 'Use Customer Managed Key (CMK)'

Parameters:
  pAwsOrganizationId:
    Type: String
    Description: 'AWS Organization identifier'
    AllowedPattern: '^o-[a-z0-9]{10,32}$'
  pSecretsAccountId:
    Type: String
    Description: 'The ID of the AWS account where the foundational stack instance(s) were deployed'
    AllowedPattern: '^[0-9]{12}$'
  pManagementRegion:
    Type: String
    Description: 'CloudFormation StackSets management region (need to be same as in the foundational StackSet)'
    AllowedValues: [ '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' ]
    Default: 'us-east-1'
  pDynatraceUrl:
    Type: String
    Description: 'Your Dynatrace environment url'
    AllowedPattern: '^https:\/\/.+[^\/]$'
  pMonitoringConfigId:
    Type: String
    Description: 'AWS monitoring extension configuration ID'
  pDeploymentInstanceIdentifier:
    Type: String
    Description: '[Optional] Identifier of the Secrets Manager deployment where the Dynatrace Platform tokens were deployed (modify this parameter only if you are deploying multiple instances of the foundational StackSets and you want to target a specific set of Secrets).'
    AllowedPattern: '(^$)|(^[a-zA-Z0-9]{1,64}$)'
  pDtLogsIngestEnabled:
    Type: String
    Description: 'Enable log ingestion'
    AllowedValues: [ 'TRUE', 'FALSE' ]
    Default: 'FALSE'
  pDtEventsIngestEnabled:
    Type: String
    Description: 'Enable event ingestion'
    AllowedValues: [ 'TRUE', 'FALSE' ]
    Default: 'FALSE'
  pEventSources:
    Type: CommaDelimitedList
    Description: 'Comma-separated list of values of event sources to forward to Dynatrace (e.g. aws.ec2,aws.s3,aws.lambda)'
    Default: 'aws.health'
  pEventBridgeBusName:
    Type: String
    Description: 'Name of the Amazon EventBridge bus from which to consume the events from'
    AllowedPattern: '^[a-zA-Z0-9._-]*$'
    MinLength: 1
    MaxLength: 256
    Default: 'default'
  pUseCMK:
    Type: String
    Description: 'Create and use a Customer Managed Key (CMK) for encryption'
    AllowedValues: [ 'TRUE', 'FALSE' ]
    Default: 'FALSE'

Conditions:
  cCustomEventBus:
    !Not [ !Equals [ 'default', !Ref pEventBridgeBusName ] ]
  cIsManagementRegion: !Equals
    - !Ref AWS::Region
    - !Ref pManagementRegion
  cDeployLogIngestStack: !Equals
    - !Ref pDtLogsIngestEnabled
    - 'TRUE'
  cDeployEventIngestStack: !Equals
    - !Ref pDtEventsIngestEnabled
    - 'TRUE'
  cProductionEnvironment: !Equals [ !Select [ 2, !Split [ ".", !Select [ 1, !Split [ "://", !Ref pDynatraceUrl ] ] ] ], 'dynatrace' ]
  cUseCMK: !Equals [ !Ref pUseCMK, 'TRUE' ]

Resources:
  DynatraceKMSKey:
    Type: AWS::KMS::Key
    Condition: cUseCMK
    Properties:
      Description: !Sub 'KMS Key for encrypting Dynatrace tokens and Firehose resources. Deployment: ${AWS::StackName}'
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Allow CloudWatch Logs use of the key
            Effect: Allow
            Principal:
              Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:CreateGrant'
              - 'kms:DescribeKey'
            Resource: '*'
            Condition:
              ArnLike:
                'kms:EncryptionContext:aws:logs:arn':
                  - !If
                    - cIsManagementRegion
                    - !Sub
                      - 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/DynatraceApiClientFunction-${DynatraceEnvironmentId}-${DeploymentHash}'
                      - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
                        DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ]
                    - !Ref AWS::NoValue
                  - !Sub
                    - 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/datafirehose/dynatrace/${DynatraceEnvironmentId}/${AWS::Region}/${DeploymentHash}'
                    - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
                      DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ]
              StringEquals:
                'aws:SourceAccount': !Ref AWS::AccountId
          - Sid: Allow KMS in AWS Services
            Effect: Allow
            Principal:
              Service:
                - 'firehose.amazonaws.com'
                - 's3.amazonaws.com'
            Action:
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey'
            Resource: '*'
            Condition:
              StringEquals:
                'kms:ViaService':
                  - !Sub 'firehose.${AWS::Region}.amazonaws.com'
                  - !Sub 's3.${AWS::Region}.amazonaws.com'
                'aws:SourceAccount': !Ref AWS::AccountId
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceKMSKeyAlias:
    Type: AWS::KMS::Alias
    Condition: cUseCMK
    Properties:
      TargetKeyId: !Ref DynatraceKMSKey
      AliasName: !Sub
        - 'alias/dynatrace/${DynatraceEnvironmentId}/keys/aws/${pAwsOrganizationId}/activation/${pDeploymentInstanceIdentifier}'
        - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceApiClientLogGroup:
    Type: AWS::Logs::LogGroup
    Condition: cIsManagementRegion
    Properties:
      LogGroupName: !Sub
        - '/aws/lambda/DynatraceApiClientFunction-${DynatraceEnvironmentId}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      RetentionInDays: 30
      KmsKeyId: !If [ cUseCMK, !GetAtt DynatraceKMSKey.Arn, !Ref AWS::NoValue ]
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceApiClientFunction:
    Type: AWS::Lambda::Function
    Condition: cIsManagementRegion
    Properties:
      FunctionName: !Sub
        - 'DynatraceApiClientFunction-${DynatraceEnvironmentId}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      Handler: index.handler
      Runtime: python3.13
      MemorySize: 128
      Timeout: 120
      LoggingConfig:
        LogGroup: !Ref DynatraceApiClientLogGroup
      Role: !GetAtt DynatraceApiClientRole.Arn
      Environment:
        Variables:
          LOG_LEVEL: INFO
          DT_ENVIRONMENT_URL: !Ref pDynatraceUrl
          DT_API_SECRET_ARN: !Sub
            - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/integration/${pDeploymentInstanceIdentifier}'
            - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
      Code:
        ZipFile: |
          
            import boto3
            import json
            import os
            import string
            from random import choices
            import cfnresponse
            import logging
            from enum import Enum
            import urllib3
            import re
            
            session = boto3.session.Session()
            sm_client = session.client('secretsmanager')
            
            
            class DtApiException(Exception):
                pass
            
            
            class Step(Enum):
                HAS_CONFIGURATION = 1
                HAS_MONITORING_ROLE = 2
                MONITORING_CONFIGURATION = 3
                REPORT_STATUS = 4
                REPORT_COMPLETE = 5
            
            
            PROP_STEP = 'Step'
            PROP_HAS_ID = 'SettingsObjectId'
            PROP_HAS_NAME = 'HASConnectionName'
            PROP_MONITORING_ROLE = 'MonitoringRole'
            PROP_MONITORING_CONFIG_ID = 'MonitoringConfigurationId'
            PROP_ORGANIZATION_ID = 'AwsOrganizationId'
            
            LOG = logging.getLogger(__name__)
            LOG.setLevel(logging.INFO)
            
            TOKENS_API_PATH = '/api/v2/apiTokens'
            SETTINGS_OBJECTS_API_PATH = '/platform/classic/environment-api/v2/settings/objects'
            SETTINGS_UPDATE_OBJECT_API_PATH = '/platform/classic/environment-api/v2/settings/objects/{object_id}'
            MONITORING_CONFIGURATIONS_API_PATH = '/platform/extensions/v2/extensions/{extension_name}/monitoring-configurations'
            MONITORING_CONFIGURATION_GET_UPDATE_API_PATH = '/platform/extensions/v2/extensions/{extension_name}/monitoring-configurations/{configuration_id}'
            
            AWS_MONITORING_EXTENSION_NAME = 'com.dynatrace.extension.da-aws'
            
            DYNATRACE_URL_PATTERN = r'^https://.*\.(?:dynatrace|dynatracelabs)\.com$'
            
            
            def _get_random_string(size=8):
                return ''.join(choices(string.ascii_lowercase + string.digits, k=size))
            
            
            def _get_create_has_settings_body(connection_name, arn):
                return {
                    'schemaId': 'builtin:hyperscaler-authentication.connections.aws',
                    'value': {
                        'name': connection_name,
                        'type': 'awsRoleBasedAuthentication',
                        'awsRoleBasedAuthentication': {
                            'roleArn': arn,
                            'consumers': ['SVC:com.dynatrace.da']
                        }
                    }
                }
            
            
            dt_api_http_client = urllib3.PoolManager()
            
            
            def _send_request(path, method, payload_obj=None):
                payload = json.dumps(payload_obj)
                dt_environment_url = os.environ['DT_ENVIRONMENT_URL']
                validate_url(dt_environment_url)
                token = _get_dt_api_key(os.environ['DT_API_SECRET_ARN'])
                dt_api_url = dt_environment_url + path
                headers = {
                    'Authorization': 'Bearer ' + token,
                    'accept': 'application/json; charset=utf-8',
                    'Content-Type': 'application/json; charset=utf-8'
                }
                if method in ['PUT', 'POST']:
                    response = dt_api_http_client.request(method, dt_api_url, headers=headers, body=payload)
                else:
                    response = dt_api_http_client.request(method, dt_api_url, headers=headers)
                if response.status > 299:
                    try:
                        response_obj = json.loads(response.data) if response.data else None
                        if isinstance(response_obj, list) and len(response_obj) > 0:
                            response_obj = response_obj[0]
                        if isinstance(response_obj, dict):
                            error = response_obj.get('error', {})
                        else:
                            error = {'message': response_obj}
                    except json.JSONDecodeError:
                        error = {'message': response.data.decode(encoding="utf-8")[:500]}
            
                    msg = f"Dynatrace API {path} {method} request failed: HTTP status {response.status} Error: {error.get('code', None)} {error.get('message', 'no message')}{error.get('constraintViolations', '')}"
            
                    constraint_violations = str(error.get('constraintViolations', ''))
                    if 'Account ID must be unique' in constraint_violations:
                        msg = f"This AWS account is already monitored by Dynatrace. {msg}"
            
                    raise DtApiException(msg)
                return json.loads(response.data) if response.data else {}
            
            
            def validate_url(url: str):
                if not re.match(DYNATRACE_URL_PATTERN, url):
                    raise DtApiException("DT_ENVIRONMENT_URL must be a proper Dynatrace URL. Got: " + url)
            
            
            def _get_dt_api_key(secret_arn):
                get_secret_value_response = sm_client.get_secret_value(SecretId=secret_arn)
                secret = get_secret_value_response['SecretString']
                try:
                    api_key = json.loads(secret)['dynatrace_api_token']
                except (json.JSONDecodeError, KeyError):
                    api_key = None
                    LOG.error("Invalid content of the secret %s", secret_arn)
                return api_key
            
            
            def create_has_settings():
                connection_name = 'da_aws_connection-' + _get_random_string()
                response = _send_request(SETTINGS_OBJECTS_API_PATH, 'POST',
                                         [_get_create_has_settings_body(connection_name, '')])
                return response[0]['objectId'], connection_name
            
            
            def update_has_settings(object_id, connection_name, monitoring_role_arn):
                path = SETTINGS_UPDATE_OBJECT_API_PATH.format(object_id=object_id)
                payload = _get_create_has_settings_body(connection_name, monitoring_role_arn)
                response = _send_request(path, 'PUT', payload)
                return response['objectId']
            
            
            def get_monitoring_configuration(configuration_id):
                path = (MONITORING_CONFIGURATION_GET_UPDATE_API_PATH
                        .format(extension_name=AWS_MONITORING_EXTENSION_NAME, configuration_id=configuration_id))
                response = _send_request(path, 'GET')
                return response
            
            
            def create_monitoring_configuration(payload):
                path = MONITORING_CONFIGURATIONS_API_PATH.format(extension_name=AWS_MONITORING_EXTENSION_NAME)
                response = _send_request(path, 'POST', payload)
                return response['objectId']
            
            
            def update_monitoring_configuration(configuration_id, payload):
                path = (MONITORING_CONFIGURATION_GET_UPDATE_API_PATH
                        .format(extension_name=AWS_MONITORING_EXTENSION_NAME, configuration_id=configuration_id))
                response = _send_request(path, 'PUT', payload)
                return response['objectId']
            
            def has_properties_from_physical_id(physical_resource_id):
                settings_obj_id, connection_name =  physical_resource_id.split(':')
                return settings_obj_id, connection_name
            
            def create_has_configuration(cfn_action, physical_resource_id):
                if cfn_action == 'Create':
                    LOG.info('[%s] Creating HAS settings', os.environ['DT_ENVIRONMENT_URL'])
                    settings_obj_id, connection_name = create_has_settings()
                    physical_resource_id = f'{settings_obj_id}:{connection_name}'
                else:
                    settings_obj_id, connection_name =  has_properties_from_physical_id(physical_resource_id)
                return physical_resource_id, {PROP_HAS_ID: settings_obj_id, PROP_HAS_NAME: connection_name}
            
            
            def update_has_configuration(properties, old_properties):
                has_settings_id = properties[PROP_HAS_ID]
                if old_properties:
                    return {PROP_HAS_ID: has_settings_id}
                monitoring_role_arn = properties[PROP_MONITORING_ROLE]
                has_connection_name = properties[PROP_HAS_NAME]
                LOG.info('[%s] Updating HAS settings with monitoring role arn', os.environ['DT_ENVIRONMENT_URL'])
                settings_obj_id = update_has_settings(has_settings_id, has_connection_name, monitoring_role_arn)
                return {PROP_HAS_ID: settings_obj_id}
            
            
            def configure_monitoring(physical_resource_id, properties, old_properties):
                if old_properties and properties.get(PROP_HAS_ID) == old_properties.get(PROP_HAS_ID):
                    return physical_resource_id, {PROP_MONITORING_CONFIG_ID: physical_resource_id}
                monitoring_configuration_id = properties[PROP_MONITORING_CONFIG_ID]
                aws_account_id = get_account_id_from_arn(properties[PROP_MONITORING_ROLE])
                connection_id = properties[PROP_HAS_ID]
                aws_organization_id = properties.get(PROP_ORGANIZATION_ID, None)
                configuration_object = get_monitoring_configuration(monitoring_configuration_id)
                if aws_organization_id:
                    validate_organization_configuration(configuration_object, aws_organization_id)
                monitoring_enabled = connection_id is not None
                configuration_object['value']['aws']['credentials'] = [{
                    'description': 'HAS connection',
                    'enabled': monitoring_enabled,
                    'connectionId': connection_id,
                    'accountId': aws_account_id
                }]
                configuration_object['value']['enabled'] = monitoring_enabled
                configuration_object['value']['aws']['automatedDeploymentTemplateVersion'] = 'v1.0.8'
                if aws_organization_id:
                    LOG.info('[%s] Creating AWS monitoring configuration for member - %s', os.environ['DT_ENVIRONMENT_URL'], aws_account_id)
                    configuration_object['value']['description'] = f'member-{aws_organization_id}-{aws_account_id}'
                    configuration_object['value']['aws']['credentials'][0]['rootOrganizationId'] = aws_organization_id
                    configuration_object['value']['aws']['credentials'][0]['parentConfigurationId'] = monitoring_configuration_id
                    configuration_object['value']['aws']['deploymentScope'] = 'SINGLE_ACCOUNT'
                    configuration_object['value']['aws']['deploymentMode'] = 'AUTOMATED'
                    configuration_object['value']['aws']['automatedDeploymentStatus'] = 'RUNNING'
                    configuration_id = create_monitoring_configuration(configuration_object)
                else:
                    LOG.info('[%s] Updating AWS monitoring configuration', os.environ['DT_ENVIRONMENT_URL'])
                    configuration_id = update_monitoring_configuration(monitoring_configuration_id, configuration_object)
                return configuration_id, {PROP_MONITORING_CONFIG_ID: configuration_id}
            
            
            def validate_organization_configuration(configuration_object, aws_organization_id):
                aws_configuration_object = configuration_object['value']['aws']
            
                deployment_scope = aws_configuration_object['deploymentScope']
                if deployment_scope != 'ORGANIZATION':
                    raise DtApiException(f"Incorrect deploymentScope provided in the monitoring configuration template (Current: {deployment_scope}, Expected: ORGANIZATION)")
            
                if 'accountId' in aws_configuration_object['credentials'][0]:
                    raise DtApiException("accountId property must be null for the organization monitoring configuration template")
            
                if 'rootOrganizationId' not in aws_configuration_object['credentials'][0]:
                    raise DtApiException("rootOrganizationId property must be set for the organization monitoring configuration template")
            
                root_organization_id = aws_configuration_object['credentials'][0]['rootOrganizationId']
                if root_organization_id != aws_organization_id:
                    raise DtApiException(f"Incorrect rootOrganizationId provided in the monitoring configuration (Current: {root_organization_id}, Expected: {aws_organization_id})")
            
            
            def delete_has_configuration(physical_resource_id):
                result_status = cfnresponse.SUCCESS
                result_reason = None
                if ':' not in physical_resource_id:
                    LOG.warning('No settings_object_id in physical_resource_id "%s", skipping deletion', physical_resource_id)
                    return result_status, result_reason
                settings_object_id, _ = has_properties_from_physical_id(physical_resource_id)
                try:
                    dt_environment_url = os.environ['DT_ENVIRONMENT_URL']
                    LOG.info('[%s] Deleting HAS AWS settings object - %s', dt_environment_url, settings_object_id)
                    path = SETTINGS_UPDATE_OBJECT_API_PATH.format(object_id=settings_object_id)
                    _send_request(path, 'DELETE', None)
                except Exception as e:
                    LOG.error(e)
                    result_status = cfnresponse.SUCCESS
                    result_reason = f"Failed to remove monitoring configuration ({settings_object_id}). It needs to be removed manually."
                return result_status, result_reason
            
            
            def report_deployment_status(monitoring_configuration_id, deployment_status):
                try:
                    dt_environment_url = os.environ['DT_ENVIRONMENT_URL']
                    LOG.info('[%s] Reporting deployment status - %s', dt_environment_url, deployment_status)
                    configuration_object = get_monitoring_configuration(monitoring_configuration_id)
                    configuration_object['value']['aws']['automatedDeploymentStatus'] = deployment_status
                    configuration_object['value']['aws']['automatedDeploymentTemplateVersion'] = 'v1.0.8'
                    update_monitoring_configuration(monitoring_configuration_id, configuration_object)
                except Exception as e:
                    LOG.error(e)
                    LOG.warning("Failed to report status to monitoring configuration.")
            
            def get_account_id_from_arn(arn):
                # Example ARN: arn:aws:iam::123456789012:resource_type/resource_id
                if not arn or len(arn.split(':')) < 5:
                    raise Exception("Invalid role ARN provided: %s.")
                return arn.split(':')[4]
            
            def handler(event, context):
                result_status = cfnresponse.SUCCESS
                response_data = {}
                result_reason = ""
                cfn_action = event['RequestType']
                properties = event['ResourceProperties']
                old_properties = event.get('OldResourceProperties')
                try:
                    if PROP_STEP not in properties.keys():
                        cfnresponse.send(event, context, cfnresponse.FAILED, {}, reason=f"Missing {PROP_STEP} property")
                        return
            
                    step = properties[PROP_STEP]
                    physical_resource_id = event.get('PhysicalResourceId', step)
            
                    if step == Step.HAS_CONFIGURATION.name and cfn_action in ['Create', 'Update']:
                        physical_resource_id, response_data = create_has_configuration(cfn_action, physical_resource_id)
                    elif step == Step.HAS_CONFIGURATION.name and cfn_action == 'Delete':
                        result_status, result_reason = delete_has_configuration(physical_resource_id)
                    elif step == Step.HAS_MONITORING_ROLE.name and cfn_action in ['Create', 'Update']:
                        response_data = update_has_configuration(properties, old_properties)
                    elif step == Step.MONITORING_CONFIGURATION.name and cfn_action in ['Create', 'Update']:
                        physical_resource_id, response_data = configure_monitoring(physical_resource_id, properties, old_properties)
                    elif step == Step.REPORT_STATUS.name and cfn_action != 'Delete':
                        report_deployment_status(properties[PROP_MONITORING_CONFIG_ID], "RUNNING")
                    elif step == Step.REPORT_COMPLETE.name and cfn_action != 'Delete':
                        report_deployment_status(properties[PROP_MONITORING_CONFIG_ID], "COMPLETE")
                    else:
                        msg = f'Dynatrace AWS monitoring activation stack {cfn_action} {properties[PROP_STEP]} - nothing to do'
                        LOG.info(msg)
                        result_reason = msg
            
                except DtApiException as e:
                    result_reason = e.args[0]
                    LOG.error(result_reason)
                    result_status = cfnresponse.FAILED
                except Exception as e:
                    result_reason = str(e)
                    LOG.error(e)
                    result_status = cfnresponse.FAILED
            
            
                cfnresponse.send(event, context, result_status, response_data, physicalResourceId=physical_resource_id,
                                 reason=result_reason)
      Tags:
      - Key: 'dt:CreatedBy'
        Value: 'Dynatrace'
      - Key: 'dt:EnvironmentID'
        Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceApiClientRole:
    Type: AWS::IAM::Role
    Condition: cIsManagementRegion
    Properties:
      RoleName: !Sub
        - 'DynatraceApiClientRole-${DynatraceEnvironmentId}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action: 'sts:AssumeRole'
            Principal:
              Service: 'lambda.amazonaws.com'
      Policies:
        - PolicyName: DynatraceApiClientLambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !GetAtt DynatraceApiClientLogGroup.Arn
              - Effect: Allow
                Action:
                  - 'secretsmanager:GetSecretValue'
                Resource: !Sub
                  - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/integration/${pDeploymentInstanceIdentifier}-*'
                  - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
              - Effect: Allow
                Action:
                  - 'kms:Decrypt'
                Resource: !Sub 'arn:aws:kms:${AWS::Region}:${pSecretsAccountId}:key/*'
                Condition:
                  StringEquals:
                    'kms:ViaService': !Sub 'secretsmanager.${AWS::Region}.amazonaws.com'
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  CreateCredentialsSettingsObject:
    Type: Custom::DynatraceApiAccessFunction
    Condition: cIsManagementRegion
    Properties:
      ServiceToken: !GetAtt DynatraceApiClientFunction.Arn
      ServiceTimeout: 120
      Step: HAS_CONFIGURATION
      DynatraceApiKeySecret: !Sub
        - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/integration/${pDeploymentInstanceIdentifier}'
        - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceMonitoringPolicy:
   Type: AWS::IAM::ManagedPolicy
   Condition: cIsManagementRegion
   Properties:
    ManagedPolicyName: !Sub
      - 'DynatraceMonitoringPolicy-${DynatraceEnvironmentId}-${DeploymentHash}'
      - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
    Description: 'Dynatrace Data Acquisition Monitoring Policy'
    PolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Sid: TopologyInventory
          Effect: Allow
          Action:
            - 'account:GetAccountInformation'
            - 'acm-pca:ListCertificateAuthorities'
            - 'aoss:BatchGetCollection'
            - 'aoss:ListCollections'
            - 'apigateway:GET'
            - 'apprunner:DescribeAutoScalingConfiguration'
            - 'apprunner:DescribeService'
            - 'apprunner:DescribeVpcConnector'
            - 'apprunner:DescribeVpcIngressConnection'
            - 'apprunner:ListAutoScalingConfigurations'
            - 'apprunner:ListServices'
            - 'apprunner:ListTagsForResource'
            - 'apprunner:ListVpcConnectors'
            - 'apprunner:ListVpcIngressConnections'
            - 'athena:GetWorkGroup'
            - 'athena:ListWorkGroups'
            - 'autoscaling:DescribeAutoScalingGroups'
            - 'autoscaling:DescribeWarmPool'
            - 'bedrock:GetAgent'
            - 'bedrock:GetAgentAlias'
            - 'bedrock:GetGuardrail'
            - 'bedrock:GetKnowledgeBase'
            - 'bedrock:ListAgentAliases'
            - 'bedrock:ListAgents'
            - 'bedrock:ListGuardrails'
            - 'bedrock:ListKnowledgeBases'
            - 'cloudfront:GetDistribution'
            - 'cloudfront:ListDistributions'
            - 'cloudfront:ListTagsForResource'
            - 'cloudhsm:DescribeClusters'
            - 'cloudtrail:GetEventSelectors'
            - 'cloudtrail:GetTrail'
            - 'cloudtrail:GetTrailStatus'
            - 'cloudtrail:ListTrails'
            - 'cloudtrail:LookupEvents'
            - 'dax:DescribeClusters'
            - 'dax:DescribeSubnetGroups'
            - 'directconnect:DescribeConnections'
            - 'directconnect:DescribeVirtualInterfaces'
            - 'dms:DescribeReplicationInstances'
            - 'dynamodb:DescribeContinuousBackups'
            - 'dynamodb:DescribeKinesisStreamingDestination'
            - 'dynamodb:DescribeTable'
            - 'dynamodb:DescribeTimeToLive'
            - 'dynamodb:ListTables'
            - 'ec2:DescribeAddresses'
            - 'ec2:DescribeAvailabilityZones'
            - 'ec2:DescribeClientVpnEndpoints'
            - 'ec2:DescribeCustomerGateways'
            - 'ec2:DescribeDhcpOptions'
            - 'ec2:DescribeEgressOnlyInternetGateways'
            - 'ec2:DescribeIamInstanceProfileAssociations'
            - 'ec2:DescribeInstances'
            - 'ec2:DescribeInternetGateways'
            - 'ec2:DescribeLaunchTemplateVersions'
            - 'ec2:DescribeLaunchTemplates'
            - 'ec2:DescribeNatGateways'
            - 'ec2:DescribeNetworkAcls'
            - 'ec2:DescribeNetworkInterfaces'
            - 'ec2:DescribeRegions'
            - 'ec2:DescribeRouteTables'
            - 'ec2:DescribeSecurityGroups'
            - 'ec2:DescribeSnapshots'
            - 'ec2:DescribeSubnets'
            - 'ec2:DescribeTransitGatewayAttachments'
            - 'ec2:DescribeTransitGatewayConnects'
            - 'ec2:DescribeTransitGatewayMulticastDomains'
            - 'ec2:DescribeTransitGatewayRouteTables'
            - 'ec2:DescribeTransitGateways'
            - 'ec2:DescribeVolumes'
            - 'ec2:DescribeVpcEndpointServiceConfigurations'
            - 'ec2:DescribeVpcEndpoints'
            - 'ec2:DescribeVpcPeeringConnections'
            - 'ec2:DescribeVpcs'
            - 'ec2:DescribeVpnConnections'
            - 'ec2:DescribeVpnGateways'
            - 'ecr-public:DescribeRepositories'
            - 'ecr-public:GetRepositoryPolicy'
            - 'ecr:DescribeRepositories'
            - 'ecr:GetRepositoryPolicy'
            - 'ecs:DescribeCapacityProviders'
            - 'ecs:DescribeClusters'
            - 'ecs:DescribeContainerInstances'
            - 'ecs:DescribeServices'
            - 'ecs:DescribeTaskDefinition'
            - 'ecs:DescribeTasks'
            - 'ecs:ListClusters'
            - 'ecs:ListContainerInstances'
            - 'ecs:ListServices'
            - 'ecs:ListTaskDefinitions'
            - 'ecs:ListTasks'
            - 'eks:DescribeCluster'
            - 'eks:DescribeNodegroup'
            - 'eks:ListClusters'
            - 'eks:ListNodegroups'
            - 'elasticache:DescribeCacheClusters'
            - 'elasticache:DescribeCacheParameterGroups'
            - 'elasticache:DescribeCacheParameters'
            - 'elasticache:DescribeCacheSubnetGroups'
            - 'elasticache:DescribeServerlessCaches'
            - 'elasticbeanstalk:DescribeApplications'
            - 'elasticbeanstalk:DescribeEnvironmentResources'
            - 'elasticbeanstalk:DescribeEnvironments'
            - 'elasticfilesystem:DescribeAccessPoints'
            - 'elasticfilesystem:DescribeFileSystems'
            - 'elasticfilesystem:DescribeMountTargets'
            - 'elasticloadbalancing:DescribeListeners'
            - 'elasticloadbalancing:DescribeLoadBalancerAttributes'
            - 'elasticloadbalancing:DescribeLoadBalancerPolicies'
            - 'elasticloadbalancing:DescribeLoadBalancers'
            - 'elasticloadbalancing:DescribeRules'
            - 'elasticloadbalancing:DescribeTargetGroupAttributes'
            - 'elasticloadbalancing:DescribeTargetGroups'
            - 'elasticloadbalancing:DescribeTargetHealth'
            - 'es:DescribeDomains'
            - 'es:ListDomainNames'
            - 'events:DescribeEventBus'
            - 'events:ListEventBuses'
            - 'firehose:DescribeDeliveryStream'
            - 'firehose:ListDeliveryStreams'
            - 'iam:GenerateCredentialReport'
            - 'iam:GetAccountAuthorizationDetails'
            - 'iam:GetAccountPasswordPolicy'
            - 'iam:GetAccountSummary'
            - 'iam:GetCredentialReport'
            - 'iam:ListAccountAliases'
            - 'iam:ListInstanceProfiles'
            - 'iam:ListMFADevices'
            - 'iam:ListServerCertificates'
            - 'kafka:ListClustersV2'
            - 'kms:DescribeKey'
            - 'kms:GetKeyRotationStatus'
            - 'kms:ListAliases'
            - 'kms:ListKeys'
            - 'lambda:GetAlias'
            - 'lambda:GetFunction'
            - 'lambda:GetPolicy'
            - 'lambda:ListAliases'
            - 'lambda:ListEventSourceMappings'
            - 'lambda:ListFunctions'
            - 'logs:DescribeLogGroups'
            - 'logs:DescribeSubscriptionFilters'
            - 'mq:DescribeBroker'
            - 'mq:DescribeConfiguration'
            - 'mq:ListBrokers'
            - 'mq:ListConfigurations'
            - 'organizations:DescribeOrganization'
            - 'rds:DescribeOptionGroups'
            - 'rds:ListTagsForResource'
            - 'redshift-serverless:ListNamespaces'
            - 'redshift-serverless:ListWorkgroups'
            - 'redshift:DescribeClusterSubnetGroups'
            - 'redshift:DescribeClusters'
            - 'redshift:DescribeLoggingStatus'
            - 'route53:GetHostedZone'
            - 'route53:ListHealthChecks'
            - 'route53:ListHostedZones'
            - 's3:GetAccelerateConfiguration'
            - 's3:GetBucketAcl'
            - 's3:GetBucketLogging'
            - 's3:GetBucketNotification'
            - 's3:GetBucketPolicy'
            - 's3:GetBucketPublicAccessBlock'
            - 's3:GetBucketRequestPayment'
            - 's3:GetBucketVersioning'
            - 's3:GetEncryptionConfiguration'
            - 's3:ListAllMyBuckets'
            - 'sns:GetTopicAttributes'
            - 'sns:ListTopics'
            - 'sqs:GetQueueAttributes'
            - 'sqs:ListQueues'
            - 'states:DescribeStateMachine'
            - 'states:ListStateMachines'
            - 'storagegateway:DescribeGatewayInformation'
            - 'storagegateway:ListGateways'
            - 'tag:GetResources'
            - 'tag:GetTagKeys'
            - 'tag:GetTagValues'
            - 'wafv2:GetWebACL'
            - 'wafv2:ListWebACLs'
          Resource: '*'
        - Sid: CloudWatchMonitoring
          Effect: Allow
          Action:
            - 'cloudwatch:GetMetricData'
            - 'cloudwatch:ListMetrics'
          Resource: '*'
  DynatraceMonitoringPolicy2:
    Type: AWS::IAM::ManagedPolicy
    Condition: cIsManagementRegion
    Properties:
      ManagedPolicyName: !Sub
        - 'DynatraceMonitoringPolicy-${DynatraceEnvironmentId}-${DeploymentHash}-2'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      Description: Dynatrace Data Acquisition Monitoring Policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: CassandraTopologyPrerequisites
            # See https://docs.aws.amazon.com/keyspaces/latest/devguide/security_iam_id-based-policy-examples.html#security_iam_id-based-policy-examples-access-one-table
            Effect: Allow
            Action:
              - 'cassandra:Select'
            Resource:
              - !Sub 'arn:aws:cassandra:*:${AWS::AccountId}:/keyspace/system*'
          - Sid: CassandraTopology
            Effect: Allow
            Action:
              - 'cassandra:GetKeyspace'
              - 'cassandra:GetTable'
              - 'cassandra:ListKeyspaces'
              - 'cassandra:ListTables'
            Resource: '*'
          - Sid: TopologyInventory
            Effect: Allow
            Action:
              - 'acm:ListCertificates'
              - 'airflow:GetEnvironment'
              - 'airflow:ListEnvironments'
              - 'apigateway:GetStage'
              - 'appstream:DescribeFleets'
              - 'appstream:ListTagsForResource'
              - 'appsync:ListGraphqlApis'
              - 'backup:GetBackupPlan'
              - 'backup:GetBackupVaultNotifications'
              - 'backup:ListBackupPlans'
              - 'backup:ListBackupVaults'
              - 'codebuild:BatchGetProjects'
              - 'codebuild:ListProjects'
              - 'cognito-idp:DescribeUserPool'
              - 'cognito-idp:ListUserPools'
              - 'connect:DescribeInstance'
              - 'connect:ListInstanceStorageConfigs'
              - 'connect:ListInstances'
              - 'datasync:DescribeTask'
              - 'datasync:ListLocations'
              - 'datasync:ListTasks'
              - 'ec2:DescribeFlowLogs'
              - 'ec2:DescribeIpamPools'
              - 'ec2:DescribeIpamScopes'
              - 'ec2:DescribeIpams'
              - 'elasticmapreduce:DescribeCluster'
              - 'emr-containers:DescribeManagedEndpoint'
              - 'emr-containers:DescribeVirtualCluster'
              - 'emr-serverless:GetApplication'
              - 'events:ListRules'
              - 'execute-api:GetStage'
              - 'fsx:DescribeFileSystems'
              - 'fsx:DescribeStorageVirtualMachines'
              - 'fsx:DescribeVolumes'
              - 'globalaccelerator:DescribeListener'
              - 'globalaccelerator:ListAccelerators'
              - 'globalaccelerator:ListListeners'
              - 'globalaccelerator:ListTagsForResource'
              - 'glue:GetJobs'
              - 'kafka:DescribeClusterV2'
              - 'kafka:DescribeConfiguration'
              - 'kafka:DescribeVpcConnection'
              - 'kafka:ListConfigurations'
              - 'kafka:ListVpcConnections'
              - 'kafkaconnect:DescribeConnector'
              - 'kafkaconnect:ListConnectors'
              - 'kinesis:DescribeStreamSummary'
              - 'kinesis:ListStreams'
              - 'kinesisanalytics:DescribeApplication'
              - 'kinesisanalytics:ListApplications'
              - 'logs:DescribeDeliveryDestinations'
              - 'network-firewall:DescribeFirewall'
              - 'network-firewall:DescribeFirewallPolicy'
              - 'network-firewall:ListFirewallPolicies'
              - 'network-firewall:ListFirewalls'
              - 'rds:DescribeDBClusterSnapshots'
              - 'rds:DescribeDBClusters'
              - 'rds:DescribeDBInstances'
              - 'rds:DescribeDBSnapshots'
              - 'rds:DescribeDBSubnetGroups'
              - 'redshift-serverless:GetNamespace'
              - 'route53:GetHealthCheck'
              - 'route53resolver:ListResolverEndpoints'
              - 's3:ListBuckets'
              - 'sagemaker:DescribeEndpoint'
              - 'sagemaker:DescribeFeatureGroup'
              - 'sagemaker:DescribeInferenceComponent'
              - 'sagemaker:DescribeLabelingJob'
              - 'sagemaker:DescribePipeline'
              - 'sagemaker:ListEndpoints'
              - 'sagemaker:ListFeatureGroups'
              - 'sagemaker:ListInferenceComponents'
              - 'sagemaker:ListLabelingJobs'
              - 'sagemaker:ListPipelines'
              - 'secretsmanager:ListSecrets'
              - 'ssm:DescribeParameters'
            Resource: '*'

  DynatraceMonitoringRole:
    Type: AWS::IAM::Role
    Condition: cIsManagementRegion
    Properties:
      RoleName: !Sub
        - 'DynatraceMonitoringRole-${DynatraceEnvironmentId}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      Description: 'Dynatrace Data Acquisition Monitoring Role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                Fn::If:
                  - cProductionEnvironment
                  - '314146291599'
                  - '476114158034'
            Action: 'sts:AssumeRole'
            Condition:
              StringEquals:
                sts:ExternalId: !GetAtt CreateCredentialsSettingsObject.SettingsObjectId
      ManagedPolicyArns:
        - !Ref DynatraceMonitoringPolicy
        - !Ref DynatraceMonitoringPolicy2
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  ConfigureCredentialsSettingsObject:
    Type: Custom::DynatraceApiAccessFunction
    Condition: cIsManagementRegion
    Properties:
      ServiceToken: !GetAtt DynatraceApiClientFunction.Arn
      ServiceTimeout: 120
      Step: HAS_MONITORING_ROLE
      DynatraceApiKeySecret: !Sub
        - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/integration/${pDeploymentInstanceIdentifier}'
        - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
      SettingsObjectId: !GetAtt CreateCredentialsSettingsObject.SettingsObjectId
      HASConnectionName: !GetAtt CreateCredentialsSettingsObject.HASConnectionName
      MonitoringRole: !GetAtt DynatraceMonitoringRole.Arn

  ConfigureDynatraceConnectionCredentials:
    Type: Custom::DynatraceApiAccessFunction
    DependsOn: ConfigureCredentialsSettingsObject
    Condition: cIsManagementRegion
    Properties:
      ServiceToken: !GetAtt DynatraceApiClientFunction.Arn
      ServiceTimeout: 120
      Step: MONITORING_CONFIGURATION
      DynatraceApiKeySecret: !Sub
          - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/integration/${pDeploymentInstanceIdentifier}'
          - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
      MonitoringConfigurationId: !Ref pMonitoringConfigId
      SettingsObjectId: !GetAtt CreateCredentialsSettingsObject.SettingsObjectId
      HASConnectionName: !GetAtt CreateCredentialsSettingsObject.HASConnectionName
      AwsOrganizationId: !Ref pAwsOrganizationId
      MonitoringRole: !GetAtt DynatraceMonitoringRole.Arn

  DynatraceLogSubscription:
    Type: AWS::IAM::Role
    Condition: cDeployLogIngestStack
    Properties:
      RoleName: !Sub
        - 'DynatraceLogSubscription-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      Description: 'Role for Subscription Filters (to write to Firehose)'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: 'logs.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'firehose:PutRecord'
                  - 'firehose:PutRecordBatch'
                Resource: !GetAtt DynatraceLogDeliveryStream.Arn
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceLogDeliveryStream:
    Type: AWS::KinesisFirehose::DeliveryStream
    Condition: cDeployLogIngestStack
    Properties:
      DeliveryStreamName: !Sub
        - 'DynatraceLogDeliveryStream-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      DeliveryStreamType: 'DirectPut'
      DeliveryStreamEncryptionConfigurationInput: !If
        - cUseCMK
        - KeyType: CUSTOMER_MANAGED_CMK
          KeyARN: !GetAtt DynatraceKMSKey.Arn
        - !Ref AWS::NoValue
      HttpEndpointDestinationConfiguration:
        EndpointConfiguration:
          Url: !If
            - cProductionEnvironment
            - !Sub
              - 'https://${Subdomain}.da.dynatrace.com/api/aws/firehose/v1/logs'
              - Subdomain: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "://", !Ref pDynatraceUrl ] ] ] ]
            - !Sub
              - 'https://${Subdomain}.da.dynatracelabs.com/api/aws/firehose/v1/logs'
              - Subdomain: !Select [ 0, !Split [ ".apps", !Select [ 0, !Split [ ".dynatracelabs.com", !Select [ 1, !Split [ "://", !Ref pDynatraceUrl ] ] ] ] ] ]
          Name: Dynatrace delivery endpoint
        SecretsManagerConfiguration:
          Enabled: true
          SecretARN: !Sub
              - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/ingest/${pDeploymentInstanceIdentifier}'
              - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
        RequestConfiguration:
          ContentEncoding: GZIP
        BufferingHints:
          IntervalInSeconds: 60
          SizeInMBs: 1
        RetryOptions:
          DurationInSeconds: 7200
        CloudWatchLoggingOptions:
          Enabled: true
          LogGroupName: !Ref DynatraceFirehoseLogGroup
          LogStreamName: !Ref DynatraceFirehoseLogStream
        RoleARN: !GetAtt DynatraceDeliveryStreamRole.Arn
        S3BackupMode: FailedDataOnly
        S3Configuration:
          BucketARN: !GetAtt DynatraceFirehoseBackupS3Bucket.Arn
          RoleARN: !GetAtt DynatraceDeliveryStreamRole.Arn
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceFirehoseLogGroup:
    Type: AWS::Logs::LogGroup
    Condition: cDeployLogIngestStack
    Properties:
      LogGroupName: !Sub
        - '/aws/datafirehose/dynatrace/${DynatraceEnvironmentId}/${AWS::Region}/${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      RetentionInDays: 14
      KmsKeyId: !If [ cUseCMK, !GetAtt DynatraceKMSKey.Arn, !Ref AWS::NoValue ]
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceFirehoseLogStream:
    Type: AWS::Logs::LogStream
    Condition: cDeployLogIngestStack
    Properties:
      LogStreamName: !Sub
        - 'DynatraceFirehoseLogStream-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      LogGroupName: !Ref DynatraceFirehoseLogGroup

  DynatraceFirehoseBackupS3Bucket:
    Type: AWS::S3::Bucket
    Condition: cDeployLogIngestStack
    Properties:
      BucketName: !Sub
        - 'dynatrace-logs-backup-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      LifecycleConfiguration:
        Rules:
          - ExpirationInDays: 14
            Status: 'Enabled'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: !If [ cUseCMK, !GetAtt DynatraceKMSKey.Arn, 'alias/aws/s3' ]
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceDeliveryStreamRole:
    Type: AWS::IAM::Role
    Condition: cDeployLogIngestStack
    Properties:
      RoleName: !Sub
        - 'DynatraceDeliveryStreamRole-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      Description: 'Dynatrace Logs Firehose delivery stream role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: 'firehose.amazonaws.com'
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: FirehoseSecretAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'secretsmanager:GetSecretValue'
                Resource:
                  - !Sub
                    - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/ingest/${pDeploymentInstanceIdentifier}-*'
                    - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
        - PolicyName: FirehoseLoggingPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:PutLogEvents'
                Resource:
                  - !GetAtt DynatraceFirehoseLogGroup.Arn
        - PolicyName: FirehoseDeliveryPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:AbortMultipartUpload'
                  - 's3:GetBucketLocation'
                  - 's3:GetObject'
                  - 's3:ListBucket'
                  - 's3:ListBucketMultipartUploads'
                  - 's3:PutObject'
                Resource:
                  - !GetAtt DynatraceFirehoseBackupS3Bucket.Arn
                  - !Join ['/', [!GetAtt DynatraceFirehoseBackupS3Bucket.Arn, '*']]
        - PolicyName: DecryptIngestSecretFromFoundationalStackPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'kms:Decrypt'
                Resource: !Sub 'arn:aws:kms:${AWS::Region}:${pSecretsAccountId}:key/*'
                Condition:
                  StringEquals:
                    'kms:ViaService': !Sub 'secretsmanager.${AWS::Region}.amazonaws.com'
                  StringLike:
                    'kms:EncryptionContext:SecretARN': !Sub
                      - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/ingest/${pDeploymentInstanceIdentifier}-*'
                      - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
        - !If
          - cUseCMK
          - PolicyName: EncryptAndDecryptDeliveryStreamDataWithKMSPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - 'kms:Decrypt'
                    - 'kms:GenerateDataKey'
                    - 'kms:CreateGrant'
                  Resource:
                    - !GetAtt DynatraceKMSKey.Arn
          - !Ref AWS::NoValue
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceConnection:
    Type: AWS::Events::Connection
    Condition: cDeployEventIngestStack
    Properties:
      Name: !Sub
        - 'DynatraceConnection-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      AuthorizationType: API_KEY
      AuthParameters:
        ApiKeyAuthParameters:
          ApiKeyName: Authorization
          ApiKeyValue: !Join
            - ' '
            - - Bearer
              - !Sub
                - '{{resolve:secretsmanager:arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/ingest/${pDeploymentInstanceIdentifier}:SecretString:api_key}}'
                - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
        InvocationHttpParameters:
          HeaderParameters:
            - IsValueSecret: false
              Key: Dt-Tenant
              Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceApiDestination:
    Type: AWS::Events::ApiDestination
    Condition: cDeployEventIngestStack
    Properties:
      Name: !Sub
        - 'DynatraceApiDestination-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      ConnectionArn: !GetAtt DynatraceConnection.Arn
      HttpMethod: POST
      InvocationEndpoint: !If
        - cProductionEnvironment
        - !Sub
          - 'https://${Subdomain}.da.dynatrace.com/api/aws/eventbridge/v1/events'
          - Subdomain: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "://", !Ref pDynatraceUrl ] ] ] ]
        - !Sub
          - 'https://${Subdomain}.da.dynatracelabs.com/api/aws/eventbridge/v1/events'
          - Subdomain: !Select [ 0, !Split [ ".apps", !Select [ 0, !Split [ ".dynatracelabs.com", !Select [ 1, !Split [ "://", !Ref pDynatraceUrl ] ] ] ] ] ]
      InvocationRateLimitPerSecond: 100

  DynatraceEventBridgeRole:
    Type: AWS::IAM::Role
    Condition: cDeployEventIngestStack
    Properties:
      RoleName: !Sub
        - 'DynatraceEventBridgeRole-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: 'events.amazonaws.com'
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: AllowInvokeHttpApi
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'events:InvokeApiDestination'
                Resource: !Sub ${DynatraceApiDestination.Arn}
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceEventBridgeRule:
    Type: AWS::Events::Rule
    Condition: cDeployEventIngestStack
    Properties:
      Name: !Sub
        - 'DynatraceEventBridgeRule-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      EventBusName: !If [ cCustomEventBus, !Ref pEventBridgeBusName, !Ref AWS::NoValue ]
      EventPattern:
        source: !Ref pEventSources
      Targets:
        - Arn: !GetAtt DynatraceApiDestination.Arn
          RoleArn: !GetAtt DynatraceEventBridgeRole.Arn
          Id: dynatrace
          RetryPolicy:
            MaximumRetryAttempts: 185
            MaximumEventAgeInSeconds: 86400
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  ReportCompleteStatusResource:
    Type: Custom::DynatraceApiAccessFunction
    Condition: cIsManagementRegion
    Metadata:
      ConditionalLogsStackSetDependency: !If [ cDeployLogIngestStack, !Ref DynatraceLogDeliveryStream, !Ref AWS::NoValue ]
      ConditionalEventsStackSetDependency: !If [ cDeployEventIngestStack, !Ref DynatraceEventBridgeRule, !Ref AWS::NoValue ]
    Properties:
      ServiceToken: !GetAtt DynatraceApiClientFunction.Arn
      ServiceTimeout: 120
      Step: REPORT_COMPLETE
      DynatraceApiKeySecret: !Sub
        - 'arn:aws:secretsmanager:${AWS::Region}:${pSecretsAccountId}:secret:/dynatrace/${DynatraceEnvironmentId}/tokens/aws/${pAwsOrganizationId}/integration/${pDeploymentInstanceIdentifier}'
        - DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]
      MonitoringConfigurationId: !GetAtt ConfigureDynatraceConnectionCredentials.MonitoringConfigurationId
      # Pass rest of stack parameters (ignored) to trigger status reporting on every stack update
      pDynatraceUrl: !Ref pDynatraceUrl
      pDtLogsIngestEnabled: !Ref pDtLogsIngestEnabled
      pDtEventsIngestEnabled: !Ref pDtEventsIngestEnabled
      pEventBridgeBusName: !Ref pEventBridgeBusName
      pEventSources: !Ref pEventSources
      TemplateVersion: v1.0.8
