diff --git a/cmd/compose/run.go b/cmd/compose/run.go index a5880598c..14fefdb5b 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -183,7 +183,11 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * } else { options.noTty = !ttyFlag } + } else if !cmd.Flags().Changed("no-TTY") && !cmd.Flags().Changed("interactive") && !dockerCli.In().IsTerminal() { + // Check if the command was piped or not, if so, force noTty to tru + options.noTty = true } + if options.quiet { progress.Mode = progress.ModeQuiet devnull, err := os.Open(os.DevNull) diff --git a/pkg/e2e/compose_run_test.go b/pkg/e2e/compose_run_test.go index 0878c9731..36a1fd49c 100644 --- a/pkg/e2e/compose_run_test.go +++ b/pkg/e2e/compose_run_test.go @@ -222,4 +222,60 @@ func TestLocalComposeRun(t *testing.T) { res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "build", "echo", "hello world") res.Assert(t, icmd.Expected{Out: "hello world"}) }) + + t.Run("compose run with piped input detection", func(t *testing.T) { + if composeStandaloneMode { + t.Skip("Skipping test compose with piped input detection in standalone mode") + } + // Test that piped input is properly detected and TTY is automatically disabled + // This tests the logic added in run.go that checks dockerCli.In().IsTerminal() + cmd := c.NewCmd("sh", "-c", "echo 'piped-content' | docker compose -f ./fixtures/run-test/piped-test.yaml run --rm piped-test") + res := icmd.RunCmd(cmd) + + res.Assert(t, icmd.Expected{Out: "piped-content"}) + res.Assert(t, icmd.Success) + }) + + t.Run("compose run piped input should not allocate TTY", func(t *testing.T) { + if composeStandaloneMode { + t.Skip("Skipping test compose with piped input detection in standalone mode") + } + // Test that when stdin is piped, the container correctly detects no TTY + // This verifies that the automatic noTty=true setting works correctly + cmd := c.NewCmd("sh", "-c", "echo '' | docker compose -f ./fixtures/run-test/piped-test.yaml run --rm tty-test") + res := icmd.RunCmd(cmd) + + res.Assert(t, icmd.Expected{Out: "No TTY detected"}) + res.Assert(t, icmd.Success) + }) + + t.Run("compose run piped input with explicit --tty should fail", func(t *testing.T) { + if composeStandaloneMode { + t.Skip("Skipping test compose with piped input detection in standalone mode") + } + // Test that explicitly requesting TTY with piped input fails with proper error message + // This should trigger the "input device is not a TTY" error + cmd := c.NewCmd("sh", "-c", "echo 'test' | docker compose -f ./fixtures/run-test/piped-test.yaml run --rm --tty piped-test") + res := icmd.RunCmd(cmd) + + res.Assert(t, icmd.Expected{ + ExitCode: 1, + Err: "the input device is not a TTY", + }) + }) + + t.Run("compose run piped input with --no-TTY=false should fail", func(t *testing.T) { + if composeStandaloneMode { + t.Skip("Skipping test compose with piped input detection in standalone mode") + } + // Test that explicitly disabling --no-TTY (i.e., requesting TTY) with piped input fails + // This should also trigger the "input device is not a TTY" error + cmd := c.NewCmd("sh", "-c", "echo 'test' | docker compose -f ./fixtures/run-test/piped-test.yaml run --rm --no-TTY=false piped-test") + res := icmd.RunCmd(cmd) + + res.Assert(t, icmd.Expected{ + ExitCode: 1, + Err: "the input device is not a TTY", + }) + }) } diff --git a/pkg/e2e/fixtures/run-test/piped-test.yaml b/pkg/e2e/fixtures/run-test/piped-test.yaml new file mode 100644 index 000000000..247bd923a --- /dev/null +++ b/pkg/e2e/fixtures/run-test/piped-test.yaml @@ -0,0 +1,9 @@ +services: + piped-test: + image: alpine + command: cat + # Service that will receive piped input and echo it back + tty-test: + image: alpine + command: sh -c "if [ -t 0 ]; then echo 'TTY detected'; else echo 'No TTY detected'; fi" + # Service to test TTY detection \ No newline at end of file