Browse code

Merge pull request #26107 from dnephin/client_cleanup

Shrink the DockerCLI type

Alexander Morozov authored on 2016/09/09 04:18:46
Showing 21 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,33 +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
-	in io.ReadCloser
35
-	// out holds the output stream (io.Writer) for the client.
36
-	out io.Writer
37
-	// err holds the error stream (io.Writer) for the client.
38
-	err io.Writer
39
-	// keyFile holds the key file as a string.
40
-	keyFile string
41
-	// inFd holds the file descriptor of the client's STDIN (if valid).
42
-	inFd uintptr
43
-	// outFd holds file descriptor of the client's STDOUT (if valid).
44
-	outFd uintptr
45
-	// isTerminalIn indicates whether the client's STDIN is a TTY
46
-	isTerminalIn bool
47
-	// isTerminalOut indicates whether the client's STDOUT is a TTY
48
-	isTerminalOut bool
49
-	// client is the http client that performs all API operations
50
-	client client.APIClient
51
-	// inState holds the terminal input state
52
-	inState *term.State
53
-	// outState holds the terminal output state
54
-	outState *term.State
29
+	in         *InStream
30
+	out        *OutStream
31
+	err        io.Writer
32
+	keyFile    string
33
+	client     client.APIClient
55 34
 }
56 35
 
57 36
 // Client returns the APIClient
... ...
@@ -60,7 +38,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
 
... ...
@@ -70,7 +48,7 @@ func (cli *DockerCli) Err() io.Writer {
70 70
 }
71 71
 
72 72
 // In returns the reader used for stdin
73
-func (cli *DockerCli) In() io.ReadCloser {
73
+func (cli *DockerCli) In() *InStream {
74 74
 	return cli.in
75 75
 }
76 76
 
... ...
@@ -79,93 +57,16 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
79 79
 	return cli.configFile
80 80
 }
81 81
 
82
-// IsTerminalIn returns true if the clients stdin is a TTY
83
-func (cli *DockerCli) IsTerminalIn() bool {
84
-	return cli.isTerminalIn
85
-}
86
-
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
-// CheckTtyInput checks if we are trying to attach to a container tty
98
-// from a non-tty client input stream, and if so, returns an error.
99
-func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
100
-	// In order to attach to a container tty, input stream for the client must
101
-	// be a tty itself: redirecting or piping the client standard input is
102
-	// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
103
-	if ttyMode && attachStdin && !cli.isTerminalIn {
104
-		eText := "the input device is not a TTY"
105
-		if runtime.GOOS == "windows" {
106
-			return errors.New(eText + ".  If you are using mintty, try prefixing the command with 'winpty'")
107
-		}
108
-		return errors.New(eText)
109
-	}
110
-	return nil
111
-}
112
-
113
-func (cli *DockerCli) setRawTerminal() error {
114
-	if os.Getenv("NORAW") == "" {
115
-		if cli.isTerminalIn {
116
-			state, err := term.SetRawTerminal(cli.inFd)
117
-			if err != nil {
118
-				return err
119
-			}
120
-			cli.inState = state
121
-		}
122
-		if cli.isTerminalOut {
123
-			state, err := term.SetRawTerminalOutput(cli.outFd)
124
-			if err != nil {
125
-				return err
126
-			}
127
-			cli.outState = state
128
-		}
129
-	}
130
-	return nil
131
-}
132
-
133
-func (cli *DockerCli) restoreTerminal(in io.Closer) error {
134
-	if cli.inState != nil {
135
-		term.RestoreTerminal(cli.inFd, cli.inState)
136
-	}
137
-	if cli.outState != nil {
138
-		term.RestoreTerminal(cli.outFd, cli.outState)
139
-	}
140
-	// WARNING: DO NOT REMOVE THE OS CHECK !!!
141
-	// For some reason this Close call blocks on darwin..
142
-	// As the client exists right after, simply discard the close
143
-	// until we find a better solution.
144
-	if in != nil && runtime.GOOS != "darwin" {
145
-		return in.Close()
146
-	}
147
-	return nil
148
-}
149
-
150 82
 // Initialize the dockerCli runs initialization that must happen after command
