diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 770bad985a4e..acd0fa17f633 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -131,18 +131,18 @@ To push the complete multi-platform image, remove the --platform flag. }() if opts.quiet { - err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux())) + err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux(out))) if err == nil { _, _ = fmt.Fprintln(dockerCli.Out(), ref.String()) } return err } - return jsonstream.Display(ctx, responseBody, dockerCli.Out(), jsonstream.WithAuxCallback(handleAux())) + return jsonstream.Display(ctx, responseBody, dockerCli.Out(), jsonstream.WithAuxCallback(handleAux(out))) } var notes []string -func handleAux() func(jm jsonstream.JSONMessage) { +func handleAux(out tui.Output) func(jm jsonstream.JSONMessage) { return func(jm jsonstream.JSONMessage) { b := []byte(*jm.Aux) @@ -150,8 +150,8 @@ func handleAux() func(jm jsonstream.JSONMessage) { err := json.Unmarshal(b, &stripped) if err == nil && stripped.ManifestPushedInsteadOfIndex { note := fmt.Sprintf("Not all multiplatform-content is present and only the available single-platform image was pushed\n%s -> %s", - aec.RedF.Apply(stripped.OriginalIndex.Digest.String()), - aec.GreenF.Apply(stripped.SelectedManifest.Digest.String()), + out.Color(aec.RedF).Apply(stripped.OriginalIndex.Digest.String()), + out.Color(aec.GreenF).Apply(stripped.SelectedManifest.Digest.String()), ) notes = append(notes, note) } diff --git a/cli/command/image/push_test.go b/cli/command/image/push_test.go index dd38cdc84b15..ab70cdab41cb 100644 --- a/cli/command/image/push_test.go +++ b/cli/command/image/push_test.go @@ -1,9 +1,11 @@ package image import ( + "context" "errors" "io" "net/http" + "strings" "testing" "github.com/docker/cli/internal/test" @@ -86,3 +88,58 @@ func TestNewPushCommandSuccess(t *testing.T) { }) } } + +func TestRunPushManifestNoteHonorsNoColor(t *testing.T) { + oldNotes := notes + t.Cleanup(func() { + notes = oldNotes + }) + + const ( + originalDigest = "sha256:1111111111111111111111111111111111111111111111111111111111111111" + selectedDigest = "sha256:2222222222222222222222222222222222222222222222222222222222222222" + ) + response := `{"aux":{"manifestPushedInsteadOfIndex":true,"originalIndex":{"digest":"` + originalDigest + `"},"selectedManifest":{"digest":"` + selectedDigest + `"}}}` + "\n" + + testCases := []struct { + name string + noColor bool + hasANSI bool + }{ + { + name: "terminal", + hasANSI: true, + }, + { + name: "no-color", + noColor: true, + hasANSI: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + notes = nil + if tc.noColor { + t.Setenv("NO_COLOR", "1") + } else { + t.Setenv("NO_COLOR", "") + } + + cli := test.NewFakeCli(&fakeClient{ + imagePushFunc: func(ref string, options client.ImagePushOptions) (client.ImagePushResponse, error) { + return fakeStreamResult{ReadCloser: io.NopCloser(strings.NewReader(response))}, nil + }, + }) + cli.Out().SetIsTerminal(true) + + assert.NilError(t, runPush(context.Background(), cli, pushOptions{remote: "example.com/image:tag"})) + + out := cli.OutBuffer().String() + assert.Check(t, strings.Contains(out, "Not all multiplatform-content is present")) + assert.Check(t, strings.Contains(out, originalDigest)) + assert.Check(t, strings.Contains(out, selectedDigest)) + assert.Equal(t, tc.hasANSI, strings.Contains(out, "\x1b[")) + }) + } +}