Code quality in a software development project is important and a good metric to follow. Code coverage, technical debt and vulnerabilities in dependencies are a couple of things you should follow. There are some de facto tools you can use to visualize things and one of them is SonarQube. Here’s a short technical note of how to set it up on a Kotlin project and visualize metrics from different tools. We are using Detekt for static source code analysis and OWASP Dependency-Check to detect publicly disclosed vulnerabilities contained within project dependencies.
Visualizing Kotlin project metrics on SonarQube
SonarQube is a nice graphical tool to visualize different metrics of your project. Lately, it has also started to support Kotlin with the SonarKotlin and sonar-kotlin plugins. From a typical Java project, you need some extra settings to get things working. It’s also good to notice that the support for Kotlin isn’t quite yet there and the sonar-kotlin provides better information i.e. what comes to code coverage.
Steps to integrate reporting to Sonar with maven build:
- Add configuration in project pom.xml: Surefire, Failsafe, jaCoCo, Detekt, Dependency-Check
- Run Sonar in Docker
- Maven build with sonar:sonar option
- Check Sonar dashboard
(SonarQube project overview)
Configure a Kotlin project
Configure your Kotlin project built with Maven to have test reporting and static analysis. We are using Surefire to run unit tests, Failsafe for integration tests and JaCoCo to generate reports for e.g. SonarQube. See the full pom.xml from an example project (coming soon).
Test results reporting
pom.xml
<properties> <sonar.coverage.jacoco.xmlReportPaths>${project.build.directory}/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths> </properties> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <executions> <execution> <id>default-prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>pre-integration-test</id> <goals> <goal>prepare-agent-integration</goal> </goals> </execution> <execution> <id>jacoco-site</id> <phase>verify</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>${unit-tests.skip}</skipTests> <excludes> <exclude>**/*IT.java</exclude> <exclude>**/*IT.kt</exclude> <exclude>**/*IT.class</exclude> </excludes> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> <configuration> <skipTests>${integration-tests.skip}</skipTests> <includes> <include>**/*IT.class</include> </includes> <runOrder>alphabetical</runOrder> </configuration> </plugin> </plugins> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.3</version> </plugin> </plugins> </pluginManagement> ... </build>
Static code analysis with Detekt
Detekt static code analysis configuration as AntRun. There’s also an unofficial Maven plugin for Detekt. It’s good to notice that there are some ”false positive” findings on Detekt and you can either customize detekt rules or suppress findings if they are intentional such as @Suppress(”MagicNumber”).
(Detekt code smells)
pom.xml
<properties> <sonar.kotlin.detekt.reportPaths>${project.build.directory}/detekt.xml</sonar.kotlin.detekt.reportPaths> </properties> <build> ... <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>1.8</version> <executions> <execution> <!-- This can be run separately with mvn antrun:run@detekt --> <id>detekt</id> <phase>verify</phase> <configuration> <target name="detekt"> <java taskname="detekt" dir="${basedir}" fork="true" failonerror="false" classname="io.gitlab.arturbosch.detekt.cli.Main" classpathref="maven.plugin.classpath"> <arg value="--input"/> <arg value="${basedir}/src"/> <arg value="--filters"/> <arg value=".*/target/.*,.*/resources/.*"/> <arg value="--report"/> <arg value="xml:${project.build.directory}/detekt.xml"/> </java> </target> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>io.gitlab.arturbosch.detekt</groupId> <artifactId>detekt-cli</artifactId> <version>1.0.0-RC14</version> </dependency> </dependencies> </plugin> </plugins> ... </build>
Dependency checks
Dependency check with OWASP Dependency-Check Maven plugin
(OWASP Dependency-Check)
pom.xml
<properties> <dependency.check.report.dir>${project.build.directory}/dependency-check</dependency.check.report.dir> <sonar.host.url>http://localhost:9000/</sonar.host.url> <sonar.dependencyCheck.reportPath>${dependency.check.report.dir}/dependency-check-report.xml</sonar.dependencyCheck.reportPath> <sonar.dependencyCheck.htmlReportPath>${dependency.check.report.dir}/dependency-check-report.html</sonar.dependencyCheck.htmlReportPath> </properties> <build> ... <plugins> <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>4.0.2</version> <configuration> <format>ALL</format> <skipProvidedScope>true</skipProvidedScope> <skipRuntimeScope>true</skipRuntimeScope> <outputDirectory>${dependency.check.report.dir}</outputDirectory> </configuration> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> </plugins> ... </build>
Sonar scanner to run with Maven
<build> ... <pluginManagement> <plugins> <plugin> <groupId>org.sonarsource.scanner.maven</groupId> <artifactId>sonar-maven-plugin</artifactId> <version>3.6.0.1398</version> </plugin> </plugins> </pluginManagement> ... </build>
Running Sonar with a Kotlin plugin
Create a SonarQube server with Docker
$ docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube
There’s also OWASP docker image for SonarQube which adds several community plugins to enable SAST. But for our purposes, the “plain” SonarQube works nicely.
Use the Kotlin plugin which comes with SonarQube (SonarKotlin) or install the sonar-kotlin plugin which shows information differently. If you want to use sonar-kotlin and are using the official Docker image for SonarQube then you have to first remove the SonarKotlin plugin.
Using sonar-kotlin
$ git clone https://github.com/arturbosch/sonar-kotlin $ cd sonar-kotlin $ mvn package $ docker exec -it sonarqube sh -c "ls /opt/sonarqube/extensions/plugins" $ docker exec -it sonarqube sh -c "rm /opt/sonarqube/extensions/plugins/sonar-kotlin-plugin-1.5.0.315.jar" $ docker cp target/sonar-kotlin-0.5.2.jar sonarqube:/opt/sonarqube/extensions/plugins $ docker stop sonarqube $ docker start sonarqube
Adding dependency-check-sonar-plugin to SonarQube
$ curl -JLO https://github.com/SonarSecurityCommunity/dependency-check-sonar-plugin/releases/download/1.2.1/sonar-dependency-check-plugin-1.2.1.jar $ docker cp sonar-dependency-check-plugin-1.2.1.jar sonarqube:/opt/sonarqube/extensions/plugins $ docker stop sonarqube $ docker start sonarqube
Run test on project and scan with Sonar
The verify phase runs your tests and should generate i.a. jacoco.xml under target/site/jacoco and detekt.xml.
$ mvn clean verify sonar:sonar
Access Sonar via http://localhost:9000/
Code quality metrics? So what?
You now have metrics on Sonar to show to stakeholders but what should you do with those numbers?
One use case is to set quality gates on SonarQube to check that a set of conditions must be met before the project can be released into production. Ensuring code quality of “new” code while fixing existing ones is one good way to maintain a good codebase over time. The Quality Gate facilitates setting up rules for validating every new code added to the codebase on subsequent analysis. By default, the rules are: ”coverage on new code < 80%; the percentage of duplicated lines on new code > 3; maintainability, reliability or security rating is worse than A”. The default rules provide a good starting point for your projects quality metrics.