Skip to content

g1t/.g1t/workflows/deploy.yml

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