-
Notifications
You must be signed in to change notification settings - Fork 1
153 lines (137 loc) · 6.38 KB
/
Copy pathspec-changes.yml
File metadata and controls
153 lines (137 loc) · 6.38 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
name: Record spec changes
# On every spec merge, compute which post-mount services changed (and whether
# any change is breaking) and PUSH the manifest to the SDK automation bot, which
# stores it in its own D1. Nothing is committed back to this repo — the
# substance lives in scripts/build-spec-changes.mjs; this workflow runs
# `oagen diff`/`parse` and POSTs the result (HMAC-signed). The bot owns
# retention, so no artifact files accumulate here.
on:
push:
branches: [main]
paths:
- 'spec/**'
# Manual re-push: re-run the producer for the current spec@main against its
# parent (diffs HEAD~1 → HEAD), e.g. to recover a run that failed before
# POSTing its manifest. Pushing only this workflow file does not match the
# spec/** path filter, so a fix to this job needs a manual run to take effect.
workflow_dispatch:
concurrency:
# Serialize so two spec merges in quick succession each get their manifest
# pushed; do not cancel an in-flight run (each merge deserves its push).
group: spec-changes
cancel-in-progress: false
env:
SDK_BOT_URL: https://sdk-automation-bot.workos.tools
jobs:
record:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Build policy module
# build-spec-changes.mjs imports mountRules from dist/policy.mjs, which
# is git-ignored and not produced by `npm ci`.
run: npm run build:policy
- name: Resolve parent spec
id: parent
run: |
set -euo pipefail
PARENT="${{ github.event.before }}"
if [ -z "$PARENT" ] || [ "$PARENT" = "0000000000000000000000000000000000000000" ]; then
PARENT="$(git rev-parse HEAD~1 2>/dev/null || true)"
fi
if [ -z "$PARENT" ] || ! git cat-file -e "$PARENT:spec/open-api-spec.yaml" 2>/dev/null; then
echo "No parent spec to diff against; nothing to record."
echo "available=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git show "$PARENT:spec/open-api-spec.yaml" > /tmp/previous-open-api-spec.yaml
echo "parent=$PARENT" >> "$GITHUB_OUTPUT"
echo "available=true" >> "$GITHUB_OUTPUT"
- name: Diff + parse spec revisions
if: steps.parent.outputs.available == 'true'
run: |
set -euo pipefail
# `oagen diff` signals its result through the exit code: 0 = no changes,
# 1 = non-breaking changes, 2 = breaking changes — all are SUCCESSFUL
# diffs. Only a code outside {0,1,2} is a real failure; a genuine oagen
# crash also yields no parseable report, caught by the `.summary` check
# below (which is the authoritative validity gate). The `|| DIFF_EXIT=$?`
# suffix is errexit-safe (a command on the left of `||` never trips
# `-e`), so we keep `-e` on to ensure the `oagen parse` commands below
# fail the step instead of silently writing empty IR.
REPORT=$(npx oagen diff \
--old /tmp/previous-open-api-spec.yaml \
--new spec/open-api-spec.yaml) || DIFF_EXIT=$?
if [ "${DIFF_EXIT:-0}" -gt 2 ]; then
echo "::warning::oagen diff failed (exit code ${DIFF_EXIT})"
exit 1
fi
if ! echo "$REPORT" | jq -e '.summary' > /dev/null 2>&1; then
echo "::warning::oagen diff returned invalid JSON"
exit 1
fi
echo "$REPORT" > /tmp/diff-report.json
# IR for both revisions lets build-spec-changes attribute model/enum
# changes to the services that reference them.
npx oagen parse --spec /tmp/previous-open-api-spec.yaml > /tmp/previous-ir.json
npx oagen parse --spec spec/open-api-spec.yaml > /tmp/current-ir.json
- name: Push changed-services manifest to the SDK bot
if: steps.parent.outputs.available == 'true'
env:
SPEC_CHANGES_SECRET: ${{ secrets.SPEC_CHANGES_SECRET }}
HEAD_COMMIT_MSG: ${{ github.event.head_commit.message }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# Commit subject (first line) + originating PR number, for the
# dashboard's "spec commits / PRs" pane. Squash merges put `(#NN)` in
# the subject; absent that, prNumber is simply omitted.
SUBJECT=$(printf '%s\n' "$HEAD_COMMIT_MSG" | head -1)
PR_NUM=$(printf '%s' "$SUBJECT" | grep -oE '#[0-9]+' | head -1 | tr -d '#' || true)
ARGS=(
--report /tmp/diff-report.json
--old-ir /tmp/previous-ir.json
--new-ir /tmp/current-ir.json
--sha "${{ github.sha }}"
--parent-sha "${{ steps.parent.outputs.parent }}"
--commit-message "$SUBJECT"
)
if [ -n "$PR_NUM" ]; then
ARGS+=(--pr-number "$PR_NUM" --pr-url "https://github.com/$REPO/pull/$PR_NUM")
fi
# build-spec-changes.mjs writes the manifest to stdout when --output is
# omitted. Capture it, then HMAC-sign the exact bytes we POST.
MANIFEST=$(node scripts/build-spec-changes.mjs "${ARGS[@]}")
echo "Manifest:"
echo "$MANIFEST" | jq .
if [ "$(echo "$MANIFEST" | jq '.changedServices | length')" -eq 0 ]; then
echo "No service changes; nothing to push."
exit 0
fi
SIG="sha256=$(printf '%s' "$MANIFEST" | openssl dgst -sha256 -hmac "$SPEC_CHANGES_SECRET" | sed 's/^.*= //')"
for attempt in 1 2 3; do
if curl -sS -X POST "$SDK_BOT_URL/internal/spec-changes" \
-H "Content-Type: application/json" \
-H "X-Spec-Changes-Signature: $SIG" \
--data-raw "$MANIFEST" \
--fail-with-body; then
echo ""
echo "Pushed manifest for ${{ github.sha }}."
exit 0
fi
echo "push attempt ${attempt} failed; retrying in 5s..."
sleep 5
done
echo "::error::could not push spec-changes manifest after 3 attempts"
exit 1