AWS CodeCommit <> CodeBuild Pull Request CI Workflow

How to implement a Pull Request Workflow for CodeCommit and CodeBuild using Terraform to configure the AWS services, and Docker/Docker Compose to build our code and run tests.
AWS CodeCommit <> CodeBuild Pull Request CI Workflow

Overview

Running automated tests in a CI environment is an important step toward improving code quality, code standards and ensuring that only working code is merged into other environments.

With tools available in AWS, we have some low hanging fruit for teams to add some automated safeguards.

We’re going to walk through a Pull Request Workflow for CodeCommit and CodeBuild using Terraform to configure the AWS services, and Docker/Docker Compose to build our code and run tests.

Example Application

https://github.com/devato/aws-pull-request-workflow

Goals

  • Create/Update PR triggers build, reports results to codecommit.
  • CodeCommit PR requires CodeBuild approval.
  • Terraform for Infrastructure as Code.

Pre-Requisites

  • An existing CodeCommit repository.
  • An existing IAM user and roles allowing you to commmit code to the repository.
  • (Optional) An existing S3 bucket for storing Terraform State.
  • (Optional) Docker and Docker Compose running on your local machine.
  • Basic understanding of Docker and Docker Compose.

The Plan

With the pre-requisites and goals in mind, let’s take a look at a basic setup:

AWS CodeCommit CodeBuild PR Workflow

The main services we need to manage are:

  • CodeCommit
  • EventBridge (formerly Cloudwatch Events)
  • CodeBuild

Getting Started

My advice is to use our Example Application that includes the Terraform configuration to quickly get up and running.

Once you have a good handle on things work, it’ll be easier to translate the processes into your own projects.

Example Application

We have a basic Elixir Phoenix app that has some very simple tests that connect to a Postgres Database, all managed by Docker Compose.

Clone our example repo:

$ git clone [email protected]:devato/aws-pull-request-workflow.git

And test that the test process will run: (docker needs to be running)

$ docker-compose --env MIX_ENV=test run --rm test

The reason it’s important to run your tests locally is so you can understand how to debug your system to eliminate potential issues in CodeBuild.

Push to CodeCommit

Once you have the tests passing locally, configure git to point to your CodeCommit Repo:

(To simplify things, ensure your CodeCommit repo is named awsapp. Otherwise you’ll need to change the variables in .infrastructure/cicd/main.tf)

$ git remote add origin <https/ssh url for codecommit>
$ git push origin

NOTE: There are many factors to consider when configurating IAM users for CodeCommit which are outside the scope of this article.

If you have any issues getting your code into CodeCommit, please resolve them here first.

Prepare Terraform State Backend

The resources needed to automatically build and test your code will be created using “Infrastructure as Code” (IoC) via Terraform. You don’t really need experience with Terraform to complete the next steps, but like running local tests, understanding how the pieces fit together and work will help you debug any issues in AWS.

NOTE: If you would like to store Terraform state in S3, please ensure your bucket is created before starting.

If you’d rather skip remote state storage for this example, just remove the backend block from .infrastructure/cicd/main.tf:

terraform {
  required_version = ">= 0.12.4"

  # Comment this out to skip remote state storage
  backend "s3" {
    bucket = "awsapp-cicd-terraform-remote-state"
    key    = "cicd/awsapp/state"
    region = "us-east-1"
  }
}

Install Terraform & aws-cli

In order to allow Terraform to interact with AWS on your behalf, you’ll need to install and configure Terraform and the aws-cli.

Create AWS Credentials

Create AWS Credentials in the AWS Console and configure your local machine to use them.

NOTE: AWS Roles/Policies needed to manage the required resources are complex, so to keep things simple, just add Administrator privileges to your chosen user which can be fine-tuned at your convenience.

Configure aws-cli by entering the details:

$ aws configure

AWS Access Key ID [****************XXXX]:
AWS Secret Access Key [****************XXXX]:
Default region name [us-east-1]:
Default output format [json]:

Now your local machine is ready to interact with AWS using Terraform.

Initialize Terraform

The Terraform Infrastructure as Code is included in the Example Repo.

The commands will need to be executed from the .infrastructure/cicd directory.

$ cd .infrastructure/cicd
$ terraform init

Initializing modules...

...

Use terraform plan to review which resources will be created/changed:

$ terraform plan

Create Resources

If everything looks good in the terminal thus far, we should be good to create the required resources.

Open main.tf and update the account_id in the CodeBuild configuration module:

module "cicd_awsapp_codebuild" {
  ...
  account_id       = "XXXXXXXXXXXX" 
  ...
}

This is the Account ID beside in the Account dropdown found in the AWS Console

Now you can apply the config:

$ terraform apply

This will display a full plan, and ask you to verify the command:

...

Plan: 9 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

Enter yes and after a few seconds, you should see that plan was applied:

...

Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

This created:

  • EventBridge Rule that responds to pullRequestCreated and pullRequestSourceBranchUpdated events from CodeCommit.
  • CodeBuild project and role.
  • IAM Roles with the necessary permissions trigger builds, pull the code, and comment on the Pull Request.

You can explore these services in the AWS console, and because they’re created with Terraform, they’re tracked and stored in S3 (or locally) and can be easily updated or removed from your account.

With these resources in place we’re now able to automatically build our code on the events listed above.

Terraform File Structure

Explore the .infrasturcture directory to further understand the permissions and configuration for each AWS service.

  • There is a main.tf file that holds the config for our CI workflow.
  • This pulls in modules from the module directory.
  • Each module has it’s own set of variables and config.

We’re not going into too much detail about Terraform and different ways to organize IoC. So let’s continue with our Pull Request Workflow.

