Skip to content

Commit 1645a2d

Browse files
chore(infra): prune dotcom-deploy-assets-cache R2 bucket for closed PRs (tldraw#8746)
The `prune-preview-deploys` workflow already tears down preview workers, Supabase branches, fly.io zero-cache apps, and Zero litestream R2 backups, but it didn't touch `dotcom-deploy-assets-cache` — the R2 bucket where `coalesceWithPreviousAssets` (`internal/scripts/deploy-dotcom.ts:957`) writes a tarball per deploy. Closed PRs were leaving `pr-<n>/` folders behind forever. This PR adds a fifth prune pass over that bucket, reusing the existing closed-for-2-days filter via `processItems`. Since both R2 prunes were near-duplicates, I refactored them into shared `listR2PrPrefixes` / `deleteR2Prefix` helpers parameterized by an `R2BucketRef` ({client, bucket, label}). Adding more buckets in future is one literal each. Net diff is +43/-80. `staging/` and `production/` prefixes are intentionally untouched — they're required by `coalesceWithPreviousAssets` for rollback safety. Also broadens the fly.io app regex from `^pr-\d+-zero-cache$` to `^pr-\d+-zero-(cache|rm|vs)$` so preview `zero-rm` and `zero-vs` apps get torn down alongside `zero-cache`. Without this, the leftover apps kept writing new litestream backups into already-pruned `pr-<n>/` prefixes in the Zero R2 bucket, so the same prefix kept reappearing in nightly runs. Closes tldraw#8745. ### Change type - [x] `other` ### Test plan - Workflow runs daily on cron; will be exercised on the next scheduled run after merge. Reuses the same `R2_ACCESS_KEY_ID` / `R2_ACCESS_KEY_SECRET` secrets the dotcom deploy job already uses for this bucket, so no new secrets needed.
1 parent 1d99b68 commit 1645a2d

2 files changed

Lines changed: 52 additions & 24 deletions

File tree

.github/workflows/prune-preview-deploys.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ jobs:
5454
ZERO_R2_BUCKET_NAME: ${{ secrets.ZERO_R2_BUCKET_NAME }}
5555
ZERO_R2_ACCESS_KEY_ID: ${{ secrets.ZERO_R2_ACCESS_KEY_ID }}
5656
ZERO_R2_SECRET_ACCESS_KEY: ${{ secrets.ZERO_R2_SECRET_ACCESS_KEY }}
57+
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
58+
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
5759

5860
- name: Notify Discord on failure
5961
if: failure()

internal/scripts/prune-preview-deploys.ts

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const env = makeEnv([
1616
'ZERO_R2_BUCKET_NAME',
1717
'ZERO_R2_ACCESS_KEY_ID',
1818
'ZERO_R2_SECRET_ACCESS_KEY',
19+
'R2_ACCESS_KEY_ID',
20+
'R2_ACCESS_KEY_SECRET',
1921
])
2022

2123
interface ListWorkersResult {
@@ -171,7 +173,7 @@ async function listPreviewDatabases() {
171173
}
172174
return preview.map((b) => b.name)
173175
}
174-
const ZERO_CACHE_APP_REGEX = /^pr-\d+-zero-cache$/
176+
const ZERO_CACHE_APP_REGEX = /^pr-\d+-zero-(cache|rm|vs)$/
175177
async function listFlyioPreviewApps() {
176178
// This is the kind of output this returns.
177179
// We'll skip the first line then get the first column of each line.
@@ -189,22 +191,19 @@ async function listFlyioPreviewApps() {
189191
return appNames.filter((name) => ZERO_CACHE_APP_REGEX.test(name))
190192
}
191193

192-
const R2 = new S3Client({
193-
region: 'auto',
194-
endpoint: env.ZERO_R2_ENDPOINT,
195-
credentials: {
196-
accessKeyId: env.ZERO_R2_ACCESS_KEY_ID,
197-
secretAccessKey: env.ZERO_R2_SECRET_ACCESS_KEY,
198-
},
199-
})
194+
interface R2BucketRef {
195+
client: S3Client
196+
bucket: string
197+
label: string
198+
}
200199

201-
async function listR2BackupPrefixes(): Promise<string[]> {
200+
async function listR2PrPrefixes({ client, bucket }: R2BucketRef): Promise<string[]> {
202201
const prefixes: string[] = []
203202
let continuationToken: string | undefined
204203
do {
205-
const res = await R2.send(
204+
const res = await client.send(
206205
new ListObjectsV2Command({
207-
Bucket: env.ZERO_R2_BUCKET_NAME,
206+
Bucket: bucket,
208207
Prefix: 'pr-',
209208
Delimiter: '/',
210209
ContinuationToken: continuationToken,
@@ -218,20 +217,15 @@ async function listR2BackupPrefixes(): Promise<string[]> {
218217
return prefixes
219218
}
220219

221-
async function deleteR2Backup(prefix: string) {
222-
nicelog('Deleting R2 backup data:', prefix)
220+
async function deleteR2Prefix({ client, bucket, label }: R2BucketRef, prefix: string) {
221+
nicelog(`Deleting ${label}:`, prefix)
223222
while (true) {
224-
const list = await R2.send(
225-
new ListObjectsV2Command({
226-
Bucket: env.ZERO_R2_BUCKET_NAME,
227-
Prefix: prefix,
228-
})
229-
)
223+
const list = await client.send(new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix }))
230224
const objects = list.Contents
231225
if (!objects || objects.length === 0) break
232-
const result = await R2.send(
226+
const result = await client.send(
233227
new DeleteObjectsCommand({
234-
Bucket: env.ZERO_R2_BUCKET_NAME,
228+
Bucket: bucket,
235229
Delete: { Objects: objects.map((o) => ({ Key: o.Key })) },
236230
})
237231
)
@@ -243,6 +237,33 @@ async function deleteR2Backup(prefix: string) {
243237
}
244238
}
245239

240+
const zeroBackups: R2BucketRef = {
241+
client: new S3Client({
242+
region: 'auto',
243+
endpoint: env.ZERO_R2_ENDPOINT,
244+
credentials: {
245+
accessKeyId: env.ZERO_R2_ACCESS_KEY_ID,
246+
secretAccessKey: env.ZERO_R2_SECRET_ACCESS_KEY,
247+
},
248+
}),
249+
bucket: env.ZERO_R2_BUCKET_NAME,
250+
label: 'Zero litestream backup',
251+
}
252+
253+
// Matches the bucket / endpoint used by `coalesceWithPreviousAssets` in deploy-dotcom.ts.
254+
const dotcomAssetsCache: R2BucketRef = {
255+
client: new S3Client({
256+
region: 'auto',
257+
endpoint: 'https://c34edc4e76350954b63adebde86d5eb1.r2.cloudflarestorage.com',
258+
credentials: {
259+
accessKeyId: env.R2_ACCESS_KEY_ID,
260+
secretAccessKey: env.R2_ACCESS_KEY_SECRET,
261+
},
262+
}),
263+
bucket: 'dotcom-deploy-assets-cache',
264+
label: 'dotcom deploy assets cache',
265+
}
266+
246267
const deletionErrors: string[] = []
247268

248269
async function main() {
@@ -252,8 +273,13 @@ async function main() {
252273
await processItems(listPreviewDatabases, deletePreviewDatabase)
253274
nicelog('\nPruning fly.io preview apps')
254275
await processItems(listFlyioPreviewApps, deleteFlyioPreviewApp)
255-
nicelog('\nPruning R2 litestream backups')
256-
await processItems(listR2BackupPrefixes, deleteR2Backup)
276+
for (const r2 of [zeroBackups, dotcomAssetsCache]) {
277+
nicelog(`\nPruning ${r2.label}`)
278+
await processItems(
279+
() => listR2PrPrefixes(r2),
280+
(prefix) => deleteR2Prefix(r2, prefix)
281+
)
282+
}
257283
nicelog('\nDone')
258284
if (deletionErrors.length > 0) {
259285
nicelog('\nDeletion errors:')

0 commit comments

Comments
 (0)