Friday, March 17, 2017

Sonar in your build pipeline

If you're got a build pipeline in jenkins, sonar analysis should be one of your stages. But how to fail the build if sonar analysis fails?

Breaking the CI Build from the sonar docs is helpful, but here I go into some more detail on how to deal with this.

Step 1. Build your project in jenkins as the first step in your build pipeline
Step 2. Run the sonar analysis


mvn -e -X sonar:sonar \
                    -DskipTests \
                    -DXmx2g \
                    -DXX:MaxPermSize=1g \
                    -Dsonar.host.url=\${SONAR_HOST} \
                    -Dsonar.branch=\${BRANCH} \
                    -Dsonar.forceAnalysis=true \
                    -Dsonar.sourceEncoding=UTF-8 \
                    -Dmaven.test.failure.ignore=true \
                    -Dsonar.sources=. \
                    -Dsonar.junit.reportsPath=\${WORKSPACE}/**/target/surefire-reports \
                    -Dsonar.dynamicAnalysis=reuseReports \
                    -Dsonar.jacoco.reportPath=\${WORKSPACE}/target/jacoco.exec \
                    -Dsonar.javascript.lcov.reportPath=\${WORKSPACE}/frontend/target/coverage/js/lcov.info \
                    -Dsonar.java.binaries=\${WORKSPACE}/**/target/classes \
                    -Dsonar.verbose=true


This assumes you are using jacoco for coverage, and reading your front end results from an lcov report. The details of this are beyond the scope of this article.

Note that here I used mvn to run sonar. You can also run sonar with SonarRunner or other plugin.

Once sonar has run, in your jenkins console output you will see some INFO/DEBUG logs like this:


[INFO] 16:54:19.878 CPD calculation finished
[INFO] 16:54:20.300 Analysis report generated in 341ms, dir size=4 MB
[INFO] 16:54:20.835 Analysis reports compressed in 535ms, zip size=1 MB
[INFO] 16:54:20.835 Analysis report generated in /var/lib/jenkins/workspace/work_dev/target/sonar/batch-report
[DEBUG] 16:54:20.835 Upload report
[DEBUG] 16:54:20.988 POST 200 http://sonar.nymoenkumbera.no/api/ce/submit?projectKey=no.nymoenkumbera:work-project&projectName=fun&projectBranch=dev | time=153ms
[INFO] 16:54:20.990 Analysis report uploaded in 154ms
[INFO] 16:54:20.990 ANALYSIS SUCCESSFUL, you can browse http://sonar.nymoenkumbera.no/dashboard/index/no.nymoenkumbera:work-project:dev
[INFO] 16:54:20.990 Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
[INFO] 16:54:20.990 More about the report processing at http://sonar.nymoenkumbera.no/api/ce/task?id=AVrc-hOowKmvYVIu5IZf
[DEBUG] 16:54:20.997 Report metadata written to /var/lib/jenkins/workspace/fun_dev/target/sonar/report-task.txt
[DEBUG] 16:54:20.998 Post-jobs : 
[DEBUG] 16:54:21.001 Execution getVersion
[DEBUG] 16:54:21.001 Execution stop



You can't access the project status yet, since the analysis might not be complete. You first need to check that the analysis is done. You can get the URL to get this info from the report-task.txt file that sonar write in your jenkins workspace (look at the debug info above)

In the jenkins shell, grep for the ceTaskUrl


declare -r SONAR_STATUS_URL=\$(less \${WORKSPACE}/target/sonar/report-task.txt | grep ceTaskUrl | sed -e 's/ceTaskUrl=//')


This returns a JSON that looks something like this:

{
  "task": {
    "id": "AVrc-hOowKmvYVIu5IZf",
    "type": "REPORT",
    "componentId": "AVSk2d1aVi-4YDCztopa",
    "componentKey": "no.sb1.finance.bm:bm-master-project:dev",
    "componentName": "Nettbank-bm dev",
    "componentQualifier": "TRK",
    "analysisId": "4870210",
    "status": "SUCCESS",
    "submittedAt": "2017-03-17T16:54:20+0100",
    "startedAt": "2017-03-17T16:54:22+0100",
    "executedAt": "2017-03-17T16:54:55+0100",
    "executionTimeMs": 33030,
    "logs": true
  }
}

Grab the status with a sed (or use the amazing jq)


declare SONAR_STATUS=\$(curl -skg \${SONAR_STATUS_URL} | sed -e 's/.*status":"//' | sed -e 's/",.*//')


Wait for the analysis to complete


while ! [ "\${SONAR_STATUS}" == "SUCCESS" ] || [ "\${SONAR_STATUS}" == "CANCELED" ] || [ "\${SONAR_STATUS}" == "FAILED" ];
do                                    
    echo "Sonar analysis is: \${SONAR_STATUS}. Taking a nap while we wait..."
    sleep 5
    SONAR_STATUS=\$(curl -skg \${SONAR_STATUS_URL} | sed -e 's/.*status":"//' | sed -e 's/",.*//')                    
