# 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