cpflow 5.0.4 → 5.1.1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/actions/cpflow-wait-for-health/action.yml +11 -4
  3. data/.github/workflows/cpflow-promote-staging-to-production.yml +269 -43
  4. data/.github/workflows/rspec-shared.yml +8 -1
  5. data/CHANGELOG.md +28 -1
  6. data/Gemfile.lock +1 -1
  7. data/README.md +36 -11
  8. data/docs/ai-github-flow-prompt.md +1 -1
  9. data/docs/assets/logo/favicon.ico +0 -0
  10. data/docs/assets/logo/icon-1024.png +0 -0
  11. data/docs/assets/logo/icon-128.png +0 -0
  12. data/docs/assets/logo/icon-16.png +0 -0
  13. data/docs/assets/logo/icon-192.png +0 -0
  14. data/docs/assets/logo/icon-24.png +0 -0
  15. data/docs/assets/logo/icon-32.png +0 -0
  16. data/docs/assets/logo/icon-48.png +0 -0
  17. data/docs/assets/logo/icon-512.png +0 -0
  18. data/docs/assets/logo/icon-64.png +0 -0
  19. data/docs/assets/logo/icon-tile.svg +17 -0
  20. data/docs/assets/logo/mark-transparent.svg +16 -0
  21. data/docs/ci-automation.md +137 -47
  22. data/docs/commands.md +13 -3
  23. data/docs/postgres.md +6 -0
  24. data/docs/rds-private-networking.md +649 -0
  25. data/docs/secrets-and-env-values.md +49 -0
  26. data/docs/tips.md +256 -10
  27. data/examples/controlplane.yml +8 -0
  28. data/lib/command/ai_github_flow_prompt.rb +1 -1
  29. data/lib/command/apply_template.rb +3 -0
  30. data/lib/command/base.rb +69 -0
  31. data/lib/command/cleanup_stale_apps.rb +1 -1
  32. data/lib/command/delete.rb +85 -10
  33. data/lib/command/deploy_image.rb +30 -8
  34. data/lib/command/generate_github_actions.rb +6 -0
  35. data/lib/command/maintenance_off.rb +1 -0
  36. data/lib/command/maintenance_on.rb +1 -0
  37. data/lib/command/run.rb +25 -5
  38. data/lib/command/setup_app.rb +11 -2
  39. data/lib/core/config.rb +81 -0
  40. data/lib/core/controlplane.rb +15 -5
  41. data/lib/core/maintenance_mode.rb +93 -6
  42. data/lib/core/template_parser.rb +4 -0
  43. data/lib/cpflow/version.rb +1 -1
  44. data/lib/generator_templates/controlplane.yml +7 -0
  45. data/lib/generator_templates_sqlite/controlplane.yml +7 -0
  46. data/lib/github_flow_templates/.github/cpflow-help.md +48 -13
  47. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +768 -15
  48. data/lib/github_flow_templates/bin/pin-cpflow-github-ref +17 -3
  49. data/lib/github_flow_templates/bin/test-cpflow-github-flow +61 -9
  50. metadata +15 -2
