Chapter 26Lesson 02~165 minutes

Marketplace, Plugins, Extension Risks, and Compatibility Management: Guided Hands-On Workflow

Inventory a disposable Community Build instance, stage one currently compatible nonessential plugin with provenance and checksum evidence, observe startup/capability changes, then remove it and prove rollback.

PluginsSupply chainCompatibilityRollbackOperations

Learning objectives

  • Create an exact disposable Community Build 26.9 container baseline and preserve its plugin/startup evidence.
  • Select a nonessential plugin only after current compatibility, source, checksum, license, and maintenance preflight succeeds.
  • Install one exact JAR manually into the isolated server, perform the required restart, and distinguish JAR presence from successful plugin loading.
  • Verify the changed capability through startup logs and installed-plugin evidence rather than assuming a copied file is active.
  • Remove the plugin, restart deliberately, and prove the server returns to its baseline plugin inventory and healthy state.
  • Abort before installation when checksum/provenance or compatibility evidence fails.

1. Mandatory lab contract

Disposable server only. This workflow intentionally changes executable server code and restarts SonarQube. Never run it against a shared academy, production, regulated, or business-critical instance. Do not reuse production databases, plugins, secrets, volumes, or reverse-proxy endpoints.
Item Lab assumption
SonarQube Community Build 26.9.0.129388
Container image sonarqube:26.9.0.129388-community
Port 9026:9000, chosen to avoid the normal 9000 training instance
Database Container’s disposable evaluation database only; never production
Scanner Not required to prove plugin loading; an optional smoke scan may use SonarScanner CLI 8.1.0.6389
Java Use the Java runtime inside the official Community Build image; Community Build 26.x requires Java 21+ when self-managed outside the image
Plugin One currently compatible, nonessential, open-source lab plugin selected at run time from the current Plugin version matrix/Marketplace

2. Start the isolated baseline

Use distinct container/volume names so cleanup cannot touch an existing SonarQube deployment. The bootstrap-check override below is acceptable only for this disposable learning container; it is not a production deployment recommendation.

export LAB_NAME="sq-ch26"
export LAB_URL="http://localhost:9026"
export SQ_IMAGE="sonarqube:26.9.0.129388-community"
mkdir -p evidence/baseline evidence/stage evidence/installed evidence/rollback

docker volume create sq-ch26-data
docker volume create sq-ch26-logs
docker volume create sq-ch26-extensions

docker run -d --name "$LAB_NAME" \
  -p 9026:9000 \
  -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \
  -v sq-ch26-data:/opt/sonarqube/data \
  -v sq-ch26-logs:/opt/sonarqube/logs \
  -v sq-ch26-extensions:/opt/sonarqube/extensions \
  "$SQ_IMAGE"

# Wait manually until status becomes UP; preserve each failure instead of looping forever.
curl -fsS "$LAB_URL/api/system/status" | tee evidence/baseline/system-status.json
curl -fsS "$LAB_URL/api/server/version" | tee evidence/baseline/server-version.txt
docker exec "$LAB_NAME" sh -c 'ls -lah /opt/sonarqube/extensions/plugins' \
  | tee evidence/baseline/plugin-files.txt
docker logs "$LAB_NAME" > evidence/baseline/container.log 2>&1

Expected state: the exact image starts on port 9026, the API reports UP, and the extensions volume contains only the baseline files delivered or generated by the image. If startup is not clean, stop here. A plugin experiment cannot diagnose a server that was already unhealthy.

3. Select one plugin without hard-coding a stale tutorial artifact

Open the current Community Build Plugin version matrix or Marketplace from this disposable server. Choose a nonessential open-source plugin that explicitly lists compatibility with the exact Community Build release. The course intentionally does not hard-code a third-party JAR version: hard-coding today’s plugin would turn into tomorrow’s abandoned-plugin tutorial.

Record these shell variables from the verified current release. They are values, not secrets:

export PLUGIN_KEY="<manifest-plugin-key>"
export PLUGIN_VERSION="<exact-version>"
export PLUGIN_JAR="<exact-file-name.jar>"
export PLUGIN_URL="<immutable-release-asset-url>"
export EXPECTED_SHA256="<publisher-or-independently-recorded-sha256>"
export PLUGIN_LICENSE="<license-name>"
export PLUGIN_SOURCE="<canonical-source-repository>"
Selection gate. If you cannot establish an immutable source, compatible release, acceptable license, maintained repository, and trustworthy digest/signature evidence, choose a different plugin or stop. “It appears in an old article” and “a file with that name exists on the Internet” are not approvals.

