name: SonarQube Analysis # Called through "uses:" only, never triggered here - there is nothing in this # repository worth analysing, and a dispatch here would run against these files. # # The caller decides WHEN the analysis runs, through its own "on:". On the # SonarQube Community Build only the main branch is analysed, so that is in # practice a push to main, and the consequence belongs said out loud: a finding # appears AFTER the merge. This block observes a state, it does not guard a # gate - see the note on the missing quality-gate step at the bottom. on: workflow_call: inputs: # Both inputs are type: string, and that is not an oversight waiting to be # cleaned up. On this Gitea version (measured on 1.27.0, issue #4) a # boolean workflow_call input does not reach the called workflow at all - # neither its default nor an explicitly passed value - while the run stays # green. Every input of every block here is therefore a string, even one # that is a switch in meaning. Whoever "fixes" that later brings the fault # back silently. args: required: false type: string default: "" description: >- Additional arguments for the Sonar Scanner CLI, passed through to the scan action's "args" input. Everything project-specific belongs in the sonar-project.properties of the calling repository, not here; this is for the rest, e.g. -Dsonar.verbose=true for one diagnostic run. # The scan action's own default for projectBaseDir is ".", and this input # repeats it instead of defaulting to the empty string: an empty value # would be passed on and would override the action's default rather than # leave it alone. project-base-dir: required: false type: string default: "." description: >- Directory the analysis starts from, i.e. sonar.projectBaseDir. The sonar-project.properties is looked up relative to it. secrets: # required: true, and that is a real decision on this forge, not a # formality: a missing secret that the called workflow declares as # required fails JOB CREATION, not the job - the run then stands there # with no steps at all and nothing in it names the cause. Measured in # jlab-cluster/helm-charts, written up on the wiki page # "Gemessenes-Verhalten". # # It is the right setting here all the same, because every caller needs # the token: without it there is no analysis to run, so a run without # steps is the understandable case - exactly this prerequisite is missing. # (required: false would be right only if a part of the block worked # without it.) SONAR_TOKEN: required: true jobs: sonar-analysis: name: SonarQube Analysis # ubuntu-latest is enough, and that is worth a line because the sibling # block docker-build-push needs the ubuntu-docker runner: the scan action # declares "runs: using: node24", so it is a JavaScript action and runs in # the runner's Node, not in a container. No Docker socket is involved, and # ubuntu-docker is the only runner here that has one. # # What it does need is node24 in the runner image. That is NOT verified; if # it is absent the action fails, and it fails with a message about the # action rather than about the image. runs-on: ubuntu-latest steps: # Both settings belong to the CALLING repository, not to this file: a # shared block does not carry one tenant's infrastructure address, and it # certainly does not carry anyone's token. The price of that is two # prerequisites that can be forgotten, and an unset variable arrives as an # empty string. # # An empty SONAR_HOST_URL does not fail here - it fails one step later, # inside the scan action, against an address nobody can see. So the guard # comes first and names the setting, the place to set it and the shape of # the value. The publish block needed the same pattern for # OP_CONNECT_HOST. # # The token is checked in the same step even though a MISSING one never # gets this far (see the secrets block above): what this catches is a # secret that exists and is empty, and a caller that forgot # "secrets: inherit". Both arrive as an empty string, both would otherwise # surface as an unauthenticated request against the server. # # Both are checked before either exits, so a repository that has set up # neither learns both in one run instead of in two. - name: "Check the calling repository's prerequisites" env: SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: |- missing=0 if [ -z "$SONAR_HOST_URL" ]; then echo "::error::the variable SONAR_HOST_URL is not set in the calling repository (Settings -> Actions -> Variables, or on the organisation). It must hold the address of the SonarQube server, e.g. https://sonar.example.com - no trailing path. This workflow deliberately does not hardcode a host." missing=1 fi if [ -z "$SONAR_TOKEN" ]; then echo "::error::the secret SONAR_TOKEN is empty in the calling repository (Settings -> Actions -> Secrets, or on the organisation), or the caller did not pass it on with 'secrets: inherit'. It must hold a SonarQube token permitted to run analyses - and to create the project, if the first run is supposed to create it." missing=1 fi [ "$missing" -eq 0 ] || exit 1 # fetch-depth: 0, and this is the one checkout option the analysis cannot # do without. With the default shallow clone the scanner has no history to # attribute lines to, so every finding is reported against the last commit # instead of the change that introduced it: blame, the "new code" period # and every date-based measure become unusable. The clone is slower, which # is the whole price. # # Pinned to a commit, not to a tag or a branch: a moving reference pulls # the newest state into every run of every caller, and the resulting # failure shows up at a caller that changed nothing (issue #5). - name: "Checkout Repository" uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 # The action downloads the Sonar Scanner CLI from binaries.sonarsource.com # at run time and verifies its GPG signature. That download is around # 59 MiB, and it is the most likely place for a first failure on this # infrastructure - the wiki page for this block says why. - name: "SonarQube Scan" uses: SonarSource/sonarqube-scan-action@7006c4492b2e0ee0f816d36501671557c97f5995 # v8.1.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # A variable of the caller, not a secret: the address of the server is # not a credential, and as a variable it stays readable in the log, # which is what makes a wrong one diagnosable at all. SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} with: args: ${{ inputs.args }} projectBaseDir: ${{ inputs.project-base-dir }} # There is deliberately NO quality-gate step here, and it is missing on # purpose rather than by oversight. # # With an after-the-merge trigger there is nothing left to block. A red # gate would only colour the main branch red, and a permanent red is a # thing people learn to ignore - at which point the gate has cost the # credibility of every other check without having stopped anything. # # It becomes the right thing under one of two conditions: pull-request # analysis is available (then the gate blocks a merge, which is a decision # it can actually carry), or a deployment hangs off this workflow (then it # guards something concrete). Until then, adding it is a regression.