diff --git a/daemon/attach.go b/daemon/attach.go index 13824ea842..36dd13fb5e 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -103,7 +103,6 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status { } <-daemon.Attach(container, cStdin, cStdinCloser, cStdout, cStderr) - // If we are in stdinonce mode, wait for the process to end // otherwise, simply return if container.Config.StdinOnce && !container.Config.Tty { diff --git a/daemon/container.go b/daemon/container.go index 72e4dfd63e..4469510b21 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -41,6 +41,13 @@ var ( ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.") ) +type StreamConfig struct { + stdout *broadcastwriter.BroadcastWriter + stderr *broadcastwriter.BroadcastWriter + stdin io.ReadCloser + stdinPipe io.WriteCloser +} + type Container struct { *State root string // Path to the "home" of the container, including metadata. @@ -65,11 +72,8 @@ type Container struct { Driver string ExecDriver string - command *execdriver.Command - stdout *broadcastwriter.BroadcastWriter - stderr *broadcastwriter.BroadcastWriter - stdin io.ReadCloser - stdinPipe io.WriteCloser + command *execdriver.Command + StreamConfig daemon *Daemon MountLabel, ProcessLabel string @@ -247,26 +251,31 @@ func populateCommand(c *Container, env []string) error { CpuShares: c.Config.CpuShares, Cpuset: c.Config.Cpuset, } + + processConfig := execdriver.ProcessConfig{ + Privileged: c.hostConfig.Privileged, + Entrypoint: c.Path, + Arguments: c.Args, + Tty: c.Config.Tty, + User: c.Config.User, + } + processConfig.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + processConfig.Env = env c.command = &execdriver.Command{ ID: c.ID, - Privileged: c.hostConfig.Privileged, Rootfs: c.RootfsPath(), InitPath: "/.dockerinit", - Entrypoint: c.Path, - Arguments: c.Args, WorkingDir: c.Config.WorkingDir, Network: en, - Tty: c.Config.Tty, - User: c.Config.User, Config: context, Resources: resources, AllowedDevices: allowedDevices, AutoCreatedDevices: autoCreatedDevices, CapAdd: c.hostConfig.CapAdd, CapDrop: c.hostConfig.CapDrop, + ProcessConfig: processConfig, } - c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - c.command.Env = env + return nil } @@ -342,7 +351,7 @@ func (container *Container) Output() (output []byte, err error) { return output, err } -// Container.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 @@ -350,31 +359,31 @@ func (container *Container) Output() (output []byte, err error) { // copied and delivered to all StdoutPipe and StderrPipe consumers, using // a kind of "broadcaster". -func (container *Container) StdinPipe() (io.WriteCloser, error) { - return container.stdinPipe, nil +func (streamConfig *StreamConfig) StdinPipe() (io.WriteCloser, error) { + return streamConfig.stdinPipe, nil } -func (container *Container) StdoutPipe() (io.ReadCloser, error) { +func (streamConfig *StreamConfig) StdoutPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() - container.stdout.AddWriter(writer, "") + streamConfig.stdout.AddWriter(writer, "") return utils.NewBufReader(reader), nil } -func (container *Container) StderrPipe() (io.ReadCloser, error) { +func (streamConfig *StreamConfig) StderrPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() - container.stderr.AddWriter(writer, "") + streamConfig.stderr.AddWriter(writer, "") return utils.NewBufReader(reader), nil } -func (container *Container) StdoutLogPipe() io.ReadCloser { +func (streamConfig *StreamConfig) StdoutLogPipe() io.ReadCloser { reader, writer := io.Pipe() - container.stdout.AddWriter(writer, "stdout") + streamConfig.stdout.AddWriter(writer, "stdout") return utils.NewBufReader(reader) } -func (container *Container) StderrLogPipe() io.ReadCloser { +func (streamConfig *StreamConfig) StderrLogPipe() io.ReadCloser { reader, writer := io.Pipe() - container.stderr.AddWriter(writer, "stderr") + streamConfig.stderr.AddWriter(writer, "stderr") return utils.NewBufReader(reader) } @@ -631,7 +640,7 @@ func (container *Container) Restart(seconds int) error { } func (container *Container) Resize(h, w int) error { - return container.command.Terminal.Resize(h, w) + return container.command.ProcessConfig.Terminal.Resize(h, w) } func (container *Container) ExportRw() (archive.Archive, error) { @@ -815,7 +824,7 @@ func (container *Container) Exposes(p nat.Port) bool { } func (container *Container) GetPtyMaster() (*os.File, error) { - ttyConsole, ok := container.command.Terminal.(execdriver.TtyTerminal) + ttyConsole, ok := container.command.ProcessConfig.Terminal.(execdriver.TtyTerminal) if !ok { return nil, ErrNoTTY } diff --git a/daemon/daemon.go b/daemon/daemon.go index 0a4d6e0bc5..8e948c3f57 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -229,7 +229,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err ID: container.ID, } var err error - cmd.Process, err = os.FindProcess(existingPid) + cmd.ProcessConfig.Process, err = os.FindProcess(existingPid) if err != nil { log.Debugf("cannot find existing process for %d", existingPid) } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 121c6a5a03..94cb1632ab 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -20,7 +20,7 @@ var ( ErrDriverNotFound = errors.New("The requested docker init has not been found") ) -type StartCallback func(*Command) +type StartCallback func(*ProcessConfig, int) // Driver specific information based on // processes registered with the driver @@ -80,20 +80,26 @@ type Mount struct { Private bool `json:"private"` } -// Process wrapps an os/exec.Cmd to add more metadata -type Command struct { +// Describes a process that will be run inside a container. +type ProcessConfig struct { exec.Cmd `json:"-"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Tty bool `json:"tty"` + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + Terminal Terminal `json:"-"` // standard or tty terminal + Console string `json:"-"` // dev/console path +} + +// Process wrapps an os/exec.Cmd to add more metadata +type Command struct { ID string `json:"id"` - Privileged bool `json:"privileged"` - User string `json:"user"` Rootfs string `json:"rootfs"` // root fs of the container InitPath string `json:"initpath"` // dockerinit - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` WorkingDir string `json:"working_dir"` ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver - Tty bool `json:"tty"` Network *Network `json:"network"` Config map[string][]string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` @@ -102,14 +108,6 @@ type Command struct { AutoCreatedDevices []*devices.Device `json:"autocreated_devices"` CapAdd []string `json:"cap_add"` CapDrop []string `json:"cap_drop"` - - Terminal Terminal `json:"-"` // standard or tty terminal - Console string `json:"-"` // dev/console path - ContainerPid int `json:"container_pid"` // the pid for the process inside a container -} - -// Return the pid of the process -// If the process is nil -1 will be returned -func (c *Command) Pid() int { - return c.ContainerPid + ContainerPid int `json:"container_pid"` // the pid for the process inside a container + ProcessConfig ProcessConfig `json:"process_config"` // Describes the init process of the container. } diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 3b870172bf..023f4b4d7d 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -59,12 +59,12 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba err error ) - if c.Tty { - term, err = NewTtyConsole(c, pipes) + if c.ProcessConfig.Tty { + term, err = NewTtyConsole(&c.ProcessConfig, pipes) } else { - term, err = execdriver.NewStdConsole(c, pipes) + term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes) } - c.Terminal = term + c.ProcessConfig.Terminal = term c.Mounts = append(c.Mounts, execdriver.Mount{ Source: d.initPath, @@ -98,11 +98,11 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba "-mtu", strconv.Itoa(c.Network.Mtu), ) - if c.User != "" { - params = append(params, "-u", c.User) + if c.ProcessConfig.User != "" { + params = append(params, "-u", c.ProcessConfig.User) } - if c.Privileged { + if c.ProcessConfig.Privileged { if d.apparmor { params[0] = path.Join(d.root, "lxc-start-unconfined") @@ -122,8 +122,8 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba params = append(params, fmt.Sprintf("-cap-drop=%s", strings.Join(c.CapDrop, ":"))) } - params = append(params, "--", c.Entrypoint) - params = append(params, c.Arguments...) + params = append(params, "--", c.ProcessConfig.Entrypoint) + params = append(params, c.ProcessConfig.Arguments...) if d.sharedRoot { // lxc-start really needs / to be non-shared, or all kinds of stuff break @@ -149,14 +149,14 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba if err != nil { aname = name } - c.Path = aname - c.Args = append([]string{name}, arg...) + c.ProcessConfig.Path = aname + c.ProcessConfig.Args = append([]string{name}, arg...) if err := nodes.CreateDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil { return -1, err } - if err := c.Start(); err != nil { + if err := c.ProcessConfig.Start(); err != nil { return -1, err } @@ -166,7 +166,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba ) go func() { - if err := c.Wait(); err != nil { + if err := c.ProcessConfig.Wait(); err != nil { if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0 waitErr = err } @@ -177,9 +177,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba // Poll lxc for RUNNING status pid, err := d.waitForStart(c, waitLock) if err != nil { - if c.Process != nil { - c.Process.Kill() - c.Wait() + if c.ProcessConfig.Process != nil { + c.ProcessConfig.Process.Kill() + c.ProcessConfig.Wait() } return -1, err } @@ -187,7 +187,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba c.ContainerPid = pid if startCallback != nil { - startCallback(c) + startCallback(&c.ProcessConfig, pid) } <-waitLock @@ -198,10 +198,10 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba /// Return the exit code of the process // if the process has not exited -1 will be returned func getExitCode(c *execdriver.Command) int { - if c.ProcessState == nil { + if c.ProcessConfig.ProcessState == nil { return -1 } - return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + return c.ProcessConfig.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() } func (d *driver) Kill(c *execdriver.Command, sig int) error { @@ -442,7 +442,7 @@ func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { } func (d *driver) generateEnvConfig(c *execdriver.Command) error { - data, err := json.Marshal(c.Env) + data, err := json.Marshal(c.ProcessConfig.Env) if err != nil { return err } @@ -462,7 +462,7 @@ type TtyConsole struct { SlavePty *os.File } -func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyConsole, error) { +func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) { // lxc is special in that we cannot create the master outside of the container without // opening the slave because we have nothing to provide to the cmd. We have to open both then do // the crazy setup on command right now instead of passing the console path to lxc and telling it @@ -478,12 +478,12 @@ func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyCo SlavePty: ptySlave, } - if err := tty.AttachPipes(&command.Cmd, pipes); err != nil { + if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil { tty.Close() return nil, err } - command.Console = tty.SlavePty.Name() + processConfig.Console = tty.SlavePty.Name() return tty, nil } diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 229b0a5144..709c11b9ea 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -42,7 +42,7 @@ lxc.se_context = {{ .ProcessLabel}} # no controlling tty at all lxc.tty = 1 -{{if .Privileged}} +{{if .ProcessConfig.Privileged}} lxc.cgroup.devices.allow = a {{else}} # no implicit access to devices @@ -66,7 +66,7 @@ lxc.pivotdir = lxc_putold lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0 lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 -{{if .Tty}} +{{if .ProcessConfig.Tty}} lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0 {{end}} @@ -81,7 +81,7 @@ lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabS {{end}} {{end}} -{{if .Privileged}} +{{if .ProcessConfig.Privileged}} {{if .AppArmor}} lxc.aa_profile = unconfined {{else}} diff --git a/daemon/execdriver/lxc/lxc_template_unit_test.go b/daemon/execdriver/lxc/lxc_template_unit_test.go index 8acda804ee..2aa4bf6f9e 100644 --- a/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/daemon/execdriver/lxc/lxc_template_unit_test.go @@ -52,6 +52,7 @@ func TestLXCConfig(t *testing.T) { Interface: nil, }, AllowedDevices: make([]*devices.Device, 0), + ProcessConfig: execdriver.ProcessConfig{}, } p, err := driver.generateLXCConfig(command) if err != nil { @@ -77,9 +78,11 @@ func TestCustomLxcConfig(t *testing.T) { if err != nil { t.Fatal(err) } - command := &execdriver.Command{ - ID: "1", + processConfig := execdriver.ProcessConfig{ Privileged: false, + } + command := &execdriver.Command{ + ID: "1", Config: map[string][]string{ "lxc": { "lxc.utsname = docker", @@ -90,6 +93,7 @@ func TestCustomLxcConfig(t *testing.T) { Mtu: 1500, Interface: nil, }, + ProcessConfig: processConfig, } p, err := driver.generateLXCConfig(command) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index e475a1f2ad..8a13f23862 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -23,11 +23,11 @@ import ( func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, error) { container := template.New() - container.Hostname = getEnv("HOSTNAME", c.Env) - container.Tty = c.Tty - container.User = c.User + container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env) + container.Tty = c.ProcessConfig.Tty + container.User = c.ProcessConfig.User container.WorkingDir = c.WorkingDir - container.Env = c.Env + container.Env = c.ProcessConfig.Env container.Cgroups.Name = c.ID container.Cgroups.AllowedDevices = c.AllowedDevices container.MountConfig.DeviceNodes = c.AutoCreatedDevices @@ -40,7 +40,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e return nil, err } - if c.Privileged { + if c.ProcessConfig.Privileged { if err := d.setPrivileged(container); err != nil { return nil, err } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index c45188b6bc..1d20de73ea 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -68,26 +68,26 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba var term execdriver.Terminal - if c.Tty { - term, err = NewTtyConsole(c, pipes) + if c.ProcessConfig.Tty { + term, err = NewTtyConsole(&c.ProcessConfig, pipes) } else { - term, err = execdriver.NewStdConsole(c, pipes) + term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes) } if err != nil { return -1, err } - c.Terminal = term + c.ProcessConfig.Terminal = term d.Lock() d.activeContainers[c.ID] = &activeContainer{ container: container, - cmd: &c.Cmd, + cmd: &c.ProcessConfig.Cmd, } d.Unlock() var ( dataPath = filepath.Join(d.root, c.ID) - args = append([]string{c.Entrypoint}, c.Arguments...) + args = append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...) ) if err := d.createContainerRoot(c.ID); err != nil { @@ -99,9 +99,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba return -1, err } - return namespaces.Exec(container, c.Stdin, c.Stdout, c.Stderr, c.Console, c.Rootfs, dataPath, args, func(container *libcontainer.Config, console, rootfs, dataPath, init string, child *os.File, args []string) *exec.Cmd { - c.Path = d.initPath - c.Args = append([]string{ + return namespaces.Exec(container, c.ProcessConfig.Stdin, c.ProcessConfig.Stdout, c.ProcessConfig.Stderr, c.ProcessConfig.Console, c.Rootfs, dataPath, args, func(container *libcontainer.Config, console, rootfs, dataPath, init string, child *os.File, args []string) *exec.Cmd { + c.ProcessConfig.Path = d.initPath + c.ProcessConfig.Args = append([]string{ DriverName, "-console", console, "-pipe", "3", @@ -110,25 +110,25 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba }, args...) // set this to nil so that when we set the clone flags anything else is reset - c.SysProcAttr = &syscall.SysProcAttr{ + c.ProcessConfig.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)), } - c.ExtraFiles = []*os.File{child} + c.ProcessConfig.ExtraFiles = []*os.File{child} - c.Env = container.Env - c.Dir = c.Rootfs + c.ProcessConfig.Env = container.Env + c.ProcessConfig.Dir = c.Rootfs - return &c.Cmd + return &c.ProcessConfig.Cmd }, func() { if startCallback != nil { - c.ContainerPid = c.Process.Pid - startCallback(c) + c.ContainerPid = c.ProcessConfig.Process.Pid + startCallback(&c.ProcessConfig, c.ContainerPid) } }) } func (d *driver) Kill(p *execdriver.Command, sig int) error { - return syscall.Kill(p.Process.Pid, syscall.Signal(sig)) + return syscall.Kill(p.ProcessConfig.Process.Pid, syscall.Signal(sig)) } func (d *driver) Pause(c *execdriver.Command) error { @@ -176,14 +176,14 @@ func (d *driver) Terminate(p *execdriver.Command) error { state = &libcontainer.State{InitStartTime: string(data)} } - currentStartTime, err := system.GetProcessStartTime(p.Process.Pid) + currentStartTime, err := system.GetProcessStartTime(p.ProcessConfig.Process.Pid) if err != nil { return err } if state.InitStartTime == currentStartTime { - err = syscall.Kill(p.Process.Pid, 9) - syscall.Wait4(p.Process.Pid, nil, 0, nil) + err = syscall.Kill(p.ProcessConfig.Process.Pid, 9) + syscall.Wait4(p.ProcessConfig.Process.Pid, nil, 0, nil) } d.removeContainerRoot(p.ID) @@ -252,7 +252,7 @@ type TtyConsole struct { MasterPty *os.File } -func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyConsole, error) { +func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) { ptyMaster, console, err := consolepkg.CreateMasterAndConsole() if err != nil { return nil, err @@ -262,12 +262,12 @@ func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyCo MasterPty: ptyMaster, } - if err := tty.AttachPipes(&command.Cmd, pipes); err != nil { + if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil { tty.Close() return nil, err } - command.Console = console + processConfig.Console = console return tty, nil } diff --git a/daemon/execdriver/termconsole.go b/daemon/execdriver/termconsole.go index dc0e54ccdb..4dc18e5703 100644 --- a/daemon/execdriver/termconsole.go +++ b/daemon/execdriver/termconsole.go @@ -8,10 +8,10 @@ import ( type StdConsole struct { } -func NewStdConsole(command *Command, pipes *Pipes) (*StdConsole, error) { +func NewStdConsole(processConfig *ProcessConfig, pipes *Pipes) (*StdConsole, error) { std := &StdConsole{} - if err := std.AttachPipes(&command.Cmd, pipes); err != nil { + if err := std.AttachPipes(&processConfig.Cmd, pipes); err != nil { return nil, err } return std, nil diff --git a/daemon/monitor.go b/daemon/monitor.go index 3d5a3de771..e47245a86f 100644 --- a/daemon/monitor.go +++ b/daemon/monitor.go @@ -233,17 +233,17 @@ func (m *containerMonitor) shouldRestart(exitStatus int) bool { // callback ensures that the container's state is properly updated after we // received ack from the execution drivers -func (m *containerMonitor) callback(command *execdriver.Command) { - if command.Tty { +func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int) { + if processConfig.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace // which we close here. - if c, ok := command.Stdout.(io.Closer); ok { + if c, ok := processConfig.Stdout.(io.Closer); ok { c.Close() } } - m.container.State.setRunning(command.Pid()) + m.container.State.setRunning(pid) // signal that the process has started // close channel only if not closed @@ -282,8 +282,8 @@ func (m *containerMonitor) resetContainer(lock bool) { log.Errorf("%s: Error close stderr: %s", container.ID, err) } - if container.command != nil && container.command.Terminal != nil { - if err := container.command.Terminal.Close(); err != nil { + if container.command != nil && container.command.ProcessConfig.Terminal != nil { + if err := container.command.ProcessConfig.Terminal.Close(); err != nil { log.Errorf("%s: Error closing terminal: %s", container.ID, err) } } @@ -293,9 +293,9 @@ func (m *containerMonitor) resetContainer(lock bool) { container.stdin, container.stdinPipe = io.Pipe() } - c := container.command.Cmd + c := container.command.ProcessConfig.Cmd - container.command.Cmd = exec.Cmd{ + container.command.ProcessConfig.Cmd = exec.Cmd{ Stdin: c.Stdin, Stdout: c.Stdout, Stderr: c.Stderr,