fix: OFL workflow checkpoint and clear ordering

This commit is contained in:
Ian Chua
2026-09-28 19:53:23 +08:00
parent 7707487252
commit 77f8c64d37
2 changed files with 122 additions and 33 deletions
+107 -33
View File
@@ -12,9 +12,9 @@ name: Daily OFL OTA Update
# vendor-dispatch path is also what makes post_merge_profiles.yml call the OTA auto-publish API after # vendor-dispatch path is also what makes post_merge_profiles.yml call the OTA auto-publish API after
# uploading - see post_merge_profiles.yml for both sides of that contract. # uploading - see post_merge_profiles.yml for both sides of that contract.
# #
# At the start of each run, the pending-publish table is cleared up to a captured # Each run captures a timestamp, dispatches the needed OFL publishers, waits for
# timestamp (POST /api/v1/ota/ofl/pending/clear?timestamp=...). Changes merged after # all of them to finish, then clears the pending-publish table once. Changes merged
# that timestamp remain pending for the next run. # after that timestamp remain pending for the next run.
on: on:
schedule: schedule:
@@ -34,32 +34,14 @@ jobs:
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }} if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }}
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Capture start timestamp and clear OFL pending queue - name: Capture start timestamp
id: start id: start
shell: bash shell: bash
env:
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
run: | run: |
set -euo pipefail set -euo pipefail
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
timestamp="$(date -u +%s)" timestamp="$(date -u +%s)"
echo "timestamp=$timestamp" >> "$GITHUB_OUTPUT" echo "timestamp=$timestamp" >> "$GITHUB_OUTPUT"
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$timestamp" \
-H "Authorization: Bearer $OTA_API_KEY")"
body="$(cat "$resp_file")"
echo "$body"
if [ "$status" != "200" ]; then
echo "::error::OTA pending-clear call failed with HTTP $status"
exit 1
fi
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v7 uses: actions/checkout@v7
with: with:
@@ -93,15 +75,17 @@ jobs:
# by $branch would never match anything except main. post_merge_profiles.yml # by $branch would never match anything except main. post_merge_profiles.yml
# genuinely runs per-branch (this dispatch below sets --ref "$branch"), # genuinely runs per-branch (this dispatch below sets --ref "$branch"),
# so its history is the real per-branch checkpoint. It also means a # so its history is the real per-branch checkpoint. It also means a
# failed publish naturally gets retried tomorrow: the checkpoint only # failed publish naturally gets retried tomorrow. Only runs marked by
# advances on a run that actually succeeded. # this cron count, so an unrelated successful manual run cannot advance
# the OFL checkpoint.
# --method GET is required, not cosmetic: gh api defaults to POST # --method GET is required, not cosmetic: gh api defaults to POST
# whenever -f fields are present unless a method is given # whenever -f fields are present unless a method is given
# explicitly, and POST on this list-runs endpoint 404s - confirmed # explicitly, and POST on this list-runs endpoint 404s - confirmed
# on real Actions infrastructure, not just reasoned about. # on real Actions infrastructure, not just reasoned about.
since="$(gh api --method GET "repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \ successful_runs="$(gh api --method GET "repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
-f status=success -f branch="$branch" -f per_page=1 \ -f status=success -f branch="$branch" -f per_page=100 --paginate \
--jq '.workflow_runs[0].run_started_at // empty')" --jq '.workflow_runs[] | select((.display_title // "") | contains("[OFL cron "))')"
since="$(jq -rs 'sort_by(.run_started_at) | last.run_started_at // empty' <<< "$successful_runs")"
if [ -z "$since" ]; then if [ -z "$since" ]; then
echo "No prior successful run for $branch; checking OFL changes up to $SCAN_UNTIL." echo "No prior successful run for $branch; checking OFL changes up to $SCAN_UNTIL."
@@ -124,16 +108,106 @@ jobs:
fi fi
if [ "$changed" = true ]; then if [ "$changed" = true ]; then
# Tolerate a per-branch failure (e.g. a pre-existing release branch dispatch_id="${GITHUB_RUN_ID}-${branch//\//-}"
# whose post_merge_profiles.yml predates the vendor/auto_publish # Record successful dispatches for the barrier step below. A
# inputs) rather than aborting the whole scan under set -e. # dispatch failure prevents clearing, so the branch is retried
if ! gh workflow run post_merge_profiles.yml \ # on the next cron run.
if gh workflow run post_merge_profiles.yml \
--repo "${{ github.repository }}" \ --repo "${{ github.repository }}" \
--ref "$branch" \ --ref "$branch" \
-f vendor="$VENDOR" -f auto_publish=true; then -f vendor="$VENDOR" -f auto_publish=true \
echo "::warning::failed to dispatch post_merge_profiles.yml for $branch - its post_merge_profiles.yml at this ref may predate the vendor/auto_publish inputs" -f ofl_cron_dispatch_id="$dispatch_id"; then
printf '%s\t%s\n' "$branch" "$dispatch_id" >> "$RUNNER_TEMP/ofl-dispatches.tsv"
else
echo "::error::failed to dispatch post_merge_profiles.yml for $branch"
printf '%s\n' "$branch" >> "$RUNNER_TEMP/ofl-dispatch-failures.txt"
fi fi
fi fi
echo "::endgroup::" echo "::endgroup::"
done done
- name: Wait for OFL publishers
id: wait
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DISPATCHES_FILE: ${{ runner.temp }}/ofl-dispatches.tsv
DISPATCH_FAILURES_FILE: ${{ runner.temp }}/ofl-dispatch-failures.txt
run: |
set -euo pipefail
if [ -s "$DISPATCH_FAILURES_FILE" ]; then
echo "::error::one or more OFL publisher workflows could not be dispatched:"
sed 's/^/ - /' "$DISPATCH_FAILURES_FILE"
exit 1
fi
if [ ! -s "$DISPATCHES_FILE" ]; then
echo "No OFL publisher workflows were dispatched; pending queue will not be cleared."
echo "publishers_dispatched=false" >> "$GITHUB_OUTPUT"
exit 0
fi
: > "$RUNNER_TEMP/ofl-run-ids.tsv"
while IFS=$'\t' read -r branch dispatch_id; do
[ -n "$branch" ] || continue
echo "Waiting for OFL publisher on $branch ($dispatch_id)"
run_id=""
for _ in {1..120}; do
runs_json="$(gh api --method GET \
"repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
-f branch="$branch" -f event=workflow_dispatch -f per_page=100)"
run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \
'[.workflow_runs[] | select((.display_title // "") | contains($marker))] \
| sort_by(.created_at) | last | .id // empty' <<< "$runs_json")"
[ -n "$run_id" ] && break
sleep 5
done
if [ -z "$run_id" ]; then
echo "::error::could not find dispatched post_merge_profiles run for $branch ($dispatch_id)"
exit 1
fi
printf '%s\t%s\n' "$branch" "$run_id" >> "$RUNNER_TEMP/ofl-run-ids.tsv"
done < "$DISPATCHES_FILE"
all_success=true
while IFS=$'\t' read -r branch run_id; do
[ -n "$run_id" ] || continue
echo "Watching OFL publisher run $run_id for $branch"
if ! gh run watch "$run_id" --repo "${{ github.repository }}" --exit-status; then
all_success=false
fi
done < "$RUNNER_TEMP/ofl-run-ids.tsv"
if [ "$all_success" != true ]; then
echo "::error::one or more OFL publisher workflows failed; pending queue will not be cleared"
exit 1
fi
echo "publishers_dispatched=true" >> "$GITHUB_OUTPUT"
- name: Clear OFL pending queue
if: steps.wait.outputs.publishers_dispatched == 'true'
shell: bash
env:
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
TIMESTAMP: ${{ steps.start.outputs.timestamp }}
run: |
set -euo pipefail
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$TIMESTAMP" \
-H "Authorization: Bearer $OTA_API_KEY")"
body="$(cat "$resp_file")"
echo "$body"
if [ "$status" != "200" ]; then
echo "::error::OTA pending-clear call failed with HTTP $status"
exit 1
fi
+15
View File
@@ -1,5 +1,14 @@
name: Post-merge profiles name: Post-merge profiles
run-name: >-
Post-merge profiles${{
inputs.ofl_cron_dispatch_id != '' &&
inputs.vendor == 'OrcaFilamentLibrary' &&
(inputs.auto_publish == true || inputs.auto_publish == 'true') &&
format(' [OFL cron {0}]', inputs.ofl_cron_dispatch_id) ||
''
}}
# Push-triggered counterpart to check_profiles.yml (which only gates PRs). When a # Push-triggered counterpart to check_profiles.yml (which only gates PRs). When a
# profile change lands on main or a release branch, rebuild the affected vendors' # profile change lands on main or a release branch, rebuild the affected vendors'
# binary preset caches (<vendor>.opc) and publish each as a versioned ZIP asset on # binary preset caches (<vendor>.opc) and publish each as a versioned ZIP asset on
@@ -59,6 +68,12 @@ on:
required: false required: false
type: boolean type: boolean
default: false default: false
ofl_cron_dispatch_id:
description: >-
Unique marker supplied by the trusted OFL daily cron so it can find
and wait for this dispatched workflow run.
required: false
type: string
permissions: permissions:
contents: read contents: read