Browse code

Extract input stream into a new type.

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2016/08/30 00:52:05
Showing 6 changed files
... ...
@@ -17,7 +17,6 @@ import (
17 17
 	"github.com/docker/docker/client"
18 18
 	"github.com/docker/docker/dockerversion"
19 19
 	dopts "github.com/docker/docker/opts"
20
-	"github.com/docker/docker/pkg/term"
21 20
 	"github.com/docker/go-connections/sockets"
22 21
 	"github.com/docker/go-connections/tlsconfig"
23 22
 )
... ...
@@ -25,30 +24,12 @@ import (
25 25
 // DockerCli represents the docker command line client.
26 26
 // Instances of the client can be returned from NewDockerCli.
27 27
 type DockerCli struct {
28
-	// initializing closure
29
-	init func() error
30
-
31
-	// configFile has the client configuration file
32 28
 	configFile *configfile.ConfigFile
33
-	// in holds the input stream and closer (io.ReadCloser) for the client.
34
-	// TODO: remove
35
-	in io.ReadCloser
36
-	// err holds the error stream (io.Writer) for the client.
37
-	err io.Writer
38
-	// keyFile holds the key file as a string.
39
-	keyFile string
40
-	// inFd holds the file descriptor of the client's STDIN (if valid).
41
-	// TODO: remove
42
-	inFd uintptr
43
-	// isTerminalIn indicates whether the client's STDIN is a TTY
44
-	// TODO: remove
45
-	isTerminalIn bool
46
-	// client is the http client that performs all API operations
47
-	client client.APIClient
48
-	// inState holds the terminal input state
49
-	// TODO: remove
50
-	inState *term.State
51
-	out     *OutStream
29
+	in         *InStream
30
+	out        *OutStream
31
+	err        io.Writer
32
+	keyFile    string
33
+	client     client.APIClient
52 34
 }
53 35
 
54 36
 // Client returns the APIClient
... ...
@@ -67,7 +48,7 @@ func (cli *DockerCli) Err() io.Writer {
67 67
 }
68 68
 
69 69
 // In returns the reader used for stdin
70
-func (cli *DockerCli) In() io.ReadCloser {
70
+func (cli *DockerCli) In() *InStream {
71 71
 	return cli.in
72 72
 }
73 73
 
... ...
@@ -76,48 +57,15 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
76 76
 	return cli.configFile
77 77
 }
78 78
 
79
-// IsTerminalIn returns true if the clients stdin is a TTY
80
-// TODO: remove
81
-func (cli *DockerCli) IsTerminalIn() bool {
82
-	return cli.isTerminalIn
83
-}
84
-
85
-// CheckTtyInput checks if we are trying to attach to a container tty
86
-// from a non-tty client input stream, and if so, returns an error.
87
-func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
88
-	// In order to attach to a container tty, input stream for the client must
89
-	// be a tty itself: redirecting or piping the client standard input is
90
-	// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
91
-	if ttyMode && attachStdin && !cli.isTerminalIn {
92
-		eText := "the input device is not a TTY"
93
-		if runtime.GOOS == "windows" {
94
-			return errors.New(eText + ".  If you are using mintty, try prefixing the command with 'winpty'")
95
-		}
96
-		return errors.New(eText)
97
-	}
98
-	return nil
99
-}
100
-
101 79
 func (cli *DockerCli) setRawTerminal() error {
102
-	if os.Getenv("NORAW") == "" {
103
-		if cli.isTerminalIn {
104
-			state, err := term.SetRawTerminal(cli.inFd)
105
-			if err != nil {
106
-				return err
107
-			}
108
-			cli.inState = state
109
-		}
110
-		if err := cli.out.setRawTerminal(); err != nil {
111
-			return err
112
-		}
80
+	if err := cli.in.setRawTerminal(); err != nil {
81
+		return err
113 82
 	}
114
-	return nil
83
+	return cli.out.setRawTerminal()
115 84
 }
116 85
 
117 86
 func (cli *DockerCli) restoreTerminal(in io.Closer) error {
118
-	if cli.inState != nil {
119
-		term.RestoreTerminal(cli.inFd, cli.inState)
120
-	}
87
+	cli.in.restoreTerminal()
121 88
 	cli.out.restoreTerminal()
122 89
 	// WARNING: DO NOT REMOVE THE OS CHECK !!!
123 90
 	// For some reason this Close call blocks on darwin..
... ...
@@ -138,11 +86,6 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
138 138
 	if err != nil {
139 139
 		return err
140 140
 	}
141
-
142
-	if cli.in != nil {
143
-		cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
144
-	}
145
-
146 141
 	if opts.Common.TrustKey == "" {
147 142
 		cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
148 143
 	} else {
... ...
@@ -154,7 +97,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
154 154
 
155 155
 // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
156 156
 func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
157
-	return &DockerCli{in: in, out: NewOutStream(out), err: err}
157
+	return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
158 158
 }
159 159
 
160 160
 // LoadDefaultConfigFile attempts to load the default config file and returns
... ...
@@ -60,7 +60,7 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
60 60
 		return fmt.Errorf("You cannot attach to a paused container, unpause it first")
61 61
 	}
62 62
 
63
-	if err := dockerCli.CheckTtyInput(!opts.noStdin, c.Config.Tty); err != nil {
63
+	if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
64 64
 		return err
65 65
 	}
66 66
 
... ...
@@ -80,7 +80,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
80 80
 
81 81
 	//Temp struct for execStart so that we don't need to transfer all the execConfig
82 82
 	if !execConfig.Detach {
83
-		if err := dockerCli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
83
+		if err := dockerCli.In().CheckTty(execConfig.AttachStdin, execConfig.Tty); err != nil {
84 84
 			return err
85 85
 		}
86 86
 	} else {
... ...
@@ -127,7 +127,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
127 127
 		return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
128 128
 	})
129 129
 
130
-	if execConfig.Tty && dockerCli.IsTerminalIn() {
130
+	if execConfig.Tty && dockerCli.In().IsTerminal() {
131 131
 		if err := dockerCli.MonitorTtySize(ctx, execID, true); err != nil {
132 132
 			fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
133 133
 		}
... ...
@@ -109,7 +109,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
109 109
 	config.ArgsEscaped = false
110 110
 
111 111
 	if !opts.detach {
112
-		if err := dockerCli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
112
+		if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
113 113
 			return err
114 114
 		}
115 115
 	} else {
116 116
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package client
1
+
2
+import (
3
+	"errors"
4
+	"io"
5
+	"os"
6
+	"runtime"
7
+
8
+	"github.com/docker/docker/pkg/term"
9
+)
10
+
11
+// InStream is an input stream used by the DockerCli to read user input
12
+type InStream struct {
13
+	in         io.ReadCloser
14
+	fd         uintptr
15
+	isTerminal bool
16
+	state      *term.State
17
+}
18
+
19
+func (i *InStream) Read(p []byte) (int, error) {
20
+	return i.in.Read(p)
21
+}
22
+
23
+// Close implements the Closer interface
24
+func (i *InStream) Close() error {
25
+	return i.in.Close()
26
+}
27
+
28
+// FD returns the file descriptor number for this stream
29
+func (i *InStream) FD() uintptr {
30
+	return i.fd
31
+}
32
+
33
+// IsTerminal returns true if this stream is connected to a terminal
34
+func (i *InStream) IsTerminal() bool {
35
+	return i.isTerminal
36
+}
37
+
38
+func (i *InStream) setRawTerminal() (err error) {
39
+	if os.Getenv("NORAW") != "" || !i.isTerminal {
40
+		return nil
41
+	}
42
+	i.state, err = term.SetRawTerminal(i.fd)
43
+	return err
44
+}
45
+
46
+func (i *InStream) restoreTerminal() {
47
+	if i.state != nil {
48
+		term.RestoreTerminal(i.fd, i.state)
49
+	}
50
+}
51
+
52
+// CheckTty checks if we are trying to attach to a container tty
53
+// from a non-tty client input stream, and if so, returns an error.
54
+func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
55
+	// In order to attach to a container tty, input stream for the client must
56
+	// be a tty itself: redirecting or piping the client standard input is
57
+	// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
58
+	if ttyMode && attachStdin && !i.isTerminal {
59
+		eText := "the input device is not a TTY"
60
+		if runtime.GOOS == "windows" {
61
+			return errors.New(eText + ".  If you are using mintty, try prefixing the command with 'winpty'")
62
+		}
63
+		return errors.New(eText)
64
+	}
65
+	return nil
66
+}
67
+
68
+// NewInStream returns a new OutStream object from a Writer
69
+func NewInStream(in io.ReadCloser) *InStream {
70
+	fd, isTerminal := term.GetFdInfo(in)
71
+	return &InStream{in: in, fd: fd, isTerminal: isTerminal}
72
+}
... ...
@@ -89,7 +89,7 @@ func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig {
89 89
 func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
90 90
 	// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
91 91
 	if runtime.GOOS == "windows" {
92
-		cli.in = os.Stdin
92
+		cli.in = NewInStream(os.Stdin)
93 93
 	}
94 94
 
95 95
 	if !isDefaultRegistry {
... ...
@@ -108,7 +108,7 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is
108 108
 	// Linux will hit this if you attempt `cat | docker login`, and Windows
109 109
 	// will hit this if you attempt docker login from mintty where stdin
110 110
 	// is a pipe, not a character based console.
111
-	if flPassword == "" && !cli.isTerminalIn {
111
+	if flPassword == "" && !cli.In().IsTerminal() {
112 112
 		return authconfig, fmt.Errorf("Error: Cannot perform an interactive login from a non TTY device")
113 113
 	}
114 114
 
... ...
@@ -130,17 +130,17 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is
130 130
 		return authconfig, fmt.Errorf("Error: Non-null Username Required")
131 131
 	}
132 132
 	if flPassword == "" {
133
-		oldState, err := term.SaveState(cli.inFd)
133
+		oldState, err := term.SaveState(cli.In().FD())
134 134
 		if err != nil {
135 135
 			return authconfig, err
136 136
 		}
137 137
 		fmt.Fprintf(cli.out, "Password: ")
138
-		term.DisableEcho(cli.inFd, oldState)
138
+		term.DisableEcho(cli.In().FD(), oldState)
139 139
 
140 140
 		flPassword = readInput(cli.in, cli.out)
141 141
 		fmt.Fprint(cli.out, "\n")
142 142
 
143
-		term.RestoreTerminal(cli.inFd, oldState)
143
+		term.RestoreTerminal(cli.In().FD(), oldState)
144 144
 		if flPassword == "" {
145 145
 			return authconfig, fmt.Errorf("Error: Password Required")
146 146
 		}