Browse code

Extract stream output handling to a new type.

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

Daniel Nephin authored on 2016/08/30 00:11:29
Showing 15 changed files
... ...
@@ -31,27 +31,24 @@ type DockerCli struct {
31 31
 	// configFile has the client configuration file
32 32
 	configFile *configfile.ConfigFile
33 33
 	// in holds the input stream and closer (io.ReadCloser) for the client.
34
+	// TODO: remove
34 35
 	in io.ReadCloser
35
-	// out holds the output stream (io.Writer) for the client.
36
-	out io.Writer
37 36
 	// err holds the error stream (io.Writer) for the client.
38 37
 	err io.Writer
39 38
 	// keyFile holds the key file as a string.
40 39
 	keyFile string
41 40
 	// inFd holds the file descriptor of the client's STDIN (if valid).
41
+	// TODO: remove
42 42
 	inFd uintptr
43
-	// outFd holds file descriptor of the client's STDOUT (if valid).
44
-	outFd uintptr
45 43
 	// isTerminalIn indicates whether the client's STDIN is a TTY
44
+	// TODO: remove
46 45
 	isTerminalIn bool
47
-	// isTerminalOut indicates whether the client's STDOUT is a TTY
48
-	isTerminalOut bool
49 46
 	// client is the http client that performs all API operations
50 47
 	client client.APIClient
51 48
 	// inState holds the terminal input state
49
+	// TODO: remove
52 50
 	inState *term.State
53
-	// outState holds the terminal output state
54
-	outState *term.State
51
+	out     *OutStream
55 52
 }
56 53
 
57 54
 // Client returns the APIClient
... ...
@@ -60,7 +57,7 @@ func (cli *DockerCli) Client() client.APIClient {
60 60
 }
61 61
 
62 62
 // Out returns the writer used for stdout
63
-func (cli *DockerCli) Out() io.Writer {
63
+func (cli *DockerCli) Out() *OutStream {
64 64
 	return cli.out
65 65
 }
66 66
 
... ...
@@ -80,20 +77,11 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
80 80
 }
81 81
 
82 82
 // IsTerminalIn returns true if the clients stdin is a TTY
83
+// TODO: remove
83 84
 func (cli *DockerCli) IsTerminalIn() bool {
84 85
 	return cli.isTerminalIn
85 86
 }
86 87
 
87
-// IsTerminalOut returns true if the clients stdout is a TTY
88
-func (cli *DockerCli) IsTerminalOut() bool {
89
-	return cli.isTerminalOut
90
-}
91
-
92
-// OutFd returns the fd for the stdout stream
93
-func (cli *DockerCli) OutFd() uintptr {
94
-	return cli.outFd
95
-}
96
-
97 88
 // CheckTtyInput checks if we are trying to attach to a container tty
98 89
 // from a non-tty client input stream, and if so, returns an error.
99 90
 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
... ...
@@ -119,12 +107,8 @@ func (cli *DockerCli) setRawTerminal() error {
119 119
 			}
120 120
 			cli.inState = state
121 121
 		}
122
-		if cli.isTerminalOut {
123
-			state, err := term.SetRawTerminalOutput(cli.outFd)
124
-			if err != nil {
125
-				return err
126
-			}
127
-			cli.outState = state
122
+		if err := cli.out.setRawTerminal(); err != nil {
123
+			return err
128 124
 		}
129 125
 	}
130 126
 	return nil
... ...
@@ -134,9 +118,7 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
134 134
 	if cli.inState != nil {
135 135
 		term.RestoreTerminal(cli.inFd, cli.inState)
136 136
 	}
137
-	if cli.outState != nil {
138
-		term.RestoreTerminal(cli.outFd, cli.outState)
139
-	}
137
+	cli.out.restoreTerminal()
140 138
 	// WARNING: DO NOT REMOVE THE OS CHECK !!!
141 139
 	// For some reason this Close call blocks on darwin..
142 140
 	// As the client exists right after, simply discard the close
... ...
@@ -149,22 +131,17 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
149 149
 
150 150
 // Initialize the dockerCli runs initialization that must happen after command
151 151
 // line flags are parsed.
152
-func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
152
+func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
153 153
 	cli.configFile = LoadDefaultConfigFile(cli.err)
154 154
 
155
-	client, err := NewAPIClientFromFlags(opts.Common, cli.configFile)
155
+	cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
156 156
 	if err != nil {
157 157
 		return err
158 158
 	}
159 159
 
160
-	cli.client = client
161
-
162 160
 	if cli.in != nil {
163 161
 		cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
164 162
 	}
165
-	if cli.out != nil {
166
-		cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
167
-	}
168 163
 
169 164
 	if opts.Common.TrustKey == "" {
170 165
 		cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
... ...
@@ -177,11 +154,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
177 177
 
178 178
 // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
179 179
 func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
180
-	return &DockerCli{
181
-		in:  in,
182
-		out: out,
183
-		err: err,
184
-	}
180
+	return &DockerCli{in: in, out: NewOutStream(out), err: err}
185 181
 }
186 182
 
187 183
 // LoadDefaultConfigFile attempts to load the default config file and returns
... ...
@@ -95,8 +95,8 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
95 95
 	}
96 96
 	defer resp.Close()
97 97
 
98
-	if c.Config.Tty && dockerCli.IsTerminalOut() {
99
-		height, width := dockerCli.GetTtySize()
98
+	if c.Config.Tty && dockerCli.Out().IsTerminal() {
99
+		height, width := dockerCli.Out().GetTtySize()
100 100
 		// To handle the case where a user repeatedly attaches/detaches without resizing their
101 101
 		// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
102 102
 		// resize it, then go back to normal. Without this, every attach after the first will
... ...
@@ -103,8 +103,8 @@ func pullImage(ctx context.Context, dockerCli *client.DockerCli, image string, o
103 103
 	return jsonmessage.DisplayJSONMessagesStream(
104 104
 		responseBody,
105 105
 		out,
106
-		dockerCli.OutFd(),
107
-		dockerCli.IsTerminalOut(),
106
+		dockerCli.Out().FD(),
107
+		dockerCli.Out().IsTerminal(),
108 108
 		nil)
109 109
 }
110 110
 
... ...
@@ -38,7 +38,7 @@ func NewExportCommand(dockerCli *client.DockerCli) *cobra.Command {
38 38
 }
39 39
 
40 40
 func runExport(dockerCli *client.DockerCli, opts exportOptions) error {
41
-	if opts.output == "" && dockerCli.IsTerminalOut() {
41
+	if opts.output == "" && dockerCli.Out().IsTerminal() {
42 42
 		return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
43 43
 	}
44 44
 
... ...
@@ -135,7 +135,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
135 135
 	// a far better user experience rather than relying on subsequent resizes
136 136
 	// to cause things to catch up.
137 137
 	if runtime.GOOS == "windows" {
138
-		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
138
+		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
139 139
 	}
140 140
 
141 141
 	ctx, cancelFun := context.WithCancel(context.Background())
... ...
@@ -234,7 +234,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
234 234
 		return runStartContainerErr(err)
235 235
 	}
236 236
 
237
-	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() {
237
+	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
238 238
 		if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
239 239
 			fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
240 240
 		}
... ...
@@ -118,7 +118,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
118 118
 		}
119 119
 
120 120
 		// 5. Wait for attachment to break.
121
-		if c.Config.Tty && dockerCli.IsTerminalOut() {
121
+		if c.Config.Tty && dockerCli.Out().IsTerminal() {
122 122
 			if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil {
123 123
 				fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
124 124
 			}
... ...
@@ -227,7 +227,7 @@ func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
227 227
 
228 228
 	// Setup an upload progress bar
229 229
 	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
230
-	if !dockerCli.IsTerminalOut() {
230
+	if !dockerCli.Out().IsTerminal() {
231 231
 		progressOutput = &lastProgressOutput{output: progressOutput}
232 232
 	}
233 233
 
... ...
@@ -293,7 +293,7 @@ func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
293 293
 	}
294 294
 	defer response.Body.Close()
295 295
 
296
-	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
296
+	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), nil)
297 297
 	if err != nil {
298 298
 		if jerr, ok := err.(*jsonmessage.JSONError); ok {
299 299
 			// If no error code is set, default to 1
... ...
@@ -84,5 +84,5 @@ func runImport(dockerCli *client.DockerCli, opts importOptions) error {
84 84
 	}
85 85
 	defer responseBody.Close()
86 86
 
87
-	return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
87
+	return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
88 88
 }
... ...
@@ -49,7 +49,7 @@ func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
49 49
 		defer file.Close()
50 50
 		input = file
51 51
 	}
52
-	if !dockerCli.IsTerminalOut() {
52
+	if !dockerCli.Out().IsTerminal() {
53 53
 		opts.quiet = true
54 54
 	}
55 55
 	response, err := dockerCli.Client().ImageLoad(context.Background(), input, opts.quiet)
... ...
@@ -59,7 +59,7 @@ func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
59 59
 	defer response.Body.Close()
60 60
 
61 61
 	if response.Body != nil && response.JSON {
62
-		return jsonmessage.DisplayJSONMessagesStream(response.Body, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
62
+		return jsonmessage.DisplayJSONMessagesToStream(response.Body, dockerCli.Out(), nil)
63 63
 	}
64 64
 
65 65
 	_, err = io.Copy(dockerCli.Out(), response.Body)
... ...
@@ -57,6 +57,5 @@ func runPush(dockerCli *client.DockerCli, remote string) error {
57 57
 	}
58 58
 
59 59
 	defer responseBody.Close()
60
-
61
-	return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
60
+	return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
62 61
 }
... ...
@@ -38,7 +38,7 @@ func NewSaveCommand(dockerCli *client.DockerCli) *cobra.Command {
38 38
 }
39 39
 
40 40
 func runSave(dockerCli *client.DockerCli, opts saveOptions) error {
41
-	if opts.output == "" && dockerCli.IsTerminalOut() {
41
+	if opts.output == "" && dockerCli.Out().IsTerminal() {
42 42
 		return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
43 43
 	}
44 44
 
45 45
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package client
1
+
2
+import (
3
+	"io"
4
+	"os"
5
+
6
+	"github.com/Sirupsen/logrus"
7
+	"github.com/docker/docker/pkg/term"
8
+)
9
+
10
+// OutStream is an output stream used by the DockerCli to write normal program
11
+// output.
12
+type OutStream struct {
13
+	out        io.Writer
14
+	fd         uintptr
15
+	isTerminal bool
16
+	state      *term.State
17
+}
18
+
19
+func (o *OutStream) Write(p []byte) (int, error) {
20
+	return o.out.Write(p)
21
+}
22
+
23
+// FD returns the file descriptor number for this stream
24
+func (o *OutStream) FD() uintptr {
25
+	return o.fd
26
+}
27
+
28
+// IsTerminal returns true if this stream is connected to a terminal
29
+func (o *OutStream) IsTerminal() bool {
30
+	return o.isTerminal
31
+}
32
+
33
+func (o *OutStream) setRawTerminal() (err error) {
34
+	if os.Getenv("NORAW") != "" || !o.isTerminal {
35
+		return nil
36
+	}
37
+	o.state, err = term.SetRawTerminalOutput(o.fd)
38
+	return err
39
+}
40
+
41
+func (o *OutStream) restoreTerminal() {
42
+	if o.state != nil {
43
+		term.RestoreTerminal(o.fd, o.state)
44
+	}
45
+}
46
+
47
+// GetTtySize returns the height and width in characters of the tty
48
+func (o *OutStream) GetTtySize() (int, int) {
49
+	if !o.isTerminal {
50
+		return 0, 0
51
+	}
52
+	ws, err := term.GetWinsize(o.fd)
53
+	if err != nil {
54
+		logrus.Debugf("Error getting size: %s", err)
55
+		if ws == nil {
56
+			return 0, 0
57
+		}
58
+	}
59
+	return int(ws.Height), int(ws.Width)
60
+}
61
+
62
+// NewOutStream returns a new OutStream object from a Writer
63
+func NewOutStream(out io.Writer) *OutStream {
64
+	fd, isTerminal := term.GetFdInfo(out)
65
+	return &OutStream{out: out, fd: fd, isTerminal: isTerminal}
66
+}
... ...
@@ -440,14 +440,14 @@ func (cli *DockerCli) TrustedPush(ctx context.Context, repoInfo *registry.Reposi
440 440
 	// We want trust signatures to always take an explicit tag,
441 441
 	// otherwise it will act as an untrusted push.
442 442
 	if tag == "" {
443
-		if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil); err != nil {
443
+		if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil); err != nil {
444 444
 			return err
445 445
 		}
446 446
 		fmt.Fprintln(cli.out, "No tag specified, skipping trust metadata push")
447 447
 		return nil
448 448
 	}
449 449
 
450
-	if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget); err != nil {
450
+	if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), handleTarget); err != nil {
451 451
 		return err
452 452
 	}
453 453
 
... ...
@@ -580,7 +580,7 @@ func (cli *DockerCli) ImagePullPrivileged(ctx context.Context, authConfig types.
580 580
 	}
581 581
 	defer responseBody.Close()
582 582
 
583
-	return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
583
+	return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
584 584
 }
585 585
 
586 586
 // ImagePushPrivileged push the image
... ...
@@ -17,11 +17,11 @@ import (
17 17
 	"github.com/docker/docker/api/types"
18 18
 	"github.com/docker/docker/client"
19 19
 	"github.com/docker/docker/pkg/signal"
20
-	"github.com/docker/docker/pkg/term"
20
+	"github.com/docker/engine-api/types"
21 21
 )
22 22
 
23 23
 func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
24
-	height, width := cli.GetTtySize()
24
+	height, width := cli.Out().GetTtySize()
25 25
 	cli.ResizeTtyTo(ctx, id, height, width, isExec)
26 26
 }
27 27
 
... ...
@@ -70,10 +70,10 @@ func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool
70 70
 
71 71
 	if runtime.GOOS == "windows" {
72 72
 		go func() {
73
-			prevH, prevW := cli.GetTtySize()
73
+			prevH, prevW := cli.Out().GetTtySize()
74 74
 			for {
75 75
 				time.Sleep(time.Millisecond * 250)
76
-				h, w := cli.GetTtySize()
76
+				h, w := cli.Out().GetTtySize()
77 77
 
78 78
 				if prevW != w || prevH != h {
79 79
 					cli.resizeTty(ctx, id, isExec)
... ...
@@ -94,21 +94,6 @@ func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool
94 94
 	return nil
95 95
 }
96 96
 
97
-// GetTtySize returns the height and width in characters of the tty
98
-func (cli *DockerCli) GetTtySize() (int, int) {
99
-	if !cli.isTerminalOut {
100
-		return 0, 0
101
-	}
102
-	ws, err := term.GetWinsize(cli.outFd)
103
-	if err != nil {
104
-		logrus.Debugf("Error getting size: %s", err)
105
-		if ws == nil {
106
-			return 0, 0
107
-		}
108
-	}
109
-	return int(ws.Height), int(ws.Width)
110
-}
111
-
112 97
 // CopyToFile writes the content of the reader to the specified file
113 98
 func CopyToFile(outfile string, r io.Reader) error {
114 99
 	tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
... ...
@@ -219,3 +219,14 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
219 219
 	}
220 220
 	return nil
221 221
 }
222
+
223
+type stream interface {
224
+	io.Writer
225
+	FD() uintptr
226
+	IsTerminal() bool
227
+}
228
+
229
+// DisplayJSONMessagesToStream prints json messages to the output stream
230
+func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
231
+	return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
232
+}