SonarQube Collector
Detects SonarQube/SonarCloud via Web API reads, auto-run of `sonar-scanner`, in-repo config, CI scanner runs, and the GitHub App PR check. Normalizes results into `.code_quality` for tool-agnostic policy evaluation.
sonarqube to your lunar-config.yml:uses: github://earthly/lunar-lib/collectors/sonarqube@v1.0.5
What This Integration Collects
This integration includes 5 collectors that gather metadata from your systems.
api
Queries the SonarQube/SonarCloud Web API for code-quality metrics, scoped
per-commit. On default-branch commits the query uses branch=<default>;
on PR commits it uses pullRequest=$LUNAR_COMPONENT_PR — the only
difference between the two paths is the query param, so they share one
sub-collector. Because SonarQube's Compute Engine queues analyses
asynchronously — typically publishing 10–60s after sonar-scanner exits,
longer for large projects — this sub-collector polls
api/project_analyses/search for up to api_poll_timeout_seconds
(default 180s) waiting for an analysis whose revision matches the
current head_sha. If the matching analysis never appears, writes
.code_quality.source.analysis_status = "pending" and no metrics, so
policies can distinguish "SonarQube hasn't finished yet" from "SonarQube
isn't configured". Discovers the project key from the
sonarqube/project-key meta annotation (typically set by a
company-specific cataloger via lunar catalog component --meta sonarqube/project-key <key>), or falls back to the explicit
project_key input. Writes a tool-agnostic passing signal, coverage
and duplication percentages, and severity-bucketed issue counts at the
.code_quality.* top level. SonarQube-specific structure (quality gate
detail, the reliability/security/maintainability rating split, SQALE
debt, native metric names) lands under .code_quality.native.sonarqube
for SonarQube-specific policies that need it. Runs on both default-branch
and PR commits by default — users who want only one can narrow
runs_on on their import in lunar-config.yml.
auto
Runs sonar-scanner on the checked-out source against the configured
SonarQube/SonarCloud server, then polls the Web API for results — an
all-in-one alternative to api for users who don't trigger SonarQube
from their own CI. On default-branch commits the scanner is invoked with
-Dsonar.branch.name=<default>; on PR commits with
-Dsonar.pullrequest.key=$LUNAR_COMPONENT_PR,
-Dsonar.pullrequest.branch=$LUNAR_COMPONENT_HEAD_BRANCH, and
-Dsonar.pullrequest.base=$LUNAR_COMPONENT_BASE_BRANCH. Scanner
invocation metadata (version, exit code, duration) is captured under
.code_quality.native.sonarqube.auto. Once the scanner exits, the same
polling path used by api reads the published analysis and writes the
tool-agnostic .code_quality.* fields with "integration": "auto". If
the scanner fails, writes
.code_quality.native.sonarqube.auto.status = "scanner-failed" with the
exit code and no downstream metrics. Requires SONARQUBE_TOKEN with
Execute Analysis permission (read-only Browse is not sufficient).
Mutually exclusive with api and with a user-run sonar-scanner in CI
on the same commit — enabling both means the scanner runs twice. A
future collector-dependency feature will let auto fire only when the
cicd sub-collector did not capture a scan for the same head_sha.
Runs on both default-branch and PR commits by default — narrow runs_on
on the import to restrict the scope.
config
Detects SonarQube/SonarCloud configuration in the repository — the
sonar-project.properties file, the sonar-maven-plugin in pom.xml,
the org.sonarqube plugin in build.gradle/build.gradle.kts, or
<SonarQubeEnabled> in a .csproj. Writes discovered config file
paths under .code_quality.native.sonarqube.config. Presence of this
data signals that SonarQube is wired up for the component even when
the api sub-collector cannot reach the project or no analysis has
been published yet.
cicd
Detects sonar-scanner invocations in CI pipelines via a
ci-after-command hook. Captures the command string, exit code, and
scanner version. Writes to .code_quality.native.sonarqube.cicd.cmds[],
mirroring the snyk/cli pattern. Maven (mvn sonar:sonar) and Gradle
(gradle sonarqube) launchers are not covered in V1 — those need
separate binary-match rules and will be added in follow-up PRs.
github-app
Detects the SonarCloud (or SonarQube-for-GitHub) PR analysis check on
pull requests by querying the GitHub commit status API. Writes check
state, context, and target_url to
.code_quality.native.sonarqube.github_app, plus a status field set
to "complete" on success or "pending" on timeout (mirrors the
sibling source.analysis_status and auto.status pattern). Mirrors
the snyk/github-app pattern. Only runs on PRs. Requires GH_TOKEN
to read commit statuses. Because SonarCloud publishes its status
check only after analysis completes, this sub-collector polls
/repos/<owner>/<repo>/commits/<sha>/status for up to
github_app_poll_timeout_seconds (default 180s) for a SonarCloud
status on the PR head SHA. If it never appears, writes
.code_quality.native.sonarqube.github_app.status = "pending" and
exits cleanly.
How Collectors Fit into Lunar
Lunar watches your code and CI/CD systems to collect SDLC data from config files, test results, IaC, deployment configurations, security scans, and more.
Collectors are the automatic data-gathering layer. They extract structured metadata from your repositories and pipelines, feeding it into Lunar's centralized database where guardrails evaluate it to enforce your engineering standards.
Learn How Lunar Works →Example Collected Data
This collector writes structured metadata to the Component JSON. Here's an example of the data it produces:
{
"code_quality": {
"source": {
"tool": "sonarqube",
"integration": "api",
"project_key": "my-org_my-service",
"api_url": "https://sonarcloud.io",
"analysis_status": "complete"
},
"passing": true,
"coverage_percentage": 78.3,
"duplication_percentage": 3.1,
"issues": {
"total": 46,
"critical": 0,
"high": 1,
"medium": 3,
"low": 42
},
"native": {
"sonarqube": {
"quality_gate": {
"status": "OK",
"conditions_failed": 0
},
"ratings": {
"reliability": "A",
"security": "B",
"maintainability": "A",
"security_review": "A"
},
"metrics": {
"bugs": 3,
"vulnerabilities": 1,
"code_smells": 42,
"lines_of_code": 12500
},
"auto": {
"status": "complete",
"version": "7.0.0.4796",
"exit_code": 0,
"duration_seconds": 47
},
"config": {
"files": ["sonar-project.properties"]
},
"cicd": {
"cmds": [
{"cmd": "sonar-scanner -Dsonar.projectKey=my-org_my-service", "version": "5.0.1", "exit_code": 0}
]
},
"github_app": {
"status": "complete",
"state": "success",
"context": "SonarCloud Code Analysis",
"target_url": "https://sonarcloud.io/dashboard?id=my-org_my-service&pullRequest=42"
}
}
}
}
}
Configuration
Configure this collector in your lunar-config.yml.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
project_key
|
Required | — | SonarQube/SonarCloud project key (e.g. `my-org_my-service`). Used by the `api` and `auto` sub-collectors. Optional if the component has a `sonarqube/project-key` meta annotation set by a cataloger. |
sonarqube_base_url
|
Optional |
https://sonarcloud.io
|
SonarQube/SonarCloud API base URL. Defaults to SonarCloud. Set to your SonarQube server URL (e.g. `https://sonar.example.com`) for self-hosted. |
api_poll_timeout_seconds
|
Optional |
180
|
Total seconds the `api` and `auto` sub-collectors will wait for a SonarQube analysis matching the current `head_sha` to appear before giving up and emitting `analysis_status: "pending"`. SonarQube's Compute Engine queues analyses asynchronously; results typically publish 10–60s after `sonar-scanner` exits, longer for large projects. |
api_poll_interval_seconds
|
Optional |
10
|
Seconds between polls while the `api` and `auto` sub-collectors wait for SonarQube analysis to complete. |
auto_scanner_version
|
Optional |
7.0.0.4796
|
Version of the `sonar-scanner` CLI downloaded and executed by the `auto` sub-collector. Pinned for reproducibility. If the collector image already ships with `sonar-scanner` on PATH, this input is ignored. |
auto_sources
|
Optional |
.
|
Source root passed to `sonar-scanner` as `-Dsonar.sources=<value>` by the `auto` sub-collector. Defaults to the repo root. Override for monorepos where only a sub-tree should be analysed. |
auto_extra_args
|
Required | — | Extra command-line arguments appended to every `sonar-scanner` invocation (e.g. `-Dsonar.exclusions=**/*.min.js`). Applied by the `auto` sub-collector on both default-branch and PR paths. |
github_app_poll_timeout_seconds
|
Optional |
180
|
Total seconds the `github-app` sub-collector will wait for the SonarCloud GitHub check run to appear on the PR head SHA. |
github_app_poll_interval_seconds
|
Optional |
10
|
Seconds between polls while the `github-app` sub-collector waits for the SonarCloud check run. |
Secrets
This collector requires the following secrets to be configured in Lunar:
| Secret | Description |
|---|---|
SONARQUBE_TOKEN
|
SonarQube/SonarCloud user token. For the read-only `api` sub-collector, `Browse` permission on the target project is sufficient. For `auto`, which runs `sonar-scanner` itself, the token must additionally have `Execute Analysis` permission. Sent as the HTTP Basic username with an empty password, per the SonarQube Web API convention. |
GH_TOKEN
|
GitHub token for the GitHub Checks API. Used by the `github-app` sub-collector to read SonarCloud / SonarQube check runs on PRs. |
Documentation
View on GitHubSonarQube Collector
Detect SonarQube/SonarCloud via read-only Web API queries, optional
auto-run of sonar-scanner, in-repo config, CI scanner runs, and the
GitHub App PR check.
Overview
Five sub-collectors cover SonarQube: api reads an existing analysis via
the Web API, auto runs sonar-scanner itself and then reads the result
back, config flags in-repo configuration, cicd captures CI scanner
runs, and github-app reads the SonarCloud GitHub App PR check. api
and auto are alternate paths to the same .code_quality.* data — pick
one per context (see Auto-run vs API-read). Both
run on default-branch and PR commits by default, branching internally on
LUNAR_COMPONENT_PR — narrow runs_on on the import to restrict scope.
Tool-agnostic fields land at .code_quality.*; SonarQube-specific
structure lives under .code_quality.native.sonarqube. Both self-hosted
SonarQube and SonarCloud are supported via sonarqube_base_url.
Collected Data
This collector writes to the following Component JSON paths. The top-level
.code_quality.* fields are tool-agnostic and intended for a generic
code-quality policy. SonarQube-specific structure (the rating split, quality
gate detail, SQALE debt, native metric names, config/CI/GitHub-App payloads)
lives under .code_quality.native.sonarqube for SonarQube-aware policies.
| Path | Type | Written by | Description |
|---|---|---|---|
.code_quality.source |
object | api / auto |
Tool, integration (api or auto), project key, API URL, and analysis_status (complete or pending) |
.code_quality.passing |
bool | api / auto |
Overall pass/fail signal — derived from SonarQube's quality gate status |
.code_quality.coverage_percentage |
number | api / auto |
Line coverage percentage (0–100), if measured |
.code_quality.duplication_percentage |
number | api / auto |
Duplicated lines percentage (0–100), if measured |
.code_quality.issues |
object | api / auto |
Severity buckets: total, critical, high, medium, low (same shape as .sca.vulnerabilities / .sast.findings) |
.code_quality.native.sonarqube.quality_gate |
object | api / auto |
Quality gate status (OK/WARN/ERROR) and failed condition count |
.code_quality.native.sonarqube.ratings |
object | api / auto |
SonarQube letter ratings (A–E) per dimension: reliability, security, maintainability, security review |
.code_quality.native.sonarqube.metrics |
object | api / auto |
SonarQube-native metric names: bugs, vulnerabilities, code smells, lines of code |
.code_quality.native.sonarqube.auto |
object | auto |
Scanner run metadata: version, exit_code, duration_seconds, and status (complete or scanner-failed) |
.code_quality.native.sonarqube.config |
object | config |
Paths to SonarQube config files discovered in the repo |
.code_quality.native.sonarqube.cicd |
object | cicd |
sonar-scanner invocations captured in CI: command, version, exit code |
.code_quality.native.sonarqube.github_app |
object | github-app |
SonarCloud GitHub App PR check: state, context, target_url (from the GitHub commit status API), and status (complete on success, pending on timeout) |
Collectors
This integration provides the following sub-collectors. Use include in
lunar-config.yml to select a subset.
| Collector | Hook | Description |
|---|---|---|
api |
code | Queries the SonarQube/SonarCloud Web API per-commit. On default-branch commits the query is scoped with branch=<default>; on PR commits with pullRequest=<PR number>. Polls for analysis completion before returning metrics (see Analysis completion & polling). Narrow runs_on on the import to only run on default-branch or only on PRs. |
auto |
code | Downloads/invokes sonar-scanner on the checked-out source — with -Dsonar.branch.name=<default> on default-branch commits, or -Dsonar.pullrequest.* on PR commits — then polls the Web API and writes the same fields as api with "integration": "auto". Requires SONARQUBE_TOKEN with Execute Analysis. Exclude when users run sonar-scanner themselves (see Auto-run vs API-read). |
config |
code | Detects sonar-project.properties, sonar-maven-plugin, org.sonarqube Gradle plugin, or <SonarQubeEnabled> in .csproj |
cicd |
ci-after-command on sonar-scanner |
Captures sonar-scanner invocations in CI (mirrors snyk/cli). Maven and Gradle launchers are follow-ups. |
github-app |
code (PRs only) | Reads the SonarCloud GitHub App's check run on each PR (mirrors snyk/github-app). Polls for the check run to appear. |
Installation
Add to your lunar-config.yml:
collectors:
- uses: github://earthly/lunar-lib/collectors/sonarqube@v1.0.0
on: ["domain:your-domain"]
# with:
# project_key: "my-org_my-service" # Optional — falls back to catalog meta annotation
# sonarqube_base_url: "https://sonarcloud.io" # Or your self-hosted SonarQube URL
Required secrets:
SONARQUBE_TOKEN— SonarQube/SonarCloud user token. For read-onlyapiuse,Browsepermission on the target project is sufficient. Forauto, which runssonar-scanneritself, the token also needsExecute Analysispermission. Sent as the HTTP Basic username with an empty password, per the SonarQube Web API convention.GH_TOKEN— GitHub token with read access to PR check runs (used by thegithub-appsub-collector).
Project key discovery
The collector resolves the SonarQube project key in this order:
- Catalog meta annotation — reads
sonarqube/project-keyfrom the component's lunar catalog meta. Set vialunar catalog component --meta sonarqube/project-key <key>, typically invoked by a company-specific cataloger that knows which components map to which SonarQube projects. This is the recommended approach for orgs where each component has its own project. - Explicit
project_keyinput — set inlunar-config.ymlfor static cases, or when importing the collector multiple times with differenton:scopes (e.g. one import per domain, each with its own project). - Neither found — the collector exits cleanly with no data written.
Auto-run vs API-read
Three triggers produce the same .code_quality.* data — pick the one that
matches how SonarQube is wired up for your component:
| Trigger | Sub-collectors to include | Sub-collectors to exclude |
|---|---|---|
User runs sonar-scanner in CI |
api, cicd, github-app |
auto |
| Collector auto-runs the scan | auto, github-app |
api, cicd |
| Read-only (scan happens elsewhere, not in this CI) | api |
auto, cicd |
The config sub-collector is orthogonal — it flags whether SonarQube is
wired up at all — and is safe to include in every configuration.
Including both api and auto for the same context runs the scanner once
and then reads the API twice (wasteful but not harmful); including auto
alongside a user-run sonar-scanner in CI double-scans the project, which
is more expensive and may hit rate limits on the SonarQube server. A
future collector-dependency feature will let auto fire conditionally —
only when the cicd sub-collector didn't capture a scan for the current
head_sha.
Each of api and auto runs on both default-branch and PR commits by
default. Scope narrowing is per sub-collector via the usual platform
mechanisms (plugin-level runs_on on a vendored copy, or excluding the
sub-collector entirely with exclude: when scoping by domain that only
exercises one side).
SonarQube vs SonarCloud
Both SonarQube (self-hosted) and SonarCloud expose the same Web API. Select
the instance by overriding sonarqube_base_url:
| Instance | sonarqube_base_url |
|---|---|
| SonarCloud (default) | https://sonarcloud.io |
| SonarQube self-hosted | e.g. https://sonar.example.com |
Tokens are created the same way on both — in the user profile under Security → Generate Tokens.
Analysis completion & polling
SonarQube's Compute Engine queues analyses asynchronously — when a scan
finishes uploading (via user-run sonar-scanner or our own auto
invocation), the results aren't immediately visible on the Web API.
Typical latency is 10–60 seconds; large projects can take several minutes.
Both api and auto handle this race by polling
api/project_analyses/search until the most recent analysis's revision
matches the current head_sha, or until api_poll_timeout_seconds
elapses. If the timeout hits, they write
.code_quality.source.analysis_status = "pending" with no metrics —
policies that care about "is SonarQube wired up at all?" can still read
the config sub-collector's output, and policies that want to gate on
fresh results can treat analysis_status == "pending" as a skip rather
than a fail.
The same principle applies to github-app: the SonarCloud status check on
a PR appears only after analysis completes, so the sub-collector polls the
GitHub commit status API up to github_app_poll_timeout_seconds and writes
.code_quality.native.sonarqube.github_app.status = "complete" on success
or "pending" on timeout.
Inputs
| Input | Default | Description |
|---|---|---|
project_key |
(empty — falls back to catalog meta) | SonarQube/SonarCloud project key (e.g. my-org_my-service). Optional if sonarqube/project-key meta annotation is set. |
sonarqube_base_url |
https://sonarcloud.io |
API base URL. Override for self-hosted SonarQube. |
api_poll_timeout_seconds |
180 |
Total seconds api/auto wait for a SonarQube analysis matching head_sha. |
api_poll_interval_seconds |
10 |
Seconds between polls while waiting for SonarQube analysis. |
auto_scanner_version |
7.0.0.4796 |
Pinned version of sonar-scanner used by auto. Ignored if the collector image already ships with sonar-scanner on PATH. |
auto_sources |
. |
Value passed to -Dsonar.sources= by auto. Override for monorepos. |
auto_extra_args |
(empty) | Extra command-line args appended to every auto sonar-scanner invocation (e.g. -Dsonar.exclusions=**/*.min.js). |
github_app_poll_timeout_seconds |
180 |
Total seconds github-app waits for the SonarCloud GitHub check run on the PR head SHA. |
github_app_poll_interval_seconds |
10 |
Seconds between polls while waiting for the SonarCloud check run. |
Open Source
This collector is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.
Common Use Cases
Explore guardrails that use data from SonarQube Collector.
Ready to Automate Your Standards?
See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 200+ built-in guardrails.