Configuration
Setting up SonarQube & SonarCloud for Azure DevOps is straight-forward and can be done by following the steps outlined here:
SonarQube installation/setup: https://docs.sonarqube.org/latest/setup/install-server/ (SonarQube requires Java 11 or Docker to work depending on whether you are installation the zip file or docker image).
SonarCloud installtion/setup is far more trivial and is configured when creating an account.
Continuous Integration
Once the SonarQube/Cloud has been configured you then need to configure your DevOps pipeline. In order to do so you will need to install the relevant extension: SonarQube: https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarqube SonarCloud: https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarcloud
Installing the SonarQube/Cloud extension will give you access to three tasks in DevOps:
- Prepare Analysis Configuration
- Run Code Analysis
- Publish Quality Gate Result
- Instructions for SonarQube: https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-azure-devops/#header-3
- Instructions for SonarCloud: https://sonarcloud.io/documentation/analysis/scan/sonarscanner-for-azure-devops/#configure
The configuration process for the SonarQube/Cloud extensions are identical.
For the purposes of this work I have manually added the sonar configuration settings, however I recommend adding the settings to a file called sonar-project.properties and storing that in git with the solution.
Pipeline Process
Once the extensions are configured the build pipeline needs to be updated so that the analysis can be reported to SonarQube/Cloud correctly.
The build process should have the following steps:
- Checkout solution
- Install NodeJs
- Run npm ci
- Run the SonarQubePrepare/SonarCloudPrepare task
- Run npm build
- Run npm test
- Run SonarQubeAnalyse/SonarCloudAnalyse task
- Run SonarQubePublish/SonarCloudPublish task
Below is a sample YAML pipeline.
# Node.js
# Build a general Node.js project with npm.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger:
- master
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
displayName: 'Install Node.js'
- script: |
npm run lint
displayName: 'npm lint'
- script: |
npm ci
displayName: 'npm ci'
- task: SonarCloudPrepare@1
inputs:
SonarCloud: 'SonarCloud'
organization: '[ORGANISATION_NAME]' #this will come from setup
scannerMode: 'CLI'
configMode: 'manual'
cliProjectKey: '[PROJECT_KEY_NAME]' #this will come from setup
cliProjectName: '[PROJECT_NAME]' #this will come from setup
cliSources: [COMMA_SEPARATED_LIST_OF_FOLDERS_TO_COVER] #e.g.'components,lib,pages,utils'
extraProperties: |
sonar.testExecutionReportPaths=test-report.xml
sonar.javascript.lcov.reportPaths=coverage/lcov.info
- script: |
npm run build
displayName: 'npm build'
- script: |
npm run test
displayName: 'npm run test'
- task: SonarCloudAnalyze@1
- task: SonarCloudPublish@1
inputs:
pollingTimeoutSec: '300'
- task: PowerShell@2
displayName: SonarCloud Result
inputs:
targetType: 'inline'
script: |
$token = [System.Text.Encoding]::UTF8.GetBytes([AUTHORISATION_TOKEN] + ":")
$base64 = [System.Convert]::ToBase64String($token)
$basicAuth = [string]::Format("Basic {0}", $base64)
$headers = @{ Authorization = $basicAuth }
$result = Invoke-RestMethod -Method Get -Uri https://sonarcloud.io/api/qualitygates/project_status?projectKey=[PROJECT_NAME] -Headers $headers
$result | ConvertTo-Json | Write-Host
if ($result.projectStatus.status -eq "OK") {
Write-Host "Quality Gate Succeeded"
}else{
throw "Quality gate failed"
}
- task: CopyFiles@2
displayName: "Copy Output Directory"
inputs:
Contents: |
**
!.git/**/*
!debug.log
targetFolder: $(Build.ArtifactStagingDirectory)
Solution Setup
In order for SonarQube/Cloud to analyse the code correct there are a couple steps that need to be taken. The following packages need to be installed into the solution's dev dependencies:
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/plugin-transform-react-jsx": "^7.12.1",
"@babel/preset-env": "^7.12.1",
"@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^4.5.0",
"babel-jest": "^26.6.1",
"eslint": "^7.12.1",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.5",
"jest": "^26.6.1",
"jest-sonar-reporter": "^2.0.0"
}
You need to add a jest.config.js file with the following contents:
/*
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/en/configuration.html
*/
module.exports = {
// Automatically clear mock calls and instances between every test
clearMocks: true,
/*
* Indicates whether the coverage information should be collected while executing the test
*/
collectCoverage: true,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// The test environment that will be used for testing
testEnvironment: 'node',
/*
* This option allows the use of a custom results processor
*/
testResultsProcessor: 'jest-sonar-reporter'
};
You will need to configure babel correctly - below is a sample configuration in babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
]
],
plugins: ['@babel/plugin-transform-react-jsx']
};
All these files should be stored with the root of the solution.