DEV Community

Deyan Petrov
Deyan Petrov

Posted on

Azure Devops Pipeline with SonarQube Community in Docker and Playwright

TLDR; Scanning projects with SonarQube Community and retrieving a couple of metrics and reports can be done easily with the below scripts.

Introduction

SonarQube is one of the most well-known static code analysis tools which is basically scanning source code for security issues. It does come with a "self-managed" Community version, which unfortunately does not contain out of the box any easy option to generate reports with the scan results. The "self-managed" Enterprise Edition, or the cloud Enterprise Edition do support reporting, however they come with the (IMHO pretty funny for 2025) "Talk to sales" label ;)
So the challenge I set for myself is how to set up (as a prototype, to get comfortable with SonarQube)

  1. an automated AzureDevops pipeline
  2. a manual bash script

which not only scan some typescript projects, but also scrape a couple of reports/screenshots, and all this without maintaining a fixed, always running server instance of SonarQube ..

Important Note: The (makefile.sh)[https://github.com/gitricko/sonarless/blob/main/makefile.sh] file from (Sonarless)[https://github.com/gitricko/sonarless] served as a basis for the below script/pipeline.

Run SonarQube Server in Docker

The first step is to run SonarQube server in Docker. The official SonarQube Docker images can be found in [Docker Hub)[https://hub.docker.com/_/sonarqube]. Also a dedicated docker network is created:

DOCKER_NETWORK_NAME="sonarqube"
DOCKER_SONAR_SERVER_INSTANCE_NAME="sonar-server"
DOCKER_SONAR_SERVER_INSTANCE_PORT="9234"
DOCKER_SONAR_SERVER_IMAGE="sonarqube:latest"

docker network create "${DOCKER_NETWORK_NAME}"

docker run -d --name "${DOCKER_SONAR_SERVER_INSTANCE_NAME}" \
    -p "${DOCKER_SONAR_SERVER_INSTANCE_PORT}:9000" \
    --network "${DOCKER_NETWORK_NAME}" \ 
    "${DOCKER_SONAR_SERVER_IMAGE}"
Enter fullscreen mode Exit fullscreen mode

So the server has been started, but we need to await some time (< 1 minute) for the instance to really start. This is done by first trying to make an HTTP GET to the root URL, and then to the system status endpoint:

curl -k -s -o /dev/null -I -w "%{http_code}" -H 'User-Agent: Mozilla/6.0' "http://localhost:$DOCKER_SONAR_SERVER_INSTANCE_PORT"

curl -s "http://localhost:$DOCKER_SONAR_SERVER_INSTANCE_PORT/api/system/status" 2>/dev/null | jq -r '.status'
Enter fullscreen mode Exit fullscreen mode

The above commands are wrapped in loops until successful result is received.

Reset admin password

Resetting the default admin password for the SonarQube server instance running in Docker seems to be important as otherwise the automated calls to the Web API or most importantly Web UI would require changing that password ...
The new password must "be at least 12 characters long","must contain at least one uppercase character" and "must contain at least one special character".
The password change is performed by invoking the /users/change_password operation.

curl -s -X POST -u "$USERNAME:$OLD_PASSWORD" \
    -d "login=$USERNAME&previousPassword=$OLD_PASSWORD&password=$PASSWORD" \
    "http://localhost:${DOCKER_SONAR_SERVER_INSTANCE_PORT}/api/users/change_password"
Enter fullscreen mode Exit fullscreen mode

Create Project

Then a default project must be created. This is done by calling the /projects/create and /users/set_homepage APIs:

curl -s -u "$CREDENTIALS" -X POST "http://localhost:$DOCKER_SONAR_SERVER_INSTANCE_PORT/api/projects/create?name=$PROJECT_NAME&project=$PROJECT_NAME"

curl -s -u "$CREDENTIALS" -X POST "http://localhost:$DOCKER_SONAR_SERVER_INSTANCE_PORT/api/users/set_homepage?type=PROJECT&component=$PROJECT_NAME"
Enter fullscreen mode Exit fullscreen mode

Start the Scanner using NPX

Before the scanner can be started, a token needs to be created so that the SonarQube scanner can connect to the server:

SONAR_TOKEN=$(curl -s -X POST -u "$CREDENTIALS" "http://localhost:$DOCKER_SONAR_SERVER_INSTANCE_PORT/api/user_tokens/generate?name=$(date +%s%N)" | jq -r .token)
Enter fullscreen mode Exit fullscreen mode

Then running the scanner is just an npx command away, but note that it has to be run in the source code project folder:

npx -y sonarqube-scanner -Dsonar.host.url="http://localhost:$DOCKER_SONAR_SERVER_INSTANCE_PORT" -Dsonar.token="$SONAR_TOKEN" -Dsonar.projectKey="$PROJECT_NAME"
Enter fullscreen mode Exit fullscreen mode

Get some metrics via Web API

After the scan has finished, we can collect a couple of numeric metrics from the standard/official Web API of the SonarQube (without any funky scraping):

curl -s -u "$CREDENTIALS" "http://localhost:${DOCKER_SONAR_SERVER_INSTANCE_PORT}/api/measures/component?component=${PROJECT_NAME}&metricKeys=bugs,vulnerabilities,code_smells,quality_gate_details,violations,duplicated_lines_density,ncloc,coverage,reliability_rating,security_rating,security_review_rating,sqale_rating,security_hotspots,open_issues" \
    | jq -r > $SONAR_METRICS_PATH
Enter fullscreen mode Exit fullscreen mode

The output looks like this:


{
  "component": {
    "key": "PROJECT_NAME",
    "name": "PROJECT_NAME",
    "qualifier": "TRK",
    "measures": [
      {
        "metric": "coverage",
        "value": "x.0",
        "bestValue": false
      },
      {
        "metric": "reliability_rating",
        "value": "x.0",
        "bestValue": false
      },
      {
        "metric": "code_smells",
        "value": "12345",
        "bestValue": false
      },
      {
        "metric": "duplicated_lines_density",
        "value": "x.0",
        "bestValue": false
      },
      {
        "metric": "security_rating",
        "value": "x.0",
        "bestValue": true
      },
      {
        "metric": "violations",
        "value": "12345",
        "bestValue": false
      },
      {
        "metric": "quality_gate_details",
        "value": "{\"level\":\"OK\",\"conditions\":[],\"ignoredConditions\":false}"
      },
      {
        "metric": "security_hotspots",
        "value": "x",
        "bestValue": false
      },
      {
        "metric": "security_review_rating",
        "value": "x.0",
        "bestValue": false
      },
      {
        "metric": "sqale_rating",
        "value": "x.0",
        "bestValue": true
      },
      {
        "metric": "bugs",
        "value": "12345",
        "bestValue": false
      },
      {
        "metric": "ncloc",
        "value": "12345"
      },
      {
        "metric": "vulnerabilities",
        "value": "12345",
        "bestValue": true
      },
      {
        "metric": "open_issues",
        "value": "12345",
        "bestValue": false
      }
    ]
  }
}

Enter fullscreen mode Exit fullscreen mode

The above is anonymized sample, so the real one has PROJECT_NAME, x and 12345 replaced by real values.

Capture screenshots as reports via Web UI with Playwright

In addition, a simple Playwright script is executed against the SonarQube server instance running in Docker to capture some screenshots as reports from the UI.

For that purpose the following script is executed:

tsc save_mhtml.ts
node save_mhtml.js --projectName $PROJECT_NAME --outputFolder $SONAR_OUTPUT_FOLDER
Enter fullscreen mode Exit fullscreen mode

The playwright script itself goes through the following steps:

Start a browser, etc:

    const browser = await chromium.launch({ headless: true, slowMo: 0 }); // Set headless to false to show the browser window + slowMo to 5000 for example
    const context = await browser.newContext();
    const page = await context.newPage();
Enter fullscreen mode Exit fullscreen mode

Navigate to SonarQube Server Web UI:

    await page.goto("http://localhost:9234/");
Enter fullscreen mode Exit fullscreen mode

Log in with username and password:

    await page.fill('input[name="login"]', 'admin');
    await page.fill('input[name="password"]', password);
    await page.getByRole('button', { name: 'Log in' }).click();
Enter fullscreen mode Exit fullscreen mode

Click through some really nasty buttons which just stay in the way:

    await clickButtonIfVisible(page, 'I understand the risk');
    await clickButtonIfVisible(page, 'Later');
    await clickButtonIfVisible(page, 'Got it');
    await clickButtonIfVisible(page, 'Dismiss');
Enter fullscreen mode Exit fullscreen mode

Finally scrape some web pages and store them as *.mhtml report files:

await gotoPageAndMakeMhtml(page, context, `http://localhost:9234/dashboard?branch=main&id=${projectName}&codeScope=overall`, `${outputFolder}/${projectName}_overview.mhtml`);

    await gotoPageAndMakeMhtml(page, context, `http://localhost:9234/project/issues?id=${projectName}&issueStatuses=OPEN%2CCONFIRMED`, `${outputFolder}/${projectName}_issues.mhtml`);

    await gotoPageAndMakeMhtml(page, context, `http://localhost:9234/security_hotspots?id=${projectName}`, `${outputFolder}/${projectName}_security_hotspots.mhtml`);
Enter fullscreen mode Exit fullscreen mode

Check if there are security vulnerabilities

Finally, the script can check some of the metric values captured before, and fail if a threshold has been exceeded, e.g.:

vulnerabilities=$(cat $SONAR_METRICS_PATH | jq -r '.component.measures[] | select(.metric == "vulnerabilities") | .value')
if [[ "$vulnerabilities" -gt 0 ]]; then
    echo "Vulnerabilities found: $vulnerabilities"
    exit 1
else
    echo "No vulnerabilities found"
fi
Enter fullscreen mode Exit fullscreen mode

There are of course a few waitForLoadStates and waitForTimeouts scattered in between, so the script does take about 1 minute in total.

Azure DevOps Pipeline, and the final scripts

The steps described above are wrapped up in 2 scripts:

  1. Bash script for manual scanning a project from the terminal
  2. Azure DevOps Pipeline for automatic scanning of a project, e.g. on a nightly schedule

The Azure DevOps pipeline performs some additional steps, but that depends on what is already pre-installed or not on your VM image, e.g.:

  1. Install Docker
  2. Install jq
  3. Install NodeJS

The scripts and pipelines can be found at https://github.com/deyanp/azuredevops-sonarqube

References

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more