Parameters:
  ProjectName:
    Type: String
    Description: Name for the project
    
  VpcCIDR:
    Type: String
    Default: 10.0.0.0/16

  PublicSubnet1CIDR:
    Type: String
    Default: 10.0.0.0/19

  PublicSubnet2CIDR:
    Type: String
    Default: 10.0.32.0/19

  PrivateSubnet1CIDR:
    Type: String
    Default: 10.0.64.0/19
  
  PrivateSubnet2CIDR:
    Type: String
    Default: 10.0.96.0/19

  EnvoyImage:
    Type: String

  DJAppImage:
    Type: String

  CloudWatchAgentImage:
    Type: String

  ContainerPort:
    Type: Number
    Default: 8080

  GatewayPort:
    Type: Number
    Default: 8080

Resources:

  ###################################################
  # VPC Resources
  ###################################################
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref ProjectName

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref ProjectName

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet1CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName} Public Subnet (AZ1)'

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName} Public Subnet (AZ2)'

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PrivateSubnet1CIDR
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName} Private Subnet (AZ1)'

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: !Ref PrivateSubnet2CIDR
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName} Private Subnet (AZ2)'

  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc

  NatGateway2EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1

  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnet2

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName} Public Routes'

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName} Private Routes (AZ1)'

  DefaultPrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName} Private Routes (AZ2)'

  DefaultPrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

  ###################################################
  # Public Load-Balancer
  ###################################################
  PublicLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      Type: network

  DJAppGatewayTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub '${ProjectName}-gateway'
      VpcId: !Ref VPC
      HealthCheckIntervalSeconds: 30
      HealthCheckPort: !Ref GatewayPort
      HealthCheckProtocol: TCP
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 2
      TargetType: ip
      Port: !Ref GatewayPort
      Protocol: TCP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 120
  
  PublicLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref DJAppGatewayTargetGroup
          Type: forward
      LoadBalancerArn: !Ref PublicLoadBalancer
      Port: 80
      Protocol: TCP

  ###################################################
  # CloudMap Resources
  ###################################################
  PrivateDnsNamespace:
    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
    Properties:
      Name: !Sub '${ProjectName}.local'
      Vpc: !Ref VPC

  DJAppServiceRegistry:
    Type: AWS::ServiceDiscovery::Service
    Properties:
      Name: djapp
      NamespaceId: !GetAtt PrivateDnsNamespace.Id
      DnsConfig:
        DnsRecords:
          - Type: A
            TTL: 300
      HealthCheckCustomConfig:
        FailureThreshold: 1

  MetalServiceRegistry:
    Type: AWS::ServiceDiscovery::Service
    Properties:
      Name: metal
      NamespaceId: !GetAtt PrivateDnsNamespace.Id
      DnsConfig:
        DnsRecords:
          - Type: A
            TTL: 300
      HealthCheckCustomConfig:
        FailureThreshold: 1
  
  JazzServiceRegistry:
    Type: AWS::ServiceDiscovery::Service
    Properties:
      Name: jazz
      NamespaceId: !GetAtt PrivateDnsNamespace.Id
      DnsConfig:
        DnsRecords:
          - Type: A
            TTL: 300
      HealthCheckCustomConfig:
        FailureThreshold: 1

  ###################################################
  # Security Group and IAM Roles
  ###################################################
  AppSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub 'Security group for the tasks in ${ProjectName}'
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - CidrIp: !Ref VpcCIDR
          IpProtocol: -1

  TaskIAMRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchFullAccess
        - arn:aws:iam::aws:policy/AWSAppMeshEnvoyAccess

  TaskExecutionIAMRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

  ###################################################
  # Cloudwatch Resources
  ###################################################
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '${ProjectName}-log-group'
      RetentionInDays: 30

  ###################################################
  # ECS Cluster, Services & Tasks
  ###################################################
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Ref ProjectName

  DJAppGatewayService:
    Type: AWS::ECS::Service
    DependsOn:
      - PublicLoadBalancerListener
    Properties:
      Cluster: !Ref ECSCluster
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 3
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref AppSecurityGroup
          Subnets:
            - !Ref PrivateSubnet1
            - !Ref PrivateSubnet2
      TaskDefinition: !Ref DJAppGatewayTaskDef
      LoadBalancers:
        - ContainerName: envoy
          ContainerPort: !Ref GatewayPort
          TargetGroupArn: !Ref DJAppGatewayTargetGroup

  DJAppService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 3
      LaunchType: FARGATE
      ServiceRegistries:
        - RegistryArn: !GetAtt DJAppServiceRegistry.Arn
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref AppSecurityGroup
          Subnets:
            - !Ref PrivateSubnet1
            - !Ref PrivateSubnet2
      TaskDefinition: !Ref DJAppTaskDef
    
  MetalV1Service:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 2
      LaunchType: FARGATE
      ServiceRegistries:
        - RegistryArn: !GetAtt MetalServiceRegistry.Arn
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref AppSecurityGroup
          Subnets:
            - !Ref PrivateSubnet1
            - !Ref PrivateSubnet2
      TaskDefinition: !Ref MetalV1TaskDef
  
  MetalV2Service:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 2
      LaunchType: FARGATE
      ServiceRegistries:
        - RegistryArn: !GetAtt MetalServiceRegistry.Arn
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref AppSecurityGroup
          Subnets:
            - !Ref PrivateSubnet1
            - !Ref PrivateSubnet2
      TaskDefinition: !Ref MetalV2TaskDef

  JazzService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 2
      LaunchType: FARGATE
      ServiceRegistries:
        - RegistryArn: !GetAtt JazzServiceRegistry.Arn
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref AppSecurityGroup
          Subnets:
            - !Ref PrivateSubnet1
            - !Ref PrivateSubnet2
      TaskDefinition: !Ref JazzTaskDef

  DJAppGatewayTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - FARGATE
      NetworkMode: awsvpc
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref TaskIAMRole
      ExecutionRoleArn: !Ref TaskExecutionIAMRole
      ContainerDefinitions:
        - Name: otel-collector
          Image: public.ecr.aws/aws-observability/aws-otel-collector:latest
          Essential: true
          User: '1337'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: gateway
          PortMappings:
            - ContainerPort: 8125
              Protocol: udp
          Environment:
            - Name: AOT_CONFIG_CONTENT
              Value: !Sub |
                extensions:
                  health_check:
                receivers:
                  statsd:
                    endpoint: 0.0.0.0:8125
                    aggregation_interval: 60s 
                processors:
                  batch/metrics:
                    timeout: 60s
                  filter:
                    metrics:
                      include:
                        match_type: regexp
                        metric_names:
                          - ^envoy\.appmesh\..+$
                exporters:
                  awsemf:
                    namespace: ${ProjectName}
                    log_group_name: ${LogGroup}
                    dimension_rollup_option: NoDimensionRollup
                service:
                  pipelines:
                    metrics:
                      receivers: [statsd]
                      processors: [batch/metrics, filter]
                      exporters: [awsemf]
                  extensions:  [health_check]
        - Name: envoy
          Image: !Ref EnvoyImage
          Essential: true
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: 9901
              Protocol: tcp
            - ContainerPort: !Ref GatewayPort
              Protocol: tcp
          HealthCheck:
            Command:
              - CMD-SHELL
              - curl -s http://localhost:9901/ready
            Interval: 5
            Timeout: 10
            Retries: 10
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: gateway
          Environment:
            - Name: ENVOY_LOG_LEVEL
              Value: debug
            - Name: ENABLE_ENVOY_DOG_STATSD
              Value: 1
            - Name: APPMESH_METRIC_EXTENSION_VERSION
              Value: 1
            - Name: APPMESH_RESOURCE_ARN
              Value: !Ref DJAppGateway

  DJAppTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - FARGATE
      NetworkMode: awsvpc
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref TaskIAMRole
      ExecutionRoleArn: !Ref TaskExecutionIAMRole
      ProxyConfiguration:
        Type: APPMESH
        ContainerName: envoy
        ProxyConfigurationProperties:
          - Name: IgnoredUID
            Value: '1337'
          - Name: ProxyIngressPort
            Value: '15000'
          - Name: ProxyEgressPort
            Value: '15001'
          - Name: AppPorts
            Value: !Ref ContainerPort
          - Name: EgressIgnoredIPs
            Value: '169.254.170.2,169.254.169.254'
      ContainerDefinitions:
        - Name: otel-collector
          Image: public.ecr.aws/aws-observability/aws-otel-collector:latest
          Essential: true
          User: '1337'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: djapp
          PortMappings:
            - ContainerPort: 8125
              Protocol: udp
          Environment:
            - Name: AOT_CONFIG_CONTENT
              Value: !Sub |
                extensions:
                  health_check:
                receivers:
                  statsd:
                    endpoint: 0.0.0.0:8125
                    aggregation_interval: 60s 
                processors:
                  batch/metrics:
                    timeout: 60s
                  filter:
                    metrics:
                      include:
                        match_type: regexp
                        metric_names:
                          - ^envoy\.appmesh\..+$
                exporters:
                  awsemf:
                    namespace: ${ProjectName}
                    log_group_name: ${LogGroup}
                    dimension_rollup_option: NoDimensionRollup
                service:
                  pipelines:
                    metrics:
                      receivers: [statsd]
                      processors: [batch/metrics, filter]
                      exporters: [awsemf]
                  extensions:  [health_check]
        - Name: envoy
          Image: !Ref EnvoyImage
          Essential: true
          User: '1337'
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: 9901
              Protocol: tcp
            - ContainerPort: 15000
              Protocol: tcp
            - ContainerPort: 15001
              Protocol: tcp
          HealthCheck:
            Command:
              - CMD-SHELL
              - curl -s http://localhost:9901/ready
            Interval: 5
            Timeout: 10
            Retries: 10
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: djapp
          Environment:
            - Name: ENVOY_LOG_LEVEL
              Value: debug
            - Name: ENABLE_ENVOY_DOG_STATSD
              Value: 1
            - Name: APPMESH_METRIC_EXTENSION_VERSION 
              Value: 1
            - Name: APPMESH_RESOURCE_ARN
              Value: !Ref DJAppVirtualNode
        - Name: app
          Image: !Ref DJAppImage
          Essential: true
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: !Ref ContainerPort
              Protocol: tcp
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: djapp
          Environment:
            - Name: PORT
              Value: !Ref ContainerPort
            - Name: BACKENDS
              Value: !Sub >
                [
                  "${JazzVirtualService.VirtualServiceName}:${ContainerPort}",
                  "${MetalVirtualService.VirtualServiceName}:${ContainerPort}"
                ]

  JazzTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - FARGATE
      NetworkMode: awsvpc
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref TaskIAMRole
      ExecutionRoleArn: !Ref TaskExecutionIAMRole
      ProxyConfiguration:
        Type: APPMESH
        ContainerName: envoy
        ProxyConfigurationProperties:
          - Name: IgnoredUID
            Value: '1337'
          - Name: ProxyIngressPort
            Value: '15000'
          - Name: ProxyEgressPort
            Value: '15001'
          - Name: AppPorts
            Value: !Ref ContainerPort
          - Name: EgressIgnoredIPs
            Value: '169.254.170.2,169.254.169.254'
      ContainerDefinitions:
        - Name: otel-collector
          Image: public.ecr.aws/aws-observability/aws-otel-collector:latest
          Essential: true
          User: '1337'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: jazz
          PortMappings:
            - ContainerPort: 8125
              Protocol: udp
          Environment:
            - Name: AOT_CONFIG_CONTENT
              Value: !Sub |
                extensions:
                  health_check:
                receivers:
                  statsd:
                    endpoint: 0.0.0.0:8125
                    aggregation_interval: 60s 
                processors:
                  batch/metrics:
                    timeout: 60s
                  filter:
                    metrics:
                      include:
                        match_type: regexp
                        metric_names:
                          - ^envoy\.appmesh\..+$
                exporters:
                  awsemf:
                    namespace: ${ProjectName}
                    log_group_name: ${LogGroup}
                    dimension_rollup_option: NoDimensionRollup
                service:
                  pipelines:
                    metrics:
                      receivers: [statsd]
                      processors: [batch/metrics, filter]
                      exporters: [awsemf]
                  extensions:  [health_check]
        - Name: envoy
          Image: !Ref EnvoyImage
          Essential: true
          User: '1337'
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: 9901
              Protocol: tcp
            - ContainerPort: 15000
              Protocol: tcp
            - ContainerPort: 15001
              Protocol: tcp
          HealthCheck:
            Command:
              - CMD-SHELL
              - curl -s http://localhost:9901/ready
            Interval: 5
            Timeout: 10
            Retries: 10
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: jazz
          Environment:
            - Name: ENVOY_LOG_LEVEL
              Value: debug
            - Name: ENABLE_ENVOY_DOG_STATSD
              Value: 1
            - Name: APPMESH_METRIC_EXTENSION_VERSION 
              Value: 1
            - Name: APPMESH_RESOURCE_ARN
              Value: !Ref JazzVirtualNode
        - Name: app
          Image: !Ref DJAppImage
          Essential: true
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: !Ref ContainerPort
              Protocol: tcp
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: jazz
          Environment:
            - Name: PORT
              Value: !Ref ContainerPort
            - Name: RESPONSES
              Value: >
                [
                  "Astrud Gilberto",
                  "Miles Davis"
                ]

  MetalV1TaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - FARGATE
      Family: !Sub '${ProjectName}-metal-v1'
      NetworkMode: awsvpc
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref TaskIAMRole
      ExecutionRoleArn: !Ref TaskExecutionIAMRole
      ProxyConfiguration:
        Type: APPMESH
        ContainerName: envoy
        ProxyConfigurationProperties:
          - Name: IgnoredUID
            Value: '1337'
          - Name: ProxyIngressPort
            Value: '15000'
          - Name: ProxyEgressPort
            Value: '15001'
          - Name: AppPorts
            Value: !Ref ContainerPort
          - Name: EgressIgnoredIPs
            Value: '169.254.170.2,169.254.169.254'
      ContainerDefinitions:
        - Name: otel-collector
          Image: public.ecr.aws/aws-observability/aws-otel-collector:latest
          Essential: true
          User: '1337'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: metal-v1
          PortMappings:
            - ContainerPort: 8125
              Protocol: udp
          Environment:
            - Name: AOT_CONFIG_CONTENT
              Value: !Sub |
                extensions:
                  health_check:
                receivers:
                  statsd:
                    endpoint: 0.0.0.0:8125
                    aggregation_interval: 60s 
                processors:
                  batch/metrics:
                    timeout: 60s
                  filter:
                    metrics:
                      include:
                        match_type: regexp
                        metric_names:
                          - ^envoy\.appmesh\..+$
                exporters:
                  awsemf:
                    namespace: ${ProjectName}
                    log_group_name: ${LogGroup}
                    dimension_rollup_option: NoDimensionRollup
                service:
                  pipelines:
                    metrics:
                      receivers: [statsd]
                      processors: [batch/metrics, filter]
                      exporters: [awsemf]
                  extensions:  [health_check]
        - Name: envoy
          Image: !Ref EnvoyImage
          Essential: true
          User: '1337'
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: 9901
              Protocol: tcp
            - ContainerPort: 15000
              Protocol: tcp
            - ContainerPort: 15001
              Protocol: tcp
          HealthCheck:
            Command:
              - CMD-SHELL
              - curl -s http://localhost:9901/ready
            Interval: 5
            Timeout: 10
            Retries: 10
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: metal-v1
          Environment:
            - Name: ENVOY_LOG_LEVEL
              Value: debug
            - Name: ENABLE_ENVOY_DOG_STATSD
              Value: 1
            - Name: APPMESH_METRIC_EXTENSION_VERSION 
              Value: 1
            - Name: APPMESH_RESOURCE_ARN
              Value: !Ref MetalV1VirtualNode
        - Name: app
          Image: !Ref DJAppImage
          Essential: true
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: !Ref ContainerPort
              Protocol: tcp
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: metal-v1
          Environment:
            - Name: PORT
              Value: !Ref ContainerPort
            - Name: RESPONSES
              Value: >
                [
                  "Megadeth",
                  "Judas Priest"
                ]

  MetalV2TaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - FARGATE
      Family: !Sub '${ProjectName}-metal-v2'
      NetworkMode: awsvpc
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref TaskIAMRole
      ExecutionRoleArn: !Ref TaskExecutionIAMRole
      ProxyConfiguration:
        Type: APPMESH
        ContainerName: envoy
        ProxyConfigurationProperties:
          - Name: IgnoredUID
            Value: '1337'
          - Name: ProxyIngressPort
            Value: '15000'
          - Name: ProxyEgressPort
            Value: '15001'
          - Name: AppPorts
            Value: !Ref ContainerPort
          - Name: EgressIgnoredIPs
            Value: '169.254.170.2,169.254.169.254'
      ContainerDefinitions:
        - Name: otel-collector
          Image: public.ecr.aws/aws-observability/aws-otel-collector:latest
          Essential: true
          User: '1337'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: metal-v2
          PortMappings:
            - ContainerPort: 8125
              Protocol: udp
          Environment:
            - Name: AOT_CONFIG_CONTENT
              Value: !Sub |
                extensions:
                  health_check:
                receivers:
                  statsd:
                    endpoint: 0.0.0.0:8125
                    aggregation_interval: 60s 
                processors:
                  batch/metrics:
                    timeout: 60s
                  filter:
                    metrics:
                      include:
                        match_type: regexp
                        metric_names:
                          - ^envoy\.appmesh\..+$
                exporters:
                  awsemf:
                    namespace: ${ProjectName}
                    log_group_name: ${LogGroup}
                    dimension_rollup_option: NoDimensionRollup
                service:
                  pipelines:
                    metrics:
                      receivers: [statsd]
                      processors: [batch/metrics, filter]
                      exporters: [awsemf]
                  extensions:  [health_check]
        - Name: envoy
          Image: !Ref EnvoyImage
          Essential: true
          User: '1337'
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: 9901
              Protocol: tcp
            - ContainerPort: 15000
              Protocol: tcp
            - ContainerPort: 15001
              Protocol: tcp
          HealthCheck:
            Command:
              - CMD-SHELL
              - curl -s http://localhost:9901/ready
            Interval: 5
            Timeout: 10
            Retries: 10
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: metal-v2
          Environment:
            - Name: ENVOY_LOG_LEVEL
              Value: debug
            - Name: ENABLE_ENVOY_DOG_STATSD
              Value: 1
            - Name: APPMESH_METRIC_EXTENSION_VERSION
              Value: 1
            - Name: APPMESH_RESOURCE_ARN
              Value: !Ref MetalV2VirtualNode
        - Name: app
          Image: !Ref DJAppImage
          Essential: true
          Ulimits:
            - Name: nofile
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: !Ref ContainerPort
              Protocol: tcp
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: metal-v2
          Environment:
            - Name: PORT
              Value: !Ref ContainerPort
            - Name: RESPONSES
              Value: >
                [
                  "Megadeth (Los Angeles, California)",
                  "Judas Priest (West Bromwich, England)"
                ]

  ###################################################
  # App Mesh
  ###################################################
  Mesh:
    Type: AWS::AppMesh::Mesh
    Properties:
      MeshName: !Ref ProjectName

  DJAppGateway:
    Type: AWS::AppMesh::VirtualGateway
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualGatewayName: gateway
      Spec:
        Listeners:
          - PortMapping:
              Port: !Ref GatewayPort
              Protocol: http

  DJAppVirtualNode:
    Type: AWS::AppMesh::VirtualNode
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualNodeName: djapp
      Spec:
        Backends:
          - VirtualService:
              VirtualServiceName: !GetAtt JazzVirtualService.VirtualServiceName
          - VirtualService:
              VirtualServiceName: !GetAtt MetalVirtualService.VirtualServiceName 
        Listeners:
          - PortMapping:
              Port: !Ref ContainerPort
              Protocol: http
        ServiceDiscovery:
          AWSCloudMap:
            NamespaceName: !Sub '${ProjectName}.local'
            ServiceName: !GetAtt DJAppServiceRegistry.Name

  JazzVirtualNode:
    Type: AWS::AppMesh::VirtualNode
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualNodeName: jazz
      Spec:
        Listeners:
          - PortMapping:
              Port: !Ref ContainerPort
              Protocol: http
        ServiceDiscovery:
          AWSCloudMap:
            NamespaceName: !Sub '${ProjectName}.local'
            ServiceName: !GetAtt JazzServiceRegistry.Name

  MetalV1VirtualNode:
    Type: AWS::AppMesh::VirtualNode
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualNodeName: metal-v1
      Spec:
        Listeners:
          - PortMapping:
              Port: !Ref ContainerPort
              Protocol: http
        ServiceDiscovery:
          AWSCloudMap:
            NamespaceName: !Sub '${ProjectName}.local'
            ServiceName: !GetAtt MetalServiceRegistry.Name
            Attributes:
              - Key: ECS_TASK_DEFINITION_FAMILY
                Value: !Sub '${ProjectName}-metal-v1'
  
  MetalV2VirtualNode:
    Type: AWS::AppMesh::VirtualNode
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualNodeName: metal-v2
      Spec:
        Listeners:
          - PortMapping:
              Port: !Ref ContainerPort
              Protocol: http
        ServiceDiscovery:
          AWSCloudMap:
            NamespaceName: !Sub '${ProjectName}.local'
            ServiceName: !GetAtt MetalServiceRegistry.Name
            Attributes:
              - Key: ECS_TASK_DEFINITION_FAMILY
                Value: !Sub '${ProjectName}-metal-v2'

  DJAppVirtualService:
    Type: AWS::AppMesh::VirtualService
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualServiceName: !Sub '${DJAppServiceRegistry.Name}.${ProjectName}.local'
      Spec:
        Provider:
          VirtualNode:
            VirtualNodeName: !GetAtt DJAppVirtualNode.VirtualNodeName
  
  JazzVirtualService:
    Type: AWS::AppMesh::VirtualService
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualServiceName: !Sub '${JazzServiceRegistry.Name}.${ProjectName}.local'
      Spec:
        Provider:
          VirtualNode:
            VirtualNodeName: !GetAtt JazzVirtualNode.VirtualNodeName
  
  MetalVirtualService:
    Type: AWS::AppMesh::VirtualService
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualServiceName: !Sub '${MetalServiceRegistry.Name}.${ProjectName}.local'
      Spec:
        Provider:
          VirtualRouter:
            VirtualRouterName: !GetAtt MetalVirtualRouter.VirtualRouterName

  MetalVirtualRouter:
    Type: AWS::AppMesh::VirtualRouter
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualRouterName: metal
      Spec:
        Listeners:
          - PortMapping:
              Port: !Ref ContainerPort
              Protocol: http

  MetalRoute:
    Type: AWS::AppMesh::Route
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualRouterName: !GetAtt MetalVirtualRouter.VirtualRouterName
      RouteName: metal
      Spec:
        HttpRoute:
          Match:
            Prefix: /
          Action:
            WeightedTargets:
              - VirtualNode: !GetAtt MetalV1VirtualNode.VirtualNodeName
                Weight: 60
              - VirtualNode: !GetAtt MetalV2VirtualNode.VirtualNodeName
                Weight: 40

  DJAppGatewayRoute:
    Type: AWS::AppMesh::GatewayRoute
    Properties:
      MeshName: !GetAtt Mesh.MeshName
      VirtualGatewayName: !GetAtt DJAppGateway.VirtualGatewayName
      GatewayRouteName: djapp
      Spec:
        HttpRoute:
          Match:
            Prefix: /
          Action:
            Target:
              VirtualService:
                VirtualServiceName: !GetAtt DJAppVirtualService.VirtualServiceName

Outputs:
  PublicEndpoint:
    Value: !Sub 'http://${PublicLoadBalancer.DNSName}'
    Export:
      Name: !Sub '${ProjectName}:PublicEndpoint'
