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