mirror of https://github.com/docker/cli.git
cli: Wrap Err stream
This wraps the cli stderr stream the same way as stdin and stdout, which extends the stream with TTY-related methods. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
parent
52eddcf4e4
commit
6b93cf221a
|
@ -44,7 +44,7 @@ const defaultInitTimeout = 2 * time.Second
|
||||||
type Streams interface {
|
type Streams interface {
|
||||||
In() *streams.In
|
In() *streams.In
|
||||||
Out() *streams.Out
|
Out() *streams.Out
|
||||||
Err() io.Writer
|
Err() *streams.Out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cli represents the docker command line client.
|
// Cli represents the docker command line client.
|
||||||
|
@ -75,7 +75,7 @@ type DockerCli struct {
|
||||||
options *cliflags.ClientOptions
|
options *cliflags.ClientOptions
|
||||||
in *streams.In
|
in *streams.In
|
||||||
out *streams.Out
|
out *streams.Out
|
||||||
err io.Writer
|
err *streams.Out
|
||||||
client client.APIClient
|
client client.APIClient
|
||||||
serverInfo ServerInfo
|
serverInfo ServerInfo
|
||||||
contentTrust bool
|
contentTrust bool
|
||||||
|
@ -124,7 +124,7 @@ func (cli *DockerCli) Out() *streams.Out {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err returns the writer used for stderr
|
// Err returns the writer used for stderr
|
||||||
func (cli *DockerCli) Err() io.Writer {
|
func (cli *DockerCli) Err() *streams.Out {
|
||||||
return cli.err
|
return cli.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ func WithStandardStreams() CLIOption {
|
||||||
stdin, stdout, stderr := term.StdStreams()
|
stdin, stdout, stderr := term.StdStreams()
|
||||||
cli.in = streams.NewIn(stdin)
|
cli.in = streams.NewIn(stdin)
|
||||||
cli.out = streams.NewOut(stdout)
|
cli.out = streams.NewOut(stdout)
|
||||||
cli.err = stderr
|
cli.err = streams.NewOut(stderr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,9 @@ func WithBaseContext(ctx context.Context) CLIOption {
|
||||||
// WithCombinedStreams uses the same stream for the output and error streams.
|
// WithCombinedStreams uses the same stream for the output and error streams.
|
||||||
func WithCombinedStreams(combined io.Writer) CLIOption {
|
func WithCombinedStreams(combined io.Writer) CLIOption {
|
||||||
return func(cli *DockerCli) error {
|
return func(cli *DockerCli) error {
|
||||||
cli.out = streams.NewOut(combined)
|
s := streams.NewOut(combined)
|
||||||
cli.err = combined
|
cli.out = s
|
||||||
|
cli.err = s
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +66,7 @@ func WithOutputStream(out io.Writer) CLIOption {
|
||||||
// WithErrorStream sets a cli error stream.
|
// WithErrorStream sets a cli error stream.
|
||||||
func WithErrorStream(err io.Writer) CLIOption {
|
func WithErrorStream(err io.Writer) CLIOption {
|
||||||
return func(cli *DockerCli) error {
|
return func(cli *DockerCli) error {
|
||||||
cli.err = err
|
cli.err = streams.NewOut(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
"github.com/docker/cli/cli/flags"
|
"github.com/docker/cli/cli/flags"
|
||||||
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
@ -253,7 +254,7 @@ func TestExperimentalCLI(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := &DockerCli{client: apiclient, err: os.Stderr}
|
cli := &DockerCli{client: apiclient, err: streams.NewOut(os.Stderr)}
|
||||||
config.SetDir(dir.Path())
|
config.SetDir(dir.Path())
|
||||||
err := cli.Initialize(flags.NewClientOptions())
|
err := cli.Initialize(flags.NewClientOptions())
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
|
@ -130,9 +130,9 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
|
||||||
|
|
||||||
out := dockerCli.Err()
|
out := dockerCli.Err()
|
||||||
if options.quiet {
|
if options.quiet {
|
||||||
out = io.Discard
|
out = streams.NewOut(io.Discard)
|
||||||
}
|
}
|
||||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(out), nil)
|
return jsonmessage.DisplayJSONMessagesToStream(responseBody, out, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
type cidFile struct {
|
type cidFile struct {
|
||||||
|
|
|
@ -18,7 +18,6 @@ import (
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/moby/term"
|
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -176,7 +175,7 @@ func handleAux(dockerCli command.Cli) func(jm jsonmessage.JSONMessage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func printNote(dockerCli command.Cli, format string, args ...any) {
|
func printNote(dockerCli command.Cli, format string, args ...any) {
|
||||||
if _, isTTY := term.GetFdInfo(dockerCli.Err()); isTTY {
|
if dockerCli.Err().IsTerminal() {
|
||||||
_, _ = fmt.Fprint(dockerCli.Err(), aec.WhiteF.Apply(aec.CyanB.Apply("[ NOTE ]"))+" ")
|
_, _ = fmt.Fprint(dockerCli.Err(), aec.WhiteF.Apply(aec.CyanB.Apply("[ NOTE ]"))+" ")
|
||||||
} else {
|
} else {
|
||||||
_, _ = fmt.Fprint(dockerCli.Err(), "[ NOTE ] ")
|
_, _ = fmt.Fprint(dockerCli.Err(), "[ NOTE ] ")
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
configtypes "github.com/docker/cli/cli/config/types"
|
configtypes "github.com/docker/cli/cli/config/types"
|
||||||
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/api/types/system"
|
"github.com/docker/docker/api/types/system"
|
||||||
|
@ -69,7 +70,7 @@ func TestLoginWithCredStoreCreds(t *testing.T) {
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
cli := test.NewFakeCli(&fakeClient{})
|
cli := test.NewFakeCli(&fakeClient{})
|
||||||
errBuf := new(bytes.Buffer)
|
errBuf := new(bytes.Buffer)
|
||||||
cli.SetErr(errBuf)
|
cli.SetErr(streams.NewOut(errBuf))
|
||||||
loginWithCredStoreCreds(ctx, cli, &tc.inputAuthConfig)
|
loginWithCredStoreCreds(ctx, cli, &tc.inputAuthConfig)
|
||||||
outputString := cli.OutBuffer().String()
|
outputString := cli.OutBuffer().String()
|
||||||
assert.Check(t, is.Equal(tc.expectedMsg, outputString))
|
assert.Check(t, is.Equal(tc.expectedMsg, outputString))
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/version"
|
"github.com/docker/cli/cli/version"
|
||||||
"github.com/moby/term"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
@ -101,12 +100,10 @@ func startCobraCommandTimer(mp metric.MeterProvider, attrs []attribute.KeyValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stdioAttributes(streams Streams) []attribute.KeyValue {
|
func stdioAttributes(streams Streams) []attribute.KeyValue {
|
||||||
// we don't wrap stderr, but we do wrap in/out
|
|
||||||
_, stderrTty := term.GetFdInfo(streams.Err())
|
|
||||||
return []attribute.KeyValue{
|
return []attribute.KeyValue{
|
||||||
attribute.Bool("command.stdin.isatty", streams.In().IsTerminal()),
|
attribute.Bool("command.stdin.isatty", streams.In().IsTerminal()),
|
||||||
attribute.Bool("command.stdout.isatty", streams.Out().IsTerminal()),
|
attribute.Bool("command.stdout.isatty", streams.Out().IsTerminal()),
|
||||||
attribute.Bool("command.stderr.isatty", stderrTty),
|
attribute.Bool("command.stderr.isatty", streams.Err().IsTerminal()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ func TestStdioAttributes(t *testing.T) {
|
||||||
cli := &DockerCli{
|
cli := &DockerCli{
|
||||||
in: streams.NewIn(io.NopCloser(strings.NewReader(""))),
|
in: streams.NewIn(io.NopCloser(strings.NewReader(""))),
|
||||||
out: streams.NewOut(outBuffer),
|
out: streams.NewOut(outBuffer),
|
||||||
err: errBuffer,
|
err: streams.NewOut(errBuffer),
|
||||||
}
|
}
|
||||||
cli.In().SetIsTerminal(tc.stdinTty)
|
cli.In().SetIsTerminal(tc.stdinTty)
|
||||||
cli.Out().SetIsTerminal(tc.stdoutTty)
|
cli.Out().SetIsTerminal(tc.stdoutTty)
|
||||||
|
|
|
@ -28,7 +28,8 @@ type FakeCli struct {
|
||||||
configfile *configfile.ConfigFile
|
configfile *configfile.ConfigFile
|
||||||
out *streams.Out
|
out *streams.Out
|
||||||
outBuffer *bytes.Buffer
|
outBuffer *bytes.Buffer
|
||||||
err *bytes.Buffer
|
err *streams.Out
|
||||||
|
errBuffer *bytes.Buffer
|
||||||
in *streams.In
|
in *streams.In
|
||||||
server command.ServerInfo
|
server command.ServerInfo
|
||||||
notaryClientFunc NotaryClientFuncType
|
notaryClientFunc NotaryClientFuncType
|
||||||
|
@ -48,7 +49,8 @@ func NewFakeCli(apiClient client.APIClient, opts ...func(*FakeCli)) *FakeCli {
|
||||||
client: apiClient,
|
client: apiClient,
|
||||||
out: streams.NewOut(outBuffer),
|
out: streams.NewOut(outBuffer),
|
||||||
outBuffer: outBuffer,
|
outBuffer: outBuffer,
|
||||||
err: errBuffer,
|
err: streams.NewOut(errBuffer),
|
||||||
|
errBuffer: errBuffer,
|
||||||
in: streams.NewIn(io.NopCloser(strings.NewReader(""))),
|
in: streams.NewIn(io.NopCloser(strings.NewReader(""))),
|
||||||
// Use an empty string for filename so that tests don't create configfiles
|
// Use an empty string for filename so that tests don't create configfiles
|
||||||
// Set cli.ConfigFile().Filename to a tempfile to support Save.
|
// Set cli.ConfigFile().Filename to a tempfile to support Save.
|
||||||
|
@ -67,7 +69,7 @@ func (c *FakeCli) SetIn(in *streams.In) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetErr sets the stderr stream for the cli to the specified io.Writer
|
// SetErr sets the stderr stream for the cli to the specified io.Writer
|
||||||
func (c *FakeCli) SetErr(err *bytes.Buffer) {
|
func (c *FakeCli) SetErr(err *streams.Out) {
|
||||||
c.err = err
|
c.err = err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ func (c *FakeCli) Out() *streams.Out {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err returns the output stream (stderr) the cli should write on
|
// Err returns the output stream (stderr) the cli should write on
|
||||||
func (c *FakeCli) Err() io.Writer {
|
func (c *FakeCli) Err() *streams.Out {
|
||||||
return c.err
|
return c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,13 +155,13 @@ func (c *FakeCli) OutBuffer() *bytes.Buffer {
|
||||||
|
|
||||||
// ErrBuffer Buffer returns the stderr buffer
|
// ErrBuffer Buffer returns the stderr buffer
|
||||||
func (c *FakeCli) ErrBuffer() *bytes.Buffer {
|
func (c *FakeCli) ErrBuffer() *bytes.Buffer {
|
||||||
return c.err
|
return c.errBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetOutputBuffers resets the .OutBuffer() and.ErrBuffer() back to empty
|
// ResetOutputBuffers resets the .OutBuffer() and.ErrBuffer() back to empty
|
||||||
func (c *FakeCli) ResetOutputBuffers() {
|
func (c *FakeCli) ResetOutputBuffers() {
|
||||||
c.outBuffer.Reset()
|
c.outBuffer.Reset()
|
||||||
c.err.Reset()
|
c.errBuffer.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNotaryClient sets the internal getter for retrieving a NotaryClient
|
// SetNotaryClient sets the internal getter for retrieving a NotaryClient
|
||||||
|
|
Loading…
Reference in New Issue