Send commit status from CodePipeline to GitHub

Photo by tom coe on Unsplash

Categories

  • DevOps
  • Howto

GitHub is a great tool for developers to collaborate on projects. Using it just as a service for hosting the source code of a project is only the tip of the iceberg. GitHub provides lots of other great features to streamline the development process and support collaboration. It also provides a way to show information along with changes made to the source code. One of these bits of information is the CI status of a commit. For this, GitHub provides the Commit Status API. It allows CI tools to notify GitHub whether a commit passed or failed a CI build. Using this information, the development workflow can be controlled.

This blog post shows how to integrate AWS CodePipeline with the GitHub Commit Status API. In previous blog posts CodePipeline has been used for publishing changes to Jekyll sites like this blog or driving a CI/CD pipeline for Serverless Applications based on the Serverless Application Model (SAM).

What are the building blocks?

Unfortunately, AWS CodePipeline does not provide out of the box support for pushing commit status information to GitHub yet. Fortunately, it is part of an ecosystem that provides a lot of extension points which allow such an integration to be build pretty easily.

The solution is based on the AWS services CloudWatch Events and Lambda. Whenever certain things happen during the execution of a pipeline CodePipeline sends events to CloudWatch, like the execution is started or an action fails. In CloudWatch Events rules can be set up to invoke a Lambda function for matching events. The Lambda function gets the event to act on them. In case of an integration with the GitHub Commit Status API some information can be send to the appropriate API endpoint provided by GitHub.

GitHub Commit Status Architecture Sketch

Let’s go through this step by step.

How does it work?

As mentioned before, CodePipeline sends events about state changes to CloudWatch Events. For example, when the execution of the pipeline some-pipeline starts the following event is send:

{
  "version": "0",
  "id": "CWE-event-id",
  "detail-type": "CodePipeline Pipeline Execution State Change",
  "source": "aws.codepipeline",
  "account": "123456789012",
  "time": "2017-04-22T03:31:47Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:codepipeline:us-east-1:123456789012:pipeline:some-pipeline"
  ],
  "detail": {
    "pipeline": "some-pipeline",
    "version": "1",
    "state": "STARTED",
    "execution-id": "01234567-0123-0123-0123-012345678901"
  }
}

Basically, the events for successful or failed pipeline executions differ only in the value for the state attribute.

For our use case we are only interested in getting notified when the execution of a pipeline starts, succeeds or finishes. To filter for these kinds of events, a CloudWatch Event Rule must be created which matches these events. The rule looks like this:

{
  "detail-type": [
    "CodePipeline Pipeline Execution State Change"
  ],
  "source": [
    "aws.codepipeline"
  ],
  "detail": {
    "state": [
      "STARTED",
      "SUCCEEDED",
      "FAILED"
    ]
  }
}

When the Lambda function is triggered it gets the event shown above as the input. From this event it extracts some information, like the pipeline that has send the event. The function then gets information about the execution of the pipeline, like the commit ID and the owner and name of the GitHub repository. It then sends the appropriate information to the GitHub Commit Status API. The Lambda handler looks like this:

exports.handler = async (event) => {
  const region = event.region;
  const pipelineName = event.detail.pipeline;
  const executionId = event.detail['execution-id'];
  const state = transformState(event.detail.state);

  if (state === null) {
    return;
  }

  const result = await this.getPipelineExecution(pipelineName, executionId);
  const payload = createPayload(pipelineName, region, state);
  await this.postStatusToGitHub(result.owner, result.repository, result.sha, payload);

  return null;
};

The detailed implementation of the functions the handler calls can be found in the GitHub repository.

What can it be used for?

With this solution deployed to an AWS region in which AWS CodePipeline pipelines are set up the CI status of a commit is automatically pushed to GitHub. This can be used for various things.

First, the commit status is immediately visible to project members in the commit history of a project. Green checkmarks symbolize successful builds whereas red crosses represent failed ones. Additionally, orange dots show that a build is currently performed. This helps to figure out when a commit did break a CI build. Clicking on the icon provides some more details and contains a link to the CodeBuild project to directly jump there, e.g. to looks into the logs.

Commit Status in GitHub

On the other hand, the commit status can be used in the development workflow to make it more secure. In GitHub the master branch can be configured as a Protected Branch. It is protected from commits and merges that do not adhere to the development workflow, e.g. have not been reviewed. One such rule is that the CI build needs to succeed. This acts like a safety net to ensure that only changes are merged into the master branch if a general set of controls are validated, like all unit tests are succeeding.

Protected Branch Check

Summary

This blog showed how easy it is to integrate the CodePipeline services offered by AWS with the Commit Status feature provided by GitHub. The actual costs for using this integration can basically be neglected since the AWS services used for this solution are either free or are probably covered via the free tier.

The code of this project can be found on GitHub. With this it should already be pretty easy to get started. Of course, this only is a very basic implementation. There’s probably lots of room for improvement. But it provides a good starting point.