Chapter 18Lesson 02~165 minutes

GitHub, GitLab, Azure DevOps, and Bitbucket Integration: Guided Hands-On Workflow

Map GitHub, GitLab, Azure DevOps, and Bitbucket integration models, run a real local Community Build analysis, and simulate one least-privilege provider-decoration path without production credentials.

GitHubGitLabAzure DevOpsBitbucketLeast privilege

Learning objectives

  • Map the GitHub, GitLab, Azure DevOps, and Bitbucket trust models side by side before configuring any credential.
  • Run one real local Community Build analysis with a project-scoped token and preserve ceTaskId/Quality Gate evidence.
  • Create a provider-binding manifest containing only fake identifiers and permission metadata.
  • Simulate a GitHub-style provider check locally without network access or production credentials.
  • Diagnose one deliberately insufficient provider-permission manifest and repair it without broadening unrelated scopes.
  • Revoke the SonarQube lab credential and document provider-credential cleanup as simulated/not-created.

1. Disposable lab contract

No production provider credentials. The mandatory path uses SonarQube Community Build and a local provider-check simulator. GitHub/GitLab/Azure/Bitbucket identities are fake .invalid values. If you have an authorized Developer+ sandbox, you may replace the simulator with one native provider binding, but never use a production organization/repository merely for the lesson.
Item Lab value
SonarQube Community Build 26.9.0.129388 at http://localhost:9000
Scanner SonarScanner CLI 8.1.0.6389
Project sq-ch18-provider-lab
Provider flow GitHub-style local simulation; no actual GitHub API call
Provider identity example.invalid/acme/demo, simulated PR 18
Credential Real disposable SonarQube project-analysis token; provider secrets are not created

2. Build the four-provider trust matrix before the fixture

GitHub
  identity: GitHub App installed on selected repositories
  outbound decoration: Checks + Pull Requests read/write
  private repo read: Contents read
  webhook: normally disabled unless optional security-alert feature needs it

GitLab
  identity: dedicated Reporter-capable technical user or Group Access Token
  global decoration credential: api scope on analyzed repositories
  import-only credential: read_api where documented

Azure DevOps
  identity: dedicated technical account
  global decoration credential: PAT, Code read/write on analyzed repositories
  pipeline integration: SonarQube Azure DevOps extension/service endpoint
  import/onboarding: separate PAT flow

Bitbucket Cloud
  identity: OAuth Client for global PR reporting
  permission: Pull requests Read; client-credentials flow
  import credential: API token with read:repository:bitbucket
  compatibility: older Sonar labels may still say OAuth consumer/key/secret

This matrix is the evidence baseline. Do not create credentials until the action, owner, repository boundary, and revocation path are known.

3. Create a tiny local project

mkdir -p sq-ch18-provider-lab/src
cd sq-ch18-provider-lab
git init -b main
git config user.email "learner@example.invalid"
git config user.name "SonarQube Learner"

cat > src/app.py <<'PYCODE'
def normalize(value: str) -> str:
    return value.strip().lower()
PYCODE

cat > sonar-project.properties <<'EOF'
sonar.projectKey=sq-ch18-provider-lab
sonar.projectName=SQ Chapter 18 Provider Lab
sonar.sources=src
sonar.sourceEncoding=UTF-8
EOF

cat > .gitignore <<'EOF'
.scannerwork/
evidence/
EOF

git add .
git commit -m "provider integration lab baseline"
git rev-parse HEAD

Create the disposable project in the local SonarQube UI, generate a project-analysis token, store it only in the current shell as SONAR_TOKEN, and record the token name/creation time—not the value.

4. Real SonarQube analysis: prove server state before decoration

export SONAR_HOST_URL="http://localhost:9000"
export SONAR_PROJECT_KEY="sq-ch18-provider-lab"
mkdir -p evidence/sonar

git rev-parse HEAD | tee evidence/sonar/revision.txt
sonar-scanner -X 2>&1 | tee evidence/sonar/scanner.log
cp .scannerwork/report-task.txt evidence/sonar/report-task.txt

CE_TASK_ID="$(sed -n 's/^ceTaskId=//p' .scannerwork/report-task.txt)"
echo "$CE_TASK_ID" | tee evidence/sonar/ce-task-id.txt

curl -fsS -H "Authorization: Bearer $SONAR_TOKEN" \
  "$SONAR_HOST_URL/api/ce/task?id=$CE_TASK_ID" \
  | tee evidence/sonar/ce-task.json

# Query only after CE status is SUCCESS.
curl -fsS -H "Authorization: Bearer $SONAR_TOKEN" \
  "$SONAR_HOST_URL/api/qualitygates/project_status?projectKey=$SONAR_PROJECT_KEY" \
  | tee evidence/sonar/gate.json

At this point you have a real SonarQube result but no real provider decoration. That distinction is the lab’s control condition.

5. Create a fake binding and permission manifest

mkdir -p evidence/provider
cat > evidence/provider/binding.json <<'JSON'
{
  "provider": "github",
  "mode": "local-simulation-only",
  "organization": "acme-example-invalid",
  "repository": "demo",
  "repository_url": "https://example.invalid/acme/demo",
  "pull_request_key": "18",
  "target": "main",
  "sonarqube_project_key": "sq-ch18-provider-lab",
  "credential_kind": "GitHub App (simulated; no key created)",
  "permissions": {
    "metadata": "read",
    "contents": "read",
    "checks": "read_write",
    "pull_requests": "read_write"
  }
}
JSON

python -m json.tool evidence/provider/binding.json