151 83
 // line flags are parsed.
152 84
 func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
153 85
 	cli.configFile = LoadDefaultConfigFile(cli.err)
154 86
 
155
-	client, err := NewAPIClientFromFlags(opts.Common, cli.configFile)
87
+	var err error
88
+	cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
156 89
 	if err != nil {
157 90
 		return err
158 91
 	}
159
-
160
-	cli.client = client
161
-
162
-	if cli.in != nil {
163
-		cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
164
-	}
165
-	if cli.out != nil {
166
-		cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
167
-	}
168
-
169 92
 	if opts.Common.TrustKey == "" {
170 93
 		cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
171 94
 	} else {
... ...
@@ -177,11 +78,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: NewInStream(in), out: NewOutStream(out), err: err}
185 181
 }
186 182
 
187 183
 // LoadDefaultConfigFile attempts to load the default config file and returns
... ...
@@ -46,8 +46,9 @@ func NewAttachCommand(dockerCli *client.DockerCli) *cobra.Command {
46 46
 
47 47
 func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
48 48
 	ctx := context.Background()
49
+	client := dockerCli.Client()
49 50
 
50
-	c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
51
+	c, err := client.ContainerInspect(ctx, opts.container)
51 52
 	if err != nil {
52 53
 		return err
53 54
 	}
... ...
@@ -60,7 +61,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
 
... ...
@@ -82,11 +83,11 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
82 82
 	}
83 83
 
84 84
 	if opts.proxy && !c.Config.Tty {
85
-		sigc := dockerCli.ForwardAllSignals(ctx, opts.container)
85
+		sigc := ForwardAllSignals(ctx, dockerCli, opts.container)
86 86
 		defer signal.StopCatch(sigc)
87 87
 	}
88 88
 
89
-	resp, errAttach := dockerCli.Client().ContainerAttach(ctx, opts.container, options)
89
+	resp, errAttach := client.ContainerAttach(ctx, opts.container, options)
90 90
 	if errAttach != nil && errAttach != httputil.ErrPersistEOF {
91 91
 		// ContainerAttach returns an ErrPersistEOF (connection closed)
92 92
 		// means server met an error and put it in Hijacked connection
... ...
@@ -95,21 +96,21 @@ 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 103
 		// require the user to manually resize or hit enter.
104
-		dockerCli.ResizeTtyTo(ctx, opts.container, height+1, width+1, false)
104
+		resizeTtyTo(ctx, client, opts.container, height+1, width+1, false)
105 105
 
106 106
 		// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
107 107
 		// to the actual size.
108
-		if err := dockerCli.MonitorTtySize(ctx, opts.container, false); err != nil {
108
+		if err := MonitorTtySize(ctx, dockerCli, opts.container, false); err != nil {
109 109
 			logrus.Debugf("Error monitoring TTY size: %s", err)
110 110
 		}
111 111
 	}
112
-	if err := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
112
+	if err := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
113 113
 		return err
114 114
 	}
115 115
 
... ...
@@ -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
 
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/api/client"
11 11
 	"github.com/docker/docker/api/types"
12 12
 	"github.com/docker/docker/cli"
13
+	apiclient "github.com/docker/docker/client"
13 14
 	"github.com/docker/docker/pkg/promise"
14 15
 	"github.com/spf13/cobra"
15 16
 )
... ...
@@ -66,8 +67,9 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
66 66
 	execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys
67 67
 
68 68
 	ctx := context.Background()
69
+	client := dockerCli.Client()
69 70
 
