diff --git a/executor/containerdexecutor/executor.go b/executor/containerdexecutor/executor.go index 83de3b14..214bec62 100644 --- a/executor/containerdexecutor/executor.go +++ b/executor/containerdexecutor/executor.go @@ -172,6 +172,7 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root cache.Moun return err } defer cleanup() + spec.Process.Terminal = meta.Tty container, err := w.client.NewContainer(ctx, id, containerd.WithSpec(spec), @@ -195,59 +196,24 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root cache.Moun if err != nil { return err } + defer func() { if _, err1 := task.Delete(context.TODO()); err == nil && err1 != nil { err = errors.Wrapf(err1, "failed to delete task %s", id) } }() - if err := task.Start(ctx); err != nil { - return err - } - - if started != nil { + err = w.runProcess(ctx, task, process.Resize, func() { startedOnce.Do(func() { - close(started) + if started != nil { + close(started) + } }) - } - statusCh, err := task.Wait(context.Background()) - if err != nil { - return err - } - - var cancel func() - ctxDone := ctx.Done() - for { - select { - case <-ctxDone: - ctxDone = nil - var killCtx context.Context - killCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) - task.Kill(killCtx, syscall.SIGKILL) - case status := <-statusCh: - if cancel != nil { - cancel() - } - if status.ExitCode() != 0 { - var err error - if status.ExitCode() == containerd.UnknownExitStatus && status.Error() != nil { - err = errors.Wrap(status.Error(), "failure waiting for process") - } else { - err = errors.Errorf("process returned non-zero exit code: %d", status.ExitCode()) - } - select { - case <-ctx.Done(): - err = errors.Wrap(ctx.Err(), err.Error()) - default: - } - return err - } - return nil - } - } + }) + return err } -func (w *containerdExecutor) Exec(ctx context.Context, id string, process executor.ProcessInfo) error { +func (w *containerdExecutor) Exec(ctx context.Context, id string, process executor.ProcessInfo) (err error) { meta := process.Meta // first verify the container is running, if we get an error assume the container @@ -329,5 +295,62 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut if err != nil { return errors.WithStack(err) } - return taskProcess.Start(ctx) + + return w.runProcess(ctx, taskProcess, process.Resize, nil) +} + +func (w *containerdExecutor) runProcess(ctx context.Context, p containerd.Process, resize <-chan executor.WinSize, started func()) (err error) { + err = p.Start(ctx) + if err != nil { + return err + } + + if started != nil { + started() + } + + statusCh, err := p.Wait(context.Background()) + if err != nil { + return err + } + + var cancel func() + ctxDone := ctx.Done() + for { + select { + case <-ctxDone: + ctxDone = nil + var killCtx context.Context + killCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) + p.Kill(killCtx, syscall.SIGKILL) + case size := <-resize: + err := p.Resize(ctx, size.Cols, size.Rows) + if err != nil { + cancel() + return err + } + case status := <-statusCh: + if cancel != nil { + cancel() + } + if status.ExitCode() != 0 { + var err error + if status.ExitCode() == containerd.UnknownExitStatus && status.Error() != nil { + err = errors.Wrap(status.Error(), "failure waiting for process") + } else { + err = &executor.ExitError{ + ExitCode: status.ExitCode(), + Err: status.Error(), + } + } + select { + case <-ctx.Done(): + err = errors.Wrap(ctx.Err(), err.Error()) + default: + } + return err + } + return nil + } + } } diff --git a/executor/executor.go b/executor/executor.go index 5ab42525..54435bca 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -2,6 +2,7 @@ package executor import ( "context" + "fmt" "io" "net" @@ -28,10 +29,18 @@ type Mount struct { Readonly bool } +type WinSize struct { + Rows uint32 + Cols uint32 + Xpixel uint32 + Ypixel uint32 +} + type ProcessInfo struct { Meta Meta Stdin io.ReadCloser Stdout, Stderr io.WriteCloser + Resize <-chan WinSize } type Executor interface { @@ -48,3 +57,24 @@ type HostIP struct { Host string IP net.IP } + +// ExitError will be returned from Run and Exec when the container process exits with +// a non-zero exit code. +type ExitError struct { + ExitCode uint32 + Err error +} + +func (err *ExitError) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return fmt.Sprintf("exit code: %d", err.ExitCode) +} + +func (err *ExitError) Unwrap() error { + if err.Err == nil { + return fmt.Errorf("exit code: %d", err.ExitCode) + } + return err.Err +} diff --git a/executor/runcexecutor/executor.go b/executor/runcexecutor/executor.go index 5a27732b..c0d20819 100644 --- a/executor/runcexecutor/executor.go +++ b/executor/runcexecutor/executor.go @@ -271,7 +271,10 @@ func (w *runcExecutor) Run(ctx context.Context, id string, root cache.Mountable, } } - spec.Process.Terminal = meta.Tty + if meta.Tty { + return errors.New("tty with runc not implemented") + } + spec.Process.OOMScoreAdj = w.oomScoreAdj if w.rootless { if err := rootlessspecconv.ToRootless(spec); err != nil { @@ -329,8 +332,9 @@ func (w *runcExecutor) Run(ctx context.Context, id string, root cache.Mountable, close(ended) if status != 0 || err != nil { - if err == nil { - err = errors.Errorf("exit code: %d", status) + err = &executor.ExitError{ + ExitCode: uint32(status), + Err: err, } select { case <-ctx.Done(): @@ -343,7 +347,7 @@ func (w *runcExecutor) Run(ctx context.Context, id string, root cache.Mountable, return nil } -func (w *runcExecutor) Exec(ctx context.Context, id string, process executor.ProcessInfo) error { +func (w *runcExecutor) Exec(ctx context.Context, id string, process executor.ProcessInfo) (err error) { // first verify the container is running, if we get an error assume the container // is in the process of being created and check again every 100ms or until // context is canceled. @@ -406,9 +410,21 @@ func (w *runcExecutor) Exec(ctx context.Context, id string, process executor.Pro spec.Process.Env = process.Meta.Env } - return w.runc.Exec(ctx, id, *spec.Process, &runc.ExecOpts{ + err = w.runc.Exec(ctx, id, *spec.Process, &runc.ExecOpts{ IO: &forwardIO{stdin: process.Stdin, stdout: process.Stdout, stderr: process.Stderr}, }) + + var exitError *exec.ExitError + if errors.As(err, &exitError) { + err = &executor.ExitError{ + ExitCode: uint32(exitError.ExitCode()), + Err: err, + } + return err + } else if err != nil { + return err + } + return nil } type forwardIO struct {