The manifest contains no private key, client secret, PAT, or bearer token. It records only the trust contract that a real GitHub App would need for this use case.

6. Trace the Quality Gate to a simulated provider check

The simulator reads the real SonarQube gate result and the fake provider permission contract. It refuses to “decorate” if the minimum simulated GitHub permissions are missing.

cat > provider_sim.py <<'PYCODE'
import json, sys, time
from pathlib import Path

binding = json.loads(Path("evidence/provider/binding.json").read_text())
gate = json.loads(Path("evidence/sonar/gate.json").read_text())
perms = binding["permissions"]
required = {"checks": "read_write", "pull_requests": "read_write"}
missing = [k for k,v in required.items() if perms.get(k) != v]
if missing:
    print("SIMULATED_PROVIDER_403 insufficient permissions:", ",".join(missing))
    sys.exit(3)

status = gate.get("projectStatus", {}).get("status", "UNKNOWN")
check = {
    "provider": "github-simulator",
    "repository": binding["repository_url"],
    "pull_request_key": binding["pull_request_key"],
    "check_name": "SonarQube Code Analysis (simulated)",
    "sonarqube_gate": status,
    "provider_conclusion": "success" if status == "OK" else "failure",
    "simulated": True,
    "created_at_epoch": int(time.time())
}
Path("evidence/provider/check.json").write_text(json.dumps(check, indent=2))
print(json.dumps(check, indent=2))
PYCODE

python provider_sim.py | tee evidence/provider/simulator.log

This is a faithful state simulation, not a claim that Community Build posted a GitHub check. The evidence packet contains a real analysis/gate and a clearly labeled local provider-delivery artifact.

7. Deliberate failure: permission is too narrow

cp evidence/provider/binding.json evidence/provider/binding-good.json
python - <<'PYCODE'
import json
from pathlib import Path
p=Path("evidence/provider/binding.json")
d=json.loads(p.read_text())
d["permissions"]["checks"]="read"
p.write_text(json.dumps(d,indent=2))
PYCODE

python provider_sim.py 2>&1 | tee evidence/provider/permission-failure.log || true

Expected evidence: the real SonarQube gate is unchanged, while the simulated provider path returns SIMULATED_PROVIDER_403. This proves that provider delivery can fail independently of scanner/Compute Engine/gate success.

8. Repair only the missing permission contract

mv evidence/provider/binding-good.json evidence/provider/binding.json
python provider_sim.py | tee evidence/provider/simulator-repaired.log
python -m json.tool evidence/provider/check.json

Do not “repair” a permission failure by granting organization administration, deleting/recreating the SonarQube project, changing project key, rerunning blindly, or embedding a provider secret in YAML.

9. Optional Developer+ sandbox execution

If you have an authorized disposable provider organization/repository and Developer+ SonarQube instance:

  1. Create the provider integration identity using the provider-specific minimum permissions documented in Lesson 1.
  2. Store credentials in SonarQube’s DevOps Platform Integration configuration or provider/CI secret store as documented—never in source.
  3. Import/bind the disposable repository to the SonarQube project.
  4. Run PR/MR analysis from supported CI with full-enough Git history.
  5. Preserve ceTaskId, Quality Gate, provider check/comment/status identifier, app/token permission screenshot with secret values hidden, and audit event.
  6. Revoke/delete the sandbox credential after the packet is complete.

10. Challenge: identify the owning layer

The scanner uploads successfully, Compute Engine is SUCCESS, and the Quality Gate is OK, but the provider returns HTTP 403. Which action is justified?

Inspect the provider binding, app/token identity, repository installation/access, and exact write scope. Do not change analyzer rules, retry the scanner repeatedly, restart SonarQube, or widen organization privileges before the provider evidence identifies the missing permission.

11. Cleanup

  • Revoke the disposable SonarQube project-analysis token and verify it no longer authenticates.
  • Delete the local project only if it exists solely for this lab and after exporting evidence.
  • No provider credential was created in the mandatory path, so record provider_credential_created=false.
  • Delete only the local fixture/evidence working copy after preserving the packet.
  • Do not delete global DevOps integrations, shared apps, organization tokens, or unrelated repositories.

Knowledge check

Why is the provider simulator still useful if it never contacts GitHub?

What does the deliberate permission failure prove?

Why is the fake binding file allowed to contain repository URL and permission names?

Could you replace the GitHub App simulation by pasting a personal admin token into the file?

What should the evidence say if no Developer+ sandbox was available?

Next lesson

Choose provider trust and credential patterns deliberately

Lesson 3 converts the workflow into durable app/token, direction, scope, storage, rotation and native-vs-generic design choices.

Official references and version notes

Version and compatibility note

Rechecked 2026-09-08. Mandatory examples target Community Build 26.9.0.129388 and SonarScanner CLI 8.1.0.6389; current commercial reference points are SonarQube Server 2026 Release 4.1 and 2026.1.5 LTA. Native feature/maintenance branch and pull/merge-request analysis plus provider decoration start in Developer Edition. GitHub integration uses a GitHub App with explicit repository permissions; GitLab global integration uses a dedicated Reporter-capable personal/group token with api scope; Azure DevOps uses a dedicated technical PAT and Azure DevOps Extension/service endpoint; Bitbucket Cloud is currently transitioning terminology/credentials, with API tokens replacing App passwords for import and current provider UI using OAuth Clients even where some Sonar documentation still says OAuth Consumer. Multiple configurations/instances and several monorepo/enterprise integration capabilities begin in Enterprise. Recheck provider UI, token deprecations, patch notes, callback/base URL requirements, and exact permissions before production rollout.

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.