cpflow 5.0.0.rc.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/{lib/github_flow_templates/.github → .github}/actions/cpflow-delete-control-plane-app/action.yml +5 -0
  3. data/{lib/github_flow_templates/.github → .github}/actions/cpflow-detect-release-phase/action.yml +7 -0
  4. data/.github/actions/cpflow-setup-environment/action.yml +161 -0
  5. data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +69 -0
  6. data/.github/workflows/cpflow-delete-review-app.yml +182 -0
  7. data/.github/workflows/cpflow-deploy-review-app.yml +507 -0
  8. data/.github/workflows/cpflow-deploy-staging.yml +168 -0
  9. data/.github/workflows/cpflow-help-command.yml +78 -0
  10. data/.github/workflows/cpflow-promote-staging-to-production.yml +510 -0
  11. data/.github/workflows/cpflow-review-app-help.yml +51 -0
  12. data/.github/workflows/rspec-shared.yml +3 -0
  13. data/.github/workflows/trigger-docs-site.yml +90 -0
  14. data/.rubocop.yml +14 -1
  15. data/CHANGELOG.md +43 -1
  16. data/CONTRIBUTING.md +27 -0
  17. data/Gemfile.lock +2 -2
  18. data/README.md +7 -3
  19. data/cpflow.gemspec +1 -1
  20. data/docs/ai-github-flow-prompt.md +1 -1
  21. data/docs/assets/cpflow-deploying.svg +46 -0
  22. data/docs/ci-automation.md +111 -8
  23. data/docs/commands.md +11 -5
  24. data/docs/thruster.md +149 -0
  25. data/docs/troubleshooting.md +8 -0
  26. data/lib/command/apply_template.rb +6 -2
  27. data/lib/command/base.rb +1 -0
  28. data/lib/command/cleanup_stale_apps.rb +53 -14
  29. data/lib/command/delete.rb +3 -1
  30. data/lib/command/deploy_image.rb +5 -2
  31. data/lib/command/generate.rb +7 -3
  32. data/lib/command/generate_github_actions.rb +21 -9
  33. data/lib/command/generator_helpers.rb +5 -1
  34. data/lib/command/info.rb +3 -1
  35. data/lib/command/run.rb +16 -1
  36. data/lib/command/test.rb +1 -3
  37. data/lib/core/controlplane.rb +17 -6
  38. data/lib/core/controlplane_api.rb +3 -1
  39. data/lib/core/controlplane_api_direct.rb +50 -27
  40. data/lib/core/doctor_service.rb +2 -2
  41. data/lib/core/github_flow_readiness_service.rb +26 -2
  42. data/lib/core/repo_introspection.rb +41 -3
  43. data/lib/core/shell.rb +3 -1
  44. data/lib/core/terraform_config/policy.rb +1 -1
  45. data/lib/cpflow/version.rb +1 -1
  46. data/lib/cpflow.rb +27 -13
  47. data/lib/generator_templates/templates/rails.yml +4 -0
  48. data/lib/generator_templates_sqlite/templates/rails.yml +4 -0
  49. data/lib/github_flow_templates/.github/cpflow-help.md +30 -1
  50. data/lib/github_flow_templates/.github/workflows/cpflow-cleanup-stale-review-apps.yml +10 -44
  51. data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +15 -114
  52. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +10 -413
  53. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +12 -123
  54. data/lib/github_flow_templates/.github/workflows/cpflow-help-command.yml +10 -33
  55. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +13 -475
  56. data/lib/github_flow_templates/.github/workflows/cpflow-review-app-help.yml +12 -30
  57. data/lib/github_flow_templates/bin/pin-cpflow-github-ref +72 -0
  58. data/lib/github_flow_templates/bin/test-cpflow-github-flow +89 -0
  59. data/rakelib/create_release.rake +4 -4
  60. metadata +26 -17
  61. data/lib/github_flow_templates/.github/actions/cpflow-setup-environment/action.yml +0 -98
  62. /data/{lib/github_flow_templates/.github → .github}/actions/cpflow-build-docker-image/action.yml +0 -0
  63. /data/{lib/github_flow_templates/.github → .github}/actions/cpflow-delete-control-plane-app/delete-app.sh +0 -0
  64. /data/{lib/github_flow_templates/.github → .github}/actions/cpflow-validate-config/action.yml +0 -0
  65. /data/{lib/github_flow_templates/.github → .github}/actions/cpflow-wait-for-health/action.yml +0 -0
