diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index c146d3a45..422223d41 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -657,8 +657,8 @@ dokku scheduler-k3s:autoscaling-auth:report --global ``` ``` -====> $APP autoscaling-auth report - datadog: configured +====> node-js-app autoscaling-auth information + Datadog: configured ``` By default, the report will not display configured metadata - making it safe to include in Dokku report output. To include metadata and their values, add the `--include-metadata` flag: @@ -668,13 +668,25 @@ dokku scheduler-k3s:autoscaling-auth:report node-js-app --include-metadata ``` ``` -====> node-js-app autoscaling-auth report +====> node-js-app autoscaling-auth information Datadog: configured Datadog apiKey: 1234567890 Datadog appKey: asdfghjkl Datadog datadogSite: us5.datadoghq.com ``` +JSON output emits flat keys of the form `{trigger}.{metadata_key}` and includes the actual metadata values so export tools can reconstruct the configured state: + +```shell +dokku scheduler-k3s:autoscaling-auth:report node-js-app --format json +``` + +A single configured metadata key can also be queried with a flag of the form `--scheduler-k3s-autoscaling-auth.{trigger}.{metadata_key}`. The returned value is masked in the same way as stdout output; use `--format json` to read the actual value: + +```shell +dokku scheduler-k3s:autoscaling-auth:report node-js-app --scheduler-k3s-autoscaling-auth.datadog.apiKey +``` + ### Integrating Kustomize Dokku supports integration with [Kustomize](https://kustomize.io/) to further customize the generated helm charts for app deployments. For example, a `config/kustomize/kustomization.yaml` file with the following contents will override the scale for each process deployed to `3`: diff --git a/plugins/scheduler-k3s/.gitignore b/plugins/scheduler-k3s/.gitignore index 120774657..ec24c9244 100644 --- a/plugins/scheduler-k3s/.gitignore +++ b/plugins/scheduler-k3s/.gitignore @@ -7,3 +7,4 @@ /post-* /report /core-* +/storage-* diff --git a/plugins/scheduler-k3s/report.go b/plugins/scheduler-k3s/report.go index 2b21d91af..dde2134e9 100644 --- a/plugins/scheduler-k3s/report.go +++ b/plugins/scheduler-k3s/report.go @@ -10,8 +10,9 @@ import ( ) const ( - annotationsReportType = "scheduler-k3s-annotations" - labelsReportType = "scheduler-k3s-labels" + annotationsReportType = "scheduler-k3s-annotations" + labelsReportType = "scheduler-k3s-labels" + autoscalingAuthReportType = "scheduler-k3s-autoscaling-auth" // reportProcessTypeGlobal is the rendered form of GlobalProcessType in annotation // and label report keys (both JSON and single-flag). Real process types cannot @@ -138,42 +139,164 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { }) } -// ReportAutoscalingAuthSingleApp is an internal function that displays the scheduler-k3s autoscaling-auth report for one app -func ReportAutoscalingAuthSingleApp(appName string, format string, includeMetadata bool) error { - properties, err := common.PropertyGetAllByPrefix("scheduler-k3s", appName, TriggerAuthPropertyPrefix) - if err != nil { - return fmt.Errorf("Unable to get property list: %w", err) +// autoscalingAuthReportEntry is a parsed trigger authentication metadata entry. +type autoscalingAuthReportEntry struct { + Trigger string + MetadataKey string + Value string +} + +// ReportAutoscalingAuthSingleApp displays the scheduler-k3s autoscaling-auth report for one app. +func ReportAutoscalingAuthSingleApp(appName string, format string, includeMetadata bool, infoFlag string) error { + if appName != "--global" { + if err := common.VerifyAppName(appName); err != nil { + return err + } } - infoFlags := map[string]string{} + entries, err := collectAutoscalingAuthEntries(appName) + if err != nil { + return err + } + + return renderAutoscalingAuthReport(autoscalingAuthRenderInput{ + AppName: appName, + Entries: entries, + Format: format, + IncludeMetadata: includeMetadata, + InfoFlag: infoFlag, + }) +} + +// collectAutoscalingAuthEntries scans the property store for trigger authentication metadata. +func collectAutoscalingAuthEntries(appName string) ([]autoscalingAuthReportEntry, error) { + properties, err := common.PropertyGetAllByPrefix("scheduler-k3s", appName, TriggerAuthPropertyPrefix) + if err != nil { + return nil, fmt.Errorf("Unable to get property list: %w", err) + } + + entries := []autoscalingAuthReportEntry{} for key, value := range properties { metadataKey := strings.TrimPrefix(key, TriggerAuthPropertyPrefix) - authType := strings.SplitN(metadataKey, ".", 2)[0] - if authType == "" { + parts := strings.SplitN(metadataKey, ".", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { continue } - infoFlags[authType] = "configured" - if includeMetadata { - infoFlags[strings.ReplaceAll(metadataKey, ".", "-")] = value + entries = append(entries, autoscalingAuthReportEntry{ + Trigger: parts[0], + MetadataKey: parts[1], + Value: value, + }) + } + + return entries, nil +} + +type autoscalingAuthRenderInput struct { + AppName string + Entries []autoscalingAuthReportEntry + Format string + IncludeMetadata bool + InfoFlag string +} + +// renderAutoscalingAuthReport dispatches to stdout/json/single-flag rendering for trigger auth. +func renderAutoscalingAuthReport(input autoscalingAuthRenderInput) error { + if input.Format != "stdout" && input.Format != "json" { + return fmt.Errorf("Invalid format: %s", input.Format) + } + if input.Format == "json" && input.InfoFlag != "" { + return fmt.Errorf("--format flag cannot be specified when specifying an info flag") + } + + flatValues := map[string]string{} + flagToValue := map[string]string{} + flagPrefix := "--" + autoscalingAuthReportType + "." + for _, entry := range input.Entries { + composedKey := entry.Trigger + "." + entry.MetadataKey + flatValues[composedKey] = entry.Value + flagToValue[flagPrefix+composedKey] = entry.Value + } + + if input.InfoFlag != "" { + value, ok := flagToValue[input.InfoFlag] + if !ok { + validFlags := []string{} + for flag := range flagToValue { + validFlags = append(validFlags, flag) + } + sort.Strings(validFlags) + return fmt.Errorf("Invalid flag passed, valid flags: %s", strings.Join(validFlags, ", ")) + } + if value != "" { + fmt.Println(tokenMask) + } else { + fmt.Println("") + } + return nil + } + + if input.Format == "json" { + b, err := json.Marshal(flatValues) + if err != nil { + return fmt.Errorf("Unable to marshal json: %w", err) + } + fmt.Println(string(b)) + return nil + } + + triggers := map[string]bool{} + for _, entry := range input.Entries { + triggers[entry.Trigger] = true + } + + triggerKeys := []string{} + for trigger := range triggers { + triggerKeys = append(triggerKeys, trigger) + } + sort.Strings(triggerKeys) + + common.LogInfo2Quiet(fmt.Sprintf("%s autoscaling-auth information", input.AppName)) + if len(triggerKeys) == 0 { + return nil + } + + length := 31 + if input.IncludeMetadata { + for _, entry := range input.Entries { + label := fmt.Sprintf("%s %s:", common.UcFirst(entry.Trigger), entry.MetadataKey) + if len(label) > length { + length = len(label) + } } } - flagKeys := []string{} - for flagKey := range infoFlags { - flagKeys = append(flagKeys, flagKey) + for _, trigger := range triggerKeys { + label := fmt.Sprintf("%s:", common.UcFirst(trigger)) + common.LogVerbose(fmt.Sprintf("%s%s", common.RightPad(label, length, " "), "configured")) + + if !input.IncludeMetadata { + continue + } + + triggerEntries := []autoscalingAuthReportEntry{} + for _, entry := range input.Entries { + if entry.Trigger == trigger { + triggerEntries = append(triggerEntries, entry) + } + } + sort.Slice(triggerEntries, func(i, j int) bool { + return triggerEntries[i].MetadataKey < triggerEntries[j].MetadataKey + }) + + for _, entry := range triggerEntries { + label := fmt.Sprintf("%s %s:", common.UcFirst(entry.Trigger), entry.MetadataKey) + common.LogVerbose(fmt.Sprintf("%s%s", common.RightPad(label, length, " "), entry.Value)) + } } - return common.ReportSingleApp(common.ReportSingleAppInput{ - ReportType: "scheduler-k3s", - AppName: appName, - InfoFlags: infoFlags, - InfoFlagKeys: flagKeys, - Format: format, - TrimPrefix: true, - UppercaseFirstCharacter: true, - EmitLegacyPrefix: true, - }) + return nil } // ExtractReportInfoFlag scans arguments for a single info flag whose name starts with diff --git a/plugins/scheduler-k3s/report_test.go b/plugins/scheduler-k3s/report_test.go new file mode 100644 index 000000000..b4ec283a0 --- /dev/null +++ b/plugins/scheduler-k3s/report_test.go @@ -0,0 +1,271 @@ +package scheduler_k3s + +import ( + "bytes" + "encoding/json" + "io" + "os" + "strings" + "testing" + + "github.com/dokku/dokku/plugins/common" +) + +func setupReportTest(t *testing.T, apps ...string) { + t.Helper() + t.Setenv("PLUGIN_PATH", "/var/lib/dokku/plugins") + t.Setenv("PLUGIN_ENABLED_PATH", "/var/lib/dokku/plugins/enabled") + t.Setenv("DOKKU_LIB_ROOT", t.TempDir()) + t.Setenv("DOKKU_SYSTEM_USER", "root") + t.Setenv("DOKKU_SYSTEM_GROUP", "root") + dokkuRoot := t.TempDir() + t.Setenv("DOKKU_ROOT", dokkuRoot) + if err := common.PropertySetup("scheduler-k3s"); err != nil { + t.Fatalf("PropertySetup: %v", err) + } + for _, appName := range apps { + if err := os.MkdirAll(dokkuRoot+"/"+appName, 0o755); err != nil { + t.Fatalf("MkdirAll: %v", err) + } + } +} + +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 TestExtractReportInfoFlag(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + prefix string + args []string + wantArgs []string + wantFlag string + wantError string + }{ + { + name: "no info flag", + prefix: "--scheduler-k3s-annotations.", + args: []string{"myapp", "--format", "json"}, + wantArgs: []string{"myapp", "--format", "json"}, + }, + { + name: "single info flag", + prefix: "--scheduler-k3s-labels.", + args: []string{"myapp", "--scheduler-k3s-labels.global.deployment.foo"}, + wantArgs: []string{"myapp"}, + wantFlag: "--scheduler-k3s-labels.global.deployment.foo", + }, + { + name: "multiple info flags", + prefix: "--scheduler-k3s-autoscaling-auth.", + args: []string{"myapp", "--scheduler-k3s-autoscaling-auth.datadog.apiKey", "--scheduler-k3s-autoscaling-auth.datadog.appKey"}, + wantError: "only a single info flag may be specified", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + gotArgs, gotFlag, err := ExtractReportInfoFlag(tc.prefix, tc.args) + if tc.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("expected error containing %q, got %v", tc.wantError, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gotFlag != tc.wantFlag { + t.Fatalf("info flag = %q, want %q", gotFlag, tc.wantFlag) + } + if strings.Join(gotArgs, ",") != strings.Join(tc.wantArgs, ",") { + t.Fatalf("passthrough args = %v, want %v", gotArgs, tc.wantArgs) + } + }) + } +} + +func TestRenderProcessType(t *testing.T) { + t.Parallel() + + if got := renderProcessType(GlobalProcessType); got != reportProcessTypeGlobal { + t.Fatalf("renderProcessType(GlobalProcessType) = %q, want %q", got, reportProcessTypeGlobal) + } + if got := renderProcessType("web"); got != "web" { + t.Fatalf("renderProcessType(\"web\") = %q, want %q", got, "web") + } +} + +func TestRenderAnnotationsLabelsReportJSON(t *testing.T) { + out := captureStdout(t, func() { + err := renderAnnotationsLabelsReport(annotationsLabelsRenderInput{ + AppName: "myapp", + ReportType: annotationsReportType, + Heading: "annotations", + RowLabel: "Annotation", + Entries: []annotationsLabelsReportEntry{ + {ProcessType: GlobalProcessType, ResourceType: "deployment", Key: "foo", Value: "bar"}, + {ProcessType: "web", ResourceType: "deployment", Key: "baz", Value: "qux"}, + }, + Format: "json", + }) + if err != nil { + t.Fatalf("renderAnnotationsLabelsReport 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) + } + + want := map[string]string{ + "global.deployment.foo": "bar", + "web.deployment.baz": "qux", + } + for key, value := range want { + if got[key] != value { + t.Fatalf("expected %q=%q, got %q=%q", key, value, key, got[key]) + } + } +} + +func TestRenderAutoscalingAuthReportJSON(t *testing.T) { + out := captureStdout(t, func() { + err := renderAutoscalingAuthReport(autoscalingAuthRenderInput{ + AppName: "myapp", + Entries: []autoscalingAuthReportEntry{ + {Trigger: "datadog", MetadataKey: "apiKey", Value: "secret-1"}, + {Trigger: "datadog", MetadataKey: "appKey", Value: "secret-2"}, + }, + Format: "json", + }) + if err != nil { + t.Fatalf("renderAutoscalingAuthReport 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) + } + + want := map[string]string{ + "datadog.apiKey": "secret-1", + "datadog.appKey": "secret-2", + } + for key, value := range want { + if got[key] != value { + t.Fatalf("expected %q=%q, got %q=%q", key, value, key, got[key]) + } + } +} + +func TestReportAutoscalingAuthSingleAppInfoFlag(t *testing.T) { + setupReportTest(t, "myapp") + + if err := common.PropertyWrite("scheduler-k3s", "myapp", "trigger-auth.datadog.apiKey", "secret-1"); err != nil { + t.Fatalf("PropertyWrite: %v", err) + } + + out := captureStdout(t, func() { + err := ReportAutoscalingAuthSingleApp("myapp", "stdout", false, "--scheduler-k3s-autoscaling-auth.datadog.apiKey") + if err != nil { + t.Fatalf("ReportAutoscalingAuthSingleApp returned error: %v", err) + } + }) + + if strings.TrimSpace(out) != tokenMask { + t.Fatalf("info flag output = %q, want %q", strings.TrimSpace(out), tokenMask) + } +} + +func TestCollectAutoscalingAuthEntriesFromProperties(t *testing.T) { + setupReportTest(t, "myapp") + + if err := common.PropertyWrite("scheduler-k3s", "myapp", "trigger-auth.datadog.apiKey", "secret-1"); err != nil { + t.Fatalf("PropertyWrite apiKey: %v", err) + } + if err := common.PropertyWrite("scheduler-k3s", "myapp", "trigger-auth.datadog.appKey", "secret-2"); err != nil { + t.Fatalf("PropertyWrite appKey: %v", err) + } + + entries, err := collectAutoscalingAuthEntries("myapp") + if err != nil { + t.Fatalf("collectAutoscalingAuthEntries returned error: %v", err) + } + if len(entries) != 2 { + t.Fatalf("expected 2 entries, got %d", len(entries)) + } +} + +func TestReportAutoscalingAuthGlobalJSON(t *testing.T) { + setupReportTest(t) + + if err := common.PropertyWrite("scheduler-k3s", "--global", "trigger-auth.datadog.apiKey", "global-secret"); err != nil { + t.Fatalf("PropertyWrite: %v", err) + } + + out := captureStdout(t, func() { + err := ReportAutoscalingAuthSingleApp("--global", "json", false, "") + if err != nil { + t.Fatalf("ReportAutoscalingAuthSingleApp 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 got["datadog.apiKey"] != "global-secret" { + t.Fatalf("expected datadog.apiKey=global-secret, got %v", got) + } +} + +func TestRenderAutoscalingAuthReportStdoutIncludesMetadataValues(t *testing.T) { + out := captureStdout(t, func() { + err := renderAutoscalingAuthReport(autoscalingAuthRenderInput{ + AppName: "myapp", + Entries: []autoscalingAuthReportEntry{ + {Trigger: "datadog", MetadataKey: "apiKey", Value: "secret-1"}, + }, + Format: "stdout", + IncludeMetadata: true, + }) + if err != nil { + t.Fatalf("renderAutoscalingAuthReport returned error: %v", err) + } + }) + + if !strings.Contains(out, "Datadog apiKey:") { + t.Fatalf("expected metadata key in stdout, got: %s", out) + } + if !strings.Contains(out, "secret-1") { + t.Fatalf("expected metadata value in stdout, got: %s", out) + } +} diff --git a/plugins/scheduler-k3s/src/commands/commands.go b/plugins/scheduler-k3s/src/commands/commands.go index 02afdb4e6..0e93ffc3d 100644 --- a/plugins/scheduler-k3s/src/commands/commands.go +++ b/plugins/scheduler-k3s/src/commands/commands.go @@ -18,6 +18,7 @@ Additional commands:` helpContent = ` scheduler-k3s:autoscaling-auth:set [<--metadata key=value>...], Set or clear a scheduler-k3s autoscaling keda trigger authentication resource for an app + scheduler-k3s:autoscaling-auth:report [|--global] [--format stdout|json] [--include-metadata], Displays a scheduler-k3s autoscaling auth report for one or more apps scheduler-k3s:annotations:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear an annotation for a given app/process-type/resource-type combination scheduler-k3s:annotations:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE], Displays a scheduler-k3s annotations report for one or more apps scheduler-k3s:charts:report [] [--format stdout|json], Displays a scheduler-k3s chart override report diff --git a/plugins/scheduler-k3s/src/subcommands/subcommands.go b/plugins/scheduler-k3s/src/subcommands/subcommands.go index 418d4f677..7cc2f1e5b 100644 --- a/plugins/scheduler-k3s/src/subcommands/subcommands.go +++ b/plugins/scheduler-k3s/src/subcommands/subcommands.go @@ -66,14 +66,19 @@ func main() { case "autoscaling-auth:report": args := flag.NewFlagSet("scheduler-k3s:autoscaling-auth:report", flag.ExitOnError) global := args.Bool("global", false, "--global: show the global report") - includeMetadata := args.Bool("include-metadata", false, "--include-metadata: include metadata in the report") + includeMetadata := args.Bool("include-metadata", false, "--include-metadata: include metadata keys and values in the report") format := args.String("format", "stdout", "format: [ stdout | json ]") - args.Parse(os.Args[2:]) + passthroughArgs, infoFlag, flagErr := scheduler_k3s.ExtractReportInfoFlag("--scheduler-k3s-autoscaling-auth.", os.Args[2:]) + if flagErr != nil { + err = flagErr + break + } + args.Parse(passthroughArgs) appName := args.Arg(0) if *global { appName = "--global" } - err = scheduler_k3s.CommandAutoscalingAuthReport(appName, *format, *includeMetadata) + err = scheduler_k3s.CommandAutoscalingAuthReport(appName, *format, *includeMetadata, infoFlag) case "charts:set": args := flag.NewFlagSet("scheduler-k3s:charts:set", flag.ExitOnError) args.Parse(os.Args[2:]) diff --git a/plugins/scheduler-k3s/subcommands.go b/plugins/scheduler-k3s/subcommands.go index 7fae5e462..b65844c8f 100644 --- a/plugins/scheduler-k3s/subcommands.go +++ b/plugins/scheduler-k3s/subcommands.go @@ -108,7 +108,7 @@ func CommandAutoscalingAuthSet(appName string, trigger string, metadata map[stri } // CommandAutoscalingAuthReport displays a scheduler-k3s autoscaling keda trigger authentication report for one or more apps -func CommandAutoscalingAuthReport(appName string, format string, includeMetadata bool) error { +func CommandAutoscalingAuthReport(appName string, format string, includeMetadata bool, infoFlag string) error { if len(appName) == 0 { apps, err := common.DokkuApps() if err != nil { @@ -119,14 +119,14 @@ func CommandAutoscalingAuthReport(appName string, format string, includeMetadata return err } for _, app := range apps { - if err := ReportAutoscalingAuthSingleApp(app, format, includeMetadata); err != nil { + if err := ReportAutoscalingAuthSingleApp(app, format, includeMetadata, infoFlag); err != nil { return err } } return nil } - return ReportAutoscalingAuthSingleApp(appName, format, includeMetadata) + return ReportAutoscalingAuthSingleApp(appName, format, includeMetadata, infoFlag) } // CommandAnnotationsReport displays a scheduler-k3s annotations report for one or more apps diff --git a/tests/unit/scheduler-k3s-annotations.bats b/tests/unit/scheduler-k3s-annotations.bats index 709e61369..8ff04b1a3 100644 --- a/tests/unit/scheduler-k3s-annotations.bats +++ b/tests/unit/scheduler-k3s-annotations.bats @@ -96,3 +96,20 @@ teardown() { assert_success assert_output "$value" } + +@test "(scheduler-k3s:annotations:report) rejects invalid format and info-flag combinations" { + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format yaml" + assert_failure + assert_output_contains "Invalid format" + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo bar" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format json --scheduler-k3s-annotations.global.deployment.foo" + assert_failure + assert_output_contains "--format flag cannot be specified when specifying an info flag" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --scheduler-k3s-annotations.global.deployment.missing" + assert_failure + assert_output_contains "Invalid flag passed" +} diff --git a/tests/unit/scheduler-k3s-autoscaling-auth.bats b/tests/unit/scheduler-k3s-autoscaling-auth.bats index 92e542486..3b3428962 100644 --- a/tests/unit/scheduler-k3s-autoscaling-auth.bats +++ b/tests/unit/scheduler-k3s-autoscaling-auth.bats @@ -2,6 +2,22 @@ load test_helper +scheduler_k3s_seed_trigger_auth() { + local scope="$1" + local trigger="$2" + local key="$3" + local value="$4" + local dir="/var/lib/dokku/config/scheduler-k3s/$scope" + sudo mkdir -p "$dir" + echo -n "$value" | sudo tee "$dir/trigger-auth.${trigger}.${key}" >/dev/null +} + +scheduler_k3s_clear_trigger_auth() { + local scope="$1" + local trigger="$2" + sudo rm -f "/var/lib/dokku/config/scheduler-k3s/$scope/trigger-auth.${trigger}."* 2>/dev/null || true +} + setup() { global_setup dokku apps:create $TEST_APP >/dev/null 2>/dev/null || true @@ -10,7 +26,9 @@ setup() { teardown() { dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog >/dev/null 2>/dev/null || true + dokku scheduler-k3s:autoscaling-auth:set $TEST_APP memory >/dev/null 2>/dev/null || true dokku scheduler-k3s:autoscaling-auth:set ${TEST_APP}-2 datadog >/dev/null 2>/dev/null || true + scheduler_k3s_clear_trigger_auth "--global" "datadog" dokku --force apps:destroy $TEST_APP >/dev/null 2>/dev/null || true dokku --force apps:destroy ${TEST_APP}-2 >/dev/null 2>/dev/null || true global_teardown @@ -24,7 +42,110 @@ teardown() { run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report" assert_success - assert_output_contains "$TEST_APP scheduler-k3s information" - assert_output_contains "${TEST_APP}-2 scheduler-k3s information" + assert_output_contains "$TEST_APP autoscaling-auth information" + assert_output_contains "${TEST_APP}-2 autoscaling-auth information" assert_output_contains "Datadog:" 2 } + +@test "(scheduler-k3s:autoscaling-auth:report) lists metadata in json and clears after unset" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=secret-1 --metadata appKey=secret-2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json | jq -r '.\"datadog.apiKey\"'" + assert_success + assert_output "secret-1" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json | jq -r '.\"datadog.appKey\"'" + assert_success + assert_output "secret-2" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --scheduler-k3s-autoscaling-auth.datadog.apiKey" + assert_success + assert_output "*******" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP" + assert_success + assert_output_contains "Datadog:" + assert_output_not_contains "Datadog apiKey:" + assert_output_not_contains "secret-1" + assert_output_not_contains "secret-2" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --include-metadata" + assert_success + assert_output_contains "Datadog apiKey:" + assert_output_contains "secret-1" + assert_output_contains "secret-2" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:autoscaling-auth:report) stdout includes metadata values with --include-metadata" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=super-secret-value" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP" + assert_success + assert_output_contains "Datadog:" + assert_output_contains "configured" + assert_output_not_contains "Datadog apiKey:" + assert_output_not_contains "super-secret-value" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --include-metadata" + assert_success + assert_output_contains "Datadog apiKey:" + assert_output_contains "super-secret-value" +} + +@test "(scheduler-k3s:autoscaling-auth:report) reports multiple triggers in json" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=secret-1" + assert_success + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP memory --metadata some-key=some-value" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "datadog.apiKey,memory.some-key" +} + +@test "(scheduler-k3s:autoscaling-auth:report) supports --global scope" { + scheduler_k3s_seed_trigger_auth "--global" "datadog" "apiKey" "global-secret" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report --global --format json | jq -r '.\"datadog.apiKey\"'" + assert_success + assert_output "global-secret" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report --global --include-metadata" + assert_success + assert_output_contains "--global autoscaling-auth information" + assert_output_contains "Datadog:" + assert_output_contains "Datadog apiKey:" + assert_output_contains "global-secret" +} + +@test "(scheduler-k3s:autoscaling-auth:report) returns empty json for unconfigured app" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:autoscaling-auth:report) rejects invalid format and info-flag combinations" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format yaml" + assert_failure + assert_output_contains "Invalid format" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=secret-1" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json --scheduler-k3s-autoscaling-auth.datadog.apiKey" + assert_failure + assert_output_contains "--format flag cannot be specified when specifying an info flag" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --scheduler-k3s-autoscaling-auth.datadog.missing" + assert_failure + assert_output_contains "Invalid flag passed" +} diff --git a/tests/unit/scheduler-k3s-labels.bats b/tests/unit/scheduler-k3s-labels.bats index 3d417d21a..ae007b45c 100644 --- a/tests/unit/scheduler-k3s-labels.bats +++ b/tests/unit/scheduler-k3s-labels.bats @@ -96,3 +96,20 @@ teardown() { assert_success assert_output "$value" } + +@test "(scheduler-k3s:labels:report) rejects invalid format and info-flag combinations" { + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format yaml" + assert_failure + assert_output_contains "Invalid format" + + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo bar" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format json --scheduler-k3s-labels.global.deployment.foo" + assert_failure + assert_output_contains "--format flag cannot be specified when specifying an info flag" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --scheduler-k3s-labels.global.deployment.missing" + assert_failure + assert_output_contains "Invalid flag passed" +}