Chapter 19Lesson 01~130 minutes

CI Integration with GitHub Actions, GitLab CI, Jenkins, and Other Runners: Core Concepts and Mental Model

Model CI analysis as a sequence of independently observable checkout, build/test/report, scanner, Compute Engine, Quality Gate, CI-status, and evidence-retention states.

CI/CDGitHub ActionsGitLab CIJenkinsQuality Gate

Learning objectives

  • Separate CI runner state, checkout state, build/test evidence, scanner state, asynchronous Compute Engine state, Quality Gate state, and CI job/check state.
  • Explain why a green build step, a successful scanner process, a successful Compute Engine task, and a passing Quality Gate are different facts.
  • Identify the current Community Build CI path for GitHub Actions, GitLab CI/CD, Jenkins, and generic runners without implying commercial branch/PR capabilities.
  • Inspect runner image/toolchain, checkout depth, coverage/test reports, scanner/runtime/cache, secret source, effective analysis parameters, ceTaskId, gate result, and retained artifacts before changing a pipeline.
  • Explain when sonar.qualitygate.wait=true is appropriate and why it changes CI waiting/failure semantics rather than analysis semantics.
  • Define which caches are performance aids and which files are first-failure evidence that must never be silently reused as analysis truth.

1. The practical problem: “CI passed” is too ambiguous

Chapter 18 separated provider credentials from SonarQube analysis credentials. Chapter 19 moves inside the runner. A pipeline can say success even though no coverage report existed, the scanner uploaded a report whose Compute Engine task later failed, the Quality Gate failed after the job ended, or provider decoration never happened. The reverse can also occur: tests and analysis can be correct while a runner, artifact upload, or provider API step fails.

The engineering goal is therefore not “put SonarQube in CI.” It is to create a reproducible stage in which every important state has an owner and durable evidence:

clean runner/workspace → exact checkout SHA + full-enough history → build/test/coverage artifacts → scanner + effective parameters + secret token → report upload → report-task.txt / ceTaskId → Compute Engine terminal state → Quality Gate → CI job/check decision → retained evidence

2. Mental model: CI is an evidence conveyor, not one command

CI/SonarQube causality
flowchart TD
  R[Runner image / toolchain] --> C[Checkout SHA + SCM history]
  C --> B[Build + tests]
  B --> A[Coverage / test / build reports]
  A --> S[Sonar scanner]
  T[SONAR_TOKEN secret] --> S
  S --> U[Upload analysis report]
  U --> Q[Compute Engine task / ceTaskId]
  Q --> G[Quality Gate]
  G --> J[CI job / check decision]
  B --> E[Retained CI artifacts]
  S --> E
  Q --> E
  G --> E

Every arrow answers a different question. The test runner owns whether tests executed. The coverage producer owns whether coverage.xml exists and corresponds to the checked-out revision. The scanner owns file indexing and report upload. SonarQube Compute Engine owns background processing. The project gate owns the pass/fail policy. The CI system owns whether that result fails the job. An artifact store owns how long the evidence remains accessible.

3. State map: capture before mutation

Runner/toolchain

Runner OS/image, CPU/architecture, Python/Java/build tool versions, scanner/action/plugin version, network path to SonarQube, and available utilities.

Checkout

Repository, commit SHA, branch/event, shallow/full status, merge-base/target data where licensed, submodules, generated files, and workspace root.

Build/test reports

Build exit status, test count/result, coverage producer/version, report path, report timestamp, and whether the report maps to the current source tree.

Scanner

Scanner family/version, JRE/runtime, SONAR_USER_HOME, cache path, project key/base directory, effective parameters, indexed files, upload status.

Server/policy

ceTaskId, Compute Engine state, analysis ID, profile/gate/new-code context, Quality Gate status, and server edition/version.

CI/governance

Secret source, job exit status, gate enforcement mechanism, artifact retention, concurrency controls, action/plugin provenance, and cleanup/revocation history.

4. Current version baseline and CI integration assumptions

