Comprehensive Guide for SonarQube with Quality Gate for Jenkins

Posted by {"name"=>"Palash Ray", "email"=>"paawak@gmail.com", "url"=>"https://www.linkedin.com/in/palash-ray/"} on January 28, 2018 · 9 mins read

Configuring JaCoCo code coverage Maven plugin


				org.jacoco
				jacoco-maven-plugin
				0.7.9
				
					
						com/swayam/demo/springbootdemo/rest/**
					
					
						com/swayam/demo/springbootdemo/rest/config/**
						com/swayam/demo/springbootdemo/rest/entity/**
						**/RestFulMicroserviceApplication.class
					
				
				
					
						prepare-agent
						
							${jacoco.test.unit.dataFile}
							surefireArgLine
						
						
							prepare-agent
						
					
					
						post-unit-test
						test
						
							report
						
						
							${jacoco.test.unit.dataFile}
							target/jacoco/site-unit-test
						
					
				
			

Including or excluding packages or class

It is a good practice to include only project-specific classes. Otherwise, it tends to have the coverage for 3rd party libraries as well. The way to include/exclude a package is to name it like: com/swayam/demo/springbootdemo/rest/config/**
For including a single class: **/RestFulMicroserviceApplication.class

Prepare Agent

First, we would need to prepare the JaCoCo Agent for instrumentation:
http://www.jacoco.org/jacoco/trunk/doc/prepare-agent-mojo.html
This would set a property having the agent details, which is then passed o to the surefire plugin so that the tests are run with this agent. This is how the surefire plugin is configured:

			
				org.apache.maven.plugins
				maven-surefire-plugin
				
					
					${surefireArgLine}
				
			

Generating Report

After the Junit Tests are run, we can specify the directory for generating the coverage report:
http://www.jacoco.org/jacoco/trunk/doc/report-mojo.html

Configuring Sonar Maven plugin

SonarQube’s documentation for configuring a Maven plugin is scant, misleading and difficult to decipher. Its high time that they start hiring some good Tech Writer!
https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner+for+Maven
This is how the plugin is configured in your pom.xml. No global setting bullshit in settings.xml. Whoever suggested that is an idiot! The global settings.xml should kept as pristine as possible.


						org.sonarsource.scanner.maven
						sonar-maven-plugin
						3.4.0.905
						
							
								
									sonar
								
							
						
					
				

Apart from this, SonarQube also expects certain properties to be set.
https://docs.sonarqube.org/display/SONAR/Analysis+Parameters
It is recommended to pass this from the Maven plugin. I have included this under the sonar profile as below:


			sonar
			
				${project.artifactId}
				${project.artifactId}
				${project.version}
				http://192.168.1.4:9000/
	true
				true
				
				
				src/main/java
				
					**/com/swayam/demo/springbootdemo/rest/config/**,
					**/com/swayam/demo/springbootdemo/rest/entity/**,
					**/RestFulMicroserviceApplication.java
				
				UTF-8
				java
				src/test/java
				reuseReports
				target/surefire-reports
				target/classes
				${jacoco.test.unit.dataFile}
				jacoco
			
			...
		

Including or excluding packages or class

Remember that Sonar works on Java source code. The way to include/exclude a package is to name it like: **/com/swayam/demo/springbootdemo/rest/config/**
Similarly, you can exclude individual class like this: **/RestFulMicroserviceApplication.java

Running SonarQube Analysis and Fetching the results of QualityGate

Run the Maven command:

mvn clean install sonar:sonar -P sonar

This will create the file: target/sonar/report-task.txt
There are the below 2 urls that has to be read from here:
1. serverUrl=http://192.168.1.4:9000
2. ceTaskUrl=http://192.168.1.4:9000/api/ce/task?id=AWE3eRSZAEMe8tTgpicn
Read the response from the ceTaskUrl using curl, and save it to a file ceTask.json:

curl http://192.168.1.4:9000/api/ce/task?id=AWE3eRSZAEMe8tTgpicn -o ceTask.json

The element we are interested in is task.analysisId:
"analysisId": "AWE3eRcyxJqMzJgr501D"
We need to read the response of the url: $serverUrl/api/qualitygates/project_status?analysisId=$analysisId

curl http://192.168.1.4:9000/api/qualitygates/project_status?analysisId=AWE3eRcyxJqMzJgr501D -o qualityGate.json

If the Json value projectStatus.status is ERROR, the project has failed QualityGate.

Integration with Jenkins

To start with, we would need to install the below Jenkins plugins:

Sonar Quality Gates Plugin

https://plugins.jenkins.io/sonar-quality-gates
https://github.com/arkanjoms/sonar-quality-gates-plugin/blob/master/README.md

SonarQube Scanner for Jenkins

https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner+for+Jenkins

Specifying Sonar Qube Installation

We would need to tell Jenkins about our SonarQube installation. This is done by logging into Jenkins and then navigating to Manage Jenkins -> Configure System

Step 1

Step 2

Click on Add Sonar instance and give a unique name: MySonarQubeLocal. This would be used later for Jenkins Pipeline Project.

Integration with Jenkins Freestyle Project

Go to the Build section -> Add build step -> Execute SonarQube Scanner

You can specify the sonar properties either as a separate file in the section Path to project properties or in the section Analysis properties. Both of these approaches are equally bad. Instead, the properties should be passed through a Maven plugin in the build step as shown above:

clean install sonar:sonar -P sonar

In the Jenkins Freestyle Project, thats all the configuration you need.

Integration with Jenkins Pipeline Project

The SonarQube Scanner plugin has support for Jenkins Pipeline. It can be used as follows:

withSonarQubeEnv('MySonarQubeLocal') {
sh "/var/jenkins_home/apache-maven-3.5.0/bin/mvn clean install sonar:sonar -P sonar"
}

To determine whether the project has passed the QualityGate, we need to use the logic stated in the section Running SonarQube Analysis and Fetching the results of QualityGate. Since we are using Groovy DSL, it is very easy for us to do this. I am pasting the full content of the Jenkinsfile:

pipeline {
    agent {
		label 'master'
    }
    stages {
        stage('Build') {
            steps {
                withSonarQubeEnv('MySonarQubeLocal') {
                	dir('restful-microservice') {
	                    sh "/var/jenkins_home/apache-maven-3.5.0/bin/mvn clean install sonar:sonar -P sonar"
	                    script {
	                        def sonarProps = readFile encoding: 'utf-8', file: 'target/sonar/report-task.txt'
	                        echo "sonarProps: " + sonarProps
	                        def ceTaskUrl = 'ceTaskUrl'
	                        sonarProps.split('\n').each { line ->
	                            if (line.startsWith(ceTaskUrl)) {
	                                 env.SONAR_CE_TASK_URL = line.substring(ceTaskUrl.length() + 1)
	                                 echo "env.SONAR_CE_TASK_URL: " + env.SONAR_CE_TASK_URL
	                            }
	                            if (line.startsWith('serverUrl')) {
	                            	 def sonarServerUrl = line.split('=')[1]
	                            	 if (!sonarServerUrl.endsWith('/')) {
	                            	      sonarServerUrl += '/'
	                            	 }
	                                 env.SONAR_SERVER_URL = sonarServerUrl
	                                 echo "env.SONAR_SERVER_URL: " + env.SONAR_SERVER_URL
	                            }
	                        }
	                    }
	                }
                }
            }
        }
        stage('Quality Gate') {
            steps {
            	dir('restful-microservice') {
	                script {
	                    sleep time: 3000, unit: 'MILLISECONDS'
	                    timeout(time: 1, unit: 'MINUTES') {
	                        waitUntil {
	                            def jsonOutputFile = 'target/sonar/ceTask.json'
	                            sh 'curl $SONAR_CE_TASK_URL -o ' + jsonOutputFile
	                            def jsonOutputFileContents = readFile encoding: 'utf-8', file: jsonOutputFile
	                            def ceTask = new groovy.json.JsonSlurper().parseText(jsonOutputFileContents)
	                            env.SONAR_ANALYSIS_ID = ceTask['task']['analysisId']
	                            return 'SUCCESS'.equals(ceTask['task']['status'])
	                        }
	                        def qualityGateUrl = env.SONAR_SERVER_URL + 'api/qualitygates/project_status?analysisId=' + env.SONAR_ANALYSIS_ID
	                        echo "qualityGateUrl: " + qualityGateUrl
	                        def qualityGateJsonFile = 'target/sonar/qualityGate.json'
	                        sh 'curl ' + qualityGateUrl + ' -o ' + qualityGateJsonFile
	                        def qualityGateJsonFileContents = readFile encoding: 'utf-8', file: qualityGateJsonFile
	                        def qualityGateJson = new groovy.json.JsonSlurper().parseText(qualityGateJsonFileContents)
	                        echo 'qualityGateJson: ' + qualityGateJson
	                        if ("ERROR".equals(qualityGateJson['projectStatus']['status'])) {
	                               error "Quality Gate Failure"
	                        }
	                        echo "Quality Gate Success"
	                    }
	                }
                }
            }
        }
        stage('Code Coverage') {
			steps {
				jacoco()
			}
        }
    }
}

