Journey to Integrate SonarQube Analysis on every pull request - Part 3

Akansh Singhal - Aug 3 - - Dev Community

Until now we have gone through two parts of this blog series:

In this part, we will explore how to efficiently report issues found on pull requests. I suggest reading the previous two parts for a better understanding of our approach.

We will divide this into two sections:
SonarQube Analysis
Post SonarQube Analysis: Updating the Report on the PR

SonarQube performs analysis on a commit level, but pull requests often contain multiple commits. This can lead to false positives if we communicate issues based solely on commit IDs.

So we are creating a storage in Dynamo DB. You can see how to set up DynamoDB on local. We are using below query to create table in dynamo db. You can refer this on github also.

aws dynamodb create-table \
    --table-name SonarIssues \
    --attribute-definitions \
        AttributeName=pull_request_id,AttributeType=N\
        AttributeName=analysed_at,AttributeType=S\
    --key-schema \
        AttributeName=pull_request_id,KeyType=HASH\
        AttributeName=analysed_at,KeyType=RANGE\
    --provisioned-throughput \
        ReadCapacityUnits=10,WriteCapacityUnits=5 --endpoint=http://localhost:8000
Enter fullscreen mode Exit fullscreen mode

We are storing data in below schema in DynamoDB

@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class AnalysisRequest {
    private long pullRequestId;
    private String repositoryName;
    private SonarQubeIssuesResponse issues;
    private String analysedAt;
}
Enter fullscreen mode Exit fullscreen mode

Since we have storage of All analysis on particular PR, we will collate all the issues and if Issue count is 0 we will APPROVE the PR otherwise we will do CHANGES_REQUESTED which is similar to the way common user perform in real life.
Example State Transitions:

Approved In case All Issues Resolved

Changes Requested

We have created pipeline in Jenkins on the view of above two stages:

Pipeline

#!groovy

pipeline {
    agent any
    parameters {
            string(name: 'REPO_OWNER', defaultValue: 'Akansh09', description: 'Git Repo Owner?')
            string(name: 'REPO_NAME', defaultValue: 'sonar-analysis', description: 'Git Repo Name?')
            string(name: 'SONAR_PROJECT', defaultValue: 'sonar-analysis', description: 'Sonar Project?')
            string(name: 'TARGET_BRANCH', defaultValue: 'main', description: 'Target branch?')
            string(name: 'SONAR_KEY', defaultValue: 'sonar-analysis', description: 'Sonar Key?')
    }

    triggers {
        pollSCM('*/5 * * * *')
    }

    stages {
     stage('SonarQube Analysis') {
       steps {
           script {
              def gitCommitHash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
              sh "$MAVEN_HOME/bin/mvn clean verify sonar:sonar -Dsonar.projectKey=${params.SONAR_KEY} -Dsonar.projectName=${params.SONAR_PROJECT} -Dsonar.host.url=$SONARQUBE_URL -Dsonar.token=$SONARQUBE_LOGIN -Dsonar.projectVersion=$gitCommitHash"
           }
        }
      }
      stage('Post SonarQube Analysis') {
             steps {
             script {
                 sleep(time:120,unit:"SECONDS")
                     sh "$MAVEN_HOME/bin/mvn clean compile exec:java -Dexec.mainClass=\"com.sonar.analysis.SonarAnalysis\" -DSONAR_KEY=${params.SONAR_KEY} -DTARGET_BRANCH=${params.TARGET_BRANCH} -DGIT_REPO_OWNER=${params.REPO_OWNER} -DGIT_REPO_NAME=${params.REPO_NAME}"
                 }
             }
      }
   }
}
Enter fullscreen mode Exit fullscreen mode

First stage we had already discussed in blog. In second stage we are doing following:

  • Get Corresponding Pull Request Id Source --> Target
  • Persist issue in DynamoDB for the current analysis
  • Get All Those unresolved issues which are present in the SonarQube for this PR
  • Approve in case 0 issues left, CHANGES_REQUESTED in case issues are still present

Since SonarQube do not have mapping on the basis of commit id or PR id. We are getting all OPEN issues and on the basis of that we are finding key which are still unclosed

public List<SonarQubeIssuesResponse.Issues> getListOfUnResolvedIssues(long pullRequestId){
        DynamoDBUtil dynamoDBUtil = new DynamoDBUtil();
        List<AnalysisRequest> allIssuesPertainingToPullRequest = dynamoDBUtil.getAnalysisRequest(DYNAMO_SONAR_TABLE, pullRequestId);

        Set<SonarQubeIssuesResponse.Issues> analysisRequestSet = allIssuesPertainingToPullRequest.stream().flatMap(analysisRequest -> {
            if(analysisRequest!=null && analysisRequest.getIssues()!=null){
                return Arrays.stream(analysisRequest.getIssues().getIssues());
            } else {
                return Stream.empty();
            }
        }).collect(Collectors.toSet());

        SonarClient sonarClient = new SonarClient();
        SonarQubeIssuesResponse sonarQubeIssuesResponse = sonarClient.getAllIssuesFromSonarQube();
        List<String> allKeys = Arrays.stream(sonarQubeIssuesResponse.getIssues()).map(SonarQubeIssuesResponse.Issues::getKey).toList();

        return analysisRequestSet.stream().filter(s->allKeys.stream().anyMatch(s1->s1.equals(s.getKey()))).toList();
    }
Enter fullscreen mode Exit fullscreen mode

Sample PR where this job executed can be seen here

You can refer code with the current implementation on my github repository

So we have successfully integrated SonarQube <> Jenkins <> Github. And we are able to achieve our initial goal.

Hope this resource help you. Thank you for reading this. If you have any questions or need further information, feel free to contact me at akanshsinghal7@gmail.com.

. . . . . . .
Terabox Video Player