# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation template that represents a load balanced web service on Amazon ECS.
Metadata:
  Version: v1.29.0
Parameters:
  AppName:
    Type: String
  EnvName:
    Type: String
  WorkloadName:
    Type: String
  ContainerImage:
    Type: String
  ContainerPort:
    Type: Number
  TaskCPU:
    Type: String
  TaskMemory:
    Type: String
  TaskCount:
    Type: Number
  DNSDelegated:
    Type: String
    AllowedValues: [true, false]
  LogRetention:
    Type: Number
  AddonsTemplateURL:
    Description: 'URL of the addons nested stack template within the S3 bucket.'
    Type: String
    Default: ""
  EnvFileARN:
    Description: 'URL of the environment file.'
    Type: String
    Default: ""
  LoggingEnvFileARN:
    Description: 'URL of the environment file for the logging sidecar.'
    Type: String
    Default: ""
  EnvFileARNFornginx:
    Description: 'URL of the environment file for the nginx sidecar.'
    Type: String
    Default: ""
  EnvFileARNForoperation:
    Description: 'URL of the environment file for the operation sidecar.'
    Type: String
    Default: ""
  TargetContainer:
    Type: String
  TargetPort:
    Type: Number
  HTTPSEnabled:
    Type: String
    AllowedValues: [true, false]
  RulePath:
    Type: String
Conditions:
  IsGovCloud: !Equals [!Ref "AWS::Partition", "aws-us-gov"]
  HasAssociatedDomain: !Equals [!Ref DNSDelegated, true]
  HasAddons: !Not [!Equals [!Ref AddonsTemplateURL, ""]]
  HasEnvFile: !Not [!Equals [!Ref EnvFileARN, ""]]
  HasLoggingEnvFile: !Not [!Equals [!Ref LoggingEnvFileARN, ""]]
  HasEnvFileFornginx: !Not [!Equals [!Ref EnvFileARNFornginx, ""]]
  HasEnvFileForoperation: !Not [!Equals [!Ref EnvFileARNForoperation, ""]]