@@ -20,7 +20,7 @@ prompt tells the agent to stop on.
20
20
  ```text
21
21
  Set up Control Plane GitHub Flow for this repo. Start with `cpflow github-flow-readiness` and stop on any reported blockers. The repo must be deployable from a clean clone: published package versions, complete runtime scaffold, and a production Dockerfile that can build the app. If any package version is unpublished, inaccessible from CI, or requires credentials that are not already modeled in the repo or GitHub settings, stop and report the blocker instead of generating workflow files. If the repo is a legacy sample pinned to an obsolete Ruby or Bundler toolchain, if it does not even have a production Dockerfile yet, or if it is a monorepo without an already-decided single app boundary for this flow, stop and report that as a prerequisite instead of forcing the rollout.
22
22
 
23
- If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default and rename them only if the project needs a different prefix. Then run `cpflow generate-github-actions` (or `cpflow generate-github-actions --staging-branch BRANCH` when staging should deploy from a branch other than `main`/`master`), keep review apps opt-in via `+review-app-deploy`, make sure any `STAGING_APP_BRANCH` repository variable is also present in the generated staging workflow's `on.push.branches` filter, and list the GitHub secrets and variables that must be configured. Do not hand-edit duplicated upstream refs into the generated wrappers: the only downstream Control Plane Flow pin should be the reusable workflow `uses: ...@vX.Y.Z` value generated from the installed `cpflow` gem version, and upstream workflows load their matching shared actions automatically. When bumping the `cpflow` gem in a downstream repo, run `cpflow update-github-actions` (or `bundle exec cpflow update-github-actions`) and validate with `bin/test-cpflow-github-flow` in the same PR so the checked-in wrappers move to the matching release tag. Keep the standard path simple: review apps require only `CPLN_TOKEN_STAGING` when the generated review app config can be inferred. Document the one-time Control Plane bootstrap command for persistent staging and production apps with `cpflow setup-app --skip-post-creation-hook`; for existing apps or later template updates, document `cpflow apply-template` and the need for the app identity to have `reveal` on the app secret policy. Do not imply the staging deploy or promotion workflows create those persistent GVCs. For production promotion, document a protected `production` GitHub Environment with required reviewers, prevent self-review, and `CPLN_TOKEN_PRODUCTION` stored as an environment secret, not as a repository or organization secret.
23
+ If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default and rename them only if the project needs a different prefix. Then run `cpflow generate-github-actions` (or `cpflow generate-github-actions --staging-branch BRANCH` when staging should deploy from a branch other than `main`/`master`), keep review apps opt-in via `+review-app-deploy`, make sure any `STAGING_APP_BRANCH` repository variable is also present in the generated staging workflow's `on.push.branches` filter, and list the GitHub secrets and variables that must be configured. Do not hand-edit duplicated upstream refs into the generated wrappers: the only downstream Control Plane Flow pin should be the reusable workflow `uses: ...@vX.Y.Z` value generated from the installed `cpflow` gem version, and upstream workflows load their matching shared actions automatically. When bumping the `cpflow` gem in a downstream repo, run `cpflow update-github-actions` (or `bundle exec cpflow update-github-actions`) and validate with `bin/test-cpflow-github-flow` in the same PR so the checked-in wrappers move to the matching release tag. Keep the standard path simple: review apps require only `CPLN_TOKEN_STAGING` when the generated review app config can be inferred. For shared review-app resources such as one staging database, use `shared_secret_grants` and `{{SHARED_SECRET_DATABASE}}` placeholders instead of hardcoding the base app secret name; this keeps review-app policy binding and cleanup automatic while avoiding per-PR database cost. Document the one-time Control Plane bootstrap command for persistent staging and production apps with `cpflow setup-app --skip-post-creation-hook`; for existing apps or later template updates, document `cpflow apply-template` and the need for the app identity to have `reveal` on the app secret policy. Do not imply the staging deploy or promotion workflows create those persistent GVCs. For production promotion, document a protected `production` GitHub Environment with required reviewers, prevent self-review, and `CPLN_TOKEN_PRODUCTION` stored as an environment secret, not as a repository or organization secret.
24
24
 
25
25
  Keep Node available in the final image if asset compilation or SSR depends on ExecJS, Yarn, `pnpm`, or npm after the main install layer. Make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement. Preserve repo-defined frontend build hooks: if `config/shakapacker.yml` defines a `precompile_hook`, or React on Rails enables `config.auto_load_bundle = true`, confirm the generated Dockerfile runs that codegen step before `rails assets:precompile`. If `config/database.yml` shows SQLite in production, confirm that the generated scaffold uses persistent `db` and `storage` volumes plus a release script that runs `rails db:prepare`; otherwise keep the default Postgres workload. If the public workload is not named `rails`, set `PRIMARY_WORKLOAD` or adjust the generated workflows. Inspect the Dockerfile and package sources for private GitHub dependencies or `RUN --mount=type=ssh`; if present, wire `DOCKER_BUILD_SSH_KEY`, optionally set `DOCKER_BUILD_SSH_KNOWN_HOSTS` for non-GitHub SSH hosts, and keep `DOCKER_BUILD_EXTRA_ARGS` to newline-delimited single tokens such as `--build-arg=FOO=bar`.
26
26
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,17 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-label="Control Plane Flow icon">
2
+ <title>Control Plane Flow icon</title>
3
+ <rect x="64" y="64" width="896" height="896" rx="192" fill="#0B1118" stroke="#26313D" stroke-width="12"/>
4
+ <g fill="none" stroke-linecap="round" stroke-linejoin="round">
5
+ <path d="M290 650 C330 500 435 430 512 512 C600 606 704 560 746 390" stroke="#2BD7FF" stroke-width="68"/>
6
+ <path d="M746 390 L688 397" stroke="#2BD7FF" stroke-width="68"/>
7
+ <path d="M746 390 L731 446" stroke="#2BD7FF" stroke-width="68"/>
8
+ </g>
9
+ <circle cx="290" cy="650" r="92" fill="#8973FF"/>
10
+ <circle cx="512" cy="512" r="92" fill="#70D187"/>
11
+ <circle cx="746" cy="390" r="92" fill="#FFB000"/>
12
+ <circle cx="290" cy="650" r="32" fill="#0B1118" opacity="0.88"/>
13
+ <circle cx="512" cy="512" r="32" fill="#0B1118" opacity="0.88"/>
14
+ <circle cx="746" cy="390" r="32" fill="#0B1118" opacity="0.88"/>
15
+ <path d="M390 730 H612" stroke="#2BD7FF" stroke-width="40" stroke-linecap="round" opacity="0.86"/>
16
+ <path d="M612 730 L555 692 M612 730 L555 768" stroke="#2BD7FF" stroke-width="40" stroke-linecap="round" stroke-linejoin="round" opacity="0.86"/>
17
+ </svg>
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-label="Control Plane Flow icon transparent mark">
2
+ <title>Control Plane Flow icon transparent mark</title>
3
+ <g fill="none" stroke-linecap="round" stroke-linejoin="round">
4
+ <path d="M290 650 C330 500 435 430 512 512 C600 606 704 560 746 390" stroke="#2BD7FF" stroke-width="68"/>
5
+ <path d="M746 390 L688 397" stroke="#2BD7FF" stroke-width="68"/>
6
+ <path d="M746 390 L731 446" stroke="#2BD7FF" stroke-width="68"/>
7
+ </g>
8
+ <circle cx="290" cy="650" r="92" fill="#8973FF"/>
9
+ <circle cx="512" cy="512" r="92" fill="#70D187"/>
10
+ <circle cx="746" cy="390" r="92" fill="#FFB000"/>
11
+ <circle cx="290" cy="650" r="32" fill="#0B1118" opacity="0.88"/>
12
+ <circle cx="512" cy="512" r="32" fill="#0B1118" opacity="0.88"/>
13
+ <circle cx="746" cy="390" r="32" fill="#0B1118" opacity="0.88"/>
14
+ <path d="M390 730 H612" stroke="#2BD7FF" stroke-width="40" stroke-linecap="round" opacity="0.86"/>
15
+ <path d="M612 730 L555 692 M612 730 L555 768" stroke="#2BD7FF" stroke-width="40" stroke-linecap="round" stroke-linejoin="round" opacity="0.86"/>
16
+ </svg>
@@ -16,7 +16,7 @@ End-to-end rollout in one view:
16
16
 
17
17
  1. `cpflow github-flow-readiness` — exits non-zero if the repo is not ready to deploy.
18
18
  2. `cpflow generate` — creates `.controlplane/` if missing.
19
- 3. `cpflow generate-github-actions` — adds thin `cpflow-*` workflow wrappers that call upstream reusable workflows.
19
+ 3. `cpflow generate-github-actions` — adds `cpflow-*` workflow wrappers. Review-app, staging, cleanup, and helper workflows call upstream reusable workflows; production promotion is a normal caller-repo job so it can own the protected production Environment.
20
20
  4. Configure the GitHub [repository secrets and variables](#required-github-repository-settings) the workflows expect.
21
21
  5. Push the branch, then comment `+review-app-deploy` on a PR to spin up a review environment.
22
22
 
@@ -196,23 +196,62 @@ For production promotion, also configure:
196
196
  - `CPLN_ORG_PRODUCTION` as a production environment variable, for example `company-production`
197
197
  - `PRODUCTION_APP_NAME` as a production environment variable, for example `my-app-production`
198
198
 
199
+ Enter GitHub variables such as `CPLN_ORG_STAGING`,
200
+ `CPLN_ORG_PRODUCTION`, `STAGING_APP_NAME`, and `PRODUCTION_APP_NAME`
201
+ as plain single-line values. The generated production promotion workflow trims
202
+ accidental leading/trailing whitespace and line endings from Control Plane org
203
+ names before building registry URLs, but embedded line breaks are rejected
204
+ because they could change the target org name after normalization.
205
+
206
+ Production promotion copies the exact image currently deployed on the selected
207
+ staging workload. If that staging image is digest-pinned, the digest is used for
208
+ the source copy while the production tag is derived from the tag portion. Tags
209
+ with a `_<commit>` suffix keep that suffix in production; plain numeric tags are
210
+ also valid and promote to the next plain production tag. The copy step uses
211
+ `docker buildx imagetools create --prefer-index=false --tag` with isolated
212
+ Docker credentials, which preserves multi-architecture manifests, preserves
213
+ single-platform manifest format when supported, and avoids pulling image layers
214
+ onto the GitHub Actions runner.
215
+
216
+ Before copying the image, production promotion compares the environment variable
217
+ names exposed by staging and production at both the GVC level and each configured
218
+ app workload's container level. Variables present in staging are treated as
219
+ required for production, while production-only variables emit warnings. A missing
220
+ production workload variable such as a renderer password or runtime secret fails
221
+ the promotion before the image copy starts.
222
+
199
223
  Do not put `CPLN_TOKEN_PRODUCTION` in repository or organization secrets for
200
- sensitive production systems. The generated promotion reusable workflow declares
201
- `environment: production`; the generated caller passes that environment name
202
- through `production_environment`. GitHub waits for the `production`
203
- environment's protection rules before injecting `CPLN_TOKEN_PRODUCTION` into
204
- the upstream production job.
205
-
206
- GitHub's reusable-workflow syntax still requires the upstream workflow to
207
- declare `CPLN_TOKEN_PRODUCTION` as an optional `workflow_call` secret so static
208
- validation accepts `secrets.CPLN_TOKEN_PRODUCTION`, but the generated caller
209
- must not pass it. GitHub uses the secret from the reusable workflow job's
210
- `production` environment when that environment is configured.
211
-
212
- Generated caller workflows pass only the named secrets each reusable workflow
213
- needs. They do not use `secrets: inherit`; the production token is supplied by
214
- the protected `production` Environment after approval, not forwarded from a
215
- repository secret.
224
+ sensitive production systems. Production promotion intentionally runs as a
225
+ normal caller-repo workflow job with `environment: production`, then checks out
226
+ the pinned `control-plane-flow` release for shared actions. GitHub exposes the
227
+ production token to that job only after the `production` environment gate.
228
+ GitHub does not expose which secret scope supplied a nonempty value at runtime,
229
+ so a broader repository or organization secret with the same name can mask a
230
+ missing environment secret. Keep the production token absent from broader secret
231
+ scopes.
232
+
233
+ Do not move production promotion behind a cross-repo reusable workflow. GitHub
234
+ does not expose the caller repository's environment secrets to that called
235
+ workflow, so `secrets.CPLN_TOKEN_PRODUCTION` remains empty even when the
236
+ `production` Environment contains the secret. Generated reusable-workflow
237
+ callers still pass only the named secrets each upstream workflow needs and do
238
+ not use `secrets: inherit`; production promotion is the caller-owned exception.
239
+
240
+ If promotion fails in the `Validate production token` step with
241
+ `CPLN_TOKEN_PRODUCTION is not set. Add it as a secret on the 'production' GitHub Environment.`,
242
+ check the environment scope first. Also verify that the `promote-to-production`
243
+ job declares `environment: production` and that no same-named repository or
244
+ organization secret exists. Create or verify the environment secret with:
245
+ You need permission to manage repository environments and secrets to run these
246
+ commands.
247
+
248
+ ```sh
249
+ gh secret set CPLN_TOKEN_PRODUCTION --repo OWNER/REPO --env production
250
+ # Paste the token value when prompted.
251
+ gh secret list --repo OWNER/REPO --env production
252
+ gh secret list --repo OWNER/REPO
253
+ gh secret list --org OWNER | grep '^CPLN_TOKEN_PRODUCTION[[:space:]]' || true
254
+ ```
216
255
 
217
256
  ## First-Time Control Plane Bootstrap
218
257
 
@@ -256,6 +295,12 @@ cpflow setup-app -a my-app-production --org my-org-production --skip-post-creati
256
295
  Use production-only runtime secrets and values for the production app. The
257
296
  protected GitHub Environment controls who can run the promotion workflow, but
258
297
  the production app resources still need to exist before the first promotion.
298
+ After bootstrap, populate the production app secret dictionary with the values
299
+ referenced by `.controlplane/templates`, then run `cpflow apply-template` against
300
+ production when templates change so the workload env references remain persisted.
301
+ Production promotion checks for missing GVC and workload container env names
302
+ before copying the staging image, so a staging-only runtime variable will stop
303
+ the run early instead of deploying an image that cannot boot.
259
304
 
260
305
  Review apps are different: the generated `+review-app-deploy` workflow creates
261
306
  temporary PR apps as needed, including the identity and secret policy binding.
@@ -264,6 +309,28 @@ templates, and the staging token must have access to create and update
264
309
  review-app GVCs, workloads, images, identities, policies, and secrets in the
265
310
  staging org.
266
311
 
312
+ If review apps share an existing staging database or another existing secret,
313
+ declare it with `shared_secret_grants` on the review app config entry. The
314
+ deploy workflow runs `setup-app` for new review apps and `deploy-image` for
315
+ image updates; those commands bind or repair the review app identity's `reveal`
316
+ permission on each configured shared policy. The delete and cleanup workflows
317
+ call `cpflow delete`, which removes those bindings as review apps go away. This
318
+ lets one shared database or license secret serve many short-lived review apps
319
+ without granting every review identity access to unrelated app secrets.
320
+
321
+ ```yaml
322
+ apps:
323
+ my-app-review:
324
+ match_if_app_name_starts_with: true
325
+ shared_secret_grants:
326
+ - name: database
327
+ secret_name: my-app-review-database-secrets
328
+ policy_name: my-app-review-database-secrets-policy
329
+ ```
330
+
331
+ Then reference `cpln://secret/{{SHARED_SECRET_DATABASE}}.DATABASE_URL` from the
332
+ workload template.
333
+
267
334
  ### Production Promotion Safety