@@ -20,25 +20,8 @@ permissions:
20
20
  issues: write
21
21
  pull-requests: write
22
22
 
23
- concurrency:
24
- group: cpflow-review-app-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
25
- # Match the delete workflow: a cancelled `cpflow deploy-image` mid-rollout can leave the
26
- # review app in a partially-deployed state (workload update in progress, rollout not
27
- # settled). Let an in-flight deploy finish before the next push starts a new run.
28
- cancel-in-progress: false
29
-
30
- env:
31
- APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
32
- CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }}
33
- PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
34
- PRIMARY_WORKLOAD: ${{ vars.PRIMARY_WORKLOAD }}
35
-
36
23
  jobs:
37
24
  deploy:
38
- # Skip synchronize/opened events from fork PRs at the job level — they cannot access
39
- # repository secrets anyway, so running any steps just burns billable minutes. Users
40
- # can still manually deploy a fork PR via `+review-app-deploy` (gated below by
41
- # author_association) or workflow_dispatch.
42
25
  if: |
43
26
  (github.event_name == 'pull_request' &&
44
27
  github.event.pull_request.head.repo.full_name == github.repository) ||
@@ -47,399 +30,13 @@ jobs:
47
30
  github.event.issue.pull_request &&
48
31
  contains(fromJson('["+review-app-deploy","+review-app-deploy\n","+review-app-deploy\r\n"]'), github.event.comment.body) &&
49
32
  contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))
