...

Source file src/edge-infra.dev/pkg/k8s/testing/kmp/conditions.go

Documentation: edge-infra.dev/pkg/k8s/testing/kmp

     1  package kmp
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	gocmp "github.com/google/go-cmp/cmp"
     8  	"github.com/google/go-cmp/cmp/cmpopts"
     9  	"gotest.tools/v3/assert/cmp"
    10  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
    12  	"sigs.k8s.io/controller-runtime/pkg/client"
    13  
    14  	"edge-infra.dev/pkg/k8s/meta/status"
    15  	"edge-infra.dev/pkg/k8s/object"
    16  	"edge-infra.dev/pkg/k8s/runtime/conditions"
    17  	"edge-infra.dev/pkg/k8s/unstructured"
    18  )
    19  
    20  // SameCondition checks that the conditions have the same values. It evaluates
    21  // the Type, Message, Reason, and Status fields of the condition objects.
    22  // ObservedGeneration and LastTransitionTime are ignored.
    23  func SameCondition(exp, actual *metav1.Condition) cmp.Comparison {
    24  	return cmp.DeepEqual(exp, actual, cmpopts.IgnoreFields(
    25  		metav1.Condition{},
    26  		"LastTransitionTime", "ObservedGeneration",
    27  	))
    28  }
    29  
    30  // SameConditions checks that two condition slices contain the same values in
    31  // the same order.
    32  //
    33  // See [SameCondition] for more details on how sameness is determined.
    34  func SameConditions(exp, actual []metav1.Condition) cmp.Comparison {
    35  	return cmp.DeepEqual(exp, actual, cmpopts.IgnoreFields(
    36  		metav1.Condition{},
    37  		"LastTransitionTime", "ObservedGeneration",
    38  	))
    39  }
    40  
    41  // HasSameConditions checks that the compared object's conditions are the
    42  // same as expected, including order.
    43  //
    44  // See [SameCondition] for more details on how sameness is determined.
    45  //
    46  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
    47  func HasSameConditions(exp []metav1.Condition) Komparison {
    48  	return func(o client.Object) cmp.Result {
    49  		return SameConditions(exp, o.(conditions.Getter).GetConditions())()
    50  	}
    51  }
    52  
    53  // HasSameCondition checks that the compared object's conditions contains
    54  // the expected condition.
    55  //
    56  // See [SameCondition] for more details on how sameness is determined.
    57  //
    58  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
    59  func HasSameCondition(exp *metav1.Condition) Komparison {
    60  	return func(o client.Object) cmp.Result {
    61  		return SameCondition(exp, conditions.Get(o.(conditions.Getter), exp.Type))()
    62  	}
    63  }
    64  
    65  // EqualCondition performs a less strict equality check than [SameCondition].
    66  // For two conditions to be considered equal, they must have the same Type,
    67  // Reason, and Status, but Message may be a substring / patrial match.
    68  func EqualCondition(exp, actual *metav1.Condition) cmp.Comparison {
    69  	return cmp.DeepEqual(exp, actual, gocmp.Comparer(equalCondition))
    70  }
    71  
    72  // EqualConditions checks that two condition slices contain equal values in
    73  // the same order.
    74  //
    75  // See [EqualCondition] for more details on how equality is determined.
    76  func EqualConditions(exp, actual []metav1.Condition) cmp.Comparison {
    77  	return cmp.DeepEqual(exp, actual, gocmp.Comparer(func(x, y metav1.Condition) bool {
    78  		return equalCondition(&x, &y)
    79  	}))
    80  }
    81  
    82  // HasEqualConditions checks that the compared object's conditions equal the
    83  // expected, including order.
    84  //
    85  // See [EqualCondition] for more details on how sameness is determined.
    86  //
    87  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
    88  func HasEqualConditions(exp []metav1.Condition) Komparison {
    89  	return func(o client.Object) cmp.Result {
    90  		return EqualConditions(exp, o.(conditions.Getter).GetConditions())()
    91  	}
    92  }
    93  
    94  // HasEqualConditions checks that the compared object's conditions contains
    95  // an equivalent to the expected condition.
    96  //
    97  // See [EqualCondition] for more details on how sameness is determined.
    98  //
    99  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
   100  func HasEqualCondition(exp *metav1.Condition) Komparison {
   101  	return func(o client.Object) cmp.Result {
   102  		return EqualCondition(exp, conditions.Get(o.(conditions.Getter), exp.Type))()
   103  	}
   104  }
   105  
   106  func equalCondition(x, y *metav1.Condition) bool {
   107  	if x == nil && y == nil {
   108  		return true
   109  	}
   110  	// Can't compare to nil conditions
   111  	if x == nil || y == nil {
   112  		return false
   113  	}
   114  	// Don't perform strict checks on the Message field
   115  	if x.Type != y.Type || x.Status != y.Status ||
   116  		x.Reason != y.Reason {
   117  		return false
   118  	}
   119  	xmsg, ymsg := strings.ToLower(x.Message), strings.ToLower(y.Message)
   120  	// Symmetric equality
   121  	return strings.Contains(xmsg, ymsg) ||
   122  		strings.Contains(ymsg, xmsg)
   123  }
   124  
   125  // IsReady checks that the object is ready using
   126  // [edge-infra.dev/pkg/k8s/runtime/conditions.IsReady].
   127  //
   128  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
   129  func IsReady() Komparison {
   130  	return func(o client.Object) cmp.Result {
   131  		if conditions.IsReady(o.(conditions.Getter)) {
   132  			return cmp.ResultSuccess
   133  		}
   134  		return cmp.ResultFailure(fmt.Sprintf(
   135  			"%s is not Ready: got:\n\t%s",
   136  			object.FmtObject(o),
   137  			conditions.Get(o.(conditions.Getter), status.ReadyCondition),
   138  		))
   139  	}
   140  }
   141  
   142  // IsStalled checks that the object is ready using
   143  // [edge-infra.dev/pkg/k8s/runtime/conditions.IsStalled]
   144  //
   145  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
   146  func IsStalled() Komparison {
   147  	return func(o client.Object) cmp.Result {
   148  		if conditions.IsStalled(o.(conditions.Getter)) {
   149  			return cmp.ResultSuccess
   150  		}
   151  		return cmp.ResultFailure(fmt.Sprintf(
   152  			"%s is not Stalled: got:\n\t%s",
   153  			object.FmtObject(o),
   154  			conditions.Get(o.(conditions.Getter), status.StalledCondition),
   155  		))
   156  	}
   157  }
   158  
   159  // IsReconciling checks that the object is ready using
   160  // [edge-infra.dev/pkg/k8s/runtime/conditions.IsReconciling]
   161  //
   162  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
   163  func IsReconciling() Komparison {
   164  	return func(o client.Object) cmp.Result {
   165  		if conditions.IsReconciling(o.(conditions.Getter)) {
   166  			return cmp.ResultSuccess
   167  		}
   168  		return cmp.ResultFailure(fmt.Sprintf(
   169  			"%s is not Reconciling: got:\n\t%s",
   170  			object.FmtObject(o),
   171  			conditions.Get(o.(conditions.Getter), status.ReconcilingCondition),
   172  		))
   173  	}
   174  }
   175  
   176  // HasTrueCondition checks that the object contains the condition with type t
   177  // that has a status of "True"
   178  //
   179  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
   180  func HasTrueCondition(t string) Komparison {
   181  	return func(o client.Object) cmp.Result {
   182  		if conditions.IsTrue(o.(conditions.Getter), t) {
   183  			return cmp.ResultSuccess
   184  		}
   185  		return cmp.ResultFailure(fmt.Sprintf(
   186  			"%s is not True: got:\n\t%s",
   187  			object.FmtObject(o),
   188  			conditions.Get(o.(conditions.Getter), t),
   189  		))
   190  	}
   191  }
   192  
   193  // HasFalseCondition checks that the object contains the condition with type t
   194  // that has a status of "False"
   195  //
   196  // The object must implement [edge-infra.dev/pkg/k8s/runtime/conditions.Getter]
   197  func HasFalseCondition(t string) Komparison {
   198  	return func(o client.Object) cmp.Result {
   199  		if conditions.IsFalse(o.(conditions.Getter), t) {
   200  			return cmp.ResultSuccess
   201  		}
   202  		return cmp.ResultFailure(fmt.Sprintf(
   203  			"%s is not False: got:\n\t%s",
   204  			object.FmtObject(o),
   205  			conditions.Get(o.(conditions.Getter), t),
   206  		))
   207  	}
   208  }
   209  
   210  // IsCurrent checks that the object has reconciled using
   211  // [sigs.k8s.io/cli-utils/pkg/kstatus/status.Compute], which:
   212  //
   213  //   - Works for all kinds of objects, including builtins
   214  //   - Includes evaluation of observedGeneration vs generation if object contains
   215  //     status.observedGeneration
   216  func IsCurrent() Komparison {
   217  	return func(o client.Object) cmp.Result {
   218  		u, err := unstructured.ToUnstructured(o)
   219  		if err != nil {
   220  			return cmp.ResultFromError(err)
   221  		}
   222  		res, err := kstatus.Compute(u)
   223  		if err != nil {
   224  			return cmp.ResultFromError(err)
   225  		}
   226  		if res.Status != kstatus.CurrentStatus {
   227  			return cmp.ResultFailure(fmt.Sprintf(
   228  				"%s not Current:\n\t%s",
   229  				object.FmtObject(o), res,
   230  			))
   231  		}
   232  		return cmp.ResultSuccess
   233  	}
   234  }
   235  

View as plain text