From 686d9ec44bae78ae7a7434eb7b41bf3dcd2d7b2b Mon Sep 17 00:00:00 2001 From: Markus Becker Date: Thu, 16 Jul 2026 11:40:44 +0200 Subject: [PATCH] chore: make SonarQube + Tailscale non-blocking in Kotlin CI [CPONETOPS-1078] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SonarQube is self-hosted behind the Monta VPN, so the Kotlin CI reuses a Tailscale bring-up step before the scan. Infra outages on that path — an expired/rotated headscale auth key, the Tailscale package CDN returning 504 (AWS CloudFront), or SonarQube being unreachable — currently fail the whole job and block every Kotlin PR and deploy, even though SonarQube is advisory. - pull-request-kotlin.yml: add continue-on-error to the Tailscale step and default sonar-non-blocking to true. Tests and lint still gate; only the VPN bring-up and Sonar upload are best-effort. - sonar-cloud.yml: add a sonar-non-blocking input (default true), make the Tailscale step non-blocking, and split "Build and analyze" so tests + kover still gate while the SonarQube scan is best-effort. - docs/workflow-guide.md: document the new input, default, and TAILSCALE_AUTHKEY. Callers wanting SonarQube to remain a hard gate can pass sonar-non-blocking: false. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_0134BvmGHE9QM5zwSAzfJJUv --- .github/workflows/pull-request-kotlin.yml | 5 +++-- .github/workflows/sonar-cloud.yml | 20 ++++++++++++++++++-- docs/workflow-guide.md | 15 ++++++++++----- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pull-request-kotlin.yml b/.github/workflows/pull-request-kotlin.yml index e50bc7e..3e29803 100644 --- a/.github/workflows/pull-request-kotlin.yml +++ b/.github/workflows/pull-request-kotlin.yml @@ -51,8 +51,8 @@ on: sonar-non-blocking: required: false type: boolean - description: "When true, a failure of the 'Upload results to SonarQube' step does not fail the job" - default: false + description: "When true (default), a failure of the Tailscale bring-up or the 'Upload results to SonarQube' step does not fail the job. SonarQube depends on the Monta VPN, so infra outages (expired auth key, Tailscale package CDN down, SonarQube unreachable) would otherwise block every PR. Set to false to make SonarQube a hard gate again." + default: true gradle-args: required: false type: string @@ -196,6 +196,7 @@ jobs: # automatically for repos still on SonarCloud (no TAILSCALE_AUTHKEY passed). - name: Tailscale if: ${{ !inputs.skip-sonar && env.TAILSCALE_AUTHKEY != '' }} + continue-on-error: ${{ inputs.sonar-non-blocking }} uses: tailscale/github-action@6cae46e2d796f265265cfcf628b72a32b4d7cade # v3.3.0 with: authkey: ${{ env.TAILSCALE_AUTHKEY }} diff --git a/.github/workflows/sonar-cloud.yml b/.github/workflows/sonar-cloud.yml index 434e9d9..9aa4e2a 100644 --- a/.github/workflows/sonar-cloud.yml +++ b/.github/workflows/sonar-cloud.yml @@ -33,6 +33,11 @@ on: type: string default: "--no-daemon --parallel" description: 'Additional Gradle arguments' + sonar-non-blocking: + required: false + type: boolean + description: "When true (default), a failure of the Tailscale bring-up or the SonarQube analysis step does not fail the job; tests still gate. SonarQube depends on the Monta VPN, so infra outages (expired auth key, Tailscale package CDN down, SonarQube unreachable) would otherwise fail the workflow. Set to false to make SonarQube a hard gate again." + default: true secrets: TAILSCALE_AUTHKEY: required: false @@ -97,12 +102,23 @@ jobs: # automatically for repos still on SonarCloud (no TAILSCALE_AUTHKEY passed). - name: Tailscale if: ${{ env.TAILSCALE_AUTHKEY != '' }} + continue-on-error: ${{ inputs.sonar-non-blocking }} uses: tailscale/github-action@6cae46e2d796f265265cfcf628b72a32b4d7cade # v3.3.0 with: authkey: ${{ env.TAILSCALE_AUTHKEY }} hostname: "github-${{ github.run_id }}" args: "--login-server https://headscale.monta.com --accept-routes" - - name: Build and analyze + - name: Run tests with coverage + env: + GHL_USERNAME: ${{ secrets.GHL_USERNAME }} + GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }} + uses: monta-app/github-workflows/.github/actions/gradle-multi-module@main + with: + gradle-module: ${{ inputs.gradle-module }} + gradle-tasks: 'test koverXmlReport' + gradle-args: ${{ inputs.gradle-args }} + - name: Analyze with SonarQube + continue-on-error: ${{ inputs.sonar-non-blocking }} env: GHL_USERNAME: ${{ secrets.GHL_USERNAME }} GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }} @@ -110,7 +126,7 @@ jobs: uses: monta-app/github-workflows/.github/actions/gradle-multi-module@main with: gradle-module: ${{ inputs.gradle-module }} - gradle-tasks: 'test koverXmlReport sonar' + gradle-tasks: 'sonar' gradle-args: ${{ inputs.gradle-args }} - name: Upload build reports if: always() diff --git a/docs/workflow-guide.md b/docs/workflow-guide.md index 0500acf..7fa3cec 100644 --- a/docs/workflow-guide.md +++ b/docs/workflow-guide.md @@ -848,13 +848,15 @@ jobs: | `kover-report-path` | No | "build/reports/kover/report.xml" | Kover report path | | `test-timeout-minutes` | No | 30 | Test timeout | | `skip-sonar` | No | false | Skip SonarCloud analysis | +| `sonar-non-blocking` | No | true | When true, a failure of the Tailscale bring-up or the SonarQube upload does not fail the job (tests still gate). Set to false to make SonarQube a hard gate. | ### Secrets: | Secret | Required | Description | |--------|----------|-------------| +| `TAILSCALE_AUTHKEY` | No | Tailscale auth key. When set, the runner joins the tailnet to reach the self-hosted SonarQube; leave unset to scan SonarCloud. | | `GHL_USERNAME` | Yes | GitHub username | | `GHL_PASSWORD` | Yes | GitHub token | -| `SONAR_TOKEN` | Yes | SonarCloud token | +| `SONAR_TOKEN` | Yes | SonarQube token | ### Example Usage: ```yaml @@ -1022,9 +1024,10 @@ jobs: ### What it does: 1. Checks out code with full history 2. Sets up Java environment -3. Caches SonarCloud packages -4. Runs Kover coverage report -5. Uploads analysis to SonarCloud +3. Caches SonarQube packages +4. Joins the Tailscale VPN (when `TAILSCALE_AUTHKEY` is set) to reach the self-hosted SonarQube +5. Runs tests with Kover coverage (gating) +6. Uploads analysis to SonarQube (best-effort by default) ### Inputs: | Input | Required | Default | Description | @@ -1033,13 +1036,15 @@ jobs: | `use-blacksmith-runners` | No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 | | `java-version` | No | "21" | Java version | | `gradle-module` | No | - | Gradle module name | +| `sonar-non-blocking` | No | true | When true, a failure of the Tailscale bring-up or the SonarQube analysis step does not fail the job (tests still gate). Set to false to make SonarQube a hard gate. | ### Secrets: | Secret | Required | Description | |--------|----------|-------------| +| `TAILSCALE_AUTHKEY` | No | Tailscale auth key. When set, the runner joins the tailnet to reach the self-hosted SonarQube; leave unset to scan SonarCloud. | | `GHL_USERNAME` | Yes | GitHub username | | `GHL_PASSWORD` | Yes | GitHub token | -| `SONAR_TOKEN` | Yes | SonarCloud token | +| `SONAR_TOKEN` | Yes | SonarQube token | ### Example Usage: ```yaml