Repository navigation
moxygen sync #272
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: moxygen sync | |
| # Updates the pinned MOXYGEN_REV in cmake/dependencies.cmake to latest | |
| # openmoq/moxygen main, creates a PR. | |
| # Schedule-driven (daily cron) plus workflow_dispatch for ad-hoc syncs. | |
| # repository_dispatch retained for non-sync flows (e.g., manual hand-merged | |
| # moxygen PRs that need immediate propagation); the routine cron-cascade | |
| # does not use it. | |
| # One PR at a time — blocks if one is pending. | |
| # | |
| # Branch naming: sync-moxygen/<short-sha> | |
| # Auto-merged by auto-merge-moxygen.yml after ci-pr passes. | |
| on: | |
| schedule: | |
| # Fourth (final) stage of the openmoq sync cron cascade (UTC; ET shifts under DST): | |
| # 03:23 picoquic upstream-sync (private-octopus → openmoq/picoquic) | |
| # 04:23 moxygen upstream-sync (facebookexperimental → openmoq/moxygen) | |
| # 04:37 moxygen picoquic-pin sync (openmoq/picoquic → moxygen picoquic-rev.txt) | |
| # 08:23 moqx moxygen-rev sync (this workflow — openmoq/moxygen → moqx cmake/dependencies.cmake MOXYGEN_REV) | |
| # The 3h46m gap before this stage gives moxygen sync PRs (upstream + picoquic-pin) | |
| # time to run CI and auto-merge into openmoq/moxygen main before we pull it. | |
| - cron: '23 8 * * *' | |
| repository_dispatch: | |
| types: [moxygen-update] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| sync: | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| # ── Generate GitHub App token (bot identity for PRs) ── | |
| - name: Generate app token | |
| id: app-token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| app-id: ${{ secrets.OMOQ_APP_ID }} | |
| private-key: ${{ secrets.OMOQ_APP_PRIV_KEY }} | |
| - uses: actions/checkout@v5 | |
| with: | |
| ref: main | |
| token: ${{ steps.app-token.outputs.token }} | |
| # ══════════════════════════════════════════════════════════ | |
| # PHASE A: Check for blocking sync PR | |
| # ══════════════════════════════════════════════════════════ | |
| - name: Check for open sync-moxygen PR | |
| id: blocking | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| PR=$(gh api "repos/${{ github.repository }}/pulls?state=open&base=main" \ | |
| --jq '[.[] | select(.head.ref | startswith("sync-moxygen/"))] | .[0] // empty') | |
| if [ -n "$PR" ]; then | |
| PR_NUM=$(echo "$PR" | jq -r '.number') | |
| PR_REF=$(echo "$PR" | jq -r '.head.ref') | |
| echo "::warning::Sync blocked — open PR #$PR_NUM ($PR_REF) pending." | |
| echo "blocked=true" >> "$GITHUB_OUTPUT" | |
| echo "pr_num=$PR_NUM" >> "$GITHUB_OUTPUT" | |
| echo "pr_ref=$PR_REF" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "No open sync-moxygen PR. Proceeding." | |
| echo "blocked=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Notify blocked (Slack) | |
| if: steps.blocking.outputs.blocked == 'true' | |
| continue-on-error: true | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.OMOQ_SLACK_WEBHOOK_URL }} | |
| run: | | |
| PR_NUM="${{ steps.blocking.outputs.pr_num }}" | |
| PR_URL="${{ github.server_url }}/${{ github.repository }}/pull/${PR_NUM}" | |
| TEXT=":no_entry: *${{ github.repository }}* moxygen sync blocked: PR <${PR_URL}|#${PR_NUM}> needs resolution" | |
| curl -s -X POST "$SLACK_WEBHOOK_URL" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"text\": \"${TEXT}\"}" | |
| - name: Notify blocked (email) | |
| if: steps.blocking.outputs.blocked == 'true' | |
| continue-on-error: true | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.OMOQ_AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.OMOQ_AWS_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: us-east-1 | |
| run: | | |
| PR_NUM="${{ steps.blocking.outputs.pr_num }}" | |
| PR_REF="${{ steps.blocking.outputs.pr_ref }}" | |
| PR_URL="${{ github.server_url }}/${{ github.repository }}/pull/${PR_NUM}" | |
| RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| SUBJECT="[moqx] moxygen sync paused — PR #${PR_NUM} pending" | |
| BODY="Moxygen rev sync blocked by open PR.\n\nPR: ${PR_URL}\nBranch: ${PR_REF}\nRun: ${RUN_URL}\n\nResolve or merge the existing PR, then re-run manually." | |
| aws ses send-email \ | |
| --from "noreply@ci.openmoq.org" \ | |
| --destination '{"ToAddresses":["github-notifications@openmoq.org"]}' \ | |
| --message "{ | |
| \"Subject\": {\"Data\": \"${SUBJECT}\"}, | |
| \"Body\": {\"Text\": {\"Data\": \"${BODY}\"}} | |
| }" | |
| # ══════════════════════════════════════════════════════════ | |
| # PHASE B: Bump MOXYGEN_REV and create PR | |
| # ══════════════════════════════════════════════════════════ | |
| - name: Determine target SHA | |
| if: steps.blocking.outputs.blocked == 'false' | |
| id: target | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| # Attacker-controllable on a repository_dispatch payload. Bound to an | |
| # env var so it reaches the shell as data, never spliced into script | |
| # text; the 40-hex check below is a value check, not an injection guard. | |
| CLIENT_PAYLOAD_SHA: ${{ github.event.client_payload.sha }} | |
| run: | | |
| CURRENT_SHA=$(cmake -DPIN=MOXYGEN_REV -P cmake/print-pin.cmake) | |
| echo "current_sha=${CURRENT_SHA}" >> "$GITHUB_OUTPUT" | |
| if [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| TARGET_SHA="$CLIENT_PAYLOAD_SHA" | |
| else | |
| # Manual/scheduled trigger: latest openmoq/moxygen main | |
| TARGET_SHA=$(gh api repos/openmoq/moxygen/commits/main --jq '.sha') | |
| fi | |
| # Guard: TARGET_SHA is spliced into cmake/dependencies.cmake by sed | |
| # below; a non-40-hex value would corrupt the pin (empty payload) or | |
| # break the sed. | |
| if ! printf '%s' "$TARGET_SHA" | grep -qE '^[0-9a-fA-F]{40}$'; then | |
| echo "::error::Invalid moxygen target SHA: '${TARGET_SHA}' (expected 40-hex)" | |
| exit 1 | |
| fi | |
| echo "target_sha=${TARGET_SHA}" >> "$GITHUB_OUTPUT" | |
| echo "short_sha=${TARGET_SHA:0:7}" >> "$GITHUB_OUTPUT" | |
| if [ "$CURRENT_SHA" = "$TARGET_SHA" ]; then | |
| echo "MOXYGEN_REV already at ${TARGET_SHA:0:7}. Nothing to do." | |
| echo "needs_update=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Will update MOXYGEN_REV: ${CURRENT_SHA:0:7} → ${TARGET_SHA:0:7}" | |
| echo "needs_update=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Create sync branch and PR | |
| if: steps.blocking.outputs.blocked == 'false' && steps.target.outputs.needs_update == 'true' | |
| id: new_pr | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| # Untrusted on repository_dispatch; kept out of the script text (only | |
| # interpolated into the PR body as data below). | |
| CLIENT_PAYLOAD_RUN_ID: ${{ github.event.client_payload.run_id }} | |
| run: | | |
| TARGET_SHA="${{ steps.target.outputs.target_sha }}" | |
| SHORT_SHA="${{ steps.target.outputs.short_sha }}" | |
| BRANCH="sync-moxygen/${SHORT_SHA}" | |
| REPO="${{ github.repository }}" | |
| # Reclaim orphan branch if it exists with no open PR. PHASE A's | |
| # blocking check rejects when an open sync-moxygen/* PR is pending, | |
| # so by this point any pre-existing branch with no open PR is from a | |
| # prior closed-without-merge sync attempt — safe to delete. | |
| if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then | |
| EXISTING_PR=$(gh pr list --repo "$REPO" --state open --head "$BRANCH" \ | |
| --json number --jq '.[0].number // empty' 2>/dev/null || true) | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "::error::Branch $BRANCH already used by open PR #$EXISTING_PR — aborting" | |
| exit 1 | |
| fi | |
| echo "::warning::Deleting orphan $BRANCH (closed PR or stale push, no open PR)" | |
| gh api -X DELETE "repos/$REPO/git/refs/heads/$BRANCH" | |
| fi | |
| git config user.email "omoq-sync-bot[bot]@users.noreply.github.com" | |
| git config user.name "omoq-sync-bot[bot]" | |
| git checkout -b "$BRANCH" | |
| # The regex assumes the one-line `set(MOXYGEN_REV "<40-hex>")` shape. | |
| # Verify the write landed, so a reformat of that file fails here rather | |
| # than as a puzzling empty commit. | |
| sed -i -E "s/(MOXYGEN_REV \")[0-9a-fA-F]{40}(\")/\1${TARGET_SHA}\2/" \ | |
| cmake/dependencies.cmake | |
| if ! grep -q "MOXYGEN_REV \"${TARGET_SHA}\"" cmake/dependencies.cmake; then | |
| echo "::error::MOXYGEN_REV bump did not apply — cmake/dependencies.cmake no longer matches 'MOXYGEN_REV \"<40-hex>\"'" | |
| exit 1 | |
| fi | |
| git add cmake/dependencies.cmake | |
| git commit -m "sync: update moxygen to ${SHORT_SHA}" | |
| git push -u origin "$BRANCH" | |
| echo "Created branch $BRANCH" | |
| # Determine trigger source for PR body | |
| if [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| TRIGGER="Triggered by moxygen \`ci main\` run #${CLIENT_PAYLOAD_RUN_ID}." | |
| else | |
| TRIGGER="Triggered manually." | |
| fi | |
| PR_NUM=$(gh api repos/${{ github.repository }}/pulls \ | |
| -f title="sync: moxygen ${SHORT_SHA}" \ | |
| -f body="$(cat <<EOF | |
| Automated moxygen revision update (cmake/dependencies.cmake). | |
| - **Moxygen commit:** [\`${SHORT_SHA}\`](https://github.com/openmoq/moxygen/commit/${TARGET_SHA}) | |
| - **Previous:** \`${{ steps.target.outputs.current_sha }}\` | |
| ${TRIGGER} | |
| Verification will run automatically. On success, this PR auto-merges. | |
| EOF | |
| )" \ | |
| -f head="$BRANCH" \ | |
| -f base="main" \ | |
| --jq '.number') | |
| echo "Created sync PR #$PR_NUM" | |
| echo "pr_num=$PR_NUM" >> "$GITHUB_OUTPUT" | |
| # ── Approve PR (different identity for required review) ── | |
| - name: Approve PR | |
| if: steps.blocking.outputs.blocked == 'false' && steps.target.outputs.needs_update == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.OMOQ_SYNC_TOKEN }} | |
| run: | | |
| PR_NUM="${{ steps.new_pr.outputs.pr_num }}" | |
| [ -z "$PR_NUM" ] && { echo "::warning::No PR number"; exit 0; } | |
| echo "Approving PR #$PR_NUM..." | |
| gh api repos/${{ github.repository }}/pulls/$PR_NUM/reviews \ | |
| -f event=APPROVE \ | |
| -f body="Auto-approved by moxygen sync workflow." \ | |
| --jq '.state' | |
| # ── Failure notifications ── | |
| - name: Notify Slack (sync failure) | |
| if: failure() | |
| continue-on-error: true | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.OMOQ_SLACK_WEBHOOK_URL }} | |
| run: | | |
| RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| TEXT=":warning: *${{ github.repository }}* moxygen sync failed: <${RUN_URL}|run #${{ github.run_number }}>" | |
| curl -s -X POST "$SLACK_WEBHOOK_URL" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"text\": \"${TEXT}\"}" | |
| - name: Notify email (sync failure) | |
| if: failure() | |
| continue-on-error: true | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.OMOQ_AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.OMOQ_AWS_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: us-east-1 | |
| run: | | |
| TARGET="${{ steps.target.outputs.target_sha || 'unknown' }}" | |
| RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| SUBJECT="[moqx] moxygen sync FAILED" | |
| BODY="Moxygen rev sync workflow failed.\n\nTarget SHA: ${TARGET}\nRun: ${RUN_URL}" | |
| aws ses send-email \ | |
| --from "noreply@ci.openmoq.org" \ | |
| --destination '{"ToAddresses":["github-notifications@openmoq.org"]}' \ | |
| --message "{ | |
| \"Subject\": {\"Data\": \"${SUBJECT}\"}, | |
| \"Body\": {\"Text\": {\"Data\": \"${BODY}\"}} | |
| }" |