Browse Source

Extract input stream into a new type.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
Daniel Nephin 8 years ago
parent
commit
bec81075bf

+ 11 - 68
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

+ 1 - 1
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
 	}
 

+ 2 - 2
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)
 		}

+ 1 - 1
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 {

+ 73 - 0
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}
+}

+ 5 - 5
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")
 		}