cpflow 5.0.0.rc.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/{lib/github_flow_templates/.github → .github}/actions/cpflow-delete-control-plane-app/action.yml +5 -0
  3. data/{lib/github_flow_templates/.github → .github}/actions/cpflow-detect-release-phase/action.yml +7 -0
  4. data/.github/actions/cpflow-setup-environment/action.yml +161 -0
  5. data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +69 -0
  6. data/.github/workflows/cpflow-delete-review-app.yml +182 -0
  7. data/.github/workflows/cpflow-deploy-review-app.yml +507 -0
  8. data/.github/workflows/cpflow-deploy-staging.yml +168 -0
  9. data/.github/workflows/cpflow-help-command.yml +78 -0
  10. data/.github/workflows/cpflow-promote-staging-to-production.yml +510 -0
  11. data/.github/workflows/cpflow-review-app-help.yml +51 -0
  12. data/.github/workflows/rspec-shared.yml +3 -0
  13. data/.github/workflows/trigger-docs-site.yml +90 -0
  14. data/.rubocop.yml +14 -1
  15. data/CHANGELOG.md +43 -1
  16. data/CONTRIBUTING.md +27 -0
  17. data/Gemfile.lock +2 -2
  18. data/README.md +7 -3
  19. data/cpflow.gemspec +1 -1
  20. data/docs/ai-github-flow-prompt.md +1 -1
  21. data/docs/assets/cpflow-deploying.svg +46 -0
  22. data/docs/ci-automation.md +111 -8
  23. data/docs/commands.md +11 -5
  24. data/docs/thruster.md +149 -0
  25. data/docs/troubleshooting.md +8 -0
  26. data/lib/command/apply_template.rb +6 -2
  27. data/lib/command/base.rb +1 -0
  28. data/lib/command/cleanup_stale_apps.rb +53 -14
  29. data/lib/command/delete.rb +3 -1
  30. data/lib/command/deploy_image.rb +5 -2
  31. data/lib/command/generate.rb +7 -3
  32. data/lib/command/generate_github_actions.rb +21 -9
  33. data/lib/command/generator_helpers.rb +5 -1
  34. data/lib/command/info.rb +3 -1
  35. data/lib/command/run.rb +16 -1
  36. data/lib/command/test.rb +1 -3
  37. data/lib/core/controlplane.rb +17 -6
  38. data/lib/core/controlplane_api.rb +3 -1
  39. data/lib/core/controlplane_api_direct.rb +50 -27
  40. data/lib/core/doctor_service.rb +2 -2
  41. data/lib/core/github_flow_readiness_service.rb +26 -2
  42. data/lib/core/repo_introspection.rb +41 -3
  43. data/lib/core/shell.rb +3 -1
  44. data/lib/core/terraform_config/policy.rb +1 -1
  45. data/lib/cpflow/version.rb +1 -1
  46. data/lib/cpflow.rb +27 -13
  47. data/lib/generator_templates/templates/rails.yml +4 -0
  48. data/lib/generator_templates_sqlite/templates/rails.yml +4 -0
  49. data/lib/github_flow_templates/.github/cpflow-help.md +30 -1
  50. data/lib/github_flow_templates/.github/workflows/cpflow-cleanup-stale-review-apps.yml +10 -44
  51. data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +15 -114
  52. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +10 -413
  53. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +12 -123
  54. data/lib/github_flow_templates/.github/workflows/cpflow-help-command.yml +10 -33
  55. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +13 -475
  56. data/lib/github_flow_templates/.github/workflows/cpflow-review-app-help.yml +12 -30
  57. data/lib/github_flow_templates/bin/pin-cpflow-github-ref +72 -0
  58. data/lib/github_flow_templates/bin/test-cpflow-github-flow +89 -0
  59. data/rakelib/create_release.rake +4 -4
  60. metadata +26 -17
  61. data/lib/github_flow_templates/.github/actions/cpflow-setup-environment/action.yml +0 -98
  62. /data/{lib/github_flow_templates/.github → .github}/actions/cpflow-build-docker-image/action.yml +0 -0
  63. /data/{lib/github_flow_templates/.github → .github}/actions/cpflow-delete-control-plane-app/delete-app.sh +0 -0
  64. /data/{lib/github_flow_templates/.github → .github}/actions/cpflow-validate-config/action.yml +0 -0
  65. /data/{lib/github_flow_templates/.github → .github}/actions/cpflow-wait-for-health/action.yml +0 -0
