Allow ExitError type to be transmitted over GRPC

This will allow clients to retrieve exit error codes returned during a
solve without parsing the error messages.

Signed-off-by: Aaron Lehmann <alehmann@netflix.com>
master
Aaron Lehmann 2021-07-28 14:49:33 -07:00
parent 1ba8f965c1
commit 890c3f77b6
7 changed files with 52 additions and 30 deletions

View File

@ -14,7 +14,6 @@ import (
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/frontend/gateway/client"
gwerrdefs "github.com/moby/buildkit/frontend/gateway/errdefs"
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session"
@ -451,8 +450,8 @@ func testClientGatewayContainerPID1Fail(t *testing.T, sb integration.Sandbox) {
defer ctr.Release(ctx)
err = pid1.Wait()
var exitError *gwerrdefs.ExitError
require.True(t, errors.As(err, &exitError))
var exitError *gatewayapi.ExitError
require.ErrorAs(t, err, &exitError)
require.Equal(t, uint32(99), exitError.ExitCode)
return nil, err
@ -531,6 +530,9 @@ func testClientGatewayContainerPID1Exit(t *testing.T, sb integration.Sandbox) {
_, err = c.Build(ctx, SolveOpt{}, product, b, nil)
require.Error(t, err)
var exitError *gatewayapi.ExitError
require.ErrorAs(t, err, &exitError)
require.Equal(t, uint32(137), exitError.ExitCode)
// `exit code: 137` (ie sigkill)
require.Regexp(t, "exit code: 137", err.Error())
@ -776,8 +778,8 @@ func testClientGatewayContainerPID1Tty(t *testing.T, sb integration.Sandbox) {
prompt.SendExit(99)
err = pid1.Wait()
var exitError *gwerrdefs.ExitError
require.True(t, errors.As(err, &exitError))
var exitError *gatewayapi.ExitError
require.ErrorAs(t, err, &exitError)
require.Equal(t, uint32(99), exitError.ExitCode)
return &client.Result{}, err
@ -920,8 +922,8 @@ func testClientGatewayContainerExecTty(t *testing.T, sb integration.Sandbox) {
prompt.SendExit(99)
err = pid2.Wait()
var exitError *gwerrdefs.ExitError
require.True(t, errors.As(err, &exitError))
var exitError *gatewayapi.ExitError
require.ErrorAs(t, err, &exitError)
require.Equal(t, uint32(99), exitError.ExitCode)
return &client.Result{}, err
@ -929,6 +931,9 @@ func testClientGatewayContainerExecTty(t *testing.T, sb integration.Sandbox) {
_, err = c.Build(ctx, SolveOpt{}, product, b, nil)
require.Error(t, err)
var exitError *gatewayapi.ExitError
require.ErrorAs(t, err, &exitError)
require.Equal(t, uint32(99), exitError.ExitCode)
require.Regexp(t, "exit code: 99", err.Error())
inputW.Close()
@ -1145,7 +1150,7 @@ func testClientGatewayExecError(t *testing.T, sb integration.Sandbox) {
require.Error(t, solveErr)
var se *errdefs.SolveError
require.True(t, errors.As(solveErr, &se))
require.ErrorAs(t, solveErr, &se)
require.Len(t, se.InputIDs, tt.NumMounts)
require.Len(t, se.MountIDs, tt.NumMounts)
@ -1265,7 +1270,7 @@ func testClientGatewaySlowCacheExecError(t *testing.T, sb integration.Sandbox) {
require.Error(t, solveErr)
var se *errdefs.SolveError
require.True(t, errors.As(solveErr, &se))
require.ErrorAs(t, solveErr, &se)
_, ok := se.Solve.Op.Op.(*pb.Op_Exec)
require.True(t, ok)
@ -1398,7 +1403,7 @@ func testClientGatewayExecFileActionError(t *testing.T, sb integration.Sandbox)
require.Error(t, err)
var se *errdefs.SolveError
require.True(t, errors.As(err, &se))
require.ErrorAs(t, err, &se)
require.Len(t, se.Solve.InputIDs, tt.NumInputs)
// There is one output for every action in the fileop that failed.

View File

@ -21,7 +21,7 @@ import (
"github.com/docker/docker/pkg/idtools"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
"github.com/moby/buildkit/frontend/gateway/errdefs"
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/snapshot"
"github.com/moby/buildkit/solver/pb"
@ -388,11 +388,11 @@ func (w *containerdExecutor) runProcess(ctx context.Context, p containerd.Proces
cancel()
}
if status.ExitCode() != 0 {
exitErr := &errdefs.ExitError{
exitErr := &gatewayapi.ExitError{
ExitCode: status.ExitCode(),
Err: status.Error(),
}
if status.ExitCode() == errdefs.UnknownExitStatus && status.Error() != nil {
if status.ExitCode() == gatewayapi.UnknownExitStatus && status.Error() != nil {
exitErr.Err = errors.Wrap(status.Error(), "failure waiting for process")
}
select {

View File

@ -4,13 +4,13 @@ import (
"testing"
"github.com/containerd/containerd"
"github.com/moby/buildkit/frontend/gateway/errdefs"
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
)
func TestContainerdUnknownExitStatus(t *testing.T) {
// There are assumptions in the containerd executor that the UnknownExitStatus
// used in errdefs.ExitError matches the variable in the containerd package.
if containerd.UnknownExitStatus != errdefs.UnknownExitStatus {
if containerd.UnknownExitStatus != gatewayapi.UnknownExitStatus {
t.Fatalf("containerd.UnknownExitStatus != errdefs.UnknownExitStatus")
}
}

View File

@ -21,7 +21,7 @@ import (
"github.com/docker/docker/pkg/idtools"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
"github.com/moby/buildkit/frontend/gateway/errdefs"
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/network"
@ -340,13 +340,13 @@ func (w *runcExecutor) Run(ctx context.Context, id string, root executor.Mount,
func exitError(ctx context.Context, err error) error {
if err != nil {
exitErr := &errdefs.ExitError{
ExitCode: errdefs.UnknownExitStatus,
exitErr := &gatewayapi.ExitError{
ExitCode: gatewayapi.UnknownExitStatus,
Err: err,
}
var runcExitError *runc.ExitError
if errors.As(err, &runcExitError) {
exitErr = &errdefs.ExitError{
exitErr = &gatewayapi.ExitError{
ExitCode: uint32(runcExitError.Status),
}
}

View File

@ -27,7 +27,6 @@ import (
"github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/frontend"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
gwerrdefs "github.com/moby/buildkit/frontend/gateway/errdefs"
pb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session"
@ -1168,10 +1167,10 @@ func (lbf *llbBridgeForwarder) ExecProcess(srv pb.LLBBridge_ExecProcessServer) e
err := proc.Wait()
var statusCode uint32
var exitError *gwerrdefs.ExitError
var exitError *pb.ExitError
var statusError *rpc.Status
if err != nil {
statusCode = gwerrdefs.UnknownExitStatus
statusCode = pb.UnknownExitStatus
st, _ := status.FromError(grpcerrors.ToGRPC(err))
stp := st.Proto()
statusError = &rpc.Status{

View File

@ -18,7 +18,6 @@ import (
"github.com/golang/protobuf/ptypes/any"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/frontend/gateway/errdefs"
pb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity"
opspb "github.com/moby/buildkit/solver/pb"
@ -883,8 +882,8 @@ func (ctr *container) Start(ctx context.Context, req client.StartRequest) (clien
Message: exit.Error.Message,
Details: convertGogoAny(exit.Error.Details),
}))
if exit.Code != errdefs.UnknownExitStatus {
exitError = &errdefs.ExitError{ExitCode: exit.Code, Err: exitError}
if exit.Code != pb.UnknownExitStatus {
exitError = &pb.ExitError{ExitCode: exit.Code, Err: exitError}
}
} else if serverDone := msg.GetDone(); serverDone != nil {
return exitError

View File

@ -1,6 +1,11 @@
package errdefs
package moby_buildkit_v1_frontend //nolint:golint
import "fmt"
import (
"fmt"
"github.com/containerd/typeurl"
"github.com/moby/buildkit/util/grpcerrors"
)
const (
// UnknownExitStatus might be returned in (*ExitError).ExitCode via
@ -12,6 +17,10 @@ const (
UnknownExitStatus = 255
)
func init() {
typeurl.Register((*ExitMessage)(nil), "github.com/moby/buildkit", "gatewayapi.ExitMessage+json")
}
// ExitError will be returned when the container process exits with a non-zero
// exit code.
type ExitError struct {
@ -19,6 +28,12 @@ type ExitError struct {
Err error
}
func (err *ExitError) ToProto() grpcerrors.TypedErrorProto {
return &ExitMessage{
Code: err.ExitCode,
}
}
func (err *ExitError) Error() string {
if err.Err != nil {
return err.Err.Error()
@ -27,8 +42,12 @@ func (err *ExitError) Error() string {
}
func (err *ExitError) Unwrap() error {
if err.Err == nil {
return fmt.Errorf("exit code: %d", err.ExitCode)
}
return err.Err
}
func (e *ExitMessage) WrapError(err error) error {
return &ExitError{
Err: err,
ExitCode: e.Code,
}
}