diff --git a/.github/workflows/comment-pr-artifacts.yml b/.github/workflows/comment-pr-artifacts.yml new file mode 100644 index 00000000..1c4bc17a --- /dev/null +++ b/.github/workflows/comment-pr-artifacts.yml @@ -0,0 +1,132 @@ +name: Comment PR Artifacts + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + +permissions: + actions: read + contents: read + issues: write + pull-requests: write + +jobs: + comment: + runs-on: ubuntu-latest + env: + ALLOWED_ACTORS: ${{ vars.ALLOWED_NON_DEV_PR_ACTORS }} + ACTOR: ${{ github.actor }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + PR_NUMBER: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + RETENTION_DAYS: 7 + steps: + - name: Check PR authorization + id: auth + shell: bash + run: | + set -euo pipefail + if [ "$BASE_REF" = "dev" ]; then + echo "allowed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + normalized=",${ALLOWED_ACTORS}," + if [[ "$normalized" == *",${ACTOR},"* ]]; then + echo "allowed=true" >> "$GITHUB_OUTPUT" + else + echo "allowed=false" >> "$GITHUB_OUTPUT" + fi + + - name: Wait for PR build and comment + if: ${{ steps.auth.outputs.allowed == 'true' }} + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = Number(process.env.PR_NUMBER); + const headSha = process.env.HEAD_SHA; + const retentionDays = Number(process.env.RETENTION_DAYS || '7'); + const marker = ''; + + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + let matchedRun = null; + for (let attempt = 1; attempt <= 30; attempt += 1) { + const runs = await github.paginate(github.rest.actions.listWorkflowRuns, { + owner, + repo, + workflow_id: 'pr-build.yml', + event: 'pull_request', + per_page: 100, + }); + + matchedRun = runs.find((run) => run.head_sha === headSha); + if (matchedRun && matchedRun.status === 'completed') { + break; + } + + core.info(`Waiting for PR Build Validation run for ${headSha} (attempt ${attempt}/30)`); + await sleep(10000); + } + + if (!matchedRun) { + core.setFailed(`Could not find PR Build Validation run for ${headSha}.`); + return; + } + + if (matchedRun.status !== 'completed') { + core.setFailed(`PR Build Validation run ${matchedRun.id} did not complete in time.`); + return; + } + + const artifacts = await github.paginate( + github.rest.actions.listWorkflowRunArtifacts, + { owner, repo, run_id: matchedRun.id, per_page: 100 } + ); + const active = artifacts.filter((artifact) => !artifact.expired); + + const runUrl = matchedRun.html_url; + const artifactsBlock = active.length + ? ['Artifacts:', ...active.map((artifact) => `- ${artifact.name}`)].join('\n') + : 'Artifacts: (none found on this run)'; + + const body = [ + marker, + 'PR builds are available as GitHub Actions artifacts:', + '', + runUrl, + '', + `Artifacts expire in ${retentionDays} days.`, + artifactsBlock, + ].join('\n'); + + const comments = await github.paginate( + github.rest.issues.listComments, + { owner, repo, issue_number: prNumber, per_page: 100 } + ); + const existing = comments.find((comment) => (comment.body || '').includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + core.info(`Updated existing artifacts comment: ${existing.html_url}`); + return; + } + + const created = await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body, + }); + core.info(`Created artifacts comment: ${created.data.html_url}`); diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index c0646aa3..1f780ffc 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -10,8 +10,6 @@ on: permissions: contents: read actions: write - issues: write - pull-requests: write concurrency: group: pr-build-${{ github.event.pull_request.number }} @@ -56,76 +54,3 @@ jobs: actions_artifacts_retention_days: 7 actions_artifacts_name_prefix: pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}- set_versions: false - - comment-artifacts: - needs: - - authorize - - build - if: ${{ always() && needs.authorize.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - steps: - - name: Get PR number - id: get-pr - uses: bcgov/action-get-pr@v0.1.1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Comment with artifact download link - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const prNumber = Number('${{ steps.get-pr.outputs.pr }}'); - if (!prNumber) { - core.setFailed('Failed to resolve PR number.'); - return; - } - - const artifacts = await github.paginate( - github.rest.actions.listWorkflowRunArtifacts, - { owner, repo, run_id: context.runId, per_page: 100 } - ); - const active = artifacts.filter((a) => !a.expired); - - const marker = ''; - const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${context.runId}`; - const retentionDays = 7; - const artifactsBlock = active.length - ? ['Artifacts:', ...active.map((a) => `- ${a.name}`)].join('\n') - : 'Artifacts: (none found on this run)'; - - const body = [ - marker, - 'PR builds are available as GitHub Actions artifacts:', - '', - runUrl, - '', - `Artifacts expire in ${retentionDays} days.`, - artifactsBlock, - ].join('\n'); - - const comments = await github.paginate( - github.rest.issues.listComments, - { owner, repo, issue_number: prNumber, per_page: 100 } - ); - const existing = comments.find((c) => (c.body || '').includes(marker)); - - if (existing) { - await github.rest.issues.updateComment({ - owner, - repo, - comment_id: existing.id, - body, - }); - core.info(`Updated existing artifacts comment: ${existing.html_url}`); - } else { - const created = await github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body, - }); - core.info(`Created artifacts comment: ${created.data.html_url}`); - }