cpflow 5.1.1 → 5.3.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.
- checksums.yaml +4 -4
- data/.agents/agent-workflow.yml +26 -0
- data/.agents/bin/README.md +20 -0
- data/.agents/bin/docs +5 -0
- data/.agents/bin/lint +5 -0
- data/.agents/bin/setup +5 -0
- data/.agents/bin/test +5 -0
- data/.agents/bin/validate +5 -0
- data/.agents/trusted-github-actors.yml +32 -0
- data/.agents/workflows/ai-rollout-e2e-test.md +166 -0
- data/.github/actions/cpflow-setup-environment/action.yml +1 -1
- data/.github/actions/cpflow-wait-for-health/action.yml +87 -15
- data/.github/pull_request_template.md +18 -0
- data/.github/workflows/claude-code-review.yml +2 -0
- data/.github/workflows/claude.yml +94 -1
- data/.github/workflows/cpflow-delete-review-app.yml +621 -33
- data/.github/workflows/cpflow-deploy-review-app.yml +656 -21
- data/.github/workflows/cpflow-review-app-help.yml +5 -13
- data/.github/workflows/rspec-shared.yml +10 -3
- data/.github/workflows/rspec-specific.yml +1 -0
- data/.github/workflows/rspec.yml +58 -1
- data/AGENTS.md +14 -0
- data/CHANGELOG.md +54 -1
- data/CLAUDE.md +3 -0
- data/CONTRIBUTING.md +15 -3
- data/Gemfile.lock +1 -1
- data/README.md +21 -7
- data/docs/ai-github-flow-prompt.md +18 -16
- data/docs/ci-automation.md +239 -27
- data/docs/commands.md +30 -2
- data/docs/grafana-opentelemetry.md +699 -0
- data/docs/secrets-and-env-values.md +37 -2
- data/docs/sidebars.ts +70 -0
- data/docs/telemetry/application-instrumentation.md +161 -0
- data/docs/telemetry/collector.md +297 -0
- data/docs/telemetry/index.md +152 -0
- data/docs/telemetry/pipelines.md +98 -0
- data/docs/telemetry/review-apps.md +55 -0
- data/docs/telemetry/troubleshooting.md +92 -0
- data/docs/terraform/example/.controlplane/controlplane.yml +0 -1
- data/docs/terraform/overview.md +11 -0
- data/docs/tips.md +475 -28
- data/examples/controlplane.yml +2 -0
- data/lib/command/ai_github_flow_prompt.rb +2 -2
- data/lib/command/apply_template.rb +104 -2
- data/lib/command/base.rb +69 -5
- data/lib/command/deploy_image.rb +93 -7
- data/lib/command/promote_app_from_upstream.rb +1 -0
- data/lib/command/ps_wait.rb +2 -10
- data/lib/command/run.rb +133 -10
- data/lib/command/setup_app.rb +10 -5
- data/lib/core/config.rb +94 -0
- data/lib/core/controlplane.rb +38 -5
- data/lib/core/controlplane_api.rb +8 -0
- data/lib/core/controlplane_api_direct.rb +257 -63
- data/lib/core/doctor_service.rb +44 -3
- data/lib/core/shell.rb +9 -2
- data/lib/core/template_parser.rb +43 -9
- data/lib/cpflow/version.rb +1 -1
- data/lib/generator_templates/controlplane.yml +1 -2
- data/lib/github_flow_templates/.github/cpflow-help.md +34 -10
- data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +10 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +9 -0
- metadata +22 -2
data/docs/ci-automation.md
CHANGED
|
@@ -10,6 +10,13 @@ The goal is to bring the Heroku Flow model into any `cpflow` project:
|
|
|
10
10
|
4. Promote the already-built staging artifact to production from the Actions tab.
|
|
11
11
|
5. Let a nightly workflow clean up stale review apps.
|
|
12
12
|
|
|
13
|
+
For public repositories, review apps are intentionally limited to branches in the base repository. Fork pull requests can
|
|
14
|
+
receive help comments, but generated deploy workflows skip fork heads because review-app deployment builds Docker images
|
|
15
|
+
with repository secrets. The skip is enforced by complementary guards on different trigger axes; see
|
|
16
|
+
[Review app security for repositories with external contributors](#review-app-security-for-repositories-with-external-contributors)
|
|
17
|
+
before customizing these workflows. If a forked change needs a review app, a maintainer should first review the code and
|
|
18
|
+
move the change into a trusted branch in the base repository.
|
|
19
|
+
|
|
13
20
|
## Quick Start
|
|
14
21
|
|
|
15
22
|
End-to-end rollout in one view:
|
|
@@ -17,9 +24,11 @@ End-to-end rollout in one view:
|
|
|
17
24
|
1. `cpflow github-flow-readiness` — exits non-zero if the repo is not ready to deploy.
|
|
18
25
|
2. `cpflow generate` — creates `.controlplane/` if missing.
|
|
19
26
|
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
|
-
4. Configure the GitHub
|
|
27
|
+
4. Configure the [GitHub Actions secrets and variables](#github-actions-secrets-and-variables) the workflows expect.
|
|
21
28
|
5. Push the branch, then comment `+review-app-deploy` on a PR to spin up a review environment.
|
|
22
29
|
|
|
30
|
+
AI rollout: copy the [AI rollout prompt](./ai-github-flow-prompt.md) when you want an agent to run this setup. The prompt works whether `cpflow` is already installed or the agent needs to install it first. If `cpflow` is already available in the target repo, `cpflow ai-github-flow-prompt` prints the same prompt with the default app prefix filled in.
|
|
31
|
+
|
|
23
32
|
See [Bootstrap a Project](#bootstrap-a-project) for command details, [Repo Readiness Checklist](#repo-readiness-checklist) for what "ready" means, and [AI Playbook](#ai-playbook) to run the rollout through an agent.
|
|
24
33
|
|
|
25
34
|
## Bootstrap a Project
|
|
@@ -137,7 +146,8 @@ apps:
|
|
|
137
146
|
match_if_app_name_starts_with: true
|
|
138
147
|
hooks:
|
|
139
148
|
post_creation: bundle exec rails db:prepare
|
|
140
|
-
pre_deletion:
|
|
149
|
+
# pre_deletion intentionally omitted for shared databases: `cpflow delete` runs it before removing the workloads,
|
|
150
|
+
# so live connections can block the drop. Prefer admin-side cleanup. See docs/tips.md ("Share One Control Plane Postgres").
|
|
141
151
|
|
|
142
152
|
my-app-production:
|
|
143
153
|
<<: *common
|
|
@@ -155,14 +165,23 @@ Important points:
|
|
|
155
165
|
- Review-app workflows infer the staging Control Plane org from that review app entry's `cpln_org`.
|
|
156
166
|
- `upstream: my-app-staging` is what lets the production promotion workflow copy the exact staging artifact.
|
|
157
167
|
- If your main web workload is not named `rails`, set the optional `PRIMARY_WORKLOAD` repository variable described below.
|
|
168
|
+
- For public demos, starter staging apps, and long-lived review apps, prefer `capacityAI: true` with one warm replica
|
|
169
|
+
and the workload's autoscaling metric disabled, so Control Plane can right-size CPU and memory allocation at that
|
|
170
|
+
fixed replica count. See
|
|
171
|
+
[Enable Capacity AI for Demo and Starter Staging Apps](tips.md#enable-capacity-ai-for-demo-and-starter-staging-apps).
|
|
172
|
+
|
|
173
|
+
## GitHub Actions Secrets and Variables
|
|
158
174
|
|
|
159
|
-
|
|
175
|
+
### GitHub Actions Secrets
|
|
160
176
|
|
|
161
|
-
For a normal generated review-app setup, configure one
|
|
177
|
+
For a normal generated review-app setup, configure one GitHub Actions secret:
|
|
162
178
|
|
|
163
|
-
- `CPLN_TOKEN_STAGING`: token
|
|
179
|
+
- `CPLN_TOKEN_STAGING`: service-account token scoped to the staging Control Plane org on controlplane.com. Its policies
|
|
180
|
+
should allow only review/staging CI operations; it must not read production secrets or manage production workloads.
|
|
164
181
|
|
|
165
|
-
|
|
182
|
+
### GitHub Actions Variables
|
|
183
|
+
|
|
184
|
+
No GitHub Actions variables are required for review apps when `.controlplane/controlplane.yml`
|
|
166
185
|
has exactly one review app entry with `match_if_app_name_starts_with: true` and
|
|
167
186
|
that entry has a `cpln_org`. The inferred values come from that config file:
|
|
168
187
|
the review-app prefix is the app key with `match_if_app_name_starts_with: true`,
|
|
@@ -171,9 +190,13 @@ when you need to test a fork or clone against a different Control Plane org,
|
|
|
171
190
|
choose a different review-app prefix, expose a different public workload, or
|
|
172
191
|
disambiguate generated review-app config:
|
|
173
192
|
|
|
174
|
-
- `CPLN_ORG_STAGING`:
|
|
193
|
+
- `CPLN_ORG_STAGING`: Control Plane org on controlplane.com for staging and review apps. Overrides the org inferred from `cpln_org`, for example `company-staging`
|
|
175
194
|
- `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`
|
|
195
|
+
- `PRIMARY_WORKLOAD`: override the public workload used to discover the public endpoint and do review/production health checks; defaults to `rails`
|
|
196
|
+
- `REVIEW_APP_HEALTH_CHECK_RETRIES`: override review-app health polling attempts; defaults to `24`
|
|
197
|
+
- `REVIEW_APP_HEALTH_CHECK_INTERVAL`: override seconds between review-app health attempts; defaults to `15`
|
|
198
|
+
- `REVIEW_APP_HEALTH_CHECK_ACCEPTED_STATUSES`: override space-separated healthy HTTP statuses for review apps; defaults to `200 301 302`
|
|
199
|
+
- `REVIEW_APP_HEALTH_CHECK_CURL_MAX_TIME`: override per-request review-app curl timeout in seconds; defaults to `10`
|
|
177
200
|
|
|
178
201
|
If `controlplane.yml` defines more than one app with
|
|
179
202
|
`match_if_app_name_starts_with: true`, inference intentionally fails. Set
|
|
@@ -304,6 +327,20 @@ the run early instead of deploying an image that cannot boot.
|
|
|
304
327
|
|
|
305
328
|
Review apps are different: the generated `+review-app-deploy` workflow creates
|
|
306
329
|
temporary PR apps as needed, including the identity and secret policy binding.
|
|
330
|
+
For an existing review app, each deployment runs
|
|
331
|
+
`cpflow setup-app --refresh-templates` before building and deploying the new
|
|
332
|
+
image. Refresh mode reapplies the configured `setup_app_templates` without
|
|
333
|
+
deleting the GVC or running `hooks.post_creation`, answers template replacement
|
|
334
|
+
prompts noninteractively, preserves each matching workload container's exact
|
|
335
|
+
configured app-image reference even when the app is unhealthy or workloads use
|
|
336
|
+
different image versions, and repairs the configured app and shared-secret
|
|
337
|
+
policy bindings. A missing or invalid workload image uses only an unambiguous
|
|
338
|
+
app image from ready workloads; refresh fails closed rather than selecting the
|
|
339
|
+
registry's newest image. Preserving image references keeps release-phase and
|
|
340
|
+
ordered-deploy gates in control of image rollout; a template failure stops the
|
|
341
|
+
workflow before the image build or deployment. Existing secret templates are skipped
|
|
342
|
+
as whole resources, so refresh neither changes existing values nor adds newly templated
|
|
343
|
+
keys; provision required new keys separately before deployment.
|
|
307
344
|
You still need the shared review-app runtime secret values described by your
|
|
308
345
|
templates, and the staging token must have access to create and update
|
|
309
346
|
review-app GVCs, workloads, images, identities, policies, and secrets in the
|
|
@@ -311,12 +348,13 @@ staging org.
|
|
|
311
348
|
|
|
312
349
|
If review apps share an existing staging database or another existing secret,
|
|
313
350
|
declare it with `shared_secret_grants` on the review app config entry. The
|
|
314
|
-
deploy workflow runs `setup-app` for new review apps
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
call `cpflow delete`, which removes
|
|
318
|
-
lets one shared database or license
|
|
319
|
-
|
|
351
|
+
deploy workflow runs `setup-app` for new review apps, refreshes templates for
|
|
352
|
+
existing review apps, and then runs `deploy-image`; those commands bind or
|
|
353
|
+
repair the review app identity's `reveal` permission on each configured shared
|
|
354
|
+
policy. The delete and cleanup workflows call `cpflow delete`, which removes
|
|
355
|
+
those bindings as review apps go away. This lets one shared database or license
|
|
356
|
+
secret serve many short-lived review apps without granting every review
|
|
357
|
+
identity access to unrelated app secrets.
|
|
320
358
|
|
|
321
359
|
```yaml
|
|
322
360
|
apps:
|
|
@@ -356,7 +394,11 @@ The standard path is:
|
|
|
356
394
|
references are copied by digest, commit-suffixed tags keep the commit suffix,
|
|
357
395
|
and plain numeric tags remain valid.
|
|
358
396
|
11. Expect production health and rollback readiness polling to require Control
|
|
359
|
-
Plane `status.ready` and `status.readyLatest` before checking the
|
|
397
|
+
Plane `status.ready` and `status.readyLatest` before checking the standard
|
|
398
|
+
workload endpoint. If that endpoint is unavailable, as it can be on BYOK
|
|
399
|
+
locations, the health check waits until every reported deployment location
|
|
400
|
+
is ready and not deploying, then tries their endpoints sequentially. Each
|
|
401
|
+
additional location can add up to `curl_max_time` to an attempt.
|
|
360
402
|
|
|
361
403
|
GitHub only exposes environment secrets to jobs that reference the environment
|
|
362
404
|
after configured protection rules pass. GitHub does not allow a caller job that
|
|
@@ -377,6 +419,9 @@ Recommended org layout:
|
|
|
377
419
|
|
|
378
420
|
- keep review apps and staging in a staging org that developers can access
|
|
379
421
|
- keep production in a separate org with tighter access controls
|
|
422
|
+
- for public repositories, use a staging/review token generated from a service account whose policies only allow
|
|
423
|
+
review/staging CI operations; use a dedicated review-app org only if you also customize the generated
|
|
424
|
+
workflow/configuration to target that org separately
|
|
380
425
|
|
|
381
426
|
Optional repository secret for private dependency builds:
|
|
382
427
|
|
|
@@ -393,6 +438,115 @@ Advanced optional repository variables:
|
|
|
393
438
|
- `CPLN_CLI_VERSION`: pin only when Control Plane CLI compatibility requires it.
|
|
394
439
|
- `CPFLOW_VERSION`: pin a published RubyGems version only when intentionally overriding the default build-from-ref behavior.
|
|
395
440
|
|
|
441
|
+
## Review App Security for Repositories with External Contributors
|
|
442
|
+
|
|
443
|
+
Review-app deployment and teardown can execute pull request code. Even when the workflow itself is trusted, the
|
|
444
|
+
Dockerfile, package scripts, Rails initializers, server-rendering code, application runtime, any `release_script` or
|
|
445
|
+
`hooks.post_creation` defined in the PR's `.controlplane/controlplane.yml`, and `.controlplane/templates/*.yaml` identity
|
|
446
|
+
and policy templates applied by `cpflow setup-app` at first deploy with `CPLN_TOKEN_STAGING` can all be changed by the
|
|
447
|
+
pull request being deployed. Teardown can also run a `hooks.pre_deletion` command through the latest PR-built image, even
|
|
448
|
+
when the hook command comes from the base-branch config. A PR author can embed malicious code in that image; at runtime
|
|
449
|
+
the code executes inside the review app workload and can read any secrets mounted into the workload environment.
|
|
450
|
+
The generated setup action also exports `CPLN_TOKEN_STAGING` as `CPLN_TOKEN` via `GITHUB_ENV`, making it available in the
|
|
451
|
+
process environment of every subsequent runner step for the rest of the job, so keep any custom runner steps after setup
|
|
452
|
+
trusted. PR-controlled `release_script` and hook commands normally run through `cpflow run` in remote Control Plane
|
|
453
|
+
containers from the latest image, not as runner shell steps, so they do not read that runner token directly unless a
|
|
454
|
+
custom workflow passes a local token through. Inside the Control Plane workload, a separate runtime `CPLN_TOKEN` is
|
|
455
|
+
available only when the workload spec has an `identityLink`; `skip_secrets_setup` skips automatic identity creation and
|
|
456
|
+
binding, but templates can still attach an identity. Because `cpflow setup-app` reads `controlplane.yml` from the PR
|
|
457
|
+
checkout, a PR can also set `skip_secrets_setup: false` or omit it to re-enable automatic identity binding unless the
|
|
458
|
+
trusted workflow passes `--skip-secrets-setup` as a CLI flag. Do not treat `skip_secrets_setup` in `controlplane.yml` as
|
|
459
|
+
a token-removal control or reliable security boundary. This is why workload-mounted secrets and the staging
|
|
460
|
+
service-account token must remain disposable and scoped to minimum permissions.
|
|
461
|
+
|
|
462
|
+
The generated flow uses these defaults:
|
|
463
|
+
|
|
464
|
+
- same-repository pull requests can update existing review apps automatically on each push; creating the first review app
|
|
465
|
+
requires either a `+review-app-deploy` comment from someone whose current repository permission is `write`, `maintain`,
|
|
466
|
+
or `admin`, or a manual workflow dispatch by a repository collaborator with write access. GitHub's
|
|
467
|
+
collaborator-permission API resolves comment permissions when the command runs; `read` and `triage` access are not
|
|
468
|
+
sufficient, and an API lookup failure denies the command. Accepted triggers are recorded in a hidden, bot-authored
|
|
469
|
+
intent comment before queue admission. The mutating job authenticates the newest marker against its originating
|
|
470
|
+
Actions run and successful POST-only recording step, then rechecks the manual actor's current permission after it
|
|
471
|
+
acquires the per-PR queue. Editing or deleting
|
|
472
|
+
the original command comment therefore cannot reorder accepted work, and a permission revoked while the job waits
|
|
473
|
+
makes the newest intent fail closed instead of falling back to an older operation. This permission gate applies to every
|
|
474
|
+
`+review-app-deploy` comment, whether or not a review app already exists. Later pushes to a base-repository branch PR
|
|
475
|
+
redeploy automatically without another approval because the auto-push path (`pull_request` event) does not use the
|
|
476
|
+
comment permission gate;
|
|
477
|
+
- fork pull requests cannot deploy via the generated `pull_request` path because the caller workflow's job-level `if:`
|
|
478
|
+
condition explicitly skips fork-originated runs. For `issue_comment` events, the caller `if:` restricts invocation to
|
|
479
|
+
the exact command shape; the reusable workflow resolves repository permission before its deploy job runs, then its
|
|
480
|
+
source-validation step checks fork status before checking out or building PR code. These complementary guards cover
|
|
481
|
+
different axes, so preserve both. Even a write-authorized comment on a fork PR cannot deploy that fork head.
|
|
482
|
+
Removing the source-validation guard opens a path to deploy untrusted code with repository-secret access,
|
|
483
|
+
because `issue_comment` events execute with base-repository secret access. Removing the `pull_request` guard still
|
|
484
|
+
lets untrusted fork code into the staging environment even though GitHub withholds repository secrets from fork
|
|
485
|
+
`pull_request` runs. Keep both workflow guards in place because the workflow builds Docker images with repository
|
|
486
|
+
secrets;
|
|
487
|
+
- review apps are also deleted automatically when the pull request closes; that PR-close path uses `pull_request_target`
|
|
488
|
+
so it runs in the base-repository context and has repository-secret access for teardown. That is also why you must
|
|
489
|
+
never check out PR or fork code in this job; see the customization guidance below. The PR-close path does not require a
|
|
490
|
+
comment permission check. The generated `cpflow-delete-review-app.yml` pins `GITHUB_TOKEN` permissions to the minimum it needs
|
|
491
|
+
(`actions: write`, `contents: read`, `deployments: write`, `issues: write`, `pull-requests: write`). `actions: write` is used
|
|
492
|
+
only to redispatch the newest accepted deploy/delete operation when GitHub replaces the corresponding pending run;
|
|
493
|
+
if you customize this workflow, preserve that `permissions:` block because omitting it can fall back to broader
|
|
494
|
+
repository defaults;
|
|
495
|
+
- manual workflow dispatch by a repository collaborator can also delete a review app without a `+review-app-delete`
|
|
496
|
+
comment. It is recorded in the same intent order as comment and automatic triggers, and the actor's current repository
|
|
497
|
+
permission is rechecked after queue admission;
|
|
498
|
+
- write-authorized comments on fork PRs still do not deploy the fork head; the workflow posts no PR comment or command
|
|
499
|
+
reaction in this case. The authorization job records no accepted intent, and the skip appears in its
|
|
500
|
+
`Prepare accepted review app intent` log. Review the fork code
|
|
501
|
+
carefully, then move the change to a branch in the base repository if it needs a generated review app. That build will
|
|
502
|
+
run with repository-secret access;
|
|
503
|
+
- production promotion is manual and uses production environment secrets separately from review and staging.
|
|
504
|
+
|
|
505
|
+
The generated review-app workflow targets the staging/review Control Plane org inferred from `cpln_org` in
|
|
506
|
+
`controlplane.yml`, or overridden by `CPLN_ORG_STAGING`, and uses the token from `CPLN_TOKEN_STAGING`. A fully separate
|
|
507
|
+
review-app org or token requires workflow/configuration customization; otherwise, keep review apps and staging in the
|
|
508
|
+
generated staging/review org and make that token disposable and unable to access production resources.
|
|
509
|
+
|
|
510
|
+
The PR-close teardown workflow runs trusted base-branch workflow code with repository secret access so it can delete fork
|
|
511
|
+
PR review apps. The generated `cpflow-delete-control-plane-app` composite action script refuses to call `cpflow delete`
|
|
512
|
+
on any app whose name does not match the review-app prefix. This shell-level guard is effective because the generated
|
|
513
|
+
delete workflow checks out base/default-branch code, not PR or fork code: its repository checkout has no `ref:` override,
|
|
514
|
+
so `pull_request_target` runs use the base branch. If you customize this workflow, never check out PR or fork code in the
|
|
515
|
+
same job as the delete step; doing so could let a PR replace the guard script itself and would also make
|
|
516
|
+
`hooks.pre_deletion` come from the PR's `controlplane.yml`. This is still not a token policy, so use a scoped staging
|
|
517
|
+
service account limited to review/staging operations. A configured `hooks.pre_deletion` command still runs through the
|
|
518
|
+
latest PR-built image on all delete paths: PR-close teardown, `+review-app-delete` comments, manual dispatch, and
|
|
519
|
+
scheduled cleanup. Review-app credentials must remain disposable even during deletion.
|
|
520
|
+
|
|
521
|
+
If you customize the generated `pull_request_target` workflow, never pass `github.event.pull_request.head.sha` or another
|
|
522
|
+
fork-controlled ref to `actions/checkout`, `git fetch`, `git merge`, `git cherry-pick`, or any other step that fetches,
|
|
523
|
+
materializes, or executes the ref. Those operations run untrusted fork code with repository secret access. Referencing the
|
|
524
|
+
SHA as an opaque identifier for deployment status updates, comments, or API calls is safe because SHA values are hex-only
|
|
525
|
+
and cannot contain shell metacharacters. Do not apply that reasoning to other attacker-controlled PR event fields such as
|
|
526
|
+
`head.ref`, `head.label`, `title`, `body`, `head.repo.full_name`, or repository descriptions; pass user-controlled strings
|
|
527
|
+
through environment variables instead of interpolating them directly into `run:` steps.
|
|
528
|
+
|
|
529
|
+
These defaults protect repository secrets from direct fork PR execution, but they do not make deployed PR code harmless.
|
|
530
|
+
For repositories with external contributors, keep review-app credentials and runtime values disposable:
|
|
531
|
+
|
|
532
|
+
- do not mount production secrets, staging customer data, package-publishing tokens, payment keys, or monitoring tokens
|
|
533
|
+
into review apps;
|
|
534
|
+
- do not use `DOCKER_BUILD_SSH_KEY` with a long-lived personal key or broad deploy key; see
|
|
535
|
+
[Docker Builds with Private Dependencies](#docker-builds-with-private-dependencies) for the minimum-access deploy-key
|
|
536
|
+
requirements;
|
|
537
|
+
- do not use broad Control Plane `superusers` tokens for review-app CI; use a review/staging service account scoped only to
|
|
538
|
+
review/staging CI operations;
|
|
539
|
+
- keep review databases, Redis instances, object stores, and renderer credentials separate from staging and production;
|
|
540
|
+
- rotate any credential that may have been exposed to a malicious review-app build;
|
|
541
|
+
- use scheduled cleanup so stale review apps stop consuming compute and secrets access. Cleanup deletes apps through
|
|
542
|
+
`cpflow delete`, so any configured `hooks.pre_deletion` still runs through the latest PR-built image; review-app
|
|
543
|
+
credentials must remain disposable for this path too.
|
|
544
|
+
|
|
545
|
+
Secret indirection such as `cpln://secret/...` protects values in Control Plane configuration. It does not protect a
|
|
546
|
+
value after that secret is mounted into a workload that runs pull request code. See
|
|
547
|
+
[Secrets and ENV Values - Review app secrets](./secrets-and-env-values.md#review-app-secrets) for review-app secret
|
|
548
|
+
handling guidance.
|
|
549
|
+
|
|
396
550
|
## Docker Builds with Private Dependencies
|
|
397
551
|
|
|
398
552
|
Some apps need extra Docker build configuration before the generated workflows are turnkey. Common examples are:
|
|
@@ -417,8 +571,24 @@ DOCKER_BUILD_EXTRA_ARGS=--build-arg=BUNDLE_WITHOUT=development:test
|
|
|
417
571
|
|
|
418
572
|
The action will start an SSH agent, add the key, write `known_hosts`, and pass `--ssh=default` to `cpflow build-image`. When `DOCKER_BUILD_SSH_KNOWN_HOSTS` is unset, the generated action uses pinned GitHub.com host keys by default. If your Dockerfile relies on `RUN --mount=type=ssh`, validate the build locally with `cpflow build-image -a <app> --ssh=default` before relying on CI.
|
|
419
573
|
|
|
574
|
+
Do not configure `DOCKER_BUILD_SSH_KEY` unless the Dockerfile truly needs it. When configured, it is available to all
|
|
575
|
+
review-app and staging Docker builds; follow the review-app security guidance above by using a read-only, revocable
|
|
576
|
+
deploy key scoped to the minimum private dependency access, and never use a personal SSH key.
|
|
577
|
+
|
|
420
578
|
## Generated Workflow Behavior
|
|
421
579
|
|
|
580
|
+
The deploy and delete workflows share a per-PR concurrency group, but GitHub keeps at most one running and one pending
|
|
581
|
+
member of a group and may replace the pending member without FIFO ordering. Each authorized trigger therefore records a
|
|
582
|
+
hidden `github-actions[bot]` intent marker containing its operation and originating workflow-run identity before it joins
|
|
583
|
+
the queue. Whichever job survives reads the complete marker ledger, authenticates the newest marker against the Actions
|
|
584
|
+
run API and the source run's successful POST-only recording step, revalidates a manual actor's current permission, and either performs that operation or redispatches the matching
|
|
585
|
+
generated workflow on the default branch. Internal redispatches reuse the existing marker only through a bot-owned
|
|
586
|
+
handoff bound to the returned workflow-run ID and the exact successful source dispatch step; the superseded source job
|
|
587
|
+
then stops before mutable work, and values entered manually in the internal handoff field are rejected. This convergence
|
|
588
|
+
covers automatic PR events, manual dispatches, mixed-case comment admission, authorization
|
|
589
|
+
completion in a different order from event creation, and edits or deletion of the original command comment. Invalid,
|
|
590
|
+
forged, missing, or permission-revoked newest intents fail closed and never fall back to older work.
|
|
591
|
+
|
|
422
592
|
`cpflow-review-app-help.yml`
|
|
423
593
|
|
|
424
594
|
- Posts a quick reference when a pull request opens, including on fork-based PRs.
|
|
@@ -432,18 +602,52 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
|
|
|
432
602
|
|
|
433
603
|
`cpflow-deploy-review-app.yml`
|
|
434
604
|
|
|
435
|
-
- Creates a review app when someone comments `+review-app-deploy
|
|
605
|
+
- Creates a review app when someone comments `+review-app-deploy` or via manual workflow dispatch by a repository
|
|
606
|
+
collaborator.
|
|
607
|
+
- For manual dispatch, provide the PR number; the workflow rejects fork PRs at runtime because it builds Docker images
|
|
608
|
+
with repository secrets.
|
|
436
609
|
- Redeploys an existing review app automatically on later PR pushes.
|
|
437
|
-
- Creates a GitHub deployment and comments with the review URL and logs.
|
|
610
|
+
- Creates a transient, non-production GitHub deployment and comments with the review URL and logs.
|
|
438
611
|
- Leaves PR pushes alone until the first review app is explicitly requested, which keeps demo-app costs down.
|
|
439
|
-
-
|
|
440
|
-
|
|
612
|
+
- When that no-app PR path succeeds without building or deploying, writes a prominent `Docker image not built` job
|
|
613
|
+
summary and returns the reusable-workflow output `image_built=false`. A downstream job can inspect
|
|
614
|
+
`needs.deploy.outputs.image_built`; a green deploy job with `false` is not Docker-image validation.
|
|
615
|
+
- Repositories that require production-image validation before merge should add a separate required build job when
|
|
616
|
+
Dockerfile, runtime-version, or dependency files change. The cost-saving review-app path intentionally does not create
|
|
617
|
+
an app or build an image until `+review-app-deploy` is requested.
|
|
618
|
+
- Supports cost-conscious review apps when paired with one warm replica, Capacity AI, and a disabled autoscaling
|
|
619
|
+
metric for public demos, starter staging apps, and long-lived review apps; see
|
|
620
|
+
[Enable Capacity AI for Demo and Starter Staging Apps](tips.md#enable-capacity-ai-for-demo-and-starter-staging-apps).
|
|
621
|
+
- Accepts `+review-app-deploy` only when GitHub reports the commenter has `write`, `maintain`, or `admin` repository
|
|
622
|
+
permission. `read` and `triage` are denied, permission is checked again after queue admission, and lookup failures fail
|
|
623
|
+
closed. Manual dispatch follows the same post-queue permission rule.
|
|
624
|
+
- Skips fork-based PR deploys because the workflow builds Docker images with repository secrets. An authorized comment on a
|
|
625
|
+
fork PR still does not deploy the fork head, and manual dispatch must use a base-repository PR number. A fork-targeted
|
|
626
|
+
request records no accepted intent and skips the mutating job. To give a fork PR a review app, review the code carefully first, then
|
|
627
|
+
move the change to a branch in the base repository. The build will then run with repository-secret access.
|
|
628
|
+
- Rejects comment, manual, and internal deploy requests for a closed pull request before recording a durable intent, then
|
|
629
|
+
rechecks that the pull request is still open after queue admission. A doomed deploy therefore cannot supersede the
|
|
630
|
+
automatic close-triggered deletion that cleans up an existing review app.
|
|
441
631
|
|
|
442
632
|
`cpflow-delete-review-app.yml`
|
|
443
633
|
|
|
444
634
|
- Deletes the review app on `+review-app-delete`.
|
|
445
|
-
- Also
|
|
446
|
-
|
|
635
|
+
- Also supports manual workflow dispatch by a repository collaborator. The comment-time permission gate does not apply,
|
|
636
|
+
but the dispatching actor's repository permission is rechecked after queue admission.
|
|
637
|
+
- Also deletes it automatically when the pull request closes through a `pull_request_target` event, so repository secrets
|
|
638
|
+
are available for teardown; `hooks.pre_deletion` still executes through the latest PR-built image on this path, so
|
|
639
|
+
review-app credentials must remain disposable.
|
|
640
|
+
- After Control Plane deletion succeeds, marks every GitHub deployment for the exact `review/<app-name>` environment
|
|
641
|
+
inactive so the repository does not retain a stale active review deployment, while skipping deployments whose latest
|
|
642
|
+
status is already inactive so repeated teardown is idempotent. Deploy and delete workflows share one per-PR
|
|
643
|
+
concurrency queue; the accepted-intent reconciliation described above compensates for GitHub replacing pending runs,
|
|
644
|
+
ensuring the newest accepted operation eventually wins and an older deploy cannot publish the final state after a
|
|
645
|
+
newer deletion. If GitHub deployment cleanup fails after Control Plane deletion, the workflow reports
|
|
646
|
+
that partial outcome accurately and still fails so the cleanup can be retried.
|
|
647
|
+
- Accepts `+review-app-delete` only when GitHub reports the commenter has `write`, `maintain`, or `admin` repository
|
|
648
|
+
permission. `read` and `triage` are denied, permission is checked again after queue admission, and lookup failures fail
|
|
649
|
+
closed. Manual dispatch uses the same post-queue actor check; automatic PR-close teardown does not use a manual actor
|
|
650
|
+
permission check.
|
|
447
651
|
|
|
448
652
|
`cpflow-deploy-staging.yml`
|
|
449
653
|
|
|
@@ -461,6 +665,12 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
|
|
|
461
665
|
- Attempts a rollback of every configured application workload if the new production image does not come up healthy.
|
|
462
666
|
- Creates a GitHub release after a successful promotion.
|
|
463
667
|
|
|
668
|
+
`cpflow-deploy-review-app.yml`
|
|
669
|
+
|
|
670
|
+
- Builds and deploys the pull request image after the review app exists or is created.
|
|
671
|
+
- Waits for `PRIMARY_WORKLOAD` to report `readyLatest=true` and return an accepted HTTP status before marking the GitHub deployment successful.
|
|
672
|
+
- Uses `REVIEW_APP_HEALTH_CHECK_*` repository variables to tune review-app health checks independently from production promotion.
|
|
673
|
+
|
|
464
674
|
`cpflow-cleanup-stale-review-apps.yml`
|
|
465
675
|
|
|
466
676
|
- Runs nightly and on demand.
|
|
@@ -717,15 +927,17 @@ In practice, porting the flow into a demo app usually follows five phases.
|
|
|
717
927
|
|
|
718
928
|
13. Validate the real production Docker build before relying on the workflows, especially if asset compilation or SSR requires Node, extra system packages, multiple processes, extra Docker build flags, or persistent writable paths.
|
|
719
929
|
14. Expect review app deploys to run only for branches in the base repository; fork PRs still get help comments, but deploys are skipped because the workflow uses repository secrets.
|
|
930
|
+
15. For public repositories, confirm that review-app secrets are disposable and that `CPLN_TOKEN_STAGING` cannot access production resources.
|
|
720
931
|
|
|
721
932
|
## AI Playbook
|
|
722
933
|
|
|
723
|
-
If you want an AI agent to apply this flow to another project,
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
934
|
+
If you want an AI agent to apply this flow to another project, use the
|
|
935
|
+
standalone [AI rollout prompt](./ai-github-flow-prompt.md). It captures the
|
|
936
|
+
exact wording, hard stop conditions, and definition of done for this workflow,
|
|
937
|
+
and it works whether `cpflow` is already installed or the agent needs to install
|
|
938
|
+
it first. When `cpflow` is already available in the target repo,
|
|
939
|
+
`cpflow ai-github-flow-prompt` prints the same prompt with that repo's default
|
|
940
|
+
app prefix already filled in.
|
|
729
941
|
|
|
730
942
|
Short version:
|
|
731
943
|
|
data/docs/commands.md
CHANGED
|
@@ -29,6 +29,8 @@ cpflow ai-github-flow-prompt
|
|
|
29
29
|
- Publishes (creates or updates) those at Control Plane infrastructure
|
|
30
30
|
- Picks templates from the `.controlplane/templates` directory
|
|
31
31
|
- Templates are ordinary Control Plane templates but with variable preprocessing
|
|
32
|
+
- Use `--preserve-existing-runtime` to retain each workload container's configured app image, even when the workload is unready, and skip existing secret resources entirely while applying other template changes
|
|
33
|
+
- Missing or invalid workload images use only an unambiguous app image from ready workloads; refresh fails before applying templates when no safe fallback exists
|
|
32
34
|
|
|
33
35
|
**Preprocessed template variables:**
|
|
34
36
|
|
|
@@ -149,6 +151,9 @@ cpflow delete -a $APP_NAME -w $WORKLOAD_NAME
|
|
|
149
151
|
### `deploy-image`
|
|
150
152
|
|
|
151
153
|
- Deploys the latest image to app workloads
|
|
154
|
+
- Use `--workload`/`-w` one or more times to deploy only selected app workloads
|
|
155
|
+
- If `deploy_order` is configured and no `--workload` is provided, deploys ordered workload groups one at a time and waits for each group to be ready before continuing
|
|
156
|
+
- Workloads listed in `app_workloads` but omitted from `deploy_order` deploy last as an implicit final group
|
|
152
157
|
- Runs a release script before deploying if `release_script` is specified in the `.controlplane/controlplane.yml` file and `--run-release-phase` is provided
|
|
153
158
|
- The release script is run in the context of `cpflow run` with the latest image
|
|
154
159
|
- If the release script exits with a non-zero code, the command will stop executing and also exit with a non-zero code
|
|
@@ -156,7 +161,14 @@ cpflow delete -a $APP_NAME -w $WORKLOAD_NAME
|
|
|
156
161
|
- Repairs missing `shared_secret_grants` policy bindings before running a release phase or updating workloads
|
|
157
162
|
|
|
158
163
|
```sh
|
|
164
|
+
# Deploys the latest image to all app workloads.
|
|
159
165
|
cpflow deploy-image -a $APP_NAME
|
|
166
|
+
|
|
167
|
+
# Deploys only one app workload.
|
|
168
|
+
cpflow deploy-image -a $APP_NAME -w node-renderer
|
|
169
|
+
|
|
170
|
+
# Deploys only selected app workloads.
|
|
171
|
+
cpflow deploy-image -a $APP_NAME -w node-renderer -w sidekiq
|
|
160
172
|
```
|
|
161
173
|
|
|
162
174
|
### `doctor`
|
|
@@ -374,6 +386,7 @@ cpflow open-console -a $APP_NAME
|
|
|
374
386
|
- It performs the following steps:
|
|
375
387
|
- Runs `cpflow copy-image-from-upstream` to copy the latest image from upstream
|
|
376
388
|
- Runs `cpflow deploy-image` to deploy the image
|
|
389
|
+
- Honors `deploy_order` from `.controlplane/controlplane.yml` through `cpflow deploy-image`
|
|
377
390
|
- If `.controlplane/controlplane.yml` includes the `release_script`, `cpflow deploy-image` will use the `--run-release-phase` option
|
|
378
391
|
- If the release script exits with a non-zero code, the command will stop executing and also exit with a non-zero code
|
|
379
392
|
- 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
|
|
@@ -468,8 +481,21 @@ timeout 300 cpflow ps:wait -a $APP_NAME
|
|
|
468
481
|
and also overridden per job through `--cpu` and `--memory`)
|
|
469
482
|
- By default, the job is stopped if it takes longer than 6 hours to finish
|
|
470
483
|
(can be configured though `runner_job_timeout` in `controlplane.yml`)
|
|
484
|
+
- Waiting for a runner replica is limited to the smaller of `runner_job_timeout` and 1000 seconds.
|
|
485
|
+
A terminal cron status fails immediately, and reaching the observation deadline reports the last safe status
|
|
471
486
|
- Non-interactive jobs return the Control Plane cron job status even when the job finishes before
|
|
472
487
|
Control Plane exposes a runner replica to attach logs to
|
|
488
|
+
- Injects `CPFLOW_GVC_ID` and `CPFLOW_GVC_CREATED` into the job, exposing the app's immutable GVC
|
|
489
|
+
identity, so that a command such as a release script can tell which GVC incarnation it is running in.
|
|
490
|
+
These change when a GVC is deleted and recreated under the same name, and only then, unlike
|
|
491
|
+
`CPLN_GVC_ALIAS`, which is also embedded in mutable derived values such as the app domain and so
|
|
492
|
+
cannot be attributed to recreation alone
|
|
493
|
+
- `CPFLOW_GVC_CREATED` is the GVC's creation timestamp as returned by the Control Plane API and passed
|
|
494
|
+
through unmodified, currently an ISO 8601 UTC timestamp with millisecond precision and a `Z` suffix
|
|
495
|
+
(e.g. `2026-08-28T00:54:48.648Z`)
|
|
496
|
+
- Both variables are always set, and are empty when the GVC cannot be read, so that a consumer can
|
|
497
|
+
fail closed. They are never omitted, because the runner inherits the original workload's
|
|
498
|
+
environment and an omitted variable could otherwise expose a stale inherited value
|
|
473
499
|
|
|
474
500
|
```sh
|
|
475
501
|
# Opens shell (bash by default).
|
|
@@ -493,7 +519,8 @@ cpflow run -a $APP_NAME -- rails db:migrate
|
|
|
493
519
|
# - stop the job
|
|
494
520
|
cpflow run -a $APP_NAME --detached -- rails db:migrate
|
|
495
521
|
|
|
496
|
-
#
|
|
522
|
+
# Quote the whole command to intentionally opt into shell syntax such as an env assignment.
|
|
523
|
+
# Separately supplied command arguments are passed literally.
|
|
497
524
|
cpflow run -a $APP_NAME -- 'SOME_ENV_VAR=some_value rails db:migrate'
|
|
498
525
|
|
|
499
526
|
# Uses a different image (which may not be promoted yet).
|
|
@@ -528,6 +555,7 @@ cpflow run -a $APP_NAME --entrypoint /app/alternative-entrypoint.sh -- rails db:
|
|
|
528
555
|
- Runs a post-creation hook after the app is created if `hooks.post_creation` is specified in the `.controlplane/controlplane.yml` file
|
|
529
556
|
- If the hook exits with a non-zero code, the command will stop executing and also exit with a non-zero code
|
|
530
557
|
- Use `--skip-post-creation-hook` to skip the hook if specified in `controlplane.yml`
|
|
558
|
+
- Use `--refresh-templates` to apply configured templates noninteractively to an existing app while preserving each workload's configured app image even when workloads are unready or use mixed image versions, skipping existing secret resources entirely, repairing secrets access bindings, and skipping the post-creation hook
|
|
531
559
|
|
|
532
560
|
```sh
|
|
533
561
|
cpflow setup-app -a $APP_NAME
|
|
@@ -554,7 +582,7 @@ cpflow terraform import
|
|
|
554
582
|
Regenerates the generated cpflow GitHub Actions wrappers and helper files
|
|
555
583
|
from the currently installed cpflow gem. Use this after updating the
|
|
556
584
|
cpflow gem so checked-in workflow wrappers move to the matching upstream
|
|
557
|
-
release tag, for example `v5.
|
|
585
|
+
release tag, for example `v5.2.0`.
|
|
558
586
|
|
|
559
587
|
If the existing generated staging workflow uses a custom single staging
|
|
560
588
|
branch, the command preserves it. Pass `--staging-branch BRANCH` to set or
|