...

Source file src/edge-infra.dev/pkg/k8s/runtime/errors.go

Documentation: edge-infra.dev/pkg/k8s/runtime

     1  package runtime
     2  
     3  import (
     4  	"errors"
     5  	"regexp"
     6  
     7  	kerrors "k8s.io/apimachinery/pkg/api/errors"
     8  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     9  )
    10  
    11  // Match CEL and known custom webhook immutable error variants.
    12  var matchImmutableFieldErrors = []*regexp.Regexp{
    13  	regexp.MustCompile(`.*is\simmutable.*`),
    14  	regexp.MustCompile(`.*immutable\sfield.*`), // KCC famous deadhooks
    15  }
    16  
    17  // IsImmutableError checks if the given error indicates that a recoverable
    18  // immutability error has been encountered, which occurs when an object failed
    19  // to be applied strictly due to updating immutable fields. If there are
    20  // additional causes detected (e.g., the error is caused both by an immutable
    21  // field being changed and another field being invalid), the immutability error
    22  // is considered non-recoverable and this function returns false. This is
    23  // because the only method for recovering from an immutability error is to
    24  // delete the object and re-create it -- if the next apply will not succeed due
    25  // to invalid fields, the immutability error cannot be recovered from.
    26  //
    27  // Error evaluation logic:
    28  //
    29  //  1. Nil errors and [k8s.io/apimachinery/pkg/api/errors.NewNotFound] return
    30  //     false, because an error can't be due to immutability if doesn't already
    31  //     exist.
    32  //  2. [k8s.io/apimachinery/pkg/api/errors.NewConflict] errors return true.
    33  //  3. If the error is [k8s.io/apimachinery/pkg/api/errors.NewInvalid], it is
    34  //     further probed for the specific cause, as immutability is just a subset
    35  //     of the potential invalid errors. Immutability errors should almost always
    36  //     indicate that the cause for the error is FieldValueForbidden, but some
    37  //     older buitl-in values are inconsistent and incidate the cause is
    38  //     FieldValueInvalid. If only FieldValueForbidden or FieldValueInvalid
    39  //     causes are detected, we return true.
    40  //  4. The error message is evaluated for known immutability message regexes,
    41  //     e.g. CEL or custom admission webhooks.
    42  func IsImmutableError(err error) bool {
    43  	if err == nil || kerrors.IsNotFound(err) {
    44  		return false
    45  	}
    46  
    47  	// Detect immutability like kubectl does, but try to be more specific about
    48  	// invalid errors to reduce false positives.
    49  	// https://github.com/kubernetes/kubectl/blob/8165f83007/pkg/cmd/apply/patcher.go#L201
    50  	if kerrors.IsConflict(err) {
    51  		return true
    52  	}
    53  
    54  	if kerrors.IsInvalid(err) {
    55  		// Attempt to ignore invalid errors caused by other issues (eg, simply an
    56  		// invalid object) by narrowing down to errors which are _only_ caused by
    57  		// forbidden and invalid field errors.
    58  		var status kerrors.APIStatus
    59  		// All Invalid errors must be APIErrors
    60  		errors.As(err, &status)
    61  
    62  		// If we encounter any causes that are not associated with immutability,
    63  		// return false. That is because the only way to handle immutability errors
    64  		// are deleting and re-creating. Detecting the presence of any error causes
    65  		// that would block the re-creation of the object ensures immutability
    66  		// errors are only handled when objects can cleanly be re-created.
    67  		//
    68  		// Value is hardcoded because missing constants were added in 1.28+:
    69  		// https://github.com/kubernetes/kubernetes/commit/79c02ceb73f4d64f52a9d4c46785e4c7497493d9#diff-6f084d57227fbb44adce838b4eed2e40680cef52677adce543dddf5edc1cd3c9R1002
    70  		for _, c := range status.Status().Details.Causes {
    71  			switch c.Type {
    72  			case metav1.CauseType("FieldValueForbidden"), metav1.CauseTypeFieldValueInvalid:
    73  			default:
    74  				return false
    75  			}
    76  		}
    77  		return true
    78  	}
    79  
    80  	// Detect immutable errors returned by custom admission webhooks and Kubernetes CEL
    81  	// https://kubernetes.io/blog/2022/09/29/enforce-immutability-using-cel/#immutablility-after-first-modification
    82  	for _, fieldError := range matchImmutableFieldErrors {
    83  		if fieldError.MatchString(err.Error()) {
    84  			return true
    85  		}
    86  	}
    87  
    88  	return false
    89  }
    90  

View as plain text