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)
- an automated AzureDevops pipeline
- 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}"
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'
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"
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"
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)
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"
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
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
}
]
}
}
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
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();
Navigate to SonarQube Server Web UI:
await page.goto("http://localhost:9234/");
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();
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');
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`);
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
There are of course a few waitForLoadState
s and waitForTimeout
s 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:
- Bash script for manual scanning a project from the terminal
- 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.:
- Install Docker
- Install jq
- Install NodeJS
The scripts and pipelines can be found at https://github.com/deyanp/azuredevops-sonarqube
Top comments (0)