Appendix

Sample report-task.txt

projectKey=spring-boot-demo
serverUrl=http://192.168.1.4:9000
serverVersion=6.7.0.33306
dashboardUrl=http://192.168.1.4:9000/dashboard/index/spring-boot-demo
ceTaskId=AWBlpOuTAmeRAKOg4E_E
ceTaskUrl=http://192.168.1.4:9000/api/ce/task?id=AWBlpOuTAmeRAKOg4E_E

Sample ceTask.json

{
	"task": {
		"id": "AWBlpOuTAmeRAKOg4E_E",
		"type": "REPORT",
		"componentId": "AWBg3FFS8C0jOgq5kO1m",
		"componentKey": "spring-boot-demo",
		"componentName": "spring-boot-demo",
		"componentQualifier": "TRK",
		"analysisId": "AWBlpPPI5a3_SsAmFvXW",
		"status": "SUCCESS",
		"submittedAt": "2017-12-17T18:03:15+0000",
		"submitterLogin": "admin",
		"startedAt": "2017-12-17T18:03:17+0000",
		"executedAt": "2017-12-17T18:03:18+0000",
		"executionTimeMs": 1169,
		"logs": false,
		"hasScannerContext": true,
		"organization": "default-organization"
	}
}

Sample qualityGate.json for PASS

{
	"projectStatus": {
		"status": "OK",
		"conditions": [
			{
				"status": "OK",
				"metricKey": "new_security_rating",
				"comparator": "GT",
				"periodIndex": 1,
				"errorThreshold": "1",
				"actualValue": "1"
			},
			{
				"status": "OK",
				"metricKey": "new_reliability_rating",
				"comparator": "GT",
				"periodIndex": 1,
				"errorThreshold": "1",
				"actualValue": "1"
			},
			{
				"status": "OK",
				"metricKey": "new_maintainability_rating",
				"comparator": "GT",
				"periodIndex": 1,
				"errorThreshold": "1",
				"actualValue": "1"
			},
			{
				"status": "OK",
				"metricKey": "new_duplicated_lines_density",
				"comparator": "GT",
				"periodIndex": 1,
				"errorThreshold": "3",
				"actualValue": "0.0"
			}
		],
		"periods": [
			{
				"index": 1,
				"mode": "previous_version",
				"date": "2017-12-16T19:50:29+0000"
			}
		],
		"ignoredConditions": false
	}
}

 

Sample qualityGate.json for FAIL

{
	"projectStatus": {
		"status": "ERROR",
		"conditions": [
			{
				"status": "OK",
				"metricKey": "new_security_rating",
				"comparator": "GT",
				"periodIndex": 1,
				"errorThreshold": "1",
				"actualValue": "1"
			},
			{
				"status": "OK",
				"metricKey": "new_reliability_rating",
				"comparator": "GT",
				"periodIndex": 1,
				"errorThreshold": "1",
				"actualValue": "1"
			},
			{
				"status": "OK",
				"metricKey": "new_maintainability_rating",
				"comparator": "GT",
				"periodIndex": 1,
				"errorThreshold": "1",
				"actualValue": "1"
			},
			{
				"status": "ERROR",
				"metricKey": "coverage",
				"comparator": "LT",
				"errorThreshold": "80",
				"actualValue": "12.6"
			}
		],
		"periods": [
			{
				"index": 1,
				"mode": "previous_version",
				"date": "2017-12-31T18:00:19+0000"
			}
		],
		"ignoredConditions": false
	}
}

 

Sources

The complete sources can be found here: https://github.com/paawak/spring-boot-demo/tree/master/restful-microservice