[HOWTO] Integrate SonarCloud analysis in an Azure DevOps YAML pipeline

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.

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

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.

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

Website Powered by WordPress.com.

Up ↑