268
335
 
269
336
  `CPLN_TOKEN_PRODUCTION` can change live production workloads, images, releases,
@@ -279,12 +346,25 @@ The standard path is:
279
346
  7. Store `CPLN_ORG_PRODUCTION` and `PRODUCTION_APP_NAME` as `production`
280
347
  environment variables, or as repository variables only when those names are
281
348
  intentionally non-sensitive.
349
+ 8. Keep GitHub variable values single-line; a pasted trailing newline is trimmed
350
+ for Control Plane org names, but embedded line breaks are rejected before
351
+ deployment, copy, health-check, or rollback steps run.
352
+ 9. Bootstrap or re-apply the persistent production app templates before first
353
+ promotion so app workload container env references and Control Plane secret
354
+ dictionaries exist in production.
355
+ 10. Expect promotion to preserve the selected staging image reference. Digest
356
+ references are copied by digest, commit-suffixed tags keep the commit suffix,
357
+ and plain numeric tags remain valid.
358
+ 11. Expect production health and rollback readiness polling to require Control
359
+ Plane `status.ready` and `status.readyLatest` before checking the endpoint.
282
360
 
283
361
  GitHub only exposes environment secrets to jobs that reference the environment
284
- after configured protection rules pass. GitHub also does not allow a caller job
285
- that directly invokes a reusable workflow to set `environment`; for that reason,
286
- the reusable promotion workflow itself declares `environment: production`. See
287
- GitHub's docs for [managing environments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments),
362
+ after configured protection rules pass. GitHub does not allow a caller job that
363
+ directly invokes a reusable workflow to set `environment`, and cross-repo
364
+ reusable workflows do not receive the caller repository's environment secrets.
365
+ For that reason, generated production promotion stays as a normal caller-repo
366
+ job with `environment: production`. See GitHub's docs for
367
+ [managing environments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments),
288
368
  [deployment protection rules](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments),
