Browse Source

Extract stream output handling to a new type.

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

+ 13 - 40
api/client/cli.go

@@ -31,27 +31,24 @@ type DockerCli struct {
 	// 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
-	// out holds the output stream (io.Writer) for the client.
-	out io.Writer
 	// 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
-	// outFd holds file descriptor of the client's STDOUT (if valid).
-	outFd uintptr
 	// isTerminalIn indicates whether the client's STDIN is a TTY
+	// TODO: remove
 	isTerminalIn bool
-	// isTerminalOut indicates whether the client's STDOUT is a TTY
-	isTerminalOut 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
-	// outState holds the terminal output state
-	outState *term.State
+	out     *OutStream
 }
 
 // Client returns the APIClient
@@ -60,7 +57,7 @@ func (cli *DockerCli) Client() client.APIClient {
 }
 
 // Out returns the writer used for stdout
-func (cli *DockerCli) Out() io.Writer {
+func (cli *DockerCli) Out() *OutStream {
 	return cli.out
 }
 
@@ -80,20 +77,11 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
 }
 
 // IsTerminalIn returns true if the clients stdin is a TTY
+// TODO: remove
 func (cli *DockerCli) IsTerminalIn() bool {
 	return cli.isTerminalIn
 }
 
-// IsTerminalOut returns true if the clients stdout is a TTY
-func (cli *DockerCli) IsTerminalOut() bool {
-	return cli.isTerminalOut
-}
-
-// OutFd returns the fd for the stdout stream
-func (cli *DockerCli) OutFd() uintptr {
-	return cli.outFd
-}
-
 // 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 {
@@ -119,12 +107,8 @@ func (cli *DockerCli) setRawTerminal() error {
 			}
 			cli.inState = state
 		}
-		if cli.isTerminalOut {
-			state, err := term.SetRawTerminalOutput(cli.outFd)
-			if err != nil {
-				return err
-			}
-			cli.outState = state
+		if err := cli.out.setRawTerminal(); err != nil {
+			return err
 		}
 	}
 	return nil
@@ -134,9 +118,7 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
 	if cli.inState != nil {
 		term.RestoreTerminal(cli.inFd, cli.inState)
 	}
-	if cli.outState != nil {
-		term.RestoreTerminal(cli.outFd, cli.outState)
-	}
+	cli.out.restoreTerminal()
 	// WARNING: DO NOT REMOVE THE OS CHECK !!!
 	// For some reason this Close call blocks on darwin..
 	// As the client exists right after, simply discard the close
@@ -149,22 +131,17 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
 
 // Initialize the dockerCli runs initialization that must happen after command
 // line flags are parsed.
-func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
+func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
 	cli.configFile = LoadDefaultConfigFile(cli.err)
 
-	client, err := NewAPIClientFromFlags(opts.Common, cli.configFile)
+	cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
 	if err != nil {
 		return err
 	}
 
-	cli.client = client
-
 	if cli.in != nil {
 		cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
 	}
-	if cli.out != nil {
-		cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
-	}
 
 	if opts.Common.TrustKey == "" {
 		cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
@@ -177,11 +154,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) 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: out,
-		err: err,
-	}
+	return &DockerCli{in: in, out: NewOutStream(out), err: err}
 }
 
 // LoadDefaultConfigFile attempts to load the default config file and returns

+ 2 - 2
api/client/container/attach.go

@@ -95,8 +95,8 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
 	}
 	defer resp.Close()
 
-	if c.Config.Tty && dockerCli.IsTerminalOut() {
-		height, width := dockerCli.GetTtySize()
+	if c.Config.Tty && dockerCli.Out().IsTerminal() {
+		height, width := dockerCli.Out().GetTtySize()
 		// To handle the case where a user repeatedly attaches/detaches without resizing their
 		// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
 		// resize it, then go back to normal. Without this, every attach after the first will

+ 2 - 2
api/client/container/create.go

@@ -103,8 +103,8 @@ func pullImage(ctx context.Context, dockerCli *client.DockerCli, image string, o
 	return jsonmessage.DisplayJSONMessagesStream(
 		responseBody,
 		out,
-		dockerCli.OutFd(),
-		dockerCli.IsTerminalOut(),
+		dockerCli.Out().FD(),
+		dockerCli.Out().IsTerminal(),
 		nil)
 }
 

+ 1 - 1
api/client/container/export.go

@@ -38,7 +38,7 @@ func NewExportCommand(dockerCli *client.DockerCli) *cobra.Command {
 }
 
 func runExport(dockerCli *client.DockerCli, opts exportOptions) error {
-	if opts.output == "" && dockerCli.IsTerminalOut() {
+	if opts.output == "" && dockerCli.Out().IsTerminal() {
 		return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
 	}
 

+ 2 - 2
api/client/container/run.go

@@ -135,7 +135,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
 	// a far better user experience rather than relying on subsequent resizes
 	// to cause things to catch up.
 	if runtime.GOOS == "windows" {
-		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
+		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
 	}
 
 	ctx, cancelFun := context.WithCancel(context.Background())
@@ -234,7 +234,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
 		return runStartContainerErr(err)
 	}
 
-	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() {
+	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
 		if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
 			fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
 		}

+ 1 - 1
api/client/container/start.go

@@ -118,7 +118,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
 		}
 
 		// 5. Wait for attachment to break.
-		if c.Config.Tty && dockerCli.IsTerminalOut() {
+		if c.Config.Tty && dockerCli.Out().IsTerminal() {
 			if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil {
 				fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
 			}

+ 2 - 2
api/client/image/build.go

@@ -227,7 +227,7 @@ func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
 
 	// Setup an upload progress bar
 	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
-	if !dockerCli.IsTerminalOut() {
+	if !dockerCli.Out().IsTerminal() {
 		progressOutput = &lastProgressOutput{output: progressOutput}
 	}
 
@@ -293,7 +293,7 @@ func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
 	}
 	defer response.Body.Close()
 
-	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
+	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), nil)
 	if err != nil {
 		if jerr, ok := err.(*jsonmessage.JSONError); ok {
 			// If no error code is set, default to 1

+ 1 - 1
api/client/image/import.go

@@ -84,5 +84,5 @@ func runImport(dockerCli *client.DockerCli, opts importOptions) error {
 	}
 	defer responseBody.Close()
 
-	return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
+	return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
 }

+ 2 - 2
api/client/image/load.go

@@ -49,7 +49,7 @@ func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
 		defer file.Close()
 		input = file
 	}
-	if !dockerCli.IsTerminalOut() {
+	if !dockerCli.Out().IsTerminal() {
 		opts.quiet = true
 	}
 	response, err := dockerCli.Client().ImageLoad(context.Background(), input, opts.quiet)
@@ -59,7 +59,7 @@ func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
 	defer response.Body.Close()
 
 	if response.Body != nil && response.JSON {
-		return jsonmessage.DisplayJSONMessagesStream(response.Body, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
+		return jsonmessage.DisplayJSONMessagesToStream(response.Body, dockerCli.Out(), nil)
 	}
 
 	_, err = io.Copy(dockerCli.Out(), response.Body)

+ 1 - 2
api/client/image/push.go

@@ -57,6 +57,5 @@ func runPush(dockerCli *client.DockerCli, remote string) error {
 	}
 
 	defer responseBody.Close()
-
-	return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
+	return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
 }

+ 1 - 1
api/client/image/save.go

@@ -38,7 +38,7 @@ func NewSaveCommand(dockerCli *client.DockerCli) *cobra.Command {
 }
 
 func runSave(dockerCli *client.DockerCli, opts saveOptions) error {
-	if opts.output == "" && dockerCli.IsTerminalOut() {
+	if opts.output == "" && dockerCli.Out().IsTerminal() {
 		return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
 	}
 

+ 67 - 0
api/client/out.go

@@ -0,0 +1,67 @@
+package client
+
+import (
+	"io"
+	"os"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/term"
+)
+
+// OutStream is an output stream used by the DockerCli to write normal program
+// output.
+type OutStream struct {
+	out        io.Writer
+	fd         uintptr
+	isTerminal bool
+	state      *term.State
+}
+
+func (o *OutStream) Write(p []byte) (int, error) {
+	return o.out.Write(p)
+}
+
+// FD returns the file descriptor number for this stream
+func (o *OutStream) FD() uintptr {
+	return o.fd
+}
+
+// IsTerminal returns true if this stream is connected to a terminal
+func (o *OutStream) IsTerminal() bool {
+	return o.isTerminal
+}
+
+func (o *OutStream) setRawTerminal() (err error) {
+	if os.Getenv("NORAW") != "" || !o.isTerminal {
+		return nil
+	}
+	o.state, err = term.SetRawTerminalOutput(o.fd)
+	return err
+}
+
+func (o *OutStream) restoreTerminal() {
+	if o.state != nil {
+		term.RestoreTerminal(o.fd, o.state)
+	}
+}
+
+// GetTtySize returns the height and width in characters of the tty
+func (o *OutStream) GetTtySize() (int, int) {
+	if !o.isTerminal {
+		return 0, 0
+	}
+	ws, err := term.GetWinsize(o.fd)
+	if err != nil {
+		logrus.Debugf("Error getting size: %s", err)
+		if ws == nil {
+			return 0, 0
+		}
+	}
+	return int(ws.Height), int(ws.Width)
+}
+
+// NewOutStream returns a new OutStream object from a Writer
+func NewOutStream(out io.Writer) *OutStream {
+	fd, isTerminal := term.GetFdInfo(out)
+	return &OutStream{out: out, fd: fd, isTerminal: isTerminal}
+}

+ 3 - 3
api/client/trust.go

@@ -440,14 +440,14 @@ func (cli *DockerCli) TrustedPush(ctx context.Context, repoInfo *registry.Reposi
 	// We want trust signatures to always take an explicit tag,
 	// otherwise it will act as an untrusted push.
 	if tag == "" {
-		if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil); err != nil {
+		if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil); err != nil {
 			return err
 		}
 		fmt.Fprintln(cli.out, "No tag specified, skipping trust metadata push")
 		return nil
 	}
 
-	if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget); err != nil {
+	if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), handleTarget); err != nil {
 		return err
 	}
 
@@ -580,7 +580,7 @@ func (cli *DockerCli) ImagePullPrivileged(ctx context.Context, authConfig types.
 	}
 	defer responseBody.Close()
 
-	return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
+	return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
 }
 
 // ImagePushPrivileged push the image

+ 4 - 19
api/client/utils.go

@@ -17,11 +17,11 @@ import (
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/pkg/signal"
-	"github.com/docker/docker/pkg/term"
+	"github.com/docker/engine-api/types"
 )
 
 func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
-	height, width := cli.GetTtySize()
+	height, width := cli.Out().GetTtySize()
 	cli.ResizeTtyTo(ctx, id, height, width, isExec)
 }
 
@@ -70,10 +70,10 @@ func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool
 
 	if runtime.GOOS == "windows" {
 		go func() {
-			prevH, prevW := cli.GetTtySize()
+			prevH, prevW := cli.Out().GetTtySize()
 			for {
 				time.Sleep(time.Millisecond * 250)
-				h, w := cli.GetTtySize()
+				h, w := cli.Out().GetTtySize()
 
 				if prevW != w || prevH != h {
 					cli.resizeTty(ctx, id, isExec)
@@ -94,21 +94,6 @@ func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool
 	return nil
 }
 
-// GetTtySize returns the height and width in characters of the tty
-func (cli *DockerCli) GetTtySize() (int, int) {
-	if !cli.isTerminalOut {
-		return 0, 0
-	}
-	ws, err := term.GetWinsize(cli.outFd)
-	if err != nil {
-		logrus.Debugf("Error getting size: %s", err)
-		if ws == nil {
-			return 0, 0
-		}
-	}
-	return int(ws.Height), int(ws.Width)
-}
-
 // CopyToFile writes the content of the reader to the specified file
 func CopyToFile(outfile string, r io.Reader) error {
 	tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")

+ 11 - 0
pkg/jsonmessage/jsonmessage.go

@@ -219,3 +219,14 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
 	}
 	return nil
 }
+
+type stream interface {
+	io.Writer
+	FD() uintptr
+	IsTerminal() bool
+}
+
+// DisplayJSONMessagesToStream prints json messages to the output stream
+func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
+	return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
+}