· Péhápkaři Prague meetup #38 · Prague · given in Czech

Build your own GitHub bot

Jak si udělat vlastního GitHub otroka. A bot that watches your pushes, fixes what it can, and sends you a pull request. The fix is the easy part. Cloning, committing and pushing as a bot is where it gets interesting.

The boring loop

You push, CI fails on the coding standard, you run the fixer, you push again:

vendor/bin/ecs check src app tests --fix

The slide shows that line written over and over on a school chalkboard, which is how it feels. Commit and push, error, fix, commit and push. The fix is a single command that a machine can run perfectly well, so why should a person do it?

My demo bot did exactly that. After a push it ran the fixers, for example replacing a fully qualified class name with the short imported one, and opened a pull request with the changes against master, with an overly devoted title along the lines of “Applied coding standards fixes for my dear master”.

Note that the bot doesn’t commit and push to your branch. It sends a pull request instead, and you decide whether to merge it.

What the bot has to do

  1. git clone
  2. create a new branch
  3. fix: run the command
  4. commit and push
  5. open a pull request

Running the command is the only step that’s the same as on your laptop. Everything else needs the bot to have an identity on GitHub and permission to act on someone else’s repository.

GitHub App, not OAuth App

GitHub has two kinds of integrations. An OAuth App acts on behalf of a user. A GitHub App is its own actor: it gets installed on an organization or repositories, asks for specific permissions, and shows up with a bot badge next to its name. For a bot, a GitHub App is the right choice.

Authentication has a few steps:

  1. A push webhook arrives. Its payload says which installation it came from.
  2. App auth: the app signs a JWT with its private key to prove it is the app.
  3. Installation auth: with that JWT it asks GitHub for a token for this one installation.
  4. It gets back an access token, limited to the repositories and permissions of that installation.

With the token, cloning is ordinary git over HTTPS:

https://x-access-token:%s@github.com/%s.git

The first %s is the installation token, the second is the repository name, like owner/repo.

Committing and pushing as a bot

So how do you commit and push as the bot? My answer was GitHub’s Git Data API, the low-level API that creates git objects directly on GitHub. To use it, you need to know how git stores things:

Diagram of git objects: HEAD points to a branch, the branch to a commit, the commit to a tree, and the tree to other trees and blobs. Older commits and the objects they share are greyed out.
A branch points to a commit, the commit to a tree, the tree to blobs, which hold file contents.

So a commit through the API is built bottom up:

  1. Blobs: upload the content of each changed file.
  2. Tree: create a tree that uses the new blobs, based on the existing tree.
  3. Commit: create a commit pointing to the new tree, with the current commit as its parent.
  4. Reference: create the new branch pointing to that commit.
  5. Pull request: open it from the new branch.

That way both the commit and the pull request come from the bot.

The whole flow, and a trap

Put together: push, webhook, auth, then commit, push and pull request. The last slide of the flow adds an arrow from the bot’s own push back to the webhook. The bot’s push is a push too, and it triggers the webhook again. Make sure the bot recognises its own work, or it will keep going round.

For testing webhooks I used Postman and ngrok, which gives your local server a public URL that GitHub can send webhooks to.

The surprise: Rector CI

The demo wasn’t just a toy. I was building the same thing for Rector, the tool that upgrades and refactors PHP code automatically, under the name Rector CI. You installed it as a GitHub App on your organization, with write access to code, read access to metadata, and read and write access to checks, pull requests and repository hooks. Then, on a push to a feature branch, it ran Rector and opened a pull request with the changes:

A GitHub pull request titled Rector - Fix #34, opened by the rector-ci bot to merge one commit from some-cool-feature-rectified into some-cool-feature. The bot comments: Automated pull request by Rector. The branch has no conflicts with the base branch.
A pull request from the Rector CI bot: the feature branch, rectified.
All 36 slides
  1. Slide 1: Title: Jak si udělat vlastního GitHub otroka, 20 June 2019
  2. Slide 2: Entrydo, GLAMI, Péhápkaři.cz, Rector
  3. Slide 3: Failed coding standard check and the fixer command written on a chalkboard
  4. Slide 4: Diff: fully qualified class names replaced with short ones
  5. Slide 5: Commit & push, error, fix, commit & push
  6. Slide 6: Robot hand reaching to a human hand
  7. Slide 7: The bot account
  8. Slide 8: A nagging couple
  9. Slide 9: Pull request from the bot: Applied coding standards fixes
  10. Slide 10: Fix, then commit & push
  11. Slide 11: Fix, then pull request instead of commit & push
  12. Slide 12: Brainstorm
  13. Slide 13: Steps: git clone, new branch, run the fix, commit & push, pull request
  14. Slide 14: Step: git clone
  15. Slide 15: Step: create a new branch
  16. Slide 16: Step: run the fix command
  17. Slide 17: Step: commit & push
  18. Slide 18: Step: send a pull request
  19. Slide 19: The hard steps highlighted: clone, branch, commit & push, pull request
  20. Slide 20: GitHub App vs OAuth App
  21. Slide 21: Authentication: push webhook, app auth (JWT), installation auth, access token
  22. Slide 22: Git clone URL with x-access-token
  23. Slide 23: How to commit & push as a bot?
  24. Slide 24: Git Data, the low-level API
  25. Slide 25: Diagram of git objects: branch, commit, tree, blob
  26. Slide 26: Git Data: blobs, tree, commit, reference, pull request
  27. Slide 27: Push, webhook, auth, commit & push & PR
  28. Slide 28: The flow with the bot's push highlighted
  29. Slide 29: The bot's push loops back to the webhook
  30. Slide 30: Webhooks testing: Postman and ngrok
  31. Slide 31: Kinder Surprise
  32. Slide 32: Install Rector CI GitHub App
  33. Slide 33: Pull request from the rector-ci bot
  34. Slide 34: getrector.org, rector-ci.org
  35. Slide 35: We want you!
  36. Slide 36: Thank you! Questions?

Related talks