4921171587
This patch adds the untilRemoved option to the ContainerWait API which allows the client to wait until the container is not only exited but also removed. This patch also adds some more CLI integration tests for waiting for a created container and waiting with the new --until-removed flag. Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Handle detach sequence in CLI Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Update Container Wait Conditions Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Apply container wait changes to API 1.30 The set of changes to the containerWait API missed the cut for the Docker 17.05 release (API version 1.29). This patch bumps the version checks to use 1.30 instead. This patch also makes a minor update to a testfile which was added to the builder/dockerfile package. Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Remove wait changes from CLI Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Address minor nits on wait changes - Changed the name of the tty Proxy wrapper to `escapeProxy` - Removed the unnecessary Error() method on container.State - Fixes a typo in comment (repeated word) Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Use router.WithCancel in the containerWait handler This handler previously added this functionality manually but now uses the existing wrapper which does it for us. Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Add WaitCondition constants to api/types/container Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Address more ContainerWait review comments - Update ContainerWait backend interface to not return pointer values for container.StateStatus type. - Updated container state's Wait() method comments to clarify that a context MUST be used for cancelling the request, setting timeouts, and to avoid goroutine leaks. - Removed unnecessary buffering when making channels in the client's ContainerWait methods. - Renamed result and error channels in client's ContainerWait methods to clarify that only a single result or error value would be sent on the channel. Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Move container.WaitCondition type to separate file ... to avoid conflict with swagger-generated code for API response Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) Address more ContainerWait review comments Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
178 lines
3.9 KiB
Go
178 lines
3.9 KiB
Go
package stream
|
|
|
|
import (
|
|
"io"
|
|
"sync"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/pkg/promise"
|
|
"github.com/docker/docker/pkg/term"
|
|
)
|
|
|
|
var defaultEscapeSequence = []byte{16, 17} // ctrl-p, ctrl-q
|
|
|
|
// AttachConfig is the config struct used to attach a client to a stream's stdio
|
|
type AttachConfig struct {
|
|
// Tells the attach copier that the stream's stdin is a TTY and to look for
|
|
// escape sequences in stdin to detach from the stream.
|
|
// When true the escape sequence is not passed to the underlying stream
|
|
TTY bool
|
|
// Specifies the detach keys the client will be using
|
|
// Only useful when `TTY` is true
|
|
DetachKeys []byte
|
|
|
|
// CloseStdin signals that once done, stdin for the attached stream should be closed
|
|
// For example, this would close the attached container's stdin.
|
|
CloseStdin bool
|
|
|
|
// UseStd* indicate whether the client has requested to be connected to the
|
|
// given stream or not. These flags are used instead of checking Std* != nil
|
|
// at points before the client streams Std* are wired up.
|
|
UseStdin, UseStdout, UseStderr bool
|
|
|
|
// CStd* are the streams directly connected to the container
|
|
CStdin io.WriteCloser
|
|
CStdout, CStderr io.ReadCloser
|
|
|
|
// Provide client streams to wire up to
|
|
Stdin io.ReadCloser
|
|
Stdout, Stderr io.Writer
|
|
}
|
|
|
|
// AttachStreams attaches the container's streams to the AttachConfig
|
|
func (c *Config) AttachStreams(cfg *AttachConfig) {
|
|
if cfg.UseStdin {
|
|
cfg.CStdin = c.StdinPipe()
|
|
}
|
|
|
|
if cfg.UseStdout {
|
|
cfg.CStdout = c.StdoutPipe()
|
|
}
|
|
|
|
if cfg.UseStderr {
|
|
cfg.CStderr = c.StderrPipe()
|
|
}
|
|
}
|
|
|
|
// CopyStreams starts goroutines to copy data in and out to/from the container
|
|
func (c *Config) CopyStreams(ctx context.Context, cfg *AttachConfig) chan error {
|
|
var (
|
|
wg sync.WaitGroup
|
|
errors = make(chan error, 3)
|
|
)
|
|
|
|
if cfg.Stdin != nil {
|
|
wg.Add(1)
|
|
}
|
|
|
|
if cfg.Stdout != nil {
|
|
wg.Add(1)
|
|
}
|
|
|
|
if cfg.Stderr != nil {
|
|
wg.Add(1)
|
|
}
|
|
|
|
// Connect stdin of container to the attach stdin stream.
|
|
go func() {
|
|
if cfg.Stdin == nil {
|
|
return
|
|
}
|
|
logrus.Debug("attach: stdin: begin")
|
|
|
|
var err error
|
|
if cfg.TTY {
|
|
_, err = copyEscapable(cfg.CStdin, cfg.Stdin, cfg.DetachKeys)
|
|
} else {
|
|
_, err = io.Copy(cfg.CStdin, cfg.Stdin)
|
|
}
|
|
if err == io.ErrClosedPipe {
|
|
err = nil
|
|
}
|
|
if err != nil {
|
|
logrus.Errorf("attach: stdin: %s", err)
|
|
errors <- err
|
|
}
|
|
if cfg.CloseStdin && !cfg.TTY {
|
|
cfg.CStdin.Close()
|
|
} else {
|
|
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
|
if cfg.CStdout != nil {
|
|
cfg.CStdout.Close()
|
|
}
|
|
if cfg.CStderr != nil {
|
|
cfg.CStderr.Close()
|
|
}
|
|
}
|
|
logrus.Debug("attach: stdin: end")
|
|
wg.Done()
|
|
}()
|
|
|
|
attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) {
|
|
if stream == nil {
|
|
return
|
|
}
|
|
|
|
logrus.Debugf("attach: %s: begin", name)
|
|
_, err := io.Copy(stream, streamPipe)
|
|
if err == io.ErrClosedPipe {
|
|
err = nil
|
|
}
|
|
if err != nil {
|
|
logrus.Errorf("attach: %s: %v", name, err)
|
|
errors <- err
|
|
}
|
|
// Make sure stdin gets closed
|
|
if cfg.Stdin != nil {
|
|
cfg.Stdin.Close()
|
|
}
|
|
streamPipe.Close()
|
|
logrus.Debugf("attach: %s: end", name)
|
|
wg.Done()
|
|
}
|
|
|
|
go attachStream("stdout", cfg.Stdout, cfg.CStdout)
|
|
go attachStream("stderr", cfg.Stderr, cfg.CStderr)
|
|
|
|
return promise.Go(func() error {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
wg.Wait()
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
case <-ctx.Done():
|
|
// close all pipes
|
|
if cfg.CStdin != nil {
|
|
cfg.CStdin.Close()
|
|
}
|
|
if cfg.CStdout != nil {
|
|
cfg.CStdout.Close()
|
|
}
|
|
if cfg.CStderr != nil {
|
|
cfg.CStderr.Close()
|
|
}
|
|
<-done
|
|
}
|
|
close(errors)
|
|
for err := range errors {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
|
|
if len(keys) == 0 {
|
|
keys = defaultEscapeSequence
|
|
}
|
|
pr := term.NewEscapeProxy(src, keys)
|
|
defer src.Close()
|
|
|
|
return io.Copy(dst, pr)
|
|
}
|