diff --git a/.github/workflows/CHANGELOGS/run_sonar_scan.md b/.github/workflows/CHANGELOGS/run_sonar_scan.md index 0af1c9e..7ab1149 100644 --- a/.github/workflows/CHANGELOGS/run_sonar_scan.md +++ b/.github/workflows/CHANGELOGS/run_sonar_scan.md @@ -2,6 +2,32 @@ All notable changes to the **run_sonar_scan** callable workflow are documented in this file. +## 2.0.0 + +### Added + +- `coverage-artifact-pattern` input (default `coverage-*`). Every same-run artifact whose name matches + is downloaded into `coverage-artifact-path`, one directory per artifact when more than one matches, + so callers no longer need a coverage merge job or a conditional artifact-name output. +- A warning and a job summary line when no downloaded artifact contains an `lcov.info`; the scan still + runs. +- A check that fails the job when `coverage-artifact-path` already exists with content in the + checkout, so repository files are never reported as coverage. +- A job summary listing the downloaded coverage files. + +### Changed + +- **Breaking:** the job is renamed from `sonar-scan` ("Run SonarQube scan") to `scan` ("Scan"), so the + check reads ` / Scan`. +- **Breaking:** `coverage-artifact-path` defaults to `.sonar-coverage` instead of `.`. +- The job skips runs triggered by `dependabot[bot]` or `renovate[bot]`; callers can drop that condition. +- Workflow permissions default to none, and the job requests only `contents: read`. +- The checkout no longer persists credentials. + +### Removed + +- **Breaking:** the `coverage-artifact-name` input. Use `coverage-artifact-pattern`. + ## 1.3.0 ### Added diff --git a/.github/workflows/READMES/run_sonar_scan.md b/.github/workflows/READMES/run_sonar_scan.md index 775c927..63208e1 100644 --- a/.github/workflows/READMES/run_sonar_scan.md +++ b/.github/workflows/READMES/run_sonar_scan.md @@ -1,84 +1,72 @@ # Run SonarQube Scan -This reusable workflow checks out the ref that triggered the caller, or a specified commit or ref, and runs a SonarQube scan with full Git history. Callers can optionally download a coverage artifact produced earlier in the same workflow run before the scan starts. - -## Prerequisites - -The consuming repository must include its SonarQube configuration, such as a `sonar-project.properties` file, or otherwise provide configuration supported by the scan action. +Runs a SonarQube scan with full Git history, importing coverage artifacts from the same workflow run. +Skips runs by `dependabot[bot]` and `renovate[bot]`. Requires the repo's `sonar-project.properties`. ## Usage ```yaml jobs: - sonar-scan: - uses: OpenSesame/core-github-actions/.github/workflows/run_sonar_scan.yml@workflows/run_sonar_scan/1.3.0 + sonar: + name: Sonar + needs: [ci-context, app-ci] + if: ${{ !cancelled() && needs.ci-context.outputs.should-run-ci == 'true' }} + uses: OpenSesame/core-github-actions/.github/workflows/run_sonar_scan.yml@ # workflows/run_sonar_scan/2.0.0 + permissions: + contents: read # Required to check out the code to scan. + with: + commit-identifier: ${{ needs.ci-context.outputs.commit-identifier }} secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ``` -To scan a different commit, tag, or branch, pass `commit-identifier`: +The check is reported as `Sonar / Scan`. -```yaml -with: - commit-identifier: ${{ github.sha }} -``` +## Coverage -To import coverage produced by another job, upload the report as an artifact and make the Sonar job depend on the test job: +1. Upload each package's lcov report as an artifact named `coverage-`: -```yaml -jobs: - unit-tests: - runs-on: ubuntu-26.04 - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Install dependencies - run: npm ci - - - name: Run tests with coverage - run: npm test -- --coverage - - - name: Upload coverage - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: unit-test-coverage - path: coverage/lcov.info - if-no-files-found: error - - sonar-scan: - needs: unit-tests - uses: OpenSesame/core-github-actions/.github/workflows/run_sonar_scan.yml@ - with: - coverage-artifact-name: unit-test-coverage - coverage-artifact-path: .coverage - secrets: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} -``` + ```yaml + - name: Upload coverage + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-user-management-api + path: lambdas/user-management-api/coverage/lcov.info + if-no-files-found: error + ``` -This downloads the report to `.coverage/lcov.info`. Configure SonarQube to read that location, for example with `sonar.javascript.lcov.reportPaths=.coverage/lcov.info`. The workflow does not run tests or generate coverage. If `coverage-artifact-name` is set but the artifact does not exist, the download step fails the job. +2. If Jest runs outside the repo root, make lcov paths repo-relative so Sonar maps coverage to the + right files: -## Inputs + ```js + // lambdas/user-management-api/jest.config.js + module.exports = { + coverageReporters: [ + ['lcov', { projectRoot: require('node:path').resolve(__dirname, '../..') }], + 'text', + ], + }; + ``` -| Input | Type | Required | Default | Description | -| ------------------------ | ------ | -------- | --------------------- | --------------------------------------------------------------------------- | -| `commit-identifier` | string | No | Triggering ref or SHA | Commit SHA, tag, or branch to check out and scan. | -| `coverage-artifact-name` | string | No | `''` | Name of a coverage artifact uploaded earlier in the same workflow run. | -| `coverage-artifact-path` | string | No | `.` | Directory into which the coverage artifact is downloaded before the scan. | +3. Set the same report path in every repo: -## Secrets + ```properties + sonar.javascript.lcov.reportPaths=.sonar-coverage/**/lcov.info + ``` -| Secret | Required | Description | -| ------------- | -------- | ---------------------------------------------- | -| `SONAR_TOKEN` | Yes | Token used to authenticate the SonarQube scan. | +If no `lcov.info` is downloaded, the job warns and scans without coverage. -The workflow job runs on Ubuntu 26.04. +## Inputs -## Contribution +| Input | Default | Description | +| --------------------------- | --------------------- | ------------------------------------------------------------------------ | +| `commit-identifier` | Triggering ref or SHA | Commit SHA, tag, or branch to scan. | +| `coverage-artifact-pattern` | `coverage-*` | Same-run artifact names to download. Empty skips coverage. | +| `coverage-artifact-path` | `.sonar-coverage` | Download directory. Must not already exist with content in the checkout. | -- Update the workflow, README, and changelog together. -- Create a PR and set a version label following the [versioning instructions](../../../VERSIONING.md). +Secret `SONAR_TOKEN` is required. -## References +## Contribution -- [SonarQube scan action](https://github.com/SonarSource/sonarqube-scan-action) +Update the workflow, README, and changelog together, and set a version label per +[VERSIONING.md](../../../VERSIONING.md). diff --git a/.github/workflows/run_sonar_scan.yml b/.github/workflows/run_sonar_scan.yml index d40dfe1..a2b49c1 100644 --- a/.github/workflows/run_sonar_scan.yml +++ b/.github/workflows/run_sonar_scan.yml @@ -8,42 +8,94 @@ on: type: string required: false default: '' - coverage-artifact-name: - description: Name of a coverage artifact uploaded earlier in the same workflow run + coverage-artifact-pattern: + description: Glob of coverage artifact names uploaded earlier in the same workflow run; empty skips the download type: string required: false - default: '' + default: coverage-* coverage-artifact-path: - description: Directory into which the coverage artifact is downloaded + description: Directory into which matching coverage artifacts are downloaded type: string required: false - default: '.' + default: .sonar-coverage secrets: SONAR_TOKEN: required: true -permissions: - contents: read +permissions: {} + +defaults: + run: + shell: bash jobs: - sonar-scan: - name: Run SonarQube scan + scan: + name: Scan + if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' }} runs-on: ubuntu-26.04 + permissions: + contents: read # Required to check out the code to scan. steps: - - name: Checkout + - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs.commit-identifier }} fetch-depth: 0 + persist-credentials: false - - name: Download coverage artifact - if: ${{ inputs.coverage-artifact-name != '' }} + - name: Check coverage path is empty + if: ${{ inputs.coverage-artifact-pattern != '' }} + env: + COVERAGE_PATH: ${{ inputs.coverage-artifact-path }} + run: | + if [[ -e "$COVERAGE_PATH" && -n "$(ls -A "$COVERAGE_PATH" 2>/dev/null)" ]]; then + echo "::error title=Coverage::coverage-artifact-path '$COVERAGE_PATH' already exists in the checkout and is not empty. Use a directory that only holds downloaded coverage, such as the default .sonar-coverage." + exit 1 + fi + + - name: Download coverage artifacts + if: ${{ inputs.coverage-artifact-pattern != '' }} uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: - name: ${{ inputs.coverage-artifact-name }} + pattern: ${{ inputs.coverage-artifact-pattern }} path: ${{ inputs.coverage-artifact-path }} + - name: Report coverage files + if: ${{ inputs.coverage-artifact-pattern != '' }} + env: + COVERAGE_PATTERN: ${{ inputs.coverage-artifact-pattern }} + COVERAGE_PATH: ${{ inputs.coverage-artifact-path }} + run: | + set -Eeuo pipefail + + files="" + if [[ -d "$COVERAGE_PATH" ]]; then + files=$(find "$COVERAGE_PATH" -type f -name lcov.info | sort) + fi + + if [[ -z "$files" ]]; then + echo "::warning title=Coverage::No lcov.info found in artifacts matching '$COVERAGE_PATTERN'; Sonar will report no coverage" + { + echo "## Coverage" + echo "" + echo "⚠️ No \`lcov.info\` found in artifacts matching \`$COVERAGE_PATTERN\`; Sonar will report no coverage." + } >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + + echo "Coverage files downloaded:" + echo "$files" + { + echo "## Coverage" + echo "" + echo "Artifacts matching \`$COVERAGE_PATTERN\` downloaded to \`$COVERAGE_PATH\`:" + echo "" + while IFS= read -r file; do + echo "- \`$file\`" + done <<<"$files" + } >> "$GITHUB_STEP_SUMMARY" + - name: Run SonarQube scan uses: SonarSource/sonarqube-scan-action@d209202bc7d53ff1cc128f7f907dac145c9d6ae9 # v8.3.0 env: