From a0ab33124a52853af611254cd73838e3d4407f51 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 29 Aug 2016 11:11:29 -0400 Subject: [PATCH 1/4] Extract stream output handling to a new type. Signed-off-by: Daniel Nephin --- api/client/cli.go | 53 +++++++-------------------- api/client/container/attach.go | 4 +- api/client/container/create.go | 4 +- api/client/container/export.go | 2 +- api/client/container/run.go | 4 +- api/client/container/start.go | 2 +- api/client/image/build.go | 4 +- api/client/image/import.go | 2 +- api/client/image/load.go | 4 +- api/client/image/push.go | 3 +- api/client/image/save.go | 2 +- api/client/out.go | 67 ++++++++++++++++++++++++++++++++++ api/client/trust.go | 6 +-- api/client/utils.go | 23 ++---------- pkg/jsonmessage/jsonmessage.go | 11 ++++++ 15 files changed, 113 insertions(+), 78 deletions(-) create mode 100644 api/client/out.go diff --git a/api/client/cli.go b/api/client/cli.go index 077d1650b9..cfaba28c9a 100644 --- a/api/client/cli.go +++ b/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 diff --git a/api/client/container/attach.go b/api/client/container/attach.go index fb3a715506..65e182c722 100644 --- a/api/client/container/attach.go +++ b/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 diff --git a/api/client/container/create.go b/api/client/container/create.go index 82c14ada67..d9eca183d1 100644 --- a/api/client/container/create.go +++ b/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) } diff --git a/api/client/container/export.go b/api/client/container/export.go index 8dbea9f7b0..c2fb176789 100644 --- a/api/client/container/export.go +++ b/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.") } diff --git a/api/client/container/run.go b/api/client/container/run.go index 154683819d..af9abdae22 100644 --- a/api/client/container/run.go +++ b/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) } diff --git a/api/client/container/start.go b/api/client/container/start.go index db0e948128..50aaa47896 100644 --- a/api/client/container/start.go +++ b/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) } diff --git a/api/client/image/build.go b/api/client/image/build.go index df89e7df1c..226c43dc06 100644 --- a/api/client/image/build.go +++ b/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 diff --git a/api/client/image/import.go b/api/client/image/import.go index 2ca775a4ff..d56a5bb668 100644 --- a/api/client/image/import.go +++ b/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) } diff --git a/api/client/image/load.go b/api/client/image/load.go index 240362d1e7..8905fa2819 100644 --- a/api/client/image/load.go +++ b/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) diff --git a/api/client/image/push.go b/api/client/image/push.go index 1526b2a26b..3eb41cf08d 100644 --- a/api/client/image/push.go +++ b/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) } diff --git a/api/client/image/save.go b/api/client/image/save.go index c5ea849a38..f7563880b7 100644 --- a/api/client/image/save.go +++ b/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.") } diff --git a/api/client/out.go b/api/client/out.go new file mode 100644 index 0000000000..ac33d005f3 --- /dev/null +++ b/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} +} diff --git a/api/client/trust.go b/api/client/trust.go index ae33528c2b..e86a141ece 100644 --- a/api/client/trust.go +++ b/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 diff --git a/api/client/utils.go b/api/client/utils.go index 28fbe85156..c20230dd64 100644 --- a/api/client/utils.go +++ b/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_") diff --git a/pkg/jsonmessage/jsonmessage.go b/pkg/jsonmessage/jsonmessage.go index 91b073b731..3c4a346e46 100644 --- a/pkg/jsonmessage/jsonmessage.go +++ b/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) +} From bec81075bf1ae07abcbf3f984922dedb10458cb2 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 29 Aug 2016 11:52:05 -0400 Subject: [PATCH 2/4] Extract input stream into a new type. Signed-off-by: Daniel Nephin --- api/client/cli.go | 79 +++++----------------------------- api/client/container/attach.go | 2 +- api/client/container/exec.go | 4 +- api/client/container/run.go | 2 +- api/client/in.go | 73 +++++++++++++++++++++++++++++++ api/client/registry.go | 10 ++--- 6 files changed, 93 insertions(+), 77 deletions(-) create mode 100644 api/client/in.go diff --git a/api/client/cli.go b/api/client/cli.go index cfaba28c9a..357504384f 100644 --- a/api/client/cli.go +++ b/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 diff --git a/api/client/container/attach.go b/api/client/container/attach.go index 65e182c722..12904f09b9 100644 --- a/api/client/container/attach.go +++ b/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 } diff --git a/api/client/container/exec.go b/api/client/container/exec.go index f20b9f366e..3a83e98689 100644 --- a/api/client/container/exec.go +++ b/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) } diff --git a/api/client/container/run.go b/api/client/container/run.go index af9abdae22..ac6747526d 100644 --- a/api/client/container/run.go +++ b/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 { diff --git a/api/client/in.go b/api/client/in.go new file mode 100644 index 0000000000..acbac50c34 --- /dev/null +++ b/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} +} diff --git a/api/client/registry.go b/api/client/registry.go index 2fcdf77c53..634a8b1b94 100644 --- a/api/client/registry.go +++ b/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") } From 31d8c27e704c9dd06ba3a0cccb7427054d9e6ccd Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 29 Aug 2016 12:23:15 -0400 Subject: [PATCH 3/4] Move container util methods to the container package. Signed-off-by: Daniel Nephin --- api/client/container/attach.go | 11 ++-- api/client/container/exec.go | 28 ++++++-- api/client/container/run.go | 4 +- api/client/container/start.go | 4 +- api/client/container/tty.go | 103 +++++++++++++++++++++++++++++ api/client/utils.go | 116 --------------------------------- 6 files changed, 136 insertions(+), 130 deletions(-) create mode 100644 api/client/container/tty.go diff --git a/api/client/container/attach.go b/api/client/container/attach.go index 12904f09b9..aea3eec334 100644 --- a/api/client/container/attach.go +++ b/api/client/container/attach.go @@ -46,8 +46,9 @@ func NewAttachCommand(dockerCli *client.DockerCli) *cobra.Command { func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error { ctx := context.Background() + client := dockerCli.Client() - c, err := dockerCli.Client().ContainerInspect(ctx, opts.container) + c, err := client.ContainerInspect(ctx, opts.container) if err != nil { return err } @@ -82,11 +83,11 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error { } if opts.proxy && !c.Config.Tty { - sigc := dockerCli.ForwardAllSignals(ctx, opts.container) + sigc := ForwardAllSignals(ctx, dockerCli, opts.container) defer signal.StopCatch(sigc) } - resp, errAttach := dockerCli.Client().ContainerAttach(ctx, opts.container, options) + resp, errAttach := client.ContainerAttach(ctx, opts.container, options) if errAttach != nil && errAttach != httputil.ErrPersistEOF { // ContainerAttach returns an ErrPersistEOF (connection closed) // means server met an error and put it in Hijacked connection @@ -101,11 +102,11 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error { // 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 // require the user to manually resize or hit enter. - dockerCli.ResizeTtyTo(ctx, opts.container, height+1, width+1, false) + resizeTtyTo(ctx, client, opts.container, height+1, width+1, false) // After the above resizing occurs, the call to MonitorTtySize below will handle resetting back // to the actual size. - if err := dockerCli.MonitorTtySize(ctx, opts.container, false); err != nil { + if err := MonitorTtySize(ctx, dockerCli, opts.container, false); err != nil { logrus.Debugf("Error monitoring TTY size: %s", err) } } diff --git a/api/client/container/exec.go b/api/client/container/exec.go index 3a83e98689..89338ae061 100644 --- a/api/client/container/exec.go +++ b/api/client/container/exec.go @@ -11,6 +11,8 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/pkg/promise" + apiclient "github.com/docker/docker/client" + "github.com/docker/engine-api/types" "github.com/spf13/cobra" ) @@ -66,8 +68,9 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys ctx := context.Background() + client := dockerCli.Client() - response, err := dockerCli.Client().ContainerExecCreate(ctx, container, *execConfig) + response, err := client.ContainerExecCreate(ctx, container, *execConfig) if err != nil { return err } @@ -89,7 +92,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e Tty: execConfig.Tty, } - if err := dockerCli.Client().ContainerExecStart(ctx, execID, execStartCheck); err != nil { + if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil { return err } // For now don't print this - wait for when we support exec wait() @@ -118,7 +121,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e } } - resp, err := dockerCli.Client().ContainerExecAttach(ctx, execID, *execConfig) + resp, err := client.ContainerExecAttach(ctx, execID, *execConfig) if err != nil { return err } @@ -128,7 +131,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e }) if execConfig.Tty && dockerCli.In().IsTerminal() { - if err := dockerCli.MonitorTtySize(ctx, execID, true); err != nil { + if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil { fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) } } @@ -139,7 +142,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e } var status int - if _, status, err = dockerCli.GetExecExitCode(ctx, execID); err != nil { + if _, status, err = getExecExitCode(ctx, client, execID); err != nil { return err } @@ -150,6 +153,21 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e return nil } +// getExecExitCode perform an inspect on the exec command. It returns +// the running state and the exit code. +func getExecExitCode(ctx context.Context, client apiclient.ContainerAPIClient, execID string) (bool, int, error) { + resp, err := client.ContainerExecInspect(ctx, execID) + if err != nil { + // If we can't connect, then the daemon probably died. + if err != apiclient.ErrConnectionFailed { + return false, -1, err + } + return false, -1, nil + } + + return resp.Running, resp.ExitCode, nil +} + // parseExec parses the specified args for the specified command and generates // an ExecConfig from it. func parseExec(opts *execOptions, container string, execCmd []string) (*types.ExecConfig, error) { diff --git a/api/client/container/run.go b/api/client/container/run.go index ac6747526d..5d44ff5ad5 100644 --- a/api/client/container/run.go +++ b/api/client/container/run.go @@ -146,7 +146,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, return runStartContainerErr(err) } if opts.sigProxy { - sigc := dockerCli.ForwardAllSignals(ctx, createResponse.ID) + sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID) defer signal.StopCatch(sigc) } var ( @@ -235,7 +235,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() { - if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil { + if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil { fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err) } } diff --git a/api/client/container/start.go b/api/client/container/start.go index 50aaa47896..dbd4bca056 100644 --- a/api/client/container/start.go +++ b/api/client/container/start.go @@ -64,7 +64,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error { // We always use c.ID instead of container to maintain consistency during `docker start` if !c.Config.Tty { - sigc := dockerCli.ForwardAllSignals(ctx, c.ID) + sigc := ForwardAllSignals(ctx, dockerCli, c.ID) defer signal.StopCatch(sigc) } @@ -119,7 +119,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error { // 5. Wait for attachment to break. if c.Config.Tty && dockerCli.Out().IsTerminal() { - if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil { + if err := MonitorTtySize(ctx, dockerCli, c.ID, false); err != nil { fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) } } diff --git a/api/client/container/tty.go b/api/client/container/tty.go new file mode 100644 index 0000000000..d87be0dcda --- /dev/null +++ b/api/client/container/tty.go @@ -0,0 +1,103 @@ +package container + +import ( + "fmt" + "os" + gosignal "os/signal" + "runtime" + "time" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/client" + "github.com/docker/docker/pkg/signal" + apiclient "github.com/docker/engine-api/client" + "github.com/docker/engine-api/types" + "golang.org/x/net/context" +) + +// ResizeTtyTo resizes tty to specific height and width +func resizeTtyTo(ctx context.Context, client apiclient.ContainerAPIClient, id string, height, width int, isExec bool) { + if height == 0 && width == 0 { + return + } + + options := types.ResizeOptions{ + Height: height, + Width: width, + } + + var err error + if isExec { + err = client.ContainerExecResize(ctx, id, options) + } else { + err = client.ContainerResize(ctx, id, options) + } + + if err != nil { + logrus.Debugf("Error resize: %s", err) + } +} + +// MonitorTtySize updates the container tty size when the terminal tty changes size +func MonitorTtySize(ctx context.Context, cli *client.DockerCli, id string, isExec bool) error { + resizeTty := func() { + height, width := cli.Out().GetTtySize() + resizeTtyTo(ctx, cli.Client(), id, height, width, isExec) + } + + resizeTty() + + if runtime.GOOS == "windows" { + go func() { + prevH, prevW := cli.Out().GetTtySize() + for { + time.Sleep(time.Millisecond * 250) + h, w := cli.Out().GetTtySize() + + if prevW != w || prevH != h { + resizeTty() + } + prevH = h + prevW = w + } + }() + } else { + sigchan := make(chan os.Signal, 1) + gosignal.Notify(sigchan, signal.SIGWINCH) + go func() { + for range sigchan { + resizeTty() + } + }() + } + return nil +} + +// ForwardAllSignals forwards signals to the container +func ForwardAllSignals(ctx context.Context, cli *client.DockerCli, cid string) chan os.Signal { + sigc := make(chan os.Signal, 128) + signal.CatchAll(sigc) + go func() { + for s := range sigc { + if s == signal.SIGCHLD || s == signal.SIGPIPE { + continue + } + var sig string + for sigStr, sigN := range signal.SignalMap { + if sigN == s { + sig = sigStr + break + } + } + if sig == "" { + fmt.Fprintf(cli.Err(), "Unsupported signal: %v. Discarding.\n", s) + continue + } + + if err := cli.Client().ContainerKill(ctx, cid, sig); err != nil { + logrus.Debugf("Error sending signal: %s", err) + } + } + }() + return sigc +} diff --git a/api/client/utils.go b/api/client/utils.go index c20230dd64..f76f536853 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -5,95 +5,10 @@ import ( "io" "io/ioutil" "os" - gosignal "os/signal" "path/filepath" - "runtime" "strings" - "time" - - "golang.org/x/net/context" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/api/types" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/signal" - "github.com/docker/engine-api/types" ) -func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) { - height, width := cli.Out().GetTtySize() - cli.ResizeTtyTo(ctx, id, height, width, isExec) -} - -// ResizeTtyTo resizes tty to specific height and width -// TODO: this can be unexported again once all container related commands move to package container -func (cli *DockerCli) ResizeTtyTo(ctx context.Context, id string, height, width int, isExec bool) { - if height == 0 && width == 0 { - return - } - - options := types.ResizeOptions{ - Height: height, - Width: width, - } - - var err error - if isExec { - err = cli.client.ContainerExecResize(ctx, id, options) - } else { - err = cli.client.ContainerResize(ctx, id, options) - } - - if err != nil { - logrus.Debugf("Error resize: %s", err) - } -} - -// GetExecExitCode perform an inspect on the exec command. It returns -// the running state and the exit code. -func (cli *DockerCli) GetExecExitCode(ctx context.Context, execID string) (bool, int, error) { - resp, err := cli.client.ContainerExecInspect(ctx, execID) - if err != nil { - // If we can't connect, then the daemon probably died. - if err != client.ErrConnectionFailed { - return false, -1, err - } - return false, -1, nil - } - - return resp.Running, resp.ExitCode, nil -} - -// MonitorTtySize updates the container tty size when the terminal tty changes size -func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool) error { - cli.resizeTty(ctx, id, isExec) - - if runtime.GOOS == "windows" { - go func() { - prevH, prevW := cli.Out().GetTtySize() - for { - time.Sleep(time.Millisecond * 250) - h, w := cli.Out().GetTtySize() - - if prevW != w || prevH != h { - cli.resizeTty(ctx, id, isExec) - } - prevH = h - prevW = w - } - }() - } else { - sigchan := make(chan os.Signal, 1) - gosignal.Notify(sigchan, signal.SIGWINCH) - go func() { - for range sigchan { - cli.resizeTty(ctx, id, isExec) - } - }() - } - return nil -} - // 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_") @@ -119,37 +34,6 @@ func CopyToFile(outfile string, r io.Reader) error { return nil } -// ForwardAllSignals forwards signals to the container -// TODO: this can be unexported again once all container commands are under -// api/client/container -func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal { - sigc := make(chan os.Signal, 128) - signal.CatchAll(sigc) - go func() { - for s := range sigc { - if s == signal.SIGCHLD || s == signal.SIGPIPE { - continue - } - var sig string - for sigStr, sigN := range signal.SignalMap { - if sigN == s { - sig = sigStr - break - } - } - if sig == "" { - fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s) - continue - } - - if err := cli.client.ContainerKill(ctx, cid, sig); err != nil { - logrus.Debugf("Error sending signal: %s", err) - } - } - }() - return sigc -} - // capitalizeFirst capitalizes the first character of string func capitalizeFirst(s string) string { switch l := len(s); l { From 6af6a3a5383642d2d7c3784f0f4e562dc324819a Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 29 Aug 2016 13:36:29 -0400 Subject: [PATCH 4/4] Move holdHijackConnection to the container package. Signed-off-by: Daniel Nephin --- api/client/cli.go | 23 ++------------- api/client/container/attach.go | 2 +- api/client/container/exec.go | 5 ++-- api/client/{ => container}/hijack.go | 44 ++++++++++++++++++++++------ api/client/container/run.go | 2 +- api/client/container/start.go | 2 +- api/client/container/tty.go | 6 ++-- api/client/in.go | 6 ++-- api/client/out.go | 6 ++-- 9 files changed, 53 insertions(+), 43 deletions(-) rename api/client/{ => container}/hijack.go (65%) diff --git a/api/client/cli.go b/api/client/cli.go index 357504384f..e25ea95d6f 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -57,31 +57,12 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile { return cli.configFile } -func (cli *DockerCli) setRawTerminal() error { - if err := cli.in.setRawTerminal(); err != nil { - return err - } - return cli.out.setRawTerminal() -} - -func (cli *DockerCli) restoreTerminal(in io.Closer) error { - cli.in.restoreTerminal() - 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 - // until we find a better solution. - if in != nil && runtime.GOOS != "darwin" { - return in.Close() - } - return nil -} - // Initialize the dockerCli runs initialization that must happen after command // line flags are parsed. -func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) { +func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { cli.configFile = LoadDefaultConfigFile(cli.err) + var err error cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile) if err != nil { return err diff --git a/api/client/container/attach.go b/api/client/container/attach.go index aea3eec334..b0be3df90a 100644 --- a/api/client/container/attach.go +++ b/api/client/container/attach.go @@ -110,7 +110,7 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error { logrus.Debugf("Error monitoring TTY size: %s", err) } } - if err := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil { + if err := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil { return err } diff --git a/api/client/container/exec.go b/api/client/container/exec.go index 89338ae061..2913e63723 100644 --- a/api/client/container/exec.go +++ b/api/client/container/exec.go @@ -10,9 +10,8 @@ import ( "github.com/docker/docker/api/client" "github.com/docker/docker/api/types" "github.com/docker/docker/cli" - "github.com/docker/docker/pkg/promise" apiclient "github.com/docker/docker/client" - "github.com/docker/engine-api/types" + "github.com/docker/docker/pkg/promise" "github.com/spf13/cobra" ) @@ -127,7 +126,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e } defer resp.Close() errCh = promise.Go(func() error { - return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp) + return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, in, out, stderr, resp) }) if execConfig.Tty && dockerCli.In().IsTerminal() { diff --git a/api/client/hijack.go b/api/client/container/hijack.go similarity index 65% rename from api/client/hijack.go rename to api/client/container/hijack.go index c7f7c3a0c0..5a1b4119d5 100644 --- a/api/client/hijack.go +++ b/api/client/container/hijack.go @@ -1,30 +1,36 @@ -package client +package container import ( "io" + "runtime" "sync" - "golang.org/x/net/context" - "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/client" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stdcopy" + "golang.org/x/net/context" ) -// HoldHijackedConnection handles copying input to and output from streams to the +type streams interface { + In() *client.InStream + Out() *client.OutStream +} + +// holdHijackedConnection handles copying input to and output from streams to the // connection -func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { +func holdHijackedConnection(ctx context.Context, streams streams, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { var ( err error restoreOnce sync.Once ) if inputStream != nil && tty { - if err := cli.setRawTerminal(); err != nil { + if err := setRawTerminal(streams); err != nil { return err } defer func() { restoreOnce.Do(func() { - cli.restoreTerminal(inputStream) + restoreTerminal(streams, inputStream) }) }() } @@ -39,7 +45,7 @@ func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inpu // so any following print messages will be in normal type. if inputStream != nil { restoreOnce.Do(func() { - cli.restoreTerminal(inputStream) + restoreTerminal(streams, inputStream) }) } } else { @@ -59,7 +65,7 @@ func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inpu // so any following print messages will be in normal type. if tty { restoreOnce.Do(func() { - cli.restoreTerminal(inputStream) + restoreTerminal(streams, inputStream) }) } logrus.Debug("[hijack] End of stdin") @@ -93,3 +99,23 @@ func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inpu return nil } + +func setRawTerminal(streams streams) error { + if err := streams.In().SetRawTerminal(); err != nil { + return err + } + return streams.Out().SetRawTerminal() +} + +func restoreTerminal(streams streams, in io.Closer) error { + streams.In().RestoreTerminal() + streams.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 + // until we find a better solution. + if in != nil && runtime.GOOS != "darwin" { + return in.Close() + } + return nil +} diff --git a/api/client/container/run.go b/api/client/container/run.go index 5d44ff5ad5..af1a898234 100644 --- a/api/client/container/run.go +++ b/api/client/container/run.go @@ -203,7 +203,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, defer resp.Close() errCh = promise.Go(func() error { - errHijack := dockerCli.HoldHijackedConnection(ctx, config.Tty, in, out, cerr, resp) + errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp) if errHijack == nil { return errAttach } diff --git a/api/client/container/start.go b/api/client/container/start.go index dbd4bca056..c3b4a3167b 100644 --- a/api/client/container/start.go +++ b/api/client/container/start.go @@ -95,7 +95,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error { } defer resp.Close() cErr := promise.Go(func() error { - errHijack := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp) + errHijack := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp) if errHijack == nil { return errAttach } diff --git a/api/client/container/tty.go b/api/client/container/tty.go index d87be0dcda..7af732750b 100644 --- a/api/client/container/tty.go +++ b/api/client/container/tty.go @@ -9,13 +9,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/types" + apiclient "github.com/docker/docker/client" "github.com/docker/docker/pkg/signal" - apiclient "github.com/docker/engine-api/client" - "github.com/docker/engine-api/types" "golang.org/x/net/context" ) -// ResizeTtyTo resizes tty to specific height and width +// resizeTtyTo resizes tty to specific height and width func resizeTtyTo(ctx context.Context, client apiclient.ContainerAPIClient, id string, height, width int, isExec bool) { if height == 0 && width == 0 { return diff --git a/api/client/in.go b/api/client/in.go index acbac50c34..f0ce628dec 100644 --- a/api/client/in.go +++ b/api/client/in.go @@ -36,7 +36,8 @@ func (i *InStream) IsTerminal() bool { return i.isTerminal } -func (i *InStream) setRawTerminal() (err error) { +// SetRawTerminal sets raw mode on the input terminal +func (i *InStream) SetRawTerminal() (err error) { if os.Getenv("NORAW") != "" || !i.isTerminal { return nil } @@ -44,7 +45,8 @@ func (i *InStream) setRawTerminal() (err error) { return err } -func (i *InStream) restoreTerminal() { +// RestoreTerminal restores normal mode to the terminal +func (i *InStream) RestoreTerminal() { if i.state != nil { term.RestoreTerminal(i.fd, i.state) } diff --git a/api/client/out.go b/api/client/out.go index ac33d005f3..e8b38e4b3f 100644 --- a/api/client/out.go +++ b/api/client/out.go @@ -31,7 +31,8 @@ func (o *OutStream) IsTerminal() bool { return o.isTerminal } -func (o *OutStream) setRawTerminal() (err error) { +// SetRawTerminal sets raw mode on the output terminal +func (o *OutStream) SetRawTerminal() (err error) { if os.Getenv("NORAW") != "" || !o.isTerminal { return nil } @@ -39,7 +40,8 @@ func (o *OutStream) setRawTerminal() (err error) { return err } -func (o *OutStream) restoreTerminal() { +// RestoreTerminal restores normal mode to the terminal +func (o *OutStream) RestoreTerminal() { if o.state != nil { term.RestoreTerminal(o.fd, o.state) }