Merge pull request #8679 from dokku/8674-ps-and-cron-global-report-keys-skip-the-plugin-prefix-or-global-infix

Align ps/cron :report --global keys with plugin convention
This commit is contained in:
Jose Diaz-Gonzalez
2026-05-26 20:33:52 -04:00
committed by GitHub
8 changed files with 145 additions and 40 deletions

View File

@@ -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 `<plugin>-global-<property>` 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 `<plugin>-computed-<property>` 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`<plugin>-global-<property>` and depended on the default value should switch to `<plugin>-computed-<property>`. The bare`<plugin>-<property>`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 `<plugin>-global-<property>` 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 `<plugin>-computed-<property>` 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 `<plugin>-global-<property>` and depended on the default value should switch to `<plugin>-computed-<property>`. The bare `<plugin>-<property>` 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 `<plugin>-global-<property>` / `<plugin>-computed-<property>` 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 `--<flag>` 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 `<plugin>-computed-<property>` 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 <app> <host>:<container>` 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-<hash>` entries in `storage:list-entries`. The migration is idempotent and tied to a per-app flag file at `$DOKKU_LIB_ROOT/config/storage/.migrated/<app>`; 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 <name> [<path>]` (the path defaults to the same `$DOKKU_LIB_ROOT/data/storage/<name>` 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.

View File

@@ -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

View File

@@ -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.<cron-id>` | app only | `false` | `--cron-maintenance-<cron-id>` (dynamic per task) | Suspends an individual cron task by its computed ID (one row per task); written by `cron:suspend`/`cron:resume` |

View File

@@ -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))

View File

@@ -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)

View File

@@ -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" {

View File

@@ -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"

View File

@@ -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"