From 3bf6c72451def0c044b2f71fc276e89cd8fb73d0 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 27 May 2026 07:17:00 -0400 Subject: [PATCH] fix: align Go-plugin :report JSON keys with bash-strip convention Every Go-implemented plugin's `:report --format json` now emits keys without the redundant `-` head segment, matching the shape bash plugins have always emitted. The CLI flag names and `:set` semantics are unchanged. For backwards compatibility during the 0.38.x patch series, the legacy `-` keys are emitted side-by-side with the new keys, and a future major release will drop the legacy keys. `common.ReportSingleApp` is refactored to accept a `ReportSingleAppInput` struct with a `Validate()` method so the input is checked before any work runs, which also catches the latent `"docker options"` reportType bug at the API boundary. --- docs/advanced-usage/builds.md | 3 + docs/advanced-usage/registry-management.md | 3 + docs/appendices/0.38.0-migration-guide.md | 1 + docs/appendices/file-formats/app-json.md | 3 + .../deployment/builders/builder-management.md | 3 + .../builders/buildpack-management.md | 3 + docs/deployment/logs.md | 3 + docs/deployment/schedulers/k3s.md | 3 + .../schedulers/scheduler-management.md | 3 + docs/networking/network.md | 3 + docs/networking/proxy-management.md | 3 + docs/processes/process-management.md | 3 + docs/processes/scheduled-cron-tasks.md | 3 + plugins/app-json/report.go | 14 +- plugins/apps/report.go | 14 +- plugins/builder/report.go | 14 +- plugins/buildpacks/report.go | 14 +- plugins/builds/report.go | 12 +- plugins/common/common.go | 119 ++++++-- plugins/common/report_test.go | 275 ++++++++++++++++++ plugins/cron/report.go | 14 +- plugins/docker-options/report.go | 14 +- plugins/logs/report.go | 14 +- plugins/network/report.go | 14 +- plugins/ports/report.go | 14 +- plugins/proxy/report.go | 14 +- plugins/ps/report.go | 14 +- plugins/registry/report.go | 14 +- plugins/resource/resource.go | 14 +- plugins/scheduler-k3s/report.go | 27 +- plugins/scheduler/report.go | 14 +- plugins/storage/report.go | 14 +- tests/unit/app-json.bats | 3 + tests/unit/apps_1.bats | 18 ++ tests/unit/builder.bats | 18 ++ tests/unit/buildpacks.bats | 18 ++ tests/unit/builds.bats | 19 ++ tests/unit/cron.bats | 24 ++ tests/unit/docker-options-2.bats | 6 +- tests/unit/logs.bats | 25 ++ tests/unit/network.bats | 30 ++ tests/unit/ports.bats | 10 + tests/unit/proxy.bats | 18 ++ tests/unit/ps-general-1.bats | 60 ++++ tests/unit/registry.bats | 26 ++ tests/unit/scheduler-k3s-report.bats | 18 ++ tests/unit/scheduler.bats | 18 ++ tests/unit/storage.bats | 14 + 48 files changed, 928 insertions(+), 77 deletions(-) create mode 100644 plugins/common/report_test.go diff --git a/docs/advanced-usage/builds.md b/docs/advanced-usage/builds.md index 82e81a720..1e80bca43 100644 --- a/docs/advanced-usage/builds.md +++ b/docs/advanced-usage/builds.md @@ -174,6 +174,9 @@ Available flags: ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `builds:report`. The JSON keys emitted by `builds:report --format json` are the same names with the leading `--builds-` stripped (e.g. `retention`, `global-retention`, `computed-retention`). Legacy keys with the `builds-` prefix (e.g. `builds-retention`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. Status keys (`build-id`, `build-status`, etc.) have no plugin prefix and are unaffected. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `retention` | app + global | `20` | `--builds-retention`, `--builds-global-retention`, `--builds-computed-retention` | Number of recent build records kept per app; older finalized records are pruned at the end of each deploy | diff --git a/docs/advanced-usage/registry-management.md b/docs/advanced-usage/registry-management.md index 01ace63a9..c483eff37 100644 --- a/docs/advanced-usage/registry-management.md +++ b/docs/advanced-usage/registry-management.md @@ -231,6 +231,9 @@ dokku registry:set --global push-extra-tags ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `registry:report`. The JSON keys emitted by `registry:report --format json` are the same names with the leading `--registry-` stripped (e.g. `image-repo`, `global-server`, `computed-push-on-release`). Legacy keys with the `registry-` prefix (e.g. `registry-image-repo`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `image-repo` | app only | `dokku/` | `--registry-image-repo`, `--registry-computed-image-repo` | Repository name used when pushing the app's image (overrides the global template) | diff --git a/docs/appendices/0.38.0-migration-guide.md b/docs/appendices/0.38.0-migration-guide.md index 5f049d2bb..3123ff840 100644 --- a/docs/appendices/0.38.0-migration-guide.md +++ b/docs/appendices/0.38.0-migration-guide.md @@ -24,6 +24,7 @@ - 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. - A second round of `:report` additions surfaces every remaining settable-but-unreported property under the same raw/global/computed convention so external tooling can verify drift via `:report --format json` without falling back to a generic bash task. The `ps` plugin gains `--ps-dockerfile-start-cmd` and `--ps-computed-dockerfile-start-cmd`, `--ps-start-cmd` and `--ps-computed-start-cmd`, and the `--ps-skip-deploy` / `--ps-global-skip-deploy` / `--ps-computed-skip-deploy` triple (default `false`). The `builder` plugin gains the `--builder-skip-cleanup` triple (default `false`). The `scheduler` plugin gains the `--scheduler-shell` triple. The `proxy` plugin gains the `--proxy-proxy-port` and `--proxy-proxy-ssl-port` triples and exposes the raw `disabled` property as `--proxy-disabled` / `--proxy-computed-disabled`, alongside the existing inverted `--proxy-enabled`. The `openresty` plugin gains `--openresty-global-log-level` and `--openresty-computed-log-level` (default `ERROR`). The `nginx` plugin gains the `--nginx-nginx-service-command` triple. The `scheduler-k3s` plugin gains `--scheduler-k3s-global-token`, but the value is masked as `*******` in default stdout output; the raw value is returned only when the report is requested via `--format json` or when this flag is queried explicitly by name. The traefik `dns-provider-` keys now follow the same explicit-query rule - previously they were unmasked only for `--format json`, but a query like `dokku traefik:report --traefik-dns-provider-cf_api_key` now returns the actual value instead of `*******`. +- All Go-implemented plugins (`app-json`, `apps`, `builder`, `buildpacks`, `builds`, `cron`, `docker-options`, `logs`, `network`, `ports`, `proxy`, `ps`, `registry`, `resource`, `scheduler`, `scheduler-k3s`, `storage`) now emit JSON keys from `:report --format json` without the `-` head segment, matching the shape bash plugins have always emitted. For example, `dokku ps:report myapp --format json` now contains `stop-timeout-seconds`, `global-stop-timeout-seconds`, and `computed-stop-timeout-seconds` keys. The CLI flag names (`--ps-stop-timeout-seconds`, etc.) are unchanged, and `:set` semantics are unchanged. For backwards compatibility during the 0.38.x patch series, the old `-` JSON keys are emitted side-by-side with the new keys, so external scripts reading either shape continue to work. The legacy keys will be dropped in a future major release. External JSON consumers should migrate to the new key shape. - 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/appendices/file-formats/app-json.md b/docs/appendices/file-formats/app-json.md index 6d4c2554b..4146aeb82 100644 --- a/docs/appendices/file-formats/app-json.md +++ b/docs/appendices/file-formats/app-json.md @@ -293,6 +293,9 @@ An autoscaling trigger consists of the following properties: ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `app-json:report`. The JSON keys emitted by `app-json:report --format json` are the same names with the leading `--app-json-` stripped (e.g. `appjson-path`, `global-appjson-path`, `computed-appjson-path`). Legacy keys with the `app-json-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `appjson-path` | app + global | `app.json` | `--app-json-appjson-path`, `--app-json-global-appjson-path`, `--app-json-computed-appjson-path` | Path within the app to the `app.json` manifest, relative to the build root | diff --git a/docs/deployment/builders/builder-management.md b/docs/deployment/builders/builder-management.md index cce3703d5..fd2b43c80 100644 --- a/docs/deployment/builders/builder-management.md +++ b/docs/deployment/builders/builder-management.md @@ -177,6 +177,9 @@ For a simple example of how to implement this trigger, see `builder-pack`, which ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `builder:report`. The JSON keys emitted by `builder:report --format json` are the same names with the leading `--builder-` stripped (e.g. `selected`, `global-selected`, `computed-selected`). Legacy keys with the `builder-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `build-dir` | app + global | none | `--builder-build-dir`, `--builder-global-build-dir`, `--builder-computed-build-dir` | Subdirectory within the repository to use as the build context | diff --git a/docs/deployment/builders/buildpack-management.md b/docs/deployment/builders/buildpack-management.md index 423c32722..293eb7479 100644 --- a/docs/deployment/builders/buildpack-management.md +++ b/docs/deployment/builders/buildpack-management.md @@ -197,6 +197,9 @@ dokku buildpacks:report node-js-app --buildpacks-list These properties are managed via `buildpacks:set-property` (the legacy command name for this plugin). +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `buildpacks:report`. The JSON keys emitted by `buildpacks:report --format json` are the same names with the leading `--buildpacks-` stripped (e.g. `stack`, `global-stack`, `computed-stack`). Legacy keys with the `buildpacks-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `stack` | app + global | none | `--buildpacks-stack`, `--buildpacks-global-stack`, `--buildpacks-computed-stack` | Herokuish stack image used to compile the app (e.g. `heroku/heroku:24`) | diff --git a/docs/deployment/logs.md b/docs/deployment/logs.md index fded8d606..1a56ca3f2 100644 --- a/docs/deployment/logs.md +++ b/docs/deployment/logs.md @@ -316,6 +316,9 @@ dokku logs:set --global app-label-alias ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `logs:report`. The JSON keys emitted by `logs:report --format json` are the same names with the leading `--logs-` stripped (e.g. `max-size`, `global-max-size`, `computed-max-size`). Legacy keys with the `logs-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `app-label-alias` | app + global | `com.dokku.app-name` | `--logs-app-label-alias`, `--logs-global-app-label-alias`, `--logs-computed-app-label-alias` | Docker label key whose value is used to identify the app when shipping logs | diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index 1acac303c..a0aa9c377 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -792,6 +792,9 @@ If unspecified for any task, the default reservation will be `.1` CPU and `128Mi ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `scheduler-k3s:report`. The JSON keys emitted by `scheduler-k3s:report --format json` are the same names with the leading `--scheduler-k3s-` stripped (e.g. `deploy-timeout`, `global-deploy-timeout`, `computed-deploy-timeout`). Legacy keys with the `scheduler-k3s-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `deploy-timeout` | app + global | `300s` | `--scheduler-k3s-deploy-timeout`, `--scheduler-k3s-global-deploy-timeout`, `--scheduler-k3s-computed-deploy-timeout` | Timeout for a single helm install/upgrade cycle | diff --git a/docs/deployment/schedulers/scheduler-management.md b/docs/deployment/schedulers/scheduler-management.md index e511b3531..fa838a732 100644 --- a/docs/deployment/schedulers/scheduler-management.md +++ b/docs/deployment/schedulers/scheduler-management.md @@ -152,6 +152,9 @@ Deployment tasks are currently executed directly on the primary Dokku server. ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `scheduler:report`. The JSON keys emitted by `scheduler:report --format json` are the same names with the leading `--scheduler-` stripped (e.g. `selected`, `global-selected`, `computed-selected`). Legacy keys with the `scheduler-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `selected` | app + global | `docker-local` | `--scheduler-selected`, `--scheduler-global-selected`, `--scheduler-computed-selected` | Scheduler plugin used to deploy this app (`docker-local`, `k3s`, etc.) | diff --git a/docs/networking/network.md b/docs/networking/network.md index 54443ffad..9bfdcbc77 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -516,6 +516,9 @@ dokku network:report node-js-app --network-bind-all-interfaces ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `network:report`. The JSON keys emitted by `network:report --format json` are the same names with the leading `--network-` stripped (e.g. `attach-post-create`, `global-attach-post-create`, `computed-attach-post-create`). Legacy keys with the `network-` prefix (e.g. `network-attach-post-create`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `attach-post-create` | app + global | none | `--network-attach-post-create`, `--network-global-attach-post-create`, `--network-computed-attach-post-create` | Networks attached to a container immediately after creation, before the deploy phase | diff --git a/docs/networking/proxy-management.md b/docs/networking/proxy-management.md index 5531bbef7..cda6ad6a1 100644 --- a/docs/networking/proxy-management.md +++ b/docs/networking/proxy-management.md @@ -241,6 +241,9 @@ Finally, proxy implementations _may_ install extra software needed for the proxy ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `proxy:report`. The JSON keys emitted by `proxy:report --format json` are the same names with the leading `--proxy-` stripped (e.g. `type`, `global-type`, `computed-type`). Legacy keys with the `proxy-` prefix (e.g. `proxy-type`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `disabled` | app only | `false` | `--proxy-disabled`, `--proxy-computed-disabled` (also exposed inverted as `--proxy-enabled`) | When `true`, disables proxy integration for this app (`proxy:enable`/`proxy:disable` write this) | diff --git a/docs/processes/process-management.md b/docs/processes/process-management.md index ca445c2b1..83a93bbf2 100644 --- a/docs/processes/process-management.md +++ b/docs/processes/process-management.md @@ -468,6 +468,9 @@ dokku ps:set node-js-app restore ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `ps:report`. The JSON keys emitted by `ps:report --format json` are the same names with the leading `--ps-` stripped (e.g. `procfile-path`, `global-procfile-path`, `computed-procfile-path`). Legacy keys with the `ps-` prefix (e.g. `ps-procfile-path`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `dockerfile-start-cmd` | app only | none | `--ps-dockerfile-start-cmd`, `--ps-computed-dockerfile-start-cmd` | Override `CMD` for Dockerfile-based apps | diff --git a/docs/processes/scheduled-cron-tasks.md b/docs/processes/scheduled-cron-tasks.md index a56b0afe8..12c21f507 100644 --- a/docs/processes/scheduled-cron-tasks.md +++ b/docs/processes/scheduled-cron-tasks.md @@ -301,6 +301,9 @@ SHELL=/bin/bash ### Settable properties +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `cron:report`. The JSON keys emitted by `cron:report --format json` are the same names with the leading `--cron-` stripped (e.g. `global-mailto`, `computed-mailto`, `maintenance`). Legacy keys with the `cron-` prefix (e.g. `cron-global-mailto`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + | Property | Scope | Default | Report flags | Description | |---|---|---|---|---| | `mailfrom` | global only | none | `--cron-global-mailfrom`, `--cron-computed-mailfrom` | `From:` address used in cron failure emails | diff --git a/plugins/app-json/report.go b/plugins/app-json/report.go index af5f68d07..1a2579327 100644 --- a/plugins/app-json/report.go +++ b/plugins/app-json/report.go @@ -31,10 +31,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("app-json", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "app-json", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportComputedAppjsonpath(appName string) string { diff --git a/plugins/apps/report.go b/plugins/apps/report.go index 1de1069c8..9ff4c497b 100644 --- a/plugins/apps/report.go +++ b/plugins/apps/report.go @@ -33,10 +33,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("app", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "app", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportCreatedAt(appName string) string { diff --git a/plugins/builder/report.go b/plugins/builder/report.go index 8fa76c6aa..f7d2d263d 100644 --- a/plugins/builder/report.go +++ b/plugins/builder/report.go @@ -40,10 +40,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("builder", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "builder", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportComputedSelected(appName string) string { diff --git a/plugins/buildpacks/report.go b/plugins/buildpacks/report.go index dc6b0f1c1..a2a2221a6 100644 --- a/plugins/buildpacks/report.go +++ b/plugins/buildpacks/report.go @@ -34,10 +34,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("buildpacks", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "buildpacks", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportComputedStack(appName string) string { diff --git a/plugins/builds/report.go b/plugins/builds/report.go index 629111680..3a3232f5c 100644 --- a/plugins/builds/report.go +++ b/plugins/builds/report.go @@ -42,7 +42,17 @@ func ReportSingleApp(appName, format, infoFlag string) error { } infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("builds", appName, infoFlag, infoFlags, flagKeys, format, false, true) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "builds", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func mostRecentBuild(appName string) (Build, bool) { diff --git a/plugins/common/common.go b/plugins/common/common.go index 227c18b50..5f60cde5d 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -776,22 +776,99 @@ func ParseScaleOutput(b []byte) (map[string]int32, error) { return scale, nil } -// ReportSingleApp is an internal function that displays a report for an app -func ReportSingleApp(reportType string, appName string, infoFlag string, infoFlags map[string]string, infoFlagKeys []string, format string, trimPrefix bool, uppercaseFirstCharacter bool) error { - if format != "stdout" && infoFlag != "" { +// ReportSingleAppInput configures a call to ReportSingleApp. +type ReportSingleAppInput struct { + // ReportType is the plugin name used as the JSON key prefix (e.g. "ps", "registry"). + // Must be non-empty, contain no whitespace, and not begin with "--". + ReportType string + + // AppName is the app being reported on, or "--global" for the global report. + AppName string + + // InfoFlag is an optional single flag to extract (e.g. "--ps-stop-timeout-seconds"). + // Only valid when Format is "stdout" (or empty, which defaults to "stdout"). + InfoFlag string + + // InfoFlags maps flag-name to value (e.g. "--ps-stop-timeout-seconds" -> "30"). + InfoFlags map[string]string + + // InfoFlagKeys is the list of valid flag names surfaced in InfoFlag lookup errors. + InfoFlagKeys []string + + // Format is "stdout" or "json". Empty defaults to "stdout". + Format string + + // TrimPrefix, when true, strips the "---" prefix from JSON keys so + // each report's JSON shape is independent of which plugin emitted it. Stdout + // output is not affected by this flag. + TrimPrefix bool + + // UppercaseFirstCharacter capitalizes the first character of each stdout key. + UppercaseFirstCharacter bool + + // EmitLegacyPrefix, when true alongside TrimPrefix, also emits the legacy + // "-" keys in JSON output for backwards compatibility + // during the deprecation window. Has no effect when TrimPrefix is false. + EmitLegacyPrefix bool +} + +// Validate returns an error when the input is malformed. +func (i ReportSingleAppInput) Validate() error { + if i.ReportType == "" { + return errors.New("ReportType is required") + } + if strings.ContainsAny(i.ReportType, " \t\n") { + return fmt.Errorf("ReportType must not contain whitespace: %q", i.ReportType) + } + if strings.HasPrefix(i.ReportType, "--") { + return fmt.Errorf("ReportType must not begin with --: %q", i.ReportType) + } + if i.AppName == "" { + return errors.New("AppName is required (use --global for the global report)") + } + if i.InfoFlags == nil { + return errors.New("InfoFlags is required") + } + format := i.Format + if format == "" { + format = "stdout" + } + if format != "stdout" && format != "json" { + return fmt.Errorf("Format must be \"stdout\" or \"json\": %q", i.Format) + } + if format != "stdout" && i.InfoFlag != "" { return errors.New("--format flag cannot be specified when specifying an info flag") } + if i.EmitLegacyPrefix && !i.TrimPrefix { + return errors.New("EmitLegacyPrefix has no effect when TrimPrefix is false") + } + return nil +} + +// ReportSingleApp is an internal function that displays a report for an app. +func ReportSingleApp(input ReportSingleAppInput) error { + if err := input.Validate(); err != nil { + return err + } + + format := input.Format + if format == "" { + format = "stdout" + } if format == "json" { data := map[string]string{} - for key, value := range infoFlags { - prefix := "--" - if trimPrefix { - prefix = fmt.Sprintf("--%v-", reportType) + pluginPrefix := fmt.Sprintf("--%v-", input.ReportType) + for key, value := range input.InfoFlags { + legacyKey := strings.TrimPrefix(key, "--") + if input.TrimPrefix && strings.HasPrefix(key, pluginPrefix) { + data[strings.TrimPrefix(key, pluginPrefix)] = value + if input.EmitLegacyPrefix { + data[legacyKey] = value + } + } else { + data[legacyKey] = value } - - // key = strings.Replace(strings.Replace(strings.TrimPrefix(key, prefix), "-", " ", -1), ".", " ", -1) - data[strings.TrimPrefix(key, prefix)] = value } out, err := json.Marshal(data) if err != nil { @@ -803,7 +880,7 @@ func ReportSingleApp(reportType string, appName string, infoFlag string, infoFla length := 0 flags := []string{} - for key := range infoFlags { + for key := range input.InfoFlags { if len(key) > length { length = len(key) } @@ -814,22 +891,17 @@ func ReportSingleApp(reportType string, appName string, infoFlag string, infoFla length = 31 } - if len(infoFlag) == 0 { - LogInfo2Quiet(fmt.Sprintf("%s %v information", appName, reportType)) + if len(input.InfoFlag) == 0 { + LogInfo2Quiet(fmt.Sprintf("%s %v information", input.AppName, input.ReportType)) for _, k := range flags { - v, ok := infoFlags[k] + v, ok := input.InfoFlags[k] if !ok { continue } - prefix := "--" - if trimPrefix { - prefix = fmt.Sprintf("--%v-", reportType) - } + key := strings.Replace(strings.Replace(strings.TrimPrefix(k, "--"), "-", " ", -1), ".", " ", -1) - key := strings.Replace(strings.Replace(strings.TrimPrefix(k, prefix), "-", " ", -1), ".", " ", -1) - - if uppercaseFirstCharacter { + if input.UppercaseFirstCharacter { key = UcFirst(key) } @@ -839,8 +911,8 @@ func ReportSingleApp(reportType string, appName string, infoFlag string, infoFla } for _, k := range flags { - if infoFlag == k { - v, ok := infoFlags[k] + if input.InfoFlag == k { + v, ok := input.InfoFlags[k] if !ok { continue } @@ -849,6 +921,7 @@ func ReportSingleApp(reportType string, appName string, infoFlag string, infoFla } } + infoFlagKeys := append([]string(nil), input.InfoFlagKeys...) sort.Strings(infoFlagKeys) return fmt.Errorf("Invalid flag passed, valid flags: %s", strings.Join(infoFlagKeys, ", ")) } diff --git a/plugins/common/report_test.go b/plugins/common/report_test.go new file mode 100644 index 000000000..747d58d68 --- /dev/null +++ b/plugins/common/report_test.go @@ -0,0 +1,275 @@ +package common + +import ( + "bytes" + "encoding/json" + "io" + "os" + "strings" + "testing" +) + +func TestReportSingleAppInputValidate(t *testing.T) { + t.Parallel() + + baseValid := ReportSingleAppInput{ + ReportType: "ps", + AppName: "myapp", + InfoFlags: map[string]string{}, + Format: "stdout", + } + + tests := []struct { + name string + input ReportSingleAppInput + wantError string + }{ + { + name: "valid", + input: ReportSingleAppInput{ + ReportType: "ps", + AppName: "myapp", + InfoFlags: map[string]string{"--ps-restart-policy": "always"}, + Format: "stdout", + }, + }, + { + name: "valid empty format defaults to stdout", + input: ReportSingleAppInput{ + ReportType: "ps", + AppName: "myapp", + InfoFlags: map[string]string{}, + Format: "", + }, + }, + { + name: "missing ReportType", + input: mut(baseValid, func(i *ReportSingleAppInput) { i.ReportType = "" }), + wantError: "ReportType is required", + }, + { + name: "ReportType with space rejected", + input: mut(baseValid, func(i *ReportSingleAppInput) { i.ReportType = "docker options" }), + wantError: "ReportType must not contain whitespace", + }, + { + name: "ReportType with tab rejected", + input: mut(baseValid, func(i *ReportSingleAppInput) { i.ReportType = "foo\tbar" }), + wantError: "ReportType must not contain whitespace", + }, + { + name: "ReportType beginning with -- rejected", + input: mut(baseValid, func(i *ReportSingleAppInput) { i.ReportType = "--ps" }), + wantError: "ReportType must not begin with --", + }, + { + name: "missing AppName", + input: mut(baseValid, func(i *ReportSingleAppInput) { i.AppName = "" }), + wantError: "AppName is required", + }, + { + name: "nil InfoFlags", + input: mut(baseValid, func(i *ReportSingleAppInput) { i.InfoFlags = nil }), + wantError: "InfoFlags is required", + }, + { + name: "invalid Format", + input: mut(baseValid, func(i *ReportSingleAppInput) { i.Format = "yaml" }), + wantError: "Format must be", + }, + { + name: "json with info flag rejected", + input: ReportSingleAppInput{ + ReportType: "ps", + AppName: "myapp", + InfoFlags: map[string]string{}, + Format: "json", + InfoFlag: "--ps-restart-policy", + }, + wantError: "--format flag cannot be specified when specifying an info flag", + }, + { + name: "EmitLegacyPrefix without TrimPrefix rejected", + input: ReportSingleAppInput{ + ReportType: "ps", + AppName: "myapp", + InfoFlags: map[string]string{}, + Format: "stdout", + EmitLegacyPrefix: true, + TrimPrefix: false, + }, + wantError: "EmitLegacyPrefix has no effect when TrimPrefix is false", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := tc.input.Validate() + if tc.wantError == "" { + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + return + } + if err == nil { + t.Fatalf("expected error %q, got nil", tc.wantError) + } + if !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("expected error to contain %q, got %q", tc.wantError, err.Error()) + } + }) + } +} + +func TestReportSingleAppJSONKeys(t *testing.T) { + infoFlags := map[string]string{ + "--ps-stop-timeout-seconds": "30", + "--ps-restart-policy": "always", + "--deployed": "true", + } + + tests := []struct { + name string + trimPrefix bool + emitLegacyPrefix bool + wantKeys map[string]string + }{ + { + name: "trimPrefix=false: prefixed keys only", + trimPrefix: false, + emitLegacyPrefix: false, + wantKeys: map[string]string{ + "ps-stop-timeout-seconds": "30", + "ps-restart-policy": "always", + "deployed": "true", + }, + }, + { + name: "trimPrefix=true emitLegacy=false: stripped keys only", + trimPrefix: true, + emitLegacyPrefix: false, + wantKeys: map[string]string{ + "stop-timeout-seconds": "30", + "restart-policy": "always", + "deployed": "true", + }, + }, + { + name: "trimPrefix=true emitLegacy=true: both shapes", + trimPrefix: true, + emitLegacyPrefix: true, + wantKeys: map[string]string{ + "stop-timeout-seconds": "30", + "restart-policy": "always", + "deployed": "true", + "ps-stop-timeout-seconds": "30", + "ps-restart-policy": "always", + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + out := captureStdout(t, func() { + err := ReportSingleApp(ReportSingleAppInput{ + ReportType: "ps", + AppName: "myapp", + InfoFlags: infoFlags, + Format: "json", + TrimPrefix: tc.trimPrefix, + EmitLegacyPrefix: tc.emitLegacyPrefix, + }) + if err != nil { + t.Fatalf("ReportSingleApp returned error: %v", err) + } + }) + + var got map[string]string + if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &got); err != nil { + t.Fatalf("output is not valid JSON: %v\noutput: %s", err, out) + } + + if len(got) != len(tc.wantKeys) { + t.Fatalf("expected %d keys, got %d\nwant: %v\ngot: %v", len(tc.wantKeys), len(got), tc.wantKeys, got) + } + for k, v := range tc.wantKeys { + if got[k] != v { + t.Errorf("expected %q=%q, got %q=%q", k, v, k, got[k]) + } + } + }) + } +} + +func TestReportSingleAppJSONNonPluginPrefixKey(t *testing.T) { + // Keys that don't start with --- should pass through with + // only the leading "--" stripped, regardless of TrimPrefix. + infoFlags := map[string]string{ + "--build-id": "abc123", + "--builds-retention": "5", + } + + out := captureStdout(t, func() { + err := ReportSingleApp(ReportSingleAppInput{ + ReportType: "builds", + AppName: "myapp", + InfoFlags: infoFlags, + Format: "json", + TrimPrefix: true, + EmitLegacyPrefix: true, + }) + if err != nil { + t.Fatalf("ReportSingleApp returned error: %v", err) + } + }) + + var got map[string]string + if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &got); err != nil { + t.Fatalf("output is not valid JSON: %v", err) + } + + if got["build-id"] != "abc123" { + t.Errorf("expected build-id=abc123, got %q", got["build-id"]) + } + if got["retention"] != "5" { + t.Errorf("expected retention=5 (stripped), got %q", got["retention"]) + } + if got["builds-retention"] != "5" { + t.Errorf("expected builds-retention=5 (legacy), got %q", got["builds-retention"]) + } + if _, present := got["uilds-retention"]; present { + t.Error("found malformed key uilds-retention - prefix stripping was too aggressive") + } +} + +func captureStdout(t *testing.T, fn func()) string { + t.Helper() + orig := os.Stdout + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("os.Pipe: %v", err) + } + os.Stdout = w + + done := make(chan string) + go func() { + var buf bytes.Buffer + _, _ = io.Copy(&buf, r) + done <- buf.String() + }() + + fn() + + w.Close() + os.Stdout = orig + return <-done +} + +func mut(base ReportSingleAppInput, f func(*ReportSingleAppInput)) ReportSingleAppInput { + cp := base + f(&cp) + return cp +} diff --git a/plugins/cron/report.go b/plugins/cron/report.go index 2e485a682..b3744e282 100644 --- a/plugins/cron/report.go +++ b/plugins/cron/report.go @@ -49,10 +49,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("cron", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "cron", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func addCronMaintenanceFlags(appName string, infoFlag string) map[string]common.ReportFunc { diff --git a/plugins/docker-options/report.go b/plugins/docker-options/report.go index 656ff92a3..5640e7d60 100644 --- a/plugins/docker-options/report.go +++ b/plugins/docker-options/report.go @@ -46,10 +46,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("docker options", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "docker-options", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportBuildOptions(appName string) string { diff --git a/plugins/logs/report.go b/plugins/logs/report.go index a786ece6d..91bfa6302 100644 --- a/plugins/logs/report.go +++ b/plugins/logs/report.go @@ -54,10 +54,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("logs", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "logs", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportComputedAppLabelAlias(appName string) string { diff --git a/plugins/network/report.go b/plugins/network/report.go index 38deeed1d..12f7a9d36 100644 --- a/plugins/network/report.go +++ b/plugins/network/report.go @@ -55,10 +55,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("network", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "network", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportAttachPostCreate(appName string) string { diff --git a/plugins/ports/report.go b/plugins/ports/report.go index 357cda900..855de7f06 100644 --- a/plugins/ports/report.go +++ b/plugins/ports/report.go @@ -29,10 +29,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("ports", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "ports", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportPortMap(appName string) string { diff --git a/plugins/proxy/report.go b/plugins/proxy/report.go index b971500d2..eaa1f9aa1 100644 --- a/plugins/proxy/report.go +++ b/plugins/proxy/report.go @@ -44,10 +44,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "proxy", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportEnabled(appName string) string { diff --git a/plugins/ps/report.go b/plugins/ps/report.go index c5a7a9cda..00f0304e5 100644 --- a/plugins/ps/report.go +++ b/plugins/ps/report.go @@ -60,10 +60,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("ps", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "ps", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func addStatusFlags(appName string, infoFlag string) map[string]common.ReportFunc { diff --git a/plugins/registry/report.go b/plugins/registry/report.go index c0cce6e42..c63387940 100644 --- a/plugins/registry/report.go +++ b/plugins/registry/report.go @@ -51,10 +51,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("registry", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "registry", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportComputedImageRepo(appName string) string { diff --git a/plugins/resource/resource.go b/plugins/resource/resource.go index 93c8285d4..236087984 100644 --- a/plugins/resource/resource.go +++ b/plugins/resource/resource.go @@ -41,9 +41,17 @@ func ReportSingleApp(appName, format, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := true - uppercaseFirstCharacter := false - return common.ReportSingleApp("resource", appName, infoFlag, flags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "resource", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: flags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: false, + EmitLegacyPrefix: true, + }) } // GetResourceValue fetches a single value for a given app/process/request/key combination diff --git a/plugins/scheduler-k3s/report.go b/plugins/scheduler-k3s/report.go index 7bbdbea97..5bb5e85b5 100644 --- a/plugins/scheduler-k3s/report.go +++ b/plugins/scheduler-k3s/report.go @@ -109,10 +109,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("scheduler-k3s", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "scheduler-k3s", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } // ReportAutoscalingAuthSingleApp is an internal function that displays the scheduler-k3s autoscaling-auth report for one app @@ -141,9 +149,16 @@ func ReportAutoscalingAuthSingleApp(appName string, format string, includeMetada flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true - return common.ReportSingleApp("scheduler-k3s", appName, "", infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "scheduler-k3s", + AppName: appName, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportComputedDeployTimeout(appName string) string { diff --git a/plugins/scheduler/report.go b/plugins/scheduler/report.go index 02570d8ba..be155a1fa 100644 --- a/plugins/scheduler/report.go +++ b/plugins/scheduler/report.go @@ -36,10 +36,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("scheduler", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "scheduler", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportComputedSelected(appName string) string { diff --git a/plugins/storage/report.go b/plugins/storage/report.go index d29c42e4e..25f45df27 100644 --- a/plugins/storage/report.go +++ b/plugins/storage/report.go @@ -25,10 +25,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { flagKeys = append(flagKeys, flagKey) } - trimPrefix := false - uppercaseFirstCharacter := true infoFlags := common.CollectReport(appName, infoFlag, flags) - return common.ReportSingleApp("storage", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "storage", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: true, + }) } func reportBuildMounts(appName string) string { diff --git a/tests/unit/app-json.bats b/tests/unit/app-json.bats index 4becfed65..b9bb6f8bf 100644 --- a/tests/unit/app-json.bats +++ b/tests/unit/app-json.bats @@ -287,6 +287,9 @@ teardown() { assert_output_contains '"app-json-appjson-path":"app2.json"' assert_output_contains '"app-json-computed-appjson-path":"app2.json"' assert_output_contains '"app-json-global-appjson-path":""' + assert_output_contains '"appjson-path":"app2.json"' + assert_output_contains '"computed-appjson-path":"app2.json"' + assert_output_contains '"global-appjson-path":""' assert_output_contains "app-json-selected" 0 run /bin/bash -c "dokku app-json:report $TEST_APP --app-json-selected" diff --git a/tests/unit/apps_1.bats b/tests/unit/apps_1.bats index e1dbab0c0..5838b9952 100644 --- a/tests/unit/apps_1.bats +++ b/tests/unit/apps_1.bats @@ -367,3 +367,21 @@ teardown() { destroy_app } + +@test "(apps:report) emits new stripped JSON keys alongside legacy" { + create_app + + run /bin/bash -c "dokku apps:report $TEST_APP --format json | jq -r 'has(\"dir\") and has(\"app-dir\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku apps:report $TEST_APP --format json | jq -r 'has(\"locked\") and has(\"app-locked\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku apps:report $TEST_APP --format json | jq -r 'has(\"deploy-source\") and has(\"app-deploy-source\")'" + assert_success + assert_output "true" + + destroy_app +} diff --git a/tests/unit/builder.bats b/tests/unit/builder.bats index a09136aab..8681f05e3 100644 --- a/tests/unit/builder.bats +++ b/tests/unit/builder.bats @@ -193,12 +193,30 @@ teardown() { assert_success assert_output "dockerfile" + run /bin/bash -c "dokku builder:report --global --format json | jq -r '.\"global-selected\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dockerfile" + run /bin/bash -c "dokku builder:report --global --format json | jq -r 'has(\"builder-selected\")'" echo "output: $output" echo "status: $status" assert_success assert_output "false" + run /bin/bash -c "dokku builder:report --global --format json | jq -r 'has(\"selected\") and has(\"builder-global-selected\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder:report --global --format json | jq -r 'has(\"computed-skip-cleanup\") and has(\"builder-computed-skip-cleanup\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + run /bin/bash -c "dokku builder:report --global" echo "output: $output" echo "status: $status" diff --git a/tests/unit/buildpacks.bats b/tests/unit/buildpacks.bats index 6c4ff9ec2..095884024 100644 --- a/tests/unit/buildpacks.bats +++ b/tests/unit/buildpacks.bats @@ -236,6 +236,24 @@ teardown() { assert_success } +@test "(buildpacks:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r 'has(\"stack\") and has(\"buildpacks-stack\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r 'has(\"global-stack\") and has(\"buildpacks-global-stack\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r 'has(\"computed-stack\") and has(\"buildpacks-computed-stack\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r 'has(\"list\") and has(\"buildpacks-list\")'" + assert_success + assert_output "true" +} + @test "(buildpacks) buildpacks:remove" { run /bin/bash -c "dokku buildpacks:set $TEST_APP heroku/nodejs" echo "output: $output" diff --git a/tests/unit/builds.bats b/tests/unit/builds.bats index 366658ccf..115916884 100644 --- a/tests/unit/builds.bats +++ b/tests/unit/builds.bats @@ -331,3 +331,22 @@ EOF # Recreate so teardown can run cleanly. create_app } + +@test "(builds:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku builds:report $TEST_APP --format json | jq -r 'has(\"retention\") and has(\"builds-retention\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builds:report $TEST_APP --format json | jq -r 'has(\"global-retention\") and has(\"builds-global-retention\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builds:report $TEST_APP --format json | jq -r 'has(\"computed-retention\") and has(\"builds-computed-retention\")'" + assert_success + assert_output "true" + + # build-* status keys have no plugin-prefix collision, so they pass through unchanged + run /bin/bash -c "dokku builds:report $TEST_APP --format json | jq -r 'has(\"build-id\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/cron.bats b/tests/unit/cron.bats index 9240b83af..b52a16e71 100644 --- a/tests/unit/cron.bats +++ b/tests/unit/cron.bats @@ -124,26 +124,50 @@ teardown() { assert_success assert_output "true" + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"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(\"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(\"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(\"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(\"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 --format json | jq -r 'has(\"computed-mailto\")'" + assert_success + assert_output "true" + run /bin/bash -c "dokku cron:report --global" echo "output: $output" echo "status: $status" diff --git a/tests/unit/docker-options-2.bats b/tests/unit/docker-options-2.bats index f7f7f289f..1f9f558b1 100644 --- a/tests/unit/docker-options-2.bats +++ b/tests/unit/docker-options-2.bats @@ -184,9 +184,11 @@ teardown() { echo "status: $status" assert_success assert_output_contains '"docker-options-deploy.web"' - assert_output_contains "-p 8080:5000" + assert_output_contains '"deploy.web"' + assert_output_contains "-p 8080:5000" 2 assert_output_contains '"docker-options-deploy"' - assert_output_contains "-v /logs:/logs" + assert_output_contains '"deploy"' + assert_output_contains "-v /logs:/logs" 2 } @test "(docker-options) clone copies default and per-process options" { diff --git a/tests/unit/logs.bats b/tests/unit/logs.bats index 7df4f8c89..5e5335630 100644 --- a/tests/unit/logs.bats +++ b/tests/unit/logs.bats @@ -965,3 +965,28 @@ teardown() { assert_success assert_output_contains "Stopping and removing vector container" } + +@test "(logs:report) emits new stripped JSON keys alongside legacy" { + run create_app + assert_success + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r 'has(\"vector-sink\") and has(\"logs-vector-sink\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r 'has(\"global-vector-sink\") and has(\"logs-global-vector-sink\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r 'has(\"computed-vector-sink\") and has(\"logs-computed-vector-sink\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r 'has(\"max-size\") and has(\"logs-max-size\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku logs:report --global --format json | jq -r 'has(\"global-vector-image\") and has(\"logs-global-vector-image\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/network.bats b/tests/unit/network.bats index 79f7e9ed3..d57a24d0d 100644 --- a/tests/unit/network.bats +++ b/tests/unit/network.bats @@ -48,12 +48,24 @@ teardown() { assert_success assert_output "dokku.test" + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"global-tld\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku.test" + run /bin/bash -c "dokku network:report --global --format json | jq -r 'has(\"network-tld\")'" echo "output: $output" echo "status: $status" assert_success assert_output "false" + run /bin/bash -c "dokku network:report --global --format json | jq -r 'has(\"tld\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + run /bin/bash -c "dokku network:report --global" echo "output: $output" echo "status: $status" @@ -66,6 +78,24 @@ teardown() { assert_success } +@test "(network:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku network:report --global --format json | jq -r 'has(\"global-tld\") and has(\"network-global-tld\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:report --global --format json | jq -r 'has(\"computed-tld\") and has(\"network-computed-tld\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r 'has(\"tld\") and has(\"network-tld\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r 'has(\"web-listeners\") and has(\"network-web-listeners\")'" + assert_success + assert_output "true" +} + @test "(network:report) tld raw vs computed" { run /bin/bash -c "dokku network:set --global tld" echo "output: $output" diff --git a/tests/unit/ports.bats b/tests/unit/ports.bats index e80821b98..99860b958 100644 --- a/tests/unit/ports.bats +++ b/tests/unit/ports.bats @@ -370,3 +370,13 @@ teardown() { assert_success assert_output "http:3001:3001" } + +@test "(ports:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku ports:report $TEST_APP --format json | jq -r 'has(\"map\") and has(\"ports-map\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ports:report $TEST_APP --format json | jq -r 'has(\"map-detected\") and has(\"ports-map-detected\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/proxy.bats b/tests/unit/proxy.bats index 3ffb867fb..a56538311 100644 --- a/tests/unit/proxy.bats +++ b/tests/unit/proxy.bats @@ -372,3 +372,21 @@ teardown() { assert_not_external_port $(<$CID_FILE) done } + +@test "(proxy:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r 'has(\"type\") and has(\"proxy-type\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r 'has(\"global-type\") and has(\"proxy-global-type\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r 'has(\"computed-type\") and has(\"proxy-computed-type\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r 'has(\"enabled\") and has(\"proxy-enabled\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/ps-general-1.bats b/tests/unit/ps-general-1.bats index 020591f88..c56cf427f 100644 --- a/tests/unit/ps-general-1.bats +++ b/tests/unit/ps-general-1.bats @@ -284,14 +284,26 @@ web assert_success assert_output "" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"stop-timeout-seconds\"'" + assert_success + assert_output "" + 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 '.\"global-stop-timeout-seconds\"'" + assert_success + assert_output "" + 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:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "30" + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds 90" assert_success @@ -299,10 +311,18 @@ web assert_success assert_output "90" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"global-stop-timeout-seconds\"'" + assert_success + assert_output "90" + 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:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "90" + run /bin/bash -c "dokku ps:set $TEST_APP stop-timeout-seconds 45" assert_success @@ -310,6 +330,10 @@ web assert_success assert_output "45" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"stop-timeout-seconds\"'" + assert_success + assert_output "45" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" assert_success assert_output "90" @@ -318,6 +342,10 @@ web assert_success assert_output "45" + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "45" + run /bin/bash -c "dokku ps:set $TEST_APP stop-timeout-seconds" assert_success @@ -335,18 +363,34 @@ web assert_success assert_output "true" + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"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(\"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(\"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:report --global --format json | jq -r 'has(\"computed-stop-timeout-seconds\")'" + assert_success + assert_output "true" + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds 75" assert_success @@ -354,10 +398,18 @@ web assert_success assert_output "75" + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"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:report --global --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "75" + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds" assert_success @@ -365,9 +417,17 @@ web assert_success assert_output "" + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"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" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "30" } @test "(ps:restart-policy) default policy" { diff --git a/tests/unit/registry.bats b/tests/unit/registry.bats index 4603a63e6..249676752 100644 --- a/tests/unit/registry.bats +++ b/tests/unit/registry.bats @@ -717,3 +717,29 @@ teardown() { assert_success assert_output_contains "dokku/test-app:foo" } + +@test "(registry:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"image-repo\") and has(\"registry-image-repo\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"computed-image-repo\") and has(\"registry-computed-image-repo\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"push-on-release\") and has(\"registry-push-on-release\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"global-push-on-release\") and has(\"registry-global-push-on-release\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"computed-server\") and has(\"registry-computed-server\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r 'has(\"global-image-repo-template\") and has(\"registry-global-image-repo-template\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/scheduler-k3s-report.bats b/tests/unit/scheduler-k3s-report.bats index f38547d3f..958402ace 100644 --- a/tests/unit/scheduler-k3s-report.bats +++ b/tests/unit/scheduler-k3s-report.bats @@ -158,3 +158,21 @@ assert_k3s_global_unset_set() { run /bin/bash -c "dokku scheduler-k3s:set --global token" assert_success } + +@test "(scheduler-k3s:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r 'has(\"global-deploy-timeout\") and has(\"scheduler-k3s-global-deploy-timeout\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r 'has(\"computed-deploy-timeout\") and has(\"scheduler-k3s-computed-deploy-timeout\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r 'has(\"global-namespace\") and has(\"scheduler-k3s-global-namespace\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r 'has(\"computed-namespace\") and has(\"scheduler-k3s-computed-namespace\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/scheduler.bats b/tests/unit/scheduler.bats index 235557bf3..2f110894a 100644 --- a/tests/unit/scheduler.bats +++ b/tests/unit/scheduler.bats @@ -145,3 +145,21 @@ teardown() { assert_success assert_output "false" } + +@test "(scheduler:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r 'has(\"selected\") and has(\"scheduler-selected\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r 'has(\"global-selected\") and has(\"scheduler-global-selected\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r 'has(\"computed-selected\") and has(\"scheduler-computed-selected\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler:report --global --format json | jq -r 'has(\"global-selected\") and has(\"scheduler-global-selected\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/storage.bats b/tests/unit/storage.bats index f3d11f544..7c8ca4121 100644 --- a/tests/unit/storage.bats +++ b/tests/unit/storage.bats @@ -486,3 +486,17 @@ teardown() { run /bin/bash -c "sudo test -e $flag_dir/$TEST_APP" assert_failure } + +@test "(storage:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"build-mounts\") and has(\"storage-build-mounts\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"deploy-mounts\") and has(\"storage-deploy-mounts\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"run-mounts\") and has(\"storage-run-mounts\")'" + assert_success + assert_output "true" +}