How to get SonarQube qualitygate status from Gradle build

How to get SonarQube qualitygate status from Gradle build

sonarqube_qualitygate_status

Here in this article we will setup a SonarQube server using Vagrant and Ansible. We will be using a docker-compose to launch the SonarQube service. Once the SonarQube service is up and running we will be using a sample Java based project using Gradle build and try to analyze the java code using sonarqube. We will see how we can break a CI gradle build by checking the for projects qualitygate status.

Test Environment

Fedora 37 workstation
java11
vagrant 2.3.7

What is SonarQube

SonarQube is code review or code analysis tool which helps in delivering a clean code. It can be integrated with our existing CICD pipeline and detects issues in our code to help us perform continuous code inspections of your projects. The tool analyses 30+ different programming languages and integrates into your CI pipeline and DevOps platform to ensure that your code meets high-quality standards.

If you are interested in watching the video. Here is the YouTube video on the same step by step procedure outlined below.

Here is my project structure for SonarQube setup.

$ tree .
.
├── ansible
│   ├── linux_provision_sonar_server.yml
│   └── roles
│       ├── linux_docker_install_server
│       │   ├── tasks
│       │   │   └── main.yml
│       │   └── vars
│       │       └── main.yml
│       └── linux_sonar_install_server
│           ├── tasks
│           │   └── main.yml
│           ├── templates
│           │   ├── docker-compose.yml.j2
│           │   └── sysctl.conf.j2
│           └── vars
│               └── main.yml
├── docker-compose.yml
└── Vagrantfile

Procedure

Step1: SonarQube Server Vagrantfile

Here is my basic Vagrantfile which we will use to launch the vm and provision it using ansible provisioner. We are using libvirt as the virtualization provider here. The following ansible playbook “linux_provision_sonar_server.yml” will be executed to setup the SonarQube on this vm.

$ cat Vagrantfile 
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

    config.hostmanager.enabled = true
    config.hostmanager.manage_host = true
    config.hostmanager.manage_guest = true
  
    config.vm.define "sonar" do |sonar|
      sonar.vm.box = "fedora/38-cloud-base"
      sonar.vm.hostname = "fedsonar.stack.com"
  
      sonar.vm.synced_folder '.', '/vagrant', disabled: true
      sonar.vm.synced_folder ".", "/home/vagrant/sonar", type: "sshfs"
  
      sonar.vm.provider "libvirt" do |libvirt|
        libvirt.cpus = 2
        libvirt.memory = 2048
      end
  
      # Switch to "ansible_local" to install and use ansible on guest VM
  
      sonar.vm.provision "ansible" do |ansible|
        ansible.playbook = "ansible/linux_provision_sonar_server.yml"
        ansible.verbose = true
      end
    end
  end

Step2: Provision SonarQube Server

Here is my ansible playbook “linux_provision_sonar_server.yml” which executes two roles. First one “linux_docker_install_server” is to install and setup docker engine. Second one “linux_sonar_install_server” is to install and configure the SonarQube server.

$ cat ansible/linux_provision_sonar_server.yml 
---
- name: linuxn sonar server management
  hosts: sonar
  become: true
  become_user: root
  roles:
  - { role: 'linux_docker_install_server' }  
  - { role: 'linux_sonar_install_server' }

Step3: Setup Docker

Here are the details of the role “linux_docker_install_server” which does the setup of docker engine on the vm. These are bare minimum steps required to setup docker as per the Docker documentation.

linux_docker_install_server role variables

$ cat ansible/roles/linux_docker_install_server/vars/main.yml 
---
docker_repository: "https://download.docker.com/linux/fedora/docker-ce.repo"
docker_compose_repository: "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64"
sonar_user: "vagrant"
sonar_group: "docker"

linux_docker_install_server role tasks

