From 681f62b082d65bfb672bbe4af08a22ea00efb82c Mon Sep 17 00:00:00 2001 From: Martijn Kamphuis Date: Mon, 9 Nov 2026 16:24:38 +0100 Subject: [PATCH 1/4] Adds SonarQube checks --- .github/workflows/sonarqube.yml | 122 +++++++++++++++++++++ CHANGELOG.md | 3 + README.md | 3 + templates/workflows/status-checks-go.yml | 14 ++- templates/workflows/status-checks-node.yml | 14 ++- 5 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/sonarqube.yml diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 0000000..95d17ae --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,122 @@ +# Reusable workflow: SonarQube Cloud analysis (code smells, bugs, +# security hotspots, duplication) with the quality gate as a status check. +# +# Only runs on public repositories, where SonarQube Cloud is free. On a +# private repository, on schedule and merge_group events, and on pull +# requests that get no secrets (forks, Dependabot without a Dependabot +# secret), the job succeeds without analysing, so it can sit in an +# "all checks passed" gate. +# +# Setup per repository: +# 1. Import the repository in the privacybydesign organization on +# sonarcloud.io and turn off Automatic Analysis (Administration > +# Analysis Method): SonarQube Cloud rejects CI results while it is on. +# 2. Make SONAR_TOKEN available to the repository (organization or +# repository secret, and as a Dependabot secret for Dependabot PRs). +# +# Required caller permissions: contents: read, pull-requests: read +# Required secret: SONAR_TOKEN + +name: SonarQube + +on: + workflow_call: + inputs: + organization: + description: SonarQube Cloud organization key. + type: string + default: privacybydesign + project-key: + description: SonarQube Cloud project key. Empty = _, the key SonarQube Cloud gives an imported repository. + type: string + default: "" + working-directory: + description: Analysis base directory (sonar.projectBaseDir). + type: string + default: . + args: + description: Extra scanner arguments, e.g. "-Dsonar.go.coverage.reportPaths=coverage.out -Dsonar.exclusions=**/testdata/**". + type: string + default: "" + quality-gate: + description: Fail the job when the quality gate fails (sonar.qualitygate.wait). + type: boolean + default: true + timeout-minutes: + type: number + default: 15 + secrets: + SONAR_TOKEN: + required: false + +permissions: {} + +jobs: + sonarqube: + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout-minutes }} + concurrency: + group: sonarqube-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: + contents: read + pull-requests: read + steps: + - name: Decide whether to analyse + id: gate + env: + PRIVATE: ${{ github.event.repository.private }} + EVENT: ${{ github.event_name }} + HAS_TOKEN: ${{ secrets.SONAR_TOKEN != '' }} + FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + ACTOR: ${{ github.actor }} + run: | + run=true + if [ "$PRIVATE" = "true" ]; then + echo "::notice::Private repository: SonarQube analysis skipped." + run=false + elif [ "$EVENT" = "schedule" ] || [ "$EVENT" = "merge_group" ]; then + echo "SonarQube analysis does not run on $EVENT events." + run=false + elif [ "$HAS_TOKEN" != "true" ]; then + if [ "$EVENT" = "pull_request" ] && { [ "$FORK" = "true" ] || [ "$ACTOR" = "dependabot[bot]" ]; }; then + echo "::notice::No SONAR_TOKEN for this pull request (fork or Dependabot): SonarQube analysis skipped." + run=false + else + echo "::error::SONAR_TOKEN is not set. Add it as a secret and pass it to this workflow (secrets: SONAR_TOKEN)." + exit 1 + fi + fi + echo "run=$run" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + if: steps.gate.outputs.run == 'true' + with: + fetch-depth: 0 # blame information for new-code detection + persist-credentials: false + + - name: Scanner arguments + if: steps.gate.outputs.run == 'true' + id: args + env: + ORGANIZATION: ${{ inputs.organization }} + PROJECT_KEY: ${{ inputs.project-key || format('{0}_{1}', github.repository_owner, github.event.repository.name) }} + QUALITY_GATE: ${{ inputs.quality-gate }} + EXTRA_ARGS: ${{ inputs.args }} + run: | + args="-Dsonar.organization=$ORGANIZATION -Dsonar.projectKey=$PROJECT_KEY" + if [ "$QUALITY_GATE" = "true" ]; then + args="$args -Dsonar.qualitygate.wait=true" + fi + if [ -n "$EXTRA_ARGS" ]; then + args="$args $EXTRA_ARGS" + fi + echo "args=$args" >> "$GITHUB_OUTPUT" + + - uses: SonarSource/sonarqube-scan-action@4d1dc0c91ee400dcf8a6dddf2f705ac36542e304 # v8.4.0 + if: steps.gate.outputs.run == 'true' + with: + projectBaseDir: ${{ inputs.working-directory }} + args: ${{ steps.args.outputs.args }} + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 5317c99..a13e609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- `sonarqube.yml`: new reusable workflow. SonarQube Cloud analysis that fails on a failed quality gate, for public repositories. Private repositories, schedule and merge-queue runs, and pull requests without the `SONAR_TOKEN` secret (forks, Dependabot) succeed without analysing. +- Status checks templates: a `sonarqube` job, included in `Status checks passed`. + - `templates/dependabot.yml`: security updates come as one grouped PR per ecosystem (`applies-to: security-updates`) instead of one PR per package. The version-update groups never applied to them. - Caller templates: a new push to a pull request cancels that pull request's previous Status checks run (workflow-level `concurrency`). Push, schedule and merge-queue runs are never cancelled. - Templates: no `codeql` job. Public repositories in the organization get CodeQL through default setup, which rejects results from `codeql.yml`. diff --git a/README.md b/README.md index afc6075..cbe7d9e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Reusable GitHub Actions workflows for the [privacybydesign](https://github.com/p | `terraform.yml` | fmt, validate, tflint, Checkov, plan and apply | | `codeql.yml` | CodeQL for Go, JavaScript/TypeScript and workflow files, for repositories without CodeQL default setup | | `dependency-review.yml` | Blocks pull requests that add vulnerable or disallowed-licence dependencies | +| `sonarqube.yml` | SonarQube Cloud analysis with the quality gate as a check, on public repositories | | `workflow-lint.yml` | actionlint and zizmor | | `pr-title.yml` | Checks that pull request titles follow Conventional Commits | | `release-please.yml` | Release pull requests and GitHub releases | @@ -48,6 +49,8 @@ The inputs of each workflow are documented in its file, under `on.workflow_call. On private repositories, artifact attestations are skipped, and `codeql.yml` and `dependency-review.yml` need GitHub Code Security. +The Status checks templates include a `sonarqube` job. On a public repository it needs the `SONAR_TOKEN` secret, the repository imported in the privacybydesign organization on [SonarQube Cloud](https://sonarcloud.io), and Automatic Analysis turned off there; it then fails when the quality gate fails. On a private repository it succeeds without analysing, so you can keep or remove it. Pull requests without access to the secret (forks, Dependabot without a Dependabot secret) skip the analysis. + In the privacybydesign organization, public repositories get CodeQL through GitHub's default setup (security configuration "GitHub recommended"), so the templates leave out the `codeql` job: with default setup enabled, GitHub rejects results from a separate CodeQL workflow. ## Versioning diff --git a/templates/workflows/status-checks-go.yml b/templates/workflows/status-checks-go.yml index 4c32abb..b22697f 100644 --- a/templates/workflows/status-checks-go.yml +++ b/templates/workflows/status-checks-go.yml @@ -52,12 +52,24 @@ jobs: permissions: contents: read + # SonarQube Cloud quality gate. Public repositories only: on a private + # repository it succeeds without analysing. Needs the SONAR_TOKEN secret. + sonarqube: + uses: privacybydesign/github-workflows/.github/workflows/sonarqube.yml@v1 + permissions: + contents: read + pull-requests: read + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + # with: + # args: -Dsonar.exclusions=**/testdata/** + # Single required check for rulesets/branch protection: job names inside this # workflow can change without touching the ruleset. passed: name: Status checks passed if: always() - needs: [go, docker, dependency-review, workflow-lint] + needs: [go, docker, dependency-review, workflow-lint, sonarqube] runs-on: ubuntu-latest timeout-minutes: 5 permissions: {} diff --git a/templates/workflows/status-checks-node.yml b/templates/workflows/status-checks-node.yml index bc021dd..f91aae4 100644 --- a/templates/workflows/status-checks-node.yml +++ b/templates/workflows/status-checks-node.yml @@ -53,12 +53,24 @@ jobs: permissions: contents: read + # SonarQube Cloud quality gate. Public repositories only: on a private + # repository it succeeds without analysing. Needs the SONAR_TOKEN secret. + sonarqube: + uses: privacybydesign/github-workflows/.github/workflows/sonarqube.yml@v1 + permissions: + contents: read + pull-requests: read + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + # with: + # args: -Dsonar.exclusions=**/testdata/** + # Single required check for rulesets/branch protection: job names inside this # workflow can change without touching the ruleset. passed: name: Status checks passed if: always() - needs: [node, docker, dependency-review, workflow-lint] + needs: [node, docker, dependency-review, workflow-lint, sonarqube] runs-on: ubuntu-latest timeout-minutes: 5 permissions: {} From a08b816a5788a4d048480a9e4528a63fc44d9043 Mon Sep 17 00:00:00 2001 From: Martijn Kamphuis Date: Mon, 9 Nov 2026 16:38:23 +0100 Subject: [PATCH 2/4] Add SQ exclusion paths --- .github/workflows/sonarqube.yml | 25 ++++++++++++++++------ CHANGELOG.md | 3 +-- README.md | 2 +- templates/workflows/status-checks-go.yml | 8 +++++-- templates/workflows/status-checks-node.yml | 10 +++++++-- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 95d17ae..b52a0fa 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -2,10 +2,11 @@ # security hotspots, duplication) with the quality gate as a status check. # # Only runs on public repositories, where SonarQube Cloud is free. On a -# private repository, on schedule and merge_group events, and on pull -# requests that get no secrets (forks, Dependabot without a Dependabot -# secret), the job succeeds without analysing, so it can sit in an -# "all checks passed" gate. +# private repository, on merge_group events, and on pull requests that get +# no secrets (forks, Dependabot without a Dependabot secret), the job +# succeeds without analysing, so it can sit in an "all checks passed" gate. +# Scheduled runs analyse the default branch again with SonarQube Cloud's +# current rules. # # Setup per repository: # 1. Import the repository in the privacybydesign organization on @@ -34,8 +35,14 @@ on: description: Analysis base directory (sonar.projectBaseDir). type: string default: . + exclusions: + description: >- + Comma-separated paths SonarQube does not analyse (sonar.exclusions), as + globs relative to working-directory, e.g. "**/testdata/**,docs/**". Empty = none. + type: string + default: "" args: - description: Extra scanner arguments, e.g. "-Dsonar.go.coverage.reportPaths=coverage.out -Dsonar.exclusions=**/testdata/**". + description: Extra scanner arguments, e.g. "-Dsonar.go.coverage.reportPaths=coverage.out". type: string default: "" quality-gate: @@ -75,7 +82,7 @@ jobs: if [ "$PRIVATE" = "true" ]; then echo "::notice::Private repository: SonarQube analysis skipped." run=false - elif [ "$EVENT" = "schedule" ] || [ "$EVENT" = "merge_group" ]; then + elif [ "$EVENT" = "merge_group" ]; then echo "SonarQube analysis does not run on $EVENT events." run=false elif [ "$HAS_TOKEN" != "true" ]; then @@ -102,9 +109,15 @@ jobs: ORGANIZATION: ${{ inputs.organization }} PROJECT_KEY: ${{ inputs.project-key || format('{0}_{1}', github.repository_owner, github.event.repository.name) }} QUALITY_GATE: ${{ inputs.quality-gate }} + EXCLUSIONS: ${{ inputs.exclusions }} EXTRA_ARGS: ${{ inputs.args }} run: | args="-Dsonar.organization=$ORGANIZATION -Dsonar.projectKey=$PROJECT_KEY" + # Strip whitespace so "a, b" and multi-line YAML values work too. + exclusions=$(printf '%s' "$EXCLUSIONS" | tr -d '[:space:]') + if [ -n "$exclusions" ]; then + args="$args -Dsonar.exclusions=$exclusions" + fi if [ "$QUALITY_GATE" = "true" ]; then args="$args -Dsonar.qualitygate.wait=true" fi diff --git a/CHANGELOG.md b/CHANGELOG.md index a13e609..106a9fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,7 @@ ## Unreleased -- `sonarqube.yml`: new reusable workflow. SonarQube Cloud analysis that fails on a failed quality gate, for public repositories. Private repositories, schedule and merge-queue runs, and pull requests without the `SONAR_TOKEN` secret (forks, Dependabot) succeed without analysing. -- Status checks templates: a `sonarqube` job, included in `Status checks passed`. +- `sonarqube.yml`: new reusable workflow. SonarQube Cloud analysis that fails on a failed quality gate, for public repositories. Private repositories, merge-queue runs, and pull requests without the `SONAR_TOKEN` secret (forks, Dependabot) succeed without analysing. Paths to leave out go in the `exclusions` input (comma-separated globs). - `templates/dependabot.yml`: security updates come as one grouped PR per ecosystem (`applies-to: security-updates`) instead of one PR per package. The version-update groups never applied to them. - Caller templates: a new push to a pull request cancels that pull request's previous Status checks run (workflow-level `concurrency`). Push, schedule and merge-queue runs are never cancelled. diff --git a/README.md b/README.md index cbe7d9e..ebeadd3 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The inputs of each workflow are documented in its file, under `on.workflow_call. On private repositories, artifact attestations are skipped, and `codeql.yml` and `dependency-review.yml` need GitHub Code Security. -The Status checks templates include a `sonarqube` job. On a public repository it needs the `SONAR_TOKEN` secret, the repository imported in the privacybydesign organization on [SonarQube Cloud](https://sonarcloud.io), and Automatic Analysis turned off there; it then fails when the quality gate fails. On a private repository it succeeds without analysing, so you can keep or remove it. Pull requests without access to the secret (forks, Dependabot without a Dependabot secret) skip the analysis. +The Status checks templates include a `sonarqube` job. On a public repository it needs the `SONAR_TOKEN` secret, the repository imported in the privacybydesign organization on [SonarQube Cloud](https://sonarcloud.io), and Automatic Analysis turned off there; it then fails when the quality gate fails. On a private repository it succeeds without analysing, so you can keep or remove it. Pull requests without access to the secret (forks, Dependabot without a Dependabot secret) skip the analysis. To leave paths out of the analysis without editing the template, set the Actions variable `SONAR_EXCLUSIONS` in the repository to comma-separated globs (for example `**/testdata/**,docs/**`); it replaces the template default, which every run uses otherwise, scheduled runs included. In the privacybydesign organization, public repositories get CodeQL through GitHub's default setup (security configuration "GitHub recommended"), so the templates leave out the `codeql` job: with default setup enabled, GitHub rejects results from a separate CodeQL workflow. diff --git a/templates/workflows/status-checks-go.yml b/templates/workflows/status-checks-go.yml index b22697f..6b954e1 100644 --- a/templates/workflows/status-checks-go.yml +++ b/templates/workflows/status-checks-go.yml @@ -61,8 +61,12 @@ jobs: pull-requests: read secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - # with: - # args: -Dsonar.exclusions=**/testdata/** + with: + # Comma-separated paths SonarQube does not analyse (globs, relative to the repository root). + # Set them per repository with the SONAR_EXCLUSIONS variable (Settings > Secrets and + # variables > Actions > Variables) instead of editing this file. Without it, + # every run, scheduled runs included, uses the default at the end of this line. + exclusions: ${{ vars.SONAR_EXCLUSIONS || '**/testdata/**' }} # Single required check for rulesets/branch protection: job names inside this # workflow can change without touching the ruleset. diff --git a/templates/workflows/status-checks-node.yml b/templates/workflows/status-checks-node.yml index f91aae4..bb79102 100644 --- a/templates/workflows/status-checks-node.yml +++ b/templates/workflows/status-checks-node.yml @@ -62,8 +62,14 @@ jobs: pull-requests: read secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - # with: - # args: -Dsonar.exclusions=**/testdata/** + with: + # Comma-separated paths SonarQube does not analyse (globs, relative to the repository root). + # Set them per repository with the SONAR_EXCLUSIONS variable (Settings > Secrets and + # variables > Actions > Variables) instead of editing this file. Without it, + # every run, scheduled runs included, uses the default at the end of this line. + # Example: "**/generated/**,public/vendor/**" + exclusions: ${{ vars.SONAR_EXCLUSIONS || '' }} + # args: -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info # Single required check for rulesets/branch protection: job names inside this # workflow can change without touching the ruleset. From b7e9f22e0917b3fa9b52a6202c92b7302fa935cd Mon Sep 17 00:00:00 2001 From: Martijn Kamphuis Date: Mon, 9 Nov 2026 16:57:56 +0100 Subject: [PATCH 3/4] Limit SQ scanning only for default branch --- .github/workflows/sonarqube.yml | 16 ++++++++++++++-- CHANGELOG.md | 2 +- README.md | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index b52a0fa..e629ea6 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,8 +1,10 @@ # Reusable workflow: SonarQube Cloud analysis (code smells, bugs, # security hotspots, duplication) with the quality gate as a status check. # -# Only runs on public repositories, where SonarQube Cloud is free. On a -# private repository, on merge_group events, and on pull requests that get +# Only runs on public repositories, where SonarQube Cloud is free, and only +# on the default branch and on pull requests into the default branch. On a +# private repository, on other branches (pushes, manual runs, pull requests +# into another branch), on merge_group events, and on pull requests that get # no secrets (forks, Dependabot without a Dependabot secret), the job # succeeds without analysing, so it can sit in an "all checks passed" gate. # Scheduled runs analyse the default branch again with SonarQube Cloud's @@ -77,6 +79,9 @@ jobs: HAS_TOKEN: ${{ secrets.SONAR_TOKEN != '' }} FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} ACTOR: ${{ github.actor }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + REF: ${{ github.ref }} + BASE_REF: ${{ github.event.pull_request.base.ref }} run: | run=true if [ "$PRIVATE" = "true" ]; then @@ -85,6 +90,13 @@ jobs: elif [ "$EVENT" = "merge_group" ]; then echo "SonarQube analysis does not run on $EVENT events." run=false + elif [ -n "$BASE_REF" ] && [ "$BASE_REF" != "$DEFAULT_BRANCH" ]; then + echo "Pull request targets $BASE_REF, not the default branch ($DEFAULT_BRANCH): SonarQube analysis skipped." + run=false + elif [ -z "$BASE_REF" ] && [ "$EVENT" != "schedule" ] && [ "$REF" != "refs/heads/$DEFAULT_BRANCH" ]; then + # Scheduled runs always use the default branch; their payload has no repository object. + echo "$REF is not the default branch ($DEFAULT_BRANCH): SonarQube analysis skipped." + run=false elif [ "$HAS_TOKEN" != "true" ]; then if [ "$EVENT" = "pull_request" ] && { [ "$FORK" = "true" ] || [ "$ACTOR" = "dependabot[bot]" ]; }; then echo "::notice::No SONAR_TOKEN for this pull request (fork or Dependabot): SonarQube analysis skipped." diff --git a/CHANGELOG.md b/CHANGELOG.md index 106a9fe..881ec57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- `sonarqube.yml`: new reusable workflow. SonarQube Cloud analysis that fails on a failed quality gate, for public repositories. Private repositories, merge-queue runs, and pull requests without the `SONAR_TOKEN` secret (forks, Dependabot) succeed without analysing. Paths to leave out go in the `exclusions` input (comma-separated globs). +- `sonarqube.yml`: new reusable workflow. SonarQube Cloud analysis that fails on a failed quality gate, for public repositories, on the default branch and pull requests into it. Private repositories, other branches, merge-queue runs, and pull requests without the `SONAR_TOKEN` secret (forks, Dependabot) succeed without analysing. Paths to leave out go in the `exclusions` input (comma-separated globs). - `templates/dependabot.yml`: security updates come as one grouped PR per ecosystem (`applies-to: security-updates`) instead of one PR per package. The version-update groups never applied to them. - Caller templates: a new push to a pull request cancels that pull request's previous Status checks run (workflow-level `concurrency`). Push, schedule and merge-queue runs are never cancelled. diff --git a/README.md b/README.md index ebeadd3..1125284 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The inputs of each workflow are documented in its file, under `on.workflow_call. On private repositories, artifact attestations are skipped, and `codeql.yml` and `dependency-review.yml` need GitHub Code Security. -The Status checks templates include a `sonarqube` job. On a public repository it needs the `SONAR_TOKEN` secret, the repository imported in the privacybydesign organization on [SonarQube Cloud](https://sonarcloud.io), and Automatic Analysis turned off there; it then fails when the quality gate fails. On a private repository it succeeds without analysing, so you can keep or remove it. Pull requests without access to the secret (forks, Dependabot without a Dependabot secret) skip the analysis. To leave paths out of the analysis without editing the template, set the Actions variable `SONAR_EXCLUSIONS` in the repository to comma-separated globs (for example `**/testdata/**,docs/**`); it replaces the template default, which every run uses otherwise, scheduled runs included. +The Status checks templates include a `sonarqube` job. On a public repository it needs the `SONAR_TOKEN` secret, the repository imported in the privacybydesign organization on [SonarQube Cloud](https://sonarcloud.io), and Automatic Analysis turned off there; it then fails when the quality gate fails. It only analyses the default branch and pull requests into the default branch; runs on other branches succeed without analysing. On a private repository it succeeds without analysing, so you can keep or remove it. Pull requests without access to the secret (forks, Dependabot without a Dependabot secret) skip the analysis. To leave paths out of the analysis without editing the template, set the Actions variable `SONAR_EXCLUSIONS` in the repository to comma-separated globs (for example `**/testdata/**,docs/**`); it replaces the template default, which every run uses otherwise, scheduled runs included. In the privacybydesign organization, public repositories get CodeQL through GitHub's default setup (security configuration "GitHub recommended"), so the templates leave out the `codeql` job: with default setup enabled, GitHub rejects results from a separate CodeQL workflow. From 2d954c059f416d435f7460c363a2c3740a8a7d8c Mon Sep 17 00:00:00 2001 From: Martijn Kamphuis Date: Mon, 9 Nov 2026 17:05:20 +0100 Subject: [PATCH 4/4] Add SQ opt-in for private repos --- .github/workflows/sonarqube.yml | 24 ++++++++++++++++-------- CHANGELOG.md | 2 +- README.md | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index e629ea6..fc82d44 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -2,19 +2,26 @@ # security hotspots, duplication) with the quality gate as a status check. # # Only runs on public repositories, where SonarQube Cloud is free, and only -# on the default branch and on pull requests into the default branch. On a -# private repository, on other branches (pushes, manual runs, pull requests -# into another branch), on merge_group events, and on pull requests that get -# no secrets (forks, Dependabot without a Dependabot secret), the job +# on the default branch and on pull requests into the default branch. A +# private repository opts in with the Actions variable +# SONAR_SCAN_PRIVATE_REPO set to "true" (needs a paid SonarQube Cloud plan). +# Our subscription allows 50k lines of code for private repositories, so +# check the remaining allowance in SonarQube Cloud before opting in. +# On a private repository without that opt-in, on other branches (pushes, +# manual runs, pull requests into another branch), on merge_group events, +# and on pull requests that get no secrets (forks, Dependabot without a Dependabot secret), the job # succeeds without analysing, so it can sit in an "all checks passed" gate. # Scheduled runs analyse the default branch again with SonarQube Cloud's # current rules. # # Setup per repository: -# 1. Import the repository in the privacybydesign organization on +# 1. Add the repository to "Selected repositories" of the SonarQube Cloud +# GitHub App (organization Settings > GitHub Apps > SonarQube Cloud > +# Configure); otherwise SonarQube Cloud can't see it to import it. +# 2. Import the repository in the privacybydesign organization on # sonarcloud.io and turn off Automatic Analysis (Administration > # Analysis Method): SonarQube Cloud rejects CI results while it is on. -# 2. Make SONAR_TOKEN available to the repository (organization or +# 3. Make SONAR_TOKEN available to the repository (organization or # repository secret, and as a Dependabot secret for Dependabot PRs). # # Required caller permissions: contents: read, pull-requests: read @@ -75,6 +82,7 @@ jobs: id: gate env: PRIVATE: ${{ github.event.repository.private }} + SCAN_PRIVATE: ${{ vars.SONAR_SCAN_PRIVATE_REPO }} EVENT: ${{ github.event_name }} HAS_TOKEN: ${{ secrets.SONAR_TOKEN != '' }} FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} @@ -84,8 +92,8 @@ jobs: BASE_REF: ${{ github.event.pull_request.base.ref }} run: | run=true - if [ "$PRIVATE" = "true" ]; then - echo "::notice::Private repository: SonarQube analysis skipped." + if [ "$PRIVATE" = "true" ] && [ "$SCAN_PRIVATE" != "true" ]; then + echo "::notice::Private repository: SonarQube analysis skipped. Set the Actions variable SONAR_SCAN_PRIVATE_REPO to true to analyse it." run=false elif [ "$EVENT" = "merge_group" ]; then echo "SonarQube analysis does not run on $EVENT events." diff --git a/CHANGELOG.md b/CHANGELOG.md index 881ec57..80507a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- `sonarqube.yml`: new reusable workflow. SonarQube Cloud analysis that fails on a failed quality gate, for public repositories, on the default branch and pull requests into it. Private repositories, other branches, merge-queue runs, and pull requests without the `SONAR_TOKEN` secret (forks, Dependabot) succeed without analysing. Paths to leave out go in the `exclusions` input (comma-separated globs). +- `sonarqube.yml`: new reusable workflow. SonarQube Cloud analysis that fails on a failed quality gate, for public repositories, on the default branch and pull requests into it. Private repositories opt in with the Actions variable `SONAR_SCAN_PRIVATE_REPO` set to `true`. Private repositories without it, other branches, merge-queue runs, and pull requests without the `SONAR_TOKEN` secret (forks, Dependabot) succeed without analysing. Paths to leave out go in the `exclusions` input (comma-separated globs). - `templates/dependabot.yml`: security updates come as one grouped PR per ecosystem (`applies-to: security-updates`) instead of one PR per package. The version-update groups never applied to them. - Caller templates: a new push to a pull request cancels that pull request's previous Status checks run (workflow-level `concurrency`). Push, schedule and merge-queue runs are never cancelled. diff --git a/README.md b/README.md index 1125284..075c4cb 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The inputs of each workflow are documented in its file, under `on.workflow_call. On private repositories, artifact attestations are skipped, and `codeql.yml` and `dependency-review.yml` need GitHub Code Security. -The Status checks templates include a `sonarqube` job. On a public repository it needs the `SONAR_TOKEN` secret, the repository imported in the privacybydesign organization on [SonarQube Cloud](https://sonarcloud.io), and Automatic Analysis turned off there; it then fails when the quality gate fails. It only analyses the default branch and pull requests into the default branch; runs on other branches succeed without analysing. On a private repository it succeeds without analysing, so you can keep or remove it. Pull requests without access to the secret (forks, Dependabot without a Dependabot secret) skip the analysis. To leave paths out of the analysis without editing the template, set the Actions variable `SONAR_EXCLUSIONS` in the repository to comma-separated globs (for example `**/testdata/**,docs/**`); it replaces the template default, which every run uses otherwise, scheduled runs included. +The Status checks templates include a `sonarqube` job. On a public repository it needs the `SONAR_TOKEN` secret, the repository imported in the privacybydesign organization on [SonarQube Cloud](https://sonarcloud.io), and Automatic Analysis turned off there. Don't forget to add the repository to `Selected repositories` of the SonarQube Cloud GitHub App (organization Settings > GitHub Apps > SonarQube Cloud > Configure), or SonarQube Cloud can't import it. The job then fails when the quality gate fails. It only analyses the default branch and pull requests into the default branch; runs on other branches succeed without analysing. On a private repository it succeeds without analysing, so you can keep or remove it, unless you set the Actions variable `SONAR_SCAN_PRIVATE_REPO` to `true` in the repository: then it analyses the private repository too (this needs a paid SonarQube Cloud plan). Our subscription allows 50k lines of code for private repositories, so check the remaining allowance in SonarQube Cloud before you opt in. Pull requests without access to the secret (forks, Dependabot without a Dependabot secret) skip the analysis. To leave paths out of the analysis without editing the template, set the Actions variable `SONAR_EXCLUSIONS` in the repository to comma-separated globs (for example `**/testdata/**,docs/**`); it replaces the template default, which every run uses otherwise, scheduled runs included. In the privacybydesign organization, public repositories get CodeQL through GitHub's default setup (security configuration "GitHub recommended"), so the templates leave out the `codeql` job: with default setup enabled, GitHub rejects results from a separate CodeQL workflow.