289
369
  and [reusable workflow limitations](https://docs.github.com/en/actions/reference/reusable-workflows-reference#supported-keywords-for-jobs-that-call-a-reusable-workflow).
290
370
 
@@ -376,8 +456,8 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
376
456
 
377
457
  - Manually promotes the staging artifact to production with a confirmation input.
378
458
  - Runs the production job in the `production` GitHub Environment, so configured reviewers approve the job before production environment secrets are available.
379
- - Verifies that production has the env var names staging expects.
380
- - Runs a health check against `PRIMARY_WORKLOAD`.
459
+ - Verifies that production has the GVC and app workload container env var names staging expects.
460
+ - Runs a health check against `PRIMARY_WORKLOAD` only after Control Plane reports the latest workload version ready.
381
461
  - Attempts a rollback of every configured application workload if the new production image does not come up healthy.
382
462
  - Creates a GitHub release after a successful promotion.
383
463
 
@@ -405,32 +485,37 @@ or clones that copy the workflow before configuring Control Plane can remove
405
485
  wrapper-level `if:` guard shown in that file, for example
406
486
  `vars.REVIEW_APP_PREFIX != '' || vars.CPLN_ORG_STAGING != ''`.
407
487
 
408
- ## Upstream Reusable Workflows
488
+ ## Upstream Workflows And Actions
409
489
 
410
- The generated workflows are intentionally small wrappers. The deployment logic,
411
- comment formatting, Control Plane CLI setup, Docker image build, and cleanup helpers
412
- live in upstream reusable workflows and composite actions in this repository.
490
+ Most generated workflows are intentionally small wrappers. The deployment
491
+ logic, comment formatting, Control Plane CLI setup, Docker image build, and
492
+ cleanup helpers live in upstream reusable workflows and composite actions in
493
+ this repository. Production promotion is expanded into the caller repository so
494
+ it can own `environment: production`, but it still checks out the same upstream
495
+ ref for shared composite actions.
413
496
 
414
- - `cpflow-setup-environment`: installs Ruby, the Control Plane CLI, and `cpflow`, then logs into the target org. By default it builds `cpflow` from the checked-out upstream reusable-workflow ref; set the `CPFLOW_VERSION` repository variable only when you want to force a published RubyGems release.
497
+ - `cpflow-setup-environment`: installs Ruby, the Control Plane CLI, and `cpflow`, then logs into the target org. By default it builds `cpflow` from the checked-out upstream `control-plane-flow` ref; set the `CPFLOW_VERSION` repository variable only when you want to force a published RubyGems release.
415
498
  - `cpflow-build-docker-image`: builds and pushes the app image with the desired commit SHA
416
499
  - `cpflow-delete-control-plane-app`: safely deletes temporary apps and refuses to touch names outside the configured review-app prefix
417
500
 
418
501
  ## Version Pins: GitHub Ref vs RubyGems
419
502
 
420
- The generated `cpflow-*` workflow files are thin wrappers around reusable
421
- workflows in `shakacode/control-plane-flow`. GitHub loads reusable workflows
422
- from a repository ref, not from the Ruby gem, so each wrapper has an upstream
423
- GitHub ref:
503
+ Generated `cpflow-*` workflow files pin `shakacode/control-plane-flow` from
504
+ GitHub, not from the Ruby gem. Reusable workflow wrappers pin that source with
505
+ an upstream `uses:` ref:
424
506
 
425
507
  ```yaml
426
508
  uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-review-app.yml@<ref>
427
509
  ```
428
510
 
429
- That single `uses:` ref is the downstream lock. GitHub exposes the reusable
430
- workflow's own repository, ref, and SHA to the called job, so the upstream
431
- workflow checks out the matching `control-plane-flow` source automatically from
432
- that context. Downstream wrappers should not pass `control_plane_flow_ref`; if
433
- you see that input in generated wrappers, regenerate with a newer `cpflow`.
511
+ Production promotion pins the same source in its `Checkout control-plane-flow
512
+ actions` step because it is a caller-owned job, not a reusable workflow caller.
513
+ Those refs are the downstream lock. GitHub exposes a reusable workflow's own
514
+ repository, ref, and SHA to called jobs, so reusable upstream workflows check out
515
+ matching `control-plane-flow` source automatically from that context. Downstream
516
+ reusable-workflow wrappers should not pass `control_plane_flow_ref`; if you see
517
+ that input outside the production promotion setup step, regenerate with a newer
518
+ `cpflow`.
434
519
 
435
520
  There are two locks, and they protect different things:
436
521
 
@@ -528,10 +613,12 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
528
613
  bin/pin-cpflow-github-ref <upstream-pr-sha>
529
614
  ```
530
615
 
531
- The helper updates every generated `cpflow-*` workflow wrapper. It accepts
532
- release tags and full commit SHAs by default, rejects branch names such as
533
- `main` or `feature/foo`, and requires `--allow-moving-ref` for short-lived
534
- local experiments that should not be committed.
616
+ The helper updates every generated reusable-workflow `uses:` ref plus the
617
+ production workflow's pinned `control-plane-flow` checkout and setup
618
+ validation ref. It accepts release tags and full commit SHAs by default,
619
+ rejects branch names such as `main` or `feature/foo`, and requires
620
+ `--allow-moving-ref` for short-lived local experiments that should not be
621
+ committed.
535
622
 
536
623
  3. Keep `CPFLOW_VERSION` unset so the workflow builds `cpflow` from the same
537
624
  upstream SHA that supplies the reusable workflow and composite actions. If
@@ -566,9 +653,10 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
566
653
  `bin/pin-cpflow-github-ref vX.Y.Z` only for a ref-only update when the
567
654
  generated templates are already current.
568
655
 
569
- This tests the real reusable workflow, shared composite actions, and source-built
570
- `cpflow` gem from one immutable upstream commit. It avoids merging upstream blind
571
- and avoids running production automation against a moving branch.
656
+ This tests the real reusable workflows, the production workflow's checked-out
657
+ shared composite actions, and the source-built `cpflow` gem from one immutable
658
+ upstream commit. It avoids merging upstream blind and avoids running production
659
+ automation against a moving branch.
572
660
 
573
661
  ## Local Generated-Flow Checks
574
662
 
@@ -580,9 +668,11 @@ bin/test-cpflow-github-flow
580
668
 
581
669
  The helper runs `cpflow github-flow-readiness`, parses generated workflow YAML,
582
670
  checks composite action metadata for literal GitHub expressions in descriptions,
583
- checks that all generated wrappers use one upstream ref consistently, rejects
584
- broad `secrets: inherit` usage in generated cpflow wrappers, rejects obsolete
585
- `control_plane_flow_ref` wrapper inputs, and runs
671
+ checks that all generated wrappers and the production `control-plane-flow`
672
+ checkout use one upstream ref consistently, rejects broad `secrets: inherit`
673
+ usage in generated cpflow wrappers, rejects obsolete `control_plane_flow_ref`
674
+ wrapper inputs, verifies production promotion remains a caller-owned
675
+ `environment: production` job, and runs
586
676
  `actionlint` against `.github/workflows/cpflow-*.yml`. Its `actionlint` command
587
677
  keeps the existing shellcheck ignore and also ignores stale local `actionlint`
588
678
  false positives for GitHub's newer reusable-workflow `job.workflow_*` fields.
data/docs/commands.md CHANGED
@@ -41,6 +41,9 @@ cpflow ai-github-flow-prompt
41
41
  {{APP_IMAGE_LINK}} - full link for latest app image, ready to be used for the value of `containers[].image` in the templates
42
42
  {{APP_IDENTITY}} - default identity
43
43
  {{APP_IDENTITY_LINK}} - full link for identity, ready to be used for the value of `identityLink` in the templates
44
+ {{APP_SECRETS}} - app secret dictionary name
45
+ {{APP_SECRETS_POLICY}} - app secret policy name
46
+ {{SHARED_SECRET_<NAME>}} - shared secret dictionary name from `shared_secret_grants`
44
47
  ```
45
48
 
46
49
  ```sh
@@ -79,7 +82,7 @@ cpflow cleanup-images -a $APP_NAME
79
82
  ### `cleanup-stale-apps`
80
83
 
81
84
  - Acts on stale apps based on the creation date of the latest image, or the GVC if no images exist
82
- - With `--mode=delete` (default): deletes the whole app (GVC with all workloads, all volumesets and all images), and unbinds the app from the secrets policy as long as both the identity and the policy exist (and are bound)
85
+ - With `--mode=delete` (default): deletes the whole app (GVC with all workloads, all volumesets and all images), and unbinds the app from the secrets policy and any configured `shared_secret_grants` policies as long as both the identity and each policy exist (and are bound)
83
86
  - With `--mode=stop`: suspends all workloads via `cpflow ps:stop` — no GVC, volumeset, or image is removed; resume with `cpflow ps:start`
84
87
  - `--mode=stop` only suspends workloads listed in `app_workloads` + `additional_workloads`; workloads present in the live GVC but missing from the config are skipped silently
85
88
  - `--mode=stop` returns once each workload is marked suspended; it does not wait for the workload to reach a not-ready state
@@ -128,7 +131,8 @@ cpflow copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN --
128
131
  ### `delete`
129
132
 
130
133
  - Deletes the whole app (GVC with all workloads, all volumesets and all images) or a specific workload
131
- - Also unbinds the app from the secrets policy, as long as both the identity and the policy exist (and are bound)
134
+ - Also unbinds the app from the secrets policy and any configured `shared_secret_grants` policies, as long as both the identity and each policy exist (and are bound)
135
+ - For the app-specific secrets policy, removes every permission held by the app identity; for `shared_secret_grants`, removes only `reveal`
132
136
  - Will ask for explicit user confirmation
133
137
  - Runs a pre-deletion hook before the app is deleted if `hooks.pre_deletion` is specified in the `.controlplane/controlplane.yml` file
134
138
  - If the hook exits with a non-zero code, the command will stop executing and also exit with a non-zero code
@@ -149,6 +153,7 @@ cpflow delete -a $APP_NAME -w $WORKLOAD_NAME
149
153
  - The release script is run in the context of `cpflow run` with the latest image
150
154
  - If the release script exits with a non-zero code, the command will stop executing and also exit with a non-zero code
151
155
  - If `use_digest_image_ref` is `true` in the `.controlplane/controlplane.yml` file or `--use-digest-image-ref` option is provided, deployed image's reference will include its digest
156
+ - Repairs missing `shared_secret_grants` policy bindings before running a release phase or updating workloads
152
157
 
153
158
  ```sh
154
159
  cpflow deploy-image -a $APP_NAME
@@ -310,6 +315,7 @@ cpflow maintenance -a $APP_NAME
310
315
  ### `maintenance:off`
311
316
 
312
317
  - Disables maintenance mode for an app
318
+ - Safe to re-run: if a previous run timed out after switching the domain but before stopping the maintenance workload, re-running while maintenance mode is already disabled stops the maintenance workload to finish it (so it is not a pure no-op)
313
319
  - Specify the one-off workload through `one_off_workload` in the `.controlplane/controlplane.yml` file
314
320
  - Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
315
321
  - Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
@@ -321,6 +327,7 @@ cpflow maintenance:off -a $APP_NAME
321
327
  ### `maintenance:on`
322
328
 
323
329
  - Enables maintenance mode for an app
330
+ - Safe to re-run: if a previous run timed out after switching the domain but before stopping the app workloads, re-running while maintenance mode is already enabled stops the app workloads to finish it (so it is not a pure no-op)
324
331
  - Specify the one-off workload through `one_off_workload` in the `.controlplane/controlplane.yml` file
325
332
  - Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
326
333
  - Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
@@ -461,6 +468,8 @@ timeout 300 cpflow ps:wait -a $APP_NAME
461
468
  and also overridden per job through `--cpu` and `--memory`)
