get.porter.sh/porter@v1.3.0/pkg/exec/builder/errors.go (about)

     1  package builder
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"get.porter.sh/porter/pkg/tracing"
    10  )
    11  
    12  var _ HasErrorHandling = IgnoreErrorHandler{}
    13  
    14  // IgnoreErrorHandler implements HasErrorHandling for the exec mixin
    15  // and can be used by any other mixin to get the same error handling behavior.
    16  type IgnoreErrorHandler struct {
    17  	// All ignores any error that happens when the command is run.
    18  	All bool `yaml:"all,omitempty"`
    19  
    20  	// ExitCodes ignores any exit codes in the list.
    21  	ExitCodes []int `yaml:"exitCodes,omitempty"`
    22  
    23  	// Output determines if the error should be ignored based on the command
    24  	// output.
    25  	Output IgnoreErrorWithOutput `yaml:"output,omitempty"`
    26  }
    27  
    28  type IgnoreErrorWithOutput struct {
    29  	// Contains specifies that the error is ignored when stderr contains the
    30  	// specified substring.
    31  	Contains []string `yaml:"contains,omitempty"`
    32  
    33  	// Regex specifies that the error is ignored when stderr matches the
    34  	// specified regular expression.
    35  	Regex []string `yaml:"regex,omitempty"`
    36  }
    37  
    38  func (h IgnoreErrorHandler) HandleError(ctx context.Context, err ExitError, stdout string, stderr string) error {
    39  	_, span := tracing.StartSpan(ctx)
    40  	defer span.EndSpan()
    41  
    42  	// We shouldn't be called when there is no error but just in case, let's check
    43  	if err == nil || err.ExitCode() == 0 {
    44  		return nil
    45  	}
    46  
    47  	span.Debugf("Evaluating mixin command error %s with the mixin's error handler", err.Error())
    48  
    49  	// Check if the command should always be allowed to "pass"
    50  	if h.All {
    51  		span.Debug("Ignoring mixin command error because All was specified in the mixin step definition")
    52  		return nil
    53  	}
    54  
    55  	// Check if the exit code was allowed
    56  	exitCode := err.ExitCode()
    57  	for _, code := range h.ExitCodes {
    58  		if exitCode == code {
    59  			span.Debugf("Ignoring mixin command error (exit code: %d) because it was included in the allowed ExitCodes list defined in the mixin step definition", exitCode)
    60  			return nil
    61  		}
    62  	}
    63  
    64  	// Check if the output contains a hint that it should be allowed to pass
    65  	for _, allowError := range h.Output.Contains {
    66  		if strings.Contains(stderr, allowError) {
    67  			span.Debugf("Ignoring mixin command error because the error contained the substring %q defined in the mixin step definition", allowError)
    68  			return nil
    69  		}
    70  	}
    71  
    72  	// Check if the output matches an allowed regular expression
    73  	for _, allowMatch := range h.Output.Regex {
    74  		expression, regexErr := regexp.Compile(allowMatch)
    75  		if regexErr != nil {
    76  			err := span.Error(fmt.Errorf("Could not ignore failed command because the Regex specified by the mixin step definition (%q) is invalid:%s", allowMatch, regexErr.Error()))
    77  			return err
    78  		}
    79  
    80  		if expression.MatchString(stderr) {
    81  			span.Debugf("Ignoring mixin command error because the error matched the Regex %q defined in the mixin step definition", allowMatch)
    82  			return nil
    83  		}
    84  	}
    85  
    86  	return err
    87  }