70
-	response, err := dockerCli.Client().ContainerExecCreate(ctx, container, *execConfig)
71
+	response, err := client.ContainerExecCreate(ctx, container, *execConfig)
71 72
 	if err != nil {
72 73
 		return err
73 74
 	}
... ...
@@ -80,7 +82,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 {
... ...
@@ -89,7 +91,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
89 89
 			Tty:    execConfig.Tty,
90 90
 		}
91 91
 
92
-		if err := dockerCli.Client().ContainerExecStart(ctx, execID, execStartCheck); err != nil {
92
+		if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
93 93
 			return err
94 94
 		}
95 95
 		// For now don't print this - wait for when we support exec wait()
... ...
@@ -118,17 +120,17 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
118 118
 		}
119 119
 	}
120 120
 
121
-	resp, err := dockerCli.Client().ContainerExecAttach(ctx, execID, *execConfig)
121
+	resp, err := client.ContainerExecAttach(ctx, execID, *execConfig)
122 122
 	if err != nil {
123 123
 		return err
124 124
 	}
125 125
 	defer resp.Close()
126 126
 	errCh = promise.Go(func() error {
127
-		return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
127
+		return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, in, out, stderr, resp)
128 128
 	})
129 129
 
130
-	if execConfig.Tty && dockerCli.IsTerminalIn() {
131
-		if err := dockerCli.MonitorTtySize(ctx, execID, true); err != nil {
130
+	if execConfig.Tty && dockerCli.In().IsTerminal() {
131
+		if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil {
132 132
 			fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
133 133
 		}
134 134
 	}
... ...
@@ -139,7 +141,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
139 139
 	}
140 140
 
141 141
 	var status int
142
-	if _, status, err = dockerCli.GetExecExitCode(ctx, execID); err != nil {
142
+	if _, status, err = getExecExitCode(ctx, client, execID); err != nil {
143 143
 		return err
144 144
 	}
145 145
 
... ...
@@ -150,6 +152,21 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
150 150
 	return nil
151 151
 }
152 152
 
153
+// getExecExitCode perform an inspect on the exec command. It returns
154
+// the running state and the exit code.
155
+func getExecExitCode(ctx context.Context, client apiclient.ContainerAPIClient, execID string) (bool, int, error) {
156
+	resp, err := client.ContainerExecInspect(ctx, execID)
157
+	if err != nil {
158
+		// If we can't connect, then the daemon probably died.
159
+		if err != apiclient.ErrConnectionFailed {
160
+			return false, -1, err
161
+		}
162
+		return false, -1, nil
163
+	}
164
+
165
+	return resp.Running, resp.ExitCode, nil
166
+}
167
+
153 168
 // parseExec parses the specified args for the specified command and generates
154 169
 // an ExecConfig from it.
155 170
 func parseExec(opts *execOptions, container string, execCmd []string) (*types.ExecConfig, error) {
... ...
@@ -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
 
45 45
new file mode 100644
... ...
@@ -0,0 +1,121 @@
0
+package container
1
+
2
+import (
3
+	"io"
4
+	"runtime"
5
+	"sync"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+	"github.com/docker/docker/api/client"
9
+	"github.com/docker/docker/api/types"
10
+	"github.com/docker/docker/pkg/stdcopy"
11
+	"golang.org/x/net/context"
12
+)
13
+
14
+type streams interface {
15
+	In() *client.InStream
16
+	Out() *client.OutStream
17
+}
18
+
19
+// holdHijackedConnection handles copying input to and output from streams to the
20
+// connection
21
+func holdHijackedConnection(ctx context.Context, streams streams, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
22
+	var (
23
+		err         error
24
+		restoreOnce sync.Once
25
+	)
26
+	if inputStream != nil && tty {
27
+		if err := setRawTerminal(streams); err != nil {
28
+			return err
29
+		}
30
+		defer func() {
31
+			restoreOnce.Do(func() {
32
+				restoreTerminal(streams, inputStream)
33
+			})
34
+		}()
35
+	}
36
+
37
+	receiveStdout := make(chan error, 1)
38
+	if outputStream != nil || errorStream != nil {
39
+		go func() {
40
+			// When TTY is ON, use regular copy
41
+			if tty && outputStream != nil {
42
+				_, err = io.Copy(outputStream, resp.Reader)
43
+				// we should restore the terminal as soon as possible once connection end
44
+				// so any following print messages will be in normal type.
45
+				if inputStream != nil {
46
+					restoreOnce.Do(func() {
47
+						restoreTerminal(streams, inputStream)
48
+					})
49
+				}
50
+			} else {
51
+				_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
52
+			}
53
+
54
+			logrus.Debug("[hijack] End of stdout")
55
+			receiveStdout <- err
56
+		}()
57
+	}
58
+
59
+	stdinDone := make(chan struct{})
60
+	go func() {
61
+		if inputStream != nil {
62
+			io.Copy(resp.Conn, inputStream)
63
+			// we should restore the terminal as soon as possible once connection end
64
+			// so any following print messages will be in normal type.
65
+			if tty {
66
+				restoreOnce.Do(func() {
67
+					restoreTerminal(streams, inputStream)
68
+				})
69
+			}
70
+			logrus.Debug("[hijack] End of stdin")
71
+		}
72
+
73
+		if err := resp.CloseWrite(); err != nil {
74
+			logrus.Debugf("Couldn't send EOF: %s", err)
75
+		}
76
+		close(stdinDone)
77
+	}()
78
+
79
+	select {
80
+	case err := <-receiveStdout:
81
+		if err != nil {
82
+			logrus.Debugf("Error receiveStdout: %s", err)
83
+			return err
84
+		}
85
+	case <-stdinDone:
86
+		if outputStream != nil || errorStream != nil {
87
+			select {
88
+			case err := <-receiveStdout:
89
+				if err != nil {
90
+					logrus.Debugf("Error receiveStdout: %s", err)
91
+					return err
92
+				}
93
+			case <-ctx.Done():
94
+			}
95
+		}
96
+	case <-ctx.Done():
97
+	}
98
+
99
+	return nil
100
+}
101
+
102
+func setRawTerminal(streams streams) error {
103
+	if err := streams.In().SetRawTerminal(); err != nil {
104
+		return err
105
+	}
106
+	return streams.Out().SetRawTerminal()
107
+}
108
+
109
+func restoreTerminal(streams streams, in io.Closer) error {
110
+	streams.In().RestoreTerminal()
111
+	streams.Out().RestoreTerminal()
112
+	// WARNING: DO NOT REMOVE THE OS CHECK !!!
113
+	// For some reason this Close call blocks on darwin..
114
+	// As the client exists right after, simply discard the close
115
+	// until we find a better solution.
116
+	if in != nil && runtime.GOOS != "darwin" {
117
+		return in.Close()
118
+	}
119
+	return nil
120
+}
... ...
@@ -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 {
... ...
@@ -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())
... ...
@@ -146,7 +146,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
146 146
 		return runStartContainerErr(err)
147 147
 	}
148 148
 	if opts.sigProxy {
149
-		sigc := dockerCli.ForwardAllSignals(ctx, createResponse.ID)
149
+		sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID)
150 150
 		defer signal.StopCatch(sigc)
151 151
 	}
152 152
 	var (
... ...
@@ -203,7 +203,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
203 203
 		defer resp.Close()
204 204
 
205 205
 		errCh = promise.Go(func() error {
206
-			errHijack := dockerCli.HoldHijackedConnection(ctx, config.Tty, in, out, cerr, resp)
206
+			errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp)
207 207
 			if errHijack == nil {
208 208
 				return errAttach
209 209
 			}
... ...
@@ -234,8 +234,8 @@ 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() {
238
-		if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
237
+	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
238
+		if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
239 239
 			fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
240 240
 		}
241 241
 	}
... ...
@@ -64,7 +64,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
64 64
 
65 65
 		// We always use c.ID instead of container to maintain consistency during `docker start`
66 66
 		if !c.Config.Tty {
67
-			sigc := dockerCli.ForwardAllSignals(ctx, c.ID)
67
+			sigc := ForwardAllSignals(ctx, dockerCli, c.ID)
68 68
 			defer signal.StopCatch(sigc)
69 69
 		}
70 70
 
... ...
@@ -95,7 +95,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
95 95
 		}
96 96
 		defer resp.Close()
97 97
 		cErr := promise.Go(func() error {
98
-			errHijack := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp)
98
+			errHijack := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp)
99 99
 			if errHijack == nil {
100 100
 				return errAttach
101 101
 			}