$ cat ansible/roles/linux_docker_install_server/tasks/main.yml 
---
  - name: Install pre-requisite packages
    dnf: name={{ item }} state=present
    with_items:
      - dnf-plugins-core

  - name: Add Docker repository
    command: "dnf config-manager --add-repo {{docker_repository}}"
  
  - name: Install Docker Engine
    dnf: name={{ item }} state=present
    with_items:
      - docker-ce
      - docker-ce-cli
      - containerd.io
  
  - name: Ensure group "docker" exists
    group:
      name: docker
      state: present
  
  - name: Add the user 'vagrant' to group of 'docker'
    user:
      name: "{{sonar_user}}"
      group: "{{sonar_group}}"
      groups: docker
      append: yes

  - name: Reload systemd daemon
    systemd:
      daemon_reload: yes

  - name: Enable and Start Docker service
    service:
      name: docker
      enabled: yes
      state: started

  - name: Validate Docker installation
    command: "docker run hello-world"
  
  - name: install docker compose
    command: "curl -SL {{docker_compose_repository}} -o /usr/local/bin/docker-compose"

  - name: ensure executable perm docker-compose 
    file:
      path: /usr/local/bin/docker-compose
      mode: '0755'

Step4: Setup SonarQube

In this step we are going to exeute role “linux_sonar_install_server” which installs SonarQube and Postgres as its backed. It launches SonarQube as a service. SonarQube internally uses elasticsearch for search capabilities. The default user that is setup is “admin/admin” for which the password can be changed on first login to SonarQube portal.

Here is high level architecture diagram of SonarQube setup as per their documentation.

linux_sonar_install_server role variables

$ cat ansible/roles/linux_sonar_install_server/vars/main.yml 
---
sonar_home: "/home/vagrant/sonar"

linux_sonar_install_server template files

$ cat ansible/roles/linux_sonar_install_server/templates/sysctl.conf.j2 
# sysctl settings are defined through files in
# /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/.
#
# Vendors settings live in /usr/lib/sysctl.d/.
# To override a whole file, create a new file with the same in
# /etc/sysctl.d/ and put new settings there. To override
# only specific settings, add a file with a lexically later
# name in /etc/sysctl.d/ and put new settings there.
#
# For more information, see sysctl.conf(5) and sysctl.d(5).
vm.max_map_count=262144
$ cat ansible/roles/linux_sonar_install_server/templates/docker-compose.yml.j2 
version: "3"

services:
  sonarqube:
    image: sonarqube:community
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    ports:
      - "9000:9000"
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:
  postgresql_data:

linux_sonar_install_server role tasks

$ cat ansible/roles/linux_sonar_install_server/tasks/main.yml 
---
- name: ensure docker compose file exists
  template:
    src: "docker-compose.yml.j2"
    dest: "{{sonar_home}}/docker-compose.yml"

- name: ensure max map count set
  template:
    src: "sysctl.conf.j2"
    dest: "/etc/sysctl.conf"

- name: ensure kernel parameters reloaded
  command: "sysctl -p"

- name: ensure sonar service up
  command: "/usr/local/bin/docker-compose -f {{sonar_home}}/docker-compose.yml up -d"

Step4: Launch SonarQube

Once our ansible playbook and roles are ready we can launch the VM using vagrant as shown below which will launch the vm and provision using ansible.

$ vagrant up

Once the SonarQube service is up and running you can access it using following URL.

URL: http://fedsonar.stack.com:9000/

Step5: Generate Analysis Token

In this step we are going to generate a token for the user “admin” with “analyze” permission as shown below in the screenshot.

Here is my token name and its corresponding value that you should copy and save as soon as its generated.

admin_token: sqa_eb186b3fd258430363e82cfc457bec03a43d3fe9

Step6: Clone Gradle project

We are going to use the following Gradle github project hosting some sample gradle build based java projects. Let us clone the below repository to get the source code.

$ git clone https://github.com/SonarSource/sonar-scanning-examples.git

Step7: Update build.gradle

Once we have cloned the repository let us switch into the following folder and update the “build.gradle” with the sonarqube plugin version “4.0.0.2929” along with the sonar properties which will help to connect to sonarqube server and analyze the sources and tests folder as mentioned. The minimum java version required for this project is 11.

