diff --git a/cmd/aws.go b/cmd/aws.go index 4dd7d247..5572c247 100644 --- a/cmd/aws.go +++ b/cmd/aws.go @@ -37,6 +37,8 @@ Examples: DisableFlagParsing: true, PreRunE: initConfig(nil), RunE: func(cmd *cobra.Command, args []string) error { + args, nonInteractive := stripNonInteractiveFlag(args) + rt, err := runtime.NewDockerRuntime(cfg.DockerHost) if err != nil { return err @@ -85,7 +87,7 @@ Examples: } stdout, stderr := io.Writer(os.Stdout), io.Writer(os.Stderr) - if terminal.IsTerminal(os.Stderr) { + if !nonInteractive && terminal.IsTerminal(os.Stderr) { s := terminal.NewSpinner(os.Stderr, "Loading service...", 4*time.Second) s.Start() defer s.Stop() @@ -97,3 +99,20 @@ Examples: }, } } + +// stripNonInteractiveFlag pulls lstk's --non-interactive flag out of the AWS CLI +// passthrough args and reports whether it was set. The aws command uses +// DisableFlagParsing, so Cobra never parses the flag here — left in place it would +// be forwarded to the aws binary and rejected as an unknown option. +func stripNonInteractiveFlag(args []string) ([]string, bool) { + out := make([]string, 0, len(args)) + nonInteractive := false + for _, a := range args { + if a == "--non-interactive" { + nonInteractive = true + continue + } + out = append(out, a) + } + return out, nonInteractive +} diff --git a/cmd/aws_test.go b/cmd/aws_test.go new file mode 100644 index 00000000..b6fee834 --- /dev/null +++ b/cmd/aws_test.go @@ -0,0 +1,52 @@ +package cmd + +import ( + "reflect" + "testing" +) + +func TestStripNonInteractiveFlag(t *testing.T) { + tests := []struct { + name string + args []string + wantArgs []string + wantNonInteract bool + }{ + { + name: "absent", + args: []string{"s3", "ls"}, + wantArgs: []string{"s3", "ls"}, + wantNonInteract: false, + }, + { + name: "bare flag is stripped and enables non-interactive", + args: []string{"--non-interactive", "s3", "ls"}, + wantArgs: []string{"s3", "ls"}, + wantNonInteract: true, + }, + { + name: "flag among aws args is stripped", + args: []string{"s3", "ls", "--non-interactive", "--recursive"}, + wantArgs: []string{"s3", "ls", "--recursive"}, + wantNonInteract: true, + }, + { + name: "does not strip a similarly named aws flag", + args: []string{"s3", "ls", "--non-interactive-mode"}, + wantArgs: []string{"s3", "ls", "--non-interactive-mode"}, + wantNonInteract: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotArgs, gotNonInteract := stripNonInteractiveFlag(tt.args) + if gotNonInteract != tt.wantNonInteract { + t.Errorf("nonInteractive = %v, want %v", gotNonInteract, tt.wantNonInteract) + } + if !reflect.DeepEqual(gotArgs, tt.wantArgs) { + t.Errorf("args = %v, want %v", gotArgs, tt.wantArgs) + } + }) + } +} diff --git a/test/integration/aws_cmd_test.go b/test/integration/aws_cmd_test.go index fca8df64..1e0dca99 100644 --- a/test/integration/aws_cmd_test.go +++ b/test/integration/aws_cmd_test.go @@ -342,6 +342,28 @@ func TestAWSCommandShowsSpinnerForSlowOperation(t *testing.T) { assert.Contains(t, out, "ARGS:--profile localstack s3 ls") } +func TestAWSCommandSuppressesSpinnerInNonInteractiveMode(t *testing.T) { + requireDocker(t) + cleanup() + t.Cleanup(cleanup) + ctx := testContext(t) + // A running emulator is required: without it, `lstk aws` exits before reaching the spinner. + startTestContainer(t, ctx) + + // A slow operation would normally render the spinner in a PTY; --non-interactive + // must suppress it so captured streams carry no ANSI control codes. + fakeDir := writeSlowFakeAWS(t, 5) + homeDir := t.TempDir() + writeAWSProfile(t, homeDir) + e := env.With(env.DisableEvents, "1").With("PATH", fakeDir+":/bin:/usr/bin").With(env.Home, homeDir) + + out, err := runLstkInPTY(t, ctx, e, "--non-interactive", "aws", "s3", "ls") + require.NoError(t, err, "lstk aws failed: %s", out) + + assert.NotContains(t, out, "Loading service") + assert.Contains(t, out, "ARGS:--profile localstack s3 ls") +} + func TestAWSCommandSuppressesSpinnerForFastOperation(t *testing.T) { requireDocker(t) cleanup()