mirror of
https://github.com/containers/podman.git
synced 2025-07-04 10:10:32 +08:00
Merge pull request #5429 from mheon/revert_exec_changes
Revert exec changes
This commit is contained in:
@ -282,24 +282,13 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri
|
|||||||
opts.Resize = resize
|
opts.Resize = resize
|
||||||
opts.DetachKeys = detachKeys
|
opts.DetachKeys = detachKeys
|
||||||
|
|
||||||
pid := 0
|
pid, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts)
|
||||||
pipeDataChan, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts)
|
|
||||||
// if pipeDataChan isn't nil, we should set the err
|
|
||||||
if pipeDataChan != nil {
|
|
||||||
pidData := <-pipeDataChan
|
|
||||||
if pidData.err != nil {
|
|
||||||
err = pidData.err
|
|
||||||
}
|
|
||||||
pid = pidData.data
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec := define.ExecErrorCodeGeneric
|
ec := define.ExecErrorCodeGeneric
|
||||||
// Conmon will pass a non-zero exit code from the runtime as a pid here.
|
// Conmon will pass a non-zero exit code from the runtime as a pid here.
|
||||||
// we differentiate a pid with an exit code by sending it as negative, so reverse
|
// we differentiate a pid with an exit code by sending it as negative, so reverse
|
||||||
// that change and return the exit code the runtime failed with.
|
// that change and return the exit code the runtime failed with.
|
||||||
// Make sure the value is not ErrorConmonRead, as that is a podman set bogus value
|
if pid < 0 {
|
||||||
// and not sent by conmon (and thus has no special meaning)
|
|
||||||
if pid < 0 && pid != define.ErrorConmonRead {
|
|
||||||
ec = -1 * pid
|
ec = -1 * pid
|
||||||
}
|
}
|
||||||
return ec, err
|
return ec, err
|
||||||
@ -329,24 +318,18 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri
|
|||||||
|
|
||||||
lastErr := <-attachChan
|
lastErr := <-attachChan
|
||||||
|
|
||||||
exitCodeData := <-pipeDataChan
|
exitCode, err := c.readExecExitCode(sessionID)
|
||||||
if exitCodeData.err != nil {
|
if err != nil {
|
||||||
if lastErr != nil {
|
if lastErr != nil {
|
||||||
logrus.Errorf(lastErr.Error())
|
logrus.Errorf(lastErr.Error())
|
||||||
}
|
}
|
||||||
lastErr = exitCodeData.err
|
lastErr = err
|
||||||
}
|
}
|
||||||
if exitCodeData.data != 0 {
|
if exitCode != 0 {
|
||||||
if lastErr != nil {
|
if lastErr != nil {
|
||||||
logrus.Errorf(lastErr.Error())
|
logrus.Errorf(lastErr.Error())
|
||||||
}
|
}
|
||||||
// ErrorConmonRead is a bogus value set by podman to indicate reading a value from
|
lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCode)
|
||||||
// conmon failed. Since it is specifically not a valid exit code, we should set
|
|
||||||
// a generic error here
|
|
||||||
if exitCodeData.data == define.ErrorConmonRead {
|
|
||||||
exitCodeData.data = define.ExecErrorCodeGeneric
|
|
||||||
}
|
|
||||||
lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCodeData.data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock again
|
// Lock again
|
||||||
@ -357,7 +340,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri
|
|||||||
// Sync the container again to pick up changes in state
|
// Sync the container again to pick up changes in state
|
||||||
if err := c.syncContainer(); err != nil {
|
if err := c.syncContainer(); err != nil {
|
||||||
logrus.Errorf("error syncing container %s state to remove exec session %s", c.ID(), sessionID)
|
logrus.Errorf("error syncing container %s state to remove exec session %s", c.ID(), sessionID)
|
||||||
return exitCodeData.data, lastErr
|
return exitCode, lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the exec session from state
|
// Remove the exec session from state
|
||||||
@ -365,7 +348,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri
|
|||||||
if err := c.save(); err != nil {
|
if err := c.save(); err != nil {
|
||||||
logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err)
|
logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err)
|
||||||
}
|
}
|
||||||
return exitCodeData.data, lastErr
|
return exitCode, lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachStreams contains streams that will be attached to the container
|
// AttachStreams contains streams that will be attached to the container
|
||||||
|
@ -206,6 +206,28 @@ func (c *Container) execOCILog(sessionID string) string {
|
|||||||
return filepath.Join(c.execBundlePath(sessionID), "oci-log")
|
return filepath.Join(c.execBundlePath(sessionID), "oci-log")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readExecExitCode reads the exit file for an exec session and returns
|
||||||
|
// the exit code
|
||||||
|
func (c *Container) readExecExitCode(sessionID string) (int, error) {
|
||||||
|
exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID())
|
||||||
|
chWait := make(chan error)
|
||||||
|
defer close(chWait)
|
||||||
|
|
||||||
|
_, err := WaitForFile(exitFile, chWait, time.Second*5)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
ec, err := ioutil.ReadFile(exitFile)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
ecInt, err := strconv.Atoi(string(ec))
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return ecInt, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the container's exit file to appear.
|
// Wait for the container's exit file to appear.
|
||||||
// When it does, update our state based on it.
|
// When it does, update our state based on it.
|
||||||
func (c *Container) waitForExitFileAndSync() error {
|
func (c *Container) waitForExitFileAndSync() error {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package define
|
package define
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -18,11 +17,6 @@ const (
|
|||||||
ExecErrorCodeCannotInvoke = 126
|
ExecErrorCodeCannotInvoke = 126
|
||||||
// ExecErrorCodeNotFound is the error code to return when a command cannot be found
|
// ExecErrorCodeNotFound is the error code to return when a command cannot be found
|
||||||
ExecErrorCodeNotFound = 127
|
ExecErrorCodeNotFound = 127
|
||||||
// ErrorConmonRead is a bogus value that can neither be a valid PID or exit code. It is
|
|
||||||
// used because conmon will send a negative value when sending a PID back over a pipe FD
|
|
||||||
// to signify something went wrong in the runtime. We need to differentiate between that
|
|
||||||
// value and a failure on the podman side of reading that value. Thus, we use ErrorConmonRead
|
|
||||||
ErrorConmonRead = math.MinInt32 - 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TranslateExecErrorToExitCode takes an error and checks whether it
|
// TranslateExecErrorToExitCode takes an error and checks whether it
|
||||||
|
@ -70,7 +70,7 @@ type OCIRuntime interface {
|
|||||||
// ExecContainer executes a command in a running container.
|
// ExecContainer executes a command in a running container.
|
||||||
// Returns an int (exit code), error channel (errors from attach), and
|
// Returns an int (exit code), error channel (errors from attach), and
|
||||||
// error (errors that occurred attempting to start the exec session).
|
// error (errors that occurred attempting to start the exec session).
|
||||||
ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error)
|
ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error)
|
||||||
// ExecStopContainer stops a given exec session in a running container.
|
// ExecStopContainer stops a given exec session in a running container.
|
||||||
// SIGTERM with be sent initially, then SIGKILL after the given timeout.
|
// SIGTERM with be sent initially, then SIGKILL after the given timeout.
|
||||||
// If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will
|
// If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will
|
||||||
@ -159,10 +159,3 @@ type HTTPAttachStreams struct {
|
|||||||
Stdout bool
|
Stdout bool
|
||||||
Stderr bool
|
Stderr bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataAndErr is a generic structure for passing around an int and an error
|
|
||||||
// it is especially useful for getting information from conmon
|
|
||||||
type DataAndErr struct {
|
|
||||||
data int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
@ -119,8 +119,8 @@ func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-c
|
|||||||
socketPath := buildSocketPath(sockPath)
|
socketPath := buildSocketPath(sockPath)
|
||||||
|
|
||||||
// 2: read from attachFd that the parent process has set up the console socket
|
// 2: read from attachFd that the parent process has set up the console socket
|
||||||
if pipeData := readConmonPipeData(attachFd, ""); pipeData.err != nil {
|
if _, err := readConmonPipeData(attachFd, ""); err != nil {
|
||||||
return pipeData.err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2: then attach
|
// 2: then attach
|
||||||
|
@ -595,29 +595,31 @@ func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize remotecommand.Te
|
|||||||
|
|
||||||
// ExecContainer executes a command in a running container
|
// ExecContainer executes a command in a running container
|
||||||
// TODO: Split into Create/Start/Attach/Wait
|
// TODO: Split into Create/Start/Attach/Wait
|
||||||
func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) {
|
func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (int, chan error, error) {
|
||||||
if options == nil {
|
if options == nil {
|
||||||
return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer")
|
return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer")
|
||||||
}
|
}
|
||||||
if len(options.Cmd) == 0 {
|
if len(options.Cmd) == 0 {
|
||||||
return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute")
|
return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionID == "" {
|
if sessionID == "" {
|
||||||
return nil, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec")
|
return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec")
|
||||||
}
|
}
|
||||||
|
|
||||||
// create sync pipe to receive the pid
|
// create sync pipe to receive the pid
|
||||||
parentSyncPipe, childSyncPipe, err := newPipe()
|
parentSyncPipe, childSyncPipe, err := newPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrapf(err, "error creating socket pair")
|
return -1, nil, errors.Wrapf(err, "error creating socket pair")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer errorhandling.CloseQuiet(parentSyncPipe)
|
||||||
|
|
||||||
// create start pipe to set the cgroup before running
|
// create start pipe to set the cgroup before running
|
||||||
// attachToExec is responsible for closing parentStartPipe
|
// attachToExec is responsible for closing parentStartPipe
|
||||||
childStartPipe, parentStartPipe, err := newPipe()
|
childStartPipe, parentStartPipe, err := newPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrapf(err, "error creating socket pair")
|
return -1, nil, errors.Wrapf(err, "error creating socket pair")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to make sure we close the parent{Start,Attach}Pipes if we fail
|
// We want to make sure we close the parent{Start,Attach}Pipes if we fail
|
||||||
@ -636,7 +638,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
|
|||||||
// attachToExec is responsible for closing parentAttachPipe
|
// attachToExec is responsible for closing parentAttachPipe
|
||||||
parentAttachPipe, childAttachPipe, err := newPipe()
|
parentAttachPipe, childAttachPipe, err := newPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrapf(err, "error creating socket pair")
|
return -1, nil, errors.Wrapf(err, "error creating socket pair")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -656,7 +658,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
|
|||||||
|
|
||||||
runtimeDir, err := util.GetRuntimeDir()
|
runtimeDir, err := util.GetRuntimeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return -1, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
finalEnv := make([]string, 0, len(options.Env))
|
finalEnv := make([]string, 0, len(options.Env))
|
||||||
@ -666,7 +668,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
|
|||||||
|
|
||||||
processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID)
|
processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return -1, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ociLog string
|
var ociLog string
|
||||||
@ -715,7 +717,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
|
|||||||
|
|
||||||
conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir)
|
conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return -1, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.PreserveFDs > 0 {
|
if options.PreserveFDs > 0 {
|
||||||
@ -746,10 +748,10 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
|
|||||||
childrenClosed = true
|
childrenClosed = true
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrapf(err, "cannot start container %s", c.ID())
|
return -1, nil, errors.Wrapf(err, "cannot start container %s", c.ID())
|
||||||
}
|
}
|
||||||
if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil {
|
if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil {
|
||||||
return nil, nil, err
|
return -1, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.PreserveFDs > 0 {
|
if options.PreserveFDs > 0 {
|
||||||
@ -772,16 +774,9 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
|
|||||||
}()
|
}()
|
||||||
attachToExecCalled = true
|
attachToExecCalled = true
|
||||||
|
|
||||||
dataChan := make(chan DataAndErr)
|
pid, err := readConmonPipeData(parentSyncPipe, ociLog)
|
||||||
go func() {
|
|
||||||
// read the exec pid
|
|
||||||
dataChan <- readConmonPipeData(parentSyncPipe, ociLog)
|
|
||||||
// read the exec exit code
|
|
||||||
dataChan <- readConmonPipeData(parentSyncPipe, ociLog)
|
|
||||||
errorhandling.CloseQuiet(parentSyncPipe)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return dataChan, attachChan, err
|
return pid, attachChan, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecStopContainer stops a given exec session in a running container.
|
// ExecStopContainer stops a given exec session in a running container.
|
||||||
@ -1211,14 +1206,14 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeData := readConmonPipeData(parentSyncPipe, ociLog)
|
pid, err := readConmonPipeData(parentSyncPipe, ociLog)
|
||||||
if pipeData.err != nil {
|
if err != nil {
|
||||||
if err2 := r.DeleteContainer(ctr); err2 != nil {
|
if err2 := r.DeleteContainer(ctr); err2 != nil {
|
||||||
logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID())
|
logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID())
|
||||||
}
|
}
|
||||||
return pipeData.err
|
return err
|
||||||
}
|
}
|
||||||
ctr.state.PID = pipeData.data
|
ctr.state.PID = pid
|
||||||
|
|
||||||
conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile)
|
conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1530,7 +1525,7 @@ func readConmonPidFile(pidFile string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readConmonPipeData attempts to read a syncInfo struct from the pipe
|
// readConmonPipeData attempts to read a syncInfo struct from the pipe
|
||||||
func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr {
|
func readConmonPipeData(pipe *os.File, ociLog string) (int, error) {
|
||||||
// syncInfo is used to return data from monitor process to daemon
|
// syncInfo is used to return data from monitor process to daemon
|
||||||
type syncInfo struct {
|
type syncInfo struct {
|
||||||
Data int `json:"data"`
|
Data int `json:"data"`
|
||||||
@ -1557,7 +1552,7 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr {
|
|||||||
ch <- syncStruct{si: si}
|
ch <- syncStruct{si: si}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
data := define.ErrorConmonRead
|
data := -1
|
||||||
select {
|
select {
|
||||||
case ss := <-ch:
|
case ss := <-ch:
|
||||||
if ss.err != nil {
|
if ss.err != nil {
|
||||||
@ -1566,17 +1561,11 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
var ociErr ociError
|
var ociErr ociError
|
||||||
if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
|
if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
|
||||||
return DataAndErr{
|
return -1, getOCIRuntimeError(ociErr.Msg)
|
||||||
data: data,
|
|
||||||
err: getOCIRuntimeError(ociErr.Msg),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return DataAndErr{
|
return -1, errors.Wrapf(ss.err, "container create failed (no logs from conmon)")
|
||||||
data: data,
|
|
||||||
err: errors.Wrapf(ss.err, "container create failed (no logs from conmon)"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logrus.Debugf("Received: %d", ss.si.Data)
|
logrus.Debugf("Received: %d", ss.si.Data)
|
||||||
if ss.si.Data < 0 {
|
if ss.si.Data < 0 {
|
||||||
@ -1585,36 +1574,21 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
var ociErr ociError
|
var ociErr ociError
|
||||||
if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
|
if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
|
||||||
return DataAndErr{
|
return ss.si.Data, getOCIRuntimeError(ociErr.Msg)
|
||||||
data: ss.si.Data,
|
|
||||||
err: getOCIRuntimeError(ociErr.Msg),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we failed to parse the JSON errors, then print the output as it is
|
// If we failed to parse the JSON errors, then print the output as it is
|
||||||
if ss.si.Message != "" {
|
if ss.si.Message != "" {
|
||||||
return DataAndErr{
|
return ss.si.Data, getOCIRuntimeError(ss.si.Message)
|
||||||
data: ss.si.Data,
|
|
||||||
err: getOCIRuntimeError(ss.si.Message),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return DataAndErr{
|
|
||||||
data: ss.si.Data,
|
|
||||||
err: errors.Wrapf(define.ErrInternal, "container create failed"),
|
|
||||||
}
|
}
|
||||||
|
return ss.si.Data, errors.Wrapf(define.ErrInternal, "container create failed")
|
||||||
}
|
}
|
||||||
data = ss.si.Data
|
data = ss.si.Data
|
||||||
case <-time.After(define.ContainerCreateTimeout):
|
case <-time.After(define.ContainerCreateTimeout):
|
||||||
return DataAndErr{
|
return -1, errors.Wrapf(define.ErrInternal, "container creation timeout")
|
||||||
data: data,
|
|
||||||
err: errors.Wrapf(define.ErrInternal, "container creation timeout"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return DataAndErr{
|
|
||||||
data: data,
|
|
||||||
err: nil,
|
|
||||||
}
|
}
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeConmonPipeData writes nonse data to a pipe
|
// writeConmonPipeData writes nonse data to a pipe
|
||||||
|
@ -121,8 +121,8 @@ func (r *MissingRuntime) AttachResize(ctr *Container, newSize remotecommand.Term
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExecContainer is not available as the runtime is missing
|
// ExecContainer is not available as the runtime is missing
|
||||||
func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) {
|
func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) {
|
||||||
return nil, nil, r.printError()
|
return -1, nil, r.printError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecStopContainer is not available as the runtime is missing.
|
// ExecStopContainer is not available as the runtime is missing.
|
||||||
|
Reference in New Issue
Block a user