50
- runs-on: ubuntu-latest
51
- timeout-minutes: 45
52
-
53
- steps:
54
- - name: Checkout trusted workflow sources
55
- uses: actions/checkout@v4
56
- with:
57
- # Keep generated composite actions on the trusted base branch. The PR
58
- # application code is checked out separately under ./app after source
59
- # validation so same-repo PRs cannot replace local actions before
60
- # staging secrets are passed to them.
61
- ref: ${{ github.event.repository.default_branch }}
62
- persist-credentials: false
63
-
64
- - name: Validate required secrets and variables
65
- id: config
66
- uses: ./.github/actions/cpflow-validate-config
67
- env:
68
- CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
69
- CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
70
- REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
71
- with:
72
- required: |
73
- secret:CPLN_TOKEN_STAGING
74
- variable:CPLN_ORG_STAGING
75
- variable:REVIEW_APP_PREFIX
76
- pull_request_friendly: "true"
77
-
78
- - name: Resolve PR ref and commit
79
- if: steps.config.outputs.ready == 'true'
80
- id: resolve-pr
81
- env:
82
- # Route every GitHub-controlled input through env so the run script never
83
- # interpolates ${{ ... }} into shell. All values here are GitHub-controlled
84
- # (not user-influenced), so this is for consistency with the rest of the
85
- # workflow and to quiet actionlint/StepSecurity, not a fix for an
86
- # exploitable injection.
87
- EVENT_NAME: ${{ github.event_name }}
88
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89
- DISPATCH_PR_NUMBER: ${{ github.event.inputs.pr_number }}
90
- ISSUE_NUMBER: ${{ github.event.issue.number }}
91
- PR_EVENT_NUMBER: ${{ github.event.pull_request.number }}
92
- REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
93
- shell: bash
94
- run: |
95
- set -euo pipefail
96
-
97
- case "${EVENT_NAME}" in
98
- workflow_dispatch)
99
- pr_number="${DISPATCH_PR_NUMBER}"
100
- ;;
101
- issue_comment)
102
- pr_number="${ISSUE_NUMBER}"
103
- ;;
104
- pull_request)
105
- pr_number="${PR_EVENT_NUMBER}"
106
- ;;
107
- *)
108
- echo "Unsupported event type: ${EVENT_NAME}" >&2
109
- exit 1
110
- ;;
111
- esac
112
-
113
- pr_data="$(gh pr view "$pr_number" --json headRefOid,headRepository,headRepositoryOwner)"
114
- pr_sha="$(echo "$pr_data" | jq -r '.headRefOid')"
115
- pr_repository="$(echo "$pr_data" | jq -r '[.headRepositoryOwner.login, .headRepository.name] | join("/")')"
116
- same_repo="false"
117
-
118
- if [[ "$pr_repository" == "$GITHUB_REPOSITORY" ]]; then
119
- same_repo="true"
120
- fi
121
-
122
- echo "PR_NUMBER=$pr_number" >> "$GITHUB_ENV"
123
- echo "APP_NAME=${REVIEW_APP_PREFIX}-$pr_number" >> "$GITHUB_ENV"
124
- echo "PR_SHA=$pr_sha" >> "$GITHUB_ENV"
125
- echo "same_repo=${same_repo}" >> "$GITHUB_OUTPUT"
126
-
127
- - name: Validate review app deployment source
128
- if: steps.config.outputs.ready == 'true'
129
- id: source
130
- env:
131
- EVENT_NAME: ${{ github.event_name }}
132
- # Same env-routing pattern as Resolve PR ref and commit above: keep all
133
- # ${{ ... }} values out of the run script.
134
- SAME_REPO: ${{ steps.resolve-pr.outputs.same_repo }}
135
- shell: bash
136
- run: |
137
- set -euo pipefail
138
-
139
- if [[ "${SAME_REPO}" == "true" ]]; then
140
- echo "allowed=true" >> "$GITHUB_OUTPUT"
141
- exit 0
142
- fi
143
-
144
- if [[ "${EVENT_NAME}" == "pull_request" ]]; then
145
- echo "allowed=false" >> "$GITHUB_OUTPUT"
146
- {
147
- echo "Review app deploys are skipped for fork pull requests."
148
- echo "This workflow builds Docker images with repository secrets, so review app deploys only run for branches in the base repository."
149
- } >> "$GITHUB_STEP_SUMMARY"
150
- exit 0
151
- fi
152
-
153
- if [[ "${EVENT_NAME}" == "issue_comment" ]]; then
154
- echo "allowed=false" >> "$GITHUB_OUTPUT"
155
- {
156
- echo "Review app deploys from fork pull requests require a branch in ${GITHUB_REPOSITORY}."
157
- echo "This workflow builds Docker images with repository secrets, so comment-triggered deploys only run for branches in the base repository."
158
- } >> "$GITHUB_STEP_SUMMARY"
159
- exit 0
160
- fi
161
-
162
- echo "Review app deploys from fork pull requests are not allowed for workflow_dispatch because this workflow uses repository secrets." >&2
163
- exit 1
164
-
165
- - name: Checkout PR commit
166
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true'
167
- uses: actions/checkout@v4
168
- with:
169
- ref: ${{ env.PR_SHA }}
170
- path: app
171
- persist-credentials: false
172
-
173
- - name: Remove PR checkout Git metadata
174
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true'
175
- shell: bash
176
- run: |
177
- set -euo pipefail
178
- rm -rf app/.git
179
-
180
- - name: Setup environment
181
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true'
182
- uses: ./.github/actions/cpflow-setup-environment
183
- with:
184
- token: ${{ secrets.CPLN_TOKEN_STAGING }}
185
- org: ${{ vars.CPLN_ORG_STAGING }}
186
- cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
187
- cpflow_version: ${{ vars.CPFLOW_VERSION }}
188
-
189
- - name: Detect release phase support
190
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true'
191
- id: release-phase
192
- uses: ./.github/actions/cpflow-detect-release-phase
193
- with:
194
- app_name: ${{ env.APP_NAME }}
195
- working_directory: app
196
-
197
- - name: Check if review app exists
198
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true'
199
- id: check-app
200
- working-directory: app
201
- shell: bash
202
- run: |
203
- set -euo pipefail
204
-
205
- exists_output=""
206
- set +e
207
- exists_output="$(cpflow exists -a "${APP_NAME}" --org "${CPLN_ORG}" 2>&1)"
208
- exists_status=$?
209
- set -e
210
-
211
- case "${exists_status}" in
212
- 0)
213
- if [[ -n "${exists_output}" ]]; then
214
- printf '%s\n' "${exists_output}"
215
- fi
216
- echo "exists=true" >> "$GITHUB_OUTPUT"
217
- ;;
218
- 3)
219
- if [[ -n "${exists_output}" ]]; then
220
- printf '%s\n' "${exists_output}"
221
- fi
222
- echo "exists=false" >> "$GITHUB_OUTPUT"
223
- ;;
224
- *)
225
- echo "::error::cpflow exists returned unexpected exit code ${exists_status} for ${APP_NAME}" >&2
226
- if [[ -n "${exists_output}" ]]; then
227
- printf '%s\n' "${exists_output}" >&2
228
- fi
229
- exit "${exists_status}"
230
- ;;
231
- esac
232
-
233
- - name: Skip auto deploy until a review app is created
234
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && steps.check-app.outputs.exists != 'true' && github.event_name == 'pull_request'
235
- shell: bash
236
- run: |
237
- {
238
- echo "Review app ${APP_NAME} does not exist yet."
239
- echo "Create it with +review-app-deploy as the PR comment body."
240
- } >> "$GITHUB_STEP_SUMMARY"
241
-
242
- - name: Setup review app if it does not exist yet
243
- id: setup-review-app
244
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && steps.check-app.outputs.exists != 'true' && github.event_name != 'pull_request'
245
- working-directory: app
246
- shell: bash
247
- run: |
248
- set -euo pipefail
249
- cpflow setup-app -a "${APP_NAME}" --org "${CPLN_ORG}"
250
-
251
- - name: Create initial PR comment
252
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
253
- id: create-comment
254
- uses: actions/github-script@v7
255
- with:
256
- script: |
257
- const result = await github.rest.issues.createComment({
258
- owner: context.repo.owner,
259
- repo: context.repo.repo,
260
- issue_number: Number(process.env.PR_NUMBER),
261
- body: "🚀 Starting Control Plane review app deployment..."
262
- });
263
- core.setOutput("comment-id", result.data.id);
264
-
265
- - name: Set deployment links
266
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
267
- uses: actions/github-script@v7
268
- with:
269
- script: |
270
- const workflowUrl = `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
271
- core.exportVariable("WORKFLOW_URL", workflowUrl);
272
- core.exportVariable(
273
- "CONSOLE_URL",
274
- `https://console.cpln.io/console/org/${process.env.CPLN_ORG}/gvc/${process.env.APP_NAME}/-info`
275
- );
276
-
277
- - name: Initialize GitHub deployment
278
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
279
- id: init-deployment
280
- uses: actions/github-script@v7
281
- with:
282
- script: |
283
- const deployment = await github.rest.repos.createDeployment({
284
- owner: context.repo.owner,
285
- repo: context.repo.repo,
286
- ref: process.env.PR_SHA,
287
- environment: `review/${process.env.APP_NAME}`,
288
- auto_merge: false,
289
- required_contexts: [], // intentional: review apps deploy regardless of required status checks
290
- description: `Control Plane review app for PR #${process.env.PR_NUMBER}`
291
- });
292
-
293
- await github.rest.repos.createDeploymentStatus({
294
- owner: context.repo.owner,
295
- repo: context.repo.repo,
296
- deployment_id: deployment.data.id,
297
- state: "in_progress",
298
- description: "Deployment started"
299
- });
300
-
301
- return deployment.data.id;
302
-
303
- - name: Update PR comment with build status
304
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
305
- uses: actions/github-script@v7
306
- with:
307
- script: |
308
- const commentId = Number("${{ steps.create-comment.outputs.comment-id }}");
309
- if (!Number.isFinite(commentId) || commentId <= 0) {
310
- core.warning("Skipping PR comment update because no comment id was created.");
311
- return;
312
- }
313
-
314
- const body = [
315
- `🏗️ Building Docker image for PR #${process.env.PR_NUMBER}, commit ${process.env.PR_SHA}`,
316
- "",
317
- `[View build logs](${process.env.WORKFLOW_URL})`,
318
- "",
319
- `[Open Control Plane console](${process.env.CONSOLE_URL})`
320
- ].join("\n");
321
-
322
- await github.rest.issues.updateComment({
323
- owner: context.repo.owner,
324
- repo: context.repo.repo,
325
- comment_id: commentId,
326
- body
327
- });
328
-
329
- - name: Build Docker image
330
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
331
- uses: ./.github/actions/cpflow-build-docker-image
332
- with:
333
- app_name: ${{ env.APP_NAME }}
334
- org: ${{ vars.CPLN_ORG_STAGING }}
335
- commit: ${{ env.PR_SHA }}
336
- pr_number: ${{ env.PR_NUMBER }}
337
- docker_build_extra_args: ${{ vars.DOCKER_BUILD_EXTRA_ARGS }}
338
- docker_build_ssh_key: ${{ secrets.DOCKER_BUILD_SSH_KEY }}
339
- docker_build_ssh_known_hosts: ${{ vars.DOCKER_BUILD_SSH_KNOWN_HOSTS }}
340
- working_directory: app
341
-
342
- - name: Update PR comment with deploy status
343
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
344
- uses: actions/github-script@v7
345
- with:
346
- script: |
347
- const commentId = Number("${{ steps.create-comment.outputs.comment-id }}");
348
- if (!Number.isFinite(commentId) || commentId <= 0) {
349
- core.warning("Skipping PR comment update because no comment id was created.");
350
- return;
351
- }
352
-
353
- const body = [
354
- "🚀 Deploying review app to Control Plane...",
355
- "",
356
- `[View deploy logs](${process.env.WORKFLOW_URL})`,
357
- "",
358
- `[Open Control Plane console](${process.env.CONSOLE_URL})`
359
- ].join("\n");
360
-
361
- await github.rest.issues.updateComment({
362
- owner: context.repo.owner,
363
- repo: context.repo.repo,
364
- comment_id: commentId,
365
- body
366
- });
367
-
368
- - name: Deploy to Control Plane
369
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
370
- working-directory: app
371
- env:
372
- RELEASE_PHASE_FLAG: ${{ steps.release-phase.outputs.flag }}
373
- shell: bash
374
- run: |
375
- set -euo pipefail
376
-
377
- deploy_args=(-a "${APP_NAME}")
378
- if [[ -n "${RELEASE_PHASE_FLAG}" ]]; then
379
- deploy_args+=("${RELEASE_PHASE_FLAG}")
380
- fi
381
- deploy_args+=(--org "${CPLN_ORG}" --verbose)
382
-
383
- cpflow deploy-image "${deploy_args[@]}"
384
-
385
- - name: Retrieve app URL
386
- if: steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
387
- id: workload
388
- working-directory: app
389
- shell: bash
390
- run: |
391
- set -euo pipefail
392
- workload_name="${PRIMARY_WORKLOAD:-rails}"
393
- workload_url="$(cpln workload get "${workload_name}" --gvc "${APP_NAME}" --org "${CPLN_ORG}" -o json | jq -r '.status.endpoint // empty')"
394
- echo "workload_url=${workload_url}" >> "$GITHUB_OUTPUT"
395
-
396
- - name: Finalize deployment status
397
- if: always() && steps.config.outputs.ready == 'true' && steps.source.outputs.allowed == 'true' && (steps.check-app.outputs.exists == 'true' || steps.setup-review-app.outcome == 'success')
398
- uses: actions/github-script@v7
399
- with:
400
- script: |
401
- const commentId = Number("${{ steps.create-comment.outputs.comment-id }}");
402
- const deploymentId = "${{ steps.init-deployment.outputs.result }}";
403
- const appUrl = "${{ steps.workload.outputs.workload_url }}";
404
- const success = "${{ job.status }}" === "success";
405
-
406
- if (deploymentId) {
407
- await github.rest.repos.createDeploymentStatus({
408
- owner: context.repo.owner,
409
- repo: context.repo.repo,
410
- deployment_id: Number(deploymentId),
411
- state: success ? "success" : "failure",
412
- environment: `review/${process.env.APP_NAME}`,
413
- environment_url: success && appUrl ? appUrl : undefined,
414
- log_url: process.env.WORKFLOW_URL,
415
- description: success ? "Review app ready" : "Review app deployment failed"
416
- });
417
- }
418
-
419
- const successBody = [
420
- "## Review app ready",
421
- "",
422
- appUrl ? `[Open review app](${appUrl})` : "Review app deployed, but no endpoint URL was detected.",
423
- "",
424
- `[Open Control Plane console](${process.env.CONSOLE_URL})`,
425
- `[View workflow logs](${process.env.WORKFLOW_URL})`
426
- ].join("\n");
427
-
428
- const failureBody = [
429
- `❌ Review app deployment failed for PR #${process.env.PR_NUMBER}`,
430
- "",
431
- `[Open Control Plane console](${process.env.CONSOLE_URL})`,
432
- `[View workflow logs](${process.env.WORKFLOW_URL})`
433
- ].join("\n");
434
-
435
- if (!Number.isFinite(commentId) || commentId <= 0) {
436
- core.warning("Skipping PR comment update because no comment id was created.");
437
- return;
438
- }
439
-
440
- await github.rest.issues.updateComment({
441
- owner: context.repo.owner,
442
- repo: context.repo.repo,
443
- comment_id: commentId,
444
- body: success ? successBody : failureBody
445
- });
33
+ # Keep the @ref in `uses:` and `control_plane_flow_ref` below in sync: the
34
+ # first selects the reusable workflow, the second selects its shared actions.
35
+ uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-review-app.yml@__CPFLOW_GITHUB_ACTIONS_REF__
36
+ with:
37
+ control_plane_flow_ref: __CPFLOW_GITHUB_ACTIONS_REF__
38
+ # `secrets: inherit` passes all caller repository secrets to the trusted
39
+ # upstream workflow. The upstream workflow only reads the named secrets it
40
+ # references, but GitHub does not enforce that boundary. Strict consumers can
41
+ # set CPFLOW_GITHUB_ACTIONS_REF to an immutable commit SHA.
42
+ secrets: inherit
@@ -14,127 +14,16 @@ on:
14
14
  permissions:
15
15
  contents: read
16
16
 
17
- env:
18
- APP_NAME: ${{ vars.STAGING_APP_NAME }}
19
- CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }}
20
- STAGING_APP_BRANCH: __STAGING_APP_BRANCH_EXPRESSION__
21
-
22
- concurrency:
23
- group: cpflow-deploy-staging-${{ github.ref_name }}
24
- # Match the review-app and delete workflows: a cancelled `cpflow deploy-image` mid-rollout
25
- # can leave the staging GVC in a partially-deployed state (some workloads on the new image,
26
- # others on the old). Let an in-flight deploy finish before the next push starts a new run.
27
- cancel-in-progress: false
28
-
29
17
  jobs:
30
- validate-branch:
31
- runs-on: ubuntu-latest
32
- timeout-minutes: 5
33
- outputs:
34
- is_deployable: ${{ steps.check-branch.outputs.is_deployable }}
35
- steps:
36
- - name: Check whether this branch should deploy staging
37
- id: check-branch
38
- shell: bash
39
- run: |
40
- set -euo pipefail
41
-
42
- if [[ -n "${STAGING_APP_BRANCH}" ]]; then
43
- if [[ "${GITHUB_REF_NAME}" == "${STAGING_APP_BRANCH}" ]]; then
44
- echo "is_deployable=true" >> "$GITHUB_OUTPUT"
45
- else
46
- echo "Branch '${GITHUB_REF_NAME}' does not match STAGING_APP_BRANCH='${STAGING_APP_BRANCH}'"
47
- echo "is_deployable=false" >> "$GITHUB_OUTPUT"
48
- fi
49
- elif [[ "${GITHUB_REF_NAME}" == "main" || "${GITHUB_REF_NAME}" == "master" ]]; then
50
- echo "is_deployable=true" >> "$GITHUB_OUTPUT"
51
- else
52
- echo "Branch '${GITHUB_REF_NAME}' is not main/master and no STAGING_APP_BRANCH is configured"
53
- echo "is_deployable=false" >> "$GITHUB_OUTPUT"
54
- fi
55
-
56
- - name: Checkout repository
57
- if: steps.check-branch.outputs.is_deployable == 'true'
58
- uses: actions/checkout@v4
59
-
60
- - name: Validate required secrets and variables
61
- if: steps.check-branch.outputs.is_deployable == 'true'
62
- uses: ./.github/actions/cpflow-validate-config
63
- env:
64
- CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
65
- CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
66
- STAGING_APP_NAME: ${{ vars.STAGING_APP_NAME }}
67
- with:
68
- required: |
69
- secret:CPLN_TOKEN_STAGING
70
- variable:CPLN_ORG_STAGING
71
- variable:STAGING_APP_NAME
72
-
73
- build:
74
- needs: validate-branch
75
- if: needs.validate-branch.outputs.is_deployable == 'true'
76
- runs-on: ubuntu-latest
77
- timeout-minutes: 30
78
- steps:
79
- - name: Checkout repository
80
- uses: actions/checkout@v4
81
- with:
82
- persist-credentials: false
83
-
84
- - name: Setup environment
85
- uses: ./.github/actions/cpflow-setup-environment
86
- with:
87
- token: ${{ secrets.CPLN_TOKEN_STAGING }}
88
- org: ${{ vars.CPLN_ORG_STAGING }}
89
- cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
90
- cpflow_version: ${{ vars.CPFLOW_VERSION }}
91
-
92
- - name: Build Docker image
93
- uses: ./.github/actions/cpflow-build-docker-image
94
- with:
95
- app_name: ${{ env.APP_NAME }}
96
- org: ${{ vars.CPLN_ORG_STAGING }}
97
- commit: ${{ github.sha }}
98
- docker_build_extra_args: ${{ vars.DOCKER_BUILD_EXTRA_ARGS }}
99
- docker_build_ssh_key: ${{ secrets.DOCKER_BUILD_SSH_KEY }}
100
- docker_build_ssh_known_hosts: ${{ vars.DOCKER_BUILD_SSH_KNOWN_HOSTS }}
101
-
102
- deploy:
103
- needs: [validate-branch, build]
104
- if: needs.validate-branch.outputs.is_deployable == 'true'
105
- runs-on: ubuntu-latest
106
- timeout-minutes: 30
107
- steps:
108
- - name: Checkout repository
109
- uses: actions/checkout@v4
110
- with:
111
- persist-credentials: false
112
-
113
- - name: Setup environment
114
- uses: ./.github/actions/cpflow-setup-environment
115
- with:
116
- token: ${{ secrets.CPLN_TOKEN_STAGING }}
117
- org: ${{ vars.CPLN_ORG_STAGING }}
118
- cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
119
- cpflow_version: ${{ vars.CPFLOW_VERSION }}
120
-
121
- - name: Detect release phase support
122
- id: release-phase
123
- uses: ./.github/actions/cpflow-detect-release-phase
124
- with:
125
- app_name: ${{ env.APP_NAME }}
126
-
127
- - name: Deploy staging image
128
- env:
129
- RELEASE_PHASE_FLAG: ${{ steps.release-phase.outputs.flag }}
130
- shell: bash
131
- run: |
132
- set -euo pipefail
133
-
134
- deploy_args=(-a "${APP_NAME}")
135
- if [[ -n "${RELEASE_PHASE_FLAG}" ]]; then
136
- deploy_args+=("${RELEASE_PHASE_FLAG}")
137
- fi
138
- deploy_args+=(--org "${CPLN_ORG}" --verbose)
139
-
140
- cpflow deploy-image "${deploy_args[@]}"
18
+ deploy-staging:
19
+ # Keep the @ref in `uses:` and `control_plane_flow_ref` below in sync: the
20
+ # first selects the reusable workflow, the second selects its shared actions.
21
+ uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-staging.yml@__CPFLOW_GITHUB_ACTIONS_REF__
22
+ with:
23
+ control_plane_flow_ref: __CPFLOW_GITHUB_ACTIONS_REF__
24
+ staging_app_branch_default: "__STAGING_BRANCH_DEFAULT__"
25
+ # `secrets: inherit` passes all caller repository secrets to the trusted
26
+ # upstream workflow. The upstream workflow only reads the named secrets it
27
+ # references, but GitHub does not enforce that boundary. Strict consumers can
28
+ # set CPFLOW_GITHUB_ACTIONS_REF to an immutable commit SHA.
29
+ secrets: inherit
@@ -17,42 +17,19 @@ permissions:
17
17
 
