cpflow 5.1.1 → 5.2.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 +15 -0
- data/.agents/bin/README.md +19 -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/workflows/claude-code-review.yml +2 -0
- data/.github/workflows/claude.yml +2 -0
- data/.github/workflows/cpflow-deploy-review-app.yml +16 -1
- 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 +57 -0
- data/CHANGELOG.md +22 -1
- data/CLAUDE.md +3 -0
- data/CONTRIBUTING.md +6 -2
- data/Gemfile.lock +1 -1
- data/README.md +21 -7
- data/docs/ai-github-flow-prompt.md +18 -16
- data/docs/ci-automation.md +160 -13
- data/docs/commands.md +12 -1
- data/docs/grafana-opentelemetry.md +699 -0
- data/docs/secrets-and-env-values.md +29 -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 +458 -29
- data/examples/controlplane.yml +2 -0
- data/lib/command/ai_github_flow_prompt.rb +2 -2
- data/lib/command/base.rb +17 -2
- data/lib/command/deploy_image.rb +77 -5
- data/lib/command/promote_app_from_upstream.rb +1 -0
- data/lib/command/ps_wait.rb +2 -10
- data/lib/core/config.rb +94 -0
- data/lib/core/doctor_service.rb +44 -3
- 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 +10 -0
- metadata +21 -2
data/README.md
CHANGED
|
@@ -34,7 +34,7 @@ To bootstrap a new project, run three commands from the repo root:
|
|
|
34
34
|
|
|
35
35
|
The generated scaffold is a starting point. After generation, adapt `.controlplane/` for app-specific workloads (Sidekiq, Node renderer), wire any private-dependency Docker build settings (SSH key, optional known-host overrides), and verify that the production Docker build succeeds.
|
|
36
36
|
|
|
37
|
-
See [CI automation](./docs/ci-automation.md) for the full setup and required GitHub secrets and variables. For an AI agent rollout,
|
|
37
|
+
See [CI automation](./docs/ci-automation.md) for the full setup and required GitHub secrets and variables. For an AI agent rollout, copy the [AI rollout prompt](./docs/ai-github-flow-prompt.md). If `cpflow` is already available in the target repo, `cpflow ai-github-flow-prompt` prints the same prompt with that repo's default app prefix filled in.
|
|
38
38
|
|
|
39
39
|
For a live reference, see the [demo app](https://github.com/shakacode/react-webpack-rails-tutorial/tree/master/.controlplane) and its [GitHub Actions flow](https://github.com/shakacode/react-webpack-rails-tutorial/tree/master/.github).
|
|
40
40
|
Here is a brief [video overview](https://www.youtube.com/watch?v=llaQoAV_6Iw).
|
|
@@ -65,11 +65,13 @@ Additionally, the documentation includes numerous examples and practical tips fo
|
|
|
65
65
|
10. [Scheduled Jobs](#scheduled-jobs)
|
|
66
66
|
11. [CLI Commands Reference](#cli-commands-reference)
|
|
67
67
|
12. [Mapping of Heroku Commands to `cpflow` and `cpln`](#mapping-of-heroku-commands-to-cpflow-and-cpln)
|
|
68
|
-
13. [
|
|
69
|
-
14. [
|
|
70
|
-
15. [Migrating
|
|
71
|
-
16. [
|
|
72
|
-
17. [
|
|
68
|
+
13. [Telemetry](https://www.shakacode.com/control-plane-flow/docs/telemetry/)
|
|
69
|
+
14. [Examples](#examples)
|
|
70
|
+
15. [Migrating Postgres Database from Heroku Infrastructure](https://www.shakacode.com/control-plane-flow/docs/postgres/)
|
|
71
|
+
16. [Migrating Redis Database from Heroku Infrastructure](https://www.shakacode.com/control-plane-flow/docs/redis/)
|
|
72
|
+
17. [Tips](https://www.shakacode.com/control-plane-flow/docs/tips/)
|
|
73
|
+
18. [Thruster HTTP/2 Proxy on Control Plane](https://www.shakacode.com/control-plane-flow/docs/thruster/)
|
|
74
|
+
19. [Grafana and OpenTelemetry on Control Plane](https://www.shakacode.com/control-plane-flow/docs/grafana-opentelemetry/)
|
|
73
75
|
|
|
74
76
|
## Key Features
|
|
75
77
|
|
|
@@ -266,9 +268,19 @@ aliases:
|
|
|
266
268
|
# On the other hand, if you have a workload for Redis, that would NOT use the application Docker image
|
|
267
269
|
# and not be listed here.
|
|
268
270
|
app_workloads:
|
|
271
|
+
- node-renderer
|
|
269
272
|
- rails
|
|
270
273
|
- sidekiq
|
|
271
274
|
|
|
275
|
+
# Optional ordered deploy groups for `cpflow deploy-image` and
|
|
276
|
+
# `cpflow promote-app-from-upstream`. Each group is deployed and waited on
|
|
277
|
+
# before the next group starts. Any app workloads omitted here deploy last
|
|
278
|
+
# as an implicit final group. Explicit `deploy-image -w/--workload` options
|
|
279
|
+
# override this ordering for one-off deploys.
|
|
280
|
+
deploy_order:
|
|
281
|
+
- [node-renderer]
|
|
282
|
+
- [rails, sidekiq]
|
|
283
|
+
|
|
272
284
|
# Additional "service type" workloads, using non-application Docker images.
|
|
273
285
|
# These are only used by the `info` and `ps:` commands in order to get all of the defined workloads.
|
|
274
286
|
additional_workloads:
|
|
@@ -326,6 +338,8 @@ apps:
|
|
|
326
338
|
post_creation: bundle exec rake db:prepare
|
|
327
339
|
|
|
328
340
|
# Used by the command `cpflow delete` to run a hook before deleting the app.
|
|
341
|
+
# For a shared database, prefer admin-side cleanup instead: `cpflow delete` runs this hook before removing the
|
|
342
|
+
# workloads, so live connections can block the drop. See docs/tips.md ("Share One Control Plane Postgres").
|
|
329
343
|
pre_deletion: bundle exec rake db:drop
|
|
330
344
|
|
|
331
345
|
my-app-production:
|
|
@@ -379,7 +393,7 @@ cpflow generate-github-actions
|
|
|
379
393
|
bin/test-cpflow-github-flow
|
|
380
394
|
```
|
|
381
395
|
|
|
382
|
-
`cpflow github-flow-readiness` exits non-zero when it finds blockers such as unpublished exact-pinned packages or a missing production Dockerfile, so use it as the gate before generation. Then review the generated `.controlplane/controlplane.yml` entries, adjust any app-specific workloads, and configure the GitHub repository variables and secrets described in [CI automation](./docs/ci-automation.md), including the optional Docker build settings for private GitHub dependencies and custom SSH known hosts. `cpflow generate-github-actions` also writes `bin/test-cpflow-github-flow` for local validation and `bin/pin-cpflow-github-ref` for temporarily pinning downstream wrappers to an upstream commit SHA during pre-release testing. `cpflow generate` already switches to persistent `db` and `storage` volumes when `config/database.yml` shows SQLite in production and preserves detected frontend precompile hooks, but you should still confirm that the generated Dockerfile picked a Ruby base image compatible with the app's declared Ruby requirement and that the emitted workload set matches the real app. If you want an AI agent to do this end to end,
|
|
396
|
+
`cpflow github-flow-readiness` exits non-zero when it finds blockers such as unpublished exact-pinned packages or a missing production Dockerfile, so use it as the gate before generation. Then review the generated `.controlplane/controlplane.yml` entries, adjust any app-specific workloads, and configure the GitHub repository variables and secrets described in [CI automation](./docs/ci-automation.md), including the optional Docker build settings for private GitHub dependencies and custom SSH known hosts. `cpflow generate-github-actions` also writes `bin/test-cpflow-github-flow` for local validation and `bin/pin-cpflow-github-ref` for temporarily pinning downstream wrappers to an upstream commit SHA during pre-release testing. `cpflow generate` already switches to persistent `db` and `storage` volumes when `config/database.yml` shows SQLite in production and preserves detected frontend precompile hooks, but you should still confirm that the generated Dockerfile picked a Ruby base image compatible with the app's declared Ruby requirement and that the emitted workload set matches the real app. If you want an AI agent to do this end to end, give it the [AI rollout prompt](./docs/ai-github-flow-prompt.md) rather than a vague "set up CI" request.
|
|
383
397
|
|
|
384
398
|
For a live example, see the [react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/.controlplane/readme.md) repository.
|
|
385
399
|
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
# AI Rollout Prompt for Control Plane GitHub Flow
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
staging, and production-promotion flow to a repository.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
copy-paste version of this prompt with:
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
cpflow ai-github-flow-prompt
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
That local-only command works even before `cpln` is installed and fills in the
|
|
14
|
-
repo-name default app prefix for the current checkout. You can also run
|
|
15
|
-
`cpflow github-flow-readiness` first to check the same blocker categories the
|
|
16
|
-
prompt tells the agent to stop on.
|
|
3
|
+
Copy the recommended prompt below when you want an AI agent to add the reusable
|
|
4
|
+
`cpflow` review-app, staging, and production-promotion flow to a repository.
|
|
5
|
+
It works whether `cpflow` is already installed or the agent needs to install it
|
|
6
|
+
first.
|
|
17
7
|
|
|
18
8
|
## Recommended Prompt
|
|
19
9
|
|
|
20
10
|
```text
|
|
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.
|
|
11
|
+
Set up Control Plane GitHub Flow for this repo. First make sure the `cpflow` CLI is available: use the repo's existing `bundle exec cpflow` if present, otherwise install the published `cpflow` Ruby gem with `gem install cpflow`; if neither is possible, stop and report that blocker. Use the same `cpflow` invocation for the rest of the rollout. 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
12
|
|
|
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
|
|
13
|
+
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 normal generated review-app setup simple: review apps require only `CPLN_TOKEN_STAGING` when the generated review app config can be inferred. For public demos, starter staging apps, and long-lived review apps, keep the app workload `type: standard` with one warm replica, set its autoscaling metric to `disabled`, and enable `capacityAI: true` so Control Plane can right-size CPU and memory allocation at that fixed replica count. Shared Postgres and other stateful workloads are the usual exceptions and should stay manually sized; Capacity AI is for supported stateless app/service workloads. If true idle scale-to-zero is explicitly required, create a separate `serverless` workload before first deploy or plan a delete/recreate migration because Control Plane will not change an existing `standard` workload to `serverless` in place. 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
14
|
|
|
25
15
|
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
16
|
|
|
27
17
|
Run the real local validations you can: Docker build if feasible, repo tests or smoke checks, YAML validation, and any CI-equivalent build steps. Push the branch and check the GitHub Actions results. Only stop early for a real external blocker or a product decision that changes scope.
|
|
28
18
|
```
|
|
29
19
|
|
|
20
|
+
## Local Shortcut
|
|
21
|
+
|
|
22
|
+
If `cpflow` is already available in the target repo, this local-only command
|
|
23
|
+
prints the same recommended prompt with the repo-name default app prefix filled
|
|
24
|
+
in:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
cpflow ai-github-flow-prompt
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
That command works even before `cpln` is installed.
|
|
31
|
+
|
|
30
32
|
## Hard Stop Conditions
|
|
31
33
|
|
|
32
34
|
Stop and report the blocker instead of generating `cpflow-*` workflow files when:
|
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:
|
|
@@ -20,6 +27,8 @@ End-to-end rollout in one view:
|
|
|
20
27
|
4. Configure the GitHub [repository secrets and variables](#required-github-repository-settings) 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,12 +165,18 @@ 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).
|
|
158
172
|
|
|
159
173
|
## Required GitHub Repository Settings
|
|
160
174
|
|
|
161
175
|
For a normal generated review-app setup, configure one repository secret:
|
|
162
176
|
|
|
163
|
-
- `CPLN_TOKEN_STAGING`: token for the staging Control Plane org
|
|
177
|
+
- `CPLN_TOKEN_STAGING`: token for the staging/review Control Plane org. Generate it from a Control Plane service account
|
|
178
|
+
whose policies only allow review/staging CI operations; it must not read production secrets or manage production
|
|
179
|
+
workloads.
|
|
164
180
|
|
|
165
181
|
No GitHub repository variables are required for review apps when `.controlplane/controlplane.yml`
|
|
166
182
|
has exactly one review app entry with `match_if_app_name_starts_with: true` and
|
|
@@ -173,7 +189,11 @@ disambiguate generated review-app config:
|
|
|
173
189
|
|
|
174
190
|
- `CPLN_ORG_STAGING`: override the staging/review org inferred from `cpln_org`, for example `company-staging`
|
|
175
191
|
- `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`
|
|
192
|
+
- `PRIMARY_WORKLOAD`: override the public workload used to discover the public endpoint and do review/production health checks; defaults to `rails`
|
|
193
|
+
- `REVIEW_APP_HEALTH_CHECK_RETRIES`: override review-app health polling attempts; defaults to `24`
|
|
194
|
+
- `REVIEW_APP_HEALTH_CHECK_INTERVAL`: override seconds between review-app health attempts; defaults to `15`
|
|
195
|
+
- `REVIEW_APP_HEALTH_CHECK_ACCEPTED_STATUSES`: override space-separated healthy HTTP statuses for review apps; defaults to `200 301 302`
|
|
196
|
+
- `REVIEW_APP_HEALTH_CHECK_CURL_MAX_TIME`: override per-request review-app curl timeout in seconds; defaults to `10`
|
|
177
197
|
|
|
178
198
|
If `controlplane.yml` defines more than one app with
|
|
179
199
|
`match_if_app_name_starts_with: true`, inference intentionally fails. Set
|
|
@@ -377,6 +397,9 @@ Recommended org layout:
|
|
|
377
397
|
|
|
378
398
|
- keep review apps and staging in a staging org that developers can access
|
|
379
399
|
- keep production in a separate org with tighter access controls
|
|
400
|
+
- for public repositories, use a staging/review token generated from a service account whose policies only allow
|
|
401
|
+
review/staging CI operations; use a dedicated review-app org only if you also customize the generated
|
|
402
|
+
workflow/configuration to target that org separately
|
|
380
403
|
|
|
381
404
|
Optional repository secret for private dependency builds:
|
|
382
405
|
|
|
@@ -393,6 +416,105 @@ Advanced optional repository variables:
|
|
|
393
416
|
- `CPLN_CLI_VERSION`: pin only when Control Plane CLI compatibility requires it.
|
|
394
417
|
- `CPFLOW_VERSION`: pin a published RubyGems version only when intentionally overriding the default build-from-ref behavior.
|
|
395
418
|
|
|
419
|
+
## Review App Security for Repositories with External Contributors
|
|
420
|
+
|
|
421
|
+
Review-app deployment and teardown can execute pull request code. Even when the workflow itself is trusted, the
|
|
422
|
+
Dockerfile, package scripts, Rails initializers, server-rendering code, application runtime, any `release_script` or
|
|
423
|
+
`hooks.post_creation` defined in the PR's `.controlplane/controlplane.yml`, and `.controlplane/templates/*.yaml` identity
|
|
424
|
+
and policy templates applied by `cpflow setup-app` at first deploy with `CPLN_TOKEN_STAGING` can all be changed by the
|
|
425
|
+
pull request being deployed. Teardown can also run a `hooks.pre_deletion` command through the latest PR-built image, even
|
|
426
|
+
when the hook command comes from the base-branch config. A PR author can embed malicious code in that image; at runtime
|
|
427
|
+
the code executes inside the review app workload and can read any secrets mounted into the workload environment.
|
|
428
|
+
The generated setup action also exports `CPLN_TOKEN_STAGING` as `CPLN_TOKEN` via `GITHUB_ENV`, making it available in the
|
|
429
|
+
process environment of every subsequent runner step for the rest of the job, so keep any custom runner steps after setup
|
|
430
|
+
trusted. PR-controlled `release_script` and hook commands normally run through `cpflow run` in remote Control Plane
|
|
431
|
+
containers from the latest image, not as runner shell steps, so they do not read that runner token directly unless a
|
|
432
|
+
custom workflow passes a local token through. Inside the Control Plane workload, a separate runtime `CPLN_TOKEN` is
|
|
433
|
+
available only when the workload spec has an `identityLink`; `skip_secrets_setup` skips automatic identity creation and
|
|
434
|
+
binding, but templates can still attach an identity. Because `cpflow setup-app` reads `controlplane.yml` from the PR
|
|
435
|
+
checkout, a PR can also set `skip_secrets_setup: false` or omit it to re-enable automatic identity binding unless the
|
|
436
|
+
trusted workflow passes `--skip-secrets-setup` as a CLI flag. Do not treat `skip_secrets_setup` in `controlplane.yml` as
|
|
437
|
+
a token-removal control or reliable security boundary. This is why workload-mounted secrets and the staging
|
|
438
|
+
service-account token must remain disposable and scoped to minimum permissions.
|
|
439
|
+
|
|
440
|
+
The generated flow uses these defaults:
|
|
441
|
+
|
|
442
|
+
- same-repository pull requests can update existing review apps automatically on each push; creating the first review app
|
|
443
|
+
requires either a `+review-app-deploy` comment from a trusted commenter (`OWNER`, `MEMBER`, or `COLLABORATOR`
|
|
444
|
+
association, regardless of permission level) or a manual workflow dispatch by a repository collaborator with write
|
|
445
|
+
access. The trusted-commenter gate applies to every `+review-app-deploy` comment, whether or not a review app already
|
|
446
|
+
exists. Later pushes to a base-repository branch PR redeploy automatically without another approval because the
|
|
447
|
+
auto-push path (`pull_request` event) has no trusted-commenter check; only the `issue_comment` path does;
|
|
448
|
+
- fork pull requests cannot deploy via the generated `pull_request` path because the caller workflow's job-level `if:`
|
|
449
|
+
condition explicitly skips fork-originated runs. For `issue_comment` events, the caller `if:` restricts commands to
|
|
450
|
+
trusted author associations (`OWNER`, `MEMBER`, `COLLABORATOR`) but does not check fork status; the fork check for
|
|
451
|
+
comment events is enforced inside the reusable workflow's source-validation step. These complementary guards cover
|
|
452
|
+
different axes, so preserve both. A trusted commenter can comment on a fork PR and pass the caller's
|
|
453
|
+
`author_association` check; the reusable workflow's source-validation step is what blocks that fork head from being
|
|
454
|
+
deployed. Removing the source-validation guard opens a path to deploy untrusted code with repository-secret access,
|
|
455
|
+
because `issue_comment` events execute with base-repository secret access. Removing the `pull_request` guard still
|
|
456
|
+
lets untrusted fork code into the staging environment even though GitHub withholds repository secrets from fork
|
|
457
|
+
`pull_request` runs. Keep both workflow guards in place because the workflow builds Docker images with repository
|
|
458
|
+
secrets;
|
|
459
|
+
- review apps are also deleted automatically when the pull request closes; that PR-close path uses `pull_request_target`
|
|
460
|
+
so it runs in the base-repository context and has repository-secret access for teardown. That is also why you must
|
|
461
|
+
never check out PR or fork code in this job; see the customization guidance below. The PR-close path does not require a
|
|
462
|
+
trusted commenter. The generated `cpflow-delete-review-app.yml` pins `GITHUB_TOKEN` permissions to the minimum it needs
|
|
463
|
+
(`contents: read`, `issues: write`, `pull-requests: write`); if you customize this workflow, preserve that
|
|
464
|
+
`permissions:` block because omitting it can fall back to broader repository defaults;
|
|
465
|
+
- manual workflow dispatch by a repository collaborator can also delete a review app without a `+review-app-delete`
|
|
466
|
+
comment, and does not require a trusted commenter;
|
|
467
|
+
- trusted comments on fork PRs still do not deploy the fork head; the workflow posts no PR comment in this case. The
|
|
468
|
+
commenter receives a rocket reaction and the skip appears only in the Actions step summary. Review the fork code
|
|
469
|
+
carefully, then move the change to a branch in the base repository if it needs a generated review app. That build will
|
|
470
|
+
run with repository-secret access;
|
|
471
|
+
- production promotion is manual and uses production environment secrets separately from review and staging.
|
|
472
|
+
|
|
473
|
+
The generated review-app workflow targets the staging/review Control Plane org inferred from `cpln_org` in
|
|
474
|
+
`controlplane.yml`, or overridden by `CPLN_ORG_STAGING`, and uses the token from `CPLN_TOKEN_STAGING`. A fully separate
|
|
475
|
+
review-app org or token requires workflow/configuration customization; otherwise, keep review apps and staging in the
|
|
476
|
+
generated staging/review org and make that token disposable and unable to access production resources.
|
|
477
|
+
|
|
478
|
+
The PR-close teardown workflow runs trusted base-branch workflow code with repository secret access so it can delete fork
|
|
479
|
+
PR review apps. The generated `cpflow-delete-control-plane-app` composite action script refuses to call `cpflow delete`
|
|
480
|
+
on any app whose name does not match the review-app prefix. This shell-level guard is effective because the generated
|
|
481
|
+
delete workflow checks out base/default-branch code, not PR or fork code: its repository checkout has no `ref:` override,
|
|
482
|
+
so `pull_request_target` runs use the base branch. If you customize this workflow, never check out PR or fork code in the
|
|
483
|
+
same job as the delete step; doing so could let a PR replace the guard script itself and would also make
|
|
484
|
+
`hooks.pre_deletion` come from the PR's `controlplane.yml`. This is still not a token policy, so use a scoped staging
|
|
485
|
+
service account limited to review/staging operations. A configured `hooks.pre_deletion` command still runs through the
|
|
486
|
+
latest PR-built image on all delete paths: PR-close teardown, `+review-app-delete` comments, manual dispatch, and
|
|
487
|
+
scheduled cleanup. Review-app credentials must remain disposable even during deletion.
|
|
488
|
+
|
|
489
|
+
If you customize the generated `pull_request_target` workflow, never pass `github.event.pull_request.head.sha` or another
|
|
490
|
+
fork-controlled ref to `actions/checkout`, `git fetch`, `git merge`, `git cherry-pick`, or any other step that fetches,
|
|
491
|
+
materializes, or executes the ref. Those operations run untrusted fork code with repository secret access. Referencing the
|
|
492
|
+
SHA as an opaque identifier for deployment status updates, comments, or API calls is safe because SHA values are hex-only
|
|
493
|
+
and cannot contain shell metacharacters. Do not apply that reasoning to other attacker-controlled PR event fields such as
|
|
494
|
+
`head.ref`, `head.label`, `title`, `body`, `head.repo.full_name`, or repository descriptions; pass user-controlled strings
|
|
495
|
+
through environment variables instead of interpolating them directly into `run:` steps.
|
|
496
|
+
|
|
497
|
+
These defaults protect repository secrets from direct fork PR execution, but they do not make deployed PR code harmless.
|
|
498
|
+
For repositories with external contributors, keep review-app credentials and runtime values disposable:
|
|
499
|
+
|
|
500
|
+
- do not mount production secrets, staging customer data, package-publishing tokens, payment keys, or monitoring tokens
|
|
501
|
+
into review apps;
|
|
502
|
+
- do not use `DOCKER_BUILD_SSH_KEY` with a long-lived personal key or broad deploy key; see
|
|
503
|
+
[Docker Builds with Private Dependencies](#docker-builds-with-private-dependencies) for the minimum-access deploy-key
|
|
504
|
+
requirements;
|
|
505
|
+
- do not use broad Control Plane `superusers` tokens for review-app CI; use a review/staging service account scoped only to
|
|
506
|
+
review/staging CI operations;
|
|
507
|
+
- keep review databases, Redis instances, object stores, and renderer credentials separate from staging and production;
|
|
508
|
+
- rotate any credential that may have been exposed to a malicious review-app build;
|
|
509
|
+
- use scheduled cleanup so stale review apps stop consuming compute and secrets access. Cleanup deletes apps through
|
|
510
|
+
`cpflow delete`, so any configured `hooks.pre_deletion` still runs through the latest PR-built image; review-app
|
|
511
|
+
credentials must remain disposable for this path too.
|
|
512
|
+
|
|
513
|
+
Secret indirection such as `cpln://secret/...` protects values in Control Plane configuration. It does not protect a
|
|
514
|
+
value after that secret is mounted into a workload that runs pull request code. See
|
|
515
|
+
[Secrets and ENV Values - Review app secrets](./secrets-and-env-values.md#review-app-secrets) for review-app secret
|
|
516
|
+
handling guidance.
|
|
517
|
+
|
|
396
518
|
## Docker Builds with Private Dependencies
|
|
397
519
|
|
|
398
520
|
Some apps need extra Docker build configuration before the generated workflows are turnkey. Common examples are:
|
|
@@ -417,6 +539,10 @@ DOCKER_BUILD_EXTRA_ARGS=--build-arg=BUNDLE_WITHOUT=development:test
|
|
|
417
539
|
|
|
418
540
|
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
541
|
|
|
542
|
+
Do not configure `DOCKER_BUILD_SSH_KEY` unless the Dockerfile truly needs it. When configured, it is available to all
|
|
543
|
+
review-app and staging Docker builds; follow the review-app security guidance above by using a read-only, revocable
|
|
544
|
+
deploy key scoped to the minimum private dependency access, and never use a personal SSH key.
|
|
545
|
+
|
|
420
546
|
## Generated Workflow Behavior
|
|
421
547
|
|
|
422
548
|
`cpflow-review-app-help.yml`
|
|
@@ -432,18 +558,31 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
|
|
|
432
558
|
|
|
433
559
|
`cpflow-deploy-review-app.yml`
|
|
434
560
|
|
|
435
|
-
- Creates a review app when someone comments `+review-app-deploy
|
|
561
|
+
- Creates a review app when someone comments `+review-app-deploy` or via manual workflow dispatch by a repository
|
|
562
|
+
collaborator.
|
|
563
|
+
- For manual dispatch, provide the PR number; the workflow rejects fork PRs at runtime because it builds Docker images
|
|
564
|
+
with repository secrets.
|
|
436
565
|
- Redeploys an existing review app automatically on later PR pushes.
|
|
437
566
|
- Creates a GitHub deployment and comments with the review URL and logs.
|
|
438
567
|
- Leaves PR pushes alone until the first review app is explicitly requested, which keeps demo-app costs down.
|
|
568
|
+
- Supports cost-conscious review apps when paired with one warm replica, Capacity AI, and a disabled autoscaling
|
|
569
|
+
metric for public demos, starter staging apps, and long-lived review apps; see
|
|
570
|
+
[Enable Capacity AI for Demo and Starter Staging Apps](tips.md#enable-capacity-ai-for-demo-and-starter-staging-apps).
|
|
439
571
|
- Accepts `+review-app-deploy` only from trusted commenters (`OWNER`, `MEMBER`, or `COLLABORATOR`).
|
|
440
|
-
- Skips fork-based PR deploys because the workflow builds Docker images with repository secrets.
|
|
572
|
+
- Skips fork-based PR deploys because the workflow builds Docker images with repository secrets. A trusted comment on a
|
|
573
|
+
fork PR still does not deploy the fork head, and manual dispatch must use a base-repository PR number; dispatching with
|
|
574
|
+
a fork PR number causes a hard workflow failure. To give a fork PR a review app, review the code carefully first, then
|
|
575
|
+
move the change to a branch in the base repository. The build will then run with repository-secret access.
|
|
441
576
|
|
|
442
577
|
`cpflow-delete-review-app.yml`
|
|
443
578
|
|
|
444
579
|
- Deletes the review app on `+review-app-delete`.
|
|
445
|
-
- Also
|
|
446
|
-
-
|
|
580
|
+
- Also supports manual workflow dispatch by a repository collaborator without requiring a trusted-commenter command.
|
|
581
|
+
- Also deletes it automatically when the pull request closes through a `pull_request_target` event, so repository secrets
|
|
582
|
+
are available for teardown; `hooks.pre_deletion` still executes through the latest PR-built image on this path, so
|
|
583
|
+
review-app credentials must remain disposable.
|
|
584
|
+
- Accepts `+review-app-delete` only from trusted commenters (`OWNER`, `MEMBER`, or `COLLABORATOR`); manual dispatch and
|
|
585
|
+
automatic PR-close teardown do not require a trusted commenter.
|
|
447
586
|
|
|
448
587
|
`cpflow-deploy-staging.yml`
|
|
449
588
|
|
|
@@ -461,6 +600,12 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
|
|
|
461
600
|
- Attempts a rollback of every configured application workload if the new production image does not come up healthy.
|
|
462
601
|
- Creates a GitHub release after a successful promotion.
|
|
463
602
|
|
|
603
|
+
`cpflow-deploy-review-app.yml`
|
|
604
|
+
|
|
605
|
+
- Builds and deploys the pull request image after the review app exists or is created.
|
|
606
|
+
- Waits for `PRIMARY_WORKLOAD` to report `readyLatest=true` and return an accepted HTTP status before marking the GitHub deployment successful.
|
|
607
|
+
- Uses `REVIEW_APP_HEALTH_CHECK_*` repository variables to tune review-app health checks independently from production promotion.
|
|
608
|
+
|
|
464
609
|
`cpflow-cleanup-stale-review-apps.yml`
|
|
465
610
|
|
|
466
611
|
- Runs nightly and on demand.
|
|
@@ -717,15 +862,17 @@ In practice, porting the flow into a demo app usually follows five phases.
|
|
|
717
862
|
|
|
718
863
|
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
864
|
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.
|
|
865
|
+
15. For public repositories, confirm that review-app secrets are disposable and that `CPLN_TOKEN_STAGING` cannot access production resources.
|
|
720
866
|
|
|
721
867
|
## AI Playbook
|
|
722
868
|
|
|
723
|
-
If you want an AI agent to apply this flow to another project,
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
869
|
+
If you want an AI agent to apply this flow to another project, use the
|
|
870
|
+
standalone [AI rollout prompt](./ai-github-flow-prompt.md). It captures the
|
|
871
|
+
exact wording, hard stop conditions, and definition of done for this workflow,
|
|
872
|
+
and it works whether `cpflow` is already installed or the agent needs to install
|
|
873
|
+
it first. When `cpflow` is already available in the target repo,
|
|
874
|
+
`cpflow ai-github-flow-prompt` prints the same prompt with that repo's default
|
|
875
|
+
app prefix already filled in.
|
|
729
876
|
|
|
730
877
|
Short version:
|
|
731
878
|
|
data/docs/commands.md
CHANGED
|
@@ -149,6 +149,9 @@ cpflow delete -a $APP_NAME -w $WORKLOAD_NAME
|
|
|
149
149
|
### `deploy-image`
|
|
150
150
|
|
|
151
151
|
- Deploys the latest image to app workloads
|
|
152
|
+
- Use `--workload`/`-w` one or more times to deploy only selected app workloads
|
|
153
|
+
- 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
|
|
154
|
+
- Workloads listed in `app_workloads` but omitted from `deploy_order` deploy last as an implicit final group
|
|
152
155
|
- Runs a release script before deploying if `release_script` is specified in the `.controlplane/controlplane.yml` file and `--run-release-phase` is provided
|
|
153
156
|
- The release script is run in the context of `cpflow run` with the latest image
|
|
154
157
|
- 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 +159,14 @@ cpflow delete -a $APP_NAME -w $WORKLOAD_NAME
|
|
|
156
159
|
- Repairs missing `shared_secret_grants` policy bindings before running a release phase or updating workloads
|
|
157
160
|
|
|
158
161
|
```sh
|
|
162
|
+
# Deploys the latest image to all app workloads.
|
|
159
163
|
cpflow deploy-image -a $APP_NAME
|
|
164
|
+
|
|
165
|
+
# Deploys only one app workload.
|
|
166
|
+
cpflow deploy-image -a $APP_NAME -w node-renderer
|
|
167
|
+
|
|
168
|
+
# Deploys only selected app workloads.
|
|
169
|
+
cpflow deploy-image -a $APP_NAME -w node-renderer -w sidekiq
|
|
160
170
|
```
|
|
161
171
|
|
|
162
172
|
### `doctor`
|
|
@@ -374,6 +384,7 @@ cpflow open-console -a $APP_NAME
|
|
|
374
384
|
- It performs the following steps:
|
|
375
385
|
- Runs `cpflow copy-image-from-upstream` to copy the latest image from upstream
|
|
376
386
|
- Runs `cpflow deploy-image` to deploy the image
|
|
387
|
+
- Honors `deploy_order` from `.controlplane/controlplane.yml` through `cpflow deploy-image`
|
|
377
388
|
- If `.controlplane/controlplane.yml` includes the `release_script`, `cpflow deploy-image` will use the `--run-release-phase` option
|
|
378
389
|
- If the release script exits with a non-zero code, the command will stop executing and also exit with a non-zero code
|
|
379
390
|
- 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
|
|
@@ -554,7 +565,7 @@ cpflow terraform import
|
|
|
554
565
|
Regenerates the generated cpflow GitHub Actions wrappers and helper files
|
|
555
566
|
from the currently installed cpflow gem. Use this after updating the
|
|
556
567
|
cpflow gem so checked-in workflow wrappers move to the matching upstream
|
|
557
|
-
release tag, for example `v5.1.
|
|
568
|
+
release tag, for example `v5.1.1`.
|
|
558
569
|
|
|
559
570
|
If the existing generated staging workflow uses a custom single staging
|
|
560
571
|
branch, the command preserves it. Pass `--staging-branch BRANCH` to set or
|