cpflow 5.1.0 → 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/actions/cpflow-wait-for-health/action.yml +11 -4
- 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/cpflow-promote-staging-to-production.yml +224 -37
- data/.github/workflows/rspec-shared.yml +15 -1
- data/.github/workflows/rspec-specific.yml +1 -0
- data/.github/workflows/rspec.yml +58 -1
- data/AGENTS.md +57 -0
- data/CHANGELOG.md +36 -1
- data/CLAUDE.md +3 -0
- data/CONTRIBUTING.md +6 -2
- data/Gemfile.lock +1 -1
- data/README.md +25 -7
- data/docs/ai-github-flow-prompt.md +18 -16
- data/docs/assets/logo/favicon.ico +0 -0
- data/docs/assets/logo/icon-1024.png +0 -0
- data/docs/assets/logo/icon-128.png +0 -0
- data/docs/assets/logo/icon-16.png +0 -0
- data/docs/assets/logo/icon-192.png +0 -0
- data/docs/assets/logo/icon-24.png +0 -0
- data/docs/assets/logo/icon-32.png +0 -0
- data/docs/assets/logo/icon-48.png +0 -0
- data/docs/assets/logo/icon-512.png +0 -0
- data/docs/assets/logo/icon-64.png +0 -0
- data/docs/assets/logo/icon-tile.svg +17 -0
- data/docs/assets/logo/mark-transparent.svg +16 -0
- data/docs/ci-automation.md +203 -15
- data/docs/commands.md +16 -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/maintenance_off.rb +1 -0
- data/lib/command/maintenance_on.rb +1 -0
- data/lib/command/promote_app_from_upstream.rb +1 -0
- data/lib/command/ps_wait.rb +2 -10
- data/lib/command/run.rb +25 -5
- data/lib/core/config.rb +94 -0
- data/lib/core/doctor_service.rb +44 -3
- data/lib/core/maintenance_mode.rb +93 -6
- 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 +23 -1
- data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +224 -39
- metadata +33 -2
data/docs/tips.md
CHANGED
|
@@ -6,18 +6,28 @@
|
|
|
6
6
|
4. [CPU](#cpu)
|
|
7
7
|
5. [Remote IP](#remote-ip)
|
|
8
8
|
6. [Secrets and ENV Values](/docs/secrets-and-env-values.md)
|
|
9
|
-
7. [
|
|
10
|
-
8. [
|
|
11
|
-
9. [
|
|
12
|
-
10. [
|
|
9
|
+
7. [Telemetry](#telemetry)
|
|
10
|
+
8. [CI](#ci)
|
|
11
|
+
9. [Logs](#logs)
|
|
12
|
+
10. [Grafana and OpenTelemetry](#grafana-and-opentelemetry)
|
|
13
|
+
11. [Memcached](#memcached)
|
|
14
|
+
12. [Sidekiq](#sidekiq)
|
|
13
15
|
- [Quieting Non-Critical Workers During Deployments](#quieting-non-critical-workers-during-deployments)
|
|
14
16
|
- [Setting Up a Pre Stop Hook](#setting-up-a-pre-stop-hook)
|
|
15
17
|
- [Setting Up a Liveness Probe](#setting-up-a-liveness-probe)
|
|
16
|
-
|
|
17
|
-
- [
|
|
18
|
+
13. [Minimizing Non-Production App Costs](#minimizing-non-production-app-costs)
|
|
19
|
+
- [Share One Control Plane Postgres for Staging and Review Apps](#share-one-control-plane-postgres-for-staging-and-review-apps)
|
|
20
|
+
- [Enable Capacity AI for Demo and Starter Staging Apps](#enable-capacity-ai-for-demo-and-starter-staging-apps)
|
|
18
21
|
- [Delete or Pause Abandoned Apps with `cleanup-stale-apps`](#delete-or-pause-abandoned-apps-with-cleanup-stale-apps)
|
|
19
22
|
- [Pause and Resume with `ps:stop` / `ps:start`](#pause-and-resume-with-psstop--psstart)
|
|
20
|
-
|
|
23
|
+
14. [Right-Sizing Non-Production Workloads](#right-sizing-non-production-workloads)
|
|
24
|
+
- [Enable Capacity AI on Idle Workloads](#enable-capacity-ai-on-idle-workloads)
|
|
25
|
+
- [Don't Autoscale Idle Workloads on CPU](#dont-autoscale-idle-workloads-on-cpu)
|
|
26
|
+
- [Right-Size Reserved CPU and Memory](#right-size-reserved-cpu-and-memory)
|
|
27
|
+
- [Drop Workloads You Don't Use](#drop-workloads-you-dont-use)
|
|
28
|
+
- [Share One Postgres Across Non-Production Apps](#share-one-postgres-across-non-production-apps)
|
|
29
|
+
- [Keep Templates as the Source of Truth](#keep-templates-as-the-source-of-truth)
|
|
30
|
+
15. [Useful Links](#useful-links)
|
|
21
31
|
|
|
22
32
|
## GVCs vs. Orgs
|
|
23
33
|
|
|
@@ -114,6 +124,14 @@ So `REMOTE_ADDR` should not be used directly, only `request.remote_ip`.
|
|
|
114
124
|
> **Warning:** Do not use `REMOTE_ADDR` for authentication, rate limiting, auditing, or IP allowlists. Always use
|
|
115
125
|
> framework-specific mechanisms that understand proxy headers (such as Rails' `request.remote_ip`).
|
|
116
126
|
|
|
127
|
+
## Telemetry
|
|
128
|
+
|
|
129
|
+
If your app emits OpenTelemetry, StatsD, or structured log signals, run an
|
|
130
|
+
OpenTelemetry Collector as a Control Plane workload in the same GVC and point
|
|
131
|
+
application env vars at the collector's internal service name. See the
|
|
132
|
+
[telemetry guide](https://www.shakacode.com/control-plane-flow/docs/telemetry/) for the template shape, recommended ports,
|
|
133
|
+
review-app guardrails, and troubleshooting commands.
|
|
134
|
+
|
|
117
135
|
## CI
|
|
118
136
|
|
|
119
137
|
**Note:** Docker builds much slower on Apple Silicon, so try configuring CI to build the images when using Apple
|
|
@@ -202,6 +220,15 @@ To check for truncation, compare line count to the limit: `wc -l < incident.log`
|
|
|
202
220
|
likely cut off. Prefer narrowing the time window (and concatenating the sub-ranges) over raising `--limit`, since the
|
|
203
221
|
server-side cap may be lower than the flag value.
|
|
204
222
|
|
|
223
|
+
## Grafana and OpenTelemetry
|
|
224
|
+
|
|
225
|
+
Control Plane's built-in Grafana gives useful workload metrics such as CPU, memory, restarts, and request rate. For
|
|
226
|
+
Rails applications that need app-level request latency, database spans, Redis spans, Sidekiq job metrics, or
|
|
227
|
+
trace-to-log correlation, add OpenTelemetry and an internal collector workload that exposes generated Prometheus
|
|
228
|
+
metrics.
|
|
229
|
+
|
|
230
|
+
See [Grafana and OpenTelemetry on Control Plane](/docs/grafana-opentelemetry.md) for the full setup guide.
|
|
231
|
+
|
|
205
232
|
## Memcached
|
|
206
233
|
|
|
207
234
|
On the workload container for Memcached (using the `memcached:alpine` image), configure the command with the args
|
|
@@ -266,49 +293,328 @@ To do this:
|
|
|
266
293
|
|
|
267
294
|
To set up a liveness probe on port 7433, see: https://github.com/arturictus/sidekiq_alive
|
|
268
295
|
|
|
269
|
-
## Minimizing
|
|
296
|
+
## Minimizing Non-Production App Costs
|
|
270
297
|
|
|
271
298
|
Long-tail review apps — PRs that linger for days or weeks with little traffic — can drive up Control Plane spend if every
|
|
272
299
|
workload runs full-time. `cpflow` already provides several knobs to manage this without custom orchestration.
|
|
300
|
+
The same cost-control pass applies to public demos, starter staging apps, and long-lived review apps: start with
|
|
301
|
+
Capacity AI for app workloads, then reserve true scale-to-zero for apps where cold starts and planned migrations are
|
|
302
|
+
acceptable.
|
|
273
303
|
|
|
274
304
|
> **Note:** Scaling workloads to zero or stopping review apps does not reduce costs from external databases, managed
|
|
275
305
|
> Redis instances, object storage, or other third-party services. Those continue to bill independently of Control Plane
|
|
276
306
|
> workload state.
|
|
277
307
|
|
|
278
|
-
###
|
|
308
|
+
### Share One Control Plane Postgres for Staging and Review Apps
|
|
309
|
+
|
|
310
|
+
For non-production Rails apps, a per-GVC Postgres workload is often the largest avoidable review-app cost. Each app can
|
|
311
|
+
end up with its own always-on Postgres replica and its own volume. If staging/review data can be reset, create one
|
|
312
|
+
shared Postgres GVC in the staging org, then point staging and review app GVCs at separate logical databases inside that
|
|
313
|
+
single Postgres instance.
|
|
314
|
+
|
|
315
|
+
Use separate logical databases per app or review app. Do not point multiple Rails apps at the same database/schema unless
|
|
316
|
+
they intentionally share migrations and data. For example:
|
|
279
317
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
318
|
+
```text
|
|
319
|
+
Shared GVC (in the staging org):
|
|
320
|
+
staging-shared-postgres
|
|
321
|
+
postgres workload
|
|
322
|
+
shared-postgres-vs volume
|
|
323
|
+
|
|
324
|
+
Client GVCs (each points to a separate logical database):
|
|
325
|
+
react-webpack-rails-tutorial-staging
|
|
326
|
+
react-webpack-rails-tutorial-review-pr-123
|
|
327
|
+
react-on-rails-starter-staging
|
|
328
|
+
react-on-rails-starter-review-pr-123
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The shared Postgres workload must accept internal traffic from other GVCs. `same-gvc` is not enough when the database
|
|
332
|
+
lives in a separate GVC; use `same-org`, or `workload-list` if you can keep an explicit allowlist current. `same-org` is
|
|
333
|
+
convenient for trusted staging orgs, but every workload in the org can reach the database port, including production
|
|
334
|
+
workloads if production GVCs share the same org. Use `workload-list` for a tighter blast radius if you can automate
|
|
335
|
+
entries as review apps appear and disappear.
|
|
285
336
|
|
|
286
337
|
```yaml
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
338
|
+
kind: volumeset
|
|
339
|
+
name: shared-postgres-vs
|
|
340
|
+
spec:
|
|
341
|
+
fileSystemType: ext4
|
|
342
|
+
initialCapacity: 10
|
|
343
|
+
performanceClass: general-purpose-ssd
|
|
344
|
+
snapshots:
|
|
345
|
+
createFinalSnapshot: true
|
|
346
|
+
retentionDuration: 7d
|
|
347
|
+
# Periodic snapshots need a schedule; without one, only a final snapshot is taken when the volumeset is deleted.
|
|
348
|
+
schedule: "0 2 * * *" # daily at 02:00 UTC; adjust to your retention needs
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
kind: workload
|
|
352
|
+
name: postgres
|
|
353
|
+
spec:
|
|
354
|
+
type: stateful
|
|
355
|
+
containers:
|
|
356
|
+
- name: postgres
|
|
357
|
+
image: postgres:17 # pin a specific patch (e.g. postgres:17.x) for reproducible stateful deploys
|
|
358
|
+
cpu: 250m
|
|
359
|
+
memory: 512Mi
|
|
360
|
+
env:
|
|
361
|
+
- name: PGDATA
|
|
362
|
+
value: /var/lib/postgresql/data/pg_data
|
|
363
|
+
- name: POSTGRES_DB
|
|
364
|
+
value: postgres
|
|
365
|
+
- name: POSTGRES_USER
|
|
366
|
+
value: postgres
|
|
367
|
+
- name: POSTGRES_PASSWORD
|
|
368
|
+
# Recommended after adding the workload identity/policy binding:
|
|
369
|
+
# value: cpln://secret/shared-postgres-password.password
|
|
370
|
+
# Plain-value fallback for disposable non-production experiments only.
|
|
371
|
+
# Do not commit this file with a real password in place.
|
|
372
|
+
value: REPLACE_WITH_NON_PRODUCTION_PASSWORD
|
|
373
|
+
ports:
|
|
374
|
+
- number: 5432
|
|
375
|
+
protocol: tcp
|
|
376
|
+
volumes:
|
|
377
|
+
- uri: cpln://volumeset/shared-postgres-vs
|
|
378
|
+
path: /var/lib/postgresql/data
|
|
379
|
+
recoveryPolicy: retain # keep the volume if the workload is deleted; clean up manually when no longer needed
|
|
380
|
+
defaultOptions:
|
|
381
|
+
autoscaling:
|
|
382
|
+
metric: disabled
|
|
383
|
+
minScale: 1
|
|
384
|
+
maxScale: 1
|
|
385
|
+
capacityAI: false
|
|
386
|
+
firewallConfig:
|
|
387
|
+
internal:
|
|
388
|
+
inboundAllowType: same-org
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`POSTGRES_DB: postgres` initializes the administrative database for the server. Apps should use their own logical
|
|
392
|
+
databases, not the administrative `postgres` database.
|
|
393
|
+
|
|
394
|
+
Field note: `100m` CPU and `256Mi` memory were enough for tiny Rails migrations, but a real staging seed that inserted
|
|
395
|
+
hundreds of thousands of rows caused Postgres to log `server process ... terminated by signal 9: Killed`. `250m` and
|
|
396
|
+
`512Mi` handled the same seed while still replacing multiple always-on per-app Postgres workloads.
|
|
397
|
+
|
|
398
|
+
For review apps, keep the logical database name unique per app. `cpflow`'s default app template uses `{{APP_NAME}}` in
|
|
399
|
+
both the Postgres host and database name:
|
|
400
|
+
|
|
401
|
+
```yaml
|
|
402
|
+
- name: DATABASE_URL
|
|
403
|
+
value: postgres://the_user:the_password@postgres.{{APP_NAME}}.cpln.local:5432/{{APP_NAME}}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Create a review-only app template by copying `.controlplane/templates/app.yml` to
|
|
407
|
+
`.controlplane/templates/app-review.yml`. In that copy, point `DATABASE_URL` at a per-review-app Control Plane secret.
|
|
408
|
+
Trusted automation should create the secret with a full URL whose database name is still that review app's
|
|
409
|
+
`{{APP_NAME}}`:
|
|
410
|
+
|
|
411
|
+
```yaml
|
|
412
|
+
spec:
|
|
413
|
+
env:
|
|
414
|
+
- name: DATABASE_URL
|
|
415
|
+
value: cpln://secret/{{APP_NAME}}-database.DATABASE_URL
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Control Plane's `cpln://secret/...` syntax replaces the entire env value; it is not substring interpolation, so avoid
|
|
419
|
+
forms such as `postgres://cpln://secret/...@...`. A full per-app URL secret also avoids depending on any runtime
|
|
420
|
+
ordering between secret resolution and `$(VAR)` env-var expansion. Because `{{APP_NAME}}-database` is a separate secret
|
|
421
|
+
from the app dictionary secret, trusted automation must create that secret and add it to the app identity's reveal policy
|
|
422
|
+
before the workload starts; otherwise workloads cannot resolve the `cpln://secret/...` value. For trusted staging/review
|
|
423
|
+
apps where a single shared database role is acceptable, you can still create one URL secret per app that reuses the same
|
|
424
|
+
database user/password while keeping the database name unique.
|
|
425
|
+
|
|
426
|
+
The `cpln://secret/NAME.FIELD` field syntax resolves only against **dictionary** secrets; an `opaque` or `tls` secret
|
|
427
|
+
leaves the workload with an empty or literal `cpln://...` string rather than a clear error. Define the database secret as
|
|
428
|
+
a dictionary, apply it with `cpln apply -f secret.yaml`, and confirm the app identity's policy grants `reveal` on it
|
|
429
|
+
before the workload starts:
|
|
430
|
+
|
|
431
|
+
```yaml
|
|
432
|
+
kind: secret
|
|
433
|
+
name: my-app-review-pr-123-database
|
|
434
|
+
type: dictionary
|
|
435
|
+
data:
|
|
436
|
+
DATABASE_URL: postgres://the_user:the_password@postgres.staging-shared-postgres.cpln.local:5432/my-app-review-pr-123
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
`cpflow` can automate this secret-and-policy wiring. Declare a `shared_secret_grants` entry on the review app and
|
|
440
|
+
reference the generated `{{SHARED_SECRET_DATABASE}}` placeholder in your templates instead of hardcoding the secret name;
|
|
441
|
+
`cpflow setup-app`, `deploy-image`, `delete`, and `cleanup-stale-apps` then keep the policy binding and cleanup automatic.
|
|
442
|
+
See [Shared Secrets for Review Apps](secrets-and-env-values.md#shared-secrets-for-review-apps) for the full setup.
|
|
443
|
+
|
|
444
|
+
`{{APP_NAME}}` keeps databases separate by convention, not by itself as a security boundary. If review apps can run
|
|
445
|
+
untrusted PR code, do not give every review app the same database role with `CREATEDB` or ownership of every review
|
|
446
|
+
database. Prefer one of these safer models:
|
|
447
|
+
|
|
448
|
+
1. Create a database and role per review app, store that app's URL/credentials in its DB secret, and grant the role only to
|
|
449
|
+
its own database.
|
|
450
|
+
2. Keep review app database roles low-privilege and run create/drop cleanup from trusted admin automation against the
|
|
451
|
+
shared Postgres workload.
|
|
452
|
+
|
|
453
|
+
A single shared role/password is acceptable only for trusted staging apps or review apps where database separation is a
|
|
454
|
+
cost-control convenience rather than a security boundary. The hook example below assumes the review app role is allowed
|
|
455
|
+
to create its own logical database; if you choose admin-owned cleanup, run create/drop steps from trusted automation
|
|
456
|
+
instead of from the review app workload.
|
|
457
|
+
|
|
458
|
+
Then point the review-app entry at the review-only template and remove the per-PR Postgres workload:
|
|
459
|
+
|
|
460
|
+
```yaml
|
|
461
|
+
my-app-review:
|
|
462
|
+
match_if_app_name_starts_with: true
|
|
463
|
+
setup_app_templates:
|
|
464
|
+
# postgres removed, so no per-PR database workload is created
|
|
465
|
+
- app-review # was: app
|
|
466
|
+
- redis
|
|
467
|
+
- rails
|
|
468
|
+
additional_workloads:
|
|
469
|
+
- redis # postgres removed
|
|
470
|
+
hooks:
|
|
471
|
+
post_creation: bundle exec rails db:prepare
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
The `post_creation` hook creates only that review app's logical database because the database name is still
|
|
475
|
+
`{{APP_NAME}}`. Do not rely on a generic `pre_deletion: rails db:drop` hook for shared databases: `cpflow delete` runs
|
|
476
|
+
the pre-deletion hook before it removes or suspends the app workloads, so live Rails/worker processes can still hold
|
|
477
|
+
connections and make PostgreSQL reject the drop. Stop the review app workloads first, or run trusted admin cleanup
|
|
478
|
+
against the shared Postgres workload with `DROP DATABASE ... WITH (FORCE)`.
|
|
479
|
+
|
|
480
|
+
Rails apps with multiple production databases need each connection isolated. Either set connection-specific URLs such as
|
|
481
|
+
`CACHE_DATABASE_URL`, `QUEUE_DATABASE_URL`, and `CABLE_DATABASE_URL`, or make the database names in `config/database.yml`
|
|
482
|
+
derive from an app-specific environment variable. If the database names are hard-coded, every review app for that repo
|
|
483
|
+
will collide inside the shared Postgres instance.
|
|
484
|
+
|
|
485
|
+
Suggested cutover order:
|
|
486
|
+
|
|
487
|
+
1. Create the shared Postgres GVC, workload, and volume.
|
|
488
|
+
2. Create the app roles and logical databases, or make sure the app role has `CREATEDB` and let `rails db:prepare`
|
|
489
|
+
create them. For admin-created databases, generate the password in trusted automation, store it in the matching app DB
|
|
490
|
+
secret, then run the setup from an interactive `psql` session so the password is not written into shell history or
|
|
491
|
+
process arguments:
|
|
492
|
+
|
|
493
|
+
```sh
|
|
494
|
+
cpln workload exec postgres --org ORG --gvc staging-shared-postgres --stdin --tty -- psql -U postgres
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Then enter the SQL in `psql`:
|
|
498
|
+
|
|
499
|
+
```sql
|
|
500
|
+
CREATE ROLE "my-app-staging" LOGIN;
|
|
501
|
+
\password "my-app-staging"
|
|
502
|
+
CREATE DATABASE "my-app-staging" OWNER "my-app-staging";
|
|
503
|
+
\connect "my-app-staging"
|
|
504
|
+
GRANT ALL ON SCHEMA public TO "my-app-staging";
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
In CI, run equivalent SQL through a secret-aware step that does not echo the password, add it to process arguments, or
|
|
508
|
+
persist it in logs.
|
|
509
|
+
|
|
510
|
+
3. Update staging/review GVC environment values to the shared host.
|
|
511
|
+
4. Run `cpflow run -a APP -- bin/rails db:prepare` for each app.
|
|
512
|
+
5. Force redeploy app workloads so live replicas pick up the new GVC env.
|
|
513
|
+
6. Stop the old per-app Postgres workloads and smoke test the apps.
|
|
514
|
+
7. Delete the old Postgres workloads and volumes only after smoke tests pass.
|
|
515
|
+
8. When a review app is deleted, drop its logical database from the shared instance so orphaned review databases do not
|
|
516
|
+
accumulate. For the most reliable cleanup, stop the app workloads first, then run the drop from trusted admin
|
|
517
|
+
automation or directly against the shared Postgres workload. Use `WITH (FORCE)` on PostgreSQL 13+ to terminate
|
|
518
|
+
remaining sessions:
|
|
519
|
+
|
|
520
|
+
```sh
|
|
521
|
+
cpln workload exec postgres --org ORG --gvc staging-shared-postgres -- \
|
|
522
|
+
psql -U postgres -c 'DROP DATABASE IF EXISTS "my-app-review-pr-123" WITH (FORCE);'
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
`cpln workload exec` runs `psql` inside the container over its local Unix socket, which the official Postgres image
|
|
526
|
+
grants the `postgres` superuser `trust` auth — so no `PGPASSWORD` or `-W` flag is required here.
|
|
527
|
+
|
|
528
|
+
When updating URL-like env values, prefer applying a full GVC YAML update with `cpln apply`, then re-read the GVC env to
|
|
529
|
+
confirm the new reference took effect:
|
|
530
|
+
|
|
531
|
+
```sh
|
|
532
|
+
cpln apply -f my-app-review-pr-123-gvc.yaml
|
|
533
|
+
cpln gvc get my-app-review-pr-123 -o yaml | grep DATABASE_URL
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
`cpln gvc update --set` also works, but treat it as a known-fragile shortcut. Quote the entire `path=value` expression,
|
|
537
|
+
or the CLI can leave the old value in place while the command appears superficially successful. The `spec.env.NAME.value`
|
|
538
|
+
path relies on env-array lookup by name, so verify against your installed CLI version before relying on it:
|
|
539
|
+
|
|
540
|
+
```sh
|
|
541
|
+
cpln gvc update my-app-review-pr-123 \
|
|
542
|
+
--set 'spec.env.DATABASE_URL.value=cpln://secret/my-app-review-pr-123-database.DATABASE_URL'
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
A few tradeoffs remain even after the cost savings:
|
|
546
|
+
|
|
547
|
+
- **Noisy neighbor risk.** All staging/review apps share one server's CPU, RAM, disk, and connection pool. A runaway
|
|
548
|
+
query or connection leak in one app can affect the others; per-app connection caps or PgBouncer can help. Mind
|
|
549
|
+
Postgres's own `max_connections` (default 100): a staging seed running alongside several review apps, each at Rails'
|
|
550
|
+
default `pool: 5`, can exhaust it before any query runs. The official image ignores a `POSTGRES_MAX_CONNECTIONS` env
|
|
551
|
+
var; raise the server limit with a `-c max_connections=N` server argument or a custom `postgresql.conf`, and lower
|
|
552
|
+
`pool:` in `config/database.yml` for review apps as the simplest app-side lever.
|
|
553
|
+
- **Operational ownership.** Backups, restores, password rotation, sizing, and access control move to the shared server.
|
|
554
|
+
- **Other trusted services can use the same pattern.** Redis and Memcached can also be shared for trusted apps, but a
|
|
555
|
+
per-app key prefix or logical database index is only conventional separation when apps share credentials. If review
|
|
556
|
+
app code is not trusted, use enforced isolation such as per-app ACL users/credentials or separate instances.
|
|
557
|
+
|
|
558
|
+
### Enable Capacity AI for Demo and Starter Staging Apps
|
|
559
|
+
|
|
560
|
+
`templates/rails.yml` ships with CPU autoscaling pinned to one replica (`minScale: 1`, `maxScale: 1`) and
|
|
561
|
+
`capacityAI: false`. That's a conservative production-safe default, but for public demos, starter staging apps, and
|
|
562
|
+
long-lived review apps, Capacity AI can right-size CPU and memory allocation while keeping the same warm replica count.
|
|
563
|
+
For these non-production apps, keep the Rails workload as `type: standard`, disable the explicit autoscaling metric,
|
|
564
|
+
and enable Capacity AI. Apply the snippet below to your project's `.controlplane/templates/rails.yml`, or create an
|
|
565
|
+
environment-specific template (for example `rails-review.yml` or `rails-demo-staging.yml`) and list it under
|
|
566
|
+
`setup_app_templates` for the matching app entry in `.controlplane/controlplane.yml`.
|
|
567
|
+
|
|
568
|
+
```yaml
|
|
569
|
+
# Only `autoscaling.metric` and `capacityAI` change from templates/rails.yml.
|
|
570
|
+
# `type: standard` is shown here to confirm this is not a serverless migration.
|
|
571
|
+
# Keep containers, firewallConfig, identityLink, and everything else from the template intact.
|
|
291
572
|
kind: workload
|
|
292
573
|
name: rails
|
|
293
574
|
spec:
|
|
294
|
-
type:
|
|
575
|
+
type: standard
|
|
295
576
|
defaultOptions:
|
|
296
577
|
autoscaling:
|
|
297
|
-
minScale:
|
|
578
|
+
minScale: 1
|
|
298
579
|
maxScale: 1
|
|
299
|
-
|
|
300
|
-
|
|
580
|
+
metric: disabled
|
|
581
|
+
capacityAI: true
|
|
301
582
|
```
|
|
302
583
|
|
|
303
|
-
See [`templates/rails.yml`](/templates/rails.yml) for the full default — `containers`, `firewallConfig`,
|
|
584
|
+
See [`templates/rails.yml`](https://github.com/shakacode/control-plane-flow/blob/main/templates/rails.yml) for the full default — `containers`, `firewallConfig`,
|
|
304
585
|
`identityLink`, and the other required fields must be preserved when you copy the snippet above.
|
|
305
586
|
|
|
306
|
-
|
|
307
|
-
|
|
587
|
+
This is not the same as scale-to-zero. Capacity AI can reduce over-allocation for mostly idle demos, but it will not
|
|
588
|
+
make costs approach zero when a workload has steady RAM usage or background load. Expect it to settle over several
|
|
589
|
+
hours, and treat memory sizing as a separate cost lever.
|
|
590
|
+
|
|
591
|
+
Shared Postgres is the usual exception: keep shared databases manually sized rather than enabling Capacity AI
|
|
592
|
+
indiscriminately. Apply this guidance to stateless app/service workloads first (Rails, renderers, workers, and similar
|
|
593
|
+
staging-only services). Stateful workloads are not supported by Capacity AI, so keep stateful Redis, Elasticsearch,
|
|
594
|
+
Mongo, and similar support services manually sized unless you intentionally deploy them as supported stateless
|
|
595
|
+
workloads.
|
|
596
|
+
|
|
597
|
+
If you intentionally need true idle scale-to-zero, use a separate `type: serverless` workload with `minScale: 0` and
|
|
598
|
+
an HTTP wake-up autoscaling metric such as `rps` or `concurrency`:
|
|
599
|
+
|
|
600
|
+
```yaml
|
|
601
|
+
kind: workload
|
|
602
|
+
name: rails
|
|
603
|
+
spec:
|
|
604
|
+
type: serverless
|
|
605
|
+
defaultOptions:
|
|
606
|
+
autoscaling:
|
|
607
|
+
minScale: 0
|
|
608
|
+
maxScale: 1
|
|
609
|
+
metric: rps
|
|
610
|
+
target: 1
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
Existing `type: standard` workloads cannot change to `serverless` in place; that requires a planned delete/recreate
|
|
614
|
+
migration and can interrupt traffic.
|
|
308
615
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
usually isn't.
|
|
616
|
+
> **Warning:** Treat a `standard` to `serverless` conversion as an operational migration because deleting a running
|
|
617
|
+
> workload can interrupt traffic.
|
|
312
618
|
|
|
313
619
|
> **Note:** if you later suspend the app with `cpflow ps:stop`, Control Plane will not auto-wake it on the next
|
|
314
620
|
> request. Run `cpflow ps:start` explicitly first. See
|
|
@@ -390,7 +696,130 @@ No re-deploy is needed; the workloads come back with the same images they had be
|
|
|
390
696
|
> keep running while only the web tier sleeps. `cpflow ps:stop -a $APP_NAME` suspends every configured workload, web
|
|
391
697
|
> included, and `cleanup-stale-apps --mode=stop` applies the same pause behavior to stale review apps.
|
|
392
698
|
|
|
699
|
+
## Right-Sizing Non-Production Workloads
|
|
700
|
+
|
|
701
|
+
[Minimizing Non-Production App Costs](#minimizing-non-production-app-costs) above focuses on review-app lifecycle
|
|
702
|
+
controls: scale-to-zero, explicit pauses, and stale app cleanup. Long-lived staging and demo apps are the other common
|
|
703
|
+
source of avoidable Control Plane spend: they tend to keep generously-sized workloads running full-time. The levers
|
|
704
|
+
below apply to any non-production environment (staging, demos, and review apps alike).
|
|
705
|
+
|
|
706
|
+
### Enable Capacity AI on Idle Workloads
|
|
707
|
+
|
|
708
|
+
Control Plane bills the CPU and memory a running replica *reserves*. With `minScale: 1` and
|
|
709
|
+
Capacity AI off, a workload reserves its full `cpu`/`memory` around the clock, even when the
|
|
710
|
+
app is idle. **Capacity AI** lets Control Plane right-size that reservation toward actual
|
|
711
|
+
usage, so an idle non-production workload costs a fraction of its ceiling.
|
|
712
|
+
|
|
713
|
+
Set it in `defaultOptions`:
|
|
714
|
+
|
|
715
|
+
```yaml
|
|
716
|
+
kind: workload
|
|
717
|
+
name: rails
|
|
718
|
+
spec:
|
|
719
|
+
defaultOptions:
|
|
720
|
+
capacityAI: true
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
Also disable CPU-utilization autoscaling for idle non-production workloads; the
|
|
724
|
+
next section shows the complete `capacityAI` and autoscaling shape together.
|
|
725
|
+
|
|
726
|
+
Tradeoff: Control Plane reprovisions the replica when it adjusts the reservation. For
|
|
727
|
+
stateless web/renderer workloads that's negligible. For stateful workloads, see the
|
|
728
|
+
[guidance above](#enable-capacity-ai-for-demo-and-starter-staging-apps) — Postgres,
|
|
729
|
+
Redis, Elasticsearch, Mongo, and similar services should remain manually sized.
|
|
730
|
+
|
|
731
|
+
### Don't Autoscale Idle Workloads on CPU
|
|
732
|
+
|
|
733
|
+
CPU-utilization autoscaling adds nothing for an idle non-production app and works against
|
|
734
|
+
Capacity AI. Disable it and let Capacity AI handle right-sizing:
|
|
735
|
+
|
|
736
|
+
```yaml
|
|
737
|
+
kind: workload
|
|
738
|
+
name: rails
|
|
739
|
+
spec:
|
|
740
|
+
defaultOptions:
|
|
741
|
+
capacityAI: true
|
|
742
|
+
autoscaling:
|
|
743
|
+
metric: disabled
|
|
744
|
+
minScale: 1
|
|
745
|
+
maxScale: 1
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
(For the web tier you can go further and scale to zero — see
|
|
749
|
+
[Enable Capacity AI for Demo and Starter Staging Apps](#enable-capacity-ai-for-demo-and-starter-staging-apps).)
|
|
750
|
+
|
|
751
|
+
### Right-Size Reserved CPU and Memory
|
|
752
|
+
|
|
753
|
+
The shipped templates use production-leaning defaults. Check each workload's reserved
|
|
754
|
+
`cpu`/`memory` against its real usage — the workload's **Metrics** tab in Control Plane
|
|
755
|
+
shows Grafana CPU/memory graphs — because non-production workloads are routinely
|
|
756
|
+
over-provisioned.
|
|
757
|
+
|
|
758
|
+
Postgres is the usual offender: a demo or staging database does **not** need a full core.
|
|
759
|
+
Pinning `cpu: 1000m` keeps a whole reserved CPU running 24/7, while an idle Postgres
|
|
760
|
+
typically sits at single-digit millicores. Something like `cpu: 250m` / `memory: 512Mi`
|
|
761
|
+
is a field-tested non-production starting point; raise memory toward `1Gi` if the
|
|
762
|
+
workload's Metrics tab shows pressure during seeds, imports, or larger staging datasets.
|
|
763
|
+
|
|
764
|
+
```yaml
|
|
765
|
+
kind: workload
|
|
766
|
+
name: postgres
|
|
767
|
+
spec:
|
|
768
|
+
containers:
|
|
769
|
+
- name: postgres
|
|
770
|
+
cpu: 250m
|
|
771
|
+
memory: 512Mi
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Drop Workloads You Don't Use
|
|
775
|
+
|
|
776
|
+
Every workload listed under `app_workloads` / `additional_workloads` is another full-time
|
|
777
|
+
container. Remove the ones a non-production app doesn't actually need.
|
|
778
|
+
|
|
779
|
+
A common one is a separate background-job worker when the app has no jobs to run. On Rails
|
|
780
|
+
8, [Solid Queue](https://github.com/rails/solid_queue) can run inside Puma instead of as its
|
|
781
|
+
own workload — set `SOLID_QUEUE_IN_PUMA=true` when the app uses the Rails 8 default
|
|
782
|
+
`config/puma.rb`, or add `plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]` manually for
|
|
783
|
+
apps upgraded from Rails 7. Then drop the `worker` workload from `app_workloads` and
|
|
784
|
+
`setup_app_templates` in `.controlplane/controlplane.yml`, and delete its template.
|
|
785
|
+
Solid Queue is database-backed, so job processing needs no Redis; if your app uses Redis for caching or Action Cable,
|
|
786
|
+
keep that workload.
|
|
787
|
+
|
|
788
|
+
### Share One Postgres Across Non-Production Apps
|
|
789
|
+
|
|
790
|
+
Running a dedicated Postgres workload — and its SSD volume — for every staging and review
|
|
791
|
+
app multiplies standing cost. For non-production, several apps can share a single Postgres
|
|
792
|
+
server, each using its own database:
|
|
793
|
+
|
|
794
|
+
- Point each app's `DATABASE_URL` environment variable (in `.controlplane/templates/`) at the shared instance — for
|
|
795
|
+
example `postgres://user:pass@postgres.staging-shared-postgres.cpln.local:5432/my_app_staging` — and give each app a
|
|
796
|
+
distinct database name in the path.
|
|
797
|
+
- Set `inboundAllowType` to the narrowest scope that covers your use case — `workload-list` gives the tightest blast
|
|
798
|
+
radius when you can keep an explicit per-workload allowlist current, while `same-org` is the practical default when
|
|
799
|
+
client apps are too dynamic to maintain manually. `same-gvc` only works when the shared Postgres and every client app
|
|
800
|
+
live in the same GVC, which is not the cross-GVC setup described in
|
|
801
|
+
[Share One Control Plane Postgres for Staging and Review Apps](#share-one-control-plane-postgres-for-staging-and-review-apps).
|
|
802
|
+
Overly broad allow-types expand the attack surface, especially when review apps can run untrusted PR code.
|
|
803
|
+
- Store shared database credentials in Control Plane secrets for long-lived staging and demos; plaintext
|
|
804
|
+
`DATABASE_URL` values are only reasonable for disposable non-production experiments.
|
|
805
|
+
- Prefer per-app database roles over a shared superuser or broad `CREATEDB` role, especially when review apps can run
|
|
806
|
+
untrusted PR code.
|
|
807
|
+
|
|
808
|
+
A managed alternative is a single small RDS instance hosting many databases; see
|
|
809
|
+
[Hetzner RDS Postgres](https://pelle.io/posts/hetzner-rds-postgres).
|
|
810
|
+
|
|
811
|
+
### Keep Templates as the Source of Truth
|
|
812
|
+
|
|
813
|
+
It's tempting to tune `cpu`, `capacityAI`, or autoscaling directly in the Control Plane UI.
|
|
814
|
+
Don't: `cpflow apply-template` reconciles workloads from your `.controlplane/templates/`, so console edits are
|
|
815
|
+
overwritten when it runs next; non-interactive CI runs with `--yes` do that silently, while interactive runs prompt
|
|
816
|
+
before re-creating existing workloads. Make cost changes in the templates and deploy them.
|
|
817
|
+
|
|
818
|
+
If you want drift caught automatically, manage long-lived environments with Terraform via
|
|
819
|
+
[`cpflow terraform`](/docs/terraform/overview.md) — `terraform plan` reports any difference
|
|
820
|
+
between the repo and live infrastructure before you apply.
|
|
821
|
+
|
|
393
822
|
## Useful Links
|
|
394
823
|
|
|
395
824
|
- For best practices for the app's Dockerfile, see: https://lipanski.com/posts/dockerfile-ruby-best-practices
|
|
396
|
-
- For
|
|
825
|
+
- For Hetzner RDS Postgres, see: https://pelle.io/posts/hetzner-rds-postgres
|
data/examples/controlplane.yml
CHANGED
|
@@ -135,6 +135,8 @@ apps:
|
|
|
135
135
|
post_creation: bundle exec rake db:prepare
|
|
136
136
|
|
|
137
137
|
# Used by the command `cpflow delete` to run a hook before deleting the app.
|
|
138
|
+
# For a shared database, prefer admin-side cleanup instead: `cpflow delete` runs this hook before removing the
|
|
139
|
+
# workloads, so live connections can block the drop. See docs/tips.md ("Share One Control Plane Postgres").
|
|
138
140
|
pre_deletion: bundle exec rake db:drop
|
|
139
141
|
|
|
140
142
|
my-app-production:
|
|
@@ -30,9 +30,9 @@ module Command
|
|
|
30
30
|
|
|
31
31
|
def prompt
|
|
32
32
|
<<~PROMPT
|
|
33
|
-
Set up Control Plane GitHub Flow for this repo. Start with `cpflow github-flow-readiness` and stop on any reported blockers. The repo must be deployable from a clean clone: published package versions, complete runtime scaffold, and a production Dockerfile that can build the app. If any package version is unpublished, inaccessible from CI, or requires credentials that are not already modeled in the repo or GitHub settings, stop and report the blocker instead of generating workflow files. If the repo is a legacy sample pinned to an obsolete Ruby or Bundler toolchain, if it does not even have a production Dockerfile yet, or if it is a monorepo without an already-decided single app boundary for this flow, stop and report that as a prerequisite instead of forcing the rollout.
|
|
33
|
+
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.
|
|
34
34
|
|
|
35
|
-
If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default (`#{inferred_app_prefix}`) and rename them only if the project needs a different prefix. Then run `cpflow generate-github-actions` (or `cpflow generate-github-actions --staging-branch BRANCH` when staging should deploy from a branch other than `main`/`master`), keep review apps opt-in via `+review-app-deploy`, make sure any `STAGING_APP_BRANCH` repository variable is also present in the generated staging workflow's `on.push.branches` filter, and list the GitHub secrets and variables that must be configured. 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
|
|
35
|
+
If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default (`#{inferred_app_prefix}`) and rename them only if the project needs a different prefix. Then run `cpflow generate-github-actions` (or `cpflow generate-github-actions --staging-branch BRANCH` when staging should deploy from a branch other than `main`/`master`), keep review apps opt-in via `+review-app-deploy`, make sure any `STAGING_APP_BRANCH` repository variable is also present in the generated staging workflow's `on.push.branches` filter, and list the GitHub secrets and variables that must be configured. Do not hand-edit duplicated upstream refs into the generated wrappers: the only downstream Control Plane Flow pin should be the reusable workflow `uses: ...@vX.Y.Z` value generated from the installed `cpflow` gem version, and upstream workflows load their matching shared actions automatically. 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.
|
|
36
36
|
|
|
37
37
|
Keep Node available in the final image if asset compilation or SSR depends on ExecJS, Yarn, `pnpm`, or npm after the main install layer. Make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement. Preserve repo-defined frontend build hooks: if `config/shakapacker.yml` defines a `precompile_hook`, or React on Rails enables `config.auto_load_bundle = true`, confirm the generated Dockerfile runs that codegen step before `rails assets:precompile`. If `config/database.yml` shows SQLite in production, confirm that the generated scaffold uses persistent `db` and `storage` volumes plus a release script that runs `rails db:prepare`; otherwise keep the default Postgres workload. If the public workload is not named `rails`, set `PRIMARY_WORKLOAD` or adjust the generated workflows. Inspect the Dockerfile and package sources for private GitHub dependencies or `RUN --mount=type=ssh`; if present, wire `DOCKER_BUILD_SSH_KEY`, optionally set `DOCKER_BUILD_SSH_KNOWN_HOSTS` for non-GitHub SSH hosts, and keep `DOCKER_BUILD_EXTRA_ARGS` to newline-delimited single tokens such as `--build-arg=FOO=bar`.
|
|
38
38
|
|
data/lib/command/base.rb
CHANGED
|
@@ -49,7 +49,7 @@ module Command
|
|
|
49
49
|
|
|
50
50
|
def self.all_commands # rubocop:disable Metrics/MethodLength
|
|
51
51
|
Dir["#{__dir__}/**/*.rb"].each_with_object({}) do |file, result|
|
|
52
|
-
content = File.read(file)
|
|
52
|
+
content = File.read(file, encoding: "UTF-8")
|
|
53
53
|
|
|
54
54
|
classname = content.match(/^\s+class (?!Base\b)(\w+) < (?:.*(?!Command::)Base)(?:$| .*$)/)&.captures&.first
|
|
55
55
|
next unless classname
|
|
@@ -96,7 +96,7 @@ module Command
|
|
|
96
96
|
}
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
def self.workload_option(required: false)
|
|
99
|
+
def self.workload_option(required: false, repeatable: false)
|
|
100
100
|
{
|
|
101
101
|
name: :workload,
|
|
102
102
|
params: {
|
|
@@ -104,6 +104,7 @@ module Command
|
|
|
104
104
|
banner: "WORKLOAD_NAME",
|
|
105
105
|
desc: "Workload name",
|
|
106
106
|
type: :string,
|
|
107
|
+
repeatable: repeatable,
|
|
107
108
|
required: required
|
|
108
109
|
}
|
|
109
110
|
}
|
|
@@ -583,6 +584,20 @@ module Command
|
|
|
583
584
|
success
|
|
584
585
|
end
|
|
585
586
|
|
|
587
|
+
def wait_for_workloads_ready(workloads, reverse: false)
|
|
588
|
+
workloads_to_wait_for = reverse ? workloads.reverse : workloads
|
|
589
|
+
|
|
590
|
+
workloads_to_wait_for.each do |workload|
|
|
591
|
+
if cp.workload_suspended?(workload)
|
|
592
|
+
progress.puts("Workload '#{workload}' is suspended. Skipping...")
|
|
593
|
+
else
|
|
594
|
+
step("Waiting for workload '#{workload}' to be ready", retry_on_failure: true) do
|
|
595
|
+
cp.workload_deployments_ready?(workload, location: config.location, expected_status: true)
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
|
|
586
601
|
def cp
|
|
587
602
|
@cp ||= Controlplane.new(config)
|
|
588
603
|
end
|