462
469
  - By default, the job is stopped if it takes longer than 6 hours to finish
463
470
  (can be configured though `runner_job_timeout` in `controlplane.yml`)
471
+ - Non-interactive jobs return the Control Plane cron job status even when the job finishes before
472
+ Control Plane exposes a runner replica to attach logs to
464
473
 
465
474
  ```sh
466
475
  # Opens shell (bash by default).
@@ -513,6 +522,7 @@ cpflow run -a $APP_NAME --entrypoint /app/alternative-entrypoint.sh -- rails db:
513
522
  - Configures app to have org-level secrets with default name `"{APP_PREFIX}-secrets"`
514
523
  using org-level policy with default name `"{APP_PREFIX}-secrets-policy"` (names can be customized, see docs)
515
524
  - Creates identity for secrets if it does not exist
525
+ - Binds the app identity to any configured `shared_secret_grants` policies as part of the secrets setup flow; skipped when `--skip-secrets-setup` or `--skip-secret-access-binding` is provided, or `skip_secrets_setup` is set
516
526
  - Use `--skip-secrets-setup` to prevent the automatic setup of secrets,
517
527
  or set it through `skip_secrets_setup` in the `.controlplane/controlplane.yml` file
518
528
  - Runs a post-creation hook after the app is created if `hooks.post_creation` is specified in the `.controlplane/controlplane.yml` file
@@ -544,7 +554,7 @@ cpflow terraform import
544
554
  Regenerates the generated cpflow GitHub Actions wrappers and helper files
545
555
  from the currently installed cpflow gem. Use this after updating the
546
556
  cpflow gem so checked-in workflow wrappers move to the matching upstream
547
- release tag, for example `v5.0.3`.
557
+ release tag, for example `v5.1.0`.
548
558
 
549
559
  If the existing generated staging workflow uses a custom single staging
550
560
  branch, the command preserves it. Pass `--staging-branch BRANCH` to set or
data/docs/postgres.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Migrating Postgres database from Heroku infrastructure
2
2
 
3
+ > **Networking note.** This guide is written against a *publicly reachable* RDS instance for simplicity of the
4
+ > Bucardo migration steps. For production, you almost certainly want RDS/Aurora in private subnets reached from
5
+ > Control Plane workloads via the Cloud Wormhole agent — see
6
+ > [Connecting Control Plane workloads to a private AWS RDS/Aurora database](./rds-private-networking.md). The
7
+ > Bucardo migration here still works against a private RDS once the network path is set up.
8
+
3
9
  If you are replacing Heroku Postgres or another pre-provisioned database service, also review the
4
10
  [Control Plane PostgreSQL Template Catalog page](https://shakadocs.controlplane.com/template-catalog/templates/postgres). The
5
11
  catalog template covers a single-instance PostgreSQL workload with persistent storage, optional PgBouncer, and optional