... ...
@@ -118,8 +118,8 @@ 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() {
122
-			if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil {
121
+		if c.Config.Tty && dockerCli.Out().IsTerminal() {
122
+			if err := MonitorTtySize(ctx, dockerCli, c.ID, false); err != nil {
123 123
 				fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
124 124
 			}
125 125
 		}
126 126
new file mode 100644
... ...
@@ -0,0 +1,103 @@
0
+package container
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	gosignal "os/signal"
6
+	"runtime"
7
+	"time"
8
+
9
+	"github.com/Sirupsen/logrus"
10
+	"github.com/docker/docker/api/client"
11
+	"github.com/docker/docker/api/types"
12
+	apiclient "github.com/docker/docker/client"
13
+	"github.com/docker/docker/pkg/signal"
14
+	"golang.org/x/net/context"
15
+)
16
+
17
+// resizeTtyTo resizes tty to specific height and width
18
+func resizeTtyTo(ctx context.Context, client apiclient.ContainerAPIClient, id string, height, width int, isExec bool) {
19
+	if height == 0 && width == 0 {
20
+		return
21
+	}
22
+
23
+	options := types.ResizeOptions{
24
+		Height: height,
25
+		Width:  width,
26
+	}
27
+
28
+	var err error
29
+	if isExec {
30
+		err = client.ContainerExecResize(ctx, id, options)
31
+	} else {
32
+		err = client.ContainerResize(ctx, id, options)
33
+	}
34
+
35
+	if err != nil {
36
+		logrus.Debugf("Error resize: %s", err)
37
+	}
38
+}
39
+
40
+// MonitorTtySize updates the container tty size when the terminal tty changes size
41
+func MonitorTtySize(ctx context.Context, cli *client.DockerCli, id string, isExec bool) error {
42
+	resizeTty := func() {
43
+		height, width := cli.Out().GetTtySize()
44
+		resizeTtyTo(ctx, cli.Client(), id, height, width, isExec)
45
+	}
46
+
47
+	resizeTty()
48
+
49
+	if runtime.GOOS == "windows" {
50
+		go func() {
51
+			prevH, prevW := cli.Out().GetTtySize()
52
+			for {
53
+				time.Sleep(time.Millisecond * 250)
54
+				h, w := cli.Out().GetTtySize()
55
+
56
+				if prevW != w || prevH != h {
57
+					resizeTty()
58
+				}
59
+				prevH = h
60
+				prevW = w
61
+			}
62
+		}()
63
+	} else {
64
+		sigchan := make(chan os.Signal, 1)
65
+		gosignal.Notify(sigchan, signal.SIGWINCH)
66
+		go func() {
67
+			for range sigchan {
68
+				resizeTty()
69
+			}
70
+		}()
71
+	}
72
+	return nil
73
+}
74
+
75
+// ForwardAllSignals forwards signals to the container
76
+func ForwardAllSignals(ctx context.Context, cli *client.DockerCli, cid string) chan os.Signal {
77
+	sigc := make(chan os.Signal, 128)
78
+	signal.CatchAll(sigc)
79
+	go func() {
80
+		for s := range sigc {
81
+			if s == signal.SIGCHLD || s == signal.SIGPIPE {
82
+				continue
83
+			}
84
+			var sig string
85
+			for sigStr, sigN := range signal.SignalMap {
86
+				if sigN == s {
87
+					sig = sigStr
88
+					break
89
+				}
90
+			}
91
+			if sig == "" {
92
+				fmt.Fprintf(cli.Err(), "Unsupported signal: %v. Discarding.\n", s)
93
+				continue
94
+			}
95
+
96
+			if err := cli.Client().ContainerKill(ctx, cid, sig); err != nil {
97
+				logrus.Debugf("Error sending signal: %s", err)
98
+			}
99
+		}
100
+	}()
101
+	return sigc
102
+}
0 103
deleted file mode 100644
... ...
@@ -1,95 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"io"
5
-	"sync"
6
-
7
-	"golang.org/x/net/context"
8
-
9
-	"github.com/Sirupsen/logrus"
10
-	"github.com/docker/docker/api/types"
11
-	"github.com/docker/docker/pkg/stdcopy"
12
-)
13
-
14
-// HoldHijackedConnection handles copying input to and output from streams to the
15
-// connection
16
-func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
17
-	var (
18
-		err         error
19
-		restoreOnce sync.Once
20
-	)
21
-	if inputStream != nil && tty {
22
-		if err := cli.setRawTerminal(); err != nil {
23
-			return err
24
-		}
25
-		defer func() {
26
-			restoreOnce.Do(func() {
27
-				cli.restoreTerminal(inputStream)
28
-			})
29
-		}()
30
-	}
31
-
32
-	receiveStdout := make(chan error, 1)
33
-	if outputStream != nil || errorStream != nil {
34
-		go func() {
35
-			// When TTY is ON, use regular copy
36
-			if tty && outputStream != nil {
37
-				_, err = io.Copy(outputStream, resp.Reader)
38
-				// we should restore the terminal as soon as possible once connection end
39
-				// so any following print messages will be in normal type.
40
-				if inputStream != nil {
41
-					restoreOnce.Do(func() {
42
-						cli.restoreTerminal(inputStream)
43
-					})
44
-				}
45
-			} else {
46
-				_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
47
-			}
48
-
49
-			logrus.Debug("[hijack] End of stdout")
50
-			receiveStdout <- err
51
-		}()
52
-	}
53
-
54
-	stdinDone := make(chan struct{})
55
-	go func() {
56
-		if inputStream != nil {
57
-			io.Copy(resp.Conn, inputStream)
58
-			// we should restore the terminal as soon as possible once connection end
59
-			// so any following print messages will be in normal type.
60
-			if tty {
61
-				restoreOnce.Do(func() {
62
-					cli.restoreTerminal(inputStream)
63
-				})
64
-			}
65
-			logrus.Debug("[hijack] End of stdin")
66
-		}
67
-
68
-		if err := resp.CloseWrite(); err != nil {
69
-			logrus.Debugf("Couldn't send EOF: %s", err)
70
-		}
71
-		close(stdinDone)
72
-	}()
73
-
74
-	select {
75
-	case err := <-receiveStdout:
76
-		if err != nil {
77
-			logrus.Debugf("Error receiveStdout: %s", err)
78
-			return err
79
-		}
80
-	case <-stdinDone:
81
-		if outputStream != nil || errorStream != nil {
82
-			select {
83
-			case err := <-receiveStdout:
84
-				if err != nil {
85
-					logrus.Debugf("Error receiveStdout: %s", err)
86
-					return err
87
-				}
88
-			case <-ctx.Done():
89
-			}
90
-		}
91
-	case <-ctx.Done():
92
-	}
93
-
94
-	return nil
95
-}
... ...
@@ -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,75 @@
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
+// SetRawTerminal sets raw mode on the input terminal
39
+func (i *InStream) SetRawTerminal() (err error) {
40
+	if os.Getenv("NORAW") != "" || !i.isTerminal {
41
+		return nil
42
+	}
43
+	i.state, err = term.SetRawTerminal(i.fd)
44
+	return err
45
+}
46
+
47
+// RestoreTerminal restores normal mode to the terminal
48
+func (i *InStream) RestoreTerminal() {
49
+	if i.state != nil {
50
+		term.RestoreTerminal(i.fd, i.state)
51
+	}
52
+}
53
+
54
+// CheckTty checks if we are trying to attach to a container tty
55
+// from a non-tty client input stream, and if so, returns an error.
56
+func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
57
+	// In order to attach to a container tty, input stream for the client must
58
+	// be a tty itself: redirecting or piping the client standard input is
59
+	// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
60
+	if ttyMode && attachStdin && !i.isTerminal {
61
+		eText := "the input device is not a TTY"
62
+		if runtime.GOOS == "windows" {
63
+			return errors.New(eText + ".  If you are using mintty, try prefixing the command with 'winpty'")
64
+		}
65
+		return errors.New(eText)
66
+	}
67
+	return nil
68
+}
69
+
70
+// NewInStream returns a new OutStream object from a Writer
71
+func NewInStream(in io.ReadCloser) *InStream {
72
+	fd, isTerminal := term.GetFdInfo(in)
73
+	return &InStream{in: in, fd: fd, isTerminal: isTerminal}
74
+}
0 75
new file mode 100644
... ...
@@ -0,0 +1,69 @@
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
+// SetRawTerminal sets raw mode on the output terminal
34
+func (o *OutStream) SetRawTerminal() (err error) {
35
+	if os.Getenv("NORAW") != "" || !o.isTerminal {
36
+		return nil
37
+	}
38
+	o.state, err = term.SetRawTerminalOutput(o.fd)
39
+	return err
40
+}
41
+
42
+// RestoreTerminal restores normal mode to the terminal
43
+func (o *OutStream) RestoreTerminal() {
44
+	if o.state != nil {
45
+		term.RestoreTerminal(o.fd, o.state)
46
+	}
47
+}
48
+
49
+// GetTtySize returns the height and width in characters of the tty
50
+func (o *OutStream) GetTtySize() (int, int) {
51
+	if !o.isTerminal {
52
+		return 0, 0
53
+	}
54
+	ws, err := term.GetWinsize(o.fd)
55
+	if err != nil {
56
+		logrus.Debugf("Error getting size: %s", err)
57
+		if ws == nil {
58
+			return 0, 0
59
+		}
60
+	}
61
+	return int(ws.Height), int(ws.Width)
62
+}
63
+
64
+// NewOutStream returns a new OutStream object from a Writer
65
+func NewOutStream(out io.Writer) *OutStream {
66
+	fd, isTerminal := term.GetFdInfo(out)
67
+	return &OutStream{out: out, fd: fd, isTerminal: isTerminal}
68
+}
... ...
@@ -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
 		}
