|
@@ -34,13 +34,12 @@ import (
|
|
|
)
|
|
|
|
|
|
var (
|
|
|
- ErrNotATTY = errors.New("The PTY is not a file")
|
|
|
- ErrNoTTY = errors.New("No PTY found")
|
|
|
- ErrContainerStart = errors.New("The container failed to start. Unknown error")
|
|
|
- ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
|
|
|
- ErrContainerRootfsReadonly = errors.New("container rootfs is marked read-only")
|
|
|
+ // ErrRootFSReadOnly is returned when a container
|
|
|
+ // rootfs is marked readonly.
|
|
|
+ ErrRootFSReadOnly = errors.New("container rootfs is marked read-only")
|
|
|
)
|
|
|
|
|
|
+// ErrContainerNotRunning holds the id of the container that is not running.
|
|
|
type ErrContainerNotRunning struct {
|
|
|
id string
|
|
|
}
|
|
@@ -49,48 +48,49 @@ func (e ErrContainerNotRunning) Error() string {
|
|
|
return fmt.Sprintf("Container %s is not running", e.id)
|
|
|
}
|
|
|
|
|
|
-type StreamConfig struct {
|
|
|
+type streamConfig struct {
|
|
|
stdout *broadcastwriter.BroadcastWriter
|
|
|
stderr *broadcastwriter.BroadcastWriter
|
|
|
stdin io.ReadCloser
|
|
|
stdinPipe io.WriteCloser
|
|
|
}
|
|
|
|
|
|
-// CommonContainer holds the settings for a container which are applicable
|
|
|
-// across all platforms supported by the daemon.
|
|
|
+// CommonContainer holds the fields for a container which are
|
|
|
+// applicable across all platforms supported by the daemon.
|
|
|
type CommonContainer struct {
|
|
|
- StreamConfig
|
|
|
-
|
|
|
- *State `json:"State"` // Needed for remote api version <= 1.11
|
|
|
- root string // Path to the "home" of the container, including metadata.
|
|
|
- basefs string // Path to the graphdriver mountpoint
|
|
|
-
|
|
|
- ID string
|
|
|
- Created time.Time
|
|
|
- Path string
|
|
|
- Args []string
|
|
|
- Config *runconfig.Config
|
|
|
- ImageID string `json:"Image"`
|
|
|
- NetworkSettings *network.Settings
|
|
|
- LogPath string
|
|
|
- Name string
|
|
|
- Driver string
|
|
|
- ExecDriver string
|
|
|
- MountLabel, ProcessLabel string
|
|
|
- RestartCount int
|
|
|
- HasBeenStartedBefore bool
|
|
|
- HasBeenManuallyStopped bool // used for unless-stopped restart policy
|
|
|
- hostConfig *runconfig.HostConfig
|
|
|
- command *execdriver.Command
|
|
|
- monitor *containerMonitor
|
|
|
- execCommands *execStore
|
|
|
- daemon *Daemon
|
|
|
+ streamConfig
|
|
|
+ // embed for Container to support states directly.
|
|
|
+ *State `json:"State"` // Needed for remote api version <= 1.11
|
|
|
+ root string // Path to the "home" of the container, including metadata.
|
|
|
+ basefs string // Path to the graphdriver mountpoint
|
|
|
+ ID string
|
|
|
+ Created time.Time
|
|
|
+ Path string
|
|
|
+ Args []string
|
|
|
+ Config *runconfig.Config
|
|
|
+ ImageID string `json:"Image"`
|
|
|
+ NetworkSettings *network.Settings
|
|
|
+ LogPath string
|
|
|
+ Name string
|
|
|
+ Driver string
|
|
|
+ ExecDriver string
|
|
|
+ // MountLabel contains the options for the 'mount' command
|
|
|
+ MountLabel string
|
|
|
+ ProcessLabel string
|
|
|
+ RestartCount int
|
|
|
+ HasBeenStartedBefore bool
|
|
|
+ HasBeenManuallyStopped bool // used for unless-stopped restart policy
|
|
|
+ hostConfig *runconfig.HostConfig
|
|
|
+ command *execdriver.Command
|
|
|
+ monitor *containerMonitor
|
|
|
+ execCommands *execStore
|
|
|
+ daemon *Daemon
|
|
|
// logDriver for closing
|
|
|
logDriver logger.Logger
|
|
|
logCopier *logger.Copier
|
|
|
}
|
|
|
|
|
|
-func (container *Container) FromDisk() error {
|
|
|
+func (container *Container) fromDisk() error {
|
|
|
pth, err := container.jsonPath()
|
|
|
if err != nil {
|
|
|
return err
|
|
@@ -131,10 +131,10 @@ func (container *Container) toDisk() error {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- return container.WriteHostConfig()
|
|
|
+ return container.writeHostConfig()
|
|
|
}
|
|
|
|
|
|
-func (container *Container) ToDisk() error {
|
|
|
+func (container *Container) toDiskLocking() error {
|
|
|
container.Lock()
|
|
|
err := container.toDisk()
|
|
|
container.Unlock()
|
|
@@ -165,7 +165,7 @@ func (container *Container) readHostConfig() error {
|
|
|
return json.NewDecoder(f).Decode(&container.hostConfig)
|
|
|
}
|
|
|
|
|
|
-func (container *Container) WriteHostConfig() error {
|
|
|
+func (container *Container) writeHostConfig() error {
|
|
|
data, err := json.Marshal(container.hostConfig)
|
|
|
if err != nil {
|
|
|
return err
|
|
@@ -179,7 +179,7 @@ func (container *Container) WriteHostConfig() error {
|
|
|
return ioutil.WriteFile(pth, data, 0666)
|
|
|
}
|
|
|
|
|
|
-func (container *Container) LogEvent(action string) {
|
|
|
+func (container *Container) logEvent(action string) {
|
|
|
d := container.daemon
|
|
|
d.EventsService.Log(
|
|
|
action,
|
|
@@ -188,7 +188,7 @@ func (container *Container) LogEvent(action string) {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-// Evaluates `path` in the scope of the container's basefs, with proper path
|
|
|
+// GetResourcePath evaluates `path` in the scope of the container's basefs, with proper path
|
|
|
// sanitisation. Symlinks are all scoped to the basefs of the container, as
|
|
|
// though the container's basefs was `/`.
|
|
|
//
|
|
@@ -221,18 +221,18 @@ func (container *Container) GetResourcePath(path string) (string, error) {
|
|
|
// if no component of the returned path changes (such as a component
|
|
|
// symlinking to a different path) between using this method and using the
|
|
|
// path. See symlink.FollowSymlinkInScope for more details.
|
|
|
-func (container *Container) GetRootResourcePath(path string) (string, error) {
|
|
|
+func (container *Container) getRootResourcePath(path string) (string, error) {
|
|
|
// IMPORTANT - These are paths on the OS where the daemon is running, hence
|
|
|
// any filepath operations must be done in an OS agnostic way.
|
|
|
cleanPath := filepath.Join(string(os.PathSeparator), path)
|
|
|
return symlink.FollowSymlinkInScope(filepath.Join(container.root, cleanPath), container.root)
|
|
|
}
|
|
|
|
|
|
-func (container *Container) ExportRw() (archive.Archive, error) {
|
|
|
+func (container *Container) exportContainerRw() (archive.Archive, error) {
|
|
|
if container.daemon == nil {
|
|
|
return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID)
|
|
|
}
|
|
|
- archive, err := container.daemon.Diff(container)
|
|
|
+ archive, err := container.daemon.diff(container)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
@@ -243,6 +243,10 @@ func (container *Container) ExportRw() (archive.Archive, error) {
|
|
|
nil
|
|
|
}
|
|
|
|
|
|
+// Start prepares the container to run by setting up everything the
|
|
|
+// container needs, such as storage and networking, as well as links
|
|
|
+// between containers. The container is left waiting for a signal to
|
|
|
+// begin running.
|
|
|
func (container *Container) Start() (err error) {
|
|
|
container.Lock()
|
|
|
defer container.Unlock()
|
|
@@ -266,7 +270,7 @@ func (container *Container) Start() (err error) {
|
|
|
}
|
|
|
container.toDisk()
|
|
|
container.cleanup()
|
|
|
- container.LogEvent("die")
|
|
|
+ container.logEvent("die")
|
|
|
}
|
|
|
}()
|
|
|
|
|
@@ -302,7 +306,7 @@ func (container *Container) Start() (err error) {
|
|
|
return container.waitForStart()
|
|
|
}
|
|
|
|
|
|
-func (container *Container) Run() error {
|
|
|
+func (container *Container) run() error {
|
|
|
if err := container.Start(); err != nil {
|
|
|
return err
|
|
|
}
|
|
@@ -311,7 +315,7 @@ func (container *Container) Run() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (container *Container) Output() (output []byte, err error) {
|
|
|
+func (container *Container) output() (output []byte, err error) {
|
|
|
pipe := container.StdoutPipe()
|
|
|
defer pipe.Close()
|
|
|
if err := container.Start(); err != nil {
|
|
@@ -322,7 +326,7 @@ func (container *Container) Output() (output []byte, err error) {
|
|
|
return output, err
|
|
|
}
|
|
|
|
|
|
-// StreamConfig.StdinPipe returns a WriteCloser which can be used to feed data
|
|
|
+// 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
|
|
@@ -330,17 +334,17 @@ func (container *Container) Output() (output []byte, err error) {
|
|
|
// copied and delivered to all StdoutPipe and StderrPipe consumers, using
|
|
|
// a kind of "broadcaster".
|
|
|
|
|
|
-func (streamConfig *StreamConfig) StdinPipe() io.WriteCloser {
|
|
|
+func (streamConfig *streamConfig) StdinPipe() io.WriteCloser {
|
|
|
return streamConfig.stdinPipe
|
|
|
}
|
|
|
|
|
|
-func (streamConfig *StreamConfig) StdoutPipe() io.ReadCloser {
|
|
|
+func (streamConfig *streamConfig) StdoutPipe() io.ReadCloser {
|
|
|
reader, writer := io.Pipe()
|
|
|
streamConfig.stdout.AddWriter(writer)
|
|
|
return ioutils.NewBufReader(reader)
|
|
|
}
|
|
|
|
|
|
-func (streamConfig *StreamConfig) StderrPipe() io.ReadCloser {
|
|
|
+func (streamConfig *streamConfig) StderrPipe() io.ReadCloser {
|
|
|
reader, writer := io.Pipe()
|
|
|
streamConfig.stderr.AddWriter(writer)
|
|
|
return ioutils.NewBufReader(reader)
|
|
@@ -353,7 +357,7 @@ func (container *Container) isNetworkAllocated() bool {
|
|
|
// cleanup releases any network resources allocated to the container along with any rules
|
|
|
// around how containers are linked together. It also unmounts the container's root filesystem.
|
|
|
func (container *Container) cleanup() {
|
|
|
- container.ReleaseNetwork()
|
|
|
+ container.releaseNetwork()
|
|
|
|
|
|
if err := container.Unmount(); err != nil {
|
|
|
logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
|
|
@@ -363,10 +367,15 @@ func (container *Container) cleanup() {
|
|
|
container.daemon.unregisterExecCommand(eConfig)
|
|
|
}
|
|
|
|
|
|
- container.UnmountVolumes(false)
|
|
|
+ container.unmountVolumes(false)
|
|
|
}
|
|
|
|
|
|
-func (container *Container) KillSig(sig int) error {
|
|
|
+// killSig sends the container the given signal. This wrapper for the
|
|
|
+// host specific kill command prepares the container before attempting
|
|
|
+// to send the signal. An error is returned if the container is paused
|
|
|
+// or not running, or if there is a problem returned from the
|
|
|
+// underlying kill command.
|
|
|
+func (container *Container) killSig(sig int) error {
|
|
|
logrus.Debugf("Sending %d to %s", sig, container.ID)
|
|
|
container.Lock()
|
|
|
defer container.Unlock()
|
|
@@ -391,24 +400,24 @@ func (container *Container) KillSig(sig int) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- if err := container.daemon.Kill(container, sig); err != nil {
|
|
|
+ if err := container.daemon.kill(container, sig); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- container.LogEvent("kill")
|
|
|
+ container.logEvent("kill")
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// Wrapper aroung KillSig() suppressing "no such process" error.
|
|
|
+// Wrapper aroung killSig() suppressing "no such process" error.
|
|
|
func (container *Container) killPossiblyDeadProcess(sig int) error {
|
|
|
- err := container.KillSig(sig)
|
|
|
+ err := container.killSig(sig)
|
|
|
if err == syscall.ESRCH {
|
|
|
- logrus.Debugf("Cannot kill process (pid=%d) with signal %d: no such process.", container.GetPid(), sig)
|
|
|
+ logrus.Debugf("Cannot kill process (pid=%d) with signal %d: no such process.", container.getPID(), sig)
|
|
|
return nil
|
|
|
}
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
-func (container *Container) Pause() error {
|
|
|
+func (container *Container) pause() error {
|
|
|
container.Lock()
|
|
|
defer container.Unlock()
|
|
|
|
|
@@ -426,11 +435,11 @@ func (container *Container) Pause() error {
|
|
|
return err
|
|
|
}
|
|
|
container.Paused = true
|
|
|
- container.LogEvent("pause")
|
|
|
+ container.logEvent("pause")
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (container *Container) Unpause() error {
|
|
|
+func (container *Container) unpause() error {
|
|
|
container.Lock()
|
|
|
defer container.Unlock()
|
|
|
|
|
@@ -448,17 +457,18 @@ func (container *Container) Unpause() error {
|
|
|
return err
|
|
|
}
|
|
|
container.Paused = false
|
|
|
- container.LogEvent("unpause")
|
|
|
+ container.logEvent("unpause")
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// Kill forcefully terminates a container.
|
|
|
func (container *Container) Kill() error {
|
|
|
if !container.IsRunning() {
|
|
|
return ErrContainerNotRunning{container.ID}
|
|
|
}
|
|
|
|
|
|
// 1. Send SIGKILL
|
|
|
- if err := container.killPossiblyDeadProcess(9); err != nil {
|
|
|
+ if err := container.killPossiblyDeadProcess(int(syscall.SIGKILL)); err != nil {
|
|
|
// While normally we might "return err" here we're not going to
|
|
|
// because if we can't stop the container by this point then
|
|
|
// its probably because its already stopped. Meaning, between
|
|
@@ -487,15 +497,20 @@ func (container *Container) Kill() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// Stop halts a container by sending SIGTERM, waiting for the given
|
|
|
+// duration in seconds, and then calling SIGKILL and waiting for the
|
|
|
+// process to exit. If a negative duration is given, Stop will wait
|
|
|
+// for SIGTERM forever. If the container is not running Stop returns
|
|
|
+// immediately.
|
|
|
func (container *Container) Stop(seconds int) error {
|
|
|
if !container.IsRunning() {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
// 1. Send a SIGTERM
|
|
|
- if err := container.killPossiblyDeadProcess(15); err != nil {
|
|
|
+ if err := container.killPossiblyDeadProcess(int(syscall.SIGTERM)); err != nil {
|
|
|
logrus.Infof("Failed to send SIGTERM to the process, force killing")
|
|
|
- if err := container.killPossiblyDeadProcess(9); err != nil {
|
|
|
+ if err := container.killPossiblyDeadProcess(int(syscall.SIGKILL)); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
}
|
|
@@ -510,10 +525,14 @@ func (container *Container) Stop(seconds int) error {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- container.LogEvent("stop")
|
|
|
+ container.logEvent("stop")
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// Restart attempts to gracefully stop and then start the
|
|
|
+// container. When stopping, wait for the given duration in seconds to
|
|
|
+// gracefully stop, before forcefully terminating the container. If
|
|
|
+// given a negative duration, wait forever for a graceful stop.
|
|
|
func (container *Container) Restart(seconds int) error {
|
|
|
// Avoid unnecessarily unmounting and then directly mounting
|
|
|
// the container when the container stops and then starts
|
|
@@ -530,10 +549,12 @@ func (container *Container) Restart(seconds int) error {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- container.LogEvent("restart")
|
|
|
+ container.logEvent("restart")
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// Resize changes the TTY of the process running inside the container
|
|
|
+// to the given height and width. The container must be running.
|
|
|
func (container *Container) Resize(h, w int) error {
|
|
|
if !container.IsRunning() {
|
|
|
return ErrContainerNotRunning{container.ID}
|
|
@@ -541,11 +562,11 @@ func (container *Container) Resize(h, w int) error {
|
|
|
if err := container.command.ProcessConfig.Terminal.Resize(h, w); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- container.LogEvent("resize")
|
|
|
+ container.logEvent("resize")
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (container *Container) Export() (archive.Archive, error) {
|
|
|
+func (container *Container) export() (archive.Archive, error) {
|
|
|
if err := container.Mount(); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
@@ -560,46 +581,45 @@ func (container *Container) Export() (archive.Archive, error) {
|
|
|
container.Unmount()
|
|
|
return err
|
|
|
})
|
|
|
- container.LogEvent("export")
|
|
|
+ container.logEvent("export")
|
|
|
return arch, err
|
|
|
}
|
|
|
|
|
|
+// Mount sets container.basefs
|
|
|
func (container *Container) Mount() error {
|
|
|
return container.daemon.Mount(container)
|
|
|
}
|
|
|
|
|
|
func (container *Container) changes() ([]archive.Change, error) {
|
|
|
- return container.daemon.Changes(container)
|
|
|
-}
|
|
|
-
|
|
|
-func (container *Container) Changes() ([]archive.Change, error) {
|
|
|
container.Lock()
|
|
|
defer container.Unlock()
|
|
|
- return container.changes()
|
|
|
+ return container.daemon.changes(container)
|
|
|
}
|
|
|
|
|
|
-func (container *Container) GetImage() (*image.Image, error) {
|
|
|
+func (container *Container) getImage() (*image.Image, error) {
|
|
|
if container.daemon == nil {
|
|
|
return nil, fmt.Errorf("Can't get image of unregistered container")
|
|
|
}
|
|
|
return container.daemon.graph.Get(container.ImageID)
|
|
|
}
|
|
|
|
|
|
+// Unmount asks the daemon to release the layered filesystems that are
|
|
|
+// mounted by the container.
|
|
|
func (container *Container) Unmount() error {
|
|
|
- return container.daemon.Unmount(container)
|
|
|
+ return container.daemon.unmount(container)
|
|
|
}
|
|
|
|
|
|
func (container *Container) hostConfigPath() (string, error) {
|
|
|
- return container.GetRootResourcePath("hostconfig.json")
|
|
|
+ return container.getRootResourcePath("hostconfig.json")
|
|
|
}
|
|
|
|
|
|
func (container *Container) jsonPath() (string, error) {
|
|
|
- return container.GetRootResourcePath("config.json")
|
|
|
+ return container.getRootResourcePath("config.json")
|
|
|
}
|
|
|
|
|
|
// This method must be exported to be used from the lxc template
|
|
|
// This directory is only usable when the container is running
|
|
|
-func (container *Container) RootfsPath() string {
|
|
|
+func (container *Container) rootfsPath() string {
|
|
|
return container.basefs
|
|
|
}
|
|
|
|
|
@@ -610,7 +630,7 @@ func validateID(id string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (container *Container) Copy(resource string) (rc io.ReadCloser, err error) {
|
|
|
+func (container *Container) copy(resource string) (rc io.ReadCloser, err error) {
|
|
|
container.Lock()
|
|
|
|
|
|
defer func() {
|
|
@@ -629,7 +649,7 @@ func (container *Container) Copy(resource string) (rc io.ReadCloser, err error)
|
|
|
defer func() {
|
|
|
if err != nil {
|
|
|
// unmount any volumes
|
|
|
- container.UnmountVolumes(true)
|
|
|
+ container.unmountVolumes(true)
|
|
|
// unmount the container's rootfs
|
|
|
container.Unmount()
|
|
|
}
|
|
@@ -666,17 +686,17 @@ func (container *Container) Copy(resource string) (rc io.ReadCloser, err error)
|
|
|
|
|
|
reader := ioutils.NewReadCloserWrapper(archive, func() error {
|
|
|
err := archive.Close()
|
|
|
- container.UnmountVolumes(true)
|
|
|
+ container.unmountVolumes(true)
|
|
|
container.Unmount()
|
|
|
container.Unlock()
|
|
|
return err
|
|
|
})
|
|
|
- container.LogEvent("copy")
|
|
|
+ container.logEvent("copy")
|
|
|
return reader, nil
|
|
|
}
|
|
|
|
|
|
// Returns true if the container exposes a certain port
|
|
|
-func (container *Container) Exposes(p nat.Port) bool {
|
|
|
+func (container *Container) exposes(p nat.Port) bool {
|
|
|
_, exists := container.Config.ExposedPorts[p]
|
|
|
return exists
|
|
|
}
|
|
@@ -718,7 +738,7 @@ func (container *Container) getLogger() (logger.Logger, error) {
|
|
|
|
|
|
// Set logging file for "json-logger"
|
|
|
if cfg.Type == jsonfilelog.Name {
|
|
|
- ctx.LogPath, err = container.GetRootResourcePath(fmt.Sprintf("%s-json.log", container.ID))
|
|
|
+ ctx.LogPath, err = container.getRootResourcePath(fmt.Sprintf("%s-json.log", container.ID))
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
@@ -764,7 +784,7 @@ func (container *Container) waitForStart() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (container *Container) GetProcessLabel() string {
|
|
|
+func (container *Container) getProcessLabel() string {
|
|
|
// even if we have a process label return "" if we are running
|
|
|
// in privileged mode
|
|
|
if container.hostConfig.Privileged {
|
|
@@ -773,31 +793,22 @@ func (container *Container) GetProcessLabel() string {
|
|
|
return container.ProcessLabel
|
|
|
}
|
|
|
|
|
|
-func (container *Container) GetMountLabel() string {
|
|
|
+func (container *Container) getMountLabel() string {
|
|
|
if container.hostConfig.Privileged {
|
|
|
return ""
|
|
|
}
|
|
|
return container.MountLabel
|
|
|
}
|
|
|
|
|
|
-func (container *Container) Stats() (*execdriver.ResourceStats, error) {
|
|
|
- return container.daemon.Stats(container)
|
|
|
-}
|
|
|
-
|
|
|
-func (c *Container) LogDriverType() string {
|
|
|
- c.Lock()
|
|
|
- defer c.Unlock()
|
|
|
- if c.hostConfig.LogConfig.Type == "" {
|
|
|
- return c.daemon.defaultLogConfig.Type
|
|
|
- }
|
|
|
- return c.hostConfig.LogConfig.Type
|
|
|
+func (container *Container) stats() (*execdriver.ResourceStats, error) {
|
|
|
+ return container.daemon.stats(container)
|
|
|
}
|
|
|
|
|
|
-func (container *Container) GetExecIDs() []string {
|
|
|
+func (container *Container) getExecIDs() []string {
|
|
|
return container.execCommands.List()
|
|
|
}
|
|
|
|
|
|
-func (container *Container) Exec(execConfig *execConfig) error {
|
|
|
+func (container *Container) exec(ExecConfig *ExecConfig) error {
|
|
|
container.Lock()
|
|
|
defer container.Unlock()
|
|
|
|
|
@@ -810,16 +821,16 @@ func (container *Container) Exec(execConfig *execConfig) error {
|
|
|
c.Close()
|
|
|
}
|
|
|
}
|
|
|
- close(execConfig.waitStart)
|
|
|
+ close(ExecConfig.waitStart)
|
|
|
}
|
|
|
|
|
|
// We use a callback here instead of a goroutine and an chan for
|
|
|
// synchronization purposes
|
|
|
- cErr := promise.Go(func() error { return container.monitorExec(execConfig, callback) })
|
|
|
+ cErr := promise.Go(func() error { return container.monitorExec(ExecConfig, callback) })
|
|
|
|
|
|
// Exec should not return until the process is actually running
|
|
|
select {
|
|
|
- case <-execConfig.waitStart:
|
|
|
+ case <-ExecConfig.waitStart:
|
|
|
case err := <-cErr:
|
|
|
return err
|
|
|
}
|
|
@@ -827,46 +838,48 @@ func (container *Container) Exec(execConfig *execConfig) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error {
|
|
|
+func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.StartCallback) error {
|
|
|
var (
|
|
|
err error
|
|
|
exitCode int
|
|
|
)
|
|
|
- pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin)
|
|
|
- exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback)
|
|
|
+ pipes := execdriver.NewPipes(ExecConfig.streamConfig.stdin, ExecConfig.streamConfig.stdout, ExecConfig.streamConfig.stderr, ExecConfig.OpenStdin)
|
|
|
+ exitCode, err = container.daemon.Exec(container, ExecConfig, pipes, callback)
|
|
|
if err != nil {
|
|
|
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)
|
|
|
- if execConfig.OpenStdin {
|
|
|
- if err := execConfig.StreamConfig.stdin.Close(); err != nil {
|
|
|
+ if ExecConfig.OpenStdin {
|
|
|
+ if err := ExecConfig.streamConfig.stdin.Close(); err != nil {
|
|
|
logrus.Errorf("Error closing stdin while running in %s: %s", container.ID, err)
|
|
|
}
|
|
|
}
|
|
|
- if err := execConfig.StreamConfig.stdout.Clean(); err != nil {
|
|
|
+ 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 {
|
|
|
+ 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 err := execConfig.ProcessConfig.Terminal.Close(); err != nil {
|
|
|
+ if ExecConfig.ProcessConfig.Terminal != nil {
|
|
|
+ if err := ExecConfig.ProcessConfig.Terminal.Close(); err != nil {
|
|
|
logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
|
|
|
}
|
|
|
}
|
|
|
// remove the exec command from the container's store only and not the
|
|
|
// daemon's store so that the exec command can be inspected.
|
|
|
- container.execCommands.Delete(execConfig.ID)
|
|
|
+ container.execCommands.Delete(ExecConfig.ID)
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
-func (c *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
|
|
- return attach(&c.StreamConfig, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, stdin, stdout, stderr)
|
|
|
+// Attach connects to the container's TTY, delegating to standard
|
|
|
+// streams or websockets depending on the configuration.
|
|
|
+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)
|
|
|
}
|
|
|
|
|
|
-func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error {
|
|
|
+func (container *Container) attachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error {
|
|
|
if logs {
|
|
|
- logDriver, err := c.getLogger()
|
|
|
+ logDriver, err := container.getLogger()
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
@@ -896,7 +909,7 @@ func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- c.LogEvent("attach")
|
|
|
+ container.logEvent("attach")
|
|
|
|
|
|
//stream
|
|
|
if stream {
|
|
@@ -910,17 +923,17 @@ func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer
|
|
|
}()
|
|
|
stdinPipe = r
|
|
|
}
|
|
|
- <-c.Attach(stdinPipe, stdout, stderr)
|
|
|
+ <-container.Attach(stdinPipe, stdout, stderr)
|
|
|
// If we are in stdinonce mode, wait for the process to end
|
|
|
// otherwise, simply return
|
|
|
- if c.Config.StdinOnce && !c.Config.Tty {
|
|
|
- c.WaitStop(-1 * time.Second)
|
|
|
+ if container.Config.StdinOnce && !container.Config.Tty {
|
|
|
+ container.WaitStop(-1 * time.Second)
|
|
|
}
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
|
|
+func attach(streamConfig *streamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
|
|
var (
|
|
|
cStdout, cStderr io.ReadCloser
|
|
|
cStdin io.WriteCloser
|