How to write a Github integration
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".
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:
- Make sure the code passes all the tests
- Check that a maintainer signs off the pull request
- Ensures that every pull request is code reviewed
- Checks that individual comments use a commit syntax (for tools like semantic-release)
- Ensure that all pull requests include at least one emoji!
- …and much more 😃
How a commit status integration works
There are two main parts to creating these integrations:
- Your server listens for events from Github, via a webhook
- 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:
- Creating a personal access token
- Releasing the code to a server (I'm going to use Glitch to host this example)
- 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.
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):
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.