Skip to content

g1t/.g1t/workflows/deploy.yml

243 lines9,563 bytesCodeBlame
1# Deploys g1t.sh from main, with g1t's own Actions. What it does is
2# scripts/deploy.mjs, the same tool a person runs; docs/DEPLOYING.md is the
3# guide.
4#
5# check the deploy manifest is consistent, and the tool's tests pass
6# plan what changed since each Worker's live commit, and pending migrations
7# migrate pending D1 migrations, before any code
8# core, edge, front the units of each stage, in jobs that share a build;
9# a stage starts only when the one before it succeeded
10#
11# Each run that deploys is one production deployment of g1t.sh, made by the
12# jobs that name `environment: production` (one per run, however many jobs):
13# in progress when the first starts, then a success or a failure when the
14# run ends. It shows on the project's Deployments page and as the commit's
15# `deploy / production` check. The plan job reads production's secrets
16# with `deployment: false`, so a dry run or a change that deploys nothing
17# makes no deployment.
18#
19# Needs the repository secret CLOUDFLARE_API_TOKEN (a Production row, with
20# Containers write), the variable CLOUDFLARE_ACCOUNT_ID, and
21# api.cloudflare.com among the project's workflow-only domains for
22# deploy.yml in production (Settings, Guardrails), and
23# registry.cloudflare.com there too, to find, pull and push the runner's
24# image. A job that must build that image (the `runner-image` group) does
25# so with its own Docker Engine, on a larger machine. See docs/DEPLOYING.md.
26name: Deploy
27
28on:
29 push:
30 branches: [main]
31 workflow_dispatch:
32 inputs:
33 units:
34 description: "Units to deploy whether or not they changed, comma separated (empty: what changed)"
35 type: string
36 default: ""
37 all:
38 description: "Deploy every unit"
39 type: boolean
40 default: false
41 dry_run:
42 description: "Plan only: deploy nothing"
43 type: boolean
44 default: false
45
46# One deploy at a time, and never one cut off halfway: the next waits.
47concurrency:
48 group: deploy-production
49 cancel-in-progress: false
50
51env:
52 CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
53 CARGO_TERM_COLOR: never
54 WRANGLER_SEND_METRICS: "false"
55
56jobs:
57 check:
58 name: Check
59 runs-on: ubuntu-latest
60 timeout-minutes: 20
61 steps:
62 - uses: actions/checkout@v5
63 - name: Install Wrangler
64 run: npm ci --workspaces=false --no-audit --no-fund
65 - name: The manifest matches every wrangler.jsonc
66 run: node scripts/deploy.mjs manifest --check
67 - name: The deploy tool's tests
68 run: npm run test:deploy
69
70 plan:
71 name: Plan
72 needs: check
73 runs-on: ubuntu-latest
74 # Production's secrets, without a deployment: planning deploys nothing.
75 environment:
76 name: production
77 deployment: false
78 timeout-minutes: 15
79 outputs:
80 migrate: ${{ steps.plan.outputs.migrate }}
81 migrate_units: ${{ steps.plan.outputs.migrate_units }}
82 has_core: ${{ steps.plan.outputs.has_core }}
83 core: ${{ steps.plan.outputs.core }}
84 has_edge: ${{ steps.plan.outputs.has_edge }}
85 edge: ${{ steps.plan.outputs.edge }}
86 has_front: ${{ steps.plan.outputs.has_front }}
87 front: ${{ steps.plan.outputs.front }}
88 steps:
89 - uses: actions/checkout@v5
90 with:
91 # Each Worker's live commit is compared with this one.
92 fetch-depth: 0
93 - name: Install Wrangler
94 run: npm ci --workspaces=false --no-audit --no-fund
95 - name: Plan
96 id: plan
97 env:
98 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
99 UNITS: ${{ inputs.units }}
100 ALL: ${{ inputs.all }}
101 run: |
102 args=()
103 if [ -n "$UNITS" ]; then args+=(--only "$UNITS" --force); fi
104 if [ "$ALL" = "true" ]; then args+=(--all); fi
105 node scripts/deploy.mjs plan "${args[@]}" --github-output
106
107 migrate:
108 name: Migrations
109 needs: plan
110 if: ${{ needs.plan.outputs.migrate == 'true' && inputs.dry_run != true }}
111 runs-on: ubuntu-latest
112 environment:
113 name: production
114 url: https://g1t.sh
115 timeout-minutes: 20
116 steps:
117 - uses: actions/checkout@v5
118 - name: Install Wrangler
119 run: npm ci --workspaces=false --no-audit --no-fund
120 - name: Apply pending migrations
121 env:
122 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
123 run: node scripts/deploy.mjs migrate --only "${{ needs.plan.outputs.migrate_units }}"
124
125 core:
126 name: core (${{ matrix.group }})
127 needs: [plan, migrate]
128 # Runs when nothing before it failed: a migrate job skipped for having
129 # nothing to apply is not a failure.
130 if: ${{ !failure() && !cancelled() && needs.plan.outputs.has_core == 'true' && inputs.dry_run != true }}
131 # Rust builds and the runner's image get 4 vCPUs; everything else the
132 # standard machine.
133 runs-on: ${{ (matrix.rust || matrix.image) && 'g1t-4core' || 'ubuntu-latest' }}
134 environment:
135 name: production
136 url: https://g1t.sh
137 timeout-minutes: 60
138 strategy:
139 # A deploy cut off halfway is worse than one that finishes: the other
140 # jobs of a stage run on when one fails, and the next stage does not.
141 fail-fast: false
142 max-parallel: 4
143 matrix: ${{ fromJSON(needs.plan.outputs.core) }}
144 steps: &deploy
145 - uses: actions/checkout@v5
146 with:
147 fetch-depth: 0
148 # Rust workers: the wasm target, and worker-build kept between runs
149 # (its version is pinned in scripts/build-rust-worker.mjs).
150 - name: Rust for Workers
151 if: ${{ matrix.rust }}
152 run: rustup target add wasm32-unknown-unknown
153 - name: Cache worker-build
154 if: ${{ matrix.rust }}
155 uses: actions/cache@v4
156 with:
157 path: ~/.cargo/bin/worker-build
158 key: worker-build-${{ runner.os }}-${{ hashFiles('scripts/build-rust-worker.mjs') }}
159 - name: Cache worker-build's tools (wasm-bindgen, esbuild)
160 if: ${{ matrix.rust }}
161 uses: actions/cache@v4
162 with:
163 path: ~/.cache/worker-build
164 key: worker-build-tools-${{ runner.os }}-${{ hashFiles('scripts/build-rust-worker.mjs') }}
165 - name: Cache crates
166 if: ${{ matrix.rust }}
167 uses: actions/cache@v4
168 with:
169 path: ~/.cargo/registry/cache
170 key: cargo-crates-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
171 restore-keys: cargo-crates-${{ runner.os }}-
172 # The compiled dependencies of this job's units, for wasm32 and the
173 # build scripts and proc macros they run. The workspace's own crates
174 # are compiled again whatever is cached (a checkout's sources are
175 # newer), so an entry is saved only when the dependencies change: a
176 # new Cargo.lock, or a new base image (base.json names its Rust).
177 # Otherwise the nearest earlier entry, of any group, is a start.
178 - name: Cache the Cargo target
179 if: ${{ matrix.rust }}
180 uses: actions/cache@v4
181 with:
182 path: |
183 target/release
184 target/wasm32-unknown-unknown/release
185 !target/**/incremental
186 !target/**/*.wasm
187 key: cargo-target-${{ runner.os }}-${{ matrix.group }}-${{ hashFiles('Cargo.lock', 'services/runner/base.json') }}
188 restore-keys: |
189 cargo-target-${{ runner.os }}-${{ matrix.group }}-
190 cargo-target-${{ runner.os }}-
191 # The runner's image: its binary, built natively for musl (the base
192 # has musl-gcc; the target is added here), with its Cargo target kept
193 # between runs. The image itself is built and pushed with the job's
194 # own Docker Engine (scripts/deploy/image.mjs).
195 - name: Rust for the runner
196 if: ${{ matrix.image }}
197 run: rustup target add x86_64-unknown-linux-musl
198 - name: Cache the runner's build
199 if: ${{ matrix.image }}
200 uses: actions/cache@v4
201 with:
202 path: |
203 ~/.cargo/registry/cache
204 target/x86_64-unknown-linux-musl/release
205 !target/**/incremental
206 key: runner-musl-${{ runner.os }}-${{ hashFiles('Cargo.lock', 'services/runner/base.json') }}
207 restore-keys: runner-musl-${{ runner.os }}-
208 - name: Install
209 run: node scripts/deploy.mjs install --only "${{ matrix.units }}"
210 - name: Deploy ${{ matrix.units }}
211 env:
212 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
213 run: node scripts/deploy.mjs deploy --only "${{ matrix.units }}" --force --no-migrations --concurrency 2
214
215 edge:
216 name: edge (${{ matrix.group }})
217 needs: [plan, migrate, core]
218 if: ${{ !failure() && !cancelled() && needs.plan.outputs.has_edge == 'true' && inputs.dry_run != true }}
219 runs-on: ${{ (matrix.rust || matrix.image) && 'g1t-4core' || 'ubuntu-latest' }}
220 environment:
221 name: production
222 url: https://g1t.sh
223 timeout-minutes: 60
224 strategy:
225 fail-fast: false
226 max-parallel: 4
227 matrix: ${{ fromJSON(needs.plan.outputs.edge) }}
228 steps: *deploy
229
230 front:
231 name: front (${{ matrix.group }})
232 needs: [plan, migrate, core, edge]
233 if: ${{ !failure() && !cancelled() && needs.plan.outputs.has_front == 'true' && inputs.dry_run != true }}
234 runs-on: ${{ (matrix.rust || matrix.image) && 'g1t-4core' || 'ubuntu-latest' }}
235 environment:
236 name: production
237 url: https://g1t.sh
238 timeout-minutes: 60
239 strategy:
240 fail-fast: false
241 max-parallel: 4
242 matrix: ${{ fromJSON(needs.plan.outputs.front) }}
243 steps: *deploy