Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -57,31 +57,12 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
| 57 | 57 |
return cli.configFile |
| 58 | 58 |
} |
| 59 | 59 |
|
| 60 |
-func (cli *DockerCli) setRawTerminal() error {
|
|
| 61 |
- if err := cli.in.setRawTerminal(); err != nil {
|
|
| 62 |
- return err |
|
| 63 |
- } |
|
| 64 |
- return cli.out.setRawTerminal() |
|
| 65 |
-} |
|
| 66 |
- |
|
| 67 |
-func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
|
| 68 |
- cli.in.restoreTerminal() |
|
| 69 |
- cli.out.restoreTerminal() |
|
| 70 |
- // WARNING: DO NOT REMOVE THE OS CHECK !!! |
|
| 71 |
- // For some reason this Close call blocks on darwin.. |
|
| 72 |
- // As the client exists right after, simply discard the close |
|
| 73 |
- // until we find a better solution. |
|
| 74 |
- if in != nil && runtime.GOOS != "darwin" {
|
|
| 75 |
- return in.Close() |
|
| 76 |
- } |
|
| 77 |
- return nil |
|
| 78 |
-} |
|
| 79 |
- |
|
| 80 | 60 |
// Initialize the dockerCli runs initialization that must happen after command |
| 81 | 61 |
// line flags are parsed. |
| 82 |
-func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
|
|
| 62 |
+func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
|
| 83 | 63 |
cli.configFile = LoadDefaultConfigFile(cli.err) |
| 84 | 64 |
|
| 65 |
+ var err error |
|
| 85 | 66 |
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile) |
| 86 | 67 |
if err != nil {
|
| 87 | 68 |
return err |
| ... | ... |
@@ -110,7 +110,7 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
|
| 110 | 110 |
logrus.Debugf("Error monitoring TTY size: %s", err)
|
| 111 | 111 |
} |
| 112 | 112 |
} |
| 113 |
- if err := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
|
|
| 113 |
+ if err := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
|
|
| 114 | 114 |
return err |
| 115 | 115 |
} |
| 116 | 116 |
|
| ... | ... |
@@ -10,9 +10,8 @@ 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 |
- "github.com/docker/docker/pkg/promise" |
|
| 14 | 13 |
apiclient "github.com/docker/docker/client" |
| 15 |
- "github.com/docker/engine-api/types" |
|
| 14 |
+ "github.com/docker/docker/pkg/promise" |
|
| 16 | 15 |
"github.com/spf13/cobra" |
| 17 | 16 |
) |
| 18 | 17 |
|
| ... | ... |
@@ -127,7 +126,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e |
| 127 | 127 |
} |
| 128 | 128 |
defer resp.Close() |
| 129 | 129 |
errCh = promise.Go(func() error {
|
| 130 |
- return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp) |
|
| 130 |
+ return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, in, out, stderr, resp) |
|
| 131 | 131 |
}) |
| 132 | 132 |
|
| 133 | 133 |
if execConfig.Tty && dockerCli.In().IsTerminal() {
|
| 134 | 134 |
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 |
+} |
| ... | ... |
@@ -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 |
} |
| ... | ... |
@@ -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 |
} |
| ... | ... |
@@ -9,13 +9,13 @@ import ( |
| 9 | 9 |
|
| 10 | 10 |
"github.com/Sirupsen/logrus" |
| 11 | 11 |
"github.com/docker/docker/api/client" |
| 12 |
+ "github.com/docker/docker/api/types" |
|
| 13 |
+ apiclient "github.com/docker/docker/client" |
|
| 12 | 14 |
"github.com/docker/docker/pkg/signal" |
| 13 |
- apiclient "github.com/docker/engine-api/client" |
|
| 14 |
- "github.com/docker/engine-api/types" |
|
| 15 | 15 |
"golang.org/x/net/context" |
| 16 | 16 |
) |
| 17 | 17 |
|
| 18 |
-// ResizeTtyTo resizes tty to specific height and width |
|
| 18 |
+// resizeTtyTo resizes tty to specific height and width |
|
| 19 | 19 |
func resizeTtyTo(ctx context.Context, client apiclient.ContainerAPIClient, id string, height, width int, isExec bool) {
|
| 20 | 20 |
if height == 0 && width == 0 {
|
| 21 | 21 |
return |
| 22 | 22 |
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 |
-} |
| ... | ... |
@@ -36,7 +36,8 @@ func (i *InStream) IsTerminal() bool {
|
| 36 | 36 |
return i.isTerminal |
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 |
-func (i *InStream) setRawTerminal() (err error) {
|
|
| 39 |
+// SetRawTerminal sets raw mode on the input terminal |
|
| 40 |
+func (i *InStream) SetRawTerminal() (err error) {
|
|
| 40 | 41 |
if os.Getenv("NORAW") != "" || !i.isTerminal {
|
| 41 | 42 |
return nil |
| 42 | 43 |
} |
| ... | ... |
@@ -44,7 +45,8 @@ func (i *InStream) setRawTerminal() (err error) {
|
| 44 | 44 |
return err |
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 |
-func (i *InStream) restoreTerminal() {
|
|
| 47 |
+// RestoreTerminal restores normal mode to the terminal |
|
| 48 |
+func (i *InStream) RestoreTerminal() {
|
|
| 48 | 49 |
if i.state != nil {
|
| 49 | 50 |
term.RestoreTerminal(i.fd, i.state) |
| 50 | 51 |
} |
| ... | ... |
@@ -31,7 +31,8 @@ func (o *OutStream) IsTerminal() bool {
|
| 31 | 31 |
return o.isTerminal |
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 |
-func (o *OutStream) setRawTerminal() (err error) {
|
|
| 34 |
+// SetRawTerminal sets raw mode on the output terminal |
|
| 35 |
+func (o *OutStream) SetRawTerminal() (err error) {
|
|
| 35 | 36 |
if os.Getenv("NORAW") != "" || !o.isTerminal {
|
| 36 | 37 |
return nil |
| 37 | 38 |
} |
| ... | ... |
@@ -39,7 +40,8 @@ func (o *OutStream) setRawTerminal() (err error) {
|
| 39 | 39 |
return err |
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 |
-func (o *OutStream) restoreTerminal() {
|
|
| 42 |
+// RestoreTerminal restores normal mode to the terminal |
|
| 43 |
+func (o *OutStream) RestoreTerminal() {
|
|
| 43 | 44 |
if o.state != nil {
|
| 44 | 45 |
term.RestoreTerminal(o.fd, o.state) |
| 45 | 46 |
} |