Skip to content

Commit a2ff52b

Browse files
authored
Merge pull request #725 from SprocketBot/cursor/ci-parameterized-pulumi-deploy-682-3e0a
ci: parameterized Pulumi deploy from stack-map (#682)
2 parents abbcbeb + b0db92f commit a2ff52b

9 files changed

Lines changed: 593 additions & 353 deletions
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# Reusable Pulumi sequences driven by infra/ci/stack-map.yaml (#682).
2+
#
3+
# Callers set deploy_profile + target + git SHA. Stack order and per-step commands are emitted by
4+
# scripts/ci/read-stack-map.mjs (parses stack-map.yaml; no secrets).
5+
#
6+
# Secrets: pass `secrets: inherit` from the caller. Credential *names* differ for production
7+
# (PROD_*) vs dev/staging/preview (shared pre-prod names). New lanes: add a caller workflow that
8+
# maps lane-specific environment secrets explicitly — GitHub Actions cannot index secrets dynamically.
9+
10+
on:
11+
workflow_call:
12+
inputs:
13+
lane:
14+
description: 'Logical lane (e.g. main, v15); for documentation and future env JSON routing'
15+
required: false
16+
type: string
17+
default: main
18+
target:
19+
description: 'Stack-map key: dev | staging | prod (used by preview_all plan)'
20+
required: false
21+
type: string
22+
default: dev
23+
git_sha:
24+
description: 'Commit SHA to check out'
25+
required: true
26+
type: string
27+
deploy_profile:
28+
description: 'dev_cd | staging_up | prod_cd | preview_all'
29+
required: true
30+
type: string
31+
bom_download_run_id:
32+
description: 'If set, download artifact bom-main from this run (dev CD after Autobuild)'
33+
required: false
34+
type: string
35+
default: ''
36+
dev_platform_stack:
37+
description: 'Platform stack name for dev_cd (must not be prod). Required when deploy_profile is dev_cd.'
38+
required: false
39+
type: string
40+
default: ''
41+
promote_bom_file:
42+
description: 'For staging_up: optional repo-relative BOM path (see scripts/ci/promote-images.sh)'
43+
required: false
44+
type: string
45+
default: ''
46+
promote_image_tag:
47+
description: 'For staging_up: optional explicit image tag (skips BOM / sha derivation)'
48+
required: false
49+
type: string
50+
default: ''
51+
staging_include_promote_step:
52+
description: 'If true (staging_up only), run promote-images.sh before Pulumi ups'
53+
required: false
54+
type: boolean
55+
default: false
56+
prod_confirm:
57+
description: 'Typo guard for prod_cd: empty ok; else must equal repo full name or DEPLOY_PROD'
58+
required: false
59+
type: string
60+
default: ''
61+
62+
jobs:
63+
dev_prepare:
64+
name: Dev — BOM + platform image tag
65+
if: inputs.deploy_profile == 'dev_cd'
66+
runs-on: ubuntu-latest
67+
environment: development
68+
steps:
69+
- name: Check out
70+
uses: actions/checkout@v6
71+
with:
72+
ref: ${{ inputs.git_sha }}
73+
74+
- name: Download merged BOM (optional)
75+
if: inputs.bom_download_run_id != ''
76+
uses: actions/download-artifact@v4
77+
with:
78+
name: bom-main
79+
path: bom-download
80+
github-token: ${{ secrets.GITHUB_TOKEN }}
81+
repository: ${{ github.repository }}
82+
run-id: ${{ inputs.bom_download_run_id }}
83+
84+
- name: Set up Node.js (Pulumi CLI for platform config)
85+
uses: actions/setup-node@v6
86+
with:
87+
node-version: '20'
88+
89+
- name: Install Pulumi CLI
90+
shell: bash
91+
run: |
92+
curl -fsSL https://get.pulumi.com | sh -s -- --version "3.229.0"
93+
echo "${HOME}/.pulumi/bin" >> "${GITHUB_PATH}"
94+
95+
- name: Set dev platform image tag (sha-* from BOM when present)
96+
shell: bash
97+
env:
98+
PULUMI_BACKEND_URL: ${{ secrets.PULUMI_BACKEND_URL }}
99+
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
100+
DEPLOY_SHA: ${{ inputs.git_sha }}
101+
PULUMI_DEV_PLATFORM_STACK: ${{ inputs.dev_platform_stack }}
102+
run: |
103+
set -euo pipefail
104+
if [ -z "${PULUMI_DEV_PLATFORM_STACK}" ]; then
105+
echo "::error::dev_platform_stack is required for deploy_profile dev_cd"
106+
exit 1
107+
fi
108+
if [ "${PULUMI_DEV_PLATFORM_STACK}" = "prod" ]; then
109+
echo "::error::Refusing to set image tag on platform stack named prod."
110+
exit 1
111+
fi
112+
if [ -n "${PULUMI_BACKEND_URL:-}" ]; then
113+
pulumi login "${PULUMI_BACKEND_URL}"
114+
fi
115+
BOM_PATH=""
116+
if [ -d bom-download ]; then
117+
BOM_PATH="$(find bom-download -type f -name 'main-*.json' | head -1 || true)"
118+
fi
119+
bash ./scripts/ci/pulumi-set-dev-image-tag.sh "${DEPLOY_SHA}" "${BOM_PATH}"
120+
121+
matrix_prepare:
122+
name: Build deploy matrix from stack-map
123+
runs-on: ubuntu-latest
124+
outputs:
125+
matrix_json: ${{ steps.map.outputs.matrix_json }}
126+
steps:
127+
- name: Validate prod confirm (typo guard)
128+
if: inputs.deploy_profile == 'prod_cd'
129+
shell: bash
130+
env:
131+
CONFIRM: ${{ inputs.prod_confirm }}
132+
REPO: ${{ github.repository }}
133+
run: |
134+
if [ -n "${CONFIRM}" ] && [ "${CONFIRM}" != "${REPO}" ] && [ "${CONFIRM}" != "DEPLOY_PROD" ]; then
135+
echo "::error::When set, confirm must equal ${REPO} or DEPLOY_PROD (got: ${CONFIRM})"
136+
exit 1
137+
fi
138+
139+
- name: Check out
140+
uses: actions/checkout@v6
141+
with:
142+
ref: ${{ inputs.git_sha }}
143+
144+
- name: Set up Node.js
145+
uses: actions/setup-node@v6
146+
with:
147+
node-version-file: .nvmrc
148+
cache: npm
149+
150+
- name: Install npm dependencies (yaml parser for read-stack-map)
151+
run: npm ci
152+
153+
- name: Emit matrix JSON
154+
id: map
155+
shell: bash
156+
env:
157+
PROFILE: ${{ inputs.deploy_profile }}
158+
TARGET: ${{ inputs.target }}
159+
PLAT: ${{ inputs.dev_platform_stack }}
160+
STAGING_PROMOTE: ${{ inputs.staging_include_promote_step }}
161+
run: |
162+
set -euo pipefail
163+
case "${PROFILE}" in
164+
dev_cd)
165+
if [ -z "${PLAT}" ]; then
166+
echo "::error::dev_platform_stack required for dev_cd"
167+
exit 1
168+
fi
169+
node scripts/ci/read-stack-map.mjs emit --plan dev_cd_full --platform-stack "${PLAT}"
170+
;;
171+
staging_up)
172+
if [ "${STAGING_PROMOTE}" = "true" ]; then
173+
node scripts/ci/read-stack-map.mjs emit --plan staging_up --prepend-staging-promote
174+
else
175+
node scripts/ci/read-stack-map.mjs emit --plan staging_up
176+
fi
177+
;;
178+
prod_cd)
179+
node scripts/ci/read-stack-map.mjs emit --plan prod_cd
180+
;;
181+
preview_all)
182+
case "${TARGET}" in
183+
dev|staging|prod) ;;
184+
*)
185+
echo "::error::target must be dev, staging, or prod for preview_all (got: ${TARGET})"
186+
exit 1
187+
;;
188+
esac
189+
node scripts/ci/read-stack-map.mjs emit --plan dev_preview_all --target "${TARGET}"
190+
;;
191+
*)
192+
echo "::error::Unknown deploy_profile: ${PROFILE}"
193+
exit 1
194+
;;
195+
esac
196+
197+
deploy:
198+
name: Pulumi ${{ matrix.command }} — ${{ matrix.stack_project }} / ${{ matrix.stack_name }}
199+
needs:
200+
- matrix_prepare
201+
- dev_prepare
202+
if: |
203+
always() &&
204+
needs.matrix_prepare.result == 'success' &&
205+
(
206+
inputs.deploy_profile != 'dev_cd' ||
207+
needs.dev_prepare.result == 'success'
208+
)
209+
runs-on: ubuntu-latest
210+
environment: >-
211+
${{
212+
inputs.deploy_profile == 'prod_cd' && 'production' ||
213+
inputs.deploy_profile == 'staging_up' && 'staging' ||
214+
inputs.deploy_profile == 'preview_all' && 'preview' ||
215+
'development'
216+
}}
217+
strategy:
218+
fail-fast: true
219+
max-parallel: 1
220+
matrix:
221+
include: ${{ fromJSON(needs.matrix_prepare.outputs.matrix_json) }}
222+
steps:
223+
- name: Check out
224+
uses: actions/checkout@v6
225+
with:
226+
ref: ${{ inputs.git_sha }}
227+
228+
- name: Staging — set platform image tag (promote)
229+
if: inputs.deploy_profile == 'staging_up' && matrix.command == 'staging_promote'
230+
shell: bash
231+
env:
232+
PULUMI_BACKEND_URL: ${{ secrets.PULUMI_BACKEND_URL }}
233+
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
234+
PROMOTE_GIT_SHA: ${{ inputs.git_sha }}
235+
PROMOTE_BOM_FILE: ${{ inputs.promote_bom_file }}
236+
PROMOTE_IMAGE_TAG: ${{ inputs.promote_image_tag }}
237+
run: |
238+
set -euo pipefail
239+
curl -fsSL https://get.pulumi.com | sh -s -- --version "3.229.0"
240+
echo "${HOME}/.pulumi/bin" >> "${GITHUB_PATH}"
241+
args=(./scripts/ci/promote-images.sh pulumi-set-platform-tag)
242+
if [[ -n "${PROMOTE_BOM_FILE}" ]]; then
243+
args+=(--bom-file "${PROMOTE_BOM_FILE}")
244+
fi
245+
if [[ -n "${PROMOTE_IMAGE_TAG}" ]]; then
246+
args+=(--image-tag "${PROMOTE_IMAGE_TAG}")
247+
fi
248+
args+=("${PROMOTE_GIT_SHA}" "staging")
249+
"${args[@]}"
250+
251+
- name: Pulumi (dev / preview — with Pulumi Cloud token)
252+
if: (inputs.deploy_profile == 'dev_cd' || inputs.deploy_profile == 'preview_all') && matrix.command != 'staging_promote'
253+
uses: ./.github/reusable_workflows/pulumi_up
254+
env:
255+
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
256+
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
257+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
258+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
259+
AWS_REGION: nyc3
260+
with:
261+
command: ${{ matrix.command }}
262+
stack-project: ${{ matrix.stack_project }}
263+
stack-name: ${{ matrix.stack_name }}
264+
manager-node-host: ${{ secrets.PULUMI_MANAGER_NODE_HOST }}
265+
ssh-user: ${{ secrets.PULUMI_SSH_USER }}
266+
ssh-key: ${{ secrets.PULUMI_SSH_KEY }}
267+
tailscale-authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
268+
pulumi-backend-url: ${{ secrets.PULUMI_BACKEND_URL }}
269+
270+
- name: Pulumi (staging promote — no Pulumi Cloud token)
271+
if: inputs.deploy_profile == 'staging_up' && matrix.command != 'staging_promote'
272+
uses: ./.github/reusable_workflows/pulumi_up
273+
env:
274+
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
275+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
276+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
277+
AWS_REGION: nyc3
278+
with:
279+
command: ${{ matrix.command }}
280+
stack-project: ${{ matrix.stack_project }}
281+
stack-name: ${{ matrix.stack_name }}
282+
manager-node-host: ${{ secrets.PULUMI_MANAGER_NODE_HOST }}
283+
ssh-user: ${{ secrets.PULUMI_SSH_USER }}
284+
ssh-key: ${{ secrets.PULUMI_SSH_KEY }}
285+
tailscale-authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
286+
pulumi-backend-url: ${{ secrets.PULUMI_BACKEND_URL }}
287+
288+
- name: Pulumi (production credentials)
289+
if: inputs.deploy_profile == 'prod_cd'
290+
uses: ./.github/reusable_workflows/pulumi_up
291+
env:
292+
PULUMI_ACCESS_TOKEN: ${{ secrets.PROD_PULUMI_ACCESS_TOKEN }}
293+
with:
294+
command: ${{ matrix.command }}
295+
stack-project: ${{ matrix.stack_project }}
296+
stack-name: ${{ matrix.stack_name }}
297+
manager-node-host: ${{ secrets.PROD_MANAGER_HOST }}
298+
ssh-user: ${{ secrets.PROD_SSH_USER }}
299+
ssh-key: ${{ secrets.PROD_SSH_KEY }}
300+
tailscale-authkey: ${{ secrets.PROD_TAILSCALE_AUTHKEY }}

0 commit comments

Comments
 (0)