Bladeren bron

Merge pull request #16305 from estesp/hooks-for-the-hooks-gods

Use libcontainer hook for network namespace info passing to libnetwork's sandbox
Jess Frazelle 9 jaren geleden
bovenliggende
commit
ac34ce0307

+ 3 - 2
daemon/container.go

@@ -811,7 +811,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error {
 	container.Lock()
 	container.Lock()
 	defer container.Unlock()
 	defer container.Unlock()
 
 
-	callback := func(processConfig *execdriver.ProcessConfig, pid int) {
+	callback := func(processConfig *execdriver.ProcessConfig, pid int) error {
 		if processConfig.Tty {
 		if processConfig.Tty {
 			// The callback is called after the process Start()
 			// The callback is called after the process Start()
 			// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
 			// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
@@ -821,6 +821,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error {
 			}
 			}
 		}
 		}
 		close(ExecConfig.waitStart)
 		close(ExecConfig.waitStart)
+		return nil
 	}
 	}
 
 
 	// We use a callback here instead of a goroutine and an chan for
 	// We use a callback here instead of a goroutine and an chan for
@@ -837,7 +838,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error {
 	return nil
 	return nil
 }
 }
 
 
-func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.StartCallback) error {
+func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.DriverCallback) error {
 	var (
 	var (
 		err      error
 		err      error
 		exitCode int
 		exitCode int

+ 21 - 2
daemon/container_unix.go

@@ -174,8 +174,9 @@ func getDevicesFromPath(deviceMapping runconfig.DeviceMapping) (devs []*configs.
 func populateCommand(c *Container, env []string) error {
 func populateCommand(c *Container, env []string) error {
 	var en *execdriver.Network
 	var en *execdriver.Network
 	if !c.Config.NetworkDisabled {
 	if !c.Config.NetworkDisabled {
-		en = &execdriver.Network{
-			NamespacePath: c.NetworkSettings.SandboxKey,
+		en = &execdriver.Network{}
+		if !c.daemon.execDriver.SupportsHooks() || c.hostConfig.NetworkMode.IsHost() {
+			en.NamespacePath = c.NetworkSettings.SandboxKey
 		}
 		}
 
 
 		parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2)
 		parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2)
@@ -405,6 +406,10 @@ func (container *Container) buildSandboxOptions() ([]libnetwork.SandboxOption, e
 		sboxOptions = append(sboxOptions, libnetwork.OptionUseDefaultSandbox())
 		sboxOptions = append(sboxOptions, libnetwork.OptionUseDefaultSandbox())
 		sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts"))
 		sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts"))
 		sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"))
 		sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"))
+	} else if container.daemon.execDriver.SupportsHooks() {
+		// OptionUseExternalKey is mandatory for userns support.
+		// But optional for non-userns support
+		sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey())
 	}
 	}
 
 
 	container.HostsPath, err = container.getRootResourcePath("hosts")
 	container.HostsPath, err = container.getRootResourcePath("hosts")
@@ -947,6 +952,20 @@ func (container *Container) initializeNetworking() error {
 	return container.buildHostnameFile()
 	return container.buildHostnameFile()
 }
 }
 
 
+// called from the libcontainer pre-start hook to set the network
+// namespace configuration linkage to the libnetwork "sandbox" entity
+func (container *Container) setNetworkNamespaceKey(pid int) error {
+	path := fmt.Sprintf("/proc/%d/ns/net", pid)
+	var sandbox libnetwork.Sandbox
+	search := libnetwork.SandboxContainerWalker(&sandbox, container.ID)
+	container.daemon.netController.WalkSandboxes(search)
+	if sandbox == nil {
+		return fmt.Errorf("no sandbox present for %s", container.ID)
+	}
+
+	return sandbox.SetKey(path)
+}
+
 func (container *Container) getIpcContainer() (*Container, error) {
 func (container *Container) getIpcContainer() (*Container, error) {
 	containerID := container.hostConfig.IpcMode.Container()
 	containerID := container.hostConfig.IpcMode.Container()
 	c, err := container.daemon.Get(containerID)
 	c, err := container.daemon.Get(containerID)

+ 5 - 0
daemon/container_windows.go

@@ -138,6 +138,11 @@ func (container *Container) getSize() (int64, int64) {
 	return 0, 0
 	return 0, 0
 }
 }
 
 
+// setNetworkNamespaceKey is a no-op on Windows.
+func (container *Container) setNetworkNamespaceKey(pid int) error {
+	return nil
+}
+
 // allocateNetwork is a no-op on Windows.
 // allocateNetwork is a no-op on Windows.
 func (container *Container) allocateNetwork() error {
 func (container *Container) allocateNetwork() error {
 	return nil
 	return nil

+ 8 - 2
daemon/daemon.go

@@ -875,8 +875,14 @@ func (daemon *Daemon) unmount(container *Container) error {
 	return nil
 	return nil
 }
 }
 
 
-func (daemon *Daemon) run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
-	return daemon.execDriver.Run(c.command, pipes, startCallback)
+func (daemon *Daemon) run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (execdriver.ExitStatus, error) {
+	hooks := execdriver.Hooks{
+		Start: startCallback,
+	}
+	hooks.PreStart = append(hooks.PreStart, func(processConfig *execdriver.ProcessConfig, pid int) error {
+		return c.setNetworkNamespaceKey(pid)
+	})
+	return daemon.execDriver.Run(c.command, pipes, hooks)
 }
 }
 
 
 func (daemon *Daemon) kill(c *Container, sig int) error {
 func (daemon *Daemon) kill(c *Container, sig int) error {

+ 5 - 2
daemon/exec.go

@@ -267,8 +267,11 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout
 }
 }
 
 
 // Exec calls the underlying exec driver to run
 // Exec calls the underlying exec driver to run
-func (d *Daemon) Exec(c *Container, ExecConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
-	exitStatus, err := d.execDriver.Exec(c.command, ExecConfig.ProcessConfig, pipes, startCallback)
+func (d *Daemon) Exec(c *Container, ExecConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (int, error) {
+	hooks := execdriver.Hooks{
+		Start: startCallback,
+	}
+	exitStatus, err := d.execDriver.Exec(c.command, ExecConfig.ProcessConfig, pipes, hooks)
 
 
 	// On err, make sure we don't leave ExitCode at zero
 	// On err, make sure we don't leave ExitCode at zero
 	if err != nil && exitStatus == 0 {
 	if err != nil && exitStatus == 0 {

+ 21 - 6
daemon/execdriver/driver.go

@@ -24,10 +24,22 @@ var (
 	ErrDriverNotFound          = errors.New("The requested docker init has not been found")
 	ErrDriverNotFound          = errors.New("The requested docker init has not been found")
 )
 )
 
 
-// StartCallback defines a callback function.
-// It's used by 'Run' and 'Exec', does some work in parent process
-// after child process is started.
-type StartCallback func(*ProcessConfig, int)
+// DriverCallback defines a callback function which is used in "Run" and "Exec".
+// This allows work to be done in the parent process when the child is passing
+// through PreStart, Start and PostStop events.
+// Callbacks are provided a processConfig pointer and the pid of the child
+type DriverCallback func(processConfig *ProcessConfig, pid int) error
+
+// Hooks is a struct containing function pointers to callbacks
+// used by any execdriver implementation exploiting hooks capabilities
+type Hooks struct {
+	// PreStart is called before container's CMD/ENTRYPOINT is executed
+	PreStart []DriverCallback
+	// Start is called after the container's process is full started
+	Start DriverCallback
+	// PostStop is called after the container process exits
+	PostStop []DriverCallback
+}
 
 
 // Info is driver specific information based on
 // Info is driver specific information based on
 // processes registered with the driver
 // processes registered with the driver
@@ -56,11 +68,11 @@ type ExitStatus struct {
 type Driver interface {
 type Driver interface {
 	// Run executes the process, blocks until the process exits and returns
 	// Run executes the process, blocks until the process exits and returns
 	// the exit code. It's the last stage on Docker side for running a container.
 	// the exit code. It's the last stage on Docker side for running a container.
-	Run(c *Command, pipes *Pipes, startCallback StartCallback) (ExitStatus, error)
+	Run(c *Command, pipes *Pipes, hooks Hooks) (ExitStatus, error)
 
 
 	// Exec executes the process in an existing container, blocks until the
 	// Exec executes the process in an existing container, blocks until the
 	// process exits and returns the exit code.
 	// process exits and returns the exit code.
-	Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error)
+	Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, hooks Hooks) (int, error)
 
 
 	// Kill sends signals to process in container.
 	// Kill sends signals to process in container.
 	Kill(c *Command, sig int) error
 	Kill(c *Command, sig int) error
@@ -89,6 +101,9 @@ type Driver interface {
 
 
 	// Stats returns resource stats for a running container
 	// Stats returns resource stats for a running container
 	Stats(id string) (*ResourceStats, error)
 	Stats(id string) (*ResourceStats, error)
+
+	// SupportsHooks refers to the driver capability to exploit pre/post hook functionality
+	SupportsHooks() bool
 }
 }
 
 
 // Ipc settings of the container
 // Ipc settings of the container

+ 10 - 4
daemon/execdriver/lxc/driver.go

@@ -125,7 +125,7 @@ func killNetNsProc(proc *os.Process) {
 
 
 // Run implements the exec driver Driver interface,
 // Run implements the exec driver Driver interface,
 // it calls 'exec.Cmd' to launch lxc commands to run a container.
 // it calls 'exec.Cmd' to launch lxc commands to run a container.
-func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
+func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
 	var (
 	var (
 		term     execdriver.Terminal
 		term     execdriver.Terminal
 		err      error
 		err      error
@@ -324,9 +324,9 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
 
 
 	c.ContainerPid = pid
 	c.ContainerPid = pid
 
 
-	if startCallback != nil {
+	if hooks.Start != nil {
 		logrus.Debugf("Invoking startCallback")
 		logrus.Debugf("Invoking startCallback")
-		startCallback(&c.ProcessConfig, pid)
+		hooks.Start(&c.ProcessConfig, pid)
 	}
 	}
 
 
 	oomKill := false
 	oomKill := false
@@ -870,7 +870,7 @@ func (t *TtyConsole) Close() error {
 
 
 // Exec implements the exec driver Driver interface,
 // Exec implements the exec driver Driver interface,
 // it is not implemented by lxc.
 // it is not implemented by lxc.
-func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
+func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
 	return -1, ErrExec
 	return -1, ErrExec
 }
 }
 
 
@@ -883,3 +883,9 @@ func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
 	}
 	}
 	return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
 	return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
 }
 }
+
+// SupportsHooks implements the execdriver Driver interface.
+// The LXC execdriver does not support the hook mechanism, which is currently unique to runC/libcontainer.
+func (d *Driver) SupportsHooks() bool {
+	return false
+}

+ 22 - 7
daemon/execdriver/native/create.go

@@ -18,7 +18,7 @@ import (
 
 
 // createContainer populates and configures the container type with the
 // createContainer populates and configures the container type with the
 // data provided by the execdriver.Command
 // data provided by the execdriver.Command
-func (d *Driver) createContainer(c *execdriver.Command) (*configs.Config, error) {
+func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (*configs.Config, error) {
 	container := execdriver.InitContainer(c)
 	container := execdriver.InitContainer(c)
 
 
 	if err := d.createIpc(container, c); err != nil {
 	if err := d.createIpc(container, c); err != nil {
@@ -33,7 +33,7 @@ func (d *Driver) createContainer(c *execdriver.Command) (*configs.Config, error)
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	if err := d.createNetwork(container, c); err != nil {
+	if err := d.createNetwork(container, c, hooks); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
@@ -113,7 +113,7 @@ func generateIfaceName() (string, error) {
 	return "", errors.New("Failed to find name for new interface")
 	return "", errors.New("Failed to find name for new interface")
 }
 }
 
 
-func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command) error {
+func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command, hooks execdriver.Hooks) error {
 	if c.Network == nil {
 	if c.Network == nil {
 		return nil
 		return nil
 	}
 	}
@@ -135,11 +135,26 @@ func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command)
 		return nil
 		return nil
 	}
 	}
 
 
-	if c.Network.NamespacePath == "" {
-		return fmt.Errorf("network namespace path is empty")
+	if c.Network.NamespacePath != "" {
+		container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath)
+		return nil
+	}
+	// only set up prestart hook if the namespace path is not set (this should be
+	// all cases *except* for --net=host shared networking)
+	container.Hooks = &configs.Hooks{
+		Prestart: []configs.Hook{
+			configs.NewFunctionHook(func(s configs.HookState) error {
+				if len(hooks.PreStart) > 0 {
+					for _, fnHook := range hooks.PreStart {
+						if err := fnHook(&c.ProcessConfig, s.Pid); err != nil {
+							return err
+						}
+					}
+				}
+				return nil
+			}),
+		},
 	}
 	}
-
-	container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath)
 	return nil
 	return nil
 }
 }
 
 

+ 10 - 4
daemon/execdriver/native/driver.go

@@ -131,9 +131,9 @@ type execOutput struct {
 
 
 // Run implements the exec driver Driver interface,
 // Run implements the exec driver Driver interface,
 // it calls libcontainer APIs to run a container.
 // it calls libcontainer APIs to run a container.
-func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
+func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
 	// take the Command and populate the libcontainer.Config from it
 	// take the Command and populate the libcontainer.Config from it
-	container, err := d.createContainer(c)
+	container, err := d.createContainer(c, hooks)
 	if err != nil {
 	if err != nil {
 		return execdriver.ExitStatus{ExitCode: -1}, err
 		return execdriver.ExitStatus{ExitCode: -1}, err
 	}
 	}
@@ -165,14 +165,14 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
 		return execdriver.ExitStatus{ExitCode: -1}, err
 		return execdriver.ExitStatus{ExitCode: -1}, err
 	}
 	}
 
 
-	if startCallback != nil {
+	if hooks.Start != nil {
 		pid, err := p.Pid()
 		pid, err := p.Pid()
 		if err != nil {
 		if err != nil {
 			p.Signal(os.Kill)
 			p.Signal(os.Kill)
 			p.Wait()
 			p.Wait()
 			return execdriver.ExitStatus{ExitCode: -1}, err
 			return execdriver.ExitStatus{ExitCode: -1}, err
 		}
 		}
-		startCallback(&c.ProcessConfig, pid)
+		hooks.Start(&c.ProcessConfig, pid)
 	}
 	}
 
 
 	oom := notifyOnOOM(cont)
 	oom := notifyOnOOM(cont)
@@ -477,3 +477,9 @@ func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConf
 	processConfig.Terminal = term
 	processConfig.Terminal = term
 	return nil
 	return nil
 }
 }
+
+// SupportsHooks implements the execdriver Driver interface.
+// The libcontainer/runC-based native execdriver does exploit the hook mechanism
+func (d *Driver) SupportsHooks() bool {
+	return true
+}

+ 3 - 3
daemon/execdriver/native/exec.go

@@ -19,7 +19,7 @@ import (
 
 
 // Exec implements the exec driver Driver interface,
 // Exec implements the exec driver Driver interface,
 // it calls libcontainer APIs to execute a container.
 // it calls libcontainer APIs to execute a container.
-func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
+func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
 	active := d.activeContainers[c.ID]
 	active := d.activeContainers[c.ID]
 	if active == nil {
 	if active == nil {
 		return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
 		return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
@@ -45,14 +45,14 @@ func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
 		return -1, err
 		return -1, err
 	}
 	}
 
 
-	if startCallback != nil {
+	if hooks.Start != nil {
 		pid, err := p.Pid()
 		pid, err := p.Pid()
 		if err != nil {
 		if err != nil {
 			p.Signal(os.Kill)
 			p.Signal(os.Kill)
 			p.Wait()
 			p.Wait()
 			return -1, err
 			return -1, err
 		}
 		}
-		startCallback(&c.ProcessConfig, pid)
+		hooks.Start(&c.ProcessConfig, pid)
 	}
 	}
 
 
 	ps, err := p.Wait()
 	ps, err := p.Wait()

+ 3 - 3
daemon/execdriver/windows/exec.go

@@ -12,7 +12,7 @@ import (
 )
 )
 
 
 // Exec implements the exec driver Driver interface.
 // Exec implements the exec driver Driver interface.
-func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
+func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
 
 
 	var (
 	var (
 		term     execdriver.Terminal
 		term     execdriver.Terminal
@@ -69,8 +69,8 @@ func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
 	processConfig.Terminal = term
 	processConfig.Terminal = term
 
 
 	// Invoke the start callback
 	// Invoke the start callback
-	if startCallback != nil {
-		startCallback(&c.ProcessConfig, int(pid))
+	if hooks.Start != nil {
+		hooks.Start(&c.ProcessConfig, int(pid))
 	}
 	}
 
 
 	if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil {
 	if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil {

+ 9 - 4
daemon/execdriver/windows/run.go

@@ -77,7 +77,7 @@ type containerInit struct {
 const defaultOwner = "docker"
 const defaultOwner = "docker"
 
 
 // Run implements the exec driver Driver interface
 // Run implements the exec driver Driver interface
-func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
+func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
 
 
 	var (
 	var (
 		term execdriver.Terminal
 		term execdriver.Terminal
@@ -290,9 +290,8 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
 	}
 	}
 	d.Unlock()
 	d.Unlock()
 
 
-	// Invoke the start callback
-	if startCallback != nil {
-		startCallback(&c.ProcessConfig, int(pid))
+	if hooks.Start != nil {
+		hooks.Start(&c.ProcessConfig, int(pid))
 	}
 	}
 
 
 	var exitCode int32
 	var exitCode int32
@@ -305,3 +304,9 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
 	logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
 	logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
 	return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
 	return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
 }
 }
+
+// SupportsHooks implements the execdriver Driver interface.
+// The windows driver does not support the hook mechanism
+func (d *Driver) SupportsHooks() bool {
+	return false
+}

+ 2 - 1
daemon/monitor.go

@@ -250,7 +250,7 @@ func (m *containerMonitor) shouldRestart(exitCode int) bool {
 
 
 // callback ensures that the container's state is properly updated after we
 // callback ensures that the container's state is properly updated after we
 // received ack from the execution drivers
 // received ack from the execution drivers
-func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int) {
+func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int) error {
 	if processConfig.Tty {
 	if processConfig.Tty {
 		// The callback is called after the process Start()
 		// The callback is called after the process Start()
 		// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
 		// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
@@ -273,6 +273,7 @@ func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid
 	if err := m.container.toDiskLocking(); err != nil {
 	if err := m.container.toDiskLocking(); err != nil {
 		logrus.Errorf("Error saving container to disk: %v", err)
 		logrus.Errorf("Error saving container to disk: %v", err)
 	}
 	}
+	return nil
 }
 }
 
 
 // resetContainer resets the container's IO and ensures that the command is able to be executed again
 // resetContainer resets the container's IO and ensures that the command is able to be executed again