# 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 backend 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 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: "" LogRetention: Type: Number Default: 30 TargetContainer: Type: String TargetPort: Type: Number Conditions: IsGovCloud: !Equals [!Ref "AWS::Partition", "aws-us-gov"] HasAddons: !Not [!Equals [!Ref AddonsTemplateURL, ""]] HasEnvFile: !Not [!Equals [!Ref EnvFileARN, ""]] ExposePort: !Not [!Equals [!Ref TargetPort, -1]] Resources: 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: my-env.my-app.local - Name: COPILOT_ENVIRONMENT_NAME Value: !Sub "${EnvName}" - Name: COPILOT_SERVICE_NAME Value: !Sub "${WorkloadName}" EnvironmentFiles: - !If - HasEnvFile - Type: s3 Value: !Ref EnvFileARN - !Ref AWS::NoValue LogConfiguration: LogDriver: awslogs Options: awslogs-region: !Ref AWS::Region awslogs-group: !Ref LogGroup awslogs-stream-prefix: copilot PortMappings: - ContainerPort: 8080 Protocol: tcp Name: target 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 # Optional IAM permission required by ECS task def env file # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/taskdef-envfiles.html#taskdef-envfiles-iam # Example EnvFileARN: arn:aws:s3:::stackset-demo-infrastruc-pipelinebuiltartifactbuc-11dj7ctf52wyf/manual/1638391936/env - 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 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: "ExecuteCommand" PolicyDocument: Version: '2012-10-17' Statement: - Effect: "Allow" Action: [ "ssmmessages:CreateControlChannel", "ssmmessages:OpenControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenDataChannel", ] Resource: "*" - Effect: "Allow" Action: [ "logs:CreateLogStream", "logs:DescribeLogGroups", "logs:DescribeLogStreams", "logs:PutLogEvents", ] Resource: "*" 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" Service: DependsOn: - EnvControllerAction Metadata: "aws:copilot:description": "An ECS service to run and maintain your tasks in the environment cluster" Type: AWS::ECS::Service Properties: PlatformVersion: LATEST Cluster: Fn::ImportValue: !Sub "${AppName}-${EnvName}-ClusterId" TaskDefinition: !Ref TaskDefinition DesiredCount: !Ref TaskCount DeploymentConfiguration: DeploymentCircuitBreaker: Enable: true Rollback: true MinimumHealthyPercent: 100 MaximumPercent: 200 Alarms: !If - IsGovCloud - !Ref AWS::NoValue - Enable: false AlarmNames: [] Rollback: true PropagateTags: SERVICE EnableExecuteCommand: true LaunchType: FARGATE ServiceConnectConfiguration: Enabled: True Namespace: my-env.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: !Ref WorkloadName NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: ENABLED Subnets: Fn::Split: - "," - Fn::ImportValue: !Sub "${AppName}-${EnvName}-PublicSubnets" SecurityGroups: - Fn::ImportValue: !Sub "${AppName}-${EnvName}-EnvironmentSecurityGroup" ServiceRegistries: !If [ ExposePort, [ { RegistryArn: !GetAtt DiscoveryService.Arn, Port: !Ref TargetPort, }, ], !Ref "AWS::NoValue", ] AddonsStack: Metadata: "aws:copilot:description": "An Addons CloudFormation Stack for your additional AWS resources" Type: AWS::CloudFormation::Stack Condition: HasAddons Properties: Parameters: App: !Ref AppName Env: !Ref EnvName Name: !Ref WorkloadName TemplateURL: !Ref AddonsTemplateURL EnvControllerAction: Metadata: "aws:copilot:description": "Update your environment's shared resources" Type: Custom::EnvControllerFunction Properties: ServiceToken: !GetAtt EnvControllerFunction.Arn Workload: !Ref WorkloadName EnvStack: !Sub "${AppName}-${EnvName}" Parameters: [] 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 Outputs: DiscoveryServiceARN: Description: ARN of the Discovery Service. Value: !GetAtt DiscoveryService.Arn Export: Name: !Sub ${AWS::StackName}-DiscoveryServiceARN