diff --git a/cli/command/image/tree.go b/cli/command/image/tree.go index 35e901d04..d856f69f9 100644 --- a/cli/command/image/tree.go +++ b/cli/command/image/tree.go @@ -292,7 +292,7 @@ func printImageTree(outs command.Streams, view treeView) { Width: func() int { maxChipsWidth := 0 for _, chip := range possibleChips { - s := chip.String(isTerm) + s := out.Sprint(chip) l := tui.Width(s) maxChipsWidth += l } @@ -308,9 +308,9 @@ func printImageTree(outs command.Streams, view treeView) { var b strings.Builder for _, chip := range possibleChips { if chip.check(d) { - b.WriteString(chip.String(isTerm)) + b.WriteString(out.Sprint(chip)) } else { - b.WriteString(chipPlaceholder.String(isTerm)) + b.WriteString(out.Sprint(chipPlaceholder)) } } return b.String() diff --git a/cli/command/image/tree_test.go b/cli/command/image/tree_test.go index e050c8478..8e340cc84 100644 --- a/cli/command/image/tree_test.go +++ b/cli/command/image/tree_test.go @@ -15,6 +15,7 @@ func TestPrintImageTreeAnsiTty(t *testing.T) { stdoutTty bool stderrTty bool expectedAnsi bool + noColorEnv bool }{ { name: "non-terminal", @@ -80,6 +81,24 @@ func TestPrintImageTreeAnsiTty(t *testing.T) { expectedAnsi: false, }, + { + name: "no-color-env", + stdinTty: false, + stdoutTty: false, + stderrTty: false, + + noColorEnv: true, + expectedAnsi: false, + }, + { + name: "no-color-env-terminal", + stdinTty: true, + stdoutTty: true, + stderrTty: true, + + noColorEnv: true, + expectedAnsi: false, + }, } mockView := treeView{ @@ -115,6 +134,11 @@ func TestPrintImageTreeAnsiTty(t *testing.T) { cli.In().SetIsTerminal(tc.stdinTty) cli.Out().SetIsTerminal(tc.stdoutTty) cli.Err().SetIsTerminal(tc.stderrTty) + if tc.noColorEnv { + t.Setenv("NO_COLOR", "1") + } else { + t.Setenv("NO_COLOR", "") + } printImageTree(cli, mockView) @@ -123,9 +147,9 @@ func TestPrintImageTreeAnsiTty(t *testing.T) { hasAnsi := strings.Contains(out, "\x1b[") if tc.expectedAnsi { - assert.Check(t, hasAnsi, "Output should contain ANSI escape codes") + assert.Check(t, hasAnsi, "Output should contain ANSI escape codes, output: %s", out) } else { - assert.Check(t, !hasAnsi, "Output should not contain ANSI escape codes") + assert.Check(t, !hasAnsi, "Output should not contain ANSI escape codes, output: %s", out) } }) } diff --git a/docs/reference/commandline/docker.md b/docs/reference/commandline/docker.md index 6ee6c2b76..b68df2ad3 100644 --- a/docs/reference/commandline/docker.md +++ b/docs/reference/commandline/docker.md @@ -130,6 +130,8 @@ line: | `DOCKER_TLS` | Enable TLS for connections made by the `docker` CLI (equivalent of the `--tls` command-line option). Set to a non-empty value to enable TLS. Note that TLS is enabled automatically if any of the other TLS options are set. | | `DOCKER_TLS_VERIFY` | When set Docker uses TLS and verifies the remote. This variable is used both by the `docker` CLI and the [`dockerd` daemon](https://docs.docker.com/reference/cli/dockerd/) | | `BUILDKIT_PROGRESS` | Set type of progress output (`auto`, `plain`, `tty`, `rawjson`) when [building](https://docs.docker.com/reference/cli/docker/image/build/) with [BuildKit backend](https://docs.docker.com/build/buildkit/). Use plain to show container output (default `auto`). | +| `NO_COLOR` | Disable any ANSI escape codes in the output in accordance with https://no-color.org/ + | Because Docker is developed using Go, you can also use any environment variables used by the Go runtime. In particular, you may find these useful: diff --git a/internal/tui/note.go b/internal/tui/note.go index 0d375c7ae..759d8d0e2 100644 --- a/internal/tui/note.go +++ b/internal/tui/note.go @@ -28,7 +28,7 @@ func withHeader(header Str) noteOptions { } func (o Output) printNoteWithOptions(format string, args []any, opts ...noteOptions) { - if o.isTerminal { + if !o.noColor { // TODO: Handle all flags format = strings.ReplaceAll(format, "--platform", ColorFlag.Apply("--platform")) } @@ -51,7 +51,7 @@ func (o Output) printNoteWithOptions(format string, args []any, opts ...noteOpti } l := line - if o.isTerminal { + if !o.noColor { l = aec.Italic.Apply(l) } _, _ = fmt.Fprintln(o, l) diff --git a/internal/tui/output.go b/internal/tui/output.go index 2d29bda42..34bc13a3a 100644 --- a/internal/tui/output.go +++ b/internal/tui/output.go @@ -5,6 +5,7 @@ package tui import ( "fmt" + "os" "github.com/docker/cli/cli/streams" "github.com/morikuni/aec" @@ -12,7 +13,7 @@ import ( type Output struct { *streams.Out - isTerminal bool + noColor bool } type terminalPrintable interface { @@ -20,24 +21,28 @@ type terminalPrintable interface { } func NewOutput(out *streams.Out) Output { + noColor := !out.IsTerminal() + if os.Getenv("NO_COLOR") != "" { + noColor = true + } return Output{ - Out: out, - isTerminal: out.IsTerminal(), + Out: out, + noColor: noColor, } } func (o Output) Color(clr aec.ANSI) aec.ANSI { - if o.isTerminal { - return clr + if o.noColor { + return ColorNone } - return ColorNone + return clr } func (o Output) Sprint(all ...any) string { var out []any for _, p := range all { if s, ok := p.(terminalPrintable); ok { - out = append(out, s.String(o.isTerminal)) + out = append(out, s.String(!o.noColor)) } else { out = append(out, p) } @@ -47,7 +52,7 @@ func (o Output) Sprint(all ...any) string { func (o Output) PrintlnWithColor(clr aec.ANSI, args ...any) { msg := o.Sprint(args...) - if o.isTerminal { + if !o.noColor { msg = clr.Apply(msg) } _, _ = fmt.Fprintln(o.Out, msg)