Skip to content

moxygen sync

moxygen sync #153

Workflow file for this run

name: moxygen sync
# Updates deps/moxygen submodule to latest 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-submodule sync (this workflow — openmoq/moxygen → moqx deps/moxygen)
# 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@v2
with:
app-id: ${{ secrets.OMOQ_APP_ID }}
private-key: ${{ secrets.OMOQ_APP_PRIV_KEY }}
- uses: actions/checkout@v4
with:
ref: main
submodules: true
token: ${{ steps.app-token.outputs.token }}
# Full history required so we can check out arbitrary moxygen
# SHAs inside the submodule. Shallow (default depth=1) creates
# shallow submodule clones, which fail with "unable to read
# tree" when checking out a SHA whose tree isn't present.
fetch-depth: 0
# ══════════════════════════════════════════════════════════
# 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 submodule 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: Update submodule and create PR
# ══════════════════════════════════════════════════════════
- name: Determine target SHA
if: steps.blocking.outputs.blocked == 'false'
id: target
run: |
CURRENT_SHA=$(git -C deps/moxygen rev-parse HEAD)
echo "current_sha=${CURRENT_SHA}" >> "$GITHUB_OUTPUT"
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
TARGET_SHA="${{ github.event.client_payload.sha }}"
else
# Manual trigger: fetch latest moxygen main
git -C deps/moxygen fetch origin main
TARGET_SHA=$(git -C deps/moxygen rev-parse origin/main)
fi
echo "target_sha=${TARGET_SHA}" >> "$GITHUB_OUTPUT"
echo "short_sha=${TARGET_SHA:0:7}" >> "$GITHUB_OUTPUT"
if [ "$CURRENT_SHA" = "$TARGET_SHA" ]; then
echo "Submodule already at ${TARGET_SHA:0:7}. Nothing to do."
echo "needs_update=false" >> "$GITHUB_OUTPUT"
else
echo "Will update submodule: ${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 }}
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"
# Update submodule to target SHA
git -C deps/moxygen fetch origin
git -C deps/moxygen checkout "$TARGET_SHA"
git add deps/moxygen
git commit -m "sync: update moxygen submodule 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 #${{ github.event.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 submodule update.
- **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 submodule 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}\"}}
}"