I've been using Github for many years now and similarly tools like Travis. The commit status integrations always intrigued me. I kept seeing little things that I'd love to add a little Github integration. Recently I took a run at how they work, and it's actually relatively simple once I understood all the moving parts, but I hadn't seen it written down. So this is a guide to making your own Github integration.

What is a Github integration?

In this particular example, I'm talking about the services that (tend to) check your individual commits and show a visual cue on pull requests as to whether the repo owner should merge or not. This particular type of integration is a call a "commit status".

When a commit is pushed to Github, it's checked against a number of services, and this can indicate whether the commit is good or bad.

If you look closely you can see that there are green ticks and red cross by the latest commits in addition to the overall checks that are run against the pull request.

Here's a few examples of what an integration could do:

  1. Make sure the code passes all the tests
  2. Check that a maintainer signs off the pull request
  3. Ensures that every pull request is code reviewed
  4. Checks that individual comments use a commit syntax (for tools like semantic-release)
  5. Ensure that all pull requests include at least one emoji!
  6. …and much more 😃

How a commit status integration works

There are two main parts to creating these integrations:

  1. Your server listens for events from Github, via a webhook
  2. Calling Github's API with a commit status message and success or failure status

Then, in-between step 1 and 2, you can run whatever code you want to decide what message and status is sent back to Github.

Other tasks include:

  1. Creating a personal access token
  2. Releasing the code to a server (I'm going to use Glitch to host this example)
  3. Configuring a Github repo to send webhooks to our service

Let's make a Github integration

For this tutorial, I'm going to guide you through how to make an integration that will only go green when a contributor on the repository comments on a PR with a message containing LGTM or red (failure) if they comment with 👎.

I'll start by explaining the code required, then show you how to get the service up and running.

Start with a server

It makes things easier to start with a live URL that you can start pointing Github repositories at. There's two ways you can get going on this very quickly with minimal faff. The first (and what I'll be doing) is use glitch.com (you might want to remove some of the assets).

Alternatively you could run this code locally and use a tool like ngrok to create a public URL that points back to your machine. Of course, you could deploy with something like Zeit's now, Heroku and a mass of other products.

With Glitch, you'll have a URL right away (even if it doesn't do what you want), so I'll assume you've gotten that far. My URL is https://festive-peony.glitch.me/

Configuring a webhook

Assuming you have a Github repository that you want to use this project against (or make a test one like I did), then head over to Settings - Webhooks and click "Add webhook" (.../settings/hooks/new). Then enter your URL and (I'd recommend) adding a secret.

Before we add the webhook, we need to define the events that will trigger the hook. Select "Let me select individual events" and tick the following (at a minimum):

  • Issue comment
  • Pull request

Now you're ready to make the webhook.

creating a github webhook

Accept the the webhook

As soon as we created the webhook, a test payload was sent. It's possible the server responded with a 200 OK (certainly it will if you used Glitch). It's useful to know that you can go back to the newly created webhook and re-send payloads using the "Redeliver" button (we'll use this to help debug):

recent-payloads.png

Now we're ready to start handling the webhook payloads properly. I'm writing this using JavaScript and Node.js. You can use whatever flavour language you like, the logical process is pretty much the same (I would assume!).

There are two node libraries I'm relying on: githubhook (currently at 1.9.3) for handling and responding to the webhooks from Github, and github (currently at 12.1.0) to post the commit status messages against the Github API.

// filename: index.js
function handlePullRequest(repo, ref, data) {
  // this is where we'll post the git commit status message
  console.log('got webhook on %s', repo);
}

function listen(port = 8000, secret = 'a secret code') {
  const githubhook = require('githubhook');
  const hook = githubhook({
    port,
    // secret, // we'll uncomment later
    path: '/',
  });

  hook.on('pull_request', handlePullRequest);
  hook.listen();
}

listen(process.env.PORT, process.env.SECRET);

The PORT and SECRET are environment values that we'll need to set upon deploy. The secret is a value that you add to the webhook so that only your webhook is able to use this service. Note that for now, our secret is disabled to allow us to test.

I can now put this minimal code in Glitch (or any other node hosting environment) and when I hook my repo up against the production URL, I'll see the following in the logs after the first pull request lands:

POST / 127.0.0.1
received 29604 bytes from 127.0.0.1
got pull_request event on github-integration-testing from 127.0.0.1
got webhook on github-integration-testing

You can either create a real pull request on your repo, or you can run the following code in your terminal to test the webhook - remember to change the URL to your own hosted integration:

$ curl -X POST https://festive-peony.glitch.me/ \
     -H"$(curl -L https://git.io/vbuH7)" \
     -d$(curl -L https://git.io/vbuH5)

Explanation of the command above: I've put the payload and headers required to test the integration in a gist and then shortened the URLs. The curl above will POST to your URL, and use the result of vbuH7 as the headers, and the result of vbuH5 for the posted body (the payload).

Updating the commit status

Now that our server is accepting a webhook, next we need to do something with the webhook. Using the github project dependency, we'll create an API call to update the current commit status to pending.

Early on in the index.js file, the following code is added to authenticate and allow the code to call the Github API (I'll include the final version of the code next):

const github = new GitHubApi({
  version: '3.0.0',
  protocol: 'https',
  host: 'api.github.com',
  timeout: 5000,
  headers: {
    'user-agent': 'my first github integration',
  },
});

github.authenticate({
  type: 'oauth',
  token: process.env.TOKEN, // personal token
});

You'll need to create your own personal access token and check the following options: repo:status and public_repo (the public_repo is so that we can make API requests for comments on the pull requests).

Drafts may be incomplete or entirely abandoned, so please forgive me. If you find an issue with a draft, or would like to see me write about something specifically, please try raising an issue.