From 4fd90da58dcd70e5afc22e2610d59ad6fafcf971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Burzy=C5=84ski?= Date: Mon, 7 Oct 2024 17:19:47 +0200 Subject: [PATCH] fix: do not reconcile gateways with unsupported gw class --- controller/gateway/controller.go | 27 +++-- controller/gateway/controller_cleanup.go | 2 +- controller/gateway/controller_consts.go | 4 +- .../gateway/controller_reconciler_utils.go | 19 --- controller/gateway/controller_test.go | 114 ++++++++++++++++-- controller/gateway/controller_watch.go | 5 +- .../specialized/aigateway_controller.go | 5 +- .../aigateway_controller_reconciler_utils.go | 42 ------- .../specialized/aigateway_controller_watch.go | 5 +- internal/errors/errors.go | 20 +-- internal/utils/gatewayclass/get.go | 46 +++++++ internal/utils/gatewayclass/get_test.go | 92 ++++++++++++++ 12 files changed, 286 insertions(+), 95 deletions(-) delete mode 100644 controller/specialized/aigateway_controller_reconciler_utils.go create mode 100644 internal/utils/gatewayclass/get.go create mode 100644 internal/utils/gatewayclass/get_test.go diff --git a/controller/gateway/controller.go b/controller/gateway/controller.go index 70e1e01e6..1b5156f8f 100644 --- a/controller/gateway/controller.go +++ b/controller/gateway/controller.go @@ -32,6 +32,7 @@ import ( "github.com/kong/gateway-operator/controller/pkg/watch" operatorerrors "github.com/kong/gateway-operator/internal/errors" gwtypes "github.com/kong/gateway-operator/internal/types" + "github.com/kong/gateway-operator/internal/utils/gatewayclass" "github.com/kong/gateway-operator/pkg/consts" gatewayutils "github.com/kong/gateway-operator/pkg/utils/gateway" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" @@ -112,6 +113,20 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, client.IgnoreNotFound(err) } + log.Trace(logger, "checking gatewayclass", gateway) + gwc, err := gatewayclass.Get(ctx, r.Client, string(gateway.Spec.GatewayClassName)) + if err != nil { + if errors.As(err, &operatorerrors.ErrUnsupportedGateway{}) { + log.Debug(logger, "resource not supported, ignoring", gateway, + "ExpectedGatewayClass", vars.ControllerName(), + "GatewayClass", gateway.Spec.GatewayClassName, + "Reason", err.Error(), + ) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + log.Trace(logger, "managing cleanup for gateway resource", gateway) if shouldReturnEarly, result, err := r.cleanup(ctx, logger, &gateway); err != nil || !result.IsZero() { return result, err @@ -122,7 +137,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu log.Trace(logger, "managing the gateway resource finalizers", gateway) cpFinalizerSet := controllerutil.AddFinalizer(&gateway, string(GatewayFinalizerCleanupControlPlanes)) dpFinalizerSet := controllerutil.AddFinalizer(&gateway, string(GatewayFinalizerCleanupDataPlanes)) - npFinalizerSet := controllerutil.AddFinalizer(&gateway, string(GatewayFinalizerCleanupNetworkpolicies)) + npFinalizerSet := controllerutil.AddFinalizer(&gateway, string(GatewayFinalizerCleanupNetworkPolicies)) if cpFinalizerSet || dpFinalizerSet || npFinalizerSet { log.Trace(logger, "Setting finalizers", gateway) if err := r.Client.Update(ctx, &gateway); err != nil { @@ -137,16 +152,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, nil } - log.Trace(logger, "checking gatewayclass", gateway) - gwc, err := r.verifyGatewayClassSupport(ctx, &gateway) - if err != nil { - if errors.Is(err, operatorerrors.ErrUnsupportedGateway) { - log.Debug(logger, "resource not supported, ignoring", gateway, "ExpectedGatewayClass", vars.ControllerName()) - return ctrl.Result{}, nil - } - return ctrl.Result{}, err - } - if !gwc.IsAccepted() { log.Debug(logger, "gatewayclass not accepted , ignoring", gateway) return ctrl.Result{}, nil diff --git a/controller/gateway/controller_cleanup.go b/controller/gateway/controller_cleanup.go index a7948fa49..0f7e96e4a 100644 --- a/controller/gateway/controller_cleanup.go +++ b/controller/gateway/controller_cleanup.go @@ -136,7 +136,7 @@ func (r *Reconciler) cleanup( } } else { oldGateway := gateway.DeepCopy() - if controllerutil.RemoveFinalizer(gateway, string(GatewayFinalizerCleanupNetworkpolicies)) { + if controllerutil.RemoveFinalizer(gateway, string(GatewayFinalizerCleanupNetworkPolicies)) { if err := r.Client.Patch(ctx, gateway, client.MergeFrom(oldGateway)); err != nil { res, err := handleGatewayFinalizerPatchOrUpdateError(err, gateway, logger) return true, res, err diff --git a/controller/gateway/controller_consts.go b/controller/gateway/controller_consts.go index e1f0124dd..44006532b 100644 --- a/controller/gateway/controller_consts.go +++ b/controller/gateway/controller_consts.go @@ -12,6 +12,6 @@ const ( GatewayFinalizerCleanupDataPlanes GatewayFinalizer = "gateway-operator.konghq.com/cleanup-dataplanes" // GatewayFinalizerCleanupControlPlanes is the finalizer to cleanup owned controlplane resources. GatewayFinalizerCleanupControlPlanes GatewayFinalizer = "gateway-operator.konghq.com/cleanup-controlplanes" - // GatewayFinalizerCleanupNetworkpolicies is the finalizer to cleanup owned network policies. - GatewayFinalizerCleanupNetworkpolicies GatewayFinalizer = "gateway-operator.konghq.com/cleanup-network-policies" + // GatewayFinalizerCleanupNetworkPolicies is the finalizer to cleanup owned network policies. + GatewayFinalizerCleanupNetworkPolicies GatewayFinalizer = "gateway-operator.konghq.com/cleanup-network-policies" ) diff --git a/controller/gateway/controller_reconciler_utils.go b/controller/gateway/controller_reconciler_utils.go index 8978d9edb..0ec44b247 100644 --- a/controller/gateway/controller_reconciler_utils.go +++ b/controller/gateway/controller_reconciler_utils.go @@ -27,13 +27,11 @@ import ( "github.com/kong/gateway-operator/controller/pkg/secrets/ref" operatorerrors "github.com/kong/gateway-operator/internal/errors" gwtypes "github.com/kong/gateway-operator/internal/types" - "github.com/kong/gateway-operator/internal/utils/gatewayclass" "github.com/kong/gateway-operator/pkg/consts" gatewayutils "github.com/kong/gateway-operator/pkg/utils/gateway" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" k8sreduce "github.com/kong/gateway-operator/pkg/utils/kubernetes/reduce" k8sresources "github.com/kong/gateway-operator/pkg/utils/kubernetes/resources" - "github.com/kong/gateway-operator/pkg/vars" ) // ----------------------------------------------------------------------------- @@ -193,23 +191,6 @@ func gatewayAddressesFromService(svc corev1.Service) ([]gwtypes.GatewayStatusAdd return addresses, nil } -func (r *Reconciler) verifyGatewayClassSupport(ctx context.Context, gateway *gwtypes.Gateway) (*gatewayclass.Decorator, error) { - if gateway.Spec.GatewayClassName == "" { - return nil, operatorerrors.ErrUnsupportedGateway - } - - gwc := gatewayclass.NewDecorator() - if err := r.Client.Get(ctx, client.ObjectKey{Name: string(gateway.Spec.GatewayClassName)}, gwc.GatewayClass); err != nil { - return nil, err - } - - if string(gwc.Spec.ControllerName) != vars.ControllerName() { - return nil, operatorerrors.ErrUnsupportedGateway - } - - return gwc, nil -} - func (r *Reconciler) getOrCreateGatewayConfiguration(ctx context.Context, gatewayClass *gatewayv1.GatewayClass) (*operatorv1beta1.GatewayConfiguration, error) { gatewayConfig, err := r.getGatewayConfigForGatewayClass(ctx, gatewayClass) if err != nil { diff --git a/controller/gateway/controller_test.go b/controller/gateway/controller_test.go index 90c090a03..3d053c946 100644 --- a/controller/gateway/controller_test.go +++ b/controller/gateway/controller_test.go @@ -51,6 +51,94 @@ func TestGatewayReconciler_Reconcile(t *testing.T) { controlplaneSubResources []controllerruntimeclient.Object testBody func(t *testing.T, reconciler Reconciler, gatewayReq reconcile.Request) }{ + { + name: "gateway class not found - gateway is ignored", + gatewayReq: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test-namespace", + Name: "test-gateway", + }, + }, + gateway: &gwtypes.Gateway{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "gateway.networking.k8s.io/v1beta1", + Kind: "Gateway", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gateway", + Namespace: "test-namespace", + UID: types.UID(uuid.NewString()), + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "not-existing-gatewayclass", + }, + }, + testBody: func(t *testing.T, r Reconciler, gatewayReq reconcile.Request) { + ctx := context.Background() + res, err := r.Reconcile(ctx, gatewayReq) + require.NoError(t, err, "reconciliation should not return an error") + require.Equal(t, res, reconcile.Result{}, "reconciliation should not return a requeue") + + var gw gwtypes.Gateway + require.NoError(t, r.Client.Get(ctx, gatewayReq.NamespacedName, &gw)) + + require.Empty(t, gw.GetFinalizers(), "gateway should not have any finalizers as it's ignored") + }, + }, + { + name: "gateway class found, but controller name is not matching - gateway is ignored", + gatewayReq: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test-namespace", + Name: "test-gateway", + }, + }, + gateway: &gwtypes.Gateway{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "gateway.networking.k8s.io/v1beta1", + Kind: "Gateway", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gateway", + Namespace: "test-namespace", + UID: types.UID(uuid.NewString()), + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "test-gatewayclass", + }, + }, + gatewayClass: &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gatewayclass", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController("not-existing-controller"), + }, + Status: gatewayv1.GatewayClassStatus{ + Conditions: []metav1.Condition{ + { + Type: string(gatewayv1.GatewayClassConditionStatusAccepted), + Status: metav1.ConditionTrue, + ObservedGeneration: 0, + LastTransitionTime: metav1.Now(), + Reason: string(gatewayv1.GatewayClassReasonAccepted), + Message: "the gatewayclass has been accepted by the controller", + }, + }, + }, + }, + testBody: func(t *testing.T, r Reconciler, gatewayReq reconcile.Request) { + ctx := context.Background() + res, err := r.Reconcile(ctx, gatewayReq) + require.NoError(t, err, "reconciliation should not return an error") + require.Equal(t, res, reconcile.Result{}, "reconciliation should not return a requeue") + + var gw gwtypes.Gateway + require.NoError(t, r.Client.Get(ctx, gatewayReq.NamespacedName, &gw)) + + require.Empty(t, gw.GetFinalizers(), "gateway should not have any finalizers as it's ignored") + }, + }, { name: "service connectivity", gatewayReq: reconcile.Request{ @@ -169,6 +257,16 @@ func TestGatewayReconciler_Reconcile(t *testing.T) { // the dataplane service starts with no IP assigned, the gateway must be not ready _, err := reconciler.Reconcile(ctx, gatewayReq) require.NoError(t, err, "reconciliation returned an error") + + t.Log("verifying the gateway gets finalizers assigned") + var gateway gwtypes.Gateway + require.NoError(t, reconciler.Client.Get(ctx, gatewayReq.NamespacedName, &gateway)) + require.ElementsMatch(t, gateway.GetFinalizers(), []string{ + string(GatewayFinalizerCleanupControlPlanes), + string(GatewayFinalizerCleanupDataPlanes), + string(GatewayFinalizerCleanupNetworkPolicies), + }) + // need to trigger the Reconcile again because the first one only updated the finalizers _, err = reconciler.Reconcile(ctx, gatewayReq) require.NoError(t, err, "reconciliation returned an error") @@ -301,9 +399,11 @@ func TestGatewayReconciler_Reconcile(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ObjectsToAdd := []controllerruntimeclient.Object{ + objectsToAdd := []controllerruntimeclient.Object{ tc.gateway, - tc.gatewayClass, + } + if tc.gatewayClass != nil { + objectsToAdd = append(objectsToAdd, tc.gatewayClass) } for _, gatewaySubResource := range tc.gatewaySubResources { k8sutils.SetOwnerForObject(gatewaySubResource, tc.gateway) @@ -311,7 +411,7 @@ func TestGatewayReconciler_Reconcile(t *testing.T) { if gatewaySubResource.GetName() == "test-dataplane" { for _, dataplaneSubresource := range tc.dataplaneSubResources { k8sutils.SetOwnerForObject(dataplaneSubresource, gatewaySubResource) - ObjectsToAdd = append(ObjectsToAdd, dataplaneSubresource) + objectsToAdd = append(objectsToAdd, dataplaneSubresource) } } if gatewaySubResource.GetName() == "test-controlplane" { @@ -324,17 +424,17 @@ func TestGatewayReconciler_Reconcile(t *testing.T) { }) for _, controlplaneSubResource := range tc.controlplaneSubResources { k8sutils.SetOwnerForObject(controlplaneSubResource, gatewaySubResource) - ObjectsToAdd = append(ObjectsToAdd, controlplaneSubResource) + objectsToAdd = append(objectsToAdd, controlplaneSubResource) } } - ObjectsToAdd = append(ObjectsToAdd, gatewaySubResource) + objectsToAdd = append(objectsToAdd, gatewaySubResource) } fakeClient := fakectrlruntimeclient. NewClientBuilder(). WithScheme(scheme.Scheme). - WithObjects(ObjectsToAdd...). - WithStatusSubresource(ObjectsToAdd...). + WithObjects(objectsToAdd...). + WithStatusSubresource(objectsToAdd...). Build() reconciler := Reconciler{ diff --git a/controller/gateway/controller_watch.go b/controller/gateway/controller_watch.go index 6a861a4cb..34e99e8ea 100644 --- a/controller/gateway/controller_watch.go +++ b/controller/gateway/controller_watch.go @@ -20,6 +20,7 @@ import ( "github.com/kong/gateway-operator/controller/pkg/secrets/ref" operatorerrors "github.com/kong/gateway-operator/internal/errors" gwtypes "github.com/kong/gateway-operator/internal/types" + "github.com/kong/gateway-operator/internal/utils/gatewayclass" "github.com/kong/gateway-operator/pkg/consts" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" "github.com/kong/gateway-operator/pkg/utils/kubernetes/resources" @@ -41,13 +42,13 @@ func (r *Reconciler) gatewayHasMatchingGatewayClass(obj client.Object) bool { return false } - _, err := r.verifyGatewayClassSupport(context.Background(), gateway) + _, err := gatewayclass.Get(context.Background(), r.Client, string(gateway.Spec.GatewayClassName)) if err != nil { // filtering here is just an optimization, the reconciler will check the // 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.Is(err, operatorerrors.ErrUnsupportedGateway) + return !errors.As(err, &operatorerrors.ErrUnsupportedGateway{}) } return true diff --git a/controller/specialized/aigateway_controller.go b/controller/specialized/aigateway_controller.go index 26f2a32c9..9b32fbd5c 100644 --- a/controller/specialized/aigateway_controller.go +++ b/controller/specialized/aigateway_controller.go @@ -17,6 +17,7 @@ import ( "github.com/kong/gateway-operator/controller/pkg/log" "github.com/kong/gateway-operator/controller/pkg/watch" operatorerrors "github.com/kong/gateway-operator/internal/errors" + "github.com/kong/gateway-operator/internal/utils/gatewayclass" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" "github.com/kong/gateway-operator/pkg/vars" ) @@ -66,9 +67,9 @@ func (r *AIGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // and this check must be done here to ensure consistency. // // See: https://github.com/kubernetes-sigs/controller-runtime/issues/1996 - gwc, err := r.verifyGatewayClassSupport(ctx, &aigateway) + gwc, err := gatewayclass.Get(ctx, r.Client, aigateway.Spec.GatewayClassName) if err != nil { - if errors.Is(err, operatorerrors.ErrUnsupportedGateway) { + if errors.As(err, &operatorerrors.ErrUnsupportedGateway{}) { log.Debug(logger, "resource not supported, ignoring", aigateway, "ExpectedGatewayClass", vars.ControllerName()) return ctrl.Result{}, nil } diff --git a/controller/specialized/aigateway_controller_reconciler_utils.go b/controller/specialized/aigateway_controller_reconciler_utils.go deleted file mode 100644 index 122cda15e..000000000 --- a/controller/specialized/aigateway_controller_reconciler_utils.go +++ /dev/null @@ -1,42 +0,0 @@ -package specialized - -import ( - "context" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/kong/gateway-operator/api/v1alpha1" - operatorerrors "github.com/kong/gateway-operator/internal/errors" - "github.com/kong/gateway-operator/internal/utils/gatewayclass" - "github.com/kong/gateway-operator/pkg/vars" -) - -// ----------------------------------------------------------------------------- -// AIGatewayReconciler - Filter Functions -// ----------------------------------------------------------------------------- - -func (r *AIGatewayReconciler) verifyGatewayClassSupport( - ctx context.Context, - aigateway *v1alpha1.AIGateway, -) ( - *gatewayclass.Decorator, - error, -) { - if aigateway.Spec.GatewayClassName == "" { - return nil, operatorerrors.ErrUnsupportedGateway - } - - gwc := gatewayclass.NewDecorator() - if err := r.Client.Get(ctx, client.ObjectKey{ - Name: aigateway.Spec.GatewayClassName}, - gwc.GatewayClass, - ); err != nil { - return nil, err - } - - if string(gwc.Spec.ControllerName) != vars.ControllerName() { - return nil, operatorerrors.ErrUnsupportedGateway - } - - return gwc, nil -} diff --git a/controller/specialized/aigateway_controller_watch.go b/controller/specialized/aigateway_controller_watch.go index 3afa63382..ef8330720 100644 --- a/controller/specialized/aigateway_controller_watch.go +++ b/controller/specialized/aigateway_controller_watch.go @@ -13,6 +13,7 @@ import ( "github.com/kong/gateway-operator/api/v1alpha1" operatorerrors "github.com/kong/gateway-operator/internal/errors" + "github.com/kong/gateway-operator/internal/utils/gatewayclass" ) // ----------------------------------------------------------------------------- @@ -30,13 +31,13 @@ func (r *AIGatewayReconciler) aiGatewayHasMatchingGatewayClass(obj client.Object return false } - _, err := r.verifyGatewayClassSupport(context.Background(), aigateway) + _, err := gatewayclass.Get(context.Background(), r.Client, aigateway.Spec.GatewayClassName) if err != nil { // filtering here is just an optimization, the reconciler will check the // 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.Is(err, operatorerrors.ErrUnsupportedGateway) + return !errors.As(err, &operatorerrors.ErrUnsupportedGateway{}) } return true diff --git a/internal/errors/errors.go b/internal/errors/errors.go index 262705dc1..16d164cdf 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -2,6 +2,7 @@ package errors import ( "errors" + "fmt" ) // ----------------------------------------------------------------------------- @@ -17,13 +18,18 @@ var ErrUnexpectedObject = errors.New("unexpected object type provided") // ----------------------------------------------------------------------------- // ErrUnsupportedGateway is an error which indicates that a provided Gateway -// is not supported because it's GatewayClass was not associated with this -// controller. -var ErrUnsupportedGateway = errors.New("gateway not supported") - -// ErrTooManyDataPlaneNetworkPolicies is an error which indicates that a DataPlane -// has too many NetworkPolicies configured. -var ErrTooManyDataPlaneNetworkPolicies = errors.New("too many data plane network policies") +// is not supported. +type ErrUnsupportedGateway struct { + reason string +} + +func NewErrUnsupportedGateway(reason string) ErrUnsupportedGateway { + return ErrUnsupportedGateway{reason: reason} +} + +func (e ErrUnsupportedGateway) Error() string { + return fmt.Errorf("unsupported gateway: %s", e.reason).Error() +} // ----------------------------------------------------------------------------- // GatewayClass - Errors diff --git a/internal/utils/gatewayclass/get.go b/internal/utils/gatewayclass/get.go new file mode 100644 index 000000000..03eb9252d --- /dev/null +++ b/internal/utils/gatewayclass/get.go @@ -0,0 +1,46 @@ +package gatewayclass + +import ( + "context" + "fmt" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + operatorerrors "github.com/kong/gateway-operator/internal/errors" + "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 `ErrUnsupportedGateway` 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") + } + + gwc := NewDecorator() + if err := cl.Get(ctx, client.ObjectKey{Name: gatewayClassName}, gwc.GatewayClass); err != nil { + if k8serrors.IsNotFound(err) { + return nil, operatorerrors.NewErrUnsupportedGateway(fmt.Sprintf("GatewayClass %q not found", gatewayClassName)) + } + return nil, fmt.Errorf("unexpected error while fetching GatewayClass %q: %w", gatewayClassName, err) + } + + if string(gwc.Spec.ControllerName) != vars.ControllerName() { + return nil, operatorerrors.NewErrUnsupportedGateway(fmt.Sprintf( + "GatewayClass %q with %q ControllerName does not match expected %q", + gatewayClassName, + gwc.Spec.ControllerName, + vars.ControllerName(), + )) + } + + return gwc, nil +} diff --git a/internal/utils/gatewayclass/get_test.go b/internal/utils/gatewayclass/get_test.go new file mode 100644 index 000000000..8e9744853 --- /dev/null +++ b/internal/utils/gatewayclass/get_test.go @@ -0,0 +1,92 @@ +package gatewayclass_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + + operatorerrors "github.com/kong/gateway-operator/internal/errors" + "github.com/kong/gateway-operator/internal/utils/gatewayclass" + "github.com/kong/gateway-operator/modules/manager/scheme" + "github.com/kong/gateway-operator/pkg/vars" +) + +func TestGet(t *testing.T) { + testCases := []struct { + name string + gatewayClassName string + objectsToAdd []client.Object + expectedError error + }{ + { + name: "gateway class not found", + gatewayClassName: "non-existent-gateway-class", + objectsToAdd: []client.Object{}, + expectedError: operatorerrors.NewErrUnsupportedGateway( + `GatewayClass "non-existent-gateway-class" not found`, + ), + }, + { + name: "gateway class not supported", + gatewayClassName: "gateway-class-1", + objectsToAdd: []client.Object{&gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-1", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: "some-other-controller", + }, + }}, + expectedError: operatorerrors.NewErrUnsupportedGateway( + `GatewayClass "gateway-class-1" with "some-other-controller" ` + + `ControllerName does not match expected "konghq.com/gateway-operator"`, + ), + }, + { + name: "empty gateway class name", + gatewayClassName: "", + objectsToAdd: []client.Object{}, + expectedError: operatorerrors.NewErrUnsupportedGateway( + "no GatewayClassName provided", + ), + }, + { + name: "gateway class supported", + gatewayClassName: "gateway-class-2", + objectsToAdd: []client.Object{&gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-2", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController(vars.ControllerName()), + }, + }}, + expectedError: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fakeClient := fake. + NewClientBuilder(). + WithScheme(scheme.Get()). + WithObjects(tc.objectsToAdd...). + Build() + + gwc, err := gatewayclass.Get(context.Background(), fakeClient, tc.gatewayClassName) + if tc.expectedError != nil { + require.ErrorIs(t, err, tc.expectedError) + return + } + + require.NoError(t, err) + require.NotNil(t, gwc, "returned decorator should not be nil") + require.NotNil(t, gwc.GatewayClass, "decorator's GatewayClass should not be nil") + }) + } +}