Skip to content

Post CI test comment #6403

Post CI test comment

Post CI test comment #6403

#
# 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-latest
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}`);
}