Parameters:
  ProjectName:
    Type: String
    Description: Project name to link stacks

  AppMeshXdsEndpoint:
    Type: String
    Description: App Mesh XDS Endpoint Override
    Default: ""

  EnvoyImage:
    Type: String
    Description: Envoy container image

  FrontAppImage:
    Type: String
    Description: Front app container image

  ColorAppImage:
    Type: String
    Description: Color app container image

  ContainerPort:
    Type: Number
    Description: Port number to use for applications
    Default: 8080

Resources:

  TaskSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Security group for the tasks"
      VpcId:
        Fn::ImportValue: !Sub '${ProjectName}:VPC'
      SecurityGroupIngress:
        - CidrIp:
            Fn::ImportValue: !Sub '${ProjectName}:VpcCIDR'
          IpProtocol: -1

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

  TaskIamRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument: |
        {
            "Statement": [{
                "Effect": "Allow",
                "Principal": { "Service": [ "ecs-tasks.amazonaws.com" ]},
                "Action": [ "sts:AssumeRole" ]
            }]
        }
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchFullAccess
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
        - arn:aws:iam::aws:policy/AWSAppMeshEnvoyAccess

  TaskExecutionIamRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument: |
        {
            "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

  BackendRecordSet:
    Type: AWS::Route53::RecordSet
    DependsOn:
      - BackendV1LoadBalancer
    Properties:
      AliasTarget:
        HostedZoneId: !GetAtt 'BackendV1LoadBalancer.CanonicalHostedZoneID'
        DNSName: !GetAtt 'BackendV1LoadBalancer.DNSName'
      HostedZoneId:
        Fn::ImportValue:
          !Sub '${ProjectName}:DnsHostedZoneId'
      Name:
        Fn::Join:
          - '.'
          -
            - backend
            - Fn::ImportValue:
                !Sub '${ProjectName}:DnsHostedZoneName'
      Type: A

  # Backend V1 behind ALB
  BackendV1LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internal
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: '30'
      Subnets:
        - Fn::ImportValue:
            !Sub '${ProjectName}:PrivateSubnet1'
        - Fn::ImportValue:
            !Sub '${ProjectName}:PrivateSubnet2'
      SecurityGroups:
        - !Ref TaskSecurityGroup

  BackendV1TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 60
      HealthCheckPath: '/ping'
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      TargetType: ip
      Name: !Sub '${ProjectName}-backendtarget'
      Port: !Ref ContainerPort
      Protocol: HTTP
      UnhealthyThresholdCount: 2
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 120
      VpcId:
        Fn::ImportValue:
          !Sub "${ProjectName}:VPC"

  BackendV1LoadBalancerListener:
    DependsOn:
      - BackendV1LoadBalancer
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref BackendV1TargetGroup
          Type: 'forward'
      LoadBalancerArn: !Ref BackendV1LoadBalancer
      Port: !Ref ContainerPort
      Protocol: HTTP

  BackendV1LoadBalancerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref BackendV1TargetGroup
          Type: 'forward'
      Conditions:
        - Field: path-pattern
          Values:
            - '*'
      ListenerArn: !Ref BackendV1LoadBalancerListener
      Priority: 1

  BackendV1TaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - 'FARGATE'
      Family: 'blue'
      NetworkMode: 'awsvpc'
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref TaskIamRole
      ExecutionRoleArn: !Ref TaskExecutionIamRole
      ContainerDefinitions:
        - Name: 'app'
          Image: !Ref ColorAppImage
          Essential: true
          DependsOn:
            - ContainerName: 'xray'
              Condition: 'START'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub ${ProjectName}-log-group
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'backend-v1'
          PortMappings:
            - ContainerPort: !Ref ContainerPort
              HostPort: !Ref ContainerPort
              Protocol: 'tcp'
          Environment:
            - Name: COLOR
              Value: 'blue'
            - Name: PORT
              Value: !Sub '${ContainerPort}'
            - Name: XRAY_APP_NAME
              Value:
                Fn::Join:
                  - ''
                  -
                    - Fn::ImportValue:
                        !Sub '${ProjectName}:Mesh'
                    - '/'
                    - !GetAtt 'BackendV1VirtualNode.VirtualNodeName'
        - Name: 'xray'
          Image: "public.ecr.aws/xray/aws-xray-daemon"
          Essential: true
          User: '1337'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${ProjectName}-log-group'
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'front'
          PortMappings:
            - ContainerPort: 2000
              Protocol: 'udp'

  BackendV1Service:
    Type: AWS::ECS::Service
    DependsOn:
      - BackendV1LoadBalancerListener
    Properties:
      Cluster:
        Fn::ImportValue:
          !Sub "${ProjectName}:ECSCluster"
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 1
      LaunchType: 'FARGATE'
      LoadBalancers:
        - ContainerName: app
          ContainerPort: !Ref ContainerPort
          TargetGroupArn: !Ref BackendV1TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref TaskSecurityGroup
          Subnets:
            - Fn::ImportValue:
                !Sub '${ProjectName}:PrivateSubnet1'
            - Fn::ImportValue:
                !Sub '${ProjectName}:PrivateSubnet2'
      TaskDefinition: !Ref BackendV1TaskDef

  # Backend V2 with CloudMap service registry
  BackendV2ServiceRegistry:
    Type: AWS::ServiceDiscovery::Service
    Properties:
      Name: 'backend-v2'
      DnsConfig:
        NamespaceId:
          Fn::ImportValue:
            !Sub '${ProjectName}:DnsNamespaceId'
        DnsRecords:
          - Type: A
            TTL: 300
      HealthCheckCustomConfig:
        FailureThreshold: 1

  BackendV2TaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - 'FARGATE'
      Family: 'green'
      NetworkMode: 'awsvpc'
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref TaskIamRole
      ExecutionRoleArn: !Ref TaskExecutionIamRole
      ContainerDefinitions:
        - Name: 'app'
          Image: !Ref ColorAppImage
          Essential: true
          DependsOn:
            - ContainerName: 'xray'
              Condition: 'START'
            - ContainerName: 'envoy'
              Condition: 'HEALTHY'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub ${ProjectName}-log-group
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'backend-v2'
          PortMappings:
            - ContainerPort: !Ref ContainerPort
              HostPort: !Ref ContainerPort
              Protocol: 'tcp'
          Environment:
            - Name: COLOR
              Value: 'green'
            - Name: PORT
              Value: !Sub '${ContainerPort}'
            - Name: XRAY_APP_NAME
              Value:
                Fn::Join:
                  - ''
                  -
                    - Fn::ImportValue:
                        !Sub '${ProjectName}:Mesh'
                    - '/'
                    - !GetAtt 'BackendV2VirtualNode.VirtualNodeName'
        - Name: 'xray'
          Image: "public.ecr.aws/xray/aws-xray-daemon"
          Essential: true
          User: '1337'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${ProjectName}-log-group'
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'backend-v2'
          PortMappings:
            - ContainerPort: 2000
              Protocol: 'udp'
        - Name: envoy
          Image: !Ref EnvoyImage
          Essential: true
          User: '1337'
          DependsOn:
            - ContainerName: 'xray'
              Condition: 'START'
          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/server_info | grep state | grep -q LIVE'
            Interval: 5
            Timeout: 10
            Retries: 10
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${ProjectName}-log-group'
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'backend-v2'
          Environment:
            - Name: 'ENVOY_LOG_LEVEL'
              Value: 'debug'
            - Name: 'ENABLE_ENVOY_XRAY_TRACING'
              Value: '1'
            - Name: 'ENABLE_ENVOY_STATS_TAGS'
              Value: '1'
            - Name: 'APPMESH_RESOURCE_ARN'
              Value:
                Fn::Join:
                  - ''
                  -
                    - 'mesh/'
                    - Fn::ImportValue:
                        !Sub '${ProjectName}:Mesh'
                    - '/virtualNode/'
                    - !GetAtt 'BackendV2VirtualNode.VirtualNodeName'

  BackendV2Service:
    Type: AWS::ECS::Service
    DependsOn:
      - BackendV2ServiceRegistry
    Properties:
      Cluster:
        Fn::ImportValue:
          !Sub "${ProjectName}:ECSCluster"
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 1
      LaunchType: 'FARGATE'
      ServiceRegistries:
        - RegistryArn: !GetAtt 'BackendV2ServiceRegistry.Arn'
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref TaskSecurityGroup
          Subnets:
            - Fn::ImportValue:
               !Sub '${ProjectName}:PrivateSubnet1'
            - Fn::ImportValue:
                !Sub '${ProjectName}:PrivateSubnet2'
      TaskDefinition: !Ref BackendV2TaskDef

  # Backend exposed in mesh as virtual-service
  BackendV1VirtualNode:
    Type: AWS::AppMesh::VirtualNode
    Properties:
      MeshName:
        Fn::ImportValue:
          !Sub '${ProjectName}:Mesh'
      VirtualNodeName: !Sub '${ProjectName}-backend-v1-node'
      Spec:
        Listeners:
          - PortMapping:
              Port: !Ref ContainerPort
              Protocol: http
        ServiceDiscovery:
          DNS:
            Hostname: !GetAtt BackendV1LoadBalancer.DNSName

  BackendV2VirtualNode:
    Type: AWS::AppMesh::VirtualNode
    Properties:
      MeshName:
        Fn::ImportValue:
          !Sub '${ProjectName}:Mesh'
      VirtualNodeName: !Sub '${ProjectName}-backend-v2-node'
      Spec:
        Listeners:
          - PortMapping:
              Port: !Ref ContainerPort
              Protocol: http
        ServiceDiscovery:
          AWSCloudMap:
            NamespaceName:
              Fn::ImportValue:
                !Sub '${ProjectName}:DnsNamespaceName'
            ServiceName: !GetAtt BackendV2ServiceRegistry.Name
            Attributes:
              - Key: 'ECS_TASK_DEFINITION_FAMILY'
                Value: 'green'

  BackendVirtualService:
    Type: AWS::AppMesh::VirtualService
    DependsOn:
      - BackendVirtualRouter
    Properties:
      MeshName:
        Fn::ImportValue:
          !Sub '${ProjectName}:Mesh'
      VirtualServiceName: !Ref BackendRecordSet
      Spec:
        Provider:
          VirtualRouter:
            VirtualRouterName: !GetAtt BackendVirtualRouter.VirtualRouterName

  BackendVirtualRouter:
    Type: AWS::AppMesh::VirtualRouter
    Properties:
      MeshName:
        Fn::ImportValue:
          !Sub '${ProjectName}:Mesh'
      VirtualRouterName: !Sub '${ProjectName}-backend-router'
      Spec:
        Listeners:
          - PortMapping:
              Port: !Ref ContainerPort
              Protocol: http

  BackendRoute:
    Type: AWS::AppMesh::Route
    DependsOn:
      - BackendVirtualRouter
      - BackendV1VirtualNode
      - BackendV2VirtualNode
    Properties:
      MeshName:
        Fn::ImportValue:
          !Sub '${ProjectName}:Mesh'
      VirtualRouterName: !GetAtt BackendVirtualRouter.VirtualRouterName
      RouteName: !Sub '${ProjectName}-backend-route'
      Spec:
        HttpRoute:
          Match:
            Prefix: '/'
          Action:
            WeightedTargets:
              - VirtualNode: !GetAtt BackendV1VirtualNode.VirtualNodeName
                Weight: 50
              - VirtualNode: !GetAtt BackendV2VirtualNode.VirtualNodeName
                Weight: 50

  # Frontend
  SecurityGroupIngressFromPublicALB:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Ingress from the public ALB
      GroupId: !Ref TaskSecurityGroup
      IpProtocol: -1
      SourceSecurityGroupId: !Ref PublicLoadBalancerSecurityGroup

  PublicLoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: 'Access to the public facing load balancer'
      VpcId:
        Fn::ImportValue:
          !Sub "${ProjectName}:VPC"
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80

  PublicLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: '30'
      Subnets:
        - Fn::ImportValue:
            !Sub '${ProjectName}:PublicSubnet1'
        - Fn::ImportValue:
            !Sub '${ProjectName}:PublicSubnet2'
      SecurityGroups:
        - !Ref PublicLoadBalancerSecurityGroup

  WebTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 60
      HealthCheckPath: '/ping'
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      TargetType: ip
      Name: !Sub '${ProjectName}-webtarget'
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 2
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 120
      VpcId:
        Fn::ImportValue:
          !Sub "${ProjectName}:VPC"

  PublicLoadBalancerListener:
    DependsOn:
      - PublicLoadBalancer
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref WebTargetGroup
          Type: 'forward'
      LoadBalancerArn: !Ref PublicLoadBalancer
      Port: 80
      Protocol: HTTP

  WebLoadBalancerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref WebTargetGroup
          Type: 'forward'
      Conditions:
        - Field: path-pattern
          Values:
            - '*'
      ListenerArn: !Ref PublicLoadBalancerListener
      Priority: 1

  FrontVirtualNode:
    Type: AWS::AppMesh::VirtualNode
    DependsOn:
      - BackendVirtualService
    Properties:
      MeshName:
        Fn::ImportValue:
          !Sub '${ProjectName}:Mesh'
      VirtualNodeName: !Sub "${ProjectName}-front-node"
      Spec:
        Listeners:
          - PortMapping:
              Port: !Sub '${ContainerPort}'
              Protocol: http
        ServiceDiscovery:
          DNS:
            Hostname: !GetAtt PublicLoadBalancer.DNSName
        Backends:
          - VirtualService:
              VirtualServiceName: !GetAtt 'BackendVirtualService.VirtualServiceName'

  FrontTaskDef:
    Type: AWS::ECS::TaskDefinition
    DependsOn:
      - BackendVirtualService
      - FrontVirtualNode
    Properties:
      RequiresCompatibilities:
        - 'FARGATE'
      Family: 'front'
      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: !Sub '${ContainerPort}'
          - Name: 'EgressIgnoredIPs'
            Value: '169.254.170.2,169.254.169.254'
      ContainerDefinitions:
        - Name: 'app'
          Image: !Ref FrontAppImage
          Essential: true
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${ProjectName}-log-group'
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'front'
          PortMappings:
            - ContainerPort: !Ref ContainerPort
              Protocol: 'tcp'
          DependsOn:
            - ContainerName: 'envoy'
              Condition: 'HEALTHY'
            - ContainerName: 'xray'
              Condition: 'START'
          Environment:
            - Name: 'COLOR_HOST'
              Value: !Join ['', [!GetAtt 'BackendVirtualService.VirtualServiceName', ':', !Sub '${ContainerPort}']]
            - Name: PORT
              Value: !Sub '${ContainerPort}'
            - Name: XRAY_APP_NAME
              Value:
                Fn::Join:
                  - ''
                  -
                    - Fn::ImportValue:
                        !Sub '${ProjectName}:Mesh'
                    - '/'
                    - !GetAtt 'FrontVirtualNode.VirtualNodeName'
        - Name: 'xray'
          Image: "public.ecr.aws/xray/aws-xray-daemon"
          Essential: true
          User: '1337'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${ProjectName}-log-group'
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'front'
          PortMappings:
            - ContainerPort: 2000
              Protocol: 'udp'
        - Name: envoy
          Image: !Ref EnvoyImage
          Essential: true
          User: '1337'
          DependsOn:
            - ContainerName: 'xray'
              Condition: 'START'
          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/server_info | grep state | grep -q LIVE'
            Interval: 5
            Timeout: 10
            Retries: 10
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${ProjectName}-log-group'
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'front'
          Environment:
            - Name: 'ENVOY_LOG_LEVEL'
              Value: 'debug'
            - Name: 'ENABLE_ENVOY_XRAY_TRACING'
              Value: '1'
            - Name: 'ENABLE_ENVOY_STATS_TAGS'
              Value: '1'
            - Name: 'APPMESH_RESOURCE_ARN'
              Value:
                Fn::Join:
                  - ''
                  -
                    - 'mesh/'
                    - Fn::ImportValue:
                        !Sub '${ProjectName}:Mesh'
                    - '/virtualNode/'
                    - !GetAtt 'FrontVirtualNode.VirtualNodeName'

  FrontService:
    Type: AWS::ECS::Service
    DependsOn:
      - WebLoadBalancerRule
    Properties:
      Cluster:
        Fn::ImportValue:
          !Sub "${ProjectName}:ECSCluster"
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 1
      LaunchType: 'FARGATE'
      TaskDefinition: !Ref FrontTaskDef
      LoadBalancers:
        - ContainerName: app
          ContainerPort: !Ref ContainerPort
          TargetGroupArn: !Ref WebTargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref TaskSecurityGroup
          Subnets:
            - Fn::ImportValue:
                !Sub '${ProjectName}:PrivateSubnet1'
            - Fn::ImportValue:
                !Sub '${ProjectName}:PrivateSubnet2'

Outputs:
  FrontEndpoint:
    Description: 'Public endpoint for Front service'
    Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']]
    Export:
      Name: !Sub "${ProjectName}:FrontEndpoint"
