github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/containerupdate/error.go (about)

     1  package containerupdate
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"k8s.io/client-go/util/exec"
     8  
     9  	"github.com/tilt-dev/tilt/internal/build"
    10  	"github.com/tilt-dev/tilt/internal/docker"
    11  	"github.com/tilt-dev/tilt/pkg/model"
    12  )
    13  
    14  const (
    15  	// GenericExitCodeKilled indicates the container runtime killed the process.
    16  	GenericExitCodeKilled = 137
    17  
    18  	// GenericExitCodeCannotExec indicates the command cannot be executed.
    19  	// In a shell, this generally is a form of permission issues (i.e. the
    20  	// binary was found but is not +x). However, container runtimes also
    21  	// use this to indicate that the binary wasn't found at all, which is
    22  	// extremely common when we try to use common tools such as `tar` but
    23  	// the image is missing them.
    24  	GenericExitCodeCannotExec = 126
    25  
    26  	GenericExitCodeNotFound = 127
    27  )
    28  
    29  // ExtractExitCode returns exit status information from different types of
    30  // errors.
    31  func ExtractExitCode(err error) (int, bool) {
    32  	var k8sExitErr exec.ExitError
    33  	if errors.As(err, &k8sExitErr) {
    34  		return k8sExitErr.ExitStatus(), true
    35  	}
    36  
    37  	var dockerExitErr docker.ExitError
    38  	if errors.As(err, &dockerExitErr) {
    39  		return dockerExitErr.ExitCode, true
    40  	}
    41  
    42  	return -1, false
    43  }
    44  
    45  type ExecError struct {
    46  	Cmd      model.Cmd
    47  	ExitCode int
    48  }
    49  
    50  func NewExecError(cmd model.Cmd, exitCode int) ExecError {
    51  	return ExecError{Cmd: cmd, ExitCode: exitCode}
    52  }
    53  
    54  func (e ExecError) Error() string {
    55  	var reason string
    56  	switch e.ExitCode {
    57  	case GenericExitCodeKilled:
    58  		reason = "killed by container runtime"
    59  	case GenericExitCodeCannotExec:
    60  		// docker uses this when it can't find the command for an exec, so we
    61  		// have a single error message that covers both cases
    62  		fallthrough
    63  	case GenericExitCodeNotFound:
    64  		reason = "not found in PATH or not executable"
    65  	}
    66  
    67  	if reason != "" {
    68  		reason = " (" + reason + ")"
    69  	}
    70  
    71  	return fmt.Sprintf("command %q failed with exit code: %d%s", e.Cmd.String(), e.ExitCode, reason)
    72  }
    73  
    74  func wrapRunStepError(err error) error {
    75  	var execErr ExecError
    76  	if errors.As(err, &execErr) {
    77  		if execErr.ExitCode == GenericExitCodeKilled {
    78  			// a SIGKILL of a run() command is not treated as a step failure
    79  			// but as a more generic "infra" error, so it's not wrapped
    80  			return err
    81  		}
    82  		return build.NewRunStepFailure(err)
    83  	}
    84  	return err
    85  }