From 38591f20d07795aaef45d400df89ca12f29c603b Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Tue, 16 May 2017 18:31:04 -0700 Subject: [PATCH] Update CLI package with containerWait changes The docker/client package was updated to support the updated Container Wait API functionality. The run and start commands have been updated to use the new wait features. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- cli/command/container/attach.go | 2 +- cli/command/container/exec.go | 2 +- cli/command/container/hijack.go | 27 +++++++++++++++++++++++++-- cli/command/container/run.go | 8 +++++++- cli/command/container/start.go | 7 ++++++- cli/command/container/utils.go | 32 +++++++++++++++++++++++++++++++- cli/command/container/wait.go | 11 +++++++---- 7 files changed, 78 insertions(+), 11 deletions(-) diff --git a/cli/command/container/attach.go b/cli/command/container/attach.go index f86b33a8ca..16c8897b2a 100644 --- a/cli/command/container/attach.go +++ b/cli/command/container/attach.go @@ -109,7 +109,7 @@ func runAttach(dockerCli *command.DockerCli, opts *attachOptions) error { logrus.Debugf("Error monitoring TTY size: %s", err) } } - if err := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil { + if err := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, options.DetachKeys, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil { return err } diff --git a/cli/command/container/exec.go b/cli/command/container/exec.go index f11b9a7ccc..f3929a1b0a 100644 --- a/cli/command/container/exec.go +++ b/cli/command/container/exec.go @@ -146,7 +146,7 @@ func runExec(dockerCli *command.DockerCli, options *execOptions, container strin } defer resp.Close() errCh = promise.Go(func() error { - return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, in, out, stderr, resp) + return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, execConfig.DetachKeys, in, out, stderr, resp) }) if execConfig.Tty && dockerCli.In().IsTerminal() { diff --git a/cli/command/container/hijack.go b/cli/command/container/hijack.go index 952e31998d..6fd0dc7cad 100644 --- a/cli/command/container/hijack.go +++ b/cli/command/container/hijack.go @@ -8,14 +8,19 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/docker/pkg/term" "golang.org/x/net/context" ) +// The default escape key sequence: ctrl-p, ctrl-q +var defaultEscapeKeys = []byte{16, 17} + // holdHijackedConnection handles copying input to and output from streams to the // connection // nolint: gocyclo -func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { +func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bool, detachKeys string, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { var ( err error restoreOnce sync.Once @@ -29,6 +34,15 @@ func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bo restoreTerminal(streams, inputStream) }) }() + + // Wrap the input to detect detach control sequence. + // Use default detach sequence if an invalid sequence is given. + escapeKeys, err := term.ToBytes(detachKeys) + if len(escapeKeys) == 0 || err != nil { + escapeKeys = defaultEscapeKeys + } + + inputStream = ioutils.NewReadCloserWrapper(term.NewEscapeProxy(inputStream, escapeKeys), inputStream.Close) } receiveStdout := make(chan error, 1) @@ -54,9 +68,10 @@ func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bo } stdinDone := make(chan struct{}) + detachedC := make(chan term.EscapeError) go func() { if inputStream != nil { - io.Copy(resp.Conn, inputStream) + _, inputErr := io.Copy(resp.Conn, inputStream) // we should restore the terminal as soon as possible once connection end // so any following print messages will be in normal type. if tty { @@ -65,6 +80,11 @@ func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bo }) } logrus.Debug("[hijack] End of stdin") + + if detached, ok := inputErr.(term.EscapeError); ok { + detachedC <- detached + return + } } if err := resp.CloseWrite(); err != nil { @@ -90,6 +110,9 @@ func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bo case <-ctx.Done(): } } + case err := <-detachedC: + // Got a detach key sequence. + return err case <-ctx.Done(): } diff --git a/cli/command/container/run.go b/cli/command/container/run.go index c1f5541899..5cc92ce21f 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/term" "github.com/docker/libnetwork/resolvconf/dns" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -199,6 +200,11 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain if errCh != nil { if err := <-errCh; err != nil { + if _, ok := err.(term.EscapeError); ok { + // The user entered the detach escape sequence. + return nil + } + logrus.Debugf("Error hijack: %s", err) return err } @@ -261,7 +267,7 @@ func attachContainer( } *errCh = promise.Go(func() error { - if errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp); errHijack != nil { + if errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, options.DetachKeys, in, out, cerr, resp); errHijack != nil { return errHijack } return errAttach diff --git a/cli/command/container/start.go b/cli/command/container/start.go index efc83e78f0..54ec8344f7 100644 --- a/cli/command/container/start.go +++ b/cli/command/container/start.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/term" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -103,7 +104,7 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error { } defer resp.Close() cErr := promise.Go(func() error { - errHijack := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp) + errHijack := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, options.DetachKeys, in, dockerCli.Out(), dockerCli.Err(), resp) if errHijack == nil { return errAttach } @@ -136,6 +137,10 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error { } } if attchErr := <-cErr; attchErr != nil { + if _, ok := err.(term.EscapeError); ok { + // The user entered the detach escape sequence. + return nil + } return attchErr } diff --git a/cli/command/container/utils.go b/cli/command/container/utils.go index cf1cad95b2..fbb38e8557 100644 --- a/cli/command/container/utils.go +++ b/cli/command/container/utils.go @@ -6,6 +6,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/versions" @@ -13,12 +14,41 @@ import ( "golang.org/x/net/context" ) -func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) chan int { +func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) <-chan int { if len(containerID) == 0 { // containerID can never be empty panic("Internal Error: waitExitOrRemoved needs a containerID as parameter") } + // Older versions used the Events API, and even older versions did not + // support server-side removal. This legacyWaitExitOrRemoved method + // preserves that old behavior and any issues it may have. + if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") { + return legacyWaitExitOrRemoved(ctx, dockerCli, containerID, waitRemove) + } + + condition := container.WaitConditionNextExit + if waitRemove { + condition = container.WaitConditionRemoved + } + + resultC, errC := dockerCli.Client().ContainerWait(ctx, containerID, condition) + + statusC := make(chan int) + go func() { + select { + case result := <-resultC: + statusC <- int(result.StatusCode) + case err := <-errC: + logrus.Errorf("error waiting for container: %v", err) + statusC <- 125 + } + }() + + return statusC +} + +func legacyWaitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) <-chan int { var removeErr error statusChan := make(chan int) exitCode := 125 diff --git a/cli/command/container/wait.go b/cli/command/container/wait.go index 1deeb59fa2..f582469c3e 100644 --- a/cli/command/container/wait.go +++ b/cli/command/container/wait.go @@ -28,6 +28,7 @@ func NewWaitCommand(dockerCli *command.DockerCli) *cobra.Command { return runWait(dockerCli, &opts) }, } + return cmd } @@ -36,12 +37,14 @@ func runWait(dockerCli *command.DockerCli, opts *waitOptions) error { var errs []string for _, container := range opts.containers { - status, err := dockerCli.Client().ContainerWait(ctx, container) - if err != nil { + resultC, errC := dockerCli.Client().ContainerWait(ctx, container, "") + + select { + case result := <-resultC: + fmt.Fprintf(dockerCli.Out(), "%d\n", result.StatusCode) + case err := <-errC: errs = append(errs, err.Error()) - continue } - fmt.Fprintf(dockerCli.Out(), "%d\n", status) } if len(errs) > 0 { return errors.New(strings.Join(errs, "\n"))