| ... | ... |
@@ -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 |
+} |