done


Once the loop completes, you can check the project status


declare -r SONAR_STATUS=\$(curl -skg \${SONAR_HOST}/api/qualitygates/project_status?projectKey=\${SONAR_PROJECT_KEY})


This returns a JSON that looks like this:


{
  "projectStatus": {
    "status": "OK",
    "conditions": [
      {
        "status": "OK",
        "metricKey": "new_coverage",
        "comparator": "LT",
        "periodIndex": 1,
        "errorThreshold": "50",
        "actualValue": "52.577319587628864"
      },
      {
        "status": "OK",
        "metricKey": "new_sqale_debt_ratio",
        "comparator": "GT",
        "periodIndex": 1,
        "errorThreshold": "10",
        "actualValue": "4.208754208754209"
      },
      {
        "status": "OK",
        "metricKey": "new_blocker_violations",
        "comparator": "GT",
        "periodIndex": 1,
        "errorThreshold": "0",
        "actualValue": "0"
      },
      {
        "status": "OK",
        "metricKey": "new_critical_violations",
        "comparator": "GT",
        "periodIndex": 1,
        "errorThreshold": "0",
        "actualValue": "0"
      }
    ],
    "periods": [
      {
        "index": 1,
        "mode": "previous_version",
        "date": "2017-03-15T11:49:15+0100",
        "parameter": "2.25.0-SNAPSHOT"
      },
      {
        "index": 2,
        "mode": "previous_analysis",
        "date": "2017-03-17T14:07:46+0100",
        "parameter": "2017-03-17"
      },
      {
        "index": 3,
        "mode": "days",
        "date": "2017-01-06T13:17:02+0100",
        "parameter": "70"
      }
    ]
  }
}



So you can just check the projectStatus value and figure out if your jenkins build pipeline should fail or keep going.

For readability, here is the full code

#!/bin/bash

set -e

failOnSonarFailure() {
 echo "Sonar analysis failed!"

 # send a hipchat message to the right room
 HIPCHAT_HOST="https://hipchat.nymoenkumbera.no"
 HIPCHAT_ROOM="83"
 HIPCHAT_AUTH="xxx"

 echo "Sending a notification to room: \${HIPCHAT_ROOM}"
 curl -skg --data "color=red&message_format=text&message=Oops! Sonar analysis of \${POM_ARTIFACTID} failed (boom) \${SONAR_HOST}/dashboard/index/\${SONAR_PROJECT_KEY}" \${HIPCHAT_HOST}/v2/room/\${HIPCHAT_ROOM}/notification?auth_token=\${HIPCHAT_AUTH}

 exit 1
}


declare -r POM_GROUPID=\$(mvn org.apache.maven.plugins:maven-help-plugin:2.2:evaluate -Dexpression=project.groupId | egrep -v "(^\[INFO\]|^\[DEBUG\]|^\[WARNING\])")
declare -r POM_ARTIFACTID=\$(mvn org.apache.maven.plugins:maven-help-plugin:2.2:evaluate -Dexpression=project.artifactId | egrep -v "(^\[INFO\]|^\[DEBUG\]|^\[WARNING\])")

declare -r SONAR_PROJECT_KEY=\$(echo \${POM_GROUPID}:\${POM_ARTIFACTID}:\${BRANCH})

echo "Checking if analysis is finished.."
declare -r SONAR_STATUS_URL=\$(less \${WORKSPACE}/target/sonar/report-task.txt | grep ceTaskUrl | sed -e 's/ceTaskUrl=//')

declare SONAR_STATUS=\$(curl -skg \${SONAR_STATUS_URL} | sed -e 's/.*status":"//' | sed -e 's/",.*//')

while ! [ "\${SONAR_STATUS}" == "SUCCESS" ] || [ "\${SONAR_STATUS}" == "CANCELED" ] || [ "\${SONAR_STATUS}" == "FAILED" ];
do                                    
        echo "Sonar analysis is: \${SONAR_STATUS}. Taking a nap while we wait..."
 sleep 5
 SONAR_STATUS=\$(curl -skg \${SONAR_STATUS_URL} | sed -e 's/.*status":"//' | sed -e 's/",.*//')                    
done

echo "Sonar Task returned: \${SONAR_STATUS}" 

if [ "\${SONAR_STATUS}" == "FAILED" ]; then                
 failOnSonarFailure
fi

echo "Checking status of analysis with sonar key: \${SONAR_PROJECT_KEY}"
declare -r SONAR_STATUS=\$(curl -skg \${SONAR_HOST}/api/qualitygates/project_status?projectKey=\${SONAR_PROJECT_KEY})

echo "Sonar WEB API returned:"
echo "\${SONAR_STATUS}"

if [[ \${SONAR_STATUS} == *'{"projectStatus":{"status":"ERROR",'* ]]; then
 failOnSonarFailure
fi



No comments:

Post a Comment