--- AWSTemplateFormatVersion: '2010-09-09' Description: This stack deploys the core network infrastructure and IAM resources to be used for a service hosted in Amazon ECS using AWS Fargate. Parameters: DDBTableName: Type: String Default: mm-ddbtable Description: Name of your DynamoDB Table. IsSecondary: Type: String Default: false AllowedValues: - true - false Description: Choose Yes if this stack is being deployed in the disaster recovery (DR) region Mappings: # Hard values for the subnet masks. These masks define # the range of internal IP addresses that can be assigned.. # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255 # There are four subnets which cover the ranges: # # 10.0.0.0 - 10.0.0.255 # 10.0.1.0 - 10.0.1.255 # 10.0.2.0 - 10.0.2.255 # 10.0.3.0 - 10.0.3.255 # # If you need more IP addresses (perhaps you have so many # instances that you run out) then you can customize these # ranges to add more SubnetConfig: VPC: CIDR: '10.0.0.0/16' PublicOne: CIDR: '10.0.0.0/24' PublicTwo: CIDR: '10.0.1.0/24' PrivateOne: CIDR: '10.0.2.0/24' PrivateTwo: CIDR: '10.0.3.0/24' Conditions: PrimaryRegion: !Equals [ !Ref IsSecondary, false ] SecondaryRegion: !Equals [ !Ref IsSecondary, true ] Resources: # VPC in which containers will be networked. # It has two public subnets, and two private subnets. # We distribute the subnets across the first two available subnets # for the region, for high availability. VPC: Type: AWS::EC2::VPC Properties: EnableDnsSupport: true EnableDnsHostnames: true CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] Tags: - Key: Name Value: !Sub Mysfits-VPC-${AWS::StackName} # Two public subnets, where a public load balancer will later be created. PublicSubnetOne: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: {Ref: 'AWS::Region'} VpcId: !Ref 'VPC' CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub MysfitsPublicOne-${AWS::StackName} PublicSubnetTwo: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: {Ref: 'AWS::Region'} VpcId: !Ref 'VPC' CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub MysfitsPublicTwo-${AWS::StackName} # Two private subnets where containers will only have private # IP addresses, and will only be reachable by other members of the # VPC PrivateSubnetOne: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: {Ref: 'AWS::Region'} VpcId: !Ref 'VPC' CidrBlock: !FindInMap ['SubnetConfig', 'PrivateOne', 'CIDR'] Tags: - Key: Name Value: !Sub MysfitsPrivateOne-${AWS::StackName} PrivateSubnetTwo: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: {Ref: 'AWS::Region'} VpcId: !Ref 'VPC' CidrBlock: !FindInMap ['SubnetConfig', 'PrivateTwo', 'CIDR'] Tags: - Key: Name Value: !Sub MysfitsPrivateTwo-${AWS::StackName} # Setup networking resources for the public subnets. InternetGateway: Type: AWS::EC2::InternetGateway GatewayAttachement: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref 'VPC' InternetGatewayId: !Ref 'InternetGateway' PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref 'VPC' PublicRoute: Type: AWS::EC2::Route DependsOn: GatewayAttachement Properties: RouteTableId: !Ref 'PublicRouteTable' DestinationCidrBlock: '0.0.0.0/0' GatewayId: !Ref 'InternetGateway' PublicSubnetOneRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnetOne RouteTableId: !Ref PublicRouteTable PublicSubnetTwoRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnetTwo RouteTableId: !Ref PublicRouteTable # Setup networking resources for the private subnets. Containers # in these subnets have only private IP addresses, and must use a NAT # gateway to talk to the internet. We launch two NAT gateways, one for # each private subnet. NatGatewayOneAttachment: Type: AWS::EC2::EIP DependsOn: GatewayAttachement Properties: Domain: vpc NatGatewayTwoAttachment: Type: AWS::EC2::EIP DependsOn: GatewayAttachement Properties: Domain: vpc NatGatewayOne: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGatewayOneAttachment.AllocationId SubnetId: !Ref PublicSubnetOne NatGatewayTwo: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGatewayTwoAttachment.AllocationId SubnetId: !Ref PublicSubnetTwo PrivateRouteTableOne: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref 'VPC' PrivateRouteOne: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTableOne DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGatewayOne PrivateRouteTableOneAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTableOne SubnetId: !Ref PrivateSubnetOne PrivateRouteTableTwo: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref 'VPC' PrivateRouteTwo: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTableTwo DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGatewayTwo PrivateRouteTableTwoAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTableTwo SubnetId: !Ref PrivateSubnetTwo # VPC Endpoint for DynamoDB # If a container needs to access DynamoDB (coming in module 3) this # allows a container in the private subnet to talk to DynamoDB directly # without needing to go via the NAT gateway. DynamoDBEndpoint: Type: AWS::EC2::VPCEndpoint Properties: PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: "*" Principal: "*" Resource: "*" RouteTableIds: - !Ref 'PrivateRouteTableOne' - !Ref 'PrivateRouteTableTwo' ServiceName: !Join [ "", [ "com.amazonaws.", { "Ref": "AWS::Region" }, ".dynamodb" ] ] VpcId: !Ref 'VPC' # Service linked role for ECS; TEST ME AND/OR DELETEME IF NOT NEEDED # ECSSLR: # Type: AWS::IAM::ServiceLinkedRole # Properties: # AWSServiceName: ecs.amazonaws.com # The security group for our service containers to be hosted in Fargate. # Even though traffic from users will pass through a Network Load Balancer, # that traffic is purely TCP passthrough, without security group inspection. # Therefore, we will allow for traffic from the Internet to be accepted by our # containers. But, because the containers will only have Private IP addresses, # the only traffic that will reach the containers is traffic that is routed # to them by the public load balancer on the specific ports that we configure. FargateContainerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Access to the fargate containers from the Internet VpcId: !Ref 'VPC' SecurityGroupIngress: # Allow access to NLB from anywhere on the internet - CidrIp: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] IpProtocol: -1 # This is an IAM role which authorizes ECS to manage resources on your # account on your behalf, such as updating your load balancer with the # details of where your containers are, so that traffic can reach your # containers. EcsServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - ecs.amazonaws.com - ecs-tasks.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: ecs-service PolicyDocument: Statement: - Effect: Allow Action: # Rules which allow ECS to attach network interfaces to instances # on your behalf in order for awsvpc networking mode to work right - 'ec2:AttachNetworkInterface' - 'ec2:CreateNetworkInterface' - 'ec2:CreateNetworkInterfacePermission' - 'ec2:DeleteNetworkInterface' - 'ec2:DeleteNetworkInterfacePermission' - 'ec2:Describe*' - 'ec2:DetachNetworkInterface' # Rules which allow ECS to update load balancers on your behalf # with the information sabout how to send traffic to your containers - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' - 'elasticloadbalancing:DeregisterTargets' - 'elasticloadbalancing:Describe*' - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' - 'elasticloadbalancing:RegisterTargets' # Rules which allow ECS to run tasks that have IAM roles assigned to them. - 'iam:PassRole' # Rules that let ECS interact with container images. - 'ecr:GetAuthorizationToken' - 'ecr:BatchCheckLayerAvailability' - 'ecr:GetDownloadUrlForLayer' - 'ecr:BatchGetImage' # Rules that let ECS create and push logs to CloudWatch. - 'logs:DescribeLogStreams' - 'logs:CreateLogStream' - 'logs:CreateLogGroup' - 'logs:PutLogEvents' Resource: '*' # This is a role which is used by the ECS tasks. Tasks in Amazon ECS define # the containers that should be deployed togehter and the resources they # require from a compute/memory perspective. So, the policies below will define # the IAM permissions that our Mythical Mysfits docker containers will have. # If you attempted to write any code for the Mythical Mysfits service that # interacted with different AWS service APIs, these roles would need to include # those as allowed actions. ECSTaskRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - ecs-tasks.amazonaws.com - ec2.amazonaws.com Action: ['sts:AssumeRole'] Path: / Policies: - PolicyName: AmazonECSTaskRolePolicy PolicyDocument: Statement: - Effect: Allow Action: # Allow the ECS Tasks to download images from ECR - 'ecr:GetAuthorizationToken' - 'ecr:BatchCheckLayerAvailability' - 'ecr:GetDownloadUrlForLayer' - 'ecr:BatchGetImage' # Allow the ECS tasks to upload logs to CloudWatch - 'logs:CreateLogStream' - 'logs:CreateLogGroup' - 'logs:PutLogEvents' Resource: '*' - Effect: Allow Action: # Allows the ECS tasks to interact with only the MysfitsTable # in DynamoDB - 'dynamodb:Scan' - 'dynamodb:Query' - 'dynamodb:UpdateItem' - 'dynamodb:GetItem' Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${DDBTableName}' # Removing this for now to test with multi-region^ # Resource: '*' - Effect: Allow Action: # Allows the X-Ray sidecar to send telemetry data to X-Ray API - 'xray:PutTraceSegments' - 'xray:PutTelemetryRecords' - 'xray:GetSamplingRules' - 'xray:GetSamplingTargets' - 'xray:GetSamplingStatisticSummaries' Resource: '*' MythicalProfile: Condition: PrimaryRegion Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref ECSTaskRole MythicalDynamoTable: Condition: PrimaryRegion Type: AWS::DynamoDB::Table Properties: TableName: !Ref DDBTableName AttributeDefinitions: - AttributeName: MysfitId AttributeType: S - AttributeName: GoodEvil AttributeType: S - AttributeName: LawChaos AttributeType: S BillingMode: PAY_PER_REQUEST GlobalSecondaryIndexes: - IndexName: LawChaosIndex KeySchema: - AttributeName: LawChaos KeyType: HASH - AttributeName: MysfitId KeyType: RANGE Projection: ProjectionType: ALL - IndexName: GoodEvilIndex KeySchema: - AttributeName: GoodEvil KeyType: HASH - AttributeName: MysfitId KeyType: RANGE Projection: ProjectionType: ALL KeySchema: - AttributeName: MysfitId KeyType: HASH MythicalEnvironment: Type: AWS::Cloud9::EnvironmentEC2 Condition: PrimaryRegion Properties: AutomaticStopTimeMinutes: 30 InstanceType: t2.small Name: !Sub Project-${AWS::StackName} SubnetId: !Ref PublicSubnetOne CoreServiceRepo: Type: AWS::ECR::Repository LikeServiceRepo: Type: AWS::ECR::Repository MythicalEcsCluster: Type: AWS::ECS::Cluster Properties: ClusterName: !Sub Cluster-${AWS::StackName} MythicalServiceLogGroup: Type: AWS::Logs::LogGroup LikeServiceLogGroup: Type: AWS::Logs::LogGroup CoreServiceTaskDefinition: Condition: PrimaryRegion Type: AWS::ECS::TaskDefinition Properties: Cpu: 256 ExecutionRoleArn: !GetAtt EcsServiceRole.Arn Family: !Sub Core-Service-${AWS::StackName} Memory: 512 NetworkMode: awsvpc RequiresCompatibilities: - FARGATE TaskRoleArn: !GetAtt ECSTaskRole.Arn ContainerDefinitions: - Name: service Image: docker.io/nginx:latest PortMappings: - ContainerPort: 8080 Protocol: http Environment: - Name: UPSTREAM_URL Value: !GetAtt MythicalLoadBalancer.DNSName - Name: DDB_TABLE_NAME Value: !Ref DDBTableName LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref MythicalServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: awslogs-mythicalmysfits-service Essential: true - Name: xray-daemon Image: amazon/aws-xray-daemon PortMappings: - ContainerPort: 2000 Protocol: udp Essential: true LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref MythicalServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: xray-daemon LikeServiceTaskDefinition: Condition: PrimaryRegion Type: AWS::ECS::TaskDefinition Properties: Cpu: 256 ExecutionRoleArn: !GetAtt EcsServiceRole.Arn Family: !Sub Like-Service-${AWS::StackName} Memory: 512 NetworkMode: awsvpc RequiresCompatibilities: - FARGATE TaskRoleArn: !GetAtt ECSTaskRole.Arn ContainerDefinitions: - Name: like-service Image: docker.io/nginx:latest PortMappings: - ContainerPort: 8080 Protocol: http Environment: - Name: CHAOSMODE Value: 'on' - Name: DDB_TABLE_NAME Value: !Ref DDBTableName - Name: LOGLEVEL Value: 'ERROR' LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref LikeServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: awslogs-mythicalmysfits-service Essential: true - Name: xray-daemon Image: amazon/aws-xray-daemon PortMappings: - ContainerPort: 2000 Protocol: udp Essential: true LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref LikeServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: xray-daemon SecondaryCoreServiceTaskDefinition: Condition: SecondaryRegion Type: AWS::ECS::TaskDefinition Properties: Cpu: 256 ExecutionRoleArn: !GetAtt EcsServiceRole.Arn Family: !Sub Secondary-Core-Service-${AWS::StackName} Memory: 512 NetworkMode: awsvpc RequiresCompatibilities: - FARGATE TaskRoleArn: !GetAtt ECSTaskRole.Arn ContainerDefinitions: - Name: service Image: docker.io/nginx:latest PortMappings: - ContainerPort: 8080 Protocol: http Environment: - Name: UPSTREAM_URL Value: !GetAtt MythicalLoadBalancer.DNSName - Name: DDB_TABLE_NAME Value: !Ref DDBTableName LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref MythicalServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: awslogs-mythicalmysfits-service Essential: true - Name: xray-daemon Image: amazon/aws-xray-daemon PortMappings: - ContainerPort: 2000 Protocol: udp Essential: true LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref MythicalServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: xray-daemon SecondaryLikeServiceTaskDefinition: Condition: SecondaryRegion Type: AWS::ECS::TaskDefinition Properties: Cpu: 256 ExecutionRoleArn: !GetAtt EcsServiceRole.Arn Family: !Sub Secondary-Like-Service-${AWS::StackName} Memory: 512 NetworkMode: awsvpc RequiresCompatibilities: - FARGATE TaskRoleArn: !GetAtt ECSTaskRole.Arn ContainerDefinitions: - Name: like-service Image: docker.io/nginx:latest PortMappings: - ContainerPort: 8080 Protocol: http Environment: - Name: CHAOSMODE Value: 'off' - Name: DDB_TABLE_NAME Value: !Ref DDBTableName - Name: LOGLEVEL Value: 'ERROR' LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref LikeServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: awslogs-mythicalmysfits-service Essential: true - Name: xray-daemon Image: amazon/aws-xray-daemon PortMappings: - ContainerPort: 2000 Protocol: udp Essential: true LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref LikeServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: xray-daemon CoreEcsService: Condition: SecondaryRegion DependsOn: MythicalListener Type: AWS::ECS::Service Properties: TaskDefinition: !Ref SecondaryCoreServiceTaskDefinition DesiredCount: 1 LaunchType: FARGATE LoadBalancers: - TargetGroupArn: !Ref MythicalServiceTargetGroup ContainerPort: 8080 ContainerName: service Cluster: !Ref MythicalEcsCluster NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - !Ref FargateContainerSecurityGroup Subnets: - !Ref PrivateSubnetOne - !Ref PrivateSubnetTwo LikeEcsService: Condition: SecondaryRegion DependsOn: MythicalLikeListenerRule Type: AWS::ECS::Service Properties: TaskDefinition: !Ref SecondaryLikeServiceTaskDefinition DesiredCount: 1 LaunchType: FARGATE LoadBalancers: - TargetGroupArn: !Ref LikeServiceTargetGroup ContainerPort: 8080 ContainerName: like-service Cluster: !Ref MythicalEcsCluster NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - !Ref FargateContainerSecurityGroup Subnets: - !Ref PrivateSubnetOne - !Ref PrivateSubnetTwo MythicalLoadBalancerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub SecurityGroup-${AWS::StackName} GroupDescription: Access to the load balancer VpcId: !Ref 'VPC' SecurityGroupIngress: # Allow access to ALB from anywhere on the internet - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 MythicalLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer DependsOn: GatewayAttachement Properties: Name: !Sub alb-${AWS::StackName} Scheme: internet-facing Type: application SecurityGroups: - !Ref MythicalLoadBalancerSecurityGroup Subnets: - !Ref PublicSubnetOne - !Ref PublicSubnetTwo MythicalListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref MythicalServiceTargetGroup Type: forward LoadBalancerArn: !Ref MythicalLoadBalancer Port: 80 Protocol: HTTP MythicalServiceTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 10 HealthCheckPath: / HealthCheckProtocol: HTTP HealthyThresholdCount: 3 UnhealthyThresholdCount: 3 Port: 8080 Protocol: HTTP VpcId: !Ref VPC TargetType: ip TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 30 LikeServiceTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 10 HealthCheckPath: / HealthCheckProtocol: HTTP HealthyThresholdCount: 3 UnhealthyThresholdCount: 3 Port: 8080 Protocol: HTTP VpcId: !Ref VPC TargetType: ip TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 30 MythicalLikeListenerRule: Type: AWS::ElasticLoadBalancingV2::ListenerRule Properties: Actions: - Type: forward TargetGroupArn: !Ref LikeServiceTargetGroup Conditions: - Field: path-pattern Values: - "/mysfits/*/like" ListenerArn: !Ref MythicalListener Priority: 1 MythicalCiCdStack: Type: AWS::CloudFormation::Stack Condition: PrimaryRegion Properties: TemplateURL: "https://mythical-mysfits-website.s3.amazonaws.com/multi-region-bcdr/cicd.yml" Parameters: MythicalEcsCluster: !Ref MythicalEcsCluster CoreServiceName: !Sub ${AWS::StackName}_Core-Service LikeServiceName: !Sub ${AWS::StackName}_Like-Service CfnStackName: !Sub ${AWS::StackName} MultiRegionCwDashboard: Type: AWS::CloudFormation::Stack DependsOn: MythicalCiCdStack Condition: PrimaryRegion Properties: TemplateURL: "https://mythical-mysfits-website.s3.amazonaws.com/multi-region-bcdr/cwdashboard.yml" Parameters: MythicalEcsCluster: !Ref MythicalEcsCluster CoreServiceName: !Sub ${AWS::StackName}_Core-Service LikeServiceName: !Sub ${AWS::StackName}_Like-Service PrimaryRegionALBFullName: !GetAtt MythicalLoadBalancer.LoadBalancerFullName DDBTable: !Ref DDBTableName DashboardName: !Sub ${AWS::StackName}_Dashboard CleanDeletionCustomResource: # Removing from secondary region for time being. Todo. Condition: PrimaryRegion Type: Custom::CleanDelete DependsOn: CleanDeletionLambdaFunction Properties: ServiceToken: !GetAtt CleanDeletionLambdaFunction.Arn Region: !Ref AWS::Region CfnStackName: !Ref AWS::StackName MythicalArtifactBucket: !GetAtt 'MythicalCiCdStack.Outputs.MythicalArtifactBucket' CoreServiceRepo: !Ref CoreServiceRepo LikeServiceRepo: !Ref LikeServiceRepo EcsClusterName: !Ref MythicalEcsCluster SecondaryRegion: !Ref IsSecondary SecondaryCleanDeletionCustomResource: # Removing from secondary region for time being. Todo. Condition: SecondaryRegion Type: Custom::CleanDelete DependsOn: CleanDeletionLambdaFunction Properties: ServiceToken: !GetAtt CleanDeletionLambdaFunction.Arn Region: !Ref AWS::Region CfnStackName: !Ref AWS::StackName CoreServiceRepo: !Ref CoreServiceRepo LikeServiceRepo: !Ref LikeServiceRepo EcsClusterName: !Ref MythicalEcsCluster SecondaryRegion: !Ref IsSecondary #Actual Lambda function to delete stuff CleanDeletionLambdaFunction: Type: AWS::Lambda::Function Properties: Code: ZipFile: !Sub | from __future__ import print_function import boto3 import logging import cfnresponse logger = logging.getLogger() logger.setLevel(logging.INFO) ecrClient = boto3.client('ecr') ecsClient = boto3.client('ecs') s3 = boto3.resource('s3') def handler(event, context): print(event) CoreServiceEcrRepo = event['ResourceProperties']['CoreServiceRepo'] LikeServiceEcrRepo = event['ResourceProperties']['LikeServiceRepo'] EcsClusterName = event['ResourceProperties']['EcsClusterName'] CfnStackName = event['ResourceProperties']['CfnStackName'] IsSecondary = event['ResourceProperties']['SecondaryRegion'] if IsSecondary == 'false': MythicalArtifactBucket = event['ResourceProperties']['MythicalArtifactBucket'] logger.debug('Event: {}'.format(event)) logger.debug('Context: {}'.format(context)) tempResponse = [] responseData = {} failure = False # Immediately respond on Delete if event['RequestType'] == 'Delete': try: coreDeleteResponse = ecrClient.delete_repository( repositoryName=CoreServiceEcrRepo, force=True) except Exception as e: if "RepositoryNotFoundException" in str(e): failure = False else: failure = True tempResponse.append({CoreServiceEcrRepo:str(e)}) try: likeDeleteResponse = ecrClient.delete_repository( repositoryName=LikeServiceEcrRepo, force=True) except Exception as e: if "RepositoryNotFoundException" in str(e): failure = False else: failure = True tempResponse.append({LikeServiceEcrRepo:str(e)}) if IsSecondary == 'false': try: bucket = s3.Bucket(MythicalArtifactBucket) # Delete S3 bucket for key in bucket.objects.all(): key.delete() bucket.delete() except Exception as e: if "The specified bucket does not exist" in str(e): failure = False else: failure = True tempResponse.append({MythicalArtifactBucket:str(e)}) try: # List ECS Services and delete services in a specific cluster ecsServicesToDelete = ecsClient.list_services( cluster=EcsClusterName ) if ecsServicesToDelete['serviceArns'] != []: for serviceName in ecsServicesToDelete['serviceArns']: ecsDeleteResponse = ecsClient.delete_service( cluster=EcsClusterName, service=serviceName, force=True) except Exception as e: failure = True tempResponse.append({EcsClusterName:str(e)}) responseData['Data'] = str(tempResponse) if failure == False: cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') else: cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID') if event['RequestType'] == 'Update' or event['RequestType'] == 'Create': try: responseData = {} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') except Exception as e: cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID') Handler: index.handler Role: !If [PrimaryRegion, !GetAtt 'LambdaExecutionRole.Arn', !GetAtt 'SecondaryLambdaExecutionRole.Arn'] Runtime: python3.7 Timeout: '300' LambdaExecutionRole: Condition: PrimaryRegion Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - ecr:* - ecs:ListServices Resource: '*' - Effect: Allow Action: - s3:DeleteObject - s3:DeleteBucket - s3:ListObjects - s3:ListBucket Resource: - !Sub arn:aws:s3:::${MythicalCiCdStack.Outputs.MythicalArtifactBucket} - !Sub arn:aws:s3:::${MythicalCiCdStack.Outputs.MythicalArtifactBucket}/* - Effect: Allow Action: - ecs:DeleteService Resource: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:service/${MythicalEcsCluster}/* SecondaryLambdaExecutionRole: Condition: SecondaryRegion Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - ecr:* - ecs:ListServices Resource: '*' # These are the values output by the CloudFormation template. Be careful # about changing any of them, because of them are exported with specific # names so that the other task related CF templates can use them. Outputs: CurrentRegion: Description: REPLACE_ME_REGION Value: !Ref AWS::Region Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CurrentRegion' ] ] SecondaryRegion: Condition: SecondaryRegion Description: REPLACEME_SECONDARY_REGION Value: !Ref AWS::Region Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'SecondaryRegion' ] ] CurrentAccount: Description: REPLACE_ME_ACCOUNT_ID Value: !Ref AWS::AccountId Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CurrentAccount' ] ] EcsServiceRole: Description: REPLACE_ME_ECS_SERVICE_ROLE_ARN Value: !GetAtt 'EcsServiceRole.Arn' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'EcsServiceRole' ] ] ECSTaskRole: Description: REPLACE_ME_ECS_TASK_ROLE_ARN Value: !GetAtt 'ECSTaskRole.Arn' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSTaskRole' ] ] VPCId: Description: REPLACE_ME_VPC_ID Value: !Ref 'VPC' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ] PublicSubnetOne: Description: REPLACE_ME_PUBLIC_SUBNET_ONE Value: !Ref 'PublicSubnetOne' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ] PublicSubnetTwo: Description: REPLACE_ME_PUBLIC_SUBNET_TWO Value: !Ref 'PublicSubnetTwo' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ] PrivateSubnetOne: Description: REPLACE_ME_PRIVATE_SUBNET_ONE Value: !Ref 'PrivateSubnetOne' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrivateSubnetOne' ] ] PrivateSubnetTwo: Description: REPLACE_ME_PRIVATE_SUBNET_TWO Value: !Ref 'PrivateSubnetTwo' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrivateSubnetTwo' ] ] FargateContainerSecurityGroup: Description: REPLACE_ME_SECURITY_GROUP_ID Value: !Ref 'FargateContainerSecurityGroup' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'FargateContainerSecurityGroup' ] ] CodeBuildRole: Condition: PrimaryRegion Description: REPLACE_ME_CODEBUILD_ROLE_ARN Value: !GetAtt 'MythicalCiCdStack.Outputs.MythicalMysfitsServiceCodeBuildServiceRoleArn' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'MythicalMysfitsServiceCodeBuildServiceRole' ] ] CodePipelineRole: Condition: PrimaryRegion Description: REPLACE_ME_CODEPIPELINE_ROLE_ARN Value: !GetAtt 'MythicalCiCdStack.Outputs.MythicalMysfitsServiceCodePipelineServiceRoleArn' Export: Name: !Join [ ':', [ !Ref 'AWS::StackName', 'MythicalMysfitsServiceCodePipelineServiceRole' ] ] LoadBalancerDNS: Description: The DNS for the load balancer Value: !GetAtt MythicalLoadBalancer.DNSName SecondaryLoadBalancerDNS: Condition: SecondaryRegion Description: The DNS for the load balancer Value: !GetAtt MythicalLoadBalancer.DNSName DynamoTable: Condition: PrimaryRegion Value: !Ref DDBTableName ProfileName: Condition: PrimaryRegion Value: !Ref MythicalProfile CoreServiceEcrRepo: Condition: PrimaryRegion Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${CoreServiceRepo} LikeServiceEcrRepo: Condition: PrimaryRegion Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${LikeServiceRepo} SecondaryCoreServiceEcrRepo: Condition: SecondaryRegion Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${CoreServiceRepo} SecondaryLikeServiceEcrRepo: Condition: SecondaryRegion Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${LikeServiceRepo} MythicalServiceTargetGroupArn: Value: !Ref MythicalServiceTargetGroup LikeServiceTargetGroupArn: Value: !Ref LikeServiceTargetGroup EcsClusterName: Value: !Ref MythicalEcsCluster StackName: Value: !Sub ${AWS::StackName} CoreServiceTaskDefinition: Condition: PrimaryRegion Value: !Ref CoreServiceTaskDefinition LikeServiceTaskDefinition: Condition: PrimaryRegion Value: !Ref LikeServiceTaskDefinition SecondaryCoreServiceTaskDefinition: Condition: SecondaryRegion Value: !Ref SecondaryCoreServiceTaskDefinition SecondaryLikeServiceTaskDefinition: Condition: SecondaryRegion Value: !Ref SecondaryLikeServiceTaskDefinition MythicalLikeGitRepositoryCloneUrl: Condition: PrimaryRegion Value: !GetAtt 'MythicalCiCdStack.Outputs.MythicalLikeGitRepositoryCloneUrl' MythicalLikeGitRepositoryName: Condition: PrimaryRegion Value: !GetAtt 'MythicalCiCdStack.Outputs.MythicalLikeGitRepositoryName' MythicalCoreGitRepositoryCloneUrl: Condition: PrimaryRegion Value: !GetAtt 'MythicalCiCdStack.Outputs.MythicalCoreGitRepositoryCloneUrl' MythicalCoreGitRepositoryName: Condition: PrimaryRegion Value: !GetAtt 'MythicalCiCdStack.Outputs.MythicalCoreGitRepositoryName' CoreEcsServiceName: Value: !If [PrimaryRegion, !Sub '${AWS::StackName}_Core-Service', !GetAtt 'CoreEcsService.Name'] LikeEcsServiceName: Value: !If [PrimaryRegion, !Sub '${AWS::StackName}_Like-Service', !GetAtt 'LikeEcsService.Name'] LoadBalancerFullName: Description: The Full Name for the load balancer Value: !GetAtt MythicalLoadBalancer.LoadBalancerFullName