Rechecked 2026-09-08. Mandatory examples use SonarQube Community Build 26.9.0.129388 and SonarScanner CLI 8.1.0.6389. The current SonarQube Scan GitHub Action release is v8.2.1; the current SonarQube Quality Gate Check Action release is v1.2.0. Current SonarSource GitHub examples use the checkout v6 generation with fetch-depth: 0. Jenkins documentation requires SonarQube Scanner plugin 2.11+ and a SonarQube→Jenkins webhook for waitForQualityGate.

Community Build can run analysis inside all of these CI systems, but it does not acquire commercial multi-branch or pull/merge-request analysis merely because the runner exposes branch metadata. In Community Build, keep the executable provider examples on the main branch. Chapter 17 already provided the faithful branch/PR simulation boundary.

5. Checkout depth is analysis input, not a Git cosmetic

SCM blame, changed-code calculation, and branch/PR comparison depend on Git history. Current SonarSource GitHub and GitLab guidance explicitly disables shallow checkout for analysis. A shallow workspace can produce warnings such as missing blame or missing refs, and it can invalidate later reasoning about New Code.

git rev-parse HEAD
git rev-parse --is-shallow-repository
git status --short
git log -1 --decorate --oneline

# GitHub Actions: current SonarSource examples use a full checkout.
# with:
#   fetch-depth: 0

# GitLab CI/CD: current SonarSource examples set:
# GIT_DEPTH: "0"

The correct fix is to fetch enough history, not to disable SCM analysis simply to silence the warning.

6. Build/test/report order: produce evidence before the scanner consumes it

SonarQube does not run your unit tests. A CI pipeline must first execute the build/test tool and create coverage or other external reports. The scanner then imports those files. If coverage.xml is created after the scan, a later artifact upload cannot retroactively change the analysis that was already submitted.

Stage Owned state Evidence Wrong shortcut
Build Compiled/generated program state where required Build log, binaries, compiler version Assume scanner compiles the project.
Test Executed test cases Test exit status, JUnit/xUnit output Run scanner first and tests later.
Coverage producer Covered executable lines/branches Coverage XML at a known path Expect SonarQube to generate coverage.
Scanner Indexed files + imported reports + analysis report Scanner log, report-task.txt Ignore warnings about missing report paths.

7. Cache the expensive downloads—not the analysis truth

The scanner cache under SONAR_USER_HOME is a performance cache for analyzer/scanner downloads. Reusing it is normally safe when the key and permissions are controlled. The project-local .scannerwork directory is different: it contains ephemeral state for one analysis, including report-task.txt. Do not restore an old .scannerwork directory from CI cache and mistake its task ID for the current run.

  • Cache: .sonar/cache or the configured user-home cache.
  • Do not cache as reusable truth: .scannerwork, coverage.xml, build outputs that were not rebuilt for the current SHA, old report-task.txt.
  • Archive evidence after the run; do not feed archived evidence into the next run unless the workflow explicitly validates provenance.

8. Secret path: CI secret store → environment → scanner

The scanner token belongs in the provider secret store or Jenkins credential store. It must not appear literally in YAML, committed properties, shell history, or retained logs. Current GitHub and GitLab guidance uses the conventional SONAR_TOKEN secret/variable and SONAR_HOST_URL variable.

# Safe inspection prints presence, never the value.
if [ -n "${SONAR_TOKEN:-}" ]; then
  echo "SONAR_TOKEN=set"
else
  echo "SONAR_TOKEN=missing" >&2
  exit 2
fi
printf 'SONAR_HOST_URL=%s\n' "$SONAR_HOST_URL"

# Avoid: set -x around commands that could expand credentials.
# Avoid: echo "$SONAR_TOKEN"
# Avoid: sonar-scanner -Dsonar.token="$SONAR_TOKEN" when environment auth works.

9. Scanner success, Compute Engine success, and Quality Gate are separate

By default, a scanner can finish after report upload while SonarQube continues processing asynchronously. Preserve .scannerwork/report-task.txt; its ceTaskId is the durable bridge from runner evidence to the server background task.

