This post is about the integration of SonarCloud analysis in Azure DevOps YAML pipelines starting from a basic scenario that analyzes a repository containing a .NET Core 7 solution and ending up with a more complex scenario that analyzes a repository that additionally contains a React application and Terraform files.
Prerequisites
First things first, so let’s start with the prerequisites.
- Sign up at SonarCloud
See Getting started with Azure DevOps for details
Hint: if your repository is a private repository, subscribe to a paid plan (there is a 14-day free trial) - Set up a SonarCloud organization
See Set up your organization for details
Important: it’s strongly recommended to use a technical user / service account in Azure DevOps to set up your SonarCloud organization and the connection between your SonarCloud organization and the Azure DevOps organization - Install SonarCloud extension for Azure DevOps in your Azure DevOps organization
- Import the Azure DevOps Services project you want to analyze into SonarCloud
See “Import repositories” under “Set up your analysis” for details
Hint: this step can also be achieved by running the pipeline below the first time - Choose your new code definition
See Choose your new code definition for details
Create new service connection in Azure DevOps services
For the integration of SonarCloud analysis in Azure DevOps YAML pipelines, an Azure DevOps service connection is needed. To avoid that multiple service connections have to be adjusted/maintained when changing/rotating the SonarCloud token, I recommend to create a shared service connection as follows.
- Create a new Azure DevOps project (name it i.e.
SonarCloud
) that will contain the shared service connection - Go to
Project settings
of the newly created Azure DevOps project - Navigate to
Service connections
in the menu on the left - Click button
New service connection
- Search for
SonarCloud
- Click
Next
- Browse to sonarcloud.io and log in with the technical user / service account you set up the SonarCloud organization with
- Go to
My Account
- Navigate to tab
Security
- Generate a new token and copy its value
- Go back to the service connection wizard in Azure DevOps and paste the token into the input field
SonarCloud token
- Enter a meaningful name for the service connection (i.e.
shared-sonarcloud-service-connection
) - Click
Verify and save
- Open the service connection by clicking on it
- Open security settings
- Under
Project permissions
add the Azure DevOps project the service connection should be shared with (the Azure DevOps project that contains the repository to be analyzed)
The Azure DevOps project you just added under Project permissions
now contains a shared service connection with name `[SERVICE_CONNECTION_NAME]-[AZURE_DEVOPS_PROJECT_NAME]`.
Azure DevOps YAML pipeline
Now, let’s have a look at the Azure DevOps YAML pipeline that will trigger the SonarCloud analysis.
The repository I implemented the pipeline for has the following structure and consists of Terraform files (located in deploy/iac/
), Azure DevOps YAML pipelines (located in deploy/pipelines/
), a React frontend application (located in src/Client/
) and a ASP.NET Core 7 Web API (ArbitrarySolution.sln
+ several projects under src/
).
├───deploy
│ ├───iac
│ │ ├───backend
│ │ └───vars
│ │ └───*.tf
│ └───pipelines
│ ├───*.yml
├───src
│ ├───Client
│ │ ├───.vscode
│ │ ├───.yarn
│ │ ├───node_modules
│ │ ├───public
│ │ │ └───well-known
│ │ └───src
│ │ ├───assets
│ │ ├───auth
│ │ ├───components
│ │ ├───services
│ │ └───utils
│ ├───Domain
│ ├───Server
│ ├───Services
│ ├───Shared
│ └───Tests
│ ├───Services.Tests
│ └───Shared.Tests
└───ArbitrarySolution.sln
In a first phase we only focus on the analysis of the ASP.NET Core 7 Web API solution. Therefore I created the following files under deploy/pipelines/
and pushed them to the Git repository.
ArbitrarySolution-quality.yml
name: ArbitrarySolution Quality Pipeline
# pipeline gets triggered on every commit to dev or main branch, if there are changes in src and/or deploy directory
trigger:
branches:
include:
- dev
- main
paths:
include:
- src
- deploy
# as we are using yarn (v3.6.3), we specify the folder for the yarn cache
variables:
YARN_CACHE_FOLDER: "$(Pipeline.Workspace)/.yarn"
CI: true
stages:
- stage: Quality
displayName: Quality Build
pool:
vmImage: ubuntu-latest
jobs:
- template: jobs-build-quality.yml
parameters:
dotNetBuildProjects: "**/*.sln"
dotNetTestProjects: "**/*.sln"
jobs-build-quality.yml
parameters:
- name: dotNetBuildProjects
type: string
default: "**/*.sln"
- name: dotNetTestProjects
type: string
default: "**/*.Tests/*.Tests.csproj"
- name: buildConfiguration
type: string
default: "Release"
- name: "clientPath"
type: string
default: "src/Client"
jobs:
- job: Build
displayName: Quality Build ArbitrarySolution
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
fetchDepth: 0 # disable shallow fetch to avoid SCM warnings in SonarCloud (see https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/steps-checkout?view=azure-pipelines#shallow-fetch)
- task: UseDotNet@2
displayName: Get DotNet SDK
inputs:
packageType: sdk
useGlobalJson: true # .NET version is specified in global.json file which resides in the repository root
- task: SonarCloudPrepare@1
displayName: "Prepare analysis configuration"
inputs:
SonarCloud: "AZ_DEVOPS_SERVICE_CONNECTION_NAME_HERE"
organization: NAME_OF_THE_SONARCLOUD_ORGANIZATION_HERE
projectKey: SONARCLOUD_PROJECT_KEY_HERE # if project already exists in SonarCloud, get the project key from the project settings
extraProperties: |
scm.provider=git
- task: DotNetCoreCLI@2
displayName: Build ArbitrarySolution Backend
inputs:
command: build
projects: ${{parameters.dotNetBuildProjects}}
arguments: "--configuration ${{parameters.buildConfiguration}}"
- task: DotNetCoreCLI@2
displayName: Test ArbitrarySolution Backend
inputs:
command: test
projects: ${{parameters.dotNetTestProjects}}
arguments: '--configuration ${{parameters.buildConfiguration}} --collect "Code coverage"'
- task: NodeTool@0
inputs:
versionSpec: "18.17.x"
- task: Bash@3
displayName: Yarn - Install
inputs:
targetType: inline
workingDirectory: ${{parameters.clientPath}}
script: |
yarn set version 3.6.3
- task: Bash@3
displayName: Yarn - Install NPM packages
inputs:
targetType: inline
workingDirectory: ${{parameters.clientPath}}
script: |
yarn install --immutable
- task: Bash@3
displayName: Yarn - Run jest
inputs:
targetType: inline
workingDirectory: ${{parameters.clientPath}}
script: |
yarn test-ci
- task: SonarCloudAnalyze@1
displayName: SonarCloud - Run code analysis
- task: SonarCloudPublish@1
displayName: SonarCloud - Publish quality gate result
Important notes
- Pipeline is designed to be executed on Microsoft-hosted agents
- Code coverage report in SonarCloud will not work out of the box as we use a linux agent. To make code coverage report work, check this topic on the sonar community website
More details concerning the scanning of .NET solutions can be found by following the links below
- https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarcloud-extension-for-azure-devops/
- https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-for-net/
Import YAML pipeline in Azure DevOps
To import the before created YAML pipeline in Azure DevOps proceed as follows.
- Open the Azure DevOps project in the browser
- Navigate to
Pipelines
in the menu bar on the left side - Click
New pipeline
on the top right - Select
Azure Repos Git
- Select the repository containing the pipelines
- Select
Existing Azure Pipelines YAML file
- Select the pipeline file
- Click
Continue
- Click
Run
to run the pipeline
Now you have a pipeline that gets triggered on every commit to dev
or main
branch, if there are changes in src
and/or deploy
directory. It builds and tests the frontend and the backend plus executes SonarCloud analysis on the .NET Core code.
Include other languages into the analysis
As SonarCloud not only supports C# but also many other languages, let’s include terraform and React / TypeScript files into the analysis I thought… but when I tried this, I failed with quite a few attempts – I checked the docs several times (especially the chapter about Analysis parameters) and tried many parameters and combinations of them. I also checked the Sonar community but after getting stuck with the following error, I gave up and opened a new topic in the Sonar community forum.
java.nio.file.AccessDeniedException: /sys/kernel/tracing error during anlysis
Fortunately, Victor Diez from SonarSource was able to find out the cause of the error pretty fast and provided me with the solution to my problem!
To include terraform and React / TypeScript files, I needed to do the following adjustments:
# update task SonarCloudPrepare@1 in jobs-build-quality.yml as follows
- task: SonarCloudPrepare@1
displayName: "Prepare analysis configuration"
inputs:
SonarCloud: "AZ_DEVOPS_SERVICE_CONNECTION_NAME_HERE"
organization: NAME_OF_THE_SONARCLOUD_ORGANIZATION_HERE
projectKey: SONARCLOUD_PROJECT_KEY_HERE # if project already exists in SonarCloud, get the project key from the project settings
extraProperties: |
scm.provider=git
sonar.typescript.tsconfigPaths=src/Client/tsconfig.json
sonar.projectBaseDir=/home/vsts/work/1/s/
sonar.exclusions=**/bin/**,**/obj/**,**/*.dll,**/.yarn/**,**/node_modules/**,**/.pnp*
As described here (see section “Analyzing languages other than C# and VB”), the easiest way is to include the contents outside the solution in one of the projects. Therefore I added the following tags to the shared projects .csproj
file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<AnalysisMode>Recommended</AnalysisMode>
</PropertyGroup>
<!-- ItemGroup with PackageReference tags here -->
<ItemGroup>
<Content Include="..\..\deploy\**\*.tf" Visible="false">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
<Content Include="..\..\src\Client\**\*.*" Visible="false">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
Finally also terraform and TypeScript files got analyzed by SonarCloud!
Pull Request Analysis
To be able to tackle SonarCloud findings even before the affected code gets merged into dev
/ develop
branch, you can configure pull request analysis. SonarCloud then decorates your pull requests with comments concerning findings of type Code smell
and bug
. To set pull request analysis up, follow the official docs. Some additional recommendations from my side.
- Check this blog post out: https://writeabout.net/2019/04/18/use-pull-request-decoration-in-azure-devops-with-sonarcloud/
- Make use of a technical user / service account in Azure DevOps instead of using user specific personal access token
- Assign the technical user / service account the
Contributor
permission on your Azure DevOps project - Make sure you set up a branch policy so that the pipeline gets executed on every pull request
The only disadvantages of pull request analysis are that findings of type Security Hotspot
do not get commented on the PRs and that the comments get deleted and recreated on every analysis run, even if they are marked as won’t fix.
Leave a comment