Repository navigation
273 lines (241 loc) · 12.5 KB
/
Copy pathmoxygen-sync.yml
File metadata and controls
273 lines (241 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
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}\"}}
}"