$ cd sonar-scanning-examples/sonarqube-scanner-gradle/gradle-basic/

$ cat build.gradle 
plugins {
    id "jacoco"
    id "java"
    id "application"
    id("org.sonarqube") version "4.0.0.2929"
}

description = 'Example of Sonar Scanner for Gradle Usage'
version = '1.0'

sonar {
    properties {
        property('sonar.projectName', 'Example of SonarQube Scanner for Gradle Usage')
        property("sonar.projectKey", "sonarqube-scanner-gradle")
        property("sonar.token", "sqa_eb186b3fd258430363e82cfc457bec03a43d3fe9")
        property("sonar.sources", "src/main")
        property "sonar.tests", "src/test"
    }
}

// In this section you declare where to find the dependencies of your project
repositories {
    // Use jcenter for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    mavenCentral()
}

dependencies {
    // This dependency is found on compile classpath of this component and consumers.
    implementation 'com.google.guava:guava:31.1-jre'

    // Use JUnit test framework
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
    implementation 'org.junit.jupiter:junit-jupiter:5.9.2'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

// Apply a specific Java toolchain to ease working on different environments.
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
    sourceSets.main.java.srcDirs = ['src']
}

application {
    // Define the main class for the application.
    mainClass='com.acme.App'
}

test {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

jacocoTestReport {
    reports {
        xml.required = true
        html.outputLocation = layout.buildDirectory.dir('jacocoHtml')
    }
}

Step8: Build Gradle Project

Now its time to build the gradle project with two input paramters. “sonar.host.url” is used to point to the sonarqube server ui hosted at port 9000. “sonar.qualitygate.wait” is used to forces the build to wait for the QualityGate status response and the build will continue or fail based on the status of QualityGate.

$ ./gradlew -Dsonar.host.url=http://fedsonar.stack.com:9000 -Dsonar.qualitygate.wait=true sonar --stacktrace --info
...
> Task :sonar
Sensor TextAndSecretsSensor [text] (done) | time=274ms
Sensor VB.NET Project Type Information [vbnet]
Sensor VB.NET Project Type Information [vbnet] (done) | time=1ms
Sensor VB.NET Analysis Log [vbnet]
Sensor VB.NET Analysis Log [vbnet] (done) | time=10ms
Sensor VB.NET Properties [vbnet]
Sensor VB.NET Properties [vbnet] (done) | time=0ms
Sensor IaC Docker Sensor [iac]
Sensor IaC Docker Sensor [iac] (done) | time=50ms
------------- Run sensors on project
Sensor Analysis Warnings import [csharp]
Sensor Analysis Warnings import [csharp] (done) | time=1ms
Sensor Zero Coverage Sensor
Sensor Zero Coverage Sensor (done) | time=2ms
Sensor Java CPD Block Indexer
Sensor Java CPD Block Indexer (done) | time=6ms
CPD Executor 1 file had no CPD blocks
CPD Executor Calculating CPD for 0 files
Analysis report generated in 48ms, dir size=141.6 kB
Analysis report compressed in 9ms, zip size=19.6 kB
Analysis report uploaded in 23ms
------------- Check Quality Gate status
Waiting for the analysis report to be processed (max 300s)
QUALITY GATE STATUS: PASSED - View details on http://fedsonar.stack.com:9000/dashboard?id=sonarqube-scanner-gradle
Analysis total time: 9.457 s

We can also fetch QualityGate Status for a project using the REST API call as shown below.

API Syntax

http://[sonarhost]/api/qualitygates/project_status?projectKey=[key]

API Request

$ curl -k -X GET -s -u admin:admin@1234 http://fedsonar.stack.com:9000/api/qualitygates/project_status?projectKey=sonarqube-scanner-gradle | jq -r '.projectStatus.status'
OK

Step9: Validate SonarQube Analysis Report

Now its time to validate our analysis report on the SonarQube portal as shown below.

Hope you enjoyed reading this article. Thank you..