diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 6586460..9b5c87f 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -1,60 +1,61 @@ name: Qodana # -# KNOWN BLOCKER - this workflow cannot currently produce results, and the cause is a -# licence, not a configuration defect. Dispatch 33428446669 on this branch (the first run -# ever to get past solution loading) failed with: +# FORMER BLOCKER (issue #151) - every scan used to fail a licence check before the +# solution was even opened: # # Your Qodana Cloud organization has Community license that doesn't support # "Qodana for .NET" linter, please try one of the community linters instead # -# The check happens before the solution is opened, so no SARIF can be produced no matter -# how the scan is configured. Everything below - the correct solution file, the sibling -# clones and their container mounts, the private-feed credentials, the exclude paths - is -# necessary for a scan that runs, but none of it is sufficient while the linter itself is -# unlicensed. Resolving that (a paid Qodana licence, or moving to a community linter) is -# tracked separately; see issue #138. The observability wiring below is what surfaced this -# at all: the job stays green, emits warnings and reports the scan outcome instead of -# failing silently. +# qodana.yaml pointed at `jetbrains/qodana-dotnet`, the paid "Qodana for .NET" linter, +# which the mrploch organisation's Community licence does not cover. Switched to +# `jetbrains/qodana-cdnet` (Community .NET) - confirmed by dispatch 34691387171 to get +# past the licence check, restore, build and produce a report. +# +# Also dropped the github/codeql-action/upload-sarif step that same run then failed on +# ("GITHUB_REF environment variable must be set") and the bespoke SARIF-existence/outcome +# scaffolding it needed. ploch-common's working qodana_code_quality.yml never had that +# step either - Qodana reports straight to Qodana Cloud via QODANA_TOKEN, which is also +# what registers the project there (it showed "not configured" on qodana.cloud only +# because no scan had ever completed with a licensed linter to report in). Re-add a +# GitHub code-scanning upload only if that is genuinely wanted in addition to Qodana +# Cloud, with `ref`/`sha` passed explicitly to satisfy upload-sarif's context detection. on: workflow_dispatch: + pull_request: + push: + branches: + - main + - 'releases/*' jobs: qodana: # A cold container scan of this solution is not a fast job: a plain # `dotnet build -c Release` of Ploch.Data.slnx takes ~12 minutes locally, and Qodana # additionally pulls the linter image, restores, and runs InspectCode over everything. - # A job-level timeout CANCELS the job, and cancellation skips `if: always()` steps - - # which would silently defeat the SARIF observability this workflow exists for - # (issue #138). Tighten this only once a successful dispatch has reported a real - # duration. - timeout-minutes: 60 + timeout-minutes: 30 runs-on: ubuntu-latest - # Least privilege (issue #128). Declaring any permissions block drops every scope not - # listed to none, so each entry below is load-bearing: - # contents: read - checkout of this repository. Nothing in this workflow - # pushes, which is also why the checkout below sets - # persist-credentials: false. - # security-events: write - required by github/codeql-action/upload-sarif below; - # without it the SARIF upload fails with 403 and the Qodana - # results never reach code scanning. - # `pull-requests: write` is deliberately NOT granted: the trigger is - # workflow_dispatch-only, so the Qodana action's PR-comment path can never engage. - # Re-add it if and when a pull_request trigger is introduced. + # A fork's `pull_request` event never receives repository secrets (GitHub strips them + # by design), so on a fork PR QODANA_TOKEN and QODANA_NUGET_PASSWORD would both expand + # to empty strings: the scan can't report to Qodana Cloud and can't restore the + # SampleApp projects from the authenticated GitHub Packages feed either. Skipping fork + # PRs here avoids a scan that is guaranteed to fail on missing credentials, and avoids + # ever handing PR-controlled MSBuild/restore code a live token from a fork's context + # (flagged by Sourcery, codeant-ai, qodo-code-review and CodeRabbit on PR #190). + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository permissions: contents: read - security-events: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} # check out the actual PR commit, not the merge commit + fetch-depth: 0 # full history is required for pull request analysis # Nothing in this workflow pushes, so the job token has no reason to stay in # .git/config. persist-credentials: false # Ploch.Data.slnx references three projects under ../ploch-common by relative path, # and Directory.Packages.props imports five .props files from ../mrploch-development - # unconditionally. Without these clones the solution cannot be evaluated at all, so - # pointing Qodana at the right solution file is necessary but not sufficient. The + # unconditionally. Without these clones the solution cannot be evaluated at all. The # clone depths deliberately match build-dotnet.yml and release.yml: mrploch-development # supplies only .props files and is never built, but ploch-common IS built and uses # Nerdbank.GitVersioning, which cannot compute commit height from a shallow clone. @@ -64,14 +65,9 @@ jobs: run: git clone https://github.com/mrploch/ploch-common.git ../ploch-common - name: 'Qodana Scan' - id: qodana uses: JetBrains/qodana-action@10be11607eb323a180e2b76b26c9c5cdceac3e77 # v2026.2.1 - # The scan is deliberately non-blocking so that a Qodana failure still lets the - # upload-sarif step below run and publish whatever SARIF was produced. Without this - # the job aborts here and the upload never becomes observable (issue #138). The - # step's own outcome is surfaced by the warning step that follows. - continue-on-error: true with: + pr-mode: false # Qodana analyses the project inside a container that bind-mounts only the project # directory at /data/project. The two sibling clones above live one level ABOVE # that directory on the runner, so without these extra mounts the unconditional @@ -79,69 +75,15 @@ jobs: # references resolve to paths that do not exist inside the container and the # clones are effectively invisible. Mounting them at /data/ makes # /data/project/../ resolve exactly as it does on the runner. - # - # No `--baseline` here on purpose. The previous value was - # `--baseline,./qodana.sarif.json`, pointing at a 4 MB SARIF checked in at the - # repository root that was produced by QDNET 231 against a *different* solution - # (Ploch.Common). A baseline from another project cannot suppress anything - # meaningful here and only risks failing the scan, so the run is unbaselined. - # Reinstate a baseline only after a green scan of this repository has produced one. args: -v,${{ github.workspace }}/../ploch-common:/data/ploch-common,-v,${{ github.workspace }}/../mrploch-development:/data/mrploch-development env: - # The secret is named QODANA_TOKEN_1681345019 in this repository; there is no secret - # called QODANA_TOKEN at either repository or organisation level, so the previous - # reference expanded to an empty string and the scan ran unauthenticated. The - # environment variable must keep its name — that is what the action reads. QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_1681345019 }} + QODANA_ENDPOINT: 'https://qodana.cloud' # Ploch.Data.slnx includes five SampleApp projects whose `Ploch.*` PackageReferences # are source-mapped in NuGet.Config exclusively to the authenticated GitHub Packages - # feed, so a container with no credentials cannot restore them. Run 33428446669 - # confirmed this from the scanner's own diagnostics: "private NuGet feed detected - # (NuGet.Config). Please set QODANA_NUGET_USER, QODANA_NUGET_PASSWORD, - # QODANA_NUGET_URL and QODANA_NUGET_NAME". These are the vendor-supported variables - # for exactly that case, so they are used here rather than a bespoke solution filter. - # NOT YET PROVEN TO WORK: run 33429634184 still logged that warning with all four - # set, and the licence failure below kills the run before restore, so there is no - # way to tell whether the credentials are actually consumed. Re-check this the - # first time a scan gets past the licence check (issue #151). + # feed, so a container with no credentials cannot restore them. These are the + # vendor-supported variables for exactly that case (issue #151). QODANA_NUGET_USER: kploch QODANA_NUGET_PASSWORD: ${{ secrets.GH_PACKAGES_TOKEN }} QODANA_NUGET_URL: https://nuget.pkg.github.com/mrploch/index.json QODANA_NUGET_NAME: github - - # A scan that fails early enough - bad configuration, authentication, a container that - # never starts - produces no SARIF at all, and upload-sarif errors on a missing file. - # That would turn the whole point of `if: always()` into a different failure, so the - # upload is gated on the file actually existing and its absence is reported instead. - - name: Check whether a SARIF report was produced - id: sarif - if: always() - env: - SARIF: ${{ runner.temp }}/qodana/results/qodana.sarif.json - run: | - if [ -f "$SARIF" ]; then - echo "found=true" >> "$GITHUB_OUTPUT" - echo "SARIF report found at $SARIF" - else - echo "found=false" >> "$GITHUB_OUTPUT" - echo "::warning title=No SARIF produced::The Qodana scan produced no SARIF report at $SARIF, so there is nothing to upload to code scanning. Check the 'Qodana Scan' step log." - fi - - # `if: always()` is what makes this step observable at all. Until it was added the scan - # failed on every recorded run and this step was skipped, so no SARIF had ever reached - # code scanning and the pinned action version was unexercised (issue #138). Uploading - # a partial SARIF from a failed scan is strictly better than uploading nothing. - - name: Upload SARIF to code scanning - if: ${{ always() && steps.sarif.outputs.found == 'true' }} - uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 - with: - sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json - - # Keeps a failed scan visible rather than hiding it behind a green job. Not always(): - # that would fire a spurious warning on a run the user cancelled deliberately. - - name: Warn if the Qodana scan did not succeed - if: ${{ !cancelled() && steps.qodana.outcome != 'success' }} - env: - QODANA_OUTCOME: ${{ steps.qodana.outcome }} - run: | - echo "::warning title=Qodana scan did not succeed::The Qodana Scan step reported '$QODANA_OUTCOME'. Any SARIF it managed to produce has still been uploaded. Check the 'Qodana Scan' step log." diff --git a/qodana.yaml b/qodana.yaml index 0e4b63d..740a93e 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -1,5 +1,11 @@ version: "1.0" -linter: jetbrains/qodana-dotnet:latest +# jetbrains/qodana-dotnet is the paid "Qodana for .NET" linter, which the mrploch +# organisation's Community licence does not cover (issue #151) - every scan failed a +# licence check before the solution was even opened. jetbrains/qodana-cdnet is the free +# Community .NET linter and is what ploch-common uses successfully (its +# qodana_code_quality.yml has been green on every push/PR since 2026-09-04). Same linter +# tag pinned here for consistency across the two repos. +linter: jetbrains/qodana-cdnet:2026.1 dotnet: # This was `Ploch.Common.sln` - a solution file that has never existed in this repository; # it was copied verbatim from a sibling repo. Qodana could not open it, which is why the