mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Extract StreamConfig struct out of the daemon package.
This is a small configuration struct used in two scenarios: 1. To attach I/O pipes to a running containers. 2. To attach to execution processes inside running containers. Although they are similar, keeping the struct in the same package than exec and container can generate cycled dependencies if we move any of them outside the daemon, like we want to do with the container. Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
parent
877fe61f75
commit
3f5b8f712d
5 changed files with 132 additions and 85 deletions
|
@ -19,8 +19,6 @@ import (
|
||||||
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||||
"github.com/docker/docker/daemon/network"
|
"github.com/docker/docker/daemon/network"
|
||||||
derr "github.com/docker/docker/errors"
|
derr "github.com/docker/docker/errors"
|
||||||
"github.com/docker/docker/pkg/broadcaster"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/nat"
|
"github.com/docker/docker/pkg/nat"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
|
@ -36,17 +34,10 @@ var (
|
||||||
ErrRootFSReadOnly = errors.New("container rootfs is marked read-only")
|
ErrRootFSReadOnly = errors.New("container rootfs is marked read-only")
|
||||||
)
|
)
|
||||||
|
|
||||||
type streamConfig struct {
|
|
||||||
stdout *broadcaster.Unbuffered
|
|
||||||
stderr *broadcaster.Unbuffered
|
|
||||||
stdin io.ReadCloser
|
|
||||||
stdinPipe io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommonContainer holds the fields for a container which are
|
// CommonContainer holds the fields for a container which are
|
||||||
// applicable across all platforms supported by the daemon.
|
// applicable across all platforms supported by the daemon.
|
||||||
type CommonContainer struct {
|
type CommonContainer struct {
|
||||||
streamConfig
|
*runconfig.StreamConfig
|
||||||
// embed for Container to support states directly.
|
// embed for Container to support states directly.
|
||||||
*State `json:"State"` // Needed for remote api version <= 1.11
|
*State `json:"State"` // Needed for remote api version <= 1.11
|
||||||
root string // Path to the "home" of the container, including metadata.
|
root string // Path to the "home" of the container, including metadata.
|
||||||
|
@ -87,6 +78,7 @@ func newBaseContainer(id, root string) *Container {
|
||||||
execCommands: newExecStore(),
|
execCommands: newExecStore(),
|
||||||
root: root,
|
root: root,
|
||||||
MountPoints: make(map[string]*volume.MountPoint),
|
MountPoints: make(map[string]*volume.MountPoint),
|
||||||
|
StreamConfig: runconfig.NewStreamConfig(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,30 +235,6 @@ func (container *Container) getRootResourcePath(path string) (string, error) {
|
||||||
return symlink.FollowSymlinkInScope(filepath.Join(container.root, cleanPath), container.root)
|
return symlink.FollowSymlinkInScope(filepath.Join(container.root, cleanPath), container.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// streamConfig.StdinPipe returns a WriteCloser which can be used to feed data
|
|
||||||
// to the standard input of the container's active process.
|
|
||||||
// Container.StdoutPipe and Container.StderrPipe each return a ReadCloser
|
|
||||||
// which can be used to retrieve the standard output (and error) generated
|
|
||||||
// by the container's active process. The output (and error) are actually
|
|
||||||
// copied and delivered to all StdoutPipe and StderrPipe consumers, using
|
|
||||||
// a kind of "broadcaster".
|
|
||||||
|
|
||||||
func (streamConfig *streamConfig) StdinPipe() io.WriteCloser {
|
|
||||||
return streamConfig.stdinPipe
|
|
||||||
}
|
|
||||||
|
|
||||||
func (streamConfig *streamConfig) StdoutPipe() io.ReadCloser {
|
|
||||||
bytesPipe := ioutils.NewBytesPipe(nil)
|
|
||||||
streamConfig.stdout.Add(bytesPipe)
|
|
||||||
return bytesPipe
|
|
||||||
}
|
|
||||||
|
|
||||||
func (streamConfig *streamConfig) StderrPipe() io.ReadCloser {
|
|
||||||
bytesPipe := ioutils.NewBytesPipe(nil)
|
|
||||||
streamConfig.stderr.Add(bytesPipe)
|
|
||||||
return bytesPipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitOnNext signals to the monitor that it should not restart the container
|
// ExitOnNext signals to the monitor that it should not restart the container
|
||||||
// after we send the kill signal.
|
// after we send the kill signal.
|
||||||
func (container *Container) ExitOnNext() {
|
func (container *Container) ExitOnNext() {
|
||||||
|
@ -372,10 +340,10 @@ func (container *Container) getExecIDs() []string {
|
||||||
// Attach connects to the container's TTY, delegating to standard
|
// Attach connects to the container's TTY, delegating to standard
|
||||||
// streams or websockets depending on the configuration.
|
// streams or websockets depending on the configuration.
|
||||||
func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
||||||
return attach(&container.streamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr)
|
return attach(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func attach(streamConfig *streamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
func attach(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
||||||
var (
|
var (
|
||||||
cStdout, cStderr io.ReadCloser
|
cStdout, cStderr io.ReadCloser
|
||||||
cStdin io.WriteCloser
|
cStdin io.WriteCloser
|
||||||
|
|
|
@ -32,12 +32,10 @@ import (
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/broadcaster"
|
|
||||||
"github.com/docker/docker/pkg/discovery"
|
"github.com/docker/docker/pkg/discovery"
|
||||||
"github.com/docker/docker/pkg/fileutils"
|
"github.com/docker/docker/pkg/fileutils"
|
||||||
"github.com/docker/docker/pkg/graphdb"
|
"github.com/docker/docker/pkg/graphdb"
|
||||||
"github.com/docker/docker/pkg/idtools"
|
"github.com/docker/docker/pkg/idtools"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
"github.com/docker/docker/pkg/namesgenerator"
|
"github.com/docker/docker/pkg/namesgenerator"
|
||||||
|
@ -205,15 +203,11 @@ func (daemon *Daemon) Register(container *Container) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach to stdout and stderr
|
// Attach to stdout and stderr
|
||||||
container.stderr = new(broadcaster.Unbuffered)
|
|
||||||
container.stdout = new(broadcaster.Unbuffered)
|
|
||||||
// Attach to stdin
|
|
||||||
if container.Config.OpenStdin {
|
if container.Config.OpenStdin {
|
||||||
container.stdin, container.stdinPipe = io.Pipe()
|
container.NewInputPipes()
|
||||||
} else {
|
} else {
|
||||||
container.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
container.NewNopInputPipe()
|
||||||
}
|
}
|
||||||
// done
|
|
||||||
daemon.containers.Add(container.ID, container)
|
daemon.containers.Add(container.ID, container)
|
||||||
|
|
||||||
// don't update the Suffixarray if we're starting up
|
// don't update the Suffixarray if we're starting up
|
||||||
|
|
|
@ -2,7 +2,6 @@ package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -10,8 +9,6 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
derr "github.com/docker/docker/errors"
|
derr "github.com/docker/docker/errors"
|
||||||
"github.com/docker/docker/pkg/broadcaster"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/pools"
|
"github.com/docker/docker/pkg/pools"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
@ -28,10 +25,10 @@ type ExecConfig struct {
|
||||||
Running bool
|
Running bool
|
||||||
ExitCode int
|
ExitCode int
|
||||||
ProcessConfig *execdriver.ProcessConfig
|
ProcessConfig *execdriver.ProcessConfig
|
||||||
streamConfig
|
|
||||||
OpenStdin bool
|
OpenStdin bool
|
||||||
OpenStderr bool
|
OpenStderr bool
|
||||||
OpenStdout bool
|
OpenStdout bool
|
||||||
|
streamConfig *runconfig.StreamConfig
|
||||||
Container *Container
|
Container *Container
|
||||||
canRemove bool
|
canRemove bool
|
||||||
|
|
||||||
|
@ -170,7 +167,7 @@ func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, erro
|
||||||
OpenStdin: config.AttachStdin,
|
OpenStdin: config.AttachStdin,
|
||||||
OpenStdout: config.AttachStdout,
|
OpenStdout: config.AttachStdout,
|
||||||
OpenStderr: config.AttachStderr,
|
OpenStderr: config.AttachStderr,
|
||||||
streamConfig: streamConfig{},
|
streamConfig: runconfig.NewStreamConfig(),
|
||||||
ProcessConfig: processConfig,
|
ProcessConfig: processConfig,
|
||||||
Container: container,
|
Container: container,
|
||||||
Running: false,
|
Running: false,
|
||||||
|
@ -225,16 +222,13 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.
|
||||||
cStderr = stderr
|
cStderr = stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
ec.streamConfig.stderr = new(broadcaster.Unbuffered)
|
|
||||||
ec.streamConfig.stdout = new(broadcaster.Unbuffered)
|
|
||||||
// Attach to stdin
|
|
||||||
if ec.OpenStdin {
|
if ec.OpenStdin {
|
||||||
ec.streamConfig.stdin, ec.streamConfig.stdinPipe = io.Pipe()
|
ec.streamConfig.NewInputPipes()
|
||||||
} else {
|
} else {
|
||||||
ec.streamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
ec.streamConfig.NewNopInputPipe()
|
||||||
}
|
}
|
||||||
|
|
||||||
attachErr := attach(&ec.streamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr)
|
attachErr := attach(ec.streamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr)
|
||||||
|
|
||||||
execErr := make(chan error)
|
execErr := make(chan error)
|
||||||
|
|
||||||
|
@ -354,23 +348,17 @@ func (d *Daemon) containerExec(container *Container, ec *ExecConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) monitorExec(container *Container, ExecConfig *ExecConfig, callback execdriver.DriverCallback) error {
|
func (d *Daemon) monitorExec(container *Container, ExecConfig *ExecConfig, callback execdriver.DriverCallback) error {
|
||||||
pipes := execdriver.NewPipes(ExecConfig.streamConfig.stdin, ExecConfig.streamConfig.stdout, ExecConfig.streamConfig.stderr, ExecConfig.OpenStdin)
|
pipes := execdriver.NewPipes(ExecConfig.streamConfig.Stdin(), ExecConfig.streamConfig.Stdout(), ExecConfig.streamConfig.Stderr(), ExecConfig.OpenStdin)
|
||||||
exitCode, err := d.Exec(container, ExecConfig, pipes, callback)
|
exitCode, err := d.Exec(container, ExecConfig, pipes, callback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Error running command in existing container %s: %s", container.ID, err)
|
logrus.Errorf("Error running command in existing container %s: %s", container.ID, err)
|
||||||
}
|
}
|
||||||
logrus.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
|
logrus.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
|
||||||
if ExecConfig.OpenStdin {
|
|
||||||
if err := ExecConfig.streamConfig.stdin.Close(); err != nil {
|
if err := ExecConfig.streamConfig.CloseStreams(); err != nil {
|
||||||
logrus.Errorf("Error closing stdin while running in %s: %s", container.ID, err)
|
logrus.Errorf("%s: %s", container.ID, err)
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := ExecConfig.streamConfig.stdout.Clean(); err != nil {
|
|
||||||
logrus.Errorf("Error closing stdout while running in %s: %s", container.ID, err)
|
|
||||||
}
|
|
||||||
if err := ExecConfig.streamConfig.stderr.Clean(); err != nil {
|
|
||||||
logrus.Errorf("Error closing stderr while running in %s: %s", container.ID, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ExecConfig.ProcessConfig.Terminal != nil {
|
if ExecConfig.ProcessConfig.Terminal != nil {
|
||||||
if err := ExecConfig.ProcessConfig.Terminal.Close(); err != nil {
|
if err := ExecConfig.ProcessConfig.Terminal.Close(); err != nil {
|
||||||
logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
|
logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
|
||||||
|
|
|
@ -158,7 +158,7 @@ func (m *containerMonitor) Start() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pipes := execdriver.NewPipes(m.container.stdin, m.container.stdout, m.container.stderr, m.container.Config.OpenStdin)
|
pipes := execdriver.NewPipes(m.container.Stdin(), m.container.Stdout(), m.container.Stderr(), m.container.Config.OpenStdin)
|
||||||
|
|
||||||
m.logEvent("start")
|
m.logEvent("start")
|
||||||
|
|
||||||
|
@ -329,18 +329,8 @@ func (m *containerMonitor) resetContainer(lock bool) {
|
||||||
defer container.Unlock()
|
defer container.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.Config.OpenStdin {
|
if err := container.CloseStreams(); err != nil {
|
||||||
if err := container.stdin.Close(); err != nil {
|
logrus.Errorf("%s: %s", container.ID, err)
|
||||||
logrus.Errorf("%s: Error close stdin: %s", container.ID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := container.stdout.Clean(); err != nil {
|
|
||||||
logrus.Errorf("%s: Error close stdout: %s", container.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := container.stderr.Clean(); err != nil {
|
|
||||||
logrus.Errorf("%s: Error close stderr: %s", container.ID, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.command != nil && container.command.ProcessConfig.Terminal != nil {
|
if container.command != nil && container.command.ProcessConfig.Terminal != nil {
|
||||||
|
@ -351,7 +341,7 @@ func (m *containerMonitor) resetContainer(lock bool) {
|
||||||
|
|
||||||
// Re-create a brand new stdin pipe once the container exited
|
// Re-create a brand new stdin pipe once the container exited
|
||||||
if container.Config.OpenStdin {
|
if container.Config.OpenStdin {
|
||||||
container.stdin, container.stdinPipe = io.Pipe()
|
container.NewInputPipes()
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.logDriver != nil {
|
if container.logDriver != nil {
|
||||||
|
|
107
runconfig/streams.go
Normal file
107
runconfig/streams.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package runconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/broadcaster"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StreamConfig holds information about I/O streams managed together.
|
||||||
|
//
|
||||||
|
// streamConfig.StdinPipe returns a WriteCloser which can be used to feed data
|
||||||
|
// to the standard input of the streamConfig's active process.
|
||||||
|
// streamConfig.StdoutPipe and streamConfig.StderrPipe each return a ReadCloser
|
||||||
|
// which can be used to retrieve the standard output (and error) generated
|
||||||
|
// by the container's active process. The output (and error) are actually
|
||||||
|
// copied and delivered to all StdoutPipe and StderrPipe consumers, using
|
||||||
|
// a kind of "broadcaster".
|
||||||
|
type StreamConfig struct {
|
||||||
|
stdout *broadcaster.Unbuffered
|
||||||
|
stderr *broadcaster.Unbuffered
|
||||||
|
stdin io.ReadCloser
|
||||||
|
stdinPipe io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStreamConfig creates a stream config and initializes
|
||||||
|
// the standard err and standard out to new unbuffered broadcasters.
|
||||||
|
func NewStreamConfig() *StreamConfig {
|
||||||
|
return &StreamConfig{
|
||||||
|
stderr: new(broadcaster.Unbuffered),
|
||||||
|
stdout: new(broadcaster.Unbuffered),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stdout returns the standard output in the configuration.
|
||||||
|
func (streamConfig *StreamConfig) Stdout() *broadcaster.Unbuffered {
|
||||||
|
return streamConfig.stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stderr returns the standard error in the configuration.
|
||||||
|
func (streamConfig *StreamConfig) Stderr() *broadcaster.Unbuffered {
|
||||||
|
return streamConfig.stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stdin returns the standard input in the configuration.
|
||||||
|
func (streamConfig *StreamConfig) Stdin() io.ReadCloser {
|
||||||
|
return streamConfig.stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdinPipe returns an input writer pipe as an io.WriteCloser.
|
||||||
|
func (streamConfig *StreamConfig) StdinPipe() io.WriteCloser {
|
||||||
|
return streamConfig.stdinPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdoutPipe creates a new io.ReadCloser with an empty bytes pipe.
|
||||||
|
// It adds this new out pipe to the Stdout broadcaster.
|
||||||
|
func (streamConfig *StreamConfig) StdoutPipe() io.ReadCloser {
|
||||||
|
bytesPipe := ioutils.NewBytesPipe(nil)
|
||||||
|
streamConfig.stdout.Add(bytesPipe)
|
||||||
|
return bytesPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// StderrPipe creates a new io.ReadCloser with an empty bytes pipe.
|
||||||
|
// It adds this new err pipe to the Stderr broadcaster.
|
||||||
|
func (streamConfig *StreamConfig) StderrPipe() io.ReadCloser {
|
||||||
|
bytesPipe := ioutils.NewBytesPipe(nil)
|
||||||
|
streamConfig.stderr.Add(bytesPipe)
|
||||||
|
return bytesPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInputPipes creates new pipes for both standard inputs, Stdin and StdinPipe.
|
||||||
|
func (streamConfig *StreamConfig) NewInputPipes() {
|
||||||
|
streamConfig.stdin, streamConfig.stdinPipe = io.Pipe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNopInputPipe creates a new input pipe that will silently drop all messages in the input.
|
||||||
|
func (streamConfig *StreamConfig) NewNopInputPipe() {
|
||||||
|
streamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseStreams ensures that the configured streams are properly closed.
|
||||||
|
func (streamConfig *StreamConfig) CloseStreams() error {
|
||||||
|
var errors []string
|
||||||
|
|
||||||
|
if streamConfig.stdin != nil {
|
||||||
|
if err := streamConfig.stdin.Close(); err != nil {
|
||||||
|
errors = append(errors, fmt.Sprintf("error close stdin: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := streamConfig.stdout.Clean(); err != nil {
|
||||||
|
errors = append(errors, fmt.Sprintf("error close stdout: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := streamConfig.stderr.Clean(); err != nil {
|
||||||
|
errors = append(errors, fmt.Sprintf("error close stderr: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return fmt.Errorf(strings.Join(errors, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue