From bb1ed52ac88cc4e0133d51e83463d8aef9ff2df0 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 29 Aug 2016 11:52:05 -0400 Subject: [PATCH] Extract input stream into a new type. Signed-off-by: Daniel Nephin Upstream-commit: bec81075bf1ae07abcbf3f984922dedb10458cb2 Component: engine --- components/engine/api/client/cli.go | 79 +++---------------- .../engine/api/client/container/attach.go | 2 +- .../engine/api/client/container/exec.go | 4 +- components/engine/api/client/container/run.go | 2 +- components/engine/api/client/in.go | 73 +++++++++++++++++ components/engine/api/client/registry.go | 10 +-- 6 files changed, 93 insertions(+), 77 deletions(-) create mode 100644 components/engine/api/client/in.go diff --git a/components/engine/api/client/cli.go b/components/engine/api/client/cli.go index cfaba28c9a..357504384f 100644 --- a/components/engine/api/client/cli.go +++ b/components/engine/api/client/cli.go @@ -17,7 +17,6 @@ import ( "github.com/docker/docker/client" "github.com/docker/docker/dockerversion" dopts "github.com/docker/docker/opts" - "github.com/docker/docker/pkg/term" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" ) @@ -25,30 +24,12 @@ import ( // DockerCli represents the docker command line client. // Instances of the client can be returned from NewDockerCli. type DockerCli struct { - // initializing closure - init func() error - - // configFile has the client configuration file configFile *configfile.ConfigFile - // in holds the input stream and closer (io.ReadCloser) for the client. - // TODO: remove - in io.ReadCloser - // err holds the error stream (io.Writer) for the client. - err io.Writer - // keyFile holds the key file as a string. - keyFile string - // inFd holds the file descriptor of the client's STDIN (if valid). - // TODO: remove - inFd uintptr - // isTerminalIn indicates whether the client's STDIN is a TTY - // TODO: remove - isTerminalIn bool - // client is the http client that performs all API operations - client client.APIClient - // inState holds the terminal input state - // TODO: remove - inState *term.State - out *OutStream + in *InStream + out *OutStream + err io.Writer + keyFile string + client client.APIClient } // Client returns the APIClient @@ -67,7 +48,7 @@ func (cli *DockerCli) Err() io.Writer { } // In returns the reader used for stdin -func (cli *DockerCli) In() io.ReadCloser { +func (cli *DockerCli) In() *InStream { return cli.in } @@ -76,48 +57,15 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile { return cli.configFile } -// IsTerminalIn returns true if the clients stdin is a TTY -// TODO: remove -func (cli *DockerCli) IsTerminalIn() bool { - return cli.isTerminalIn -} - -// CheckTtyInput checks if we are trying to attach to a container tty -// from a non-tty client input stream, and if so, returns an error. -func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { - // In order to attach to a container tty, input stream for the client must - // be a tty itself: redirecting or piping the client standard input is - // incompatible with `docker run -t`, `docker exec -t` or `docker attach`. - if ttyMode && attachStdin && !cli.isTerminalIn { - eText := "the input device is not a TTY" - if runtime.GOOS == "windows" { - return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'") - } - return errors.New(eText) - } - return nil -} - func (cli *DockerCli) setRawTerminal() error { - if os.Getenv("NORAW") == "" { - if cli.isTerminalIn { - state, err := term.SetRawTerminal(cli.inFd) - if err != nil { - return err - } - cli.inState = state - } - if err := cli.out.setRawTerminal(); err != nil { - return err - } + if err := cli.in.setRawTerminal(); err != nil { + return err } - return nil + return cli.out.setRawTerminal() } func (cli *DockerCli) restoreTerminal(in io.Closer) error { - if cli.inState != nil { - term.RestoreTerminal(cli.inFd, cli.inState) - } + cli.in.restoreTerminal() cli.out.restoreTerminal() // WARNING: DO NOT REMOVE THE OS CHECK !!! // For some reason this Close call blocks on darwin.. @@ -138,11 +86,6 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) { if err != nil { return err } - - if cli.in != nil { - cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) - } - if opts.Common.TrustKey == "" { cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) } else { @@ -154,7 +97,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) { // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli { - return &DockerCli{in: in, out: NewOutStream(out), err: err} + return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err} } // LoadDefaultConfigFile attempts to load the default config file and returns diff --git a/components/engine/api/client/container/attach.go b/components/engine/api/client/container/attach.go index 65e182c722..12904f09b9 100644 --- a/components/engine/api/client/container/attach.go +++ b/components/engine/api/client/container/attach.go @@ -60,7 +60,7 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error { return fmt.Errorf("You cannot attach to a paused container, unpause it first") } - if err := dockerCli.CheckTtyInput(!opts.noStdin, c.Config.Tty); err != nil { + if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil { return err } diff --git a/components/engine/api/client/container/exec.go b/components/engine/api/client/container/exec.go index f20b9f366e..3a83e98689 100644 --- a/components/engine/api/client/container/exec.go +++ b/components/engine/api/client/container/exec.go @@ -80,7 +80,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e //Temp struct for execStart so that we don't need to transfer all the execConfig if !execConfig.Detach { - if err := dockerCli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { + if err := dockerCli.In().CheckTty(execConfig.AttachStdin, execConfig.Tty); err != nil { return err } } else { @@ -127,7 +127,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp) }) - if execConfig.Tty && dockerCli.IsTerminalIn() { + if execConfig.Tty && dockerCli.In().IsTerminal() { if err := dockerCli.MonitorTtySize(ctx, execID, true); err != nil { fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) } diff --git a/components/engine/api/client/container/run.go b/components/engine/api/client/container/run.go index af9abdae22..ac6747526d 100644 --- a/components/engine/api/client/container/run.go +++ b/components/engine/api/client/container/run.go @@ -109,7 +109,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, config.ArgsEscaped = false if !opts.detach { - if err := dockerCli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { + if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil { return err } } else { diff --git a/components/engine/api/client/in.go b/components/engine/api/client/in.go new file mode 100644 index 0000000000..acbac50c34 --- /dev/null +++ b/components/engine/api/client/in.go @@ -0,0 +1,73 @@ +package client + +import ( + "errors" + "io" + "os" + "runtime" + + "github.com/docker/docker/pkg/term" +) + +// InStream is an input stream used by the DockerCli to read user input +type InStream struct { + in io.ReadCloser + fd uintptr + isTerminal bool + state *term.State +} + +func (i *InStream) Read(p []byte) (int, error) { + return i.in.Read(p) +} + +// Close implements the Closer interface +func (i *InStream) Close() error { + return i.in.Close() +} + +// FD returns the file descriptor number for this stream +func (i *InStream) FD() uintptr { + return i.fd +} + +// IsTerminal returns true if this stream is connected to a terminal +func (i *InStream) IsTerminal() bool { + return i.isTerminal +} + +func (i *InStream) setRawTerminal() (err error) { + if os.Getenv("NORAW") != "" || !i.isTerminal { + return nil + } + i.state, err = term.SetRawTerminal(i.fd) + return err +} + +func (i *InStream) restoreTerminal() { + if i.state != nil { + term.RestoreTerminal(i.fd, i.state) + } +} + +// CheckTty checks if we are trying to attach to a container tty +// from a non-tty client input stream, and if so, returns an error. +func (i *InStream) CheckTty(attachStdin, ttyMode bool) error { + // In order to attach to a container tty, input stream for the client must + // be a tty itself: redirecting or piping the client standard input is + // incompatible with `docker run -t`, `docker exec -t` or `docker attach`. + if ttyMode && attachStdin && !i.isTerminal { + eText := "the input device is not a TTY" + if runtime.GOOS == "windows" { + return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'") + } + return errors.New(eText) + } + return nil +} + +// NewInStream returns a new OutStream object from a Writer +func NewInStream(in io.ReadCloser) *InStream { + fd, isTerminal := term.GetFdInfo(in) + return &InStream{in: in, fd: fd, isTerminal: isTerminal} +} diff --git a/components/engine/api/client/registry.go b/components/engine/api/client/registry.go index 2fcdf77c53..634a8b1b94 100644 --- a/components/engine/api/client/registry.go +++ b/components/engine/api/client/registry.go @@ -89,7 +89,7 @@ func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig { func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) { // On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210 if runtime.GOOS == "windows" { - cli.in = os.Stdin + cli.in = NewInStream(os.Stdin) } if !isDefaultRegistry { @@ -108,7 +108,7 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is // Linux will hit this if you attempt `cat | docker login`, and Windows // will hit this if you attempt docker login from mintty where stdin // is a pipe, not a character based console. - if flPassword == "" && !cli.isTerminalIn { + if flPassword == "" && !cli.In().IsTerminal() { return authconfig, fmt.Errorf("Error: Cannot perform an interactive login from a non TTY device") } @@ -130,17 +130,17 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is return authconfig, fmt.Errorf("Error: Non-null Username Required") } if flPassword == "" { - oldState, err := term.SaveState(cli.inFd) + oldState, err := term.SaveState(cli.In().FD()) if err != nil { return authconfig, err } fmt.Fprintf(cli.out, "Password: ") - term.DisableEcho(cli.inFd, oldState) + term.DisableEcho(cli.In().FD(), oldState) flPassword = readInput(cli.in, cli.out) fmt.Fprint(cli.out, "\n") - term.RestoreTerminal(cli.inFd, oldState) + term.RestoreTerminal(cli.In().FD(), oldState) if flPassword == "" { return authconfig, fmt.Errorf("Error: Password Required") }