diff --git a/docs/appendices/0.38.0-migration-guide.md b/docs/appendices/0.38.0-migration-guide.md index aa27e9559..f53c718a8 100644 --- a/docs/appendices/0.38.0-migration-guide.md +++ b/docs/appendices/0.38.0-migration-guide.md @@ -21,7 +21,8 @@ - The `docker-local` scheduler now sends `SIGTERM` to old containers immediately after a successful deploy, rather than waiting `wait-to-retire` seconds before signaling. This matches Heroku's graceful-shutdown contract and lets applications begin draining in-flight work as soon as proxy traffic switches. The `wait-to-retire` grace period and `stop-timeout-seconds` hard-stop continue to apply as before. See the [zero downtime deploys documentation](/docs/deployment/zero-downtime-deploys.md#wait-to-retire) for more details. - The `docker-local` scheduler no longer queues an image for retirement when another running container of the same app still uses it. This fixes the case where a `ps:rebuild` against an image-based deploy (`git:from-image`) produced an identical-SHA image and the `dokku-retire` cron timer would log `Image ... has running containers, skipping rm` on every run. Stuck entries from prior versions are pruned automatically on the next `ps:retire` run. - All `:report` subcommands now accept the `--global` flag, which scopes the report to globally-configured properties. The flag composes with `--format json`, so a JSON report of global properties can be obtained via, for example, `dokku scheduler:report --global --format json`. Previously, combining `--global` with `--format json` was rejected with an "info flag" error, and `--global` on its own was treated as an unknown flag. -- Every `:report` key of the form `-global-` now returns the raw stored global value (empty when the property has never been set) instead of the resolved value with the built-in default substituted in. A new `-computed-` key has been added wherever a default existed and returns the effective value (per-app value, falling back to the global value, then to the built-in default). This affects `app-json`, `builder-dockerfile`, `builder-herokuish`, `builder-lambda`, `builder-nixpacks`, `builder-pack`, `builder-railpack`, `caddy`, `checks`, `git`, `haproxy`, `logs`, `network`, `nginx`, `openresty``proxy`,`registry`,`scheduler`, `scheduler-k3s` and `traefik`. External tooling that read`-global-` and depended on the default value should switch to `-computed-`. The bare`-`keys for caddy/haproxy/traefik/openresty global-only properties (`caddy-image`,`haproxy-log-level`,`traefik-api-enabled`,`openresty-image`,`openresty-letsencrypt-email`,`openresty-letsencrypt-server`,`openresty-allowed-letsencrypt-domains-func-base64`, etc.) have also been replaced by the`global-` and `computed-` pair. +- Every `:report` key of the form `-global-` now returns the raw stored global value (empty when the property has never been set) instead of the resolved value with the built-in default substituted in. A new `-computed-` key has been added wherever a default existed and returns the effective value (per-app value, falling back to the global value, then to the built-in default). This affects `app-json`, `builder-dockerfile`, `builder-herokuish`, `builder-lambda`, `builder-nixpacks`, `builder-pack`, `builder-railpack`, `caddy`, `checks`, `git`, `haproxy`, `logs`, `network`, `nginx`, `openresty`, `proxy`, `registry`, `scheduler`, `scheduler-k3s` and `traefik`. External tooling that read `-global-` and depended on the default value should switch to `-computed-`. The bare `-` keys for caddy/haproxy/traefik/openresty global-only properties (`caddy-image`, `haproxy-log-level`, `traefik-api-enabled`, `openresty-image`, `openresty-letsencrypt-email`, `openresty-letsencrypt-server`, `openresty-allowed-letsencrypt-domains-func-base64`, etc.) have also been replaced by the `global-` and `computed-` pair. +- The `ps` and `cron` plugins now follow the same `-global-` / `-computed-` convention. The `ps:report` keys `stop-timeout-seconds`, `global-stop-timeout-seconds`, and `computed-stop-timeout-seconds` have been renamed to `ps-stop-timeout-seconds`, `ps-global-stop-timeout-seconds`, and `ps-computed-stop-timeout-seconds`. The `cron:report` keys `cron-mailfrom` and `cron-mailto` have been renamed to `cron-global-mailfrom` and `cron-global-mailto`, and new `cron-computed-mailfrom` and `cron-computed-mailto` keys have been added. The corresponding user-facing `--` arguments to `ps:report` and `cron:report` were renamed alongside the JSON keys; no aliases are kept. Additionally, both `ps:report --global` and `cron:report --global` now emit the `-computed-` keys for every settable global property with a default (`ps-computed-procfile-path`, `ps-computed-stop-timeout-seconds`, `cron-computed-maintenance`, `cron-computed-mailfrom`, `cron-computed-mailto`), matching the shape used by the other plugins. - The `scheduler-k3s` plugin now manages env config and the dokku-generated image pull Secret as their own helm releases with stable names (`config-{app}` and `pull-secret-{app}`) rather than bundling them into the app helm chart with a per-deploy timestamp suffix (`env-{app}.{ts}` / `ims-{app}.{ts}`). This fixes two bugs: a helm rollback of the app chart no longer deletes Secrets that older ReplicaSets still reference, and the Deployment's `imagePullSecrets` list no longer accumulates references to nonexistent Secrets across deploys. The next deploy of an app switches the Deployment's `envFrom` and `imagePullSecrets` references to the stable names and prunes any leaked entries; existing live Deployments do not need to be patched manually. App rename now also uninstalls the old `tls-{app}`, `config-{app}`, and `pull-secret-{app}` releases under the previous app name; the new name's releases are recreated on the next deploy or certs sync. - The storage plugin now treats persistent volumes as named, scheduler-aware first-class resources via `storage:create`, `storage:mount`, `storage:set`, and `storage:destroy`. The legacy `storage:mount :` colon form continues to work on docker-local apps but is deprecated; on k3s apps it is rejected. Existing colon-form mounts are migrated automatically the first time the new storage plugin runs (during the install trigger) - they appear as `legacy-` entries in `storage:list-entries`. The migration is idempotent and tied to a per-app flag file at `$DOKKU_LIB_ROOT/config/storage/.migrated/`; deleting that file forces a re-scan on the next install. The `storage:ensure-directory` command keeps working but now emits a deprecation warning - prefer `storage:create []` (the path defaults to the same `$DOKKU_LIB_ROOT/data/storage/` location). Storage entry names must now be DNS-1123 labels of 45 characters or less so they can be used verbatim as Helm release and Kubernetes resource names; underscores and uppercase characters that the older `ensure-directory` validator accepted are rejected for new names. The migration synthesizer always uses lowercase hex hashes so existing data is never locked out. diff --git a/docs/processes/process-management.md b/docs/processes/process-management.md index 318341dbf..f9cf40175 100644 --- a/docs/processes/process-management.md +++ b/docs/processes/process-management.md @@ -476,7 +476,7 @@ dokku ps:set node-js-app restore | `restore` | app only | `true` | `--restore` | When `true`, the app is restarted automatically by `ps:retire` after a host reboot | | `skip-deploy` | app + global | `false` | (not in report) | When `true`, skips the deploy phase after a successful build | | `start-cmd` | app only | none | (not in report) | Override start command for buildpack apps | -| `stop-timeout-seconds` | app + global | `30` | `--stop-timeout-seconds`, `--global-stop-timeout-seconds`, `--computed-stop-timeout-seconds` | Seconds Docker waits before SIGKILLing a container on stop | +| `stop-timeout-seconds` | app + global | `30` | `--ps-stop-timeout-seconds`, `--ps-global-stop-timeout-seconds`, `--ps-computed-stop-timeout-seconds` | Seconds Docker waits before SIGKILLing a container on stop | ### Read-only flags diff --git a/docs/processes/scheduled-cron-tasks.md b/docs/processes/scheduled-cron-tasks.md index 9be07b666..a56b0afe8 100644 --- a/docs/processes/scheduled-cron-tasks.md +++ b/docs/processes/scheduled-cron-tasks.md @@ -303,7 +303,7 @@ SHELL=/bin/bash | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| -| `mailfrom` | app + global | none | `--cron-mailfrom` | `From:` address used in cron failure emails | -| `mailto` | app + global | none | `--cron-mailto` | Recipient address for cron failure emails; empty disables email | +| `mailfrom` | global only | none | `--cron-global-mailfrom`, `--cron-computed-mailfrom` | `From:` address used in cron failure emails | +| `mailto` | global only | none | `--cron-global-mailto`, `--cron-computed-mailto` | Recipient address for cron failure emails; empty disables email | | `maintenance` | app + global | `false` | `--cron-maintenance`, `--cron-global-maintenance`, `--cron-computed-maintenance` | When `true`, suspends all cron tasks for the app (or globally) | | `maintenance.` | app only | `false` | `--cron-maintenance-` (dynamic per task) | Suspends an individual cron task by its computed ID (one row per task); written by `cron:suspend`/`cron:resume` | diff --git a/plugins/cron/report.go b/plugins/cron/report.go index 0c4ae3a91..2e485a682 100644 --- a/plugins/cron/report.go +++ b/plugins/cron/report.go @@ -19,18 +19,23 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { var flags map[string]common.ReportFunc if appName == "--global" { flags = map[string]common.ReportFunc{ - "--cron-mailfrom": reportMailfrom, - "--cron-mailto": reportMailto, - "--cron-global-maintenance": reportGlobalMaintenance, + "--cron-computed-mailfrom": reportComputedMailfrom, + "--cron-computed-mailto": reportComputedMailto, + "--cron-computed-maintenance": reportComputedMaintenance, + "--cron-global-mailfrom": reportGlobalMailfrom, + "--cron-global-mailto": reportGlobalMailto, + "--cron-global-maintenance": reportGlobalMaintenance, } } else { flags = map[string]common.ReportFunc{ - "--cron-mailfrom": reportMailfrom, - "--cron-mailto": reportMailto, - "--cron-task-count": reportTasks, - "--cron-global-maintenance": reportGlobalMaintenance, + "--cron-computed-mailfrom": reportComputedMailfrom, + "--cron-computed-mailto": reportComputedMailto, "--cron-computed-maintenance": reportComputedMaintenance, + "--cron-global-mailfrom": reportGlobalMailfrom, + "--cron-global-mailto": reportGlobalMailto, + "--cron-global-maintenance": reportGlobalMaintenance, "--cron-maintenance": reportMaintenance, + "--cron-task-count": reportTasks, } extraFlags := addCronMaintenanceFlags(appName, infoFlag) @@ -68,14 +73,22 @@ func addCronMaintenanceFlags(appName string, infoFlag string) map[string]common. return flags } -func reportMailfrom(_ string) string { +func reportGlobalMailfrom(_ string) string { return common.PropertyGet("cron", "--global", "mailfrom") } -func reportMailto(_ string) string { +func reportGlobalMailto(_ string) string { return common.PropertyGet("cron", "--global", "mailto") } +func reportComputedMailfrom(_ string) string { + return common.PropertyGetDefault("cron", "--global", "mailfrom", DefaultProperties["mailfrom"]) +} + +func reportComputedMailto(_ string) string { + return common.PropertyGetDefault("cron", "--global", "mailto", DefaultProperties["mailto"]) +} + func reportTasks(appName string) string { c, _ := FetchCronTasks(FetchCronTasksInput{AppName: appName}) return strconv.Itoa(len(c)) diff --git a/plugins/ps/report.go b/plugins/ps/report.go index 0f312e288..5e9c9b291 100644 --- a/plugins/ps/report.go +++ b/plugins/ps/report.go @@ -19,23 +19,25 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { var flags map[string]common.ReportFunc if appName == "--global" { flags = map[string]common.ReportFunc{ - "--ps-global-procfile-path": reportGlobalProcfilePath, - "--global-stop-timeout-seconds": reportGlobalStopTimeoutSeconds, + "--ps-computed-procfile-path": reportComputedProcfilePath, + "--ps-computed-stop-timeout-seconds": reportComputedStopTimeoutSeconds, + "--ps-global-procfile-path": reportGlobalProcfilePath, + "--ps-global-stop-timeout-seconds": reportGlobalStopTimeoutSeconds, } } else { flags = map[string]common.ReportFunc{ - "--deployed": reportDeployed, - "--processes": reportProcesses, - "--ps-can-scale": reportCanScale, - "--ps-restart-policy": reportRestartPolicy, - "--ps-computed-procfile-path": reportComputedProcfilePath, - "--ps-global-procfile-path": reportGlobalProcfilePath, - "--ps-procfile-path": reportProcfilePath, - "--restore": reportRestore, - "--running": reportRunningState, - "--global-stop-timeout-seconds": reportGlobalStopTimeoutSeconds, - "--computed-stop-timeout-seconds": reportComputedStopTimeoutSeconds, - "--stop-timeout-seconds": reportStopTimeoutSeconds, + "--deployed": reportDeployed, + "--processes": reportProcesses, + "--ps-can-scale": reportCanScale, + "--ps-restart-policy": reportRestartPolicy, + "--ps-computed-procfile-path": reportComputedProcfilePath, + "--ps-global-procfile-path": reportGlobalProcfilePath, + "--ps-procfile-path": reportProcfilePath, + "--restore": reportRestore, + "--running": reportRunningState, + "--ps-global-stop-timeout-seconds": reportGlobalStopTimeoutSeconds, + "--ps-computed-stop-timeout-seconds": reportComputedStopTimeoutSeconds, + "--ps-stop-timeout-seconds": reportStopTimeoutSeconds, } extraFlags := addStatusFlags(appName, infoFlag) diff --git a/tests/unit/cron.bats b/tests/unit/cron.bats index 6170002d7..9240b83af 100644 --- a/tests/unit/cron.bats +++ b/tests/unit/cron.bats @@ -120,6 +120,30 @@ teardown() { assert_success assert_output "false" + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-maintenance\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-maintenance\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-mailfrom\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-mailfrom\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-mailto\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-mailto\")'" + assert_success + assert_output "true" + run /bin/bash -c "dokku cron:report --global" echo "output: $output" echo "status: $status" @@ -133,23 +157,31 @@ teardown() { assert_success assert_output_not_contains "unknown flag" - run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-mailto\"'" + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailto\"'" echo "output: $output" echo "status: $status" assert_success assert_output "admin@example.com" + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailto\"'" + assert_success + assert_output "admin@example.com" + run /bin/bash -c "dokku cron:set --global mailto" echo "output: $output" echo "status: $status" assert_success assert_output_not_contains "unknown flag" - run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-mailto\"'" + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailto\"'" echo "output: $output" echo "status: $status" assert_success assert_output "" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailto\"'" + assert_success + assert_output "" } @test "(cron:set) --global mailfrom" { @@ -159,17 +191,29 @@ teardown() { assert_success assert_output_not_contains "unknown flag" - run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-mailfrom\"'" + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailfrom\"'" echo "output: $output" echo "status: $status" assert_success assert_output "dokku@example.com" + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailfrom\"'" + assert_success + assert_output "dokku@example.com" + run /bin/bash -c "dokku cron:set --global mailfrom" echo "output: $output" echo "status: $status" assert_success assert_output_not_contains "unknown flag" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailfrom\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailfrom\"'" + assert_success + assert_output "" } @test "(cron:set) --global maintenance" { diff --git a/tests/unit/ps-general-1.bats b/tests/unit/ps-general-1.bats index 24a38c9c4..31e3a18f0 100644 --- a/tests/unit/ps-general-1.bats +++ b/tests/unit/ps-general-1.bats @@ -172,41 +172,41 @@ web run /bin/bash -c "dokku ps:set --global stop-timeout-seconds" assert_success - run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"stop-timeout-seconds\"'" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-stop-timeout-seconds\"'" assert_success assert_output "" - run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"global-stop-timeout-seconds\"'" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" assert_success assert_output "" - run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" assert_success assert_output "30" run /bin/bash -c "dokku ps:set --global stop-timeout-seconds 90" assert_success - run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"global-stop-timeout-seconds\"'" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" assert_success assert_output "90" - run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" assert_success assert_output "90" run /bin/bash -c "dokku ps:set $TEST_APP stop-timeout-seconds 45" assert_success - run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"stop-timeout-seconds\"'" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-stop-timeout-seconds\"'" assert_success assert_output "45" - run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"global-stop-timeout-seconds\"'" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" assert_success assert_output "90" - run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" assert_success assert_output "45" @@ -217,6 +217,51 @@ web assert_success } +@test "(ps:report) --global --format json" { + run /bin/bash -c "dokku ps:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"ps-global-procfile-path\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"ps-computed-procfile-path\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"ps-global-stop-timeout-seconds\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"ps-computed-stop-timeout-seconds\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds 75" + assert_success + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" + assert_success + assert_output "75" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" + assert_success + assert_output "75" + + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds" + assert_success + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" + assert_success + assert_output "30" +} + @test "(ps:restart-policy) default policy" { run /bin/bash -c "dokku ps:report $TEST_APP --ps-restart-policy" echo "output: $output" diff --git a/tests/unit/report.bats b/tests/unit/report.bats index 7eea94310..13f96c810 100644 --- a/tests/unit/report.bats +++ b/tests/unit/report.bats @@ -26,7 +26,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_output_contains "herokuish version:" - assert_output_contains "Deployed: false" "0" + assert_output_contains "Deployed: false" "0" assert_success run /bin/bash -c "dokku report fake-app-name" @@ -48,7 +48,7 @@ teardown() { run /bin/bash -c "dokku report ${TEST_APP}-2 2>&1" echo "output: $output" echo "status: $status" - assert_output_contains "Deployed: false" + assert_output_contains "Deployed: false" assert_success run /bin/bash -c "dokku --force apps:destroy ${TEST_APP}-2"