cpflow 5.0.0 → 5.0.2

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/update-changelog.md +88 -23
  3. data/.github/actions/cpflow-resolve-review-config/action.yml +137 -0
  4. data/.github/actions/cpflow-setup-environment/action.yml +118 -0
  5. data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +26 -21
  6. data/.github/workflows/cpflow-delete-review-app.yml +21 -18
  7. data/.github/workflows/cpflow-deploy-review-app.yml +23 -19
  8. data/.github/workflows/cpflow-deploy-staging.yml +15 -11
  9. data/.github/workflows/cpflow-help-command.yml +0 -6
  10. data/.github/workflows/cpflow-promote-staging-to-production.yml +30 -5
  11. data/.github/workflows/cpflow-review-app-help.yml +1 -10
  12. data/CHANGELOG.md +32 -1
  13. data/Gemfile.lock +1 -1
  14. data/docs/ai-github-flow-prompt.md +1 -1
  15. data/docs/ci-automation.md +215 -29
  16. data/docs/commands.md +1 -1
  17. data/lib/command/ai_github_flow_prompt.rb +1 -1
  18. data/lib/command/run.rb +11 -1
  19. data/lib/command/setup_app.rb +1 -1
  20. data/lib/cpflow/version.rb +1 -1
  21. data/lib/generator_templates/Dockerfile +9 -3
  22. data/lib/generator_templates/entrypoint.sh +42 -2
  23. data/lib/generator_templates/templates/app.yml +3 -3
  24. data/lib/generator_templates/templates/postgres.yml +19 -16
  25. data/lib/generator_templates/templates/rails.yml +3 -1
  26. data/lib/generator_templates_sqlite/templates/app.yml +3 -3
  27. data/lib/generator_templates_sqlite/templates/rails.yml +3 -1
  28. data/lib/github_flow_templates/.github/cpflow-help.md +95 -79
  29. data/lib/github_flow_templates/.github/workflows/cpflow-cleanup-stale-review-apps.yml +4 -9
  30. data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +2 -9
  31. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +3 -9
  32. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +3 -8
  33. data/lib/github_flow_templates/.github/workflows/cpflow-help-command.yml +0 -9
  34. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +10 -8
  35. data/lib/github_flow_templates/.github/workflows/cpflow-review-app-help.yml +4 -10
  36. data/lib/github_flow_templates/bin/pin-cpflow-github-ref +3 -1
  37. data/lib/github_flow_templates/bin/test-cpflow-github-flow +23 -8
  38. metadata +2 -1
@@ -150,29 +150,148 @@ apps:
150
150
 
151
151
  Important points:
152
152
 
153
- - `REVIEW_APP_PREFIX` in GitHub Actions must match the review config key prefix, for example `my-app-review`.
154
153
  - `match_if_app_name_starts_with: true` is what allows a single config entry to back `my-app-review-123`, `my-app-review-456`, and cleanup commands like `cpflow cleanup-stale-apps -a my-app-review`.
154
+ - Review-app deploy, delete, and cleanup workflows infer the review app prefix from the single app entry with `match_if_app_name_starts_with: true`.
155
+ - Review-app workflows infer the staging Control Plane org from that review app entry's `cpln_org`.
155
156
  - `upstream: my-app-staging` is what lets the production promotion workflow copy the exact staging artifact.
156
157
  - If your main web workload is not named `rails`, set the optional `PRIMARY_WORKLOAD` repository variable described below.
157
158
 
158
159
  ## Required GitHub Repository Settings
159
160
 
160
- Configure these repository secrets:
161
+ For a normal generated review-app setup, configure one repository secret:
161
162
 
162
163
  - `CPLN_TOKEN_STAGING`: token for the staging Control Plane org
163
- - `CPLN_TOKEN_PRODUCTION`: token for the production Control Plane org
164
164
 
165
- Configure these repository variables:
165
+ No GitHub repository variables are required for review apps when `.controlplane/controlplane.yml`
166
+ has exactly one review app entry with `match_if_app_name_starts_with: true` and
167
+ that entry has a `cpln_org`. The inferred values come from that config file:
168
+ the review-app prefix is the app key with `match_if_app_name_starts_with: true`,
169
+ and the staging org is that app's `cpln_org` value. Set these variables only
170
+ when you need to test a fork or clone against a different Control Plane org,
171
+ choose a different review-app prefix, expose a different public workload, or
172
+ disambiguate generated review-app config:
173
+
174
+ - `CPLN_ORG_STAGING`: override the staging/review org inferred from `cpln_org`, for example `company-staging`
175
+ - `REVIEW_APP_PREFIX`: override the inferred review-app prefix; required only when multiple review app prefixes exist in `controlplane.yml`
176
+ - `PRIMARY_WORKLOAD`: override the public workload used to discover the public endpoint and do production health checks; defaults to `rails`
177
+
178
+ If `controlplane.yml` defines more than one app with
179
+ `match_if_app_name_starts_with: true`, inference intentionally fails. Set
180
+ `CPLN_ORG_STAGING` and `REVIEW_APP_PREFIX` to tell the workflow which review-app
181
+ family to manage.
182
+
183
+ For staging deploys, also configure:
166
184
 
167
185
  - `CPLN_ORG_STAGING`: staging org name, for example `company-staging`
168
- - `CPLN_ORG_PRODUCTION`: production org name, for example `company-production`
169
186
  - `STAGING_APP_NAME`: staging GVC name, for example `my-app-staging`
170
- - `PRODUCTION_APP_NAME`: production GVC name, for example `my-app-production`
171
- - `REVIEW_APP_PREFIX`: review-app prefix, for example `my-app-review`
172
187
  - `STAGING_APP_BRANCH`: optional branch that auto-deploys staging. If you use a custom branch, either pass it to `cpflow generate-github-actions --staging-branch BRANCH` during generation or edit `cpflow-deploy-staging.yml` so its `on.push.branches` list includes the same branch.
173
- - `PRIMARY_WORKLOAD`: optional workload name used to discover the public endpoint and do production health checks; defaults to `rails`
174
- - `DOCKER_BUILD_EXTRA_ARGS`: optional newline-delimited single `docker build` tokens passed through to `cpflow build-image`, for example `--build-arg=FOO=bar` or `--secret=id=npmrc,src=.npmrc`
175
- - `DOCKER_BUILD_SSH_KNOWN_HOSTS`: optional multi-line `known_hosts` content used with `DOCKER_BUILD_SSH_KEY` when the build needs SSH access to hosts other than GitHub.com
188
+
189
+ For production promotion, also configure:
190
+
191
+ - a GitHub Environment named `production`
192
+ - required reviewers on that environment, limited to the people or team allowed to promote production
193
+ - "Prevent self-review" on that environment, so the person who starts the promotion cannot approve it
194
+ - optionally disable administrator bypass and restrict deployment branches/tags to your protected release branch
195
+ - `CPLN_TOKEN_PRODUCTION` as an environment secret on `production`, not as a repository or organization secret
196
+ - `CPLN_ORG_PRODUCTION` as a production environment variable, for example `company-production`
197
+ - `PRODUCTION_APP_NAME` as a production environment variable, for example `my-app-production`
198
+
199
+ 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.
216
+
217
+ ## First-Time Control Plane Bootstrap
218
+
219
+ GitHub settings only give the workflows permission to act. They do not create
220
+ the persistent staging or production GVCs for you on the first merge.
221
+
222
+ Before the first staging deploy, bootstrap the staging app once:
223
+
224
+ ```sh
225
+ cpflow setup-app -a my-app-staging --org my-org-staging --skip-post-creation-hook
226
+ ```
227
+
228
+ `setup-app` reads the `setup_app_templates` list from
229
+ `.controlplane/controlplane.yml`. It creates the persistent staging GVC,
230
+ workloads, app identity, app secret dictionary, app secret policy, and policy
231
+ binding that grants the app identity `reveal` permission on that dictionary.
232
+ Use `--skip-post-creation-hook` for first-time bootstrap so a database hook does
233
+ not try to run before the first image exists.
234
+
235
+ After the persistent app exists, use `apply-template` for later template
236
+ updates. Adjust the template list to match your repo, such as adding `worker`,
237
+ `sidekiq`, `renderer`, `redis`, or other templates present under
238
+ `.controlplane/templates`:
239
+
240
+ ```sh
241
+ cpflow apply-template app postgres rails -a my-app-staging --org my-org-staging --yes --add-app-identity
242
+ ```
243
+
244
+ If you use `apply-template` to create or repair an existing app, also confirm
245
+ that the app identity has `reveal` permission on the app secret policy. Without
246
+ that binding, workloads that reference `cpln://secret/<app-secrets>.*` stay
247
+ paused until the policy is fixed.
248
+
249
+ Before the first production promotion, run the same kind of bootstrap for the
250
+ production app in the production org:
251
+
252
+ ```sh
253
+ cpflow setup-app -a my-app-production --org my-org-production --skip-post-creation-hook
254
+ ```
255
+
256
+ Use production-only runtime secrets and values for the production app. The
257
+ protected GitHub Environment controls who can run the promotion workflow, but
258
+ the production app resources still need to exist before the first promotion.
259
+
260
+ Review apps are different: the generated `+review-app-deploy` workflow creates
261
+ temporary PR apps as needed, including the identity and secret policy binding.
262
+ You still need the shared review-app runtime secret values described by your
263
+ templates, and the staging token must have access to create and update
264
+ review-app GVCs, workloads, images, identities, policies, and secrets in the
265
+ staging org.
266
+
267
+ ### Production Promotion Safety
268
+
269
+ `CPLN_TOKEN_PRODUCTION` can change live production workloads, images, releases,
270
+ and rollback state. Treat it differently from review-app and staging credentials.
271
+ The standard path is:
272
+
273
+ 1. Create the `production` GitHub Environment before setting the production token.
274
+ 2. Add a small required-reviewer list or team with production authority.
275
+ 3. Enable prevent self-review.
276
+ 4. Disable administrator bypass if your org policy requires two-person control.
277
+ 5. Restrict deployable branches or tags to the protected release branch.
278
+ 6. Store `CPLN_TOKEN_PRODUCTION` only as a `production` environment secret.
279
+ 7. Store `CPLN_ORG_PRODUCTION` and `PRODUCTION_APP_NAME` as `production`
280
+ environment variables, or as repository variables only when those names are
281
+ intentionally non-sensitive.
282
+
283
+ 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),
288
+ [deployment protection rules](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments),
289
+ and [reusable workflow limitations](https://docs.github.com/en/actions/reference/reusable-workflows-reference#supported-keywords-for-jobs-that-call-a-reusable-workflow).
290
+
291
+ Application runtime secrets such as `SECRET_KEY_BASE`, API keys, or private
292
+ license keys belong in Control Plane secret dictionaries referenced by
293
+ `controlplane.yml`. They are not GitHub repository variables unless your
294
+ Docker build itself needs them.
176
295
 
177
296
  Recommended org layout:
178
297
 
@@ -183,6 +302,17 @@ Optional repository secret for private dependency builds:
183
302
 
184
303
  - `DOCKER_BUILD_SSH_KEY`: private SSH key used when the Dockerfile needs `RUN --mount=type=ssh` to fetch private GitHub dependencies during image build
185
304
 
305
+ Optional repository variables for private dependency builds:
306
+
307
+ - `DOCKER_BUILD_EXTRA_ARGS`: optional newline-delimited single `docker build` tokens passed through to `cpflow build-image`, for example `--build-arg=FOO=bar` or `--secret=id=npmrc,src=.npmrc`
308
+ - `DOCKER_BUILD_SSH_KNOWN_HOSTS`: optional multi-line `known_hosts` content used with `DOCKER_BUILD_SSH_KEY` when the build needs SSH access to hosts other than GitHub.com
309
+
310
+ Advanced optional repository variables:
311
+
312
+ - `REVIEW_APP_DEPLOYING_ICON_URL`: custom image URL for the animated icon in review-app PR comments. Ignore this for the standard setup; it is cosmetic only.
313
+ - `CPLN_CLI_VERSION`: pin only when Control Plane CLI compatibility requires it.
314
+ - `CPFLOW_VERSION`: pin a published RubyGems version only when intentionally overriding the default build-from-ref behavior.
315
+
186
316
  ## Docker Builds with Private Dependencies
187
317
 
188
318
  Some apps need extra Docker build configuration before the generated workflows are turnkey. Common examples are:
@@ -212,6 +342,9 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
212
342
  `cpflow-review-app-help.yml`
213
343
 
214
344
  - Posts a quick reference when a pull request opens, including on fork-based PRs.
345
+ - This is an onboarding comment only; it does not checkout PR code or receive
346
+ Control Plane secrets. Remove this wrapper if a repo does not want automatic
347
+ review-app command help on every new PR.
215
348
 
216
349
  `cpflow-help-command.yml`
217
350
 
@@ -242,6 +375,7 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
242
375
  `cpflow-promote-staging-to-production.yml`
243
376
 
244
377
  - Manually promotes the staging artifact to production with a confirmation input.
378
+ - Runs the production job in the `production` GitHub Environment, so configured reviewers approve the job before production environment secrets are available.
245
379
  - Verifies that production has the env var names staging expects.
246
380
  - Runs a health check against `PRIMARY_WORKLOAD`.
247
381
  - Attempts a rollback of every configured application workload if the new production image does not come up healthy.
@@ -252,6 +386,25 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
252
386
  - Runs nightly and on demand.
253
387
  - Deletes stale review apps using `cpflow cleanup-stale-apps`.
254
388
 
389
+ Generated review app names use `<review-app-prefix>-<PR number>`, for example
390
+ `my-app-review-123`. If an existing repository is migrating from older local
391
+ workflow glue that created names like `<review-app-prefix>-pr-123`, delete those
392
+ old review apps manually after merging the generated flow; the cleanup workflow
393
+ only targets the current prefix convention.
394
+
395
+ To inventory old-prefix review apps before cleanup, run:
396
+
397
+ ```sh
398
+ cpln gvc query --org <staging-org> -o yaml --prop name~<review-app-prefix>-pr-
399
+ ```
400
+
401
+ The PR-open help workflow posts the short command reference whenever the
402
+ generated wrapper exists. That is intentional for configured demo repos. Forks
403
+ or clones that copy the workflow before configuring Control Plane can remove
404
+ `.github/workflows/cpflow-review-app-help.yml` or uncomment and adapt the
405
+ wrapper-level `if:` guard shown in that file, for example
406
+ `vars.REVIEW_APP_PREFIX != '' || vars.CPLN_ORG_STAGING != ''`.
407
+
255
408
  ## Upstream Reusable Workflows
256
409
 
257
410
  The generated workflows are intentionally small wrappers. The deployment logic,
@@ -271,18 +424,26 @@ GitHub ref:
271
424
 
272
425
  ```yaml
273
426
  uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-review-app.yml@<ref>
274
- with:
275
- control_plane_flow_ref: <ref>
276
427
  ```
277
428
 
278
- Those two pins must stay in sync:
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`.
434
+
435
+ There are two locks, and they protect different things:
279
436
 
280
- - `uses: ...@<ref>` chooses the upstream reusable workflow file.
281
- - `control_plane_flow_ref: <ref>` tells that reusable workflow which
282
- `control-plane-flow` checkout to use for shared composite actions and, when
283
- `CPFLOW_VERSION` is empty, for building and installing the `cpflow` gem.
437
+ - The GitHub ref locks the reusable workflow and composite action code that
438
+ GitHub runs.
439
+ - The RubyGems version locks the `cpflow` CLI/runtime code only when you install
440
+ or run that gem. It does not make GitHub load reusable workflow YAML from the
441
+ gem.
284
442
 
285
- The stable release path is still gem-driven:
443
+ That means a downstream app cannot rely on the gem alone for GitHub Actions
444
+ behavior. The safe stable path is still gem-driven for generation, but
445
+ developers must commit generated wrappers that reference the matching upstream
446
+ release tag:
286
447
 
287
448
  1. Publish a `cpflow` gem.
288
449
  2. Install or bundle that released gem in the downstream project.
@@ -297,9 +458,26 @@ feature-branch refs.
297
458
  `CPFLOW_VERSION` is a runtime override. If a downstream repository sets the
298
459
  `CPFLOW_VERSION` variable, the setup action runs `gem install cpflow -v
299
460
  <version>`. If it is unset, the setup action builds `cpflow` from the checked-out
300
- `control-plane-flow` ref. For normal releases, leave `CPFLOW_VERSION` unset while
301
- pinning the wrappers to the matching `v<version>` tag, or set
302
- `CPFLOW_VERSION` to that same released gem version without the leading `v`.
461
+ `control-plane-flow` source selected by the reusable workflow's own SHA. For
462
+ normal releases, leave `CPFLOW_VERSION` unset while pinning the wrappers to the
463
+ matching `v<version>` tag, or set `CPFLOW_VERSION` to that same released gem
464
+ version without the leading `v`.
465
+ When setting `CPFLOW_VERSION`, use RubyGems version syntax, for example
466
+ `5.0.0` or `5.0.0.rc.1`; do not use `v5.0.0` or dash-separated prereleases
467
+ because the value is passed directly to `gem install cpflow -v`.
468
+
469
+ The setup action fails early when `CPFLOW_VERSION` and the reusable workflow tag
470
+ are out of sync. `CPFLOW_VERSION=5.0.0` is accepted only when the wrapper uses a
471
+ release tag such as `@v5.0.0` (or GitHub resolves it to `refs/tags/v5.0.0`).
472
+ Release tags may use dot- or dash-separated prerelease suffixes, such as
473
+ `v5.0.0.rc.1` or `v5.0.0-rc.1`; the gem version should still use dots. The
474
+ action also checks the remote `control-plane-flow` tag and the checked-out
475
+ action commit, so a moving branch named like `v5.0.0` cannot be used with
476
+ `CPFLOW_VERSION=5.0.0`. That tag check uses outbound HTTPS to GitHub; restricted
477
+ runners that cannot reach GitHub should leave `CPFLOW_VERSION` unset and build
478
+ `cpflow` from the checked-out ref instead. When testing an unreleased upstream
479
+ commit SHA, leave `CPFLOW_VERSION` unset so the workflow builds `cpflow` from the
480
+ same source that supplies the reusable workflow and composite actions.
303
481
 
304
482
  ## Testing Unreleased Upstream Changes Downstream
305
483
 
@@ -319,7 +497,10 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
319
497
  local experiments that should not be committed.
320
498
 
321
499
  3. Keep `CPFLOW_VERSION` unset so the workflow builds `cpflow` from the same
322
- upstream SHA that supplies the reusable workflow and composite actions.
500
+ upstream SHA that supplies the reusable workflow and composite actions. If
501
+ `CPFLOW_VERSION` is set while the wrapper is pinned to a SHA, the setup
502
+ action fails before deployment because the gem and action code cannot be
503
+ proven to match.
323
504
  4. Run:
324
505
 
325
506
  ```sh
@@ -343,8 +524,10 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
343
524
  6. Verify the deploy logs show the expected upstream commit SHA, the setup step
344
525
  prints the expected `cpflow` source/version, and the review app URL returns
345
526
  HTTP 200.
346
- 7. After the upstream PR merges and a gem is released, regenerate or repin the
347
- downstream wrappers to the release tag.
527
+ 7. After the upstream PR merges and a gem is released, regenerate the downstream
528
+ wrappers from that released gem and commit the release tag. Use
529
+ `bin/pin-cpflow-github-ref vX.Y.Z` only for a ref-only update when the
530
+ generated templates are already current.
348
531
 
349
532
  This tests the real reusable workflow, shared composite actions, and source-built
350
533
  `cpflow` gem from one immutable upstream commit. It avoids merging upstream blind
@@ -360,9 +543,12 @@ bin/test-cpflow-github-flow
360
543
 
361
544
  The helper runs `cpflow github-flow-readiness`, parses generated workflow YAML,
362
545
  checks composite action metadata for literal GitHub expressions in descriptions,
363
- checks that all generated wrappers use one upstream ref consistently, requires
364
- secret-inheriting reusable workflows to pass `control_plane_flow_ref`, and runs
365
- `actionlint -ignore "SC2129" .github/workflows/cpflow-*.yml`.
546
+ checks that all generated wrappers use one upstream ref consistently, rejects
547
+ broad `secrets: inherit` usage in generated cpflow wrappers, rejects obsolete
548
+ `control_plane_flow_ref` wrapper inputs, and runs
549
+ `actionlint` against `.github/workflows/cpflow-*.yml`. Its `actionlint` command
550
+ keeps the existing shellcheck ignore and also ignores stale local `actionlint`
551
+ false positives for GitHub's newer reusable-workflow `job.workflow_*` fields.
366
552
 
367
553
  ## Applying This to React on Rails Demo Apps
368
554
 
@@ -397,7 +583,7 @@ In practice, porting the flow into a demo app usually follows five phases.
397
583
 
398
584
  **Wire up GitHub secrets, variables, and private builds:**
399
585
 
400
- 11. Make sure the repo variables and secrets line up with the configured app names.
586
+ 11. Make sure the repo variables and secrets line up with the configured app names. For production promotion, store `CPLN_TOKEN_PRODUCTION` only on a protected `production` GitHub Environment with required reviewers.
401
587
  12. If the Dockerfile pulls private dependencies over SSH, configure `DOCKER_BUILD_SSH_KEY`, add `DOCKER_BUILD_SSH_KNOWN_HOSTS` when the host is not GitHub.com, and validate that the image can build with `RUN --mount=type=ssh`.
402
588
 
403
589
  **Validate and push:**
@@ -417,7 +603,7 @@ current prompt with that repo's default app prefix already filled in.
417
603
  Short version:
418
604
 
419
605
  ```text
420
- 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, with published package versions and a production Dockerfile that can really build the app. Stop and report blockers for unpublished packages, inaccessible private dependencies, legacy toolchains, or missing production build paths instead of generating workflows blindly. Then run `cpflow generate` if `.controlplane/` is missing, run `cpflow generate-github-actions`, adapt the generated scaffold to the real workloads, document the required GitHub secrets and variables, validate the real build path locally, push the branch, and check the GitHub Actions results.
606
+ 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, with published package versions and a production Dockerfile that can really build the app. Stop and report blockers for unpublished packages, inaccessible private dependencies, legacy toolchains, or missing production build paths instead of generating workflows blindly. Then run `cpflow generate` if `.controlplane/` is missing, run `cpflow generate-github-actions`, adapt the generated scaffold to the real workloads, document the required GitHub secrets and variables, validate the real build path locally, push the branch, and check the GitHub Actions results. Keep production promotion safe by documenting `CPLN_TOKEN_PRODUCTION` as a protected `production` GitHub Environment secret, not a repository or organization secret.
421
607
  ```
422
608
 
423
609
  Expand that prompt with app-specific requirements before editing files:
data/docs/commands.md CHANGED
@@ -503,7 +503,7 @@ cpflow run -a $APP_NAME --entrypoint /app/alternative-entrypoint.sh -- rails db:
503
503
 
504
504
  - Creates an app and all its workloads
505
505
  - Specify the templates for the app and workloads through `setup_app_templates` in the `.controlplane/controlplane.yml` file
506
- - This should only be used for temporary apps like review apps, never for persistent apps like production or staging (to update workloads for those, use 'cpflow apply-template' instead)
506
+ - Use this for temporary apps like review apps and for first-time bootstrap of persistent staging or production apps; after a persistent app exists, use 'cpflow apply-template' for template updates
507
507
  - Configures app to have org-level secrets with default name `"{APP_PREFIX}-secrets"`
508
508
  using org-level policy with default name `"{APP_PREFIX}-secrets-policy"` (names can be customized, see docs)
509
509
  - Creates identity for secrets if it does not exist
@@ -32,7 +32,7 @@ module Command
32
32
  <<~PROMPT
33
33
  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.
34
34
 
35
- If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default (`#{inferred_app_prefix}`) 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.
35
+ If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default (`#{inferred_app_prefix}`) 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. 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.
36
36
 
37
37
  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`.
38
38
 
data/lib/command/run.rb CHANGED
@@ -197,7 +197,7 @@ module Command
197
197
  spec = nil
198
198
 
199
199
  step("Checking if runner workload '#{runner_workload}' needs to be updated") do # rubocop:disable Metrics/BlockLength
200
- _, original_container_spec = base_workload_specs(original_workload)
200
+ original_spec, original_container_spec = base_workload_specs(original_workload)
201
201
  spec, container_spec = base_workload_specs(runner_workload)
202
202
 
203
203
  # Keep ENV synced between original and runner workloads
@@ -208,6 +208,16 @@ module Command
208
208
  should_update = true
209
209
  end
210
210
 
211
+ # Keep the app identity in sync so runner jobs can resolve GVC-level secrets.
212
+ if spec["identityLink"] != original_spec["identityLink"]
213
+ if original_spec["identityLink"]
214
+ spec["identityLink"] = original_spec["identityLink"]
215
+ else
216
+ spec.delete("identityLink")
217
+ end
218
+ should_update = true
219
+ end
220
+
211
221
  if container_spec["image"] != default_image
212
222
  container_spec["image"] = default_image
213
223
  should_update = true
@@ -13,7 +13,7 @@ module Command
13
13
  LONG_DESCRIPTION = <<~DESC
14
14
  - Creates an app and all its workloads
15
15
  - Specify the templates for the app and workloads through `setup_app_templates` in the `.controlplane/controlplane.yml` file
16
- - This should only be used for temporary apps like review apps, never for persistent apps like production or staging (to update workloads for those, use 'cpflow apply-template' instead)
16
+ - Use this for temporary apps like review apps and for first-time bootstrap of persistent staging or production apps; after a persistent app exists, use 'cpflow apply-template' for template updates
17
17
  - Configures app to have org-level secrets with default name `"{APP_PREFIX}-secrets"`
18
18
  using org-level policy with default name `"{APP_PREFIX}-secrets-policy"` (names can be customized, see docs)
19
19
  - Creates identity for secrets if it does not exist
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpflow
4
- VERSION = "5.0.0"
4
+ VERSION = "5.0.2"
5
5
  MIN_CPLN_VERSION = "3.1.0"
6
6
  end
@@ -17,12 +17,17 @@ WORKDIR /app
17
17
  # rely on ExecJS in production. Narrowed to just what the node stage actually
18
18
  # ships under /usr/local so we don't drag in unused Debian libs from that image.
19
19
  COPY --from=node /usr/local/bin/node /usr/local/bin/node
20
- COPY --from=node /usr/local/bin/npm /usr/local/bin/npm
21
- COPY --from=node /usr/local/bin/npx /usr/local/bin/npx
22
- COPY --from=node /usr/local/bin/corepack /usr/local/bin/corepack
23
20
  COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
24
21
  COPY --from=node /usr/local/include/node /usr/local/include/node
25
22
 
23
+ RUN ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
24
+ ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx && \
25
+ ln -sf ../lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack && \
26
+ chmod +x /usr/local/lib/node_modules/npm/bin/npm-cli.js \
27
+ /usr/local/lib/node_modules/npm/bin/npx-cli.js \
28
+ /usr/local/lib/node_modules/corepack/dist/corepack.js && \
29
+ node --version && npm --version && corepack --version
30
+
26
31
  # Expose Corepack-managed shims so later build steps can call yarn/pnpm
27
32
  # directly during asset precompilation hooks.
28
33
  RUN printf '%s\n' '#!/bin/sh' 'exec corepack yarn "$@"' > /usr/bin/yarn && \
@@ -78,6 +83,7 @@ RUN rails assets:precompile
78
83
 
79
84
  # add entrypoint
80
85
  COPY .controlplane/entrypoint.sh ./
86
+ RUN chmod +x /app/entrypoint.sh
81
87
  ENTRYPOINT ["/app/entrypoint.sh"]
82
88
 
83
89
  CMD ["rails", "s"]
@@ -1,8 +1,48 @@
1
1
  #!/bin/sh
2
+ set -e
2
3
  # Runs before the main command
3
4
 
4
- echo " -- Preparing database"
5
- rails db:prepare
5
+ is_rails_server_command() {
6
+ if [ "${1:-}" = "env" ]; then
7
+ shift
8
+ while [ "$#" -gt 0 ]; do
9
+ case "${1}" in
10
+ *=*) shift ;;
11
+ --) shift; break ;;
12
+ -*) return 1 ;;
13
+ *) break ;;
14
+ esac
15
+ done
16
+ fi
17
+
18
+ if [ "${1:-}" = "bundle" ] && [ "${2:-}" = "exec" ]; then
19
+ shift 2
20
+ fi
21
+
22
+ # Matches generated, optionally env-prefixed, flag-free Thruster invocations.
23
+ # Hand-edited commands with env flags or Thruster flags before rails skip
24
+ # generated DB prep.
25
+ if [ "${1:-}" = "thrust" ] || [ "${1:-}" = "bin/thrust" ] || [ "${1:-}" = "./bin/thrust" ]; then
26
+ shift
27
+ fi
28
+
29
+ # Thruster may be wrapped with its own `bundle exec`, and the Rails command
30
+ # it proxies may also be wrapped with `bundle exec`.
31
+ if [ "${1:-}" = "bundle" ] && [ "${2:-}" = "exec" ]; then
32
+ shift 2
33
+ fi
34
+
35
+ { [ "${1:-}" = "rails" ] || [ "${1:-}" = "bin/rails" ] || [ "${1:-}" = "./bin/rails" ]; } &&
36
+ { [ "${2:-}" = "server" ] || [ "${2:-}" = "s" ]; }
37
+ }
38
+
39
+ # Match generated Rails server commands; workers and renderers skip DB prep.
40
+ # Generated Dockerfiles use WORKDIR /app; adjust this path if your hand-edited
41
+ # image runs the entrypoint from a different working directory.
42
+ if is_rails_server_command "$@"; then
43
+ echo " -- Preparing database"
44
+ ./bin/rails db:prepare
45
+ fi
6
46
 
7
47
  echo " -- Finishing entrypoint.sh, executing command"
8
48
  exec "$@"
@@ -1,6 +1,6 @@
1
1
  # Template setup of the GVC, roughly corresponding to a Heroku app
2
2
  kind: gvc
3
- name: {{APP_NAME}}
3
+ name: "{{APP_NAME}}"
4
4
  spec:
5
5
  env:
6
6
  - name: DATABASE_URL
@@ -12,7 +12,7 @@ spec:
12
12
  - name: RAILS_SERVE_STATIC_FILES
13
13
  value: "true"
14
14
  - name: SECRET_KEY_BASE
15
- value: cpln://secret/{{APP_SECRETS}}.SECRET_KEY_BASE
15
+ value: "cpln://secret/{{APP_SECRETS}}.SECRET_KEY_BASE"
16
16
  staticPlacement:
17
17
  locationLinks:
18
- - {{APP_LOCATION_LINK}}
18
+ - "{{APP_LOCATION_LINK}}"
@@ -2,8 +2,8 @@
2
2
  # https://github.com/controlplane-com/examples/blob/main/examples/postgres/manifest.yaml
3
3
 
4
4
  kind: volumeset
5
- name: postgres-poc-vs
6
- description: postgres-poc-vs
5
+ name: "{{APP_NAME}}-pg-vs"
6
+ description: "{{APP_NAME}}-pg-vs"
7
7
  spec:
8
8
  autoscaling:
9
9
  maxCapacity: 1000
@@ -18,7 +18,7 @@ spec:
18
18
 
19
19
  ---
20
20
  kind: secret
21
- name: postgres-poc-credentials
21
+ name: "{{APP_NAME}}-pg"
22
22
  description: ''
23
23
  type: dictionary
24
24
  data:
@@ -27,7 +27,7 @@ data:
27
27
 
28
28
  ---
29
29
  kind: secret
30
- name: postgres-poc-entrypoint-script
30
+ name: "{{APP_NAME}}-pg-script"
31
31
  type: opaque
32
32
  data:
33
33
  encoding: base64
@@ -92,13 +92,13 @@ data:
92
92
 
93
93
  ---
94
94
  kind: identity
95
- name: postgres-poc-identity
96
- description: postgres-poc-identity
95
+ name: "{{APP_NAME}}-pg-identity"
96
+ description: "{{APP_NAME}}-pg-identity"
97
97
 
98
98
  ---
99
99
  kind: policy
100
- name: postgres-poc-access
101
- description: postgres-poc-access
100
+ name: "{{APP_NAME}}-pg-access"
101
+ description: "{{APP_NAME}}-pg-access"
102
102
  bindings:
103
103
  - permissions:
104
104
  - reveal
@@ -106,11 +106,14 @@ bindings:
106
106
  # - use
107
107
  # - view
108
108
  principalLinks:
109
- - //gvc/{{APP_NAME}}/identity/postgres-poc-identity
109
+ - "//gvc/{{APP_NAME}}/identity/{{APP_NAME}}-pg-identity"
110
+ # cpflow apply-template replaces {{APP_IDENTITY_LINK}} with the full app workload identity link.
111
+ # Example: //gvc/{{APP_NAME}}/identity/{{APP_NAME}}-identity.
112
+ - "{{APP_IDENTITY_LINK}}"
110
113
  targetKind: secret
111
114
  targetLinks:
112
- - //secret/postgres-poc-credentials
113
- - //secret/postgres-poc-entrypoint-script
115
+ - "//secret/{{APP_NAME}}-pg"
116
+ - "//secret/{{APP_NAME}}-pg-script"
114
117
 
115
118
  ---
116
119
  kind: workload
@@ -130,9 +133,9 @@ spec:
130
133
  - name: PGDATA #The location postgres stores the db. This can be anything other than /var/lib/postgresql/data, but it must be inside the mount point for the volume set
131
134
  value: "/var/lib/postgresql/data/pg_data"
132
135
  - name: POSTGRES_PASSWORD #The password for the default user
133
- value: cpln://secret/postgres-poc-credentials.password
136
+ value: "cpln://secret/{{APP_NAME}}-pg.password"
134
137
  - name: POSTGRES_USER #The name of the default user
135
- value: cpln://secret/postgres-poc-credentials.username
138
+ value: "cpln://secret/{{APP_NAME}}-pg.username"
136
139
  name: postgres
137
140
  image: postgres:15
138
141
  command: /bin/bash
@@ -146,10 +149,10 @@ spec:
146
149
  - number: 5432
147
150
  protocol: tcp
148
151
  volumes:
149
- - uri: cpln://volumeset/postgres-poc-vs
152
+ - uri: "cpln://volumeset/{{APP_NAME}}-pg-vs"
150
153
  path: "/var/lib/postgresql/data"
151
154
  # Make the ENV value for the entry script a file
152
- - uri: cpln://secret/postgres-poc-entrypoint-script
155
+ - uri: "cpln://secret/{{APP_NAME}}-pg-script"
153
156
  path: "/usr/local/bin/cpln-entrypoint.sh"
154
157
  inheritEnv: false
155
158
  livenessProbe:
@@ -160,7 +163,7 @@ spec:
160
163
  tcpSocket:
161
164
  port: 5432
162
165
  failureThreshold: 1
163
- identityLink: //identity/postgres-poc-identity
166
+ identityLink: "//identity/{{APP_NAME}}-pg-identity"
164
167
  defaultOptions:
165
168
  capacityAI: false
166
169
  autoscaling:
@@ -8,7 +8,7 @@ spec:
8
8
  - name: rails
9
9
  cpu: 300m
10
10
  inheritEnv: true
11
- image: {{APP_IMAGE_LINK}}
11
+ image: "{{APP_IMAGE_LINK}}"
12
12
  memory: 512Mi
13
13
  ports:
14
14
  - number: 3000
@@ -29,3 +29,5 @@ spec:
29
29
  - 0.0.0.0/0
30
30
  outboundAllowCIDR:
31
31
  - 0.0.0.0/0
32
+ # cpflow apply-template replaces {{APP_IDENTITY_LINK}} with the app workload identity link.
33
+ identityLink: "{{APP_IDENTITY_LINK}}"
@@ -1,5 +1,5 @@
1
1
  kind: gvc
2
- name: {{APP_NAME}}
2
+ name: "{{APP_NAME}}"
3
3
  spec:
4
4
  env:
5
5
  - name: RAILS_ENV
@@ -9,7 +9,7 @@ spec:
9
9
  - name: RAILS_SERVE_STATIC_FILES
10
10
  value: "true"
11
11
  - name: SECRET_KEY_BASE
12
- value: cpln://secret/{{APP_SECRETS}}.SECRET_KEY_BASE
12
+ value: "cpln://secret/{{APP_SECRETS}}.SECRET_KEY_BASE"
13
13
  staticPlacement:
14
14
  locationLinks:
15
- - {{APP_LOCATION_LINK}}
15
+ - "{{APP_LOCATION_LINK}}"