From 486119aab168c3039cc09943cfd7503af9d59a2c Mon Sep 17 00:00:00 2001 From: Niklas Fondberg Date: Fri, 9 Oct 2020 12:33:57 +0200 Subject: [PATCH] Add docker hub credentials and lifecycle for ECR (#27) * Add credentials for docker hub * Add lifecycle policy --- cloudformation/Makefile | 16 +++-- cloudformation/ecr-lifecycle-policy.json | 13 ++++ cloudformation/ecs-service.yaml | 76 +++++++++++++++--------- 3 files changed, 73 insertions(+), 32 deletions(-) create mode 100644 cloudformation/ecr-lifecycle-policy.json diff --git a/cloudformation/Makefile b/cloudformation/Makefile index c99c1b3..f875cf6 100644 --- a/cloudformation/Makefile +++ b/cloudformation/Makefile @@ -1,11 +1,11 @@ ## # Example: -# make create-stack GITHUB_TOKEN=xxxxx ENVIRONMENT_NAME=staging STACK_NAME=api -# make create-stack GITHUB_TOKEN=xxxxx ENVIRONMENT_NAME=production STACK_NAME=api +# make create-stack GITHUB_TOKEN=xxxxx ENVIRONMENT_NAME=staging +# make create-stack GITHUB_TOKEN=xxxxx ENVIRONMENT_NAME=production # -# # make update-stack ENVIRONMENT_NAME=staging STACK_NAME=api +# # make update-stack ENVIRONMENT_NAME=staging ## - +STACK_NAME=api GITHUB_REPO := api2 GITHUB_USER := Photowall # HOSTED_ZONE=photowall.com default in the template @@ -54,6 +54,12 @@ check-create-initial-ecr-image: echo ""; \ fi +set-repository-lifecycle-policy: + @echo Setting lifecycle policy for repo $(STACK_NAME)-$(ENVIRONMENT_NAME) + @aws ecr put-lifecycle-policy \ + --repository-name $(STACK_NAME)-$(ENVIRONMENT_NAME) \ + --lifecycle-policy-text "file://ecr-lifecycle-policy.json" + check-create-variables-set: $(call checkdef,STACK_NAME) $(call checkdef,ENVIRONMENT_NAME) @@ -77,7 +83,7 @@ create-stack: check-create-variables-set check-create-initial-ecr-image ParameterKey="MemorySize",ParameterValue="$(MEMORY_SIZE)" \ ParameterKey="BernardSQSPolicy",ParameterValue="$(BERNARD_SQS_POLICY)" -update-stack: +update-stack: set-repository-lifecycle-policy $(call checkdef,STACK_NAME) $(call checkdef,ENVIRONMENT_NAME) aws cloudformation update-stack --stack-name $(STACK_NAME)-$(ENVIRONMENT_NAME) --template-body file://ecs-service.yaml \ diff --git a/cloudformation/ecr-lifecycle-policy.json b/cloudformation/ecr-lifecycle-policy.json new file mode 100644 index 0000000..bbc1859 --- /dev/null +++ b/cloudformation/ecr-lifecycle-policy.json @@ -0,0 +1,13 @@ +{ + "rules": [ + { + "rulePriority": 1, + "description": "Only keep 8 images", + "selection": { + "tagStatus": "any", + "countType": "imageCountMoreThan", + "countNumber": 8 + }, + "action": { "type": "expire" } + }] +} diff --git a/cloudformation/ecs-service.yaml b/cloudformation/ecs-service.yaml index 679f318..a7ad56e 100644 --- a/cloudformation/ecs-service.yaml +++ b/cloudformation/ecs-service.yaml @@ -94,7 +94,7 @@ Resources: Cluster: Type: AWS::ECS::Cluster Properties: - ClusterName: !Join ['', [!Ref 'AWS::StackName', Cluster]] + ClusterName: !Join ["", [!Ref "AWS::StackName", Cluster]] TaskDefinition: Type: AWS::ECS::TaskDefinition @@ -102,7 +102,7 @@ Resources: DependsOn: LogGroup Properties: # Name of the task definition. Subsequent versions of the task definition are grouped together under this name. - Family: !Join ['', [!Ref 'AWS::StackName', TaskDefinition]] + Family: !Join ["", [!Ref "AWS::StackName", TaskDefinition]] # awsvpc is required for Fargate NetworkMode: awsvpc RequiresCompatibilities: @@ -125,9 +125,12 @@ Resources: - ContainerPort: !Ref ContainerPort # Define which parameters should be fetched Secrets: - - { Name: "SQLALCHEMY_DATABASE_URI", ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/SQLALCHEMY_DATABASE_URI" } - - { Name: "API_KEYS", ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/API2_API_KEYS" } - - { Name: "BERNARD_QUEUE_URL", ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/BERNARD_QUEUE_URL" } + - Name: "SQLALCHEMY_DATABASE_URI" + ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/SQLALCHEMY_DATABASE_URI" + - Name: "API_KEYS" + ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/API2_API_KEYS" + - Name: "BERNARD_QUEUE_URL" + ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/BERNARD_QUEUE_URL" # Send logs to CloudWatch Logs LogConfiguration: LogDriver: awslogs @@ -140,49 +143,49 @@ Resources: ExecutionRole: Type: AWS::IAM::Role Properties: - RoleName: !Join ['', [!Ref 'AWS::StackName', ExecutionRole]] + RoleName: !Join ["", [!Ref "AWS::StackName", ExecutionRole]] AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com - Action: 'sts:AssumeRole' + Action: "sts:AssumeRole" ManagedPolicyArns: - - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy' + - "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess # A role for the containers TaskRole: Type: AWS::IAM::Role Properties: - RoleName: !Join ['', [!Ref 'AWS::StackName', TaskRole]] + RoleName: !Join ["", [!Ref "AWS::StackName", TaskRole]] AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com - Action: 'sts:AssumeRole' + Action: "sts:AssumeRole" ManagedPolicyArns: - !Ref BernardSQSPolicy - # A role needed for auto scaling AutoScalingRole: Type: AWS::IAM::Role Properties: - RoleName: !Join ['', [!Ref 'AWS::StackName', AutoScalingRole]] + RoleName: !Join ["", [!Ref "AWS::StackName", AutoScalingRole]] AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com - Action: 'sts:AssumeRole' + Action: "sts:AssumeRole" ManagedPolicyArns: - - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole' + - "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole" ContainerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: - GroupDescription: !Join ['', [!Ref 'AWS::StackName', ContainerSecurityGroup]] + GroupDescription: + !Join ["", [!Ref "AWS::StackName", ContainerSecurityGroup]] VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp @@ -193,7 +196,8 @@ Resources: LoadBalancerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: - GroupDescription: !Join ['', [!Ref 'AWS::StackName', LoadBalancerSecurityGroup]] + GroupDescription: + !Join ["", [!Ref "AWS::StackName", LoadBalancerSecurityGroup]] VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp @@ -241,7 +245,7 @@ Resources: HealthCheckTimeoutSeconds: 5 UnhealthyThresholdCount: 2 HealthyThresholdCount: 2 - Name: !Join ['', [!Ref 'AWS::StackName', TargetGroup]] + Name: !Join ["", [!Ref "AWS::StackName", TargetGroup]] Port: !Ref ContainerPort Protocol: HTTP TargetGroupAttributes: @@ -269,7 +273,7 @@ Resources: # this is the default, but is specified here in case it needs to be changed - Key: idle_timeout.timeout_seconds Value: 60 - Name: !Join ['', [!Ref 'AWS::StackName', LoadBalancer]] + Name: !Join ["", [!Ref "AWS::StackName", LoadBalancer]] # "internal" is also an option Scheme: internet-facing SecurityGroups: @@ -281,8 +285,8 @@ Resources: DNSRecord: Type: AWS::Route53::RecordSet Properties: - HostedZoneName: !Join ['', [!Ref HostedZoneName, .]] - Name: !Join ['', [!Ref Subdomain, ., !Ref HostedZoneName, .]] + HostedZoneName: !Join ["", [!Ref HostedZoneName, .]] + Name: !Join ["", [!Ref Subdomain, ., !Ref HostedZoneName, .]] Type: A AliasTarget: DNSName: !GetAtt LoadBalancer.DNSName @@ -291,14 +295,14 @@ Resources: LogGroup: Type: AWS::Logs::LogGroup Properties: - LogGroupName: !Join ['', [/ecs/, !Ref 'AWS::StackName', TaskDefinition]] + LogGroupName: !Join ["", [/ecs/, !Ref "AWS::StackName", TaskDefinition]] AutoScalingTarget: Type: AWS::ApplicationAutoScaling::ScalableTarget Properties: MinCapacity: !Ref MinContainers MaxCapacity: !Ref MaxContainers - ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]] + ResourceId: !Join ["/", [service, !Ref Cluster, !GetAtt Service.Name]] ScalableDimension: ecs:service:DesiredCount ServiceNamespace: ecs # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that allows Application Auto Scaling to modify your scalable target." @@ -306,7 +310,7 @@ Resources: AutoScalingPolicy: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: - PolicyName: !Join ['', [!Ref 'AWS::StackName', AutoScalingPolicy]] + PolicyName: !Join ["", [!Ref "AWS::StackName", AutoScalingPolicy]] PolicyType: TargetTrackingScaling ScalingTargetId: !Ref AutoScalingTarget TargetTrackingScalingPolicyConfiguration: @@ -357,6 +361,11 @@ Resources: - ecr:InitiateLayerUpload - ecr:UploadLayerPart - ecr:CompleteLayerUpload + - Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/dockerhub/* + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters CodePipelineServiceRole: Type: AWS::IAM::Role @@ -407,9 +416,13 @@ Resources: BuildSpec: | version: 0.2 phases: + install: + runtime-versions: + docker: 19 pre_build: commands: - - $(aws ecr get-login --no-include-email) + - echo Logging in to Docker Hub... + - echo $DOCKERHUB_PASSWORD | docker login --username $DOCKERHUB_USERNAME --password-stdin - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" - IMAGE_URI="${REPOSITORY_URI}:${TAG}" - IMAGE_LATEST_URI="${REPOSITORY_URI}:latest" @@ -419,6 +432,8 @@ Resources: - docker tag "$IMAGE_URI" "$IMAGE_LATEST_URI" post_build: commands: + - echo Logging in to ECR to push image + - $(aws ecr get-login --no-include-email --region eu-west-1) - docker push "$IMAGE_URI" - docker push "$IMAGE_LATEST_URI" - printf '[{"name":"%s","imageUri":"%s"}]' "$IMAGE_NAME" "$IMAGE_LATEST_URI" > images.json @@ -426,7 +441,8 @@ Resources: files: images.json Environment: ComputeType: BUILD_GENERAL1_SMALL - Image: aws/codebuild/docker:17.09.0 + PrivilegedMode: true + Image: aws/codebuild/standard:4.0 Type: LINUX_CONTAINER EnvironmentVariables: - Name: AWS_DEFAULT_REGION @@ -435,6 +451,12 @@ Resources: Value: !Ref Image - Name: IMAGE_NAME Value: !Ref AWS::StackName + - Name: DOCKERHUB_USERNAME + Value: /dockerhub/username + Type: PARAMETER_STORE + - Name: DOCKERHUB_PASSWORD + Value: /dockerhub/password + Type: PARAMETER_STORE Name: !Ref AWS::StackName ServiceRole: !Ref CodeBuildServiceRole @@ -442,7 +464,7 @@ Resources: Type: AWS::CodePipeline::Pipeline Properties: RoleArn: !GetAtt CodePipelineServiceRole.Arn - Name: !Join ['', [!Ref 'AWS::StackName', Pipeline]] + Name: !Join ["", [!Ref "AWS::StackName", Pipeline]] ArtifactStore: Type: S3 Location: !Ref ArtifactBucket @@ -498,7 +520,7 @@ Resources: Outputs: Endpoint: Description: Endpoint - Value: !Join ['', ['https://', !Ref DNSRecord]] + Value: !Join ["", ["https://", !Ref DNSRecord]] Service: Value: Service PipelineUrl: