Skip to content

ci: add label-triggered TechEngineBot final-check #6

ci: add label-triggered TechEngineBot final-check

ci: add label-triggered TechEngineBot final-check #6

Workflow file for this run

name: final-check
# A maintainer adds the `final-check` label to an important PR. The check job
# runs what CI and Pages would run on the PR merged into its base (the ES
# validator, the tests, and the static index build) and lists the records the
# PR changes. TechEngineBot leaves ONE comment with the verdict; adding the
# label again edits that comment.
#
# The check job runs PR code, so it gets no secrets and a read-only token.
# Only the comment job sees TECHENGINEBOT_TOKEN, which must be a fine-grained
# token of TechEngineBot with Pull requests: read and write on this repository
# and nothing more; the job refuses any other kind of token or account. Fork
# PRs never get a comment.
on:
pull_request:
types: [labeled]
branches: [develop, main]
permissions: {}
jobs:
check:
if: github.event.label.name == 'final-check'
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
concurrency:
group: final-check-${{ github.event.pull_request.number }}
cancel-in-progress: true
outputs:
body: ${{ steps.report.outputs.body }}
env:
PYTHONIOENCODING: utf-8
steps:
# The merge commit, as develop/main would look after merging. Depth 2
# keeps HEAD^1 (the base) for the list of changed records.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.12"
- name: Validate ES catalog
id: validate
continue-on-error: true
run: |
status=0
python -m app.validate > "$RUNNER_TEMP/validate.log" 2>&1 || status=$?
cat "$RUNNER_TEMP/validate.log"
exit "$status"
- name: Tests
id: tests
continue-on-error: true
run: |
status=0
{ python -m pip install --disable-pip-version-check pytest && python -m pytest -q; } \
> "$RUNNER_TEMP/tests.log" 2>&1 || status=$?
tail -n 40 "$RUNNER_TEMP/tests.log"
exit "$status"
# site/build.py replays history from data commits; with a shallow clone
# history.json is partial, which is fine for a build check.
- name: Build static index
id: site
continue-on-error: true
run: |
status=0
python site/build.py > "$RUNNER_TEMP/site.log" 2>&1 || status=$?
cat "$RUNNER_TEMP/site.log"
exit "$status"
# The comment quotes tool output and record names, which come from the
# PR: logs stay inside code fences with backticks and @ removed, so they
# can neither break out into markdown nor mention anyone.
- name: Report
id: report
if: always()
env:
VALIDATE: ${{ steps.validate.outcome }}
TESTS: ${{ steps.tests.outcome }}
SITE: ${{ steps.site.outcome }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
clean() { tr -d '`@' | cut -c1-300; }
fence() { # fence <log> <lines>
echo '```text'
tail -n "$2" "$1" 2>/dev/null | clean
echo '```'
}
verdict='✅ Final check passed'
[ "$VALIDATE/$TESTS/$SITE" = success/success/success ] || verdict='❌ Final check failed'
git diff --name-status --no-renames HEAD^1 HEAD -- data/cpu > "$RUNNER_TEMP/changed.txt" 2>/dev/null || true
added=$(grep -c '^A' "$RUNNER_TEMP/changed.txt" || true)
modified=$(grep -c '^M' "$RUNNER_TEMP/changed.txt" || true)
removed=$(grep -c '^D' "$RUNNER_TEMP/changed.txt" || true)
{
echo '<!-- cpu-es-final-check -->'
echo "### $verdict"
echo
echo "Head \`${HEAD_SHA::7}\`, checked as merged into the base · [run]($RUN_URL)"
echo
echo '| Step | Result |'
echo '| --- | --- |'
echo "| ES validator (\`python -m app.validate\`) | ${VALIDATE:-skipped} |"
echo "| Tests (\`pytest\`) | ${TESTS:-skipped} |"
echo "| Static index build (\`site/build.py\`) | ${SITE:-skipped} |"
echo
echo "Records: +${added:-0} added · ~${modified:-0} modified · −${removed:-0} removed"
if [ -s "$RUNNER_TEMP/changed.txt" ]; then
echo
echo '<details><summary>Changed records</summary>'
echo
echo '```text'
head -n 200 "$RUNNER_TEMP/changed.txt" | clean
echo '```'
echo '</details>'
fi
[ "$VALIDATE" = failure ] && { echo; echo '<details><summary>Validator log</summary>'; echo; fence "$RUNNER_TEMP/validate.log" 30; echo '</details>'; }
[ "$TESTS" = failure ] && { echo; echo '<details><summary>Test log</summary>'; echo; fence "$RUNNER_TEMP/tests.log" 30; echo '</details>'; }
[ "$SITE" = failure ] && { echo; echo '<details><summary>Build log</summary>'; echo; fence "$RUNNER_TEMP/site.log" 20; echo '</details>'; }
true
} > "$RUNNER_TEMP/body.md"
cat "$RUNNER_TEMP/body.md" >> "$GITHUB_STEP_SUMMARY"
delim="BODY_$(openssl rand -hex 16)"
{ echo "body<<$delim"; cat "$RUNNER_TEMP/body.md"; echo "$delim"; } >> "$GITHUB_OUTPUT"
[ "$verdict" = '✅ Final check passed' ]
comment:
needs: check
if: >-
always() && github.event.label.name == 'final-check' &&
(needs.check.result == 'success' || needs.check.result == 'failure') &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions: {}
steps:
- name: Check the token
id: token
env:
TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN }}
run: |
if [ -z "$TOKEN" ]; then
echo "::warning::TECHENGINEBOT_TOKEN is not set; the verdict is in the check job's summary only."
echo "ok=false" >> "$GITHUB_OUTPUT"
exit 0
fi
case "$TOKEN" in
github_pat_*) echo "ok=true" >> "$GITHUB_OUTPUT" ;;
*) echo "::error::TECHENGINEBOT_TOKEN must be a fine-grained token (github_pat_...) of TechEngineBot with Pull requests: read and write."
exit 1 ;;
esac
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
if: steps.token.outputs.ok == 'true'
env:
BODY: ${{ needs.check.outputs.body }}
PR: ${{ github.event.pull_request.number }}
with:
github-token: ${{ secrets.TECHENGINEBOT_TOKEN }}
script: |
const marker = '<!-- cpu-es-final-check -->';
const body = process.env.BODY || '';
if (!body.startsWith(marker)) {
core.setFailed('The check job produced no report; nothing posted.');
return;
}
const { data: me } = await github.rest.users.getAuthenticated();
if (me.login !== 'TechEngineBot') {
core.setFailed(`TECHENGINEBOT_TOKEN belongs to ${me.login}, not TechEngineBot; nothing posted.`);
return;
}
const { owner, repo } = context.repo;
const issue_number = Number(process.env.PR);
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100,
});
const mine = comments.find((c) => c.user?.login === me.login && c.body?.startsWith(marker));
if (!mine) {
const { data } = await github.rest.issues.createComment({ owner, repo, issue_number, body });
core.info(`Posted ${data.html_url}`);
} else if (mine.body !== body) {
await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body });
core.info(`Updated ${mine.html_url}`);
} else {
core.info(`Unchanged: ${mine.html_url}`);
}