From d5218fd8ab4db802e2f688590efcbff0f57cd93c Mon Sep 17 00:00:00 2001 From: Said Boudjelda Date: Sun, 18 Oct 2026 16:04:47 +0200 Subject: [PATCH] fix(cli): scaffold export and ctx set stop hiding the global -o/--output flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two commands defined their own --output, which hid the global output format flag: - `kates test scaffold export quick-load -o json` wrote the template to a file named json. - `kates ctx set foo --url … -o json` failed with "unknown shorthand flag", since ctx set's --output had no -o. scaffold export names the file with --file/-f now, and ctx set takes the context's default format with --default-output. -o and --output are the global flag on both. ctx set notes on stderr when it gets --output without --default-output, since such a script used to set the context's format and now leaves it at table. The CLI help, `kates docs` entries, the book and the tutorials use the new names. BREAKING: `kates test scaffold export --output/-o` is now --file/-f, and `kates ctx set --output` is now --default-output. --- cli/cmd/config.go | 11 +++- cli/cmd/data/doc_entries.yaml | 4 +- cli/cmd/output_flag_test.go | 86 +++++++++++++++++++++++++++ cli/cmd/scaffold.go | 4 +- docs/book/10-cli-reference.md | 2 +- docs/book/13-scenario-files.md | 2 +- docs/tutorials/02-all-test-types.md | 10 ++-- docs/tutorials/06-cicd-integration.md | 2 +- 8 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 cli/cmd/output_flag_test.go diff --git a/cli/cmd/config.go b/cli/cmd/config.go index fd260a28..d85f56b5 100644 --- a/cli/cmd/config.go +++ b/cli/cmd/config.go @@ -86,7 +86,7 @@ var ctxSetCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Example: ` kates ctx set local --url http://localhost:30083 kates ctx set staging --url https://kates-staging.company.com - kates ctx set prod --url https://kates.company.com --output json --api-key mykey`, + kates ctx set prod --url https://kates.company.com --default-output json --api-key mykey`, // RunE, not Run: this validates its input, and a command that rejects what // you gave it must exit non-zero. With Run the errors below printed and then // returned 0, so a script could not tell a stored context from a refused one. @@ -110,6 +110,11 @@ var ctxSetCmd = &cobra.Command{ return cmdErr("--api-key was given but is empty") } + // --output used to be this flag. A script still passing it now sets + // this command's output format, and the context keeps table. + if cmd.Flags().Changed("output") && !cmd.Flags().Changed("default-output") { + fmt.Fprintln(output.Err, " ! --output is this command's output format. The context's default is --default-output.") + } out := ctxSetOutput if out == "" { out = "table" @@ -479,7 +484,9 @@ func importContext(name string, in, old Context, had bool) (Context, []string) { func init() { ctxSetCmd.Flags().StringVar(&ctxSetURL, "url", "", "Kates API base URL (required)") - ctxSetCmd.Flags().StringVar(&ctxSetOutput, "output", "", "Default output format for this context") + // Not --output: that is the global output format, and this flag hid it, + // so `ctx set … -o json` failed with "unknown shorthand flag". + ctxSetCmd.Flags().StringVar(&ctxSetOutput, "default-output", "", "Default output format for this context") ctxSetCmd.Flags().StringVar(&ctxSetAPIKey, "api-key", "", "API key for authentication") ctxSetCmd.Flags().StringVar(&ctxSetProxy, "proxy", "", "Proxy server URL (e.g., http://proxy:8080)") ctxSetCmd.Flags().BoolVar(&ctxSetInsecure, "insecure", false, "Skip TLS certificate verification (useful for SSL proxies)") diff --git a/cli/cmd/data/doc_entries.yaml b/cli/cmd/data/doc_entries.yaml index 9f7c1377..2343c66c 100644 --- a/cli/cmd/data/doc_entries.yaml +++ b/cli/cmd/data/doc_entries.yaml @@ -1309,12 +1309,12 @@ - name: "--url" type: string desc: "Kates API base URL (required)" - - name: "--output" + - name: "--default-output" type: string desc: "Default output format for this context" examples: - "kates ctx set local --url http://localhost:30083" - - "kates ctx set staging --url https://kates.staging.internal --output json" + - "kates ctx set staging --url https://kates.staging.internal --default-output json" see_also: - ctx use - ctx show diff --git a/cli/cmd/output_flag_test.go b/cli/cmd/output_flag_test.go new file mode 100644 index 00000000..b27c7ccf --- /dev/null +++ b/cli/cmd/output_flag_test.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/spf13/cobra" +) + +// resetFlags puts cmd's named flags back to unset when the test ends, and +// whenever the test calls the function it returns, as a new process would +// have them: runCommandLine parses into the package's variables and marks the +// flags changed. +func resetFlags(t *testing.T, cmd *cobra.Command, names ...string) func() { + t.Helper() + reset := func() { + for _, name := range names { + f := cmd.Flags().Lookup(name) + _ = f.Value.Set(f.DefValue) + f.Changed = false + } + } + t.Cleanup(reset) + return reset +} + +// scaffold export's own -o/--output hid the global flag, so +// `kates test scaffold export quick-load -o json` wrote the template to a +// file named json. +func TestScaffoldExport_OutputIsTheGlobalFlag(t *testing.T) { + resetFlags(t, scaffoldExportCmd, "dir", "file", "output") + dir := t.TempDir() + + if err := runCommandLine(t, "test", "scaffold", "export", "quick-load", "--dir", dir, "-o", "json"); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(filepath.Join(dir, "json")); err == nil { + t.Error("-o json wrote the template to a file named json") + } + if _, err := os.Stat(filepath.Join(dir, "quick-load.yaml")); err != nil { + t.Errorf("-o json: the template is not under its own name: %v", err) + } + if outputMode != "json" { + t.Errorf("outputMode = %q, want json from the global -o", outputMode) + } + + if err := runCommandLine(t, "test", "scaffold", "export", "quick-load", "--dir", dir, "-f", "mine.yaml"); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(filepath.Join(dir, "mine.yaml")); err != nil { + t.Errorf("-f mine.yaml: %v", err) + } +} + +// ctx set's own --output hid the global flag, so `ctx set … -o json` failed +// with "unknown shorthand flag". The context's default is --default-output. +func TestCtxSet_DefaultOutputLeavesOutputGlobal(t *testing.T) { + reset := resetFlags(t, ctxSetCmd, "url", "default-output", "output") + t.Setenv("HOME", t.TempDir()) + + _, stderr, err := commandOutput(t, func() error { + return runCommandLine(t, "ctx", "set", "lab", "--url", "http://lab:8080", "--default-output", "json", "-o", "json") + }) + if err != nil { + t.Fatalf("ctx set with -o: %v", err) + } + if got := loadConfig().Contexts["lab"].Output; got != "json" { + t.Errorf("the context's output = %q, want json from --default-output", got) + } + if strings.Contains(stderr, "--default-output") { + t.Errorf("warned with --default-output given:\n%s", stderr) + } + + reset() + _, stderr, err = commandOutput(t, func() error { + return runCommandLine(t, "ctx", "set", "staging", "--url", "http://staging:8080", "--output", "json") + }) + if err != nil { + t.Fatal(err) + } + if got := loadConfig().Contexts["staging"].Output; got != "table" || !strings.Contains(stderr, "--default-output") { + t.Errorf("--output alone: the context's output = %q and stderr %q, want table and a note naming --default-output", got, stderr) + } +} diff --git a/cli/cmd/scaffold.go b/cli/cmd/scaffold.go index 22dcc2ff..8ad12dd4 100644 --- a/cli/cmd/scaffold.go +++ b/cli/cmd/scaffold.go @@ -226,7 +226,9 @@ func findScenario(name string) *scenarioMeta { func init() { testScaffoldCmd.PersistentFlags().StringVarP(&scaffoldTypeFilter, "type", "t", "", "Filter by test type (LOAD, STRESS, INTEGRITY, etc.)") scaffoldExportCmd.Flags().StringVarP(&scaffoldExportDir, "dir", "d", "", "Output directory (default: current)") - scaffoldExportCmd.Flags().StringVarP(&scaffoldExportFile, "output", "o", "", "Output filename (overrides default)") + // Not -o/--output: that is the global output format, and this flag hid it, + // so `-o json` wrote the template to a file named json. + scaffoldExportCmd.Flags().StringVarP(&scaffoldExportFile, "file", "f", "", "Output filename (overrides default)") scaffoldExportCmd.Flags().BoolVar(&scaffoldExportAll, "all", false, "Export all templates") testScaffoldCmd.AddCommand(scaffoldListCmd) diff --git a/docs/book/10-cli-reference.md b/docs/book/10-cli-reference.md index a705109e..ee6c92de 100644 --- a/docs/book/10-cli-reference.md +++ b/docs/book/10-cli-reference.md @@ -792,7 +792,7 @@ kates test scaffold # list all templates kates test scaffold --type LOAD # filter by test type kates test scaffold show production-load # preview a template kates test scaffold export ci-gate # write ci-gate.yaml to current dir -kates test scaffold export ci-gate -o my-gate.yaml +kates test scaffold export ci-gate -f my-gate.yaml kates test scaffold export --all # export every template ``` diff --git a/docs/book/13-scenario-files.md b/docs/book/13-scenario-files.md index af91b6c5..170264bc 100644 --- a/docs/book/13-scenario-files.md +++ b/docs/book/13-scenario-files.md @@ -413,7 +413,7 @@ kates test scaffold show quick-load kates test scaffold export quick-load # Export with a custom filename or directory, or export everything -kates test scaffold export production-load -o load-scenario.yaml +kates test scaffold export production-load -f load-scenario.yaml kates test scaffold export --all --dir ./scenarios/ ``` diff --git a/docs/tutorials/02-all-test-types.md b/docs/tutorials/02-all-test-types.md index 63b582fa..7a8d85b7 100644 --- a/docs/tutorials/02-all-test-types.md +++ b/docs/tutorials/02-all-test-types.md @@ -42,7 +42,7 @@ The STRESS test ramps load until the cluster can no longer keep up. ```bash # Export the built-in STRESS template -kates test scaffold export stress-test -o stress.yaml +kates test scaffold export stress-test -f stress.yaml # Quick stress test with 4 producers ramping up kates test create --type STRESS \ @@ -72,7 +72,7 @@ The SPIKE test hits the cluster with a sudden burst of traffic, then drops back ```bash # Export the built-in SPIKE template -kates test scaffold export spike-test -o spike.yaml +kates test scaffold export spike-test -f spike.yaml # View the exported YAML cat spike.yaml @@ -97,7 +97,7 @@ The ENDURANCE test runs at moderate load for an extended period to detect slow r ```bash # Export the built-in ENDURANCE template (a 1-hour soak at 5k msg/s) -kates test scaffold export endurance-soak -o endurance.yaml +kates test scaffold export endurance-soak -f endurance.yaml # Start a 30-minute endurance test kates test create --type ENDURANCE \ @@ -176,7 +176,7 @@ The ROUND_TRIP test measures the complete journey from producer to consumer. ```bash # Export the built-in ROUND_TRIP template (exactly-once, CRC-verified) -kates test scaffold export exactly-once -o roundtrip.yaml +kates test scaffold export exactly-once -f roundtrip.yaml # Run with 1 producer and 1 consumer kates test create --type ROUND_TRIP \ @@ -196,7 +196,7 @@ The INTEGRITY test verifies that every message is persisted and deliverable. ```bash # Export the built-in INTEGRITY template (transactional, zstd, CRC) -kates test scaffold export integrity-tx -o integrity.yaml +kates test scaffold export integrity-tx -f integrity.yaml # Run data integrity verification kates test create --type INTEGRITY \ diff --git a/docs/tutorials/06-cicd-integration.md b/docs/tutorials/06-cicd-integration.md index 22aae5ec..aa04d731 100644 --- a/docs/tutorials/06-cicd-integration.md +++ b/docs/tutorials/06-cicd-integration.md @@ -151,7 +151,7 @@ set -e echo "Running integrity gate..." # Export the built-in INTEGRITY template and run it -kates test scaffold export integrity-tx -o /tmp/integrity.yaml +kates test scaffold export integrity-tx -f /tmp/integrity.yaml ID=$(kates test apply -f /tmp/integrity.yaml --wait -o json | jq -r '.scenarios[0].runId') # Check verdict