diff --git a/.github/workflows/finalize.yml b/.github/workflows/finalize.yml new file mode 100644 index 00000000..e929b5a2 --- /dev/null +++ b/.github/workflows/finalize.yml @@ -0,0 +1,208 @@ +--- +name: finalize +on: + workflow_run: + workflows: + - CI + types: + - completed + +permissions: + actions: read + checks: write + contents: read + pull-requests: read + statuses: write + +jobs: + sonar: + name: sonar + if: | + github.repository == 'redhat-developer/abbenay' && + github.event.workflow_run.conclusion == 'success' && + (github.event.workflow_run.event == 'pull_request' || + (github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main')) + runs-on: ubuntu-latest + steps: + - name: Create check run + id: create-check + uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0 + with: + result-encoding: string + script: | + const check = await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'sonar', + head_sha: '${{ github.event.workflow_run.head_sha }}', + status: 'in_progress', + details_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + }); + console.log(`In progress check created with ID: ${check.data.id}`); + return check.data.id; + + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + ref: ${{ github.event.workflow_run.head_sha }} + repository: ${{ github.event.workflow_run.head_repository.full_name }} + fetch-depth: 0 + show-progress: false + persist-credentials: false + allow-unsafe-pr-checkout: true + + - name: Download coverage artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: sonar-coverage + path: packages/daemon/coverage + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Resolve PR metadata + if: github.event.workflow_run.event == 'pull_request' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + REPO: ${{ github.repository }} + # Trusted GitHub payload — not PR-controlled artifact content + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} + run: | + set -euo pipefail + + if [[ -z "${PR_NUMBER}" ]]; then + PR_NUMBER=$(gh api \ + -H "Accept: application/vnd.github+json" \ + "repos/${REPO}/commits/${HEAD_SHA}/pulls" \ + --jq '.[0].number // empty') + fi + + # Fork PR head SHAs are often missing from the commits/{sha}/pulls API + # and workflow_run.pull_requests. Match an open PR by head OID instead. + if [[ ! "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then + PR_NUMBER=$(gh pr list --repo "${REPO}" --state open --limit 100 \ + --json number,headRefOid \ + --jq ".[] | select(.headRefOid == \"${HEAD_SHA}\") | .number" \ + | head -n1) + fi + + if [[ ! "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then + echo "::error::Unable to resolve a trusted PR number for head SHA ${HEAD_SHA}" + exit 1 + fi + + PR_DATA=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}") + PR_BASE=$(jq -r '.base.ref' <<< "${PR_DATA}") + PR_HEAD=$(jq -r '.head.ref' <<< "${PR_DATA}") + + if [[ -z "${PR_BASE}" || "${PR_BASE}" == "null" || -z "${PR_HEAD}" || "${PR_HEAD}" == "null" ]]; then + echo "::error::Unable to resolve PR branch metadata for PR ${PR_NUMBER}" + exit 1 + fi + + { + echo "PR_NUMBER=${PR_NUMBER}" + echo "PR_BASE=${PR_BASE}" + echo "PR_HEAD=${PR_HEAD}" + } >> "${GITHUB_ENV}" + + echo "Resolved PR #${PR_NUMBER} (${PR_HEAD} -> ${PR_BASE})" + + - name: Prepare SonarCloud args + if: hashFiles('packages/daemon/coverage/lcov.info') != '' + shell: bash + env: + WORKFLOW_EVENT: ${{ github.event.workflow_run.event }} + COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} + REPO_NAME: ${{ github.repository }} + run: | + set -euo pipefail + + IFS="/" read -r REPO_OWNER REPO_NAME_ONLY <<< "${REPO_NAME}" + + # Pin endpoint; PRs must not override via sonar-project.properties + SONAR_ARGS="-Dsonar.host.url=https://sonarcloud.io" + SONAR_ARGS="${SONAR_ARGS} -Dsonar.projectKey=${REPO_OWNER}_${REPO_NAME_ONLY} -Dsonar.organization=${REPO_OWNER}" + SONAR_ARGS="${SONAR_ARGS} -Dsonar.scm.revision=${COMMIT_SHA}" + + if [[ "${WORKFLOW_EVENT}" == "pull_request" ]]; then + if [[ ! "${PR_NUMBER:-}" =~ ^[0-9]+$ ]]; then + echo "::error::PR_NUMBER is missing or invalid" + exit 1 + fi + SONAR_ARGS="${SONAR_ARGS} -Dsonar.pullrequest.key=${PR_NUMBER}" + SONAR_ARGS="${SONAR_ARGS} -Dsonar.pullrequest.branch=${PR_HEAD}" + SONAR_ARGS="${SONAR_ARGS} -Dsonar.pullrequest.base=${PR_BASE}" + fi + + echo "SONAR_ARGS=${SONAR_ARGS}" >> "${GITHUB_ENV}" + + - name: Check for coverage files + run: | + if [ -f packages/daemon/coverage/lcov.info ]; then + echo "Coverage Data: Available" + ls -la packages/daemon/coverage/lcov.info + else + echo "Coverage Data: Not available - exiting" + exit 1 + fi + + echo "Running SonarCloud analysis..." + + - name: SonarCloud Scan + if: env.SONAR_ARGS != '' + uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: ${{ env.SONAR_ARGS }} + + - name: Update check run (success) + if: success() + uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0 + with: + script: | + const isPullRequest = '${{ github.event.workflow_run.event }}' === 'pull_request'; + const prNumber = process.env.PR_NUMBER; + const summary = isPullRequest && prNumber + ? `SonarCloud analysis passed for PR #${prNumber}.` + : 'SonarCloud analysis passed.'; + await github.rest.checks.update({ + owner: context.repo.owner, + repo: context.repo.repo, + check_run_id: ${{ steps.create-check.outputs.result }}, + status: 'completed', + conclusion: 'success', + completed_at: new Date().toISOString(), + details_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + output: { + title: 'SonarCloud analysis passed', + summary, + }, + }); + + - name: Update check run (failure) + if: failure() && steps.create-check.outcome == 'success' + uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0 + with: + script: | + const isPullRequest = '${{ github.event.workflow_run.event }}' === 'pull_request'; + const prNumber = process.env.PR_NUMBER; + const summary = isPullRequest && prNumber + ? `SonarCloud analysis failed for PR #${prNumber}. Click Details for logs.` + : 'SonarCloud analysis failed. Click Details for logs.'; + await github.rest.checks.update({ + owner: context.repo.owner, + repo: context.repo.repo, + check_run_id: ${{ steps.create-check.outputs.result }}, + status: 'completed', + conclusion: 'failure', + completed_at: new Date().toISOString(), + details_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + output: { + title: 'SonarCloud analysis failed', + summary, + text: `**Workflow run:** [View details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`, + }, + });