18
18
  jobs:
19
19
  help:
20
- # Comment-triggered runs are gated on author_association so only repo
21
- # owners/members/collaborators can invoke them. workflow_dispatch is
22
- # intentionally not gated here: GitHub already restricts manual dispatches
23
- # to users with `actions: write` (write access to the repo), which is a
24
- # stricter bar than COLLABORATOR.
25
20
  if: |
26
21
  (github.event_name == 'issue_comment' &&
27
22
  github.event.issue.pull_request &&
28
23
  contains(fromJson('["+review-app-help","+review-app-help\n","+review-app-help\r\n"]'), github.event.comment.body) &&
29
24
  contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
30
25
  github.event_name == 'workflow_dispatch'
31
- runs-on: ubuntu-latest
32
- timeout-minutes: 5
33
-
34
- steps:
35
- - name: Checkout repository
36
- uses: actions/checkout@v4
37
- with:
38
- # Help only reads `.github/cpflow-help.md`; no git push happens, so drop the
39
- # GITHUB_TOKEN credential helper to keep the token out of .git/config.
40
- persist-credentials: false
41
-
42
- - name: Post help message
43
- uses: actions/github-script@v7
44
- with:
45
- script: |
46
- const fs = require("fs");
47
- const helpText = fs.readFileSync(".github/cpflow-help.md", "utf8");
48
-
49
- const prNumber = context.eventName === "workflow_dispatch"
50
- ? Number(context.payload.inputs.pr_number)
51
- : context.issue.number;
52
-
53
- await github.rest.issues.createComment({
54
- owner: context.repo.owner,
55
- repo: context.repo.repo,
56
- issue_number: prNumber,
57
- body: helpText
58
- });
26
+ # control_plane_flow_ref is accepted by the upstream workflow for wrapper
27
+ # consistency; this helper checks out caller content only.
28
+ uses: shakacode/control-plane-flow/.github/workflows/cpflow-help-command.yml@__CPFLOW_GITHUB_ACTIONS_REF__
29
+ with:
30
+ control_plane_flow_ref: __CPFLOW_GITHUB_ACTIONS_REF__
31
+ # `secrets: inherit` passes all caller repository secrets to the trusted
32
+ # upstream workflow. This helper does not read them, but GitHub does not
33
+ # enforce that boundary. Strict consumers can set CPFLOW_GITHUB_ACTIONS_REF
34
+ # to an immutable commit SHA.
35
+ secrets: inherit