Pull request pipeline in Jenkins

Phill Denness
3 min readJun 30, 2021

How many commands do you routinely perform during code review?

Here are my common commands:

  • git checkout branch_name
  • ./gradlew clean test
  • ./gradlew clean integrationTest
  • ./gradlew sonarqube

Four commands per code-review. Lets say 1 code-review a day per developer. That is 100 actions per week for my team. Let’s automate the testing and sonar (3 out of 4). Over a year that is 3900 less actions and it takes only half a day to get a PR pipeline running :)

The Pull Request pipeline will block merging of a PR until all the checks are complete.

Jenkins setup

Download Jenkins plugins:

  1. lockable-resources
  2. credentials
  3. workflow-multibranch
  4. github-branch-source

Next create a New Item in Jenkins

  1. Click ‘Multibranch Pipeline’ and give it a name

2. Now you need to add a branch source. This is the repository Jenkins will scan for open Pull Requests:

  • Click Add Source; Scan credentials must be added to Jenkins and have access to the repository.
  • Select the repository from the dropdown list.
  • Property Strategy: All branches get the same properties

3. That’s it, click Save.

Create a pipeline

Next we will tell Jenkins what commands to run against the branch. In my case I want to automate the following:

  • ./gradlew clean test
  • ./gradlew clean integrationTest
  • ./gradlew clean build sonarqube

Let’s start by creating a new file called ‘Jenkinsfile’ in the root repository. The file structure should look similar to this:

Copy + paste the this into Jenkinsfile.

pipeline {    
agent { label 'sit || stg' }
options {
buildDiscarder(logRotator(numToKeepStr:'3'))
}
environment {
JAVA_HOME = '/etc/alternatives/java_sdk_11'
}
stages {
stage('Kill previous job if running') {
steps {
milestone label: '', ordinal: Integer.parseInt(env.BUILD_ID) - 1
milestone label: '', ordinal: Integer.parseInt(env.BUILD_ID)
}
}
stage('Unit Test') {
steps {
sh "./gradlew clean test"
}
}
stage('Integration Test') {
steps {
sh "./gradlew clean integrationTest"
}
}
stage('SonarQube') {
steps {
withSonarQubeEnv('dev') {
sh "./gradlew clean build jacocoTestReport"
}
}
}
}
}

This is a declarative pipeline which is read by the Jenkins job we created. After a pull request is created the branch is automatically ‘checked out’ on the Jenkins machine and the Jenkinsfile is executed against the branch.

The agent tag describes the Jenkins node to run on

agent { label 'sit || stg' }

The buildDiscarder will discard old builds for the pull-request. For example, if a new commit is detected on the branch the pipeline is ran and recorded. The below config will keep the 3 most recent builds.

options {        
buildDiscarder(logRotator(numToKeepStr:'3'))
}

Environment variables

environment {
JAVA_HOME = '/etc/alternatives/java_sdk_11'
}

Eventually we define ‘stages’. Each stage will perform a check against the branch and fail the build if it fails.

The first check should be to make sure there isn’t already a build running for PR. Imagine the pipeline takes 5 minutes to complete, and you push 2 commits within 5 minutes of each other. We want to immediately cancel the previous pipeline and start testing the most recent commit.

stage('Kill previous job if running') {
steps {
milestone label: '', ordinal: Integer.parseInt(env.BUILD_ID) - 1
milestone label: '', ordinal: Integer.parseInt(env.BUILD_ID)
}
}

Next up, unit tests.

The order of checks will depend on your preference because subsequent checks will be ignored after a failure.

stage('Unit Test') {
steps {
sh "./gradlew clean test"
}
}

Then integration test

stage('Integration Test') {
steps {
sh "./gradlew clean integrationTest"
}
}

Finally, sonar analysis. Note this requires the sonar plugin installed onJenkins

stage('SonarQube') {
steps {
withSonarQubeEnv('dev') {
sh "./gradlew clean build jacocoTestReport"
}
}
}

Finally, ensure the Jenkinsfile is on the main branch. All future pull requests will be tested automatically.

--

--