cat .scannerwork/report-task.txt
CE_TASK_ID="$(sed -n 's/^ceTaskId=//p' .scannerwork/report-task.txt)"
printf 'ceTaskId=%s\n' "$CE_TASK_ID"

# A later evidence collector can query /api/ce/task?id=$CE_TASK_ID,
# then query the Quality Gate only after the CE task is terminal.

If the CI system must fail immediately on gate failure, current SonarQube supports explicit waiting. For generic CI, set sonar.qualitygate.wait=true; sonar.qualitygate.timeout defaults to 300 seconds. This makes the scanner job poll and fail when the gate fails. It does not mean the scanner itself computed the gate.

10. Provider patterns: same evidence contract, different orchestration

CI Current Sonar path Gate enforcement Evidence detail
GitHub Actions Official SonarQube Scan Action or maintained build-system scanner Quality Gate Check Action or scanner wait Use SONAR_TOKEN secret, SONAR_HOST_URL variable, full checkout; Community Build main only.
GitLab CI/CD Direct/build-system scanner in a Docker-executor job sonar.qualitygate.wait=true when required GIT_DEPTH: "0", SONAR_USER_HOME cache, protected/masked CI variables.
Jenkins SonarQube Scanner plugin + withSonarQubeEnv waitForQualityGate via SonarQube webhook Webhook to /sonarqube-webhook/ is mandatory for pipeline pause; webhook secret is recommended.
Other runners Direct supported scanner sonar.qualitygate.wait=true or explicit CE/gate API logic Preserve logs/task IDs and use CI-native secret/artifact stores.

11. Read-only CI preflight

printf 'revision='; git rev-parse HEAD
printf 'shallow='; git rev-parse --is-shallow-repository
printf 'workspace=%s\n' "$PWD"
python --version
sonar-scanner --version
[ -f coverage.xml ] && ls -l coverage.xml || echo 'coverage.xml: absent before scan'
[ -n "${SONAR_TOKEN:-}" ] && echo 'SONAR_TOKEN=set' || echo 'SONAR_TOKEN=missing'
curl -fsS "$SONAR_HOST_URL/api/system/status"

This inspection changes no project policy. It proves whether the runner is even capable of producing a trustworthy analysis before scanner execution.

Knowledge check

The scanner exits 0, but the Quality Gate later fails. Did the scanner lie?

Why is .scannerwork a bad cross-run cache?

What is wrong with generating coverage.xml after the scanner stage?

Why use fetch-depth: 0 or GIT_DEPTH: "0"?

Where should SONAR_TOKEN come from in CI?

Next lesson

Build the portable launcher and pinned GitHub workflow

Lesson 2 turns the CI evidence model into a real local launcher and an executable GitHub Actions translation.

Official references and version notes

Version and compatibility note

Rechecked 2026-09-08. Mandatory labs target SonarQube Community Build 26.9.0.129388 and SonarScanner CLI 8.1.0.6389. The GitHub example pins SonarQube Scan Action v8.2.1 to commit 22918119ff8e1ca75a623e15c8296b6ea4fbe28f, Quality Gate Check Action v1.2.0 to cf038b0e0cdecfa9e56c198bbb7d21d751d62c3b, checkout v6.1.0 to d23441a48e516b6c34aea4fa41551a30e30af803, setup-python v6.2.0 to a309ff8b426b58ec0e2a45f0f869d46889d02405, and upload-artifact v4.6.2 to ea165f8d65b6e75b540449e92b4886f43607fa02. Current SonarSource Community Build guidance restricts GitHub analysis to the main branch and recommends full Git history; GitLab examples use GIT_DEPTH: "0". Jenkins requires the SonarQube Scanner plugin (2.11+ per current docs) and a SonarQube webhook for waitForQualityGate. Recheck action/plugin versions, runner requirements, scanner/JRE behavior, provider deprecations, and edition boundaries before copying the examples into another release.

CI task identity. Preserve build/test reports, scanner logs, .scannerwork/report-task.txt and its ceTaskId; runner status, scanner exit, Compute Engine, Quality Gate, and provider/CI status remain separate states.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.