Resources: # If a bucket URL is specified, that means the template exists.
  LogGroup:
    Metadata:
      'aws:copilot:description': 'A CloudWatch log group to hold your service logs'
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Join ['', [/copilot/, !Ref AppName, '-', !Ref EnvName, '-', !Ref WorkloadName]]
      RetentionInDays: !Ref LogRetention
  TaskDefinition:
    Metadata:
      'aws:copilot:description': 'An ECS task definition to group your containers and run them on ECS'
    Type: AWS::ECS::TaskDefinition
    DependsOn: LogGroup
    Properties:
      Family: !Join ['', [!Ref AppName, '-', !Ref EnvName, '-', !Ref WorkloadName]]
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: !Ref TaskCPU
      Memory: !Ref TaskMemory
      ExecutionRoleArn: !GetAtt ExecutionRole.Arn
      TaskRoleArn: !GetAtt TaskRole.Arn
      ContainerDefinitions:
        - Name: !Ref WorkloadName
          Image: !Ref ContainerImage
          Environment:
            - Name: COPILOT_APPLICATION_NAME
              Value: !Sub '${AppName}'
            - Name: COPILOT_SERVICE_DISCOVERY_ENDPOINT
              Value: prod.my-app.local
            - Name: COPILOT_ENVIRONMENT_NAME
              Value: !Sub '${EnvName}'
            - Name: COPILOT_SERVICE_NAME
              Value: !Sub '${WorkloadName}'
            - Name: COPILOT_SNS_TOPIC_ARNS
              Value: '{"givesdogs":"arn:aws:sns:us-west-2:123456789123:my-app-prod-fe-givesdogs","mytopic.fifo":"arn:aws:sns:us-west-2:123456789123:my-app-prod-fe-mytopic.fifo"}'
            - Name: COPILOT_LB_DNS
              Value: !GetAtt EnvControllerAction.PublicLoadBalancerDNSName
            - Name: DB_NAME
              Value:
                Fn::ImportValue: "MyDB"
            - Name: LOG_LEVEL
              Value: "info"
            - Name: COPILOT_MOUNT_POINTS
              Value: '{"persistence":"/etc/scratch"}'
          Secrets:
            - Name: DB
              ValueFrom: !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:demo/testing/mysql'
            - Name: GIT_USERNAME
              ValueFrom:
                Fn::ImportValue: "stack-SSMGHUserName"
            - Name: SQL_PASS
              ValueFrom: SQL_PASS         
          EnvironmentFiles:
            - !If
              - HasEnvFile
              - Type: s3
                Value: !Ref EnvFileARN
              - !Ref AWS::NoValue
          LogConfiguration:
            LogDriver: awsfirelens
            SecretOptions:
            - Name: DB
              ValueFrom: !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:demo/testing/mysql'
            - Name: GIT_USERNAME
              ValueFrom:
                Fn::ImportValue: "stack-SSMGHUserName"
            - Name: SQL_USERNAME
              ValueFrom: SQL_USERNAME
            Options:
              Name: "cloudwatch"
              log_group_name: "/copilot/sidecar-test-hello"
              log_stream_prefix: "copilot/"
              region: "us-west-2"
          MountPoints:
            - ContainerPath: '/etc/scratch'
              ReadOnly: true
              SourceVolume: persistence
          PortMappings:
            - ContainerPort: 4000
              Protocol: tcp
        - Name: firelens_log_router
          Image: public.ecr.aws/aws-observability/aws-for-fluent-bit:stable
          Environment:
            - Name: COPILOT_APPLICATION_NAME
              Value: !Sub '${AppName}'
            - Name: COPILOT_SERVICE_DISCOVERY_ENDPOINT
              Value: prod.my-app.local
            - Name: COPILOT_ENVIRONMENT_NAME
              Value: !Sub '${EnvName}'
            - Name: COPILOT_SERVICE_NAME
              Value: !Sub '${WorkloadName}'
            - Name: COPILOT_SNS_TOPIC_ARNS
              Value: '{"givesdogs":"arn:aws:sns:us-west-2:123456789123:my-app-prod-fe-givesdogs","mytopic.fifo":"arn:aws:sns:us-west-2:123456789123:my-app-prod-fe-mytopic.fifo"}'
            - Name: COPILOT_LB_DNS
              Value: !GetAtt EnvControllerAction.PublicLoadBalancerDNSName
            - Name: DB_NAME
              Value:
                Fn::ImportValue: "MyDB"
            - Name: TEST
              Value: "TEST"
          EnvironmentFiles:
            - !If
              - HasLoggingEnvFile
              - Type: "s3"
                Value: !Ref LoggingEnvFileARN
              - !Ref "AWS::NoValue"
          Secrets:
            - Name: GITHUB_TOKEN
              ValueFrom: GITHUB_TOKEN
            - Name: MONGO_DB
              ValueFrom:
                Fn::ImportValue: "stack-MongoUserName"
          FirelensConfiguration:
            Type: fluentbit
            Options:
              enable-ecs-log-metadata: true
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-region: !Ref AWS::Region
              awslogs-group: !Ref LogGroup
              awslogs-stream-prefix: copilot
        - Name: nginx
          Image: 1234567890.dkr.ecr.us-west-2.amazonaws.com/reverse-proxy:revision_1
          PortMappings:
            - ContainerPort: 8080
              Protocol: tcp
              Name: target
            - ContainerPort: 8081
              Protocol: tcp
          HealthCheck:
            Command: ["CMD-SHELL", "curl -f http://localhost:8080 || exit 1"]
            Interval: 10
            Retries: 2
            StartPeriod: 0
            Timeout: 5
          Secrets:
            - Name: DB
              ValueFrom: !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:demo/testing/mysql'
            - Name: GIT_USERNAME
              ValueFrom:
                Fn::ImportValue: "stack-SSMGHUserName"
            - Name: SQL_PASS
              ValueFrom: SQL_PASS
          Environment:
            - Name: COPILOT_APPLICATION_NAME
              Value: !Sub '${AppName}'
            - Name: COPILOT_SERVICE_DISCOVERY_ENDPOINT
              Value: prod.my-app.local
            - Name: COPILOT_ENVIRONMENT_NAME
              Value: !Sub '${EnvName}'
            - Name: COPILOT_SERVICE_NAME
              Value: !Sub '${WorkloadName}'
            - Name: COPILOT_SNS_TOPIC_ARNS
              Value: '{"givesdogs":"arn:aws:sns:us-west-2:123456789123:my-app-prod-fe-givesdogs","mytopic.fifo":"arn:aws:sns:us-west-2:123456789123:my-app-prod-fe-mytopic.fifo"}'
            - Name: COPILOT_LB_DNS
              Value: !GetAtt EnvControllerAction.PublicLoadBalancerDNSName
            - Name: DB_NAME
              Value:
                Fn::ImportValue: "MyDB"
            - Name: NGINX_PORT
              Value: "80"
          EnvironmentFiles:
            - !If
              - HasEnvFileFornginx
              - Type: "s3"
                Value: !Ref EnvFileARNFornginx
              - !Ref "AWS::NoValue"
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-region: !Ref AWS::Region
              awslogs-group: !Ref LogGroup
              awslogs-stream-prefix: copilot
        - Name: operation
          Image: alpine:latest
          Command:
            - echo
            - $COPILOT_APPLICATION_NAME
          Environment:
            - Name: COPILOT_APPLICATION_NAME
              Value: !Sub '${AppName}'
            - Name: COPILOT_SERVICE_DISCOVERY_ENDPOINT
              Value: prod.my-app.local
            - Name: COPILOT_ENVIRONMENT_NAME
              Value: !Sub '${EnvName}'
            - Name: COPILOT_SERVICE_NAME
              Value: !Sub '${WorkloadName}'
            - Name: COPILOT_SNS_TOPIC_ARNS
              Value: '{"givesdogs":"arn:aws:sns:us-west-2:123456789123:my-app-prod-fe-givesdogs","mytopic.fifo":"arn:aws:sns:us-west-2:123456789123:my-app-prod-fe-mytopic.fifo"}'
            - Name: COPILOT_LB_DNS
              Value: !GetAtt EnvControllerAction.PublicLoadBalancerDNSName
          EnvironmentFiles:
            - !If
              - HasEnvFileForoperation
              - Type: "s3"
                Value: !Ref EnvFileARNForoperation
              - !Ref "AWS::NoValue"
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-region: !Ref AWS::Region
              awslogs-group: !Ref LogGroup
              awslogs-stream-prefix: copilot
      Volumes:
        - Name: persistence
  ExecutionRole:
    Metadata:
      'aws:copilot:description': 'An IAM Role for the Fargate agent to make AWS API calls on your behalf'
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: !Join ['', [!Ref AppName, '-', !Ref EnvName, '-', !Ref WorkloadName, SecretsPolicy]]
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action:
                  - 'ssm:GetParameters'
                Resource:
                  - !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*'
                Condition:
                  StringEquals:
                    'ssm:ResourceTag/copilot-application': !Sub '${AppName}'
                    'ssm:ResourceTag/copilot-environment': !Sub '${EnvName}'
              - Effect: 'Allow'
                Action:
                  - 'secretsmanager:GetSecretValue'
                Resource:
                  - !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:*'
                Condition:
                  StringEquals:
                    'secretsmanager:ResourceTag/copilot-application': !Sub '${AppName}'
                    'secretsmanager:ResourceTag/copilot-environment': !Sub '${EnvName}'
              - Effect: 'Allow'
                Action:
                  - 'kms:Decrypt'
                Resource:
                  - !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/*'
        - !If
          - HasEnvFile
          - PolicyName: !Join ['', [!Ref AppName, '-', !Ref EnvName, '-', !Ref WorkloadName, GetEnvFilePolicy]]
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: 'Allow'
                  Action:
                    - 's3:GetObject'
                  Resource:
                    - !Ref EnvFileARN
                - Effect: 'Allow'
                  Action:
                    - 's3:GetBucketLocation'
                  Resource:
                    - !Join
                      - ''
                      - - 'arn:'
                        - !Ref AWS::Partition
                        - ':s3:::'
                        - !Select [0, !Split ['/', !Select [5, !Split [':', !Ref EnvFileARN]]]]
          - !Ref AWS::NoValue
        - !If
          - HasLoggingEnvFile
          - PolicyName: !Join ['', [!Ref AppName, '-', !Ref EnvName, '-', !Ref WorkloadName, GetLoggingEnvFilePolicy]]
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: 'Allow'
                  Action:
                    - 's3:GetObject'
                  Resource:
                    - !Ref LoggingEnvFileARN
                - Effect: 'Allow'
                  Action:
                    - 's3:GetBucketLocation'
                  Resource:
                    - !Join
                      - ''
                      - - 'arn:'
                        - !Ref AWS::Partition
                        - ':s3:::'
                        - !Select [0, !Split ['/', !Select [5, !Split [':', !Ref LoggingEnvFileARN]]]]
          - !Ref AWS::NoValue
        - !If
          - HasEnvFileFornginx
          - PolicyName: !Join ['', [!Ref AppName, '-', !Ref EnvName, '-', !Ref WorkloadName, GetEnvFilePolicyFornginx]]
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: 'Allow'
                  Action:
                    - 's3:GetObject'
                  Resource:
                    - !Ref EnvFileARNFornginx
                - Effect: 'Allow'
                  Action:
                    - 's3:GetBucketLocation'
                  Resource:
                    - !Join
                      - ''
                      - - 'arn:'
                        - !Ref AWS::Partition
                        - ':s3:::'
                        - !Select [0, !Split ['/', !Select [5, !Split [':', !Ref EnvFileARNFornginx]]]]
          - !Ref AWS::NoValue
        - !If
          - HasEnvFileForoperation
          - PolicyName: !Join ['', [!Ref AppName, '-', !Ref EnvName, '-', !Ref WorkloadName, GetEnvFilePolicyForoperation]]
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: 'Allow'
                  Action:
                    - 's3:GetObject'
                  Resource:
                    - !Ref EnvFileARNForoperation
                - Effect: 'Allow'
                  Action:
                    - 's3:GetBucketLocation'
                  Resource:
                    - !Join
                      - ''
                      - - 'arn:'
                        - !Ref AWS::Partition
                        - ':s3:::'
                        - !Select [0, !Split ['/', !Select [5, !Split [':', !Ref EnvFileARNForoperation]]]]
          - !Ref AWS::NoValue
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
  TaskRole:
    Metadata:
      'aws:copilot:description': 'An IAM role to control permissions for the containers in your tasks'
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: 'DenyIAMExceptTaggedRoles'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Deny'
                Action: 'iam:*'
                Resource: '*'
              - Effect: 'Allow'
                Action: 'sts:AssumeRole'
                Resource:
                  - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/*'
                Condition:
                  StringEquals:
                    'iam:ResourceTag/copilot-application': !Sub '${AppName}'
                    'iam:ResourceTag/copilot-environment': !Sub '${EnvName}'
        - PolicyName: 'Publish2SNS'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action: 'sns:Publish'
                Resource:
                  - !Ref givesdogsSNSTopic
                  - !Ref mytopicfifoSNSTopic
  DiscoveryService:
    Metadata:
      'aws:copilot:description': 'Service discovery for your services to communicate within the VPC'
    Type: AWS::ServiceDiscovery::Service
    Properties:
      Description: Discovery Service for the Copilot services
      DnsConfig:
        RoutingPolicy: MULTIVALUE
        DnsRecords:
          - TTL: 10
            Type: A
          - TTL: 10
            Type: SRV
      HealthCheckCustomConfig:
        FailureThreshold: 1
      Name: !Ref WorkloadName
      NamespaceId:
        Fn::ImportValue: !Sub '${AppName}-${EnvName}-ServiceDiscoveryNamespaceID'
  DynamicDesiredCountAction:
    Metadata:
      'aws:copilot:description': "A custom resource returning the ECS service's running task count"
    Type: Custom::DynamicDesiredCountFunction
    Properties:
      ServiceToken: !GetAtt DynamicDesiredCountFunction.Arn
      Cluster:
        Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId'
      App: !Ref AppName
      Env: !Ref EnvName
      Svc: !Ref WorkloadName
      DefaultDesiredCount: !Ref TaskCount
      # We need to force trigger this lambda function on all deployments, so we give it a random ID as input on all event types.
      UpdateID: RandomGUID
  DynamicDesiredCountFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: "index.handler"
      Timeout: 600
      MemorySize: 512
      Role: !GetAtt 'DynamicDesiredCountFunctionRole.Arn'
      Runtime: nodejs16.x
  DynamicDesiredCountFunctionRole:
    Metadata:
      'aws:copilot:description': "An IAM Role for describing number of running tasks in your ECS service"
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: "DelegateDesiredCountAccess"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: ECS
                Effect: Allow
                Action:
                  - ecs:DescribeServices
                Resource: "*"
                Condition:
                  ArnEquals:
                    'ecs:cluster':
                      Fn::Sub:
                        - arn:${AWS::Partition}:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName}
                        - ClusterName:
                            Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId'
              - Sid: ResourceGroups
                Effect: Allow
                Action:
                  - resource-groups:GetResources
                Resource: "*"
              - Sid: Tags
                Effect: Allow
                Action:
                  - "tag:GetResources"
                Resource: "*"
  AutoScalingRole:
    Metadata:
      'aws:copilot:description': 'An IAM role for container auto scaling'
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'
  AutoScalingTarget:
    Metadata:
      'aws:copilot:description': "An autoscaling target to scale your service's desired count"
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MinCapacity: 3
      MaxCapacity: 12
      ResourceId:
        Fn::Join:
          - '/'
          - - 'service'
            - Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId'
            - !GetAtt Service.Name
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs
      RoleARN: !GetAtt AutoScalingRole.Arn
  AutoScalingPolicyECSServiceAverageCPUUtilization:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Join ['-', [!Ref WorkloadName, ECSServiceAverageCPUUtilization, ScalingPolicy]]
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref AutoScalingTarget
      TargetTrackingScalingPolicyConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ECSServiceAverageCPUUtilization
        ScaleInCooldown: 30
        ScaleOutCooldown: 150
        TargetValue: 70
  EnvControllerAction:
    Metadata:
      'aws:copilot:description': "Update your environment's shared resources"
    Type: Custom::EnvControllerFunction
    Properties:
      ServiceToken: !GetAtt EnvControllerFunction.Arn
      Workload: !Ref WorkloadName
      Aliases: ["example.com"]
      EnvStack: !Sub '${AppName}-${EnvName}'
      Parameters: [ALBWorkloads, Aliases]
      EnvVersion: v1.42.0
  EnvControllerFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: "index.handler"
      Timeout: 900
      MemorySize: 512
      Role: !GetAtt 'EnvControllerRole.Arn'
      Runtime: nodejs16.x
  EnvControllerRole:
    Metadata:
      'aws:copilot:description': "An IAM role to update your environment stack"
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: "EnvControllerStackUpdate"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:DescribeStacks
                  - cloudformation:UpdateStack
                Resource: !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AppName}-${EnvName}/*'
                Condition:
                  StringEquals:
                    'cloudformation:ResourceTag/copilot-application': !Sub '${AppName}'
                    'cloudformation:ResourceTag/copilot-environment': !Sub '${EnvName}'
        - PolicyName: "EnvControllerRolePass"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - iam:PassRole
                Resource: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${AppName}-${EnvName}-CFNExecutionRole'
                Condition:
                  StringEquals:
                    'iam:ResourceTag/copilot-application': !Sub '${AppName}'
                    'iam:ResourceTag/copilot-environment': !Sub '${EnvName}'
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  Service:
    Metadata:
      'aws:copilot:description': 'An ECS service to run and maintain your tasks in the environment cluster'
    Type: AWS::ECS::Service
    DependsOn:
      - HTTPListenerRuleWithDomain
      - HTTPSListenerRule
      - HTTPListenerRuleWithDomain1
      - HTTPSListenerRule1
      - HTTPListenerRuleWithDomain2
      - HTTPSListenerRule2
    Properties:
      PlatformVersion: LATEST
      Cluster:
        Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId'
      TaskDefinition: !Ref TaskDefinition
      DesiredCount: !GetAtt DynamicDesiredCountAction.DesiredCount
      DeploymentConfiguration:
        DeploymentCircuitBreaker:
          Enable: true
          Rollback: true
        MinimumHealthyPercent: 100
        MaximumPercent: 200
        Alarms:
          Enable: true
          Rollback: true
          AlarmNames:
            - my-app-prod-fe-CopilotRollbackCPUAlarm
      PropagateTags: SERVICE
      CapacityProviderStrategy:
        - CapacityProvider: FARGATE_SPOT
          Weight: 1
        - CapacityProvider: FARGATE
          Weight: 0
          Base: 5
      ServiceConnectConfiguration:
        Enabled: True
        Namespace: prod.my-app.local
        LogConfiguration:
          LogDriver: awslogs
          Options:
            awslogs-region: !Ref AWS::Region
            awslogs-group: !Ref LogGroup
            awslogs-stream-prefix: copilot
        Services:
          - PortName: target
            # Avoid using the same service with Service Discovery in a namespace.
            DiscoveryName: !Join ["-", [!Ref WorkloadName, "sc"]]
            ClientAliases:
              - Port: !Ref TargetPort
                DnsName: api
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          Subnets:
            Fn::Split:
              - ','
              - Fn::ImportValue: !Sub '${AppName}-${EnvName}-PublicSubnets'
          SecurityGroups:
            - Fn::ImportValue: !Sub '${AppName}-${EnvName}-EnvironmentSecurityGroup'
      # This may need to be adjusted if the container takes a while to start up
      HealthCheckGracePeriodSeconds: 60
      LoadBalancers:
        - ContainerName: nginx
          ContainerPort: 8080
          TargetGroupArn: !Ref TargetGroup
        - ContainerName: nginx
          ContainerPort: 8081
          TargetGroupArn: !Ref TargetGroup1
        - ContainerName: fe
          ContainerPort: 4000
          TargetGroupArn: !Ref TargetGroup2
      ServiceRegistries:
        - RegistryArn: !GetAtt DiscoveryService.Arn
          Port: !Ref TargetPort
  TargetGroup:
    Metadata:
      'aws:copilot:description': 'A target group to connect the load balancer to your service on port 8080'
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckPath: / # Default is '/'.
      Port: 8080
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60 # ECS Default is 300; Copilot default is 60.
        - Key: stickiness.enabled
          Value: false
      TargetType: ip
      VpcId:
        Fn::ImportValue: !Sub "${AppName}-${EnvName}-VpcId"
  TargetGroup1:
    Metadata:
      'aws:copilot:description': 'A target group to connect the load balancer to your service on port 8081'
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckPath: / # Default is '/'.
      Port: 8081
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60 # ECS Default is 300; Copilot default is 60.
        - Key: stickiness.enabled
          Value: false
      TargetType: ip
      VpcId:
        Fn::ImportValue: !Sub "${AppName}-${EnvName}-VpcId"
  TargetGroup2:
    Metadata:
      'aws:copilot:description': 'A target group to connect the load balancer to your service on port 4000'
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckPath: / # Default is '/'.
      Port: 4000
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60 # ECS Default is 300; Copilot default is 60.
        - Key: stickiness.enabled
          Value: false
      TargetType: ip
      VpcId:
        Fn::ImportValue: !Sub "${AppName}-${EnvName}-VpcId"
  RulePriorityFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: "index.nextAvailableRulePriorityHandler"
      Timeout: 600
      MemorySize: 512
      Role: !GetAtt 'RulePriorityFunctionRole.Arn'
      Runtime: nodejs16.x
  RulePriorityFunctionRole:
    Type: AWS::IAM::Role
    Metadata:
      'aws:copilot:description': "An IAM Role to describe load balancer rules for assigning a priority"
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: "RulePriorityGeneratorAccess"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - elasticloadbalancing:DescribeRules
                Resource: "*"
  HTTPSRulePriorityAction:
    Metadata:
      'aws:copilot:description': 'A custom resource assigning priority for HTTPS listener rules'
    Type: Custom::RulePriorityFunction
    Properties:
      ServiceToken: !GetAtt RulePriorityFunction.Arn
      RulePath: ["/", "/admin","/superadmin"]
      ListenerArn: !GetAtt EnvControllerAction.HTTPSListenerArn
  
  HTTPRuleWithDomainPriorityAction:
    Metadata:
      'aws:copilot:description': 'A custom resource assigning priority for HTTP listener rules'
    Type: Custom::RulePriorityFunction
    Properties:
      ServiceToken: !GetAtt RulePriorityFunction.Arn
      RulePath: ["/", "/admin", "/superadmin"]
      ListenerArn: !GetAtt EnvControllerAction.HTTPListenerArn
  
  HTTPListenerRuleWithDomain:
    Metadata:
      'aws:copilot:description': 'An HTTP listener rule for path `/` that redirects HTTP to HTTPS'
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: redirect
          RedirectConfig:
            Protocol: HTTPS
            Port: 443
            Host: "#{host}"
            Path: "/#{path}"
            Query: "#{query}"
            StatusCode: HTTP_301
      Conditions:
        - Field: 'host-header'
          HostHeaderConfig:
            Values: ["example.com"]
        - Field: 'path-pattern'
          PathPatternConfig:
            Values:
              - "/*"
      ListenerArn: !GetAtt EnvControllerAction.HTTPListenerArn
      Priority: !GetAtt HTTPRuleWithDomainPriorityAction.Priority
  HTTPListenerRuleWithDomain1:
    Metadata:
      'aws:copilot:description': 'An HTTP listener rule for path `/admin` that redirects HTTP to HTTPS'
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: redirect
          RedirectConfig:
            Protocol: HTTPS
            Port: 443
            Host: "#{host}"
            Path: "/#{path}"
            Query: "#{query}"
            StatusCode: HTTP_301
      Conditions:
        - Field: 'host-header'
          HostHeaderConfig:
            Values: [ "example.com" ]
        - Field: 'path-pattern'
          PathPatternConfig:
            Values:
              - "/admin"
              - "/admin/*"
      ListenerArn: !GetAtt EnvControllerAction.HTTPListenerArn
      Priority: !GetAtt HTTPRuleWithDomainPriorityAction.Priority1
  HTTPListenerRuleWithDomain2:
    Metadata:
      'aws:copilot:description': 'An HTTP listener rule for path `/superadmin` that redirects HTTP to HTTPS'
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: redirect
          RedirectConfig:
            Protocol: HTTPS
            Port: 443
            Host: "#{host}"
            Path: "/#{path}"
            Query: "#{query}"
            StatusCode: HTTP_301
      Conditions:
        - Field: 'host-header'
          HostHeaderConfig:
            Values: [ "example.com" ]
        - Field: 'path-pattern'
          PathPatternConfig:
            Values:
              - "/superadmin"
              - "/superadmin/*"
      ListenerArn: !GetAtt EnvControllerAction.HTTPListenerArn
      Priority: !GetAtt HTTPRuleWithDomainPriorityAction.Priority2
  HTTPSListenerRule:
    Metadata:
      'aws:copilot:description': 'An HTTPS listener rule for path `/` that forwards HTTPS traffic to your tasks'
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      Conditions:
        - Field: 'host-header'
          HostHeaderConfig:
            Values: ["example.com"]
        - Field: 'path-pattern'
          PathPatternConfig:
            Values:
              - "/*"
      ListenerArn: !GetAtt EnvControllerAction.HTTPSListenerArn
      Priority: !GetAtt HTTPSRulePriorityAction.Priority
  HTTPSListenerRule1:
    Metadata:
      'aws:copilot:description': 'An HTTPS listener rule for path `/admin` that forwards HTTPS traffic to your tasks'
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup1
          Type: forward
      Conditions:
        - Field: 'host-header'
          HostHeaderConfig:
            Values: [ "example.com" ]
        - Field: 'path-pattern'
          PathPatternConfig:
            Values:
              - "/admin"
              - "/admin/*"
      ListenerArn: !GetAtt EnvControllerAction.HTTPSListenerArn
      Priority: !GetAtt HTTPSRulePriorityAction.Priority1
  HTTPSListenerRule2:
    Metadata:
      'aws:copilot:description': 'An HTTPS listener rule for path `/superadmin` that forwards HTTPS traffic to your tasks'
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup2
          Type: forward
      Conditions:
        - Field: 'host-header'
          HostHeaderConfig:
            Values: [ "example.com" ]
        - Field: 'path-pattern'
          PathPatternConfig:
            Values:
              - "/superadmin"
              - "/superadmin/*"
      ListenerArn: !GetAtt EnvControllerAction.HTTPSListenerArn
      Priority: !GetAtt HTTPSRulePriorityAction.Priority2
  AddonsStack:
    Metadata:
      'aws:copilot:description': 'An Addons CloudFormation Stack for your additional AWS resources'
    Type: AWS::CloudFormation::Stack # Needed for #1848
    DependsOn: EnvControllerAction
    Condition: HasAddons
    Properties:
      Parameters:
        App: !Ref AppName
        Env: !Ref EnvName
        Name: !Ref WorkloadName
      TemplateURL: !Ref AddonsTemplateURL
  CPURollbackAlarm:
    Metadata:
      'aws:copilot:description': "A CloudWatch alarm associated with CPU utilization for deployment rollbacks"
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Roll back ECS service if CPU utilization is greater than or equal to 70% twice in 3 minutes."
      AlarmName: my-app-prod-fe-CopilotRollbackCPUAlarm
      Namespace: 'AWS/ECS'
      Dimensions:
        - Name: ClusterName
          Value:
            Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId'
        - Name: ServiceName
          Value: !Select [ 2, !Split [ "/", !Ref Service ] ]
      MetricName: 'CPUUtilization'
      ComparisonOperator: 'GreaterThanOrEqualToThreshold'
      DatapointsToAlarm: 2
      EvaluationPeriods: 3
      Period: 60
      Statistic: 'Average'
      Threshold: 70
      Unit: 'Percent'
  givesdogsSNSTopic:
    Metadata:
      'aws:copilot:description': 'A SNS topic to broadcast givesdogs events'
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub '${AWS::StackName}-givesdogs'
      KmsMasterKeyId: 'alias/aws/sns'
  givesdogsSNSTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    DependsOn: givesdogsSNSTopic
    Properties:
      Topics:
        - !Ref givesdogsSNSTopic
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:root'
            Action:
              - sns:Subscribe
            Resource: !Ref givesdogsSNSTopic
            Condition:
              StringEquals:
                "sns:Protocol": "sqs"
  mytopicfifoSNSTopic:
    Metadata:
      'aws:copilot:description': 'A SNS FIFO topic to broadcast mytopic.fifo events'
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub '${AWS::StackName}-mytopic.fifo'
      FifoTopic: true
      KmsMasterKeyId: 'alias/aws/sns'
  mytopicfifoSNSTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    DependsOn: mytopicfifoSNSTopic
    Properties:
      Topics:
        - !Ref mytopicfifoSNSTopic
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:root'
            Action:
              - sns:Subscribe
            Resource: !Ref mytopicfifoSNSTopic
            Condition:
              StringEquals:
                "sns:Protocol": "sqs"
Outputs:
  DiscoveryServiceARN:
    Description: ARN of the Discovery Service.
    Value: !GetAtt DiscoveryService.Arn
    Export:
      Name: !Sub ${AWS::StackName}-DiscoveryServiceARN