Skip to content

[tracing] Update common files for branch 8.3.x #15

[tracing] Update common files for branch 8.3.x

[tracing] Update common files for branch 8.3.x #15

Workflow file for this run

# WARNING: Do not edit this file directly. Instead, go to:
#
# https://github.com/micronaut-projects/micronaut-project-template/tree/master/.github/workflows
#
# and edit them there. Note that it will be sync'ed to all the Micronaut repos
#
# Sonar analysis for pull requests raised from forks.
#
# GitHub never exposes repository secrets to a `pull_request` event raised from a
# fork, so the "Run static analysis" step in gradle.yml is skipped there and the
# "SonarCloud Code Analysis" check is never reported. Where that check is required
# by a ruleset, contributor pull requests stay blocked forever.
#
# This workflow lets someone with write access run the analysis on demand, either
# from the Actions tab or by commenting "/sonar" on the pull request. The build
# scripts of the pull request are executed with SONAR_TOKEN in the environment, so
# the diff MUST be reviewed first: the human trigger IS the security boundary.
#
# Pass the commit that was reviewed - "/sonar <full 40 character sha>", or the `sha`
# dispatch input - to close the window in which the contributor pushes again between
# the review and the trigger. Without it the run analyses whatever the head is when
# it starts.
name: Sonar PR
on:
workflow_dispatch:
inputs:
pr:
description: 'Pull request number. Review the diff first - this runs the pull request build with SONAR_TOKEN.'
required: true
type: string
sha:
description: 'The full 40 character SHA of the commit you reviewed. Optional; if given, the run is refused unless it is still the head.'
required: false
type: string
issue_comment:
types: [created]
permissions:
contents: read
jobs:
# Authorisation, command parsing and pull request resolution are kept in their
# own job so that a refusal stops the run outright: a step cannot skip the rest
# of its job, so gating inline would let a rejected trigger fall through to the
# analysis. This job never checks out or executes pull request code, which is
# why it is also the only one holding a write-capable GH_TOKEN.
authorize:
name: "authorize"
# `startsWith(comment.body, '/sonar')` is deliberately coarse: a false
# negative here is a silent no-op that looks like a broken workflow, whereas
# a false positive only starts this cheap job, which then rejects the exact
# command below. Anything stricter risks missing trailing whitespace or a
# line ending in the comment body.
if: >-
github.repository != 'micronaut-projects/micronaut-project-template' &&
(github.event_name == 'workflow_dispatch' ||
(github.event.issue.pull_request != null &&
startsWith(github.event.comment.body, '/sonar')))
runs-on: ubuntu-latest
permissions:
contents: read
# `gh pr view` needs pull request read; the acknowledgement needs write.
# A comment on a pull request is an issue comment, so the reaction
# endpoint needs `issues: write` rather than `pull-requests: write`.
issues: write
pull-requests: write
outputs:
proceed: ${{ steps.command.outputs.proceed }}
head_sha: ${{ steps.pr.outputs.head_sha }}
head_branch: ${{ steps.pr.outputs.head_branch }}
base_branch: ${{ steps.pr.outputs.base_branch }}
env:
SONAR_TOKEN_AVAILABLE: ${{ secrets.SONAR_TOKEN != '' }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr || github.event.issue.number }}
steps:
- name: "❓ Parse the /sonar command"
id: command
env:
COMMENT_BODY: ${{ github.event.comment.body }}
INPUT_SHA: ${{ inputs.sha }}
run: |
proceed=true
requested_sha="$INPUT_SHA"
if [ "$GITHUB_EVENT_NAME" = "issue_comment" ]; then
line=$(printf '%s' "$COMMENT_BODY" | head -n 1 | tr -d '\r')
# Word splitting does the trimming. `xargs` is avoided because it
# aborts on an unbalanced quote in the comment, and `set -f` stops a
# `*` in the comment being expanded against the workspace.
set -f
# shellcheck disable=SC2086
set -- $line
set +f
if [ "${1:-}" != "/sonar" ]; then
echo "Ignoring comment: '${1:-}' is not the '/sonar' command."
proceed=false
else
requested_sha="${2:-}"
fi
fi
# The full 40 characters, not a prefix. A short prefix is cheap to
# grind, so a contributor could push an unreviewed commit that still
# matched it and the run would report itself as pinned.
if [ "$proceed" = "true" ] && [ -n "$requested_sha" ]; then
case "$requested_sha" in
*[!0-9a-fA-F]*|"")
echo "::error::'${requested_sha}' is not a commit SHA."
exit 1
;;
esac
if [ "${#requested_sha}" -ne 40 ]; then
echo "::error::'${requested_sha}' is abbreviated. Pass the full 40 character SHA, which 'gh pr view ${PR_NUMBER} --json headRefOid' will print."
exit 1
fi
requested_sha=$(printf '%s' "$requested_sha" | tr 'A-F' 'a-f')
fi
echo "proceed=${proceed}" >> "$GITHUB_OUTPUT"
echo "requested_sha=${requested_sha}" >> "$GITHUB_OUTPUT"
- name: "πŸ” Verify the requester has write access"
if: steps.command.outputs.proceed == 'true'
run: |
if [ "$SONAR_TOKEN_AVAILABLE" != "true" ]; then
echo "::error::SONAR_TOKEN is not configured for this repository."
exit 1
fi
# A non-collaborator makes this endpoint answer 404, so the exit code
# has to be inspected before the permission value: otherwise the step
# dies on gh's own error and never reports why the run was refused.
if ! permission=$(gh api "repos/${GITHUB_REPOSITORY}/collaborators/${GITHUB_ACTOR}/permission" -q '.permission' 2>"${RUNNER_TEMP}/perm.err"); then
echo "::error::Unable to determine ${GITHUB_ACTOR}'s permission on ${GITHUB_REPOSITORY}; they are most likely not a collaborator. $(cat "${RUNNER_TEMP}/perm.err")"
exit 1
fi
echo "${GITHUB_ACTOR} has '${permission}' permission"
case "$permission" in
admin|write) ;;
*)
echo "::error::${GITHUB_ACTOR} has '${permission}' permission on ${GITHUB_REPOSITORY}; running Sonar requires write access."
exit 1
;;
esac
- name: "πŸ”Ž Resolve the pull request"
id: pr
if: steps.command.outputs.proceed == 'true'
env:
REQUESTED_SHA: ${{ steps.command.outputs.requested_sha }}
run: |
gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \
--json headRefOid,headRefName,baseRefName,isCrossRepository > "${RUNNER_TEMP}/pr.json"
head_sha=$(jq -r '.headRefOid' "${RUNNER_TEMP}/pr.json")
# Close the window between the maintainer reading the diff and this run
# starting: if they named the commit they reviewed, refuse to analyse
# anything else.
if [ -n "$REQUESTED_SHA" ]; then
if [ "$(printf '%s' "$head_sha" | tr 'A-F' 'a-f')" != "$REQUESTED_SHA" ]; then
echo "::error::#${PR_NUMBER} has moved on to ${head_sha} since ${REQUESTED_SHA} was reviewed. Review the new commit and trigger again."
exit 1
fi
else
echo "::warning::No reviewed commit was given, so ${head_sha} is analysed as-is. Pass '/sonar <sha>' to pin the run to the commit you reviewed."
fi
{
echo "head_sha=${head_sha}"
echo "head_branch=$(jq -r '.headRefName' "${RUNNER_TEMP}/pr.json")"
echo "base_branch=$(jq -r '.baseRefName' "${RUNNER_TEMP}/pr.json")"
} >> "$GITHUB_OUTPUT"
{
echo "### Sonar analysis of #${PR_NUMBER}"
echo ""
echo "| | |"
echo "|---|---|"
echo "| Head commit | \`${head_sha}\` |"
echo "| Pinned to reviewed commit | ${REQUESTED_SHA:-no} |"
echo "| Head branch | \`$(jq -r '.headRefName' "${RUNNER_TEMP}/pr.json")\` |"
echo "| Base branch | \`$(jq -r '.baseRefName' "${RUNNER_TEMP}/pr.json")\` |"
echo "| From a fork | $(jq -r '.isCrossRepository' "${RUNNER_TEMP}/pr.json") |"
echo "| Triggered by | @${GITHUB_ACTOR} |"
} >> "$GITHUB_STEP_SUMMARY"
# Acknowledged from this job, not from `sonar`, so that the write-capable
# GH_TOKEN is never present in an environment that runs pull request code.
- name: "πŸ’¬ Acknowledge the comment"
if: steps.command.outputs.proceed == 'true' && github.event_name == 'issue_comment'
continue-on-error: true
env:
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
COMMENT_ID: ${{ github.event.comment.id }}
run: |
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}/reactions" \
-f content='eyes'
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body \
"Running Sonar analysis on \`${HEAD_SHA}\`: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
sonar:
name: "sonar"
needs: authorize
if: needs.authorize.outputs.proceed == 'true'
runs-on: ubuntu-latest
# Concurrency is scoped to this job rather than the workflow: at workflow
# level, every unrelated comment on the pull request would join the group and
# cancel an analysis that is already running.
concurrency:
group: sonar-pr-${{ github.event_name == 'workflow_dispatch' && inputs.pr || github.event.issue.number }}
cancel-in-progress: true
# This job checks out and executes contributor-controlled build scripts, so it
# holds no write permission and no GH_TOKEN. SONAR_TOKEN is the only secret it
# needs, and it is scoped to the analysis step alone.
permissions:
contents: read
env:
TESTCONTAINERS_RYUK_DISABLED: true
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr || github.event.issue.number }}
steps:
# https://github.com/actions/virtual-environments/issues/709
- name: Remove system JDKs
run: |
sudo rm -rf /usr/lib/jvm/*
unset JAVA_HOME
export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/usr/lib/jvm' | paste -sd:)
- name: "πŸ—‘ Free disk space"
run: |
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo rm -rf "/opt/ghc"
sudo rm -rf "/usr/share/dotnet"
sudo rm -rf "/usr/local/lib/android"
sudo apt-get clean
df -h
# Checked out by commit rather than by refs/pull/N/head so that the analysed
# code is exactly what the authorize job recorded, even if the contributor
# pushes again while the build is in flight.
- name: "πŸ“₯ Checkout the pull request"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.authorize.outputs.head_sha }}
fetch-depth: 0
persist-credentials: false
# The only secret left in this job besides SONAR_TOKEN. It is a `with:`
# input, so it reaches this action's step alone and not the Gradle steps,
# and the job's token is `contents: read`. Kept because dropping it makes
# release resolution flaky against the GitHub API rate limit.
- name: "πŸ”§ Setup GraalVM CE"
uses: graalvm/setup-graalvm@0426e2e191540e8514dff98dc52a5f5146a2a276 # v1
with:
distribution: 'graalvm'
java-version: '25'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: "πŸ”§ Setup Gradle"
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6
- name: "❓ Optional setup step"
run: |
[ -f ./setup.sh ] && ./setup.sh || [ ! -f ./setup.sh ]
# Unlike gradle.yml, no Develocity or GitHub credentials are passed here.
# gradle.yml can afford them because on a fork pull request GitHub withholds
# every secret and they arrive empty; this workflow runs in the base
# repository, where they would be real and readable by contributor
# controlled build scripts and tests. The remote cache credentials matter
# most: a leak there poisons every later build in every repository. The
# cost is losing build scans and remote cache reads, and fork pull requests
# already build without any of these today.
- name: "πŸ›  Build with Gradle"
run: |
./gradlew check jacocoReport --no-daemon --continue
# Analysis is a second Gradle invocation rather than `check jacocoReport
# sonar` in one, matching gradle.yml, for two reasons. `sonar` needs
# `--no-parallel`, and applying that to the whole build would serialise the
# entire test suite. And SONAR_TOKEN would otherwise be exported to every
# test in the pull request under analysis, rather than only to the scanner.
# The repeated configuration time is the lesser cost.
#
# The pull request coordinates have to be passed explicitly: this is not a
# `pull_request` event, so the scanner's own CI detection would otherwise
# analyse this as a branch build of the default branch.
- name: "πŸ”Ž Run static analysis"
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
HEAD_BRANCH: ${{ needs.authorize.outputs.head_branch }}
BASE_BRANCH: ${{ needs.authorize.outputs.base_branch }}
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
run: |
./gradlew sonar --no-parallel --continue \
-Dsonar.pullrequest.key="${PR_NUMBER}" \
-Dsonar.pullrequest.branch="${HEAD_BRANCH}" \
-Dsonar.pullrequest.base="${BASE_BRANCH}" \
-Dsonar.scm.revision="${HEAD_SHA}"
- name: "❓ Optional cleanup step"
if: always()
run: |
[ -f ./cleanup.sh ] && ./cleanup.sh || [ ! -f ./cleanup.sh ]