diff --git a/CHANGELOG.md b/CHANGELOG.md index cf03a71bb..a6f00bb5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,10 @@ For more information on this migration please consult [kubernetes-sigs/kubebuilder#3907][kubebuilder_3907]. [#956](https://github.com/Kong/gateway-operator/pull/956) +- The `GatewayClass` Accepted Condition is set to `False` with reason `InvalidParameters` + in case the `.spec.parametersRef` field is not a valid reference to an existing + `GatewayConfiguration` object. + [#1021](https://github.com/Kong/gateway-operator/pull/1021) [kubebuilder_3907]: https://github.com/kubernetes-sigs/kubebuilder/discussions/3907 diff --git a/controller/gateway/controller.go b/controller/gateway/controller.go index 10508ba0c..edcaf7edf 100644 --- a/controller/gateway/controller.go +++ b/controller/gateway/controller.go @@ -123,13 +123,20 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu log.Trace(logger, "checking gatewayclass") gwc, err := gatewayclass.Get(ctx, r.Client, string(gateway.Spec.GatewayClassName)) if err != nil { - if errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}) { + switch { + case errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}): log.Debug(logger, "resource not supported, ignoring", "expectedGatewayClass", vars.ControllerName(), "gatewayClass", gateway.Spec.GatewayClassName, "reason", err.Error(), ) return ctrl.Result{}, nil + case errors.As(err, &operatorerrors.ErrNotAcceptedGatewayClass{}): + log.Debug(logger, "GatewayClass not accepted, ignoring", + "gatewayClass", gateway.Spec.GatewayClassName, + "reason", err.Error(), + ) + return ctrl.Result{}, nil } return ctrl.Result{}, err } diff --git a/controller/gateway/controller_watch.go b/controller/gateway/controller_watch.go index 963b39f8d..adafc1eec 100644 --- a/controller/gateway/controller_watch.go +++ b/controller/gateway/controller_watch.go @@ -49,7 +49,8 @@ func (r *Reconciler) gatewayHasMatchingGatewayClass(obj client.Object) bool { // class as well. If we fail here it's most likely because of some failure // of the Kubernetes API and it's technically better to enqueue the object // than to drop it for eventual consistency during cluster outages. - return !errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}) + return !errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}) && + !errors.As(err, &operatorerrors.ErrNotAcceptedGatewayClass{}) } return true @@ -229,9 +230,12 @@ func (r *Reconciler) listManagedGatewaysInNamespace(ctx context.Context, obj cli objKey := client.ObjectKey{Name: string(gateway.Spec.GatewayClassName)} if _, err := gatewayclass.Get(ctx, r.Client, string(gateway.Spec.GatewayClassName)); err != nil { - if errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}) { + switch { + case errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}): log.Debug(logger, "gateway class not supported, ignoring") - } else { + case errors.As(err, &operatorerrors.ErrNotAcceptedGatewayClass{}): + log.Debug(logger, "gateway class not accepted, ignoring") + default: log.Error(logger, err, "failed to get Gateway's GatewayClass", "gatewayClass", objKey.Name, "gateway", gateway.Name, diff --git a/controller/gatewayclass/controller.go b/controller/gatewayclass/controller.go index 74689ea55..f9635a6a4 100644 --- a/controller/gatewayclass/controller.go +++ b/controller/gatewayclass/controller.go @@ -5,7 +5,6 @@ import ( "fmt" k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -16,7 +15,6 @@ import ( "github.com/kong/gateway-operator/controller" "github.com/kong/gateway-operator/controller/pkg/log" "github.com/kong/gateway-operator/internal/utils/gatewayclass" - "github.com/kong/gateway-operator/pkg/consts" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" ) @@ -55,29 +53,23 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, nil } - if !gwc.IsAccepted() { - oldGwc := gwc.DeepCopy() + oldGwc := gwc.DeepCopy() - k8sutils.SetCondition( - k8sutils.NewConditionWithGeneration( - consts.ConditionType(gatewayv1.GatewayClassConditionStatusAccepted), - metav1.ConditionTrue, - consts.ConditionReason(gatewayv1.GatewayClassReasonAccepted), - "the gatewayclass has been accepted by the operator", - gwc.GetGeneration(), - ), - gwc, - ) - if err := r.Status().Patch(ctx, gwc.GatewayClass, client.MergeFrom(oldGwc)); err != nil { - if k8serrors.IsConflict(err) { - log.Debug(logger, "conflict found when updating GatewayClass, retrying") - return ctrl.Result{ - Requeue: true, - RequeueAfter: controller.RequeueWithoutBackoff, - }, nil - } - return ctrl.Result{}, fmt.Errorf("failed patching GatewayClass: %w", err) + condition, err := getAcceptedCondition(ctx, r.Client, gwc.GatewayClass) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to get accepted condition: %w", err) + } + k8sutils.SetCondition(*condition, gwc) + + if err := r.Status().Patch(ctx, gwc.GatewayClass, client.MergeFrom(oldGwc)); err != nil { + if k8serrors.IsConflict(err) { + log.Debug(logger, "conflict found when updating GatewayClass, retrying") + return ctrl.Result{ + Requeue: true, + RequeueAfter: controller.RequeueWithoutBackoff, + }, nil } + return ctrl.Result{}, fmt.Errorf("failed patching GatewayClass: %w", err) } return ctrl.Result{}, nil diff --git a/controller/gatewayclass/controller_reconciler_utils.go b/controller/gatewayclass/controller_reconciler_utils.go new file mode 100644 index 000000000..d9db28836 --- /dev/null +++ b/controller/gatewayclass/controller_reconciler_utils.go @@ -0,0 +1,65 @@ +package gatewayclass + +import ( + "context" + "strings" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + + operatorv1beta1 "github.com/kong/gateway-operator/api/v1beta1" + "github.com/kong/gateway-operator/pkg/consts" + k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" +) + +// getAcceptedCondition returns the accepted condition for the GatewayClass, with +// the proper status, reason and message. +func getAcceptedCondition(ctx context.Context, cl client.Client, gwc *gatewayv1.GatewayClass) (*metav1.Condition, error) { + reason := gatewayv1.GatewayClassReasonAccepted + messages := []string{} + status := metav1.ConditionFalse + + if gwc.Spec.ParametersRef != nil { + validRef := true + if gwc.Spec.ParametersRef.Group != gatewayv1.Group(operatorv1beta1.SchemeGroupVersion.Group) || + gwc.Spec.ParametersRef.Kind != "GatewayConfiguration" { + reason = gatewayv1.GatewayClassReasonInvalidParameters + messages = append(messages, "ParametersRef must reference a gateway-operator.konghq.com/GatewayConfiguration") + validRef = false + } + + if gwc.Spec.ParametersRef.Namespace == nil { + reason = gatewayv1.GatewayClassReasonInvalidParameters + messages = append(messages, "ParametersRef must reference a namespaced resource") + validRef = false + } + + if validRef { + gatewayConfig := operatorv1beta1.GatewayConfiguration{} + err := cl.Get(ctx, client.ObjectKey{Name: gwc.Spec.ParametersRef.Name, Namespace: string(*gwc.Spec.ParametersRef.Namespace)}, &gatewayConfig) + if client.IgnoreNotFound(err) != nil { + return nil, err + } + if k8serrors.IsNotFound(err) { + reason = gatewayv1.GatewayClassReasonInvalidParameters + messages = append(messages, "The referenced GatewayConfiguration does not exist") + } + } + } + if reason == gatewayv1.GatewayClassReasonAccepted { + status = metav1.ConditionTrue + messages = []string{"GatewayClass is accepted"} + } + + acceptedCondition := k8sutils.NewConditionWithGeneration( + consts.ConditionType(gatewayv1.GatewayClassConditionStatusAccepted), + status, + consts.ConditionReason(reason), + strings.Join(messages, ". "), + gwc.GetGeneration(), + ) + + return &acceptedCondition, nil +} diff --git a/controller/gatewayclass/controller_reconciler_utils_test.go b/controller/gatewayclass/controller_reconciler_utils_test.go new file mode 100644 index 000000000..3b7a182b3 --- /dev/null +++ b/controller/gatewayclass/controller_reconciler_utils_test.go @@ -0,0 +1,131 @@ +package gatewayclass + +import ( + "context" + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + + operatorv1beta1 "github.com/kong/gateway-operator/api/v1beta1" +) + +func TestGetAcceptedCondition(t *testing.T) { + scheme := runtime.NewScheme() + assert.NoError(t, gatewayv1.Install(scheme)) + assert.NoError(t, operatorv1beta1.AddToScheme(scheme)) + + tests := []struct { + name string + gwc *gatewayv1.GatewayClass + existingObjs []runtime.Object + expectedStatus metav1.ConditionStatus + expectedReason string + expectedMsg string + }{ + { + name: "ParametersRef is nil", + gwc: &gatewayv1.GatewayClass{ + Spec: gatewayv1.GatewayClassSpec{ + ParametersRef: nil, + }, + }, + expectedStatus: metav1.ConditionTrue, + expectedReason: string(gatewayv1.GatewayClassReasonAccepted), + expectedMsg: "GatewayClass is accepted", + }, + { + name: "Invalid ParametersRef Group and kind", + gwc: &gatewayv1.GatewayClass{ + Spec: gatewayv1.GatewayClassSpec{ + + ParametersRef: &gatewayv1.ParametersReference{ + Group: "invalid.group", + Kind: "InvalidKind", + Namespace: lo.ToPtr(gatewayv1.Namespace("default")), + Name: "invalid", + }, + }, + }, + expectedStatus: metav1.ConditionFalse, + expectedReason: string(gatewayv1.GatewayClassReasonInvalidParameters), + expectedMsg: "ParametersRef must reference a gateway-operator.konghq.com/GatewayConfiguration", + }, + { + name: "ParametersRef Namespace is nil", + gwc: &gatewayv1.GatewayClass{ + Spec: gatewayv1.GatewayClassSpec{ + ParametersRef: &gatewayv1.ParametersReference{ + Group: gatewayv1.Group(operatorv1beta1.SchemeGroupVersion.Group), + Kind: "GatewayConfiguration", + Name: "no-namespace", + }, + }, + }, + expectedStatus: metav1.ConditionFalse, + expectedReason: string(gatewayv1.GatewayClassReasonInvalidParameters), + expectedMsg: "ParametersRef must reference a namespaced resource", + }, + { + name: "GatewayConfiguration does not exist", + gwc: &gatewayv1.GatewayClass{ + Spec: gatewayv1.GatewayClassSpec{ + ParametersRef: &gatewayv1.ParametersReference{ + Group: gatewayv1.Group(operatorv1beta1.SchemeGroupVersion.Group), + Kind: "GatewayConfiguration", + Name: "nonexistent", + Namespace: lo.ToPtr(gatewayv1.Namespace("default")), + }, + }, + }, + expectedStatus: metav1.ConditionFalse, + expectedReason: string(gatewayv1.GatewayClassReasonInvalidParameters), + expectedMsg: "The referenced GatewayConfiguration does not exist", + }, + { + name: "Valid ParametersRef", + gwc: &gatewayv1.GatewayClass{ + Spec: gatewayv1.GatewayClassSpec{ + ParametersRef: &gatewayv1.ParametersReference{ + Group: gatewayv1.Group(operatorv1beta1.SchemeGroupVersion.Group), + Kind: "GatewayConfiguration", + Name: "valid-config", + Namespace: lo.ToPtr(gatewayv1.Namespace("default")), + }, + }, + }, + existingObjs: []runtime.Object{ + &operatorv1beta1.GatewayConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + Namespace: "default", + }, + }, + }, + expectedStatus: metav1.ConditionTrue, + expectedReason: string(gatewayv1.GatewayClassReasonAccepted), + expectedMsg: "GatewayClass is accepted", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + cl := fake.NewClientBuilder(). + WithScheme(scheme). + WithRuntimeObjects(tt.existingObjs...). + Build() + + condition, err := getAcceptedCondition(ctx, cl, tt.gwc) + assert.NoError(t, err) + assert.NotNil(t, condition) + assert.Equal(t, tt.expectedStatus, condition.Status) + assert.Equal(t, tt.expectedReason, condition.Reason) + assert.Equal(t, tt.expectedMsg, condition.Message) + }) + } +} diff --git a/controller/specialized/aigateway_controller.go b/controller/specialized/aigateway_controller.go index 085a8e8c5..df846d8d6 100644 --- a/controller/specialized/aigateway_controller.go +++ b/controller/specialized/aigateway_controller.go @@ -69,8 +69,19 @@ func (r *AIGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // See: https://github.com/kubernetes-sigs/controller-runtime/issues/1996 gwc, err := gatewayclass.Get(ctx, r.Client, aigateway.Spec.GatewayClassName) if err != nil { - if errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}) { - log.Debug(logger, "resource not supported, ignoring", "ExpectedGatewayClass", vars.ControllerName()) + switch { + case errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}): + log.Debug(logger, "resource not supported, ignoring", + "expectedGatewayClass", vars.ControllerName(), + "gatewayClass", aigateway.Spec.GatewayClassName, + "reason", err.Error(), + ) + return ctrl.Result{}, nil + case errors.As(err, &operatorerrors.ErrNotAcceptedGatewayClass{}): + log.Debug(logger, "GatewayClass not accepted, ignoring", + "gatewayClass", aigateway.Spec.GatewayClassName, + "reason", err.Error(), + ) return ctrl.Result{}, nil } return ctrl.Result{}, err diff --git a/controller/specialized/aigateway_controller_watch.go b/controller/specialized/aigateway_controller_watch.go index 301570b2b..79d59658c 100644 --- a/controller/specialized/aigateway_controller_watch.go +++ b/controller/specialized/aigateway_controller_watch.go @@ -37,7 +37,8 @@ func (r *AIGatewayReconciler) aiGatewayHasMatchingGatewayClass(obj client.Object // class as well. If we fail here it's most likely because of some failure // of the Kubernetes API and it's technically better to enqueue the object // than to drop it for eventual consistency during cluster outages. - return !errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}) + return !errors.As(err, &operatorerrors.ErrUnsupportedGatewayClass{}) && + !errors.As(err, &operatorerrors.ErrNotAcceptedGatewayClass{}) } return true diff --git a/go.mod b/go.mod index 3ba267b80..8c317e4c0 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( golang.org/x/mod v0.22.0 k8s.io/api v0.32.0 k8s.io/apiextensions-apiserver v0.32.0 - k8s.io/apimachinery v0.32.0 + k8s.io/apimachinery v0.32.1 k8s.io/client-go v0.32.0 k8s.io/kubernetes v1.32.0 // TODO: Use official release when diff --git a/hack/generators/go.mod b/hack/generators/go.mod index 767d5b491..eec895152 100644 --- a/hack/generators/go.mod +++ b/hack/generators/go.mod @@ -12,7 +12,7 @@ require ( github.com/kong/semver/v4 v4.0.1 github.com/samber/lo v1.47.0 k8s.io/api v0.32.0 - k8s.io/apimachinery v0.32.0 + k8s.io/apimachinery v0.32.1 sigs.k8s.io/gateway-api v1.2.1 ) diff --git a/hack/generators/go.sum b/hack/generators/go.sum index 2de368877..699521fc9 100644 --- a/hack/generators/go.sum +++ b/hack/generators/go.sum @@ -124,8 +124,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= -k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= -k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= +k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= diff --git a/internal/errors/errors.go b/internal/errors/errors.go index 919097f06..64243a96a 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -3,6 +3,8 @@ package errors import ( "errors" "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ----------------------------------------------------------------------------- @@ -31,6 +33,21 @@ func (e ErrUnsupportedGatewayClass) Error() string { return fmt.Sprintf("unsupported gateway class: %s", e.reason) } +// ErrNotAcceptedGatewayClass is an error which indicates that a provided GatewayClass +// is not accepted. +type ErrNotAcceptedGatewayClass struct { + gatewayClass string + condition metav1.Condition +} + +func NewErrNotAcceptedGatewayClass(gatewayClass string, condition metav1.Condition) ErrNotAcceptedGatewayClass { + return ErrNotAcceptedGatewayClass{gatewayClass: gatewayClass, condition: condition} +} + +func (e ErrNotAcceptedGatewayClass) Error() string { + return fmt.Sprintf("gateway class %s not accepted; reason: %s, message: %s", e.gatewayClass, e.condition.Reason, e.condition.Message) +} + // ----------------------------------------------------------------------------- // GatewayClass - Errors // ----------------------------------------------------------------------------- diff --git a/internal/utils/gatewayclass/get.go b/internal/utils/gatewayclass/get.go index 408ef05ea..97348ef04 100644 --- a/internal/utils/gatewayclass/get.go +++ b/internal/utils/gatewayclass/get.go @@ -4,14 +4,19 @@ import ( "context" "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" operatorerrors "github.com/kong/gateway-operator/internal/errors" + "github.com/kong/gateway-operator/pkg/consts" + k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" "github.com/kong/gateway-operator/pkg/vars" ) // Get returns a decorated GatewayClass object for the provided GatewayClass name. If the GatewayClass is // not found or is not supported, an `ErrUnsupportedGatewayClass` error is returned with a descriptive message. +// If the GatewayClass is not accepted, an `ErrNotAcceptedGatewayClass` error is returned with a descriptive message. func Get(ctx context.Context, cl client.Client, gatewayClassName string) (*Decorator, error) { if gatewayClassName == "" { return nil, operatorerrors.NewErrUnsupportedGateway("no GatewayClassName provided") @@ -30,6 +35,10 @@ func Get(ctx context.Context, cl client.Client, gatewayClassName string) (*Decor vars.ControllerName(), )) } + acceptedCondition, found := k8sutils.GetCondition(consts.ConditionType(gatewayv1.GatewayClassConditionStatusAccepted), gwc) + if !found || acceptedCondition.Status != metav1.ConditionTrue { + return nil, operatorerrors.NewErrNotAcceptedGatewayClass(gatewayClassName, acceptedCondition) + } return gwc, nil } diff --git a/internal/utils/gatewayclass/get_test.go b/internal/utils/gatewayclass/get_test.go index 6a1d2c698..e1b2cb8ff 100644 --- a/internal/utils/gatewayclass/get_test.go +++ b/internal/utils/gatewayclass/get_test.go @@ -57,16 +57,61 @@ func TestGet(t *testing.T) { ), }, { - name: "gateway class supported", - gatewayClassName: "gateway-class-2", - objectsToAdd: []client.Object{&gatewayv1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-class-2", + name: "gateway class supported but not accepted", + gatewayClassName: "gateway-class-not-accepted", + objectsToAdd: []client.Object{ + &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-not-accepted", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController(vars.ControllerName()), + }, + Status: gatewayv1.GatewayClassStatus{ + Conditions: []metav1.Condition{ + { + Type: string(gatewayv1.GatewayClassConditionStatusAccepted), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1.GatewayClassReasonInvalidParameters), + Message: "ParametersRef must reference a gateway-operator.konghq.com/GatewayConfiguration", + }, + }, + }, }, - Spec: gatewayv1.GatewayClassSpec{ - ControllerName: gatewayv1.GatewayController(vars.ControllerName()), + }, + expectedError: operatorerrors.NewErrNotAcceptedGatewayClass( + "gateway-class-not-accepted", + metav1.Condition{ + Type: string(gatewayv1.GatewayClassConditionStatusAccepted), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1.GatewayClassReasonInvalidParameters), + Message: "ParametersRef must reference a gateway-operator.konghq.com/GatewayConfiguration", }, - }}, + ), + }, + { + name: "gateway class supported and accepted", + gatewayClassName: "gateway-class-2", + objectsToAdd: []client.Object{ + &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-2", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController(vars.ControllerName()), + }, + Status: gatewayv1.GatewayClassStatus{ + Conditions: []metav1.Condition{ + { + Type: string(gatewayv1.GatewayClassConditionStatusAccepted), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.GatewayClassReasonAccepted), + Message: "GatewayClass is accepted", + }, + }, + }, + }, + }, expectedError: nil, }, } diff --git a/pkg/utils/kubernetes/status.go b/pkg/utils/kubernetes/status.go index b1bb952ad..760f9f5b3 100644 --- a/pkg/utils/kubernetes/status.go +++ b/pkg/utils/kubernetes/status.go @@ -45,7 +45,12 @@ func SetCondition(condition metav1.Condition, resource ConditionsAware) { if conditions[i].Type != condition.Type { newConditions = append(newConditions, conditions[i]) } else { - newConditions = append(newConditions, condition) + oldCondition := conditions[i] + if conditionNeedsUpdate(oldCondition, condition) { + newConditions = append(newConditions, condition) + } else { + newConditions = append(newConditions, oldCondition) + } conditionFound = true } } @@ -264,9 +269,13 @@ func NeedsUpdate(current, updated ConditionsAware) bool { if !exists { return true } - if u.Reason != c.Reason || u.Message != c.Message || u.Status != c.Status || u.ObservedGeneration != c.ObservedGeneration { + if conditionNeedsUpdate(c, u) { return true } } return false } + +func conditionNeedsUpdate(current, updated metav1.Condition) bool { + return updated.Reason != current.Reason || updated.Message != current.Message || updated.Status != current.Status || updated.ObservedGeneration != current.ObservedGeneration +}