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 <josh.hawn@docker.com> (github: jlhawn)
This commit is contained in:
Josh Hawn 2017-05-16 18:31:04 -07:00
parent 4f9ac4899f
commit 38591f20d0
7 changed files with 78 additions and 11 deletions

View File

@ -109,7 +109,7 @@ func runAttach(dockerCli *command.DockerCli, opts *attachOptions) error {
logrus.Debugf("Error monitoring TTY size: %s", err) 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 return err
} }

View File

@ -146,7 +146,7 @@ func runExec(dockerCli *command.DockerCli, options *execOptions, container strin
} }
defer resp.Close() defer resp.Close()
errCh = promise.Go(func() error { 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() { if execConfig.Tty && dockerCli.In().IsTerminal() {

View File

@ -8,14 +8,19 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
"golang.org/x/net/context" "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 // holdHijackedConnection handles copying input to and output from streams to the
// connection // connection
// nolint: gocyclo // 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 ( var (
err error err error
restoreOnce sync.Once restoreOnce sync.Once
@ -29,6 +34,15 @@ func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bo
restoreTerminal(streams, inputStream) 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) receiveStdout := make(chan error, 1)
@ -54,9 +68,10 @@ func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bo
} }
stdinDone := make(chan struct{}) stdinDone := make(chan struct{})
detachedC := make(chan term.EscapeError)
go func() { go func() {
if inputStream != nil { 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 // we should restore the terminal as soon as possible once connection end
// so any following print messages will be in normal type. // so any following print messages will be in normal type.
if tty { if tty {
@ -65,6 +80,11 @@ func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bo
}) })
} }
logrus.Debug("[hijack] End of stdin") logrus.Debug("[hijack] End of stdin")
if detached, ok := inputErr.(term.EscapeError); ok {
detachedC <- detached
return
}
} }
if err := resp.CloseWrite(); err != nil { if err := resp.CloseWrite(); err != nil {
@ -90,6 +110,9 @@ func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bo
case <-ctx.Done(): case <-ctx.Done():
} }
} }
case err := <-detachedC:
// Got a detach key sequence.
return err
case <-ctx.Done(): case <-ctx.Done():
} }

View File

@ -16,6 +16,7 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
"github.com/docker/libnetwork/resolvconf/dns" "github.com/docker/libnetwork/resolvconf/dns"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -199,6 +200,11 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain
if errCh != nil { if errCh != nil {
if err := <-errCh; err != 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) logrus.Debugf("Error hijack: %s", err)
return err return err
} }
@ -261,7 +267,7 @@ func attachContainer(
} }
*errCh = promise.Go(func() error { *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 errHijack
} }
return errAttach return errAttach

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -103,7 +104,7 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
} }
defer resp.Close() defer resp.Close()
cErr := promise.Go(func() error { 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 { if errHijack == nil {
return errAttach return errAttach
} }
@ -136,6 +137,10 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
} }
} }
if attchErr := <-cErr; attchErr != nil { if attchErr := <-cErr; attchErr != nil {
if _, ok := err.(term.EscapeError); ok {
// The user entered the detach escape sequence.
return nil
}
return attchErr return attchErr
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "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/events"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
@ -13,12 +14,41 @@ import (
"golang.org/x/net/context" "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 { if len(containerID) == 0 {
// containerID can never be empty // containerID can never be empty
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter") 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 var removeErr error
statusChan := make(chan int) statusChan := make(chan int)
exitCode := 125 exitCode := 125

View File

@ -28,6 +28,7 @@ func NewWaitCommand(dockerCli *command.DockerCli) *cobra.Command {
return runWait(dockerCli, &opts) return runWait(dockerCli, &opts)
}, },
} }
return cmd return cmd
} }
@ -36,12 +37,14 @@ func runWait(dockerCli *command.DockerCli, opts *waitOptions) error {
var errs []string var errs []string
for _, container := range opts.containers { for _, container := range opts.containers {
status, err := dockerCli.Client().ContainerWait(ctx, container) resultC, errC := dockerCli.Client().ContainerWait(ctx, container, "")
if err != nil {
select {
case result := <-resultC:
fmt.Fprintf(dockerCli.Out(), "%d\n", result.StatusCode)
case err := <-errC:
errs = append(errs, err.Error()) errs = append(errs, err.Error())
continue
} }
fmt.Fprintf(dockerCli.Out(), "%d\n", status)
} }
if len(errs) > 0 { if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n")) return errors.New(strings.Join(errs, "\n"))