... ...
@@ -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
... ...
@@ -5,110 +5,10 @@ import (
5 5
 	"io"
6 6
 	"io/ioutil"
7 7
 	"os"
8
-	gosignal "os/signal"
9 8
 	"path/filepath"
10
-	"runtime"
11 9
 	"strings"
12
-	"time"
13
-
14
-	"golang.org/x/net/context"
15
-
16
-	"github.com/Sirupsen/logrus"
17
-	"github.com/docker/docker/api/types"
18
-	"github.com/docker/docker/client"
19
-	"github.com/docker/docker/pkg/signal"
20
-	"github.com/docker/docker/pkg/term"
21 10
 )
22 11
 
23
-func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
24
-	height, width := cli.GetTtySize()
25
-	cli.ResizeTtyTo(ctx, id, height, width, isExec)
26
-}
27
-
28
-// ResizeTtyTo resizes tty to specific height and width
29
-// TODO: this can be unexported again once all container related commands move to package container
30
-func (cli *DockerCli) ResizeTtyTo(ctx context.Context, id string, height, width int, isExec bool) {
31
-	if height == 0 && width == 0 {
32
-		return
33
-	}
34
-
35
-	options := types.ResizeOptions{
36
-		Height: height,
37
-		Width:  width,
38
-	}
39
-
40
-	var err error
41
-	if isExec {
42
-		err = cli.client.ContainerExecResize(ctx, id, options)
43
-	} else {
44
-		err = cli.client.ContainerResize(ctx, id, options)
45
-	}
46
-
47
-	if err != nil {
48
-		logrus.Debugf("Error resize: %s", err)
49
-	}
50
-}
51
-
52
-// GetExecExitCode perform an inspect on the exec command. It returns
53
-// the running state and the exit code.
54
-func (cli *DockerCli) GetExecExitCode(ctx context.Context, execID string) (bool, int, error) {
55
-	resp, err := cli.client.ContainerExecInspect(ctx, execID)
56
-	if err != nil {
57
-		// If we can't connect, then the daemon probably died.
58
-		if err != client.ErrConnectionFailed {
59
-			return false, -1, err
60
-		}
61
-		return false, -1, nil
62
-	}
63
-
64
-	return resp.Running, resp.ExitCode, nil
65
-}
66
-
67
-// MonitorTtySize updates the container tty size when the terminal tty changes size
68
-func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool) error {
69
-	cli.resizeTty(ctx, id, isExec)
70
-
71
-	if runtime.GOOS == "windows" {
72
-		go func() {
73
-			prevH, prevW := cli.GetTtySize()
74
-			for {
75
-				time.Sleep(time.Millisecond * 250)
76
-				h, w := cli.GetTtySize()
77
-
78
-				if prevW != w || prevH != h {
79
-					cli.resizeTty(ctx, id, isExec)
80
-				}
81
-				prevH = h
82
-				prevW = w
83
-			}
84
-		}()
85
-	} else {
86
-		sigchan := make(chan os.Signal, 1)
87
-		gosignal.Notify(sigchan, signal.SIGWINCH)
88
-		go func() {
89
-			for range sigchan {
90
-				cli.resizeTty(ctx, id, isExec)
91
-			}
92
-		}()
93
-	}
94
-	return nil
95
-}
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 12
 // CopyToFile writes the content of the reader to the specified file
113 13
 func CopyToFile(outfile string, r io.Reader) error {
114 14
 	tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
... ...
@@ -134,37 +34,6 @@ func CopyToFile(outfile string, r io.Reader) error {
134 134
 	return nil
135 135
 }
136 136
 
137
-// ForwardAllSignals forwards signals to the container
138
-// TODO: this can be unexported again once all container commands are under
139
-// api/client/container
140
-func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal {
141
-	sigc := make(chan os.Signal, 128)
142
-	signal.CatchAll(sigc)
143
-	go func() {
144
-		for s := range sigc {
145
-			if s == signal.SIGCHLD || s == signal.SIGPIPE {
146
-				continue
147
-			}
148
-			var sig string
149
-			for sigStr, sigN := range signal.SignalMap {
150
-				if sigN == s {
151
-					sig = sigStr
152
-					break
153
-				}
154
-			}
155
-			if sig == "" {
156
-				fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
157
-				continue
158
-			}
159
-
160
-			if err := cli.client.ContainerKill(ctx, cid, sig); err != nil {
161
-				logrus.Debugf("Error sending signal: %s", err)
162
-			}
163
-		}
164
-	}()
165
-	return sigc
166
-}
167
-
168 137
 // capitalizeFirst capitalizes the first character of string
169 138
 func capitalizeFirst(s string) string {
170 139
 	switch l := len(s); l {
... ...
@@ -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
+}