This all starts with an idea that I’d like to run unit tests whenever a pull request or a commit to a pull request is created.

I thought a bit to directly write a build server which listens for GitHub webhooks. However, I think it’s better to use an existing tool like Jenkins or AWS CodePipeline for visualizing, monitoring and easy-to-maintain.

I had a look at CodePipeline, but it only listens to changes of a branch instead of commits in a pull request like what TravisCI does. I probably can use API Gateway and Lambda to implement, but Jenkins seems to be a more sophisticated tool to learn.

Honestly, my first impression is that Jenkins’s UI is really Java-style (not looking very nice). I will use one of my previous repo in this demo, https://github.com/HengfengLi/pymapplot.

Workflow

The workflow should be as the following diagram:

Workflow

  1. Developers create a pull request and push local branch to GitHub.
  2. GitHub post a request to Jenkins’s webhook endpoint.
  3. Jenkins pulls latest code from GitHub to local directory.
  4. Jenkins builds or tests code.
  5. Jenkins informs GitHub about building/testing results.

Steps

1. Run Jenkins with docker

Run the following command:

docker run -p 8080:8080 -v {YourHostPath}/jenkins_home:/var/jenkins_home jenkins/jenkins

If you want to persist configurations and plugins, you need to map a volume from host to container /var/jenkins_home.

You will see a password in console and copy it in somewhere for next step.

2. Setup Jenkins

  1. Go to http://localhost:8080
  2. Input the saved password or you can find the password in /var/jenkins_home/secrets/initialAdminPassword
  3. Choose Install suggested plugins
  4. Create a new admin
  5. Go to Manage Jenkins
  6. Click Configure Global Security
  7. Choose Safe HTML in Markup Formatter dropdown menu (HTML will be displayed in Build History instead of plain text).

3. Install plugins

  1. Visit Manage Jenkins and then Manage Plugins
  2. Find GitHub Pull Request Builder plugin
  3. Install without restart

4. Configure Jenkins

  1. Visit Manage Jenkins and then Configure System
  2. Find GitHub Pull Request Builder section
  3. Input Jenkins’s url to Jenkins URL override field. I use ngrok to expose my local port for testing, so it is a url like https://XXXXXXXXX.ngrok.io
  4. Click Add button next to Credentials, then choose Jenkins
  5. In Kind, choose Secret text
  6. Input your GitHub access token (with repo and admin:repo_hook permissions) in Secret field
  7. Type jenkins-global in Description field (this is an option name).
  8. Click Add
  9. Update Description in GitHub Pull Request Builder section
  10. Add GitHub username into Admin list
  11. Save the changes

5. Configure GitHub webhooks

Add webhool url in GitHub repository

  1. Go the repository
  2. Click Settings, click Webhooks, add webhook
  3. Type https://XXXXXXXXX.ngrok.io/ghprbhook/ in Payload URL
  4. Choose application/x-www-form-urlencoded in Content type
  5. Choose Let me select individual events
  6. Choose Pull requests, Pushes, Issue comments
  7. Click Add webhook

Enable Jenkins GitHub plugin service

  1. Go the repository
  2. Click Settings, click Integrations & services
  3. Click Add service
  4. Choose Jenkins GitHub plugin
  5. Type https://XXXXXXXXX.ngrok.io/github-webhook/ in Jenkins hook url
  6. Click Add service

6. Create a project

  1. Go to Jenkins’s homepage
  2. Click New Item
  3. Type pull-request-demo in name
  4. Choose Freestyle project
  5. Click OK

7. Configure the project

General

  1. Tick GitHub project
  2. Type your project url like https://github.com/HengfengLi/pymapplot in Project url

Source Code Management

  1. Click Git
  2. Type https://github.com/HengfengLi/pymapplot.git in Repository URL
  3. Click Advanced
  4. Type origin in Name
  5. Type +refs/pull/*:refs/remotes/origin/pr/* in Refspec

Build Triggers

  1. Tick GitHub Pull Request Builder
  2. Tick Use github hooks for build triggering

Build

  1. Click Add build step
  2. Choose Execute shell
  3. Type the testing command python -m unittest discover tests

8. Build now

Workflow

  1. Go to the project homepage
  2. Click Build Now
  3. You can see a job has been scheduled (see above figure)
  4. You can also check the following console log:
Started by user hengfeng
Building in workspace /var/jenkins_home/workspace/pull-request-demo
Cloning the remote Git repository
Cloning repository https://github.com/HengfengLi/pymapplot.git
 > git init /var/jenkins_home/workspace/pull-request-demo # timeout=10
Fetching upstream changes from https://github.com/HengfengLi/pymapplot.git
 > git --version # timeout=10
 > git fetch --tags --progress https://github.com/HengfengLi/pymapplot.git +refs/heads/*:refs/remotes/origin/*
 > git config remote.origin.url https://github.com/HengfengLi/pymapplot.git # timeout=10
 > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
 > git config remote.origin.url https://github.com/HengfengLi/pymapplot.git # timeout=10
Fetching upstream changes from https://github.com/HengfengLi/pymapplot.git
 > git fetch --tags --progress https://github.com/HengfengLi/pymapplot.git +refs/pull/*:refs/remotes/origin/pr/*
 > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision 117a9d187255c126ad445ea19b21c63eb8a8c676 (refs/remotes/origin/master)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f 117a9d187255c126ad445ea19b21c63eb8a8c676
Commit message: "Merge pull request #5 from HengfengLi/feature/update-gitignore"
First time build. Skipping changelog.
[pull-request-demo] $ /bin/sh -xe /tmp/jenkins8196395077623670742.sh
+ python -m unittest discover tests
.
----------------------------------------------------------------------
Ran 1 test in 0.017s

OK
Finished: SUCCESS

9. Test pull requests

Workflow

  1. Check out a local branch
  2. Make some changes
  3. Push to a remote branch
  4. Make a pull request
  5. You will see a new job is scheduled
  6. You can open the pull request page and see the following changes:

Workflow Workflow

Summary

Yes, so many configurations. There are still many plugins and configurations that can be explored. This is just a “hello world” example.

There are some further questions:

  • How to ensure the security?
    • I’d like to ensure only corporate network can access the admin portal, but I also want to expose the webhook endpoints.
  • How to build docker images inside a Jenkins container?
  • How to deploy code to AWS ECS or Beanstalk?

It’s really nice to have such a small feature to check every commit of your pull request, adding lint checking and unit tests. This can really improve codebase’s quality and encourage to write more tests.

References

  1. http://www.inanzzz.com/index.php/post/ljgv/setup-github-and-jenkins-integration-for-pull-request-builder-and-merger
  2. https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin