diff --git a/docs/appendices/0.38.0-migration-guide.md b/docs/appendices/0.38.0-migration-guide.md index 46940ecf2..835a38def 100644 --- a/docs/appendices/0.38.0-migration-guide.md +++ b/docs/appendices/0.38.0-migration-guide.md @@ -23,7 +23,7 @@ - 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. - 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 `*******`. +- 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 are reported as `--traefik-global-dns-provider-` and follow the same masking and explicit-query rules, as does the traefik `basic-auth-password` property, whose `--traefik-global-basic-auth-password` and `--traefik-computed-basic-auth-password` keys are masked in default stdout output. - 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. - **New in 0.38.25:** Values supplied through docker options, `dokku run`'s `-e`/`--env` flag, and `--ttl-seconds` are no longer evaluated by the shell when assembling a container's arguments; they are now tokenized and passed through verbatim. This closes a command-injection vector where a `$(...)` or backtick expression in one of these values executed on the host as the `dokku` user during build, deploy, or run. As a result, shell metacharacters such as `$(...)`, backticks, `$VAR`, and globs in these values are treated literally instead of being expanded, and `--ttl-seconds` must now be a plain integer. Existing Traefik docker-options labels (those whose label key begins with `traefik.`) whose backticks were stored with a stray backslash are repaired automatically the first time `dokku` runs after the upgrade, so they become valid on the next deploy. diff --git a/docs/networking/proxies/traefik.md b/docs/networking/proxies/traefik.md index 2d079cc21..580ea5e18 100644 --- a/docs/networking/proxies/traefik.md +++ b/docs/networking/proxies/traefik.md @@ -260,6 +260,16 @@ dokku traefik:set --global dns-provider-cf_api_key your-api-key The `dns-provider-` prefix will be stripped and the variable name will be uppercased when passed to the Traefik container. For example, `dns-provider-cf_api_email` becomes `CF_API_EMAIL`. +Each configured variable is surfaced by `traefik:report` as `--traefik-global-dns-provider-`. As these values are provider credentials, they are masked as `*******` in the default report output, including the aggregate `dokku report`. The raw value is returned when the flag is requested explicitly or when the report is rendered as json: + +```shell +dokku traefik:report --global --traefik-global-dns-provider-cf_api_email +``` + +```shell +dokku traefik:report --global --format json | jq -r '."global-dns-provider-cf_api_email"' +``` + After configuring, the Traefik container will need to be restarted and apps will need to be rebuilt. Refer to the [Traefik DNS Challenge documentation](https://doc.traefik.io/traefik/https/acme/#dnschallenge) for the list of supported DNS providers and their required environment variables. @@ -403,12 +413,12 @@ All traefik properties are global only. Set with `traefik:set --global ` | global only | none | `--traefik-dns-provider-` (masked as `*******` in the default stdout report; the raw value is returned when queried via `--format json` or when this flag is requested explicitly) | Per-provider environment variables passed to the Traefik container; `` is the upstream variable name (e.g. `dns-provider-cloudflare-api-token`) | +| `dns-provider-` | global only | none | `--traefik-global-dns-provider-` (masked as `*******` in the default stdout report; the raw value is returned when queried via `--format json` or when this flag is requested explicitly) | Per-provider environment variables passed to the Traefik container; `` is the upstream variable name (e.g. `dns-provider-cloudflare-api-token`) | | `http-entry-point` | global only | `http` | `--traefik-global-http-entry-point`, `--traefik-computed-http-entry-point` | Entry point name handling plaintext HTTP traffic | | `https-entry-point` | global only | `https` | `--traefik-global-https-entry-point`, `--traefik-computed-https-entry-point` | Entry point name handling TLS-terminated HTTPS traffic | | `image` | global only | _parsed from `plugins/traefik-vhosts/Dockerfile`_ | `--traefik-global-image`, `--traefik-computed-image` | Docker image used to run the Traefik container | diff --git a/plugins/traefik-vhosts/report.go b/plugins/traefik-vhosts/report.go index a17cd87d2..f2aa9ba46 100644 --- a/plugins/traefik-vhosts/report.go +++ b/plugins/traefik-vhosts/report.go @@ -8,6 +8,9 @@ import ( "github.com/dokku/dokku/plugins/common" ) +// valueMask is shown in place of credential values in the default stdout report +const valueMask = "*******" + // ReportSingleApp is an internal function that displays the traefik report for one or more apps func ReportSingleApp(appName string, format string, infoFlag string) error { if appName != "--global" { @@ -49,20 +52,16 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { "--traefik-global-log-level": reportGlobalLogLevel, } - // dns-provider-* env vars are dynamic; their values are masked in the default - // stdout report, but shown for --format json or when queried explicitly by name - dnsProviderVars, err := common.PropertyGetAllByPrefix("traefik", "--global", "dns-provider-") + for _, flagName := range []string{"--traefik-computed-basic-auth-password", "--traefik-global-basic-auth-password"} { + flags[flagName] = maskedReportFunc(flags[flagName], flagName, format, infoFlag) + } + + dnsProviderFlags, err := dnsProviderReportFlags(format, infoFlag) if err != nil { return err } - for key, value := range dnsProviderVars { - flagName := "--traefik-" + key - realValue := value - if format == "json" || infoFlag == flagName { - flags[flagName] = func(string) string { return realValue } - } else { - flags[flagName] = func(string) string { return "*******" } - } + for flagName, reportFunc := range dnsProviderFlags { + flags[flagName] = reportFunc } flagKeys := []string{} @@ -84,6 +83,40 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { }) } +// maskedReportFunc hides a credential value behind valueMask so the default stdout +// report - and thus the aggregate `dokku report` - never prints it. The raw value is +// returned for machine-readable output or when the flag is requested by name. +func maskedReportFunc(fn common.ReportFunc, flagName string, format string, infoFlag string) common.ReportFunc { + if format == "json" || infoFlag == flagName { + return fn + } + + return func(appName string) string { + if fn(appName) == "" { + return "" + } + + return valueMask + } +} + +// dnsProviderReportFlags returns report functions for the dynamic dns-provider-* +// properties, keyed by their global report flag +func dnsProviderReportFlags(format string, infoFlag string) (map[string]common.ReportFunc, error) { + flags := map[string]common.ReportFunc{} + properties, err := common.PropertyGetAllByPrefix("traefik", "--global", "dns-provider-") + if err != nil { + return flags, err + } + + for property, value := range properties { + flagName := "--traefik-global-" + property + flags[flagName] = maskedReportFunc(func(string) string { return value }, flagName, format, infoFlag) + } + + return flags, nil +} + func reportGlobalAPIEnabled(appName string) string { return common.PropertyGet("traefik", "--global", "api-enabled") } diff --git a/plugins/traefik-vhosts/report_test.go b/plugins/traefik-vhosts/report_test.go new file mode 100644 index 000000000..e38c30b26 --- /dev/null +++ b/plugins/traefik-vhosts/report_test.go @@ -0,0 +1,143 @@ +package traefikvhosts + +import ( + "os/user" + "path/filepath" + "testing" + + "github.com/dokku/dokku/plugins/common" +) + +// setupPropertyEnv points the dokku env at temporary directories and tells the +// permission helpers to chown files to the current user (a no-op) so the test +// works without root. +func setupPropertyEnv(t *testing.T) { + t.Helper() + + libRoot := t.TempDir() + + t.Setenv("DOKKU_LIB_ROOT", libRoot) + t.Setenv("DOKKU_ROOT", t.TempDir()) + t.Setenv("PLUGIN_PATH", filepath.Join(libRoot, "plugins")) + + current, err := user.Current() + if err != nil { + t.Fatalf("user.Current: %v", err) + } + group, err := user.LookupGroupId(current.Gid) + if err != nil { + t.Fatalf("user.LookupGroupId: %v", err) + } + t.Setenv("DOKKU_SYSTEM_USER", current.Username) + t.Setenv("DOKKU_SYSTEM_GROUP", group.Name) +} + +func staticReportFunc(value string) common.ReportFunc { + return func(string) string { + return value + } +} + +func TestMaskedReportFunc(t *testing.T) { + const flagName = "--traefik-global-basic-auth-password" + + cases := []struct { + name string + value string + format string + infoFlag string + want string + }{ + {name: "stdout masks a set value", value: "hunter2", format: "stdout", want: valueMask}, + {name: "empty format masks a set value", value: "hunter2", want: valueMask}, + {name: "stdout leaves an unset value empty", value: "", format: "stdout", want: ""}, + {name: "json returns the raw value", value: "hunter2", format: "json", want: "hunter2"}, + {name: "matching info flag returns the raw value", value: "hunter2", format: "stdout", infoFlag: flagName, want: "hunter2"}, + {name: "other info flag masks the value", value: "hunter2", format: "stdout", infoFlag: "--traefik-global-basic-auth-username", want: valueMask}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + fn := maskedReportFunc(staticReportFunc(tc.value), flagName, tc.format, tc.infoFlag) + if got := fn("--global"); got != tc.want { + t.Errorf("maskedReportFunc(%q, %q, %q) = %q, want %q", tc.value, tc.format, tc.infoFlag, got, tc.want) + } + }) + } +} + +func TestDNSProviderReportFlags(t *testing.T) { + setupPropertyEnv(t) + + if err := common.PropertyWrite("traefik", "--global", "dns-provider", "cloudflare"); err != nil { + t.Fatalf("PropertyWrite dns-provider: %v", err) + } + if err := common.PropertyWrite("traefik", "--global", "dns-provider-cf_api_email", "test@example.com"); err != nil { + t.Fatalf("PropertyWrite dns-provider-cf_api_email: %v", err) + } + if err := common.PropertyWrite("traefik", "--global", "dns-provider-cf_api_key", "secret-key"); err != nil { + t.Fatalf("PropertyWrite dns-provider-cf_api_key: %v", err) + } + + flags, err := dnsProviderReportFlags("stdout", "") + if err != nil { + t.Fatalf("dnsProviderReportFlags: %v", err) + } + + assertReportFuncValue(t, flags, "--traefik-global-dns-provider-cf_api_email", valueMask) + assertReportFuncValue(t, flags, "--traefik-global-dns-provider-cf_api_key", valueMask) + if len(flags) != 2 { + t.Errorf("flags = %v, want only the two dns-provider-* env var flags", flagNames(flags)) + } + if _, ok := flags["--traefik-global-dns-provider"]; ok { + t.Error("--traefik-global-dns-provider should be reported statically, not as a dynamic env var") + } + if _, ok := flags["--traefik-dns-provider-cf_api_email"]; ok { + t.Error("--traefik-dns-provider-cf_api_email should not be emitted outside the global namespace") + } + + flags, err = dnsProviderReportFlags("json", "") + if err != nil { + t.Fatalf("dnsProviderReportFlags: %v", err) + } + assertReportFuncValue(t, flags, "--traefik-global-dns-provider-cf_api_email", "test@example.com") + assertReportFuncValue(t, flags, "--traefik-global-dns-provider-cf_api_key", "secret-key") + + flags, err = dnsProviderReportFlags("stdout", "--traefik-global-dns-provider-cf_api_key") + if err != nil { + t.Fatalf("dnsProviderReportFlags: %v", err) + } + assertReportFuncValue(t, flags, "--traefik-global-dns-provider-cf_api_email", valueMask) + assertReportFuncValue(t, flags, "--traefik-global-dns-provider-cf_api_key", "secret-key") +} + +func TestDNSProviderReportFlagsWithoutProperties(t *testing.T) { + setupPropertyEnv(t) + + flags, err := dnsProviderReportFlags("stdout", "") + if err != nil { + t.Fatalf("dnsProviderReportFlags: %v", err) + } + if len(flags) != 0 { + t.Errorf("flags = %v, want no flags", flagNames(flags)) + } +} + +func assertReportFuncValue(t *testing.T, flags map[string]common.ReportFunc, flagName string, want string) { + t.Helper() + fn, ok := flags[flagName] + if !ok { + t.Fatalf("flag %q missing from %v", flagName, flagNames(flags)) + } + if got := fn("--global"); got != want { + t.Errorf("flag %q = %q, want %q", flagName, got, want) + } +} + +func flagNames(flags map[string]common.ReportFunc) []string { + names := []string{} + for name := range flags { + names = append(names, name) + } + return names +} diff --git a/tests.mk b/tests.mk index 17c5a1aab..cb884a742 100644 --- a/tests.mk +++ b/tests.mk @@ -187,6 +187,7 @@ go-tests: @$(MAKE) go-test-plugin PLUGIN_NAME=buildpacks @$(MAKE) go-test-plugin PLUGIN_NAME=scheduler-k3s @$(MAKE) go-test-plugin PLUGIN_NAME=storage + @$(MAKE) go-test-plugin PLUGIN_NAME=traefik-vhosts go-test-plugin: cd plugins/$(PLUGIN_NAME) && go get github.com/onsi/gomega && DOKKU_ROOT=/home/dokku DOKKU_LIB_ROOT=/var/lib/dokku go test -v -p 1 -race -mod=readonly || exit $$? diff --git a/tests/unit/traefik-vhosts.bats b/tests/unit/traefik-vhosts.bats index b1d6a6e9f..4374b5a78 100644 --- a/tests/unit/traefik-vhosts.bats +++ b/tests/unit/traefik-vhosts.bats @@ -3,11 +3,14 @@ load test_helper setup() { + dokku traefik:set --global basic-auth-password + dokku traefik:set --global dns-provider-test_key create_app } teardown() { destroy_app + dokku traefik:set --global basic-auth-password >/dev/null 2>&1 || true dokku traefik:set --global challenge-mode >/dev/null 2>&1 || true dokku traefik:set --global dns-provider-test_key >/dev/null 2>&1 || true } @@ -58,15 +61,74 @@ teardown() { assert_success assert_output_contains "*******" - run /bin/bash -c "dokku traefik:report --global --traefik-dns-provider-test_key" + run /bin/bash -c "dokku traefik:report --global --traefik-global-dns-provider-test_key" echo "output: $output" echo "status: $status" assert_success assert_output "secretvalue" - run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"dns-provider-test_key\"'" + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"global-dns-provider-test_key\"'" echo "output: $output" echo "status: $status" assert_success assert_output "secretvalue" } + +@test "(traefik:report) basic-auth-password is masked unless queried" { + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"global-basic-auth-password\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku traefik:report --global | grep 'basic auth password'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "*******" + + run /bin/bash -c "dokku traefik:set --global basic-auth-password secretpassword" + assert_success + + run /bin/bash -c "dokku traefik:report --global | grep 'basic auth password'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "*******" 2 + assert_output_not_contains "secretpassword" + + run /bin/bash -c "dokku traefik:report --global --traefik-global-basic-auth-password" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secretpassword" + + run /bin/bash -c "dokku traefik:report --global --traefik-computed-basic-auth-password" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secretpassword" + + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"global-basic-auth-password\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secretpassword" +} + +@test "(traefik:report) credentials are masked in the aggregate report" { + run /bin/bash -c "dokku traefik:set --global dns-provider-test_key secretvalue" + assert_success + + run /bin/bash -c "dokku traefik:set --global basic-auth-password secretpassword" + assert_success + + run /bin/bash -c "dokku report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global dns provider test_key" + assert_output_contains "*******" -1 + assert_output_not_contains "secretvalue" + assert_output_not_contains "secretpassword" +} diff --git a/tests/unit/traefik.bats b/tests/unit/traefik.bats index 839ba1fb7..86cd2a5fe 100644 --- a/tests/unit/traefik.bats +++ b/tests/unit/traefik.bats @@ -10,6 +10,8 @@ setup() { dokku traefik:set --global api-enabled dokku traefik:set --global api-entry-point dokku traefik:set --global api-entry-point-address + dokku traefik:set --global basic-auth-username + dokku traefik:set --global basic-auth-password dokku traefik:set --global challenge-mode dokku traefik:set --global dns-provider dokku traefik:start @@ -539,19 +541,19 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "dns provider cf_api_email" - assert_output_contains "dns provider cf_api_key" + assert_output_contains "global dns provider cf_api_email" + assert_output_contains "global dns provider cf_api_key" assert_output_contains "*******" 2 assert_output_not_contains "test@example.com" assert_output_not_contains "secret-key" - run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-dns-provider-cf_api_email" + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-global-dns-provider-cf_api_email" echo "output: $output" echo "status: $status" assert_success assert_output "test@example.com" - run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-dns-provider-cf_api_key" + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-global-dns-provider-cf_api_key" echo "output: $output" echo "status: $status" assert_success @@ -571,8 +573,8 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_not_contains "dns provider cf_api_email" - assert_output_not_contains "dns provider cf_api_key" + assert_output_not_contains "global dns provider cf_api_email" + assert_output_not_contains "global dns provider cf_api_key" } @test "(traefik) [dns-01] dns-provider-* can only be set globally" { @@ -610,13 +612,13 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku traefik:report $TEST_APP --format json | jq -r '.\"dns-provider-cf_api_email\"'" + run /bin/bash -c "dokku traefik:report $TEST_APP --format json | jq -r '.\"global-dns-provider-cf_api_email\"'" echo "output: $output" echo "status: $status" assert_success assert_output "test@example.com" - run /bin/bash -c "dokku traefik:report $TEST_APP --format json | jq -r '.\"dns-provider-cf_api_key\"'" + run /bin/bash -c "dokku traefik:report $TEST_APP --format json | jq -r '.\"global-dns-provider-cf_api_key\"'" echo "output: $output" echo "status: $status" assert_success