-
Notifications
You must be signed in to change notification settings - Fork 0
278 lines (243 loc) · 11.5 KB
/
deploy-develop.yml
File metadata and controls
278 lines (243 loc) · 11.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
274
275
276
277
278
name: Deploy develop
on:
push:
branches:
- develop
workflow_dispatch: {}
concurrency:
group: deploy-develop
cancel-in-progress: true
permissions:
contents: read
jobs:
deploy:
name: Lint, test, migrate, and deploy registry + proxy to dev
runs-on: ubuntu-latest
timeout-minutes: 30
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CF_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
APP_VERSION: ${{ github.sha }}
REGISTRY_HEALTH_URL_OVERRIDE: ${{ secrets.REGISTRY_HEALTH_URL }}
PROXY_HEALTH_URL_OVERRIDE: ${{ secrets.PROXY_HEALTH_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.23.0
- name: Validate required secrets
run: |
test -n "${CLOUDFLARE_API_TOKEN}"
test -n "${CLOUDFLARE_ACCOUNT_ID}"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify Worker type bindings are up to date
run: |
pnpm -F @clawdentity/registry run types:dev
pnpm -F @clawdentity/proxy run types:dev
git diff --exit-code -- apps/registry/worker-configuration.d.ts apps/proxy/worker-configuration.d.ts
- name: Lint
run: pnpm lint
- name: Typecheck
run: pnpm -r typecheck
- name: Build
run: pnpm -r build
- name: Run tests
run: pnpm -r test
- name: Wrangler deploy preflight (dry-run)
run: |
pnpm exec wrangler --cwd apps/registry deploy --env dev --dry-run --var APP_VERSION:${APP_VERSION}
pnpm exec wrangler --cwd apps/proxy deploy --env dev --dry-run --var APP_VERSION:${APP_VERSION}
- name: Capture pre-deploy rollback artifacts
run: |
mkdir -p artifacts
PREDEPLOY_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "PREDEPLOY_TS=${PREDEPLOY_TS}" >> "${GITHUB_ENV}"
printf "%s\n" "${PREDEPLOY_TS}" > artifacts/predeploy.timestamp
pnpm exec wrangler --cwd apps/registry deployments list --env dev --json > artifacts/registry-deployments-pre.json
# First proxy deploy may not have an existing Worker/deployments yet.
pnpm exec wrangler --cwd apps/proxy deployments list --env dev --json > artifacts/proxy-deployments-pre.json || true
pnpm exec wrangler --cwd apps/registry d1 time-travel info clawdentity-db-dev --env dev --timestamp "${PREDEPLOY_TS}" --json > artifacts/d1-time-travel-pre.json
pnpm exec wrangler --cwd apps/registry d1 export clawdentity-db-dev --remote --env dev --output "${GITHUB_WORKSPACE}/artifacts/d1-dev-predeploy.sql"
- name: Apply registry dev migrations and deploy
run: |
pnpm exec wrangler --cwd apps/registry d1 migrations apply clawdentity-db-dev --remote --env dev
pnpm exec wrangler --cwd apps/registry deploy --env dev --var APP_VERSION:${APP_VERSION}
- name: Verify registry deployment exists in Wrangler
run: |
mkdir -p artifacts
pnpm exec wrangler --cwd apps/registry deployments list --env dev --json > artifacts/registry-deployments-current.json
python3 - <<'PY'
import json
path = "artifacts/registry-deployments-current.json"
with open(path, "r", encoding="utf-8") as f:
payload = json.load(f)
if not isinstance(payload, list) or len(payload) == 0:
raise SystemExit(f"wrangler returned no registry deployments in {path}")
print("registry wrangler deployment check passed")
print(payload[0])
PY
- name: Verify registry health endpoint
run: |
python3 - <<'PY'
import json, os, sys, time, urllib.request, urllib.error
configured_url = os.environ.get("REGISTRY_HEALTH_URL_OVERRIDE", "").strip()
if configured_url and not configured_url.endswith("/health"):
configured_url = f"{configured_url.rstrip('/')}/health"
url = configured_url or "https://dev.registry.clawdentity.com/health"
expected_version = os.environ.get("APP_VERSION", "")
if not expected_version:
raise SystemExit("APP_VERSION was not set in workflow environment")
attempts = 18
delay_seconds = 10
last_error = None
for attempt in range(1, attempts + 1):
try:
req = urllib.request.Request(
url,
headers={
"Cache-Control": "no-cache",
"Accept": "application/json",
"User-Agent": "Clawdentity-CI/1.0",
},
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.load(resp)
if data.get("status") != "ok" or data.get("environment") != "development":
raise RuntimeError(f"unexpected health payload: {data}")
if data.get("version") == "0.0.0":
raise RuntimeError(f"unexpected placeholder version after deploy: {data}")
if data.get("version") != expected_version:
raise RuntimeError(
f"unexpected version: expected {expected_version}, got {data.get('version')}"
)
print(f"healthcheck passed on attempt {attempt}", data)
break
except Exception as exc:
last_error = exc
sys.stderr.write(
f"registry health attempt {attempt}/{attempts} failed: {exc}\n"
)
if attempt == attempts:
raise SystemExit(
f"registry health check failed after {attempts} attempts: {last_error}"
)
time.sleep(delay_seconds)
PY
- name: Deploy proxy to dev environment
run: |
mkdir -p artifacts
PROXY_DEPLOY_OUTPUT_FILE="artifacts/proxy-deploy-output.txt"
pnpm exec wrangler --cwd apps/proxy deploy --env dev --var APP_VERSION:${APP_VERSION} 2>&1 | tee "${PROXY_DEPLOY_OUTPUT_FILE}"
PROXY_WORKERS_DEV_URL="$(grep -Eo 'https://[[:alnum:]._-]+\.workers\.dev' "${PROXY_DEPLOY_OUTPUT_FILE}" | head -n 1 || true)"
PROXY_HEALTH_URL=""
if [ -n "${PROXY_WORKERS_DEV_URL}" ]; then
PROXY_HEALTH_URL="${PROXY_WORKERS_DEV_URL}/health"
elif [ -n "${PROXY_HEALTH_URL_OVERRIDE}" ]; then
PROXY_HEALTH_URL="${PROXY_HEALTH_URL_OVERRIDE%/}/health"
else
PROXY_HEALTH_URL="https://dev.proxy.clawdentity.com/health"
fi
echo "PROXY_HEALTH_URL=${PROXY_HEALTH_URL}" >> "${GITHUB_ENV}"
echo "Resolved proxy health URL: ${PROXY_HEALTH_URL}"
- name: Verify proxy deployment exists in Wrangler
run: |
mkdir -p artifacts
pnpm exec wrangler --cwd apps/proxy deployments list --env dev --json > artifacts/proxy-deployments-current.json
python3 - <<'PY'
import json
path = "artifacts/proxy-deployments-current.json"
with open(path, "r", encoding="utf-8") as f:
payload = json.load(f)
if not isinstance(payload, list) or len(payload) == 0:
raise SystemExit(f"wrangler returned no proxy deployments in {path}")
print("proxy wrangler deployment check passed")
print(payload[0])
PY
- name: Verify proxy health endpoint
run: |
python3 - <<'PY'
import json, os, sys, time, urllib.request, urllib.error
url = os.environ.get("PROXY_HEALTH_URL", "")
expected_version = os.environ.get("APP_VERSION", "")
if not url:
raise SystemExit("PROXY_HEALTH_URL was not set")
if not expected_version:
raise SystemExit("APP_VERSION was not set in workflow environment")
attempts = 18
delay_seconds = 10
last_error = None
for attempt in range(1, attempts + 1):
try:
req = urllib.request.Request(
url,
headers={
"Cache-Control": "no-cache",
"Accept": "application/json",
"User-Agent": "Clawdentity-CI/1.0",
},
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.load(resp)
if data.get("status") != "ok" or data.get("environment") != "development":
raise RuntimeError(f"unexpected proxy health payload: {data}")
if data.get("version") == "0.0.0":
raise RuntimeError(
f"unexpected placeholder proxy version after deploy: {data}"
)
if data.get("version") != expected_version:
raise RuntimeError(
f"unexpected proxy version: expected {expected_version}, got {data.get('version')}"
)
print(f"proxy healthcheck passed on attempt {attempt}", data)
break
except Exception as exc:
last_error = exc
sys.stderr.write(
f"proxy health attempt {attempt}/{attempts} failed: {exc}\n"
)
if attempt == attempts:
raise SystemExit(
f"proxy health check failed after {attempts} attempts: {last_error}"
)
time.sleep(delay_seconds)
PY
- name: Capture post-deploy state
if: always()
run: |
mkdir -p artifacts
pnpm exec wrangler --cwd apps/registry deployments list --env dev --json > artifacts/registry-deployments-post.json || true
pnpm exec wrangler --cwd apps/proxy deployments list --env dev --json > artifacts/proxy-deployments-post.json || true
pnpm exec wrangler --cwd apps/registry d1 migrations list clawdentity-db-dev --remote --env dev > artifacts/d1-migrations-post.txt || true
- name: Rollback instructions on failure
if: failure()
run: |
echo "Registry Worker rollback:"
echo " wrangler --cwd apps/registry deployments list --env dev --json"
echo " wrangler --cwd apps/registry rollback <version-id> --env dev -y -m \"ci rollback\""
echo ""
echo "Proxy Worker rollback:"
echo " wrangler --cwd apps/proxy deployments list --env dev --json"
echo " wrangler --cwd apps/proxy rollback <version-id> --env dev -y -m \"ci rollback\""
echo ""
echo "D1 rollback:"
echo " wrangler --cwd apps/registry d1 time-travel restore clawdentity-db-dev --env dev --timestamp \"${PREDEPLOY_TS}\""
echo " # or restore via bookmark from artifacts/d1-time-travel-pre.json"
- name: Upload rollback artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: deploy-develop-rollback-${{ github.run_id }}
path: artifacts/
if-no-files-found: warn
retention-days: 14