Using a Codepipeline

Setting up a Codepipeline

Enhance your project Makefile in the project directory with the following content:


BITBUCKET_HOST       := ...
BITBUCKET_PROJECT    := ...
BITBUCKET_REPOSITORY := ...
BITBUCKET_BRANCH     := master

###############################################################################################

.PHONY: pipeline
pipeline: guard-BITBUCKET_TOKEN
	aws cloudformation deploy \
		--no-fail-on-empty-changeset \
		--template-file cloudformation/pipeline.yml \
		--stack-name $(SERVICE)-pipeline \
		--parameter-overrides BitbucketHost=$(BITBUCKET_HOST) \
		                      BitbucketProject=$(BITBUCKET_PROJECT) \
		                      BitbucketRepository=$(BITBUCKET_REPOSITORY) \
		                      BitbucketBranch=$(BITBUCKET_BRANCH) \
		                      BitbucketToken=$(BITBUCKET_TOKEN) \
		--capabilities CAPABILITY_IAM \
		--region $(AWS_REGION)
	touch trigger && zip trigger.zip trigger
	aws s3api put-object --bucket $(SERVICE)-pipeline-$(shell aws sts get-caller-identity --query 'Account' --output text) --key trigger --body 'trigger.zip'
	rm trigger && rm trigger.zip

.PHONY: guard-%
guard-%:
	$(if $(value ${*}),,$(error "Variable ${*} not set!"))

Create a new cloudformation template in cloudformation/pipeline.yml and define your pipeline in there:

---
AWSTemplateFormatVersion: 2010-09-09
Description: Deployment pipeline

Parameters:
  BitbucketHost:
    Type: String
  BitbucketProject:
    Type: String
  BitbucketRepository:
    Type: String
  BitbucketBranch:
    Type: String
  BitbucketToken:
    Type: String

Resources:
  CodePipelineSourceBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Sub '${AWS::StackName}-${AWS::AccountId}'
      VersioningConfiguration:
        Status: Enabled
  CodePipelineArtifactStoreBucket:
    Type: 'AWS::S3::Bucket'
  CodePipelineArtifactStoreBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref CodePipelineArtifactStoreBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: DenyUnEncryptedObjectUploads
            Effect: Deny
            Principal: '*'
            Action: 's3:PutObject'
            Resource: !Join
              - ''
              - - !GetAtt
                  - CodePipelineArtifactStoreBucket
                  - Arn
                - /*
            Condition:
              StringNotEquals:
                's3:x-amz-server-side-encryption': 'aws:kms'
          - Sid: DenyInsecureConnections
            Effect: Deny
            Principal: '*'
            Action: 's3:*'
            Resource: !Join
              - ''
              - - !GetAtt
                  - CodePipelineArtifactStoreBucket
                  - Arn
                - /*
            Condition:
              Bool:
                'aws:SecureTransport': false
  AppPipeline:
    Type: 'AWS::CodePipeline::Pipeline'
    Properties:
      Name: !Ref AWS::StackName
      RoleArn: !GetAtt
        - CodePipelineServiceRole
        - Arn
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: 1
                Provider: S3
              OutputArtifacts:
                - Name: NoOp
              Configuration:
                S3Bucket: !Ref CodePipelineSourceBucket
                S3ObjectKey: 'trigger'
                PollForSourceChanges: true
        - Name: GitClone
          Actions:
            - Name: GitClone
              InputArtifacts:
                - Name: NoOp
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              OutputArtifacts:
                - Name: SourceOutput
              Configuration:
                ProjectName: !Ref AppGitClone
        - Name: Build
          Actions:
            - Name: Build
              InputArtifacts:
                - Name: SourceOutput
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              OutputArtifacts:
                - Name: BuildOutput
              Configuration:
                ProjectName: !Ref AppCodeBuild
      ArtifactStore:
        Type: S3
        Location: !Ref CodePipelineArtifactStoreBucket
  AppGitClone:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Sub '${AWS::StackName}-clone'
      ServiceRole: !Ref CodeBuildServiceRole
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        ComputeType: BUILD_GENERAL1_MEDIUM
        Image: aws/codebuild/standard:2.0
        Type: LINUX_CONTAINER
        PrivilegedMode: true
      Source:
        Type: CODEPIPELINE
        BuildSpec: !Sub  |
          version: 0.2
          phases:
            install:
              runtime-versions:
                java: openjdk8
            build:
              commands:
                - "curl -sfLl -H 'Authorization: Bearer ${BitbucketToken}' -o repo.tar.gz 'https://${BitbucketHost}/rest/api/latest/projects/${BitbucketProject}/repos/${BitbucketRepository}/archive?at=refs%2Fheads%2F${BitbucketBranch}&format=tar.gz&filename=repo.tar.gz'"
                - mkdir -p ${BitbucketRepository}
                - tar zxvf repo.tar.gz -C ${BitbucketRepository}
          artifacts:
            files:
              - '**/*'
            base-directory: ${BitbucketRepository}
  AppCodeBuild:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref AWS::StackName
      ServiceRole: !Ref CodeBuildServiceRole
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        ComputeType: BUILD_GENERAL1_LARGE
        Image: aws/codebuild/standard:2.0
        Type: LINUX_CONTAINER
        PrivilegedMode: true
      Source:
        Type: CODEPIPELINE
        BuildSpec: buildspec.yml
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          Effect: Allow
          Principal:
            Service: codebuild.amazonaws.com
          Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess
  CodePipelineServiceRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codepipeline.amazonaws.com
            Action: 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: AWS-CodePipeline-Service-3
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'codecommit:CancelUploadArchive'
                  - 'codecommit:GetBranch'
                  - 'codecommit:GetCommit'
                  - 'codecommit:GetUploadArchiveStatus'
                  - 'codecommit:UploadArchive'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'codedeploy:CreateDeployment'
                  - 'codedeploy:GetApplicationRevision'
                  - 'codedeploy:GetDeployment'
                  - 'codedeploy:GetDeploymentConfig'
                  - 'codedeploy:RegisterApplicationRevision'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'codebuild:BatchGetBuilds'
                  - 'codebuild:StartBuild'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'devicefarm:ListProjects'
                  - 'devicefarm:ListDevicePools'
                  - 'devicefarm:GetRun'
                  - 'devicefarm:GetUpload'
                  - 'devicefarm:CreateUpload'
                  - 'devicefarm:ScheduleRun'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'lambda:InvokeFunction'
                  - 'lambda:ListFunctions'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'iam:PassRole'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'elasticbeanstalk:*'
                  - 'ec2:*'
                  - 'elasticloadbalancing:*'
                  - 'autoscaling:*'
                  - 'cloudwatch:*'
                  - 's3:*'
                  - 'sns:*'
                  - 'cloudformation:*'
                  - 'rds:*'
                  - 'sqs:*'
                  - 'ecs:*'
                Resource: '*'

Create the Cloudformation stack with the following command in the ci AWS account

AWS_PROFILE=myorg-ci BITBUCKET_TOKEN=... make pipeline

In order to make the pipeline work you also need to define a file buildspec.yml. Create it with the following content:

version: 0.2

phases:
  install:
    runtime-versions:
      java: openjdk8
      docker: 18
  build:
    commands:
      - echo "Bulding and pushing the Docker image..."
      - make docker-build docker-push

Make sure to commit and push everything to the repository:

git add Makefile cloudformation/*.yml docker/ buildspec.yml
git commit -m 'Set up docker image, create ECR repository, create pipeline'

Then push to your branch

git push ...

Log in to the AWS Console, switch to the CI account and trigger your pipeline.