Compare commits

...
Author SHA1 Message Date
Ian Chua 0030bed519 fix: ota updates workflow 2026-09-23 20:37:25 +08:00
2 changed files with 174 additions and 5 deletions
+88
View File
@@ -0,0 +1,88 @@
name: Daily OFL OTA Update
# This workflow is intended for creating and publishing the OrcaFilamentLibrary (OFL) OPC package to
# https://github.com/OrcaSlicer/orcaslicer-profiles, which generates an OTA update.
# This cronjob runs daily at 00:00 UTC every day and scans main plus every release/vX.Y.Z branch for
# changes to resources/profiles/OrcaFilamentLibrary since that branch's own last successful run. Any
# branch with no changes is skipped; each changed branch gets its own post_merge_profiles.yml dispatch.
#
# OFL has no dedicated FOLDER_MERGERS grant (it isn't merged through the PR merge-bot delegation
# scheme), so post_merge_profiles.yml is dispatched with an explicit `vendor` input, which that
# workflow trusts and uses to bypass the FOLDER_MERGERS check for this trigger. That same explicit-
# 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.
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
actions: write # list this workflow's past runs and dispatch post_merge_profiles.yml
contents: read
env:
VENDOR: OrcaFilamentLibrary
jobs:
daily-job:
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }}
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
# Full history: the per-branch "since last successful run" check below
# needs to look arbitrarily far back if a prior run failed or was skipped.
fetch-depth: 0
- name: Fetch all branches
shell: bash
run: git fetch origin '+refs/heads/*:refs/remotes/origin/*'
- name: Scan branches and publish changed OFL profiles
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
mapfile -t branches < <(
gh api "repos/${{ github.repository }}/branches" --paginate --jq '.[].name' \
| grep -E '^(main|release/v[0-9]+\.[0-9]+\.[0-9]+)$' | sort -u
)
for branch in "${branches[@]}"; do
echo "::group::$branch"
since="$(gh api "repos/${{ github.repository }}/actions/workflows/ofl-ota-cronjob.yml/runs" \
-f status=success -f branch="$branch" -f per_page=1 \
--jq '.workflow_runs[0].run_started_at // empty')"
if [ -z "$since" ]; then
echo "No prior successful run for $branch; treating OFL as changed."
changed=true
else
changed_files="$(git log --since="$since" --name-only --pretty=format: "origin/$branch" -- \
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
| sed '/^$/d')"
if [ -n "$changed_files" ]; then
echo "OFL changed on $branch since $since:"
echo "$changed_files"
changed=true
else
echo "No OFL changes on $branch since $since."
changed=false
fi
fi
if [ "$changed" = true ]; then
gh workflow run post_merge_profiles.yml \
--repo "${{ github.repository }}" \
--ref "$branch" \
-f vendor="$VENDOR"
fi
echo "::endgroup::"
done
+86 -5
View File
@@ -8,6 +8,12 @@ name: Post-merge profiles
# only then does it become a live OTA update - this workflow does none of that # only then does it become a live OTA update - this workflow does none of that
# last part (no changelog, no R2, no webhook). # last part (no changelog, no R2, no webhook).
# #
# A workflow_dispatch carrying a `vendor` input (e.g. the daily OFL cron - OFL has
# no FOLDER_MERGERS grant, since it isn't merged through the PR merge-bot delegation
# scheme) publishes that vendor directly and skips the FOLDER_MERGERS check below.
# workflow_dispatch is already a trusted, explicit trigger, unlike the automatic
# push-diff path the FOLDER_MERGERS check exists to gate.
#
# Asset contract expected by OrcaCloud's release scanner: # Asset contract expected by OrcaCloud's release scanner:
# ^(\d+\.\d+\.\d+)_([^_]+)_(\d+(?:\.\d+){3})_(\d{12})\.zip$ # ^(\d+\.\d+\.\d+)_([^_]+)_(\d+(?:\.\d+){3})_(\d{12})\.zip$
# <orca_ver>_<vendor>_<profile_version>_<UTC yyyymmddHHMM>.zip (zip root: <vendor>.opc) # <orca_ver>_<vendor>_<profile_version>_<UTC yyyymmddHHMM>.zip (zip root: <vendor>.opc)
@@ -17,13 +23,25 @@ name: Post-merge profiles
on: on:
push: push:
branches: branches:
# once v2.5.0 stable is released, this will be removed, so nightly won't receive OTA updates.
- main - main
- release/* # release/vX.Y.Z point-release branches only, not the release/vX.Y working
# branch profile PRs land on first - "v*.*.*" requires two literal dots,
# which release/vX.Y (one dot) doesn't have.
- release/v*.*.*
paths: paths:
- 'resources/profiles/**' - 'resources/profiles/**'
- '.github/workflows/post_merge_profiles.yml' - '.github/workflows/post_merge_profiles.yml'
workflow_dispatch: workflow_dispatch:
inputs:
vendor:
description: >-
Publish only this vendor, bypassing the FOLDER_MERGERS grant check.
For trusted explicit dispatches only (e.g. the OFL nightly cron).
Leave empty to fall back to diffing the triggering commit.
required: false
type: string
permissions: permissions:
contents: read contents: read
@@ -66,8 +84,23 @@ jobs:
shell: bash shell: bash
env: env:
FOLDER_MERGERS: ${{ vars.FOLDER_MERGERS }} FOLDER_MERGERS: ${{ vars.FOLDER_MERGERS }}
DISPATCH_VENDOR: ${{ github.event_name == 'workflow_dispatch' && inputs.vendor || '' }}
run: | run: |
set -euo pipefail set -euo pipefail
# Explicit vendor dispatch (e.g. the OFL cron): trust the caller and
# skip both the git-diff detection and the FOLDER_MERGERS check below.
if [ -n "$DISPATCH_VENDOR" ]; then
v="$DISPATCH_VENDOR"
json="resources/profiles/$v.json"
if [ ! -f "$json" ] || { [ ! -d "resources/profiles/$v" ] && ! jq -e '.version' "$json" >/dev/null 2>&1; }; then
echo "::error::vendor '$v' has no resources/profiles/$v.json with a profile directory or version field"
exit 1
fi
echo "vendors=$v" >> "$GITHUB_OUTPUT"
exit 0
fi
base='${{ github.event.before }}' base='${{ github.event.before }}'
head='${{ github.sha }}' head='${{ github.sha }}'
# Zero SHA (branch created / force push) or manual dispatch: fall back # Zero SHA (branch created / force push) or manual dispatch: fall back
@@ -101,6 +134,9 @@ jobs:
# sibling bundle JSON are covered by at least one FOLDER_MERGERS # sibling bundle JSON are covered by at least one FOLDER_MERGERS
# grant. The account part is intentionally ignored here: this is a # grant. The account part is intentionally ignored here: this is a
# post-merge safety check, not an authorization check for a command. # post-merge safety check, not an authorization check for a command.
# An ineligible vendor (e.g. OrcaFilamentLibrary, which has no grant)
# is dropped on its own - it never blocks other vendors in the same
# push from publishing.
grants=() grants=()
while IFS= read -r raw_line; do while IFS= read -r raw_line; do
line="${raw_line#"${raw_line%%[![:space:]]*}"}" line="${raw_line#"${raw_line%%[![:space:]]*}"}"
@@ -127,19 +163,21 @@ jobs:
return 1 return 1
} }
authorized=()
unauthorized=() unauthorized=()
for v in "${vendors[@]}"; do for v in "${vendors[@]}"; do
if ! is_granted "resources/profiles/$v" || ! is_granted "resources/profiles/$v.json"; then if is_granted "resources/profiles/$v" && is_granted "resources/profiles/$v.json"; then
authorized+=("$v")
else
unauthorized+=("$v") unauthorized+=("$v")
fi fi
done done
if [ "${#unauthorized[@]}" -ne 0 ]; then if [ "${#unauthorized[@]}" -ne 0 ]; then
echo "vendors=" >> "$GITHUB_OUTPUT" echo "::warning::skipping vendor(s) with no FOLDER_MERGERS grant (no asset built or published for them this run): ${unauthorized[*]}"
exit 0
fi fi
echo "vendors=${vendors[*]}" >> "$GITHUB_OUTPUT" echo "vendors=${authorized[*]}" >> "$GITHUB_OUTPUT"
- name: Resolve Orca version - name: Resolve Orca version
id: orca id: orca
@@ -242,3 +280,46 @@ jobs:
echo "### Published to \`$repo\` release \`$RELEASE_TAG\`" echo "### Published to \`$repo\` release \`$RELEASE_TAG\`"
for f in "$ASSET_DIR"/*.zip; do echo "- \`$(basename "$f")\`"; done for f in "$ASSET_DIR"/*.zip; do echo "- \`$(basename "$f")\`"; done
} >> "$GITHUB_STEP_SUMMARY" } >> "$GITHUB_STEP_SUMMARY"
- name: Notify OTA auto-publish
# Only the explicit-vendor-dispatch path (e.g. the OFL cron) goes live
# automatically. The ordinary push path stays human-gated: assets land on
# the profiles release above, and a maintainer still has to attach a
# changelog and hit Publish in OrcaCloud's OTA Manager for those.
if: steps.vendors.outputs.vendors != '' && github.event_name == 'workflow_dispatch' && inputs.vendor != ''
shell: bash
env:
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
ASSET_DIR: ${{ steps.pkg.outputs.dir }}
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; }
mapfile -t zip_files < <(cd "$ASSET_DIR" && ls -1 *.zip)
filenames_json="$(printf '%s\n' "${zip_files[@]}" | jq -R . | jq -s .)"
payload="$(jq -n --argjson filenames "$filenames_json" '{filenames: $filenames}')"
resp_file="$RUNNER_TEMP/ota-auto-publish-response.json"
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
"${OTA_API_BASE_URL%/}/api/v1/ota/auto-publish" \
-H "Authorization: Bearer $OTA_API_KEY" \
-H 'Content-Type: application/json' \
-d "$payload")"
body="$(cat "$resp_file")"
echo "$body"
if [ "$status" != "200" ]; then
echo "::error::OTA auto-publish call failed with HTTP $status"
exit 1
fi
# A 200 can still carry per-file "error" results (e.g. NOT_FOUND); the
# asset is already safely published to the profiles release above, but
# it never went live, so treat that as a failure worth surfacing loudly.
error_count="$(jq '[.results[] | select(.status == "error")] | length' <<< "$body")"
if [ "$error_count" != "0" ]; then
jq -r '.results[] | select(.status == "error") | "::error::\(.filename): \(.code) - \(.message)"' <<< "$body"
exit 1
fi