wrap errors from executor Run/Exec to allow access to exit code
Signed-off-by: Cory Bennett <cbennett@netflix.com>v0.8
parent
3f3957d16c
commit
4b456f17f4
|
@ -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() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue