Introduction

Hello everyone! In this post, we will see how we can configure Jenkins to automatically trigger a Jenkins build job, whenever a Pull Request is merged on Github. To follow along with this tutorial make sure that you have an initial setup of Jenkins running and a Github repository to test it out. When I started searching on how to do this, I was not able to find many useful resources. Hence, I thought to make a post.

Why you need this - Github Workflow

I like to follow the Github workflow which is followed in most companies. The recommended workflow is to create a separate branch if you are working on a feature/hot-fix and then merge the branch with the master branch at the end. In this post, I have explained how I use the same workflow for creating or editing blog posts for my site.

Github Workflow

The below sequence diagram explains the workflow I follow.

Sequence Diagram Hugo workflow

Github Webhook - Limitations

Webhooks allows you to set up an automated integration workflow with Github or other OAuth applications. Web-hook subscribes to Github events. So, whenever a particular event occurs(say commit, PR, etc.), Github webhook will send a POST request to the webhook URL with some payload. Payload data will contain all the details about the event. More about Github webhooks here and here.

Example Payload Data

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
POST /payload HTTP/1.1
Host: localhost:4567
X-GitHub-Delivery: 72d3162e-cc78-11e3-81ab-4c9367dc0958
X-Hub-Signature: sha1=7d38cdd689735b008b3c702edd92eea23791c5f6
User-Agent: GitHub-Hookshot/044aadd
Content-Type: application/json
Content-Length: 6615
X-GitHub-Event: issues
{
  "action": "opened",
  "issue": {
    "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
    "number": 1347,
    ...
  },
  "repository" : {
    "id": 1296269,
    "full_name": "octocat/Hello-World",
    "owner": {
      "login": "octocat",
      "id": 1,
      ...
    },
    ...
  },
  "sender": {
    "login": "octocat",
    "id": 1,
    ...
  }
}

To know about all the types of events available, you can check here.

The event in which we are interested is the “pull request” event, which handles all the events related to pull request. More info here.

Event Description
pull-request Triggered when a pull request is assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, ready_for_review, locked, unlocked or when a pull request review is requested or removed.

There are multiple sub-events inside the pull request event like edited, created, merged, etc. The idea here is to configure a normal Github webhook for pull request event. Once the event occurs, Github sends the payload to Jenkins, which is then parsed and necessary actions are taken.

Jenkins Plugin - Generic Webhook Trigger

I found this useful Generic Webhook Trigger plugin in Jenkins which helps in parsing the contents of any HTTP request into JSON/XML. It also allows to save the parsed contents inside variables and send them to Jenkins jobs.

We are going to use this plugin to parse the contents of the Github webhook payload and then trigger Jenkins jobs if the pull request even is a “merged” event. We do nothing/skip if it is any other type of subevent.

Setting Up Jenkins and Github Web-hook

  • Installing the plugin

The plugin can be downloaded and installed from the inbuilt plugins marketplace available on the Jenkins. You can search for “Generic Webhook Trigger Plugin” and then install the latest version.

  • Set up the plugin

Once the plugin is installed, you will be able to see additional options while configuring the build. Our goal here is to parse the JSON contents and then check for the event type and then trigger job if it is a pull-request event and the status is merged.

The JSON data will look something like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  {
    "action": "opened",
    "number": 2,
    "pull_request": {
      "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2",
      "id": 279147437,  
      "merged": false,
      "mergeable": null,
      "rebaseable": null,
      "mergeable_state": "unknown",
      "merged_by": null,
      "comments": 0,
      "review_comments": 0,
      "maintainer_can_modify": false,
      "commits": 1,
      "additions": 1,
      "deletions": 1,
      "changed_files": 1
        ......
    },
    "repository": {
      "id": 186853002,
      ........
    },
    "sender": {
      "login": "Codertocat",
     .......
    }
  }

The two values which we need to extract from the above JSON data are “action” and then the key “merged” inside “pull_request”. JsonPath syntax is used to extract the values from the JSON data. More info on how to use JsonPath here.

Extracting “action”:

Github-webhook-params-config-1

Extracting “merged”:

Github-webhook-params-config-1

Setup Secret:

We can also add a secret token for additional security purposes. Only if this secret key is received in the payload, we parse the data. Else, the data can be ignored.

Github-webhook-params-config-1

  • Setup Github Webhook Now set up the Github webhook from the settings section of your repository.

Github-webhook-params-config-1

Make sure to send the secret token which you have set up earlier in the webhook URL(invoke?token=mySecretToken).

Also, set up the trigger event to pull request.

Github-webhook-params-config-2

  • Configure the pipeline

Now, we need to configure the pipeline/build job to access the variables from the Jenkins script. Only on certain a condition, we need to execute the build.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  pipeline {
      agent any
      stages {
          stage('Check Environment') {
              when {
                  expression { return params.current_status == "closed" && params.merged == true }
              }
              steps {
                  build 'Environment Check'
              }
          }
          stage('Download Private Repo') {
              when {
                  expression { return params.current_status == "closed" && params.merged == true }
              }
              steps {
                  build 'Download Private Repo'
              }
          }
          stage('Generate Public Folder') {
              when {
                  expression { return params.current_status == "closed" && params.merged == true }
              }
              steps {
                  build 'Generate Public Folder'
              }
          }
          stage('Move Public Folder') {
              when {
                  expression { return params.current_status == "closed" && params.merged == true }
              }
              steps {
                  build 'Move Public Folder'
              }
          }
          stage('Change Security Permissions') {
              when {
                  expression { return params.current_status == "closed" && params.merged == true }
              }
              steps {
                  build 'Change Security Permissions'
              }
          }
      }
  }

In the above pipeline definition script, we are checking if the parameter current_status is closed and merged is set to true. Else the Build step will be skipped.

Testing the plugin

Now, if you have followed everything from the previous section, then the build should happen successfully when a new pull-request is opened on Github. To test our plugin, we can’t open a new pull request every time from Github and then merge it. Instead, we can also pass the parameters manually by using build with parameters in Jenkins. This can be done by changing the job as a parameterized job.

Github-webhook-params-config-1

After configuring the job as parameterized, then you can manually build the job by passing the parameters to the job.

Github-webhook-params-config-1

Overall Result

Now that we have completely configured the plugin and GitHub, we can check the results. I am sharing a sample result for my website pipeline build jobs below.

Github-webhook-params-config-1

In the above image, you can see the pipeline builds history. Now first, let’s take a look at build 44. Build 44 got triggered automatically when the pull-request was initially created. But, since it is not a merge event, none of the steps inside the build was executed.

Github-webhook-params-config-1

From the console log, we can see the parameters which we have received(current_status is opened and merged is false). So, each of the build steps is skipped in the job.

Now, let us take another pipeline job build 45.

Github-webhook-params-config-1

Build 45 got triggered while the pull-request got merged. After the pull-request got merged, GitHub triggers the webhook with the payload. Since we have received current_status as closed and merged as True, all the steps in the build were executed. Console logs confirm the same.

Conclusion

Hope you found this helpful. It might seem complicated to this with the plugin at first. But once everything is configured then there is not less manual intervention in the workflow. I was entirely new to using Jenkins and it was a good learning process for me. If you found a better way to do this/have any suggests or queries or comments, feel free to add your comment.

References

  1. https://wiki.jenkins.io/display/JENKINS/Generic+Webhook+Trigger+Plugin
  2. https://bjurr.com/jenkins-integration-on-steroids/
  3. https://stackoverflow.com/questions/31407332/how-to-process-a-github-webhook-payload-in-jenkins