Skip to content
Source
Sign inSign up

relay-preview-cleanup.yml

203 lines8.5 KB

1name: Clean up relay preview
2​
3# Tears down stage `pr-<n>` when `relay:preview` is removed or the PR closes,
4# and retries teardown for closed labeled PRs on a schedule. A label stays on a
5# PR only while its teardown is outstanding, so the scheduled retry lists
6# exactly the failures.
7#
8# This runs under pull_request_target, so the workflow and the Alchemy program
9# come from the default branch. No PR code runs here, which is why it needs no
10# reviewer approval and why a PR cannot edit its own teardown to reach secrets.
11on:
12 pull_request_target:
13 types: [unlabeled, closed]
14 schedule:
15 - cron: "17 7 * * *"
16 workflow_dispatch:
17​
18permissions:
19 contents: read
20 issues: read
21 pull-requests: read
22​
23jobs:
24 cleanup_pr:
25 name: Tear down relay preview
26 if: >-
27 github.ref == 'refs/heads/main' &&
28 github.event_name == 'pull_request_target' &&
29 (
30 (github.event.action == 'unlabeled' && github.event.label.name == 'relay:preview') ||
31 (github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'relay:preview'))
32 )
33 # Job-level so that events which skip this job, such as a closed PR without
34 # the label, never take the group's pending slot. GitHub replaces a pending
35 # run in a group with a newer one, which would drop a queued teardown.
36 concurrency:
37 group: relay-preview-${{ github.event.pull_request.number }}
38 cancel-in-progress: false
39 permissions:
40 contents: read
41 issues: write
42 pull-requests: write
43 runs-on: blacksmith-8vcpu-ubuntu-2404
44 timeout-minutes: 30
45 environment:
46 name: relay-preview-cleanup
47 env:
48 GH_TOKEN: ${{ github.token }}
49 STAGE: pr-${{ github.event.pull_request.number }}
50 CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
51 PLANETSCALE_ORGANIZATION: ${{ vars.PLANETSCALE_ORGANIZATION }}
52 AXIOM_ORG_ID: ${{ vars.AXIOM_ORG_ID }}
53 RELAY_API_ZONE_NAME: ${{ vars.RELAY_API_ZONE_NAME }}
54 RELAY_TUNNEL_ZONE_NAME: ${{ vars.RELAY_TUNNEL_ZONE_NAME }}
55 CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }}
56 CLERK_JWT_AUDIENCE: ${{ vars.CLERK_JWT_AUDIENCE }}
57 APNS_ENABLED: "false"
58 ALCHEMY_TELEMETRY_DISABLED: "1"
59 steps:
60 - name: Checkout default branch
61 uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
62 with:
63 ref: ${{ github.event.repository.default_branch }}
64 sparse-checkout: |
65 /*
66 !/.repos/
67 sparse-checkout-cone-mode: false
68​
69 - name: Setup Vite+
70 uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0
71 with:
72 node-version-file: package.json
73 cache: true
74 run-install: |
75 args:
76 - --filter=t3code-relay...
77​
78 # Destroy also runs the preview tunnel sweep, which is part of the stack.
79 - name: Destroy preview stage
80 run: vp run --filter t3code-relay destroy --stage "$STAGE" --yes --no-input
81 env:
82 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
83 PLANETSCALE_API_TOKEN_ID: ${{ secrets.PLANETSCALE_API_TOKEN_ID }}
84 PLANETSCALE_API_TOKEN: ${{ secrets.PLANETSCALE_API_TOKEN }}
85 AXIOM_TOKEN: ${{ secrets.AXIOM_TOKEN }}
86 CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
87​
88 # Only reached after a successful destroy. A failed one keeps the label,
89 # so the scheduled retry picks the PR up again.
90 - name: Clear the teardown marker
91 if: github.event.action == 'closed'
92 run: gh pr edit "${{ github.event.pull_request.number }}" --repo "$GITHUB_REPOSITORY" --remove-label relay:preview
93​
94 # An unlabeled event has already removed the label, so a failed teardown
95 # must put it back or no retry will ever see the stage. Labels added with
96 # GITHUB_TOKEN do not start new runs, so this does not redeploy.
97 - name: Restore the teardown marker after a failed destroy
98 if: failure() && github.event.action == 'unlabeled'
99 run: gh pr edit "${{ github.event.pull_request.number }}" --repo "$GITHUB_REPOSITORY" --add-label relay:preview
100​
101 list_closed_previews:
102 name: List closed relay previews
103 if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request_target'
104 runs-on: blacksmith-8vcpu-ubuntu-2404
105 timeout-minutes: 10
106 outputs:
107 numbers: ${{ steps.list.outputs.numbers }}
108 steps:
109 # The issues endpoint is paginated by gh, so there is no listing cap. Pull
110 # requests appear there with a `pull_request` key.
111 - name: List closed PRs labeled relay:preview
112 id: list
113 env:
114 GH_TOKEN: ${{ github.token }}
115 run: |
116 numbers=$(gh api --paginate --method GET "repos/$GITHUB_REPOSITORY/issues" \
117 -f labels=relay:preview -f state=closed -f per_page=100 \
118 --jq '.[] | select(.pull_request) | .number') \
119 || { echo "Could not list closed relay:preview PRs" >&2; exit 1; }
120 json=$(printf '%s\n' "$numbers" | jq -Rsc 'split("\n") | map(select(length > 0) | tonumber)')
121 echo "Closed relay:preview PRs: $json"
122 echo "numbers=$json" >> "$GITHUB_OUTPUT"
123​
124 sweep_pr:
125 name: Retry teardown for PR ${{ matrix.number }}
126 needs: list_closed_previews
127 if: github.ref == 'refs/heads/main' && needs.list_closed_previews.outputs.numbers != '[]'
128 runs-on: blacksmith-8vcpu-ubuntu-2404
129 timeout-minutes: 30
130 strategy:
131 fail-fast: false
132 max-parallel: 4
133 matrix:
134 number: ${{ fromJSON(needs.list_closed_previews.outputs.numbers) }}
135 # Same group as the deploy and teardown for this PR, so a reopened PR's
136 # deploy cannot run concurrently with a teardown of its previous stage.
137 concurrency:
138 group: relay-preview-${{ matrix.number }}
139 cancel-in-progress: false
140 permissions:
141 contents: read
142 issues: write
143 pull-requests: write
144 environment:
145 name: relay-preview-cleanup
146 env:
147 GH_TOKEN: ${{ github.token }}
148 STAGE: pr-${{ matrix.number }}
149 CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
150 PLANETSCALE_ORGANIZATION: ${{ vars.PLANETSCALE_ORGANIZATION }}
151 AXIOM_ORG_ID: ${{ vars.AXIOM_ORG_ID }}
152 RELAY_API_ZONE_NAME: ${{ vars.RELAY_API_ZONE_NAME }}
153 RELAY_TUNNEL_ZONE_NAME: ${{ vars.RELAY_TUNNEL_ZONE_NAME }}
154 CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }}
155 CLERK_JWT_AUDIENCE: ${{ vars.CLERK_JWT_AUDIENCE }}
156 APNS_ENABLED: "false"
157 ALCHEMY_TELEMETRY_DISABLED: "1"
158 steps:
159 # The lock above is taken before this runs, so the state read here is
160 # current. An open PR, including one reopened since the listing, is left
161 # alone. GitHub reports merged PRs as MERGED, so anything not OPEN counts.
162 - name: Confirm the PR is not open
163 id: eligible
164 run: |
165 state=$(gh pr view "${{ matrix.number }}" --repo "$GITHUB_REPOSITORY" \
166 --json state --jq 'if .state == "OPEN" then "no" else "yes" end') \
167 || { echo "Could not read PR ${{ matrix.number }}" >&2; exit 1; }
168 echo "eligible=$state" >> "$GITHUB_OUTPUT"
169 [ "$state" = yes ] || echo "PR ${{ matrix.number }} is open again; leaving $STAGE alone."
170​
171 - name: Checkout default branch
172 if: steps.eligible.outputs.eligible == 'yes'
173 uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
174 with:
175 ref: ${{ github.event.repository.default_branch }}
176 sparse-checkout: |
177 /*
178 !/.repos/
179 sparse-checkout-cone-mode: false
180​
181 - name: Setup Vite+
182 if: steps.eligible.outputs.eligible == 'yes'
183 uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0
184 with:
185 node-version-file: package.json
186 cache: true
187 run-install: |
188 args:
189 - --filter=t3code-relay...
190​
191 - name: Destroy preview stage
192 if: steps.eligible.outputs.eligible == 'yes'
193 run: vp run --filter t3code-relay destroy --stage "$STAGE" --yes --no-input
194 env:
195 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
196 PLANETSCALE_API_TOKEN_ID: ${{ secrets.PLANETSCALE_API_TOKEN_ID }}
197 PLANETSCALE_API_TOKEN: ${{ secrets.PLANETSCALE_API_TOKEN }}
198 AXIOM_TOKEN: ${{ secrets.AXIOM_TOKEN }}
199 CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
200​
201 - name: Clear the teardown marker
202 if: steps.eligible.outputs.eligible == 'yes'
203 run: gh pr edit "${{ matrix.number }}" --repo "$GITHUB_REPOSITORY" --remove-label relay:preview