Automating Github with Glitch
Customising your build process
Here's the story: After a year in beta, @Glitch opens up today, ready for you to build and launch real, live apps. And the app that runs http://glitch.com itself is now open source, so you can directly help shape Glitch's features and future.
@anildash
On this, the day after @anildash's announcement, I thought I'd share how we've been using Glitch to automate Github, and personalise our own team workflow.
tl;dr Glitch is a great webhook platform - it's like IFTTT for developers, and when combined with Github's API can automate almost any git workflow you can think of.
We use gitflow (with some modifications) as our git workflow in combination with Github pull requests, and Jira for tickets. Whilst this combination works well enough, without any automation there are a number of steps that require manual intervention, and are prone to errors:
Incorrect merges - with gitflow a hotfix should be branched from and merged into master (as well as dev, more on that later) - it's easy to forget;
Pull request titles - by convention we prefix PR titles with the Jira ticket number -
YJP-123: Implement feature foo
;Labels - we like to label hotfix PRs;
Reviews - we insist on two approvals per PR - something that GH does not natively support.
Using Glitch as a webhook handler, we have now automated all of the above. We have a series of Github status checks (like the CI build checks you see) that enforce all of the rules, flag any errors, and apply labels. In addition, it will automatically open and maintain a Release PR, so that we can always see what is ready to deploy. I built the initial handler myself, starting from a JS knowledge of (effectively) zero - and it was a great developer experience. (Pls don't judge me on the code - as I said, I don't really know anything about JS.)
This is the handler that enforces two reviews:
// Check the number of approvals on the PR, and respond with an appropriate
// status ('pending' if not enough, 'success' if there are) and message.
app.post('/approval_check', (req, res) => {
let status;
let pr = req.body.pull_request;
if (utils.isRelease(pr)){
status = "No approvals required for a release";
}
else {
github.countApprovals(pr).then(approval_count => {
if (approval_count === 0){
status = github.sendStatusUpdate(pr, "approvals", "pending", "Requires at least two approvals");
}
else if (approval_count === 1){
status = github.sendStatusUpdate(pr, "approvals", "pending", "Requires one more approval");
}
else {
status = github.sendStatusUpdate(pr, "approvals", "success", approval_count + " approvals received");
}
});
}
res.send(status);
});
The Github call to update the status is:
// send the PR status check update
sendStatusUpdate = async function(pull_request, context, status, description){
let retval = status + " on '" + pull_request.title + "': " + description;
console.log(retval);
// https://octokit.github.io/rest.js/#api-Repos-createStatus
let response = await octokit.repos.createStatus({
owner: pull_request.head.repo.owner.login,
repo: pull_request.head.repo.name,
sha: pull_request.head.sha,
state: status,
description: description,
context: "Junobot (" + context + ")"
});
return retval;
}
And the output looks like this:
In addition, we can now automatically create a Release PR (dev to master), so that we always have an open release ready to merge / deploy - that details the list of all the constituent Jira tickets, and also has the labels from all the merged branches applied, so we can see if a release requires database migrations, feature flags, etc.
// If this is a merge event, then update the release PR.
if (req.body.action === "closed" && pr.merged_at){
github.getRelease(pr.head.repo).then(release => {
// NB this will add all labels, so if you still
// have WIP or DNM, these will be added to the release!
github.addLabels(release, github.getLabels(pr));
});
}
All of this is created automatically on our behalf:
Emboldened by the relative success of the above, I decided to have a go at automating the merge itself - we like to squash
feature and hotfix branch merges, so that we have a clean dev, and devs can easily forget this. In addition, with gitflow it is necessary to merge a hotfix into master and dev at the same time. This is not something that Github supports, and is the one step in the process that devs still complete locally, rather than through the GH interface. Automating this completes the process.
// Merge a numbered pull request, using a specific method
// ('merge', 'squash'), and then merge back into dev if
// this is a hotfix
const mergePullRequest = async (pull_request, merge_method) => {
let repo = pull_request.base.repo;
return octokit.pullRequests.merge({
owner: repo.owner.login,
repo: repo.name,
number: pull_request.number,
merge_method: merge_method,
}).then(resp => {
// NB assuming that it merged OK
return resp.data.sha;
}).then(sha => {
if (utils.isHotfix(pull_request)){
mergeBranch(repo, "dev", sha, "Merge hotfix: " + pull_request.title);
}
return "OK";
}).catch(error => {
return "Error";
});
};
This update to Junobot has also involved a sinister change in personality:
We now have no option but to comply. I for one welcome our robot overlords.
The merging webhook is available for remixing here: https://glitch.com/edit/#!/bog-rifle
Making Freelance Work