CodeCommit Approval Rule Template

One of our goals was to require CodeBuild approval before our Pull Requests can be merged.

At the time of writing CodeCommit Approval Templates are not yet available to Terraform, so we’ll have to create it manually.

In the AWS Console, search for CodeCommit then navigate to Approval rule templates.

AWS CodeCommit Approval Rule Template

Create a new template with the following details:

Name: awsapp Codebuild approval template

Description: Requires CodeBuild role approval.

Number of approvals needed: 1

Approver Type:

  • Select IAM user name or assumed role
  • Value: value of codebuild_role variable from main.tf followed by /* (awsapp-codebuild-role/*)

Associated Repositories: awsapp

Pull Request Workflow in Action

With all the ceremony out of the way, and now that we have a full understanding of how the AWS resources work together, let’s create a Pull Request and see everything in action.

Create a Pull Request

From your local machine, create a new branch and make some changes:

$ git checkout -b pull-request
$ touch trigger
$ git add .
$ git commit -am 'Trigger build'
$ git push origin

From the AWS Console, go to CodeCommit select your repo, and hit Create pull request, give it a title and create it.

If you now click on Build -> Build Projects, and select your build project:

AWS Build Projects

You should see your build project running:

AWS Build Project Running

You can click on the running build and follow the logs to see how it’s progressing.

CodeBuild Configuration

This CodeBuild instance is based on Linux and has Docker and Docker Compose pre-installed and uses aws/codebuild/standard:4.0 image.

Codebuild uses the file buildspec.yml as instructions for how to build/test your application.

Here are the contents in the Example Application:

# buildspec.yml
version: 0.2
env:
  variables:
    AWS_DEFAULT_REGION: "us-east-1"
    MIX_ENV: "test"
phases:
  install:
    commands:
      - BUILD_NAME=$(echo $CODEBUILD_BUILD_ID | cut -d':' -f1)
      - BUILD_ID=$(echo $CODEBUILD_BUILD_ID | cut -d':' -f2)
      - BUILD_URL=$(echo "https://$AWS_DEFAULT_REGION.console.aws.amazon.com/codesuite/codebuild/projects/$BUILD_NAME/build/$BUILD_NAME%3A$BUILD_ID")
  pre_build:
    commands:
      - echo "Revoking approval"
      - aws codecommit update-pull-request-approval-state --pull-request-id $PULL_REQUEST_ID --approval-state REVOKE --revision-id $REVISION_ID;
      - echo "Checkout code"
      - git checkout $SOURCE_COMMIT
  build:
    commands:
      - docker-compose --env MIX_ENV=test run --rm test
  post_build:
    commands:
      - |
        if [ $CODEBUILD_BUILD_SUCCEEDING = 1 ]; then
          aws codecommit update-pull-request-approval-state --pull-request-id $PULL_REQUEST_ID --approval-state APPROVE --revision-id $REVISION_ID;
          content="✔️  Pull request build SUCCEEDED! ![View Build]($BUILD_URL)"
        else
          content="❌ Pull request build FAILED ![View Build]($BUILD_URL)"
        fi
      - aws codecommit post-comment-for-pull-request --pull-request-id $PULL_REQUEST_ID --repository-name $REPOSITORY_NAME --before-commit-id $DESTINATION_COMMIT --after-commit-id $SOURCE_COMMIT --content "$content"

Buildspec Breakdown:

  • Sets some ENV variables
  • Runs through different phases:
  • install: Sets env variables needed by other phases.
  • pre_build: revokes any previously approvals made by the Codebuild role, and checks out code from Pull Request branch.
  • build: uses Docker Compose to stand up DB and run tests.
  • post_build: Conditionally approves Pull Request on success, then comments on Pull Request with either success or failure message.

This is the most basic workflow and a buildspec.yml file is entirely customizable.

Notice how aws-cli commands are available directly in the Build Image. This is enabled by the privileged variable in CodeBuild’s Terraform configuration.

Back to Pull Request Workflow

At this point we’re assuming that all systems are green. The build passes, approves the pull request and submits a comment with the build results.

You should see the comment with a link to the build in the Activity tab of your Pull Request:

AWS Codebuild Success

You’ll also notice that the Pull Request as been approved, and under Approvals the CodeBuild role should be listed:

AWS Codebuild Approval

Break the Build

Let’s test the failing build process by breaking a test and commiting it to our Pull Request branch:

Open test/aws_app_web/controllers/page_controller_test.exs

And change the response code in the test to 404:

defmodule AwsAppWeb.PageControllerTest do
  use AwsAppWeb.ConnCase

  test "GET /", %{conn: conn} do
    conn = get(conn, "/")
    assert html_response(conn, 404) =~ "Welcome to Phoenix!"
  end
end

Push the code to the pull-request branch in Codecommit.

When the build is complete, you should see the failure message on the Pull Requests Activity tab:

AWS Codebuild Break Build

You’ll also notice that the Pull Request is no longer Approved and the approval has been removed.

Wrapping Up

Let’s review our Goals:

  • ✔️ Create/Update PR triggers build, reports results to codecommit.
  • ✔️ CodeCommit PR requires CodeBuild approval.
  • ✔️ Terraform for Infrastructure as Code.

When teams of developers are creating Pull Requests and pushing lots of code, having your tests automatically run and approving pull requests is a great way to reduce the work needed for code review.

This has been the most basic example of a Pull Request workflow using Codecommit <> Codebuild and can serve as a starting point for implementing CI/CD for your projects.

Hit me up on twitter if you have any questions or find any issues with this workflow @tmartin8080

Troy Martin
Senior Engineer working with Elixir and JS.
Keep in the Loop

Subscribe for Updates