4. Stage and verify before copying anything into SonarQube

curl -fL "$PLUGIN_URL" -o "evidence/stage/$PLUGIN_JAR"
ACTUAL_SHA256="$(sha256sum "evidence/stage/$PLUGIN_JAR" | awk '{print $1}')"
printf 'expected=%s\nactual=%s\n' "$EXPECTED_SHA256" "$ACTUAL_SHA256" \
  | tee evidence/stage/checksum.txt

test "$ACTUAL_SHA256" = "$EXPECTED_SHA256" || {
  echo "ABORT: plugin checksum mismatch" | tee evidence/stage/preflight-failure.txt
  exit 42
}

# A JAR is a ZIP. Capture manifest metadata without executing it.
unzip -p "evidence/stage/$PLUGIN_JAR" META-INF/MANIFEST.MF \
  | tee evidence/stage/manifest.txt

grep -E '^(Plugin-Key|Plugin-Name|Plugin-Version|Plugin-License|Sonar-Version|Plugin-RequiredForLanguages):' \
  evidence/stage/manifest.txt | tee evidence/stage/manifest-governance.txt

cat > evidence/stage/provenance.txt <<EOF
plugin_key=$PLUGIN_KEY
plugin_version=$PLUGIN_VERSION
artifact=$PLUGIN_JAR
url=$PLUGIN_URL
sha256=$ACTUAL_SHA256
license=$PLUGIN_LICENSE
source=$PLUGIN_SOURCE
compatibility=verified against current Community Build plugin version matrix
EOF

This inspection is intentionally read-only. A manifest is publisher-supplied metadata, so compare it to the external release and source record rather than treating the manifest as self-authenticating proof.

5. Controlled first failure: prove the integrity gate actually stops the change

Before the successful install, deliberately test the preflight with a fake digest. This creates useful failure evidence without making the server unhealthy.

REAL_SHA256="$EXPECTED_SHA256"
EXPECTED_SHA256="$(printf '0%.0s' {1..64})"
ACTUAL_SHA256="$(sha256sum "evidence/stage/$PLUGIN_JAR" | awk '{print $1}')"
if test "$ACTUAL_SHA256" = "$EXPECTED_SHA256"; then
  echo "UNEXPECTED: mismatch test did not fail"; exit 99
else
  printf 'PRE-INSTALL BLOCKED\nexpected=%s\nactual=%s\n' \
    "$EXPECTED_SHA256" "$ACTUAL_SHA256" \
    | tee evidence/stage/deliberate-checksum-failure.txt
fi
EXPECTED_SHA256="$REAL_SHA256"

The correct repair is to restore the verified digest—not to disable the checksum step. Notice that the SonarQube container has not been mutated, so failure isolation is strong.

6. Install the verified artifact and perform the required restart

A plugin change requires a restart to take effect. This is a deliberate lifecycle action on a disposable server, not a blanket “restart until it works” troubleshooting shortcut.

# Preserve an independent copy outside the extensions volume for rollback/audit.
cp "evidence/stage/$PLUGIN_JAR" "evidence/rollback/$PLUGIN_JAR"

# Copy only the verified bytes.
docker cp "evidence/stage/$PLUGIN_JAR" \
  "$LAB_NAME:/opt/sonarqube/extensions/plugins/$PLUGIN_JAR"

docker exec "$LAB_NAME" sh -c \
  'ls -lah /opt/sonarqube/extensions/plugins' \
  | tee evidence/installed/plugin-files-before-restart.txt

docker restart "$LAB_NAME" | tee evidence/installed/restart.txt
# Wait for UP; preserve logs if it does not return.
curl -fsS "$LAB_URL/api/system/status" | tee evidence/installed/system-status.json
docker logs "$LAB_NAME" > evidence/installed/container.log 2>&1

grep -Ei "plugin|deploy|$PLUGIN_KEY|$PLUGIN_VERSION" \
  evidence/installed/container.log \
  | tail -n 100 > evidence/installed/plugin-startup-excerpt.txt || true

Three facts are distinct: the file exists, SonarQube restarted, and the plugin loaded successfully. Require all three. If the startup fails, preserve the entire log, remove only the newly introduced JAR, and follow the rollback procedure below. Do not delete all extensions or data volumes.

7. Verify capability and runtime evidence

Verify the plugin in Administration → Marketplace/installed plugins or through the current installed-plugin Web API with a bounded read-capable user token. Also verify the plugin’s intended benign capability—for example, a language/rule repository becoming visible—without pointing the lab at proprietary source.

# If forced authentication is enabled, create a disposable read-capable user token
# in this lab and export it without printing it.
curl -fsS -H "Authorization: Bearer $SONAR_API_TOKEN" \
  "$LAB_URL/api/plugins/installed" \
  | python -m json.tool \
  | tee evidence/installed/plugins-installed.json

# Optional read-only rule repository inventory for analyzer plugins:
curl -fsS -H "Authorization: Bearer $SONAR_API_TOKEN" \
  "$LAB_URL/api/rules/repositories" \
  | python -m json.tool \
  > evidence/installed/rule-repositories.json

Do not infer that a plugin works merely because the dashboard renders. A scanner-side analyzer should also be validated by a tiny synthetic source fixture if its real purpose is analysis. Preserve the scanner process result separately from report-task.txt/ceTaskId, Compute Engine completion, and any resulting Quality Gate.

8. Remove and prove return to baseline

docker exec "$LAB_NAME" sh -c \
  "rm -f '/opt/sonarqube/extensions/plugins/$PLUGIN_JAR'"
docker restart "$LAB_NAME" | tee evidence/rollback/restart.txt
curl -fsS "$LAB_URL/api/system/status" | tee evidence/rollback/system-status.json
docker exec "$LAB_NAME" sh -c 'ls -lah /opt/sonarqube/extensions/plugins' \
  | tee evidence/rollback/plugin-files.txt
docker logs "$LAB_NAME" > evidence/rollback/container.log 2>&1

diff -u evidence/baseline/plugin-files.txt evidence/rollback/plugin-files.txt \
  > evidence/rollback/plugin-inventory.diff || true

Review the diff rather than demanding byte-for-byte equality from transient timestamps. The acceptance criterion is that the introduced JAR is absent, the server is UP, the baseline plugin set is restored, and the capability introduced only by the plugin is no longer present.

9. Explicit cleanup

docker rm -f "$LAB_NAME"
docker volume rm sq-ch26-data sq-ch26-logs sq-ch26-extensions
unset SONAR_API_TOKEN EXPECTED_SHA256 PLUGIN_URL PLUGIN_JAR PLUGIN_KEY PLUGIN_VERSION

Keep the evidence/ directory if it is your checkpoint packet. Delete only the volumes created by this lab. Never use broad docker system prune as course cleanup.

10. Challenge: choose the correct layer

The JAR checksum and manifest are correct, but after restart the web process reports an API incompatibility and never reaches UP. Which state should you change first?

Answer pattern: preserve the exact server/plugin versions and startup logs; remove only the newly introduced plugin JAR; restart once to prove baseline recovery; then re-evaluate the compatibility matrix/plugin release. Do not edit the database/search index, install an older server blindly, or try random plugin versions.

Knowledge check

Why does the lab inject a bad checksum before the real install?

A JAR exists in extensions/plugins. Is installation proven?

Why preserve an external copy of the exact plugin artifact?

Should a failed plugin startup be repaired by clearing the database?

What makes the deliberate restart acceptable here?

Next lesson

Choose the smallest safe extension boundary

Lesson 3 compares built-in analyzers, server plugins, external report integrations, and CI tooling as different coupling choices.

Official references and version notes

Version and compatibility note

Rechecked 2026-09-08. Mandatory examples target SonarQube Community Build 26.9.0.129388 using the official sonarqube:26.9.0.129388-community Docker image. Community Build supports Marketplace and manual plugin installation; current commercial SonarQube Server documentation requires manual installation. Third-party plugins are not provided by Sonar and are installed at the operator’s risk. Compatibility must be checked against the current Plugin version matrix and plugin release documentation before every install/upgrade. The checkpoint deliberately chooses the plugin at run time rather than hard-coding a third-party version that may become abandoned. The server image contains its supported Java runtime; outside the image, current Community Build 26.x requires Java 21+. No external database, production cluster, identity provider, reverse proxy, or paid capability is required. Data Center plugin consistency is taught as an optional/simulated commercial path.

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.