| # |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| # Posts the CI test summary comment on PRs. |
| # Uses workflow_run trigger so it runs in the base repo context with |
| # full permissions — this allows posting comments on fork PRs too. |
| |
| name: Post CI test comment |
| |
| on: |
| workflow_run: |
| workflows: ["Build and test"] |
| types: |
| - completed |
| |
| jobs: |
| comment: |
| runs-on: ubuntu-slim |
| if: > |
| github.event.workflow_run.event == 'pull_request' || |
| github.event.workflow_run.event == 'workflow_dispatch' |
| permissions: |
| pull-requests: write |
| actions: read |
| steps: |
| - name: Download CI comment artifact |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| with: |
| script: | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| run_id: ${{ github.event.workflow_run.id }}, |
| }); |
| const match = artifacts.data.artifacts.find(a => a.name === 'ci-comment'); |
| if (!match) { |
| core.info('No ci-comment artifact found, skipping'); |
| return; |
| } |
| const download = await github.rest.actions.downloadArtifact({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| artifact_id: match.id, |
| archive_format: 'zip', |
| }); |
| const fs = require('fs'); |
| fs.writeFileSync('${{ github.workspace }}/ci-comment.zip', Buffer.from(download.data)); |
| - name: Extract artifact |
| run: | |
| if [ -f ci-comment.zip ]; then |
| unzip -o ci-comment.zip -d ci-comment-artifact |
| fi |
| - name: Post or update PR comment |
| if: always() |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| with: |
| github-token: ${{ secrets.GITHUB_TOKEN }} |
| script: | |
| const fs = require('fs'); |
| const prFile = 'ci-comment-artifact/pr-number'; |
| const commentFile = 'ci-comment-artifact/incremental-test-comment.md'; |
| |
| if (!fs.existsSync(prFile)) { |
| core.info('No PR number file found, skipping'); |
| return; |
| } |
| const prNumber = parseInt(fs.readFileSync(prFile, 'utf8').trim(), 10); |
| if (!prNumber) { |
| core.warning('Invalid PR number, skipping'); |
| return; |
| } |
| |
| if (!fs.existsSync(commentFile)) { |
| core.info('No comment file found, skipping'); |
| return; |
| } |
| const body = fs.readFileSync(commentFile, 'utf8').trim(); |
| if (!body) return; |
| |
| const marker = '<!-- ci-tested-modules -->'; |
| |
| try { |
| const { data: comments } = await github.rest.issues.listComments({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| issue_number: prNumber, |
| }); |
| const existing = comments.find(c => c.body && c.body.includes(marker)); |
| |
| if (existing) { |
| await github.rest.issues.updateComment({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| comment_id: existing.id, |
| body: body, |
| }); |
| } else { |
| await github.rest.issues.createComment({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| issue_number: prNumber, |
| body: body, |
| }); |
| } |
| } catch (error) { |
| core.warning(`Failed to post CI test summary comment: ${error.message}`); |
| } |
| - name: Download regen patch artifact |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| with: |
| script: | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| run_id: ${{ github.event.workflow_run.id }}, |
| }); |
| const match = artifacts.data.artifacts.find(a => a.name === 'regen-patch'); |
| if (!match) { |
| core.info('No regen-patch artifact found, skipping'); |
| return; |
| } |
| const download = await github.rest.actions.downloadArtifact({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| artifact_id: match.id, |
| archive_format: 'zip', |
| }); |
| const fs = require('fs'); |
| fs.writeFileSync('${{ github.workspace }}/regen-patch.zip', Buffer.from(download.data)); |
| - name: Extract regen patch artifact |
| run: | |
| if [ -f regen-patch.zip ]; then |
| unzip -o regen-patch.zip -d regen-patch-artifact |
| fi |
| - name: Post or update regen patch comment |
| if: always() |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| with: |
| github-token: ${{ secrets.GITHUB_TOKEN }} |
| script: | |
| const fs = require('fs'); |
| |
| const regenPatchFile = 'regen-patch-artifact/regen.patch'; |
| const regenPrFile = 'regen-patch-artifact/pr-number'; |
| const ciPrFile = 'ci-comment-artifact/pr-number'; |
| |
| const regenNeeded = fs.existsSync(regenPatchFile); |
| |
| // The regen artifact carries its own PR number (the ci-comment |
| // artifact is not produced when the build fails at the regen |
| // check); on clean runs fall back to the ci-comment artifact so an |
| // earlier warning comment can be marked resolved. |
| let prNumber = 0; |
| if (fs.existsSync(regenPrFile)) { |
| prNumber = parseInt(fs.readFileSync(regenPrFile, 'utf8').trim(), 10); |
| } else if (fs.existsSync(ciPrFile)) { |
| prNumber = parseInt(fs.readFileSync(ciPrFile, 'utf8').trim(), 10); |
| } |
| if (!prNumber) { |
| core.info('No PR number available, skipping regen patch comment'); |
| return; |
| } |
| |
| const marker = '<!-- regen-patch -->'; |
| const runId = ${{ github.event.workflow_run.id }}; |
| const runUrl = `${{ github.server_url }}/${{ github.repository }}/actions/runs/${runId}`; |
| const repoSlug = `${context.repo.owner}/${context.repo.repo}`; |
| |
| let body; |
| if (regenNeeded) { |
| body = [ |
| marker, |
| '### :warning: Generated files are out of date', |
| '', |
| `The [CI build](${runUrl}) regenerated files that are not committed on this PR.`, |
| '', |
| 'If *Allow edits from maintainers* is enabled, a maintainer bot will automatically', |
| 'push a "Regen" commit to this branch shortly.', |
| '', |
| 'Otherwise, you can apply the patch manually — the missing changes are available', |
| 'as the `regen-patch` artifact of that run:', |
| '', |
| '```bash', |
| `gh pr checkout ${prNumber} --repo ${repoSlug}`, |
| `gh run download ${runId} --repo ${repoSlug} -n regen-patch`, |
| 'git apply --index regen.patch && rm regen.patch pr-number', |
| 'git commit -m "Regen" && git push', |
| '```', |
| '', |
| 'Alternatively, run `./etc/scripts/regen.sh` locally and commit the resulting changes.', |
| 'If the patch does not apply cleanly, update the branch from the base branch first —', |
| 'the patch was generated against the merge with the base branch.', |
| ].join('\n'); |
| } else { |
| body = [ |
| marker, |
| '### :white_check_mark: Generated files are up to date', |
| '', |
| `An earlier CI run reported uncommitted generated changes; the [latest run](${runUrl}) no longer does.`, |
| ].join('\n'); |
| } |
| |
| try { |
| const { data: comments } = await github.rest.issues.listComments({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| issue_number: prNumber, |
| }); |
| const existing = comments.find(c => c.body && c.body.includes(marker)); |
| |
| if (existing) { |
| if (!regenNeeded && existing.body.includes(':white_check_mark:')) { |
| return; // already marked resolved |
| } |
| await github.rest.issues.updateComment({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| comment_id: existing.id, |
| body: body, |
| }); |
| } else if (regenNeeded) { |
| await github.rest.issues.createComment({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| issue_number: prNumber, |
| body: body, |
| }); |
| } |
| } catch (error) { |
| core.warning(`Failed to post regen patch comment: ${error.message}`); |
| } |