@@ -0,0 +1,90 @@
1
+ name: Trigger docs site rebuild
2
+ run-name: Docs dispatch (${{ github.event_name == 'workflow_dispatch' && inputs.reason || github.event_name }}) @ ${{ github.sha }}
3
+
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ paths:
8
+ - README.md
9
+ - CHANGELOG.md
10
+ - CONTRIBUTING.md
11
+ - docs/**
12
+ workflow_dispatch:
13
+ inputs:
14
+ reason:
15
+ description: "Reason for forcing a docs rebuild"
16
+ required: false
17
+ default: "manual"
18
+ # Non-main manual dispatches are skipped by the job guard below.
19
+ # Only use this for forcing a rebuild from main.
20
+
21
+ concurrency:
22
+ # Include the ref so accidental non-main manual runs cannot cancel a main dispatch
23
+ # before the job guard skips them.
24
+ group: ${{ github.workflow }}-${{ github.ref }}
25
+ # Only the most recent docs dispatch for the same ref matters.
26
+ # See CONTRIBUTING.md for manual cancellation/retry guidance.
27
+ cancel-in-progress: true
28
+
29
+ jobs:
30
+ notify-docs-site:
31
+ runs-on: ubuntu-latest
32
+ if: github.ref == 'refs/heads/main' # skip non-main workflow_dispatch runs
33
+ permissions: {}
34
+ timeout-minutes: 5
35
+ steps:
36
+ - name: Generate GitHub App token
37
+ uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0
38
+ id: app-token
39
+ with:
40
+ app-id: ${{ secrets.DOCS_DISPATCH_APP_ID }}
41
+ private-key: ${{ secrets.DOCS_DISPATCH_APP_KEY }}
42
+ owner: shakacode
43
+ repositories: controlplaneflow-com
44
+
45
+ - name: Dispatch docs-updated event
46
+ id: dispatch
47
+ uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
48
+ with:
49
+ token: ${{ steps.app-token.outputs.token }}
50
+ repository: shakacode/controlplaneflow-com
51
+ event-type: docs-updated
52
+ client-payload: |
53
+ {
54
+ "sha": ${{ toJSON(github.sha) }},
55
+ "ref": ${{ toJSON(github.ref) }},
56
+ "reason": ${{ toJSON(inputs.reason || github.event_name) }}
57
+ }
58
+
59
+ - name: Summarize docs dispatch
60
+ if: always()
61
+ env:
62
+ DISPATCH_OUTCOME: ${{ steps.dispatch.outcome }}
63
+ DISPATCH_REASON: ${{ inputs.reason || github.event_name }}
64
+ DISPATCH_REF: ${{ github.ref }}
65
+ DISPATCH_SHA: ${{ github.sha }}
66
+ run: |
67
+ safe_dispatch_reason=${DISPATCH_REASON//[^a-zA-Z0-9 _.\/-]/}
68
+ # Ref and SHA come from GitHub; the job guard restricts ref to main,
69
+ # and the SHA is the triggering commit id.
70
+ {
71
+ echo "### Docs site dispatch"
72
+ echo "- Target repository: \`shakacode/controlplaneflow-com\`"
73
+ echo "- Event type: \`docs-updated\`"
74
+ echo "- Source ref: \`${DISPATCH_REF}\`"
75
+ echo "- Source SHA: \`${DISPATCH_SHA}\`"
76
+ echo "- Reason: \`${safe_dispatch_reason}\`"
77
+ echo "- Dispatch outcome: \`${DISPATCH_OUTCOME}\`"
78
+ echo
79
+ if [ "$DISPATCH_OUTCOME" = "success" ]; then
80
+ echo "Check the target repository workflow runs for downstream rebuild status."
81
+ elif [ "$DISPATCH_OUTCOME" = "skipped" ]; then
82
+ echo "Dispatch was skipped because a prior step did not complete successfully."
83
+ elif [ "$DISPATCH_OUTCOME" = "failure" ]; then
84
+ echo "Dispatch step failed. Inspect this job's logs and verify the App token has Contents: Write on the target repo."
85
+ elif [ "$DISPATCH_OUTCOME" = "cancelled" ]; then
86
+ echo "Dispatch step was interrupted mid-execution. Check the target repo's workflow runs because the event may or may not have been received."
87
+ else
88
+ echo "Dispatch outcome: ${DISPATCH_OUTCOME}. Inspect this job's logs before checking the target repository workflow runs."
89
+ fi
90
+ } >> "$GITHUB_STEP_SUMMARY"
data/.rubocop.yml CHANGED
@@ -4,7 +4,7 @@ plugins:
4
4
 
5
5
  AllCops:
6
6
  TargetRubyVersion: 3.0
7
- NewCops: disable
7
+ NewCops: enable
8
8
 
9
9
  Metrics/AbcSize:
10
10
  Enabled: false
@@ -24,3 +24,16 @@ RSpec/MultipleExpectations:
24
24
  RSpec/NestedGroups:
25
25
  Enabled: true
26
26
  Max: 5
27
+
28
+ # Test support and Rails dummy app scripts intentionally write to stdout for
29
+ # verbose test output and setup messages — not actual specs.
30
+ RSpec/Output:
31
+ Exclude:
32
+ - spec/support/**/*
33
+ - spec/dummy/**/*
34
+
35
+ # Empty blocks in this spec are intentional — the DSL must render empty
36
+ # Terraform blocks correctly.
37
+ Lint/EmptyBlock:
38
+ Exclude:
39
+ - spec/core/terraform_config/dsl_spec.rb
data/CHANGELOG.md CHANGED
@@ -12,6 +12,46 @@ In addition to the standard keepachangelog.com categories, this project uses a l
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [5.0.0] - 2026-05-23
16
+
17
+ ### Breaking Changes
18
+
19
+ - Promotes the 5.x release-candidate line to stable. The breaking changes introduced during the 5.0.0 prerelease cycle are documented in `5.0.0.rc.1`, including the Ruby 3.0 minimum, `cpflow exists` not-found exit code change, and generated Dockerfile production-group behavior.
20
+
21
+ ### Changed
22
+
23
+ - Promoted the 5.0.0 release-candidate line to stable. This release includes the generated GitHub Actions flow, upstream reusable workflow model, Node 24 action updates, richer review-app command feedback, generated downstream pin/validation helpers, and cleanup/run/deploy fixes documented in `5.0.0.rc.3`.
24
+
25
+ ### Fixed
26
+
27
+ - **Fixed packaged `cpflow` gems failing to boot without the development-only `debug` gem installed.** [PR 314](https://github.com/shakacode/control-plane-flow/pull/314) by [Justin Gordon](https://github.com/justin808). The hidden `cpflow test` command no longer requires `debug` while loading the CLI, and coverage now exercises loading `cpflow` with `require "debug"` blocked.
28
+
29
+ ## [5.0.0.rc.3] - 2026-05-23
30
+
31
+ ### Added
32
+
33
+ - **Added `--mode=stop` to `cleanup-stale-apps` for reversible idle-app handling.** [PR 302](https://github.com/shakacode/control-plane-flow/pull/302) by [Justin Gordon](https://github.com/justin808). Suspends all workloads via `cpflow ps:stop` instead of deleting; restore with `cpflow ps:start`. Default `--mode=delete` preserves existing behavior. Fixes [issue 295](https://github.com/shakacode/control-plane-flow/issues/295).
34
+ - **Added generated local helpers for downstream GitHub Actions ref pinning and validation.** [PR 308](https://github.com/shakacode/control-plane-flow/pull/308) by [Justin Gordon](https://github.com/justin808). `cpflow generate-github-actions` now writes `bin/pin-cpflow-github-ref` and `bin/test-cpflow-github-flow` so downstream repos can safely pin wrappers to release tags or full upstream commit SHAs, validate wrapper ref consistency, and test unreleased upstream workflow changes without using moving branch refs.
35
+ - **Added richer generated review-app command feedback.** [PR 304](https://github.com/shakacode/control-plane-flow/pull/304) by [Justin Gordon](https://github.com/justin808). Generated review-app deploy/delete/help workflows now react promptly to commands, publish structured progress/success/failure comments, and support `REVIEW_APP_DEPLOYING_ICON_URL` or `none` for the deploying indicator.
36
+
37
+ ### Changed
38
+
39
+ - **Changed generated GitHub Actions output to thin downstream wrappers backed by upstream reusable workflows and shared composite actions.** [PR 305](https://github.com/shakacode/control-plane-flow/pull/305) by [Justin Gordon](https://github.com/justin808). Deployment, deletion, staging, promotion, cleanup, comment formatting, Control Plane setup, and Docker image build logic now live upstream in this repository; `CPFLOW_GITHUB_ACTIONS_REF` controls which upstream ref generated wrappers use.
40
+ - **Documented the downstream testing and release model for reusable GitHub Actions.** [PR 308](https://github.com/shakacode/control-plane-flow/pull/308) by [Justin Gordon](https://github.com/justin808). The CI automation docs now spell out what is tied to the upstream GitHub ref, what is tied to the RubyGems version, how `CPFLOW_VERSION` changes runtime installation, and how to test an unmerged upstream PR from a downstream app.
41
+ - **Updated generated GitHub Actions workflow templates to Node 24-compatible action versions.** [PR 303](https://github.com/shakacode/control-plane-flow/pull/303) by [Justin Gordon](https://github.com/justin808). Uses `actions/checkout@v6` and `actions/github-script@v8`.
42
+ - **Shortened the generated review-app command quick reference.** [PR 290](https://github.com/shakacode/control-plane-flow/pull/290) by [Justin Gordon](https://github.com/justin808). Keeps the three `+review-app-*` commands and the setup-help pointer while reducing repeated wording in the PR-open comment.
43
+
44
+ ### Fixed
45
+
46
+ - **Relaxed the `thor` runtime dependency from `~> 1.4` to `~> 1.3`.** [PR 291](https://github.com/shakacode/control-plane-flow/pull/291) by [Justin Gordon](https://github.com/justin808). Allows cpflow to be bundled into Rails 8 apps that pull in `solid_queue` 1.1.0, which pins `thor ~> 1.3.1`. Fixes [issue 264](https://github.com/shakacode/control-plane-flow/issues/264).
47
+ - **Fixed `deploy-image` showing container names instead of workload names in deploy progress and endpoint summaries.** [PR 294](https://github.com/shakacode/control-plane-flow/pull/294) by [Justin Gordon](https://github.com/justin808). Also guards against duplicate work per workload by deploying only the first container whose image matches the app-image pattern. Fixes [issue 255](https://github.com/shakacode/control-plane-flow/issues/255).
48
+ - **Fixed `cpflow run` interactive sessions printing a confusing non-zero exit error on command failure or session close.** [PR 301](https://github.com/shakacode/control-plane-flow/pull/301) by [Justin Gordon](https://github.com/justin808). cpflow now prints an actionable `cpflow ps:stop` hint instead; exit code 64 is returned for non-zero exits and 130 for signal termination so scripted callers can still detect failure. Fixes [issue 199](https://github.com/shakacode/control-plane-flow/issues/199).
49
+ - **Fixed `cpflow generate` and generated GitHub Actions edge cases.** [PR 288](https://github.com/shakacode/control-plane-flow/pull/288) by [Justin Gordon](https://github.com/justin808). Detects nested production SQLite configs, normalizes folded Shakapacker precompile hooks, gates delete-review-app steps on config readiness, routes `github-script` values through environment variables, and honors HTTPS proxy settings during registry readiness checks.
50
+ - **Fixed generated `cpflow-setup-environment` Ruby setup when downstream apps do not provide a Ruby version file.** [PR 306](https://github.com/shakacode/control-plane-flow/pull/306) by [Justin Gordon](https://github.com/justin808). The shared action now auto-detects `.ruby-version`, `.tool-versions`, `mise.toml`, `.mise.toml`, or a `Gemfile` Ruby directive and falls back to Ruby 3.2 when none is present.
51
+ - **Fixed `cpflow-detect-release-phase` failing with a raw Ruby traceback when `.controlplane/controlplane.yml` is missing.** [PR 299](https://github.com/shakacode/control-plane-flow/pull/299) by [Justin Gordon](https://github.com/justin808). The generated composite action now exits with a clear missing-config message.
52
+ - **Fixed generated review-app delete workflows running `cpflow delete` from the wrong checkout.** [PR 307](https://github.com/shakacode/control-plane-flow/pull/307) by [Justin Gordon](https://github.com/justin808). The generated workflow checks out the downstream repository into `app/` and runs setup/delete from that working directory so `.controlplane/controlplane.yml` is available.
53
+ - **Fixed `cleanup-stale-apps` skipping image-less GVCs indefinitely.** [PR 310](https://github.com/shakacode/control-plane-flow/pull/310) by [Justin Gordon](https://github.com/justin808). Image-less GVCs now fall back to the GVC `created` timestamp for stale-app detection, while GVCs missing `created` are skipped instead of raising.
54
+
15
55
  ## [5.0.0.rc.1] - 2026-05-11
16
56
 
17
57
  ### Breaking Changes
@@ -313,7 +353,9 @@ Deprecated `cpl` gem. New gem is `cpflow`.
313
353
 
314
354
  First release.
315
355
 
316
- [Unreleased]: https://github.com/shakacode/control-plane-flow/compare/v5.0.0.rc.1...HEAD
356
+ [Unreleased]: https://github.com/shakacode/control-plane-flow/compare/v5.0.0...HEAD
357
+ [5.0.0]: https://github.com/shakacode/control-plane-flow/compare/v5.0.0.rc.3...v5.0.0
358
+ [5.0.0.rc.3]: https://github.com/shakacode/control-plane-flow/compare/v5.0.0.rc.1...v5.0.0.rc.3
317
359
  [5.0.0.rc.1]: https://github.com/shakacode/control-plane-flow/compare/v4.2.0...v5.0.0.rc.1
318
360
  [4.2.0]: https://github.com/shakacode/control-plane-flow/compare/v4.1.1...v4.2.0
319
361
  [4.1.1]: https://github.com/shakacode/control-plane-flow/compare/v4.1.0...v4.1.1
data/CONTRIBUTING.md CHANGED
@@ -27,6 +27,33 @@ gem install overcommit
27
27
  overcommit --install
28
28
  ```
29
29
 
30
+ ## Docs Site Dispatch
31
+
32
+ The `trigger-docs-site.yml` workflow notifies `shakacode/controlplaneflow-com` when docs-related files change on `main`.
33
+ It requires these repository secrets:
34
+
35
+ - `DOCS_DISPATCH_APP_ID`: the GitHub App ID used to create the dispatch token
36
+ - `DOCS_DISPATCH_APP_KEY`: the GitHub App private key PEM for that app
37
+
38
+ GitHub's REST docs for the repository dispatch endpoint list **Contents: Write** as the required repository permission for
39
+ GitHub App installation tokens. This is separate from **Actions: Write**, which is used by other workflow APIs. Install
40
+ the app on `shakacode/controlplaneflow-com` with **Contents: Write**. If the dispatch succeeds but the docs site does not
41
+ rebuild, check the target repo's workflow runs for the matching `docs-updated` event.
42
+
43
+ Manual runs should be started from `main`; non-main manual dispatches are skipped before token generation or docs-site
44
+ notification. The GitHub Actions run can therefore finish without dispatching anything when a non-main ref is selected.
45
+ The workflow deduplicates runs by workflow and ref with `cancel-in-progress: true`, so consecutive `main` runs can
46
+ supersede one another; that is expected because the newest docs state wins.
47
+
48
+ If the job fails and the summary shows `Dispatch outcome: skipped`, the dispatch step did not run because an earlier step
49
+ failed. Check the `Generate GitHub App token` step first, including the App ID, private key secret, installation, and target
50
+ repository permissions.
51
+
52
+ If a manual dispatch is canceled and no later successful `main` run replaces it, re-trigger the manual run from `main`. If
53
+ the run is canceled while the dispatch step is already executing, the target repo may already have received the event; check
54
+ the target repo workflow runs before re-triggering. Duplicate dispatches are harmless, but they can rebuild the docs site
55
+ twice.
56
+
30
57
  ## Testing
31
58
 
32
59
  We use real apps for the tests. You'll need to have full access to a Control Plane org, and then set it as the env var `CPLN_ORG` when running the tests (or in the `.env` file):
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cpflow (5.0.0.rc.1)
4
+ cpflow (5.0.0)
5
5
  dotenv (~> 3.1)
6
6
  jwt (~> 3.1)
7
7
  psych (~> 5.2)
8
- thor (~> 1.4)
8
+ thor (~> 1.3)
9
9
 
10
10
  GEM
11
11
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -65,6 +65,7 @@ Additionally, the documentation includes numerous examples and practical tips fo
65
65
  14. [Migrating Postgres Database from Heroku Infrastructure](https://www.shakacode.com/control-plane-flow/docs/postgres/)
66
66
  15. [Migrating Redis Database from Heroku Infrastructure](https://www.shakacode.com/control-plane-flow/docs/redis/)
67
67
  16. [Tips](https://www.shakacode.com/control-plane-flow/docs/tips/)
68
+ 17. [Thruster HTTP/2 Proxy on Control Plane](https://www.shakacode.com/control-plane-flow/docs/thruster/)
68
69
 
69
70
  ## Key Features
70
71
 
@@ -278,8 +279,8 @@ aliases:
278
279
  # If not specified, defaults to 21600 (6 hours).
279
280
  runner_job_timeout: 1000
280
281
 
281
- # Apps with a deployed image created before this amount of days will be listed for deletion
282
- # when running the command `cpflow cleanup-stale-apps`.
282
+ # Apps with a deployed image, or an image-less GVC, created before this amount of days
283
+ # will be listed for deletion when running the command `cpflow cleanup-stale-apps`.
283
284
  stale_app_image_deployed_days: 5
284
285
 
285
286
  # Images that exceed this quantity will be listed for deletion
@@ -357,9 +358,12 @@ cpflow generate
357
358
 
358
359
  # Create reusable GitHub Actions for review apps, staging, and production promotion
359
360
  cpflow generate-github-actions
361
+
362
+ # Validate the generated GitHub Actions wrapper refs and metadata
363
+ bin/test-cpflow-github-flow
360
364
  ```
361
365
 
362
- `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` 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, start with the [AI rollout prompt](./docs/ai-github-flow-prompt.md) or run `cpflow ai-github-flow-prompt` in the target repo rather than giving a vague "set up CI" request.
366
+ `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, start with the [AI rollout prompt](./docs/ai-github-flow-prompt.md) or run `cpflow ai-github-flow-prompt` in the target repo rather than giving a vague "set up CI" request.
363
367
 
364
368
  For a live example, see the [react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/.controlplane/readme.md) repository.
365
369
 
data/cpflow.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.add_dependency "dotenv", "~> 3.1"
19
19
  spec.add_dependency "jwt", "~> 3.1"
20
20
  spec.add_dependency "psych", "~> 5.2"
21
- spec.add_dependency "thor", "~> 1.4"
21
+ spec.add_dependency "thor", "~> 1.3"
22
22
 
23
23
  spec.files = `git ls-files -z`.split("\x0").reject do |file|
24
24
  file.match(%r{^(coverage|pkg|spec|tmp)/})
@@ -44,7 +44,7 @@ Stop and report the blocker instead of generating `cpflow-*` workflow files when
44
44
  The rollout is done when all of the following are true:
45
45
 
46
46
  - `.controlplane/` exists and matches the actual app shape
47
- - `.github/actions/cpflow-*` and `.github/workflows/cpflow-*` are in place
47
+ - `.github/cpflow-help.md` and `.github/workflows/cpflow-*` wrappers are in place
48
48
  - review apps are opt-in, staging auto-deploys from one branch, and production promotion is manual
49
49
  - required GitHub secrets and variables are documented for the repo
50
50
  - the production image build path is validated for the real app
@@ -0,0 +1,46 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title desc">
2
+ <title id="title">Deploying</title>
3
+ <desc id="desc">Animated deployment progress icon for Control Plane Flow review apps.</desc>
4
+ <defs>
5
+ <linearGradient id="ring" x1="8" x2="56" y1="8" y2="56" gradientUnits="userSpaceOnUse">
6
+ <stop offset="0" stop-color="#3ddc97"/>
7
+ <stop offset="0.5" stop-color="#4f8cff"/>
8
+ <stop offset="1" stop-color="#ffb84d"/>
9
+ </linearGradient>
10
+ <filter id="soft-shadow" x="-20%" y="-20%" width="140%" height="140%">
11
+ <feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#233044" flood-opacity="0.22"/>
12
+ </filter>
13
+ </defs>
14
+
15
+ <circle cx="32" cy="32" r="28" fill="#f7fbff"/>
16
+ <circle cx="32" cy="32" r="23" fill="none" stroke="#d7e4f2" stroke-width="4"/>
17
+
18
+ <g>
19
+ <animateTransform
20
+ attributeName="transform"
21
+ type="rotate"
22
+ from="0 32 32"
23
+ to="360 32 32"
24
+ dur="1.1s"
25
+ repeatCount="indefinite"/>
26
+ <circle
27
+ cx="32"
28
+ cy="32"
29
+ r="23"
30
+ fill="none"
31
+ stroke="url(#ring)"
32
+ stroke-linecap="round"
33
+ stroke-width="4"
34
+ stroke-dasharray="40 110"/>
35
+ </g>
36
+
37
+ <g filter="url(#soft-shadow)">
38
+ <path d="M35.7 13.5c-7.8 3.1-13 10-15.8 20.8l9.8 9.8c10.8-2.8 17.7-8 20.8-15.8 1.7-4.4 1.4-9.7-.9-14-4.3-2.2-9.6-2.6-13.9-.8Z" fill="#ffffff"/>
39
+ <path d="M35.7 13.5c-7.8 3.1-13 10-15.8 20.8l9.8 9.8c10.8-2.8 17.7-8 20.8-15.8 1.7-4.4 1.4-9.7-.9-14-4.3-2.2-9.6-2.6-13.9-.8Z" fill="#4f8cff" opacity="0.18"/>
40
+ <path d="M35.7 13.5c-7.8 3.1-13 10-15.8 20.8l9.8 9.8c10.8-2.8 17.7-8 20.8-15.8 1.7-4.4 1.4-9.7-.9-14-4.3-2.2-9.6-2.6-13.9-.8Z" fill="none" stroke="#233044" stroke-width="2" stroke-linejoin="round"/>
41
+ <circle cx="39.7" cy="24.3" r="4.5" fill="#3ddc97" stroke="#233044" stroke-width="2"/>
42
+ <path d="M20.2 34.1 13.5 36l7.1 7.1 2-6.6-2.4-2.4Z" fill="#ffb84d" stroke="#233044" stroke-width="2" stroke-linejoin="round"/>
43
+ <path d="M29.9 43.8 28 50.5l-7.1-7.1 6.6-2 2.4 2.4Z" fill="#ff6b6b" stroke="#233044" stroke-width="2" stroke-linejoin="round"/>
44
+ <path d="M18.8 45.2c-2.4.7-4.1 2.4-5.2 5.2 2.8-1.1 4.5-2.8 5.2-5.2Z" fill="#ffb84d"/>
45
+ </g>
46
+ </svg>
@@ -16,7 +16,7 @@ End-to-end rollout in one view:
16
16
 
17
17
  1. `cpflow github-flow-readiness` — exits non-zero if the repo is not ready to deploy.
18
18
  2. `cpflow generate` — creates `.controlplane/` if missing.
19
- 3. `cpflow generate-github-actions` — adds the `cpflow-*` composite actions and workflows.
19
+ 3. `cpflow generate-github-actions` — adds thin `cpflow-*` workflow wrappers that call upstream reusable workflows.
20
20
  4. Configure the GitHub [repository secrets and variables](#required-github-repository-settings) the workflows expect.
21
21
  5. Push the branch, then comment `+review-app-deploy` on a PR to spin up a review environment.
22
22
 
@@ -52,10 +52,7 @@ not appear to exist in the public registries.
52
52
 
53
53
  The second command writes namespaced files so they can coexist with an app's existing CI:
54
54
 
55
- - `.github/actions/cpflow-build-docker-image/action.yml`
56
- - `.github/actions/cpflow-delete-control-plane-app/action.yml`
57
- - `.github/actions/cpflow-delete-control-plane-app/delete-app.sh`
58
- - `.github/actions/cpflow-setup-environment/action.yml`
55
+ - `.github/cpflow-help.md`
59
56
  - `.github/workflows/cpflow-review-app-help.yml`
60
57
  - `.github/workflows/cpflow-help-command.yml`
61
58
  - `.github/workflows/cpflow-deploy-review-app.yml`
@@ -63,6 +60,8 @@ The second command writes namespaced files so they can coexist with an app's exi
63
60
  - `.github/workflows/cpflow-deploy-staging.yml`
64
61
  - `.github/workflows/cpflow-promote-staging-to-production.yml`
65
62
  - `.github/workflows/cpflow-cleanup-stale-review-apps.yml`
63
+ - `bin/pin-cpflow-github-ref`
64
+ - `bin/test-cpflow-github-flow`
66
65
 
67
66
  `cpflow generate` also infers the app prefix from the repo directory, infers the
68
67
  Docker base Ruby version from `.ruby-version`, `.tool-versions`, or the app's
@@ -253,14 +252,118 @@ The action will start an SSH agent, add the key, write `known_hosts`, and pass `
253
252
  - Runs nightly and on demand.
254
253
  - Deletes stale review apps using `cpflow cleanup-stale-apps`.
255
254
 
256
- ## Composite Actions
255
+ ## Upstream Reusable Workflows
257
256
 
258
- The generated workflows share these local composite actions:
257
+ The generated workflows are intentionally small wrappers. The deployment logic,
258
+ comment formatting, Control Plane CLI setup, Docker image build, and cleanup helpers
259
+ live in upstream reusable workflows and composite actions in this repository.
259
260
 
260
- - `cpflow-setup-environment`: installs Ruby, the Control Plane CLI, and the `cpflow` gem, then logs into the target org
261
+ - `cpflow-setup-environment`: installs Ruby, the Control Plane CLI, and `cpflow`, then logs into the target org. By default it builds `cpflow` from the checked-out upstream reusable-workflow ref; set the `CPFLOW_VERSION` repository variable only when you want to force a published RubyGems release.
261
262
  - `cpflow-build-docker-image`: builds and pushes the app image with the desired commit SHA
262
263
  - `cpflow-delete-control-plane-app`: safely deletes temporary apps and refuses to touch names outside the configured review-app prefix
263
264
 
265
+ ## Version Pins: GitHub Ref vs RubyGems
266
+
267
+ The generated `cpflow-*` workflow files are thin wrappers around reusable
268
+ workflows in `shakacode/control-plane-flow`. GitHub loads reusable workflows
269
+ from a repository ref, not from the Ruby gem, so each wrapper has an upstream
270
+ GitHub ref:
271
+
272
+ ```yaml
273
+ uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-review-app.yml@<ref>
274
+ with:
275
+ control_plane_flow_ref: <ref>
276
+ ```
277
+
278
+ Those two pins must stay in sync:
279
+
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.
284
+
285
+ The stable release path is still gem-driven:
286
+
287
+ 1. Publish a `cpflow` gem.
288
+ 2. Install or bundle that released gem in the downstream project.
289
+ 3. Run `cpflow generate-github-actions`.
290
+ 4. Commit the generated wrappers that point to the matching upstream release tag
291
+ such as `v5.0.0`.
292
+
293
+ That release tag should point to the same source that produced the RubyGems
294
+ release. Downstream production automation should use release tags, not `main` or
295
+ feature-branch refs.
296
+
297
+ `CPFLOW_VERSION` is a runtime override. If a downstream repository sets the
298
+ `CPFLOW_VERSION` variable, the setup action runs `gem install cpflow -v
299
+ <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`.
303
+
304
+ ## Testing Unreleased Upstream Changes Downstream
305
+
306
+ You can test a `control-plane-flow` PR in a downstream app before merging or
307
+ releasing it. Use an immutable commit SHA from the upstream PR branch:
308
+
309
+ 1. Push the upstream PR branch and copy its full 40-character head SHA.
310
+ 2. In a downstream test branch, run:
311
+
312
+ ```sh
313
+ bin/pin-cpflow-github-ref <upstream-pr-sha>
314
+ ```
315
+
316
+ The helper updates every generated `cpflow-*` workflow wrapper. It accepts
317
+ release tags and full commit SHAs by default, rejects branch names such as
318
+ `main` or `feature/foo`, and requires `--allow-moving-ref` for short-lived
319
+ local experiments that should not be committed.
320
+
321
+ 3. Keep `CPFLOW_VERSION` unset so the workflow builds `cpflow` from the same
322
+ upstream SHA that supplies the reusable workflow and composite actions.
323
+ 4. Run:
324
+
325
+ ```sh
326
+ bin/test-cpflow-github-flow
327
+ ```
328
+
329
+ Pass a local checkout command when you are validating unreleased generator
330
+ code before it is installed:
331
+
332
+ ```sh
333
+ bin/test-cpflow-github-flow ruby /path/to/control-plane-flow/bin/cpflow
334
+ ```
335
+
336
+ 5. Open a downstream PR and trigger the real review app with a comment whose body
337
+ is exactly:
338
+
339
+ ```text
340
+ +review-app-deploy
341
+ ```
342
+
343
+ 6. Verify the deploy logs show the expected upstream commit SHA, the setup step
344
+ prints the expected `cpflow` source/version, and the review app URL returns
345
+ 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.
348
+
349
+ This tests the real reusable workflow, shared composite actions, and source-built
350
+ `cpflow` gem from one immutable upstream commit. It avoids merging upstream blind
351
+ and avoids running production automation against a moving branch.
352
+
353
+ ## Local Generated-Flow Checks
354
+
355
+ Run this after generation or after changing a downstream wrapper ref:
356
+
357
+ ```sh
358
+ bin/test-cpflow-github-flow
359
+ ```
360
+
361
+ The helper runs `cpflow github-flow-readiness`, parses generated workflow YAML,
362
+ 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`.
366
+
264
367
  ## Applying This to React on Rails Demo Apps
265
368
 
266
369
  This flow is a good fit for the React on Rails demo apps because they already follow the same basic assumptions:
data/docs/commands.md CHANGED
@@ -78,15 +78,21 @@ cpflow cleanup-images -a $APP_NAME
78
78
 
79
79
  ### `cleanup-stale-apps`
80
80
 
81
- - Deletes the whole app (GVC with all workloads, all volumesets and all images) for all stale apps
82
- - Also unbinds the app from the secrets policy, as long as both the identity and the policy exist (and are bound)
83
- - Stale apps are identified based on the creation date of the latest image
81
+ - Acts on stale apps based on the creation date of the latest image, or the GVC if no images exist
82
+ - With `--mode=delete` (default): deletes the whole app (GVC with all workloads, all volumesets and all images), and unbinds the app from the secrets policy as long as both the identity and the policy exist (and are bound)
83
+ - With `--mode=stop`: suspends all workloads via `cpflow ps:stop` no GVC, volumeset, or image is removed; resume with `cpflow ps:start`
84
+ - `--mode=stop` only suspends workloads listed in `app_workloads` + `additional_workloads`; workloads present in the live GVC but missing from the config are skipped silently
85
+ - `--mode=stop` returns once each workload is marked suspended; it does not wait for the workload to reach a not-ready state
84
86
  - Specify the amount of days after an app should be considered stale through `stale_app_image_deployed_days` in the `.controlplane/controlplane.yml` file
85
- - If `match_if_app_name_starts_with` is `true` in the `.controlplane/controlplane.yml` file, it will delete all stale apps that start with the name
87
+ - If `match_if_app_name_starts_with` is `true` in the `.controlplane/controlplane.yml` file, it will act on all stale apps that start with the name
86
88
  - Will ask for explicit user confirmation
87
89
 
88
90
  ```sh
91
+ # Deletes stale apps (default).
89
92
  cpflow cleanup-stale-apps -a $APP_NAME
93
+
94
+ # Stops stale apps instead of deleting them; resume with `cpflow ps:start`.
95
+ cpflow cleanup-stale-apps -a $APP_NAME --mode=stop
90
96
  ```
91
97
 
92
98
  ### `config`
@@ -214,7 +220,7 @@ other than `main` or `master`; the generator will bake that branch into the
214
220
  GitHub Actions push trigger and use it as the default STAGING_APP_BRANCH.
215
221
 
216
222
  ```sh
217
- # Creates .github/actions and .github/workflows files for the Control Plane flow
223
+ # Creates thin .github/workflows wrappers for the Control Plane flow
218
224
  cpflow generate-github-actions
219
225
 
220
226
  # Creates the flow with staging deploys triggered from develop