Back to Blog
DevOps
3 min read

Automating SonarCloud Scanning Across All Azure DevOps Repos

Azure DevOpsSonarCloudSecurityAutomationCI/CD

Setting up SonarCloud for one repository is straightforward. Setting it up for 50 repositories is painful. Here's how to automate it.

The Problem

You want code quality scanning on all repositories, but:

  • Each repo needs its own SonarCloud project
  • Each repo needs pipeline changes
  • New repos get forgotten
  • Configuration drifts between repos

The Solution: Dynamic Pipeline

Create a single pipeline that discovers all repos and scans them:

trigger: none

schedules:
- cron: "0 2 * * *"  # 2am daily
  displayName: Nightly scan
  branches:
    include:
    - main

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: PowerShell@2
  displayName: 'Discover repositories'
  inputs:
    targetType: 'inline'
    script: |
      $org = "$(System.CollectionUri)".TrimEnd('/')
      $project = "$(System.TeamProject)"
      $pat = "$(System.AccessToken)"

      $base64Auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat"))
      $headers = @{ Authorization = "Basic $base64Auth" }

      $reposUrl = "$org/$project/_apis/git/repositories?api-version=7.0"
      $repos = (Invoke-RestMethod -Uri $reposUrl -Headers $headers).value

      $repoList = $repos | ForEach-Object { $_.name } | ConvertTo-Json -Compress
      Write-Host "##vso[task.setvariable variable=RepoList]$repoList"
      Write-Host "Found $($repos.Count) repositories"

- ${{ each repo in split(variables.RepoList, ',') }}:
  - template: templates/sonar-scan.yml
    parameters:
      repoName: ${{ repo }}

The Scan Template

Create templates/sonar-scan.yml:

parameters:
- name: repoName
  type: string

steps:
- checkout: git://$(System.TeamProject)/${{ parameters.repoName }}
  displayName: 'Checkout ${{ parameters.repoName }}'
  persistCredentials: true

- task: SonarCloudPrepare@1
  displayName: 'Prepare SonarCloud - ${{ parameters.repoName }}'
  inputs:
    SonarCloud: 'SonarCloud-Connection'
    organization: 'your-org'
    scannerMode: 'CLI'
    configMode: 'manual'
    cliProjectKey: 'your-org_${{ parameters.repoName }}'
    cliProjectName: '${{ parameters.repoName }}'
    cliSources: '.'

- task: SonarCloudAnalyze@1
  displayName: 'Run SonarCloud Analysis'
  continueOnError: true

- task: SonarCloudPublish@1
  displayName: 'Publish Results'
  continueOnError: true

Handling Different Languages

Detect project type and adjust scan configuration:

- task: PowerShell@2
  displayName: 'Detect project type'
  inputs:
    targetType: 'inline'
    script: |
      $projectType = "generic"

      if (Test-Path "*.csproj") { $projectType = "dotnet" }
      elseif (Test-Path "package.json") { $projectType = "node" }
      elseif (Test-Path "pom.xml") { $projectType = "maven" }
      elseif (Test-Path "requirements.txt") { $projectType = "python" }

      Write-Host "##vso[task.setvariable variable=ProjectType]$projectType"

Then use conditional tasks based on ProjectType.

Auto-Creating SonarCloud Projects

SonarCloud can auto-create projects on first scan, but for better control use the API:

$sonarToken = "your-sonar-token"
$headers = @{
  Authorization = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${sonarToken}:")))"
}

$body = @{
  name = $repoName
  project = "your-org_$repoName"
  organization = "your-org"
}

Invoke-RestMethod -Uri "https://sonarcloud.io/api/projects/create" `
  -Method Post -Headers $headers -Body $body

Filtering Repos

Not every repo needs scanning. Add filtering:

$excludePatterns = @("archived-*", "test-*", "docs")

$repos = $repos | Where-Object {
  $name = $_.name
  $exclude = $false

  foreach ($pattern in $excludePatterns) {
    if ($name -like $pattern) { $exclude = $true }
  }

  -not $exclude
}

Or use repo topics/tags to include/exclude.

Reporting

Aggregate results across all repos:

$projects = Invoke-RestMethod -Uri "https://sonarcloud.io/api/projects/search?organization=your-org" -Headers $headers

$summary = $projects.components | ForEach-Object {
  $measures = Invoke-RestMethod -Uri "https://sonarcloud.io/api/measures/component?component=$($_.key)&metricKeys=bugs,vulnerabilities,code_smells" -Headers $headers

  [PSCustomObject]@{
    Project = $_.name
    Bugs = ($measures.component.measures | Where-Object { $_.metric -eq "bugs" }).value
    Vulnerabilities = ($measures.component.measures | Where-Object { $_.metric -eq "vulnerabilities" }).value
    CodeSmells = ($measures.component.measures | Where-Object { $_.metric -eq "code_smells" }).value
  }
}

$summary | Export-Csv "sonar-summary.csv" -NoTypeInformation

Need help implementing code quality scanning across your organisation? Get in touch - we help teams build secure development pipelines.

Need help with your Azure environment?

Get in touch for a free consultation.

Get in Touch