diff --git a/controllers/kuadrant_controller.go b/controllers/kuadrant_controller.go index 3e23636c0..4d040bbce 100644 --- a/controllers/kuadrant_controller.go +++ b/controllers/kuadrant_controller.go @@ -25,6 +25,7 @@ import ( limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" appsv1 "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -46,6 +47,7 @@ const ( // KuadrantReconciler reconciles a Kuadrant object type KuadrantReconciler struct { *reconcilers.BaseReconciler + RestMapper meta.RESTMapper } //+kubebuilder:rbac:groups=kuadrant.io,resources=kuadrants,verbs=get;list;watch;create;update;patch;delete diff --git a/controllers/kuadrant_status.go b/controllers/kuadrant_status.go index 8bf1564e0..7df6b4832 100644 --- a/controllers/kuadrant_status.go +++ b/controllers/kuadrant_status.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -19,6 +20,7 @@ import ( kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" "github.com/kuadrant/kuadrant-operator/pkg/common" + kuadrantistioutils "github.com/kuadrant/kuadrant-operator/pkg/istio" ) const ( @@ -97,7 +99,18 @@ func (r *KuadrantReconciler) readyCondition(ctx context.Context, kObj *kuadrantv return cond, nil } - reason, err := r.checkLimitadorReady(ctx, kObj) + reason, err := r.checkGatewayProviders() + if err != nil { + return nil, err + } + if reason != nil { + cond.Status = metav1.ConditionFalse + cond.Reason = "GatewayAPIPRoviderNotFound" + cond.Message = *reason + return cond, nil + } + + reason, err = r.checkLimitadorReady(ctx, kObj) if err != nil { return nil, err } @@ -173,3 +186,32 @@ func (r *KuadrantReconciler) checkAuthorinoAvailable(ctx context.Context, kObj * return nil, nil } + +func (r *KuadrantReconciler) checkGatewayProviders() (*string, error) { + anyProviderFunc := func(checks []func(restMapper meta.RESTMapper) (bool, error)) (bool, error) { + for _, check := range checks { + ok, err := check(r.RestMapper) + if err != nil { + return false, err + } + if ok { + return true, nil + } + } + return false, nil + } + + anyProvider, err := anyProviderFunc([]func(restMapper meta.RESTMapper) (bool, error){ + kuadrantistioutils.IsIstioInstalled, + }) + + if err != nil { + return nil, err + } + + if anyProvider { + return nil, nil + } + + return ptr.To("GatewayAPI provider not found"), nil +} diff --git a/controllers/limitador_cluster_envoyfilter_controller.go b/controllers/limitador_cluster_envoyfilter_controller.go index 9100df09d..9ef3968d0 100644 --- a/controllers/limitador_cluster_envoyfilter_controller.go +++ b/controllers/limitador_cluster_envoyfilter_controller.go @@ -166,7 +166,7 @@ func (r *LimitadorClusterEnvoyFilterReconciler) desiredRateLimitingClusterEnvoyF // SetupWithManager sets up the controller with the Manager. func (r *LimitadorClusterEnvoyFilterReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantistioutils.IsIstioEnvoyFilterInstalled(mgr.GetRESTMapper()) + ok, err := kuadrantistioutils.IsEnvoyFilterInstalled(mgr.GetRESTMapper()) if err != nil { return err } diff --git a/controllers/rate_limiting_wasmplugin_controller.go b/controllers/rate_limiting_wasmplugin_controller.go index bddff6290..a49ad1ef2 100644 --- a/controllers/rate_limiting_wasmplugin_controller.go +++ b/controllers/rate_limiting_wasmplugin_controller.go @@ -366,7 +366,7 @@ func addHTTPRouteByGatewayIndexer(mgr ctrl.Manager, baseLogger logr.Logger) erro // SetupWithManager sets up the controller with the Manager. func (r *RateLimitingWASMPluginReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantistioutils.IsIstioWASMPluginInstalled(mgr.GetRESTMapper()) + ok, err := kuadrantistioutils.IsWASMPluginInstalled(mgr.GetRESTMapper()) if err != nil { return err } diff --git a/controllers/ratelimitpolicy_status_test.go b/controllers/ratelimitpolicy_status_test.go index d869c9f26..effc0c93b 100644 --- a/controllers/ratelimitpolicy_status_test.go +++ b/controllers/ratelimitpolicy_status_test.go @@ -1,3 +1,5 @@ +//go:build unit + package controllers import ( diff --git a/controllers/test_common.go b/controllers/test_common.go index 992bed31d..da948308d 100644 --- a/controllers/test_common.go +++ b/controllers/test_common.go @@ -164,6 +164,7 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { err = (&KuadrantReconciler{ BaseReconciler: kuadrantBaseReconciler, + RestMapper: mgr.GetRESTMapper(), }).SetupWithManager(mgr) Expect(err).NotTo(HaveOccurred()) diff --git a/main.go b/main.go index 40ca6472f..be2f21f2a 100644 --- a/main.go +++ b/main.go @@ -140,6 +140,7 @@ func main() { if err = (&controllers.KuadrantReconciler{ BaseReconciler: kuadrantBaseReconciler, + RestMapper: mgr.GetRESTMapper(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Kuadrant") os.Exit(1) diff --git a/pkg/istio/external_authorizer.go b/pkg/istio/external_authorizer.go index b31609568..f8902ef7d 100644 --- a/pkg/istio/external_authorizer.go +++ b/pkg/istio/external_authorizer.go @@ -120,7 +120,7 @@ func getIstioConfigObjects(ctx context.Context, cl client.Client) ([]configWrapp iop := &iopv1alpha1.IstioOperator{} istKey := client.ObjectKey{Name: controlPlaneProviderName(), Namespace: controlPlaneProviderNamespace()} err := cl.Get(ctx, istKey, iop) - // TODO(eguzki): burn this spaghetti code + // TODO(eguzki): 🔥 this spaghetti code 🔥 if err == nil { configsToUpdate = append(configsToUpdate, NewOperatorWrapper(iop)) } else if meta.IsNoMatchError(err) || apierrors.IsNotFound(err) { diff --git a/pkg/istio/utils.go b/pkg/istio/utils.go index 3a7dd15f3..e8b0c3817 100644 --- a/pkg/istio/utils.go +++ b/pkg/istio/utils.go @@ -7,6 +7,7 @@ import ( istiocommon "istio.io/api/type/v1beta1" istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" istioclientnetworkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" + istioclientgosecurityv1beta1 "istio.io/client-go/pkg/apis/security/v1beta1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" @@ -35,7 +36,7 @@ func PolicyTargetRefFromGateway(gateway *gatewayapiv1.Gateway) *istiocommon.Poli } } -func IsIstioEnvoyFilterInstalled(restMapper meta.RESTMapper) (bool, error) { +func IsEnvoyFilterInstalled(restMapper meta.RESTMapper) (bool, error) { _, err := restMapper.RESTMapping( schema.GroupKind{Group: istioclientnetworkingv1alpha3.GroupName, Kind: "EnvoyFilter"}, istioclientnetworkingv1alpha3.SchemeGroupVersion.Version, @@ -52,7 +53,7 @@ func IsIstioEnvoyFilterInstalled(restMapper meta.RESTMapper) (bool, error) { return false, err } -func IsIstioWASMPluginInstalled(restMapper meta.RESTMapper) (bool, error) { +func IsWASMPluginInstalled(restMapper meta.RESTMapper) (bool, error) { _, err := restMapper.RESTMapping( schema.GroupKind{Group: istioclientgoextensionv1alpha1.GroupName, Kind: "WasmPlugin"}, istioclientgoextensionv1alpha1.SchemeGroupVersion.Version, @@ -68,3 +69,49 @@ func IsIstioWASMPluginInstalled(restMapper meta.RESTMapper) (bool, error) { return false, err } + +func IsAuthorizationPolicyInstalled(restMapper meta.RESTMapper) (bool, error) { + _, err := restMapper.RESTMapping( + schema.GroupKind{Group: istioclientgosecurityv1beta1.GroupName, Kind: "AuthorizationPolicy"}, + istioclientgosecurityv1beta1.SchemeGroupVersion.Version, + ) + + if err == nil { + return true, nil + } + + if meta.IsNoMatchError(err) { + return false, nil + } + + return false, err +} + +func IsIstioInstalled(restMapper meta.RESTMapper) (bool, error) { + ok, err := IsWASMPluginInstalled(restMapper) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + + ok, err = IsAuthorizationPolicyInstalled(restMapper) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + + ok, err = IsEnvoyFilterInstalled(restMapper) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + + // Istio found + return true, nil +} diff --git a/tests/bare_k8s/kuadrant_controller_test.go b/tests/bare_k8s/kuadrant_controller_test.go index 9775e9a4a..41a676763 100644 --- a/tests/bare_k8s/kuadrant_controller_test.go +++ b/tests/bare_k8s/kuadrant_controller_test.go @@ -11,7 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" - "github.com/kuadrant/kuadrant-operator/controllers" + "github.com/kuadrant/kuadrant-operator/tests" ) var _ = Describe("Kuadrant controller is disabled", func() { @@ -22,11 +22,11 @@ var _ = Describe("Kuadrant controller is disabled", func() { ) BeforeEach(func(ctx SpecContext) { - testNamespace = controllers.CreateNamespace(ctx, testClient()) + testNamespace = tests.CreateNamespace(ctx, testClient()) }) AfterEach(func(ctx SpecContext) { - controllers.DeleteNamespace(ctx, testClient(), testNamespace) + tests.DeleteNamespace(ctx, testClient(), testNamespace) }, afterEachTimeOut) Context("when default kuadrant CR is created", func() { diff --git a/tests/gatewayapi/kuadrant_controller_test.go b/tests/gatewayapi/kuadrant_controller_test.go index d1ac1c068..031ca0a5a 100644 --- a/tests/gatewayapi/kuadrant_controller_test.go +++ b/tests/gatewayapi/kuadrant_controller_test.go @@ -7,11 +7,13 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" "github.com/kuadrant/kuadrant-operator/controllers" + "github.com/kuadrant/kuadrant-operator/tests" ) var _ = Describe("Kuadrant controller when gateway provider is missing", func() { @@ -22,11 +24,11 @@ var _ = Describe("Kuadrant controller when gateway provider is missing", func() ) BeforeEach(func(ctx SpecContext) { - testNamespace = controllers.CreateNamespace(ctx, testClient()) + testNamespace = tests.CreateNamespace(ctx, testClient()) }) AfterEach(func(ctx SpecContext) { - controllers.DeleteNamespace(ctx, testClient(), testNamespace) + tests.DeleteNamespace(ctx, testClient(), testNamespace) }, afterEachTimeOut) Context("when default kuadrant CR is created", func() { @@ -43,14 +45,16 @@ var _ = Describe("Kuadrant controller when gateway provider is missing", func() } Expect(testClient().Create(ctx, kuadrantCR)).ToNot(HaveOccurred()) - kObj := &kuadrantv1beta1.Kuadrant{} - err := testClient().Get(ctx, client.ObjectKeyFromObject(kuadrantCR), kObj) - Expect(err).ToNot(HaveOccurred()) - // expected empty. The controller should not have updated it - - // TODO - - //Expect(kObj.Status).To(Equal(kuadrantv1beta1.KuadrantStatus{})) + Eventually(func(g Gomega) { + kObj := &kuadrantv1beta1.Kuadrant{} + err := testClient().Get(ctx, client.ObjectKeyFromObject(kuadrantCR), kObj) + g.Expect(err).ToNot(HaveOccurred()) + cond := meta.FindStatusCondition(kObj.Status.Conditions, string(controllers.ReadyConditionType)) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + g.Expect(cond.Reason).To(Equal("GatewayAPIPRoviderNotFound")) + g.Expect(cond.Message).To(Equal("GatewayAPI provider not found")) + }, time.Minute, 15*time.Second).WithContext(ctx).Should(Succeed()) }) }) }) diff --git a/tests/istio/kuadrant_controller_test.go b/tests/istio/kuadrant_controller_test.go index 9beb2d633..78b58a86b 100644 --- a/tests/istio/kuadrant_controller_test.go +++ b/tests/istio/kuadrant_controller_test.go @@ -7,14 +7,16 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + "github.com/kuadrant/kuadrant-operator/controllers" "github.com/kuadrant/kuadrant-operator/tests" ) -var _ = Describe("Kuadrant controller is disabled", func() { +var _ = Describe("Kuadrant controller on istio", func() { var ( testNamespace string kuadrantName string = "local" @@ -30,7 +32,7 @@ var _ = Describe("Kuadrant controller is disabled", func() { }, afterEachTimeOut) Context("when default kuadrant CR is created", func() { - It("Status is not populated", func(ctx SpecContext) { + It("Status is ready", func(ctx SpecContext) { kuadrantCR := &kuadrantv1beta1.Kuadrant{ TypeMeta: metav1.TypeMeta{ Kind: "Kuadrant", @@ -43,12 +45,16 @@ var _ = Describe("Kuadrant controller is disabled", func() { } Expect(testClient().Create(ctx, kuadrantCR)).ToNot(HaveOccurred()) - kObj := &kuadrantv1beta1.Kuadrant{} - err := testClient().Get(ctx, client.ObjectKeyFromObject(kuadrantCR), kObj) - Expect(err).ToNot(HaveOccurred()) - // expected empty. The controller should not have updated it - // TODO: status should not be empty - Expect(kObj.Status).To(Equal(kuadrantv1beta1.KuadrantStatus{})) + Eventually(func(g Gomega) { + kObj := &kuadrantv1beta1.Kuadrant{} + err := testClient().Get(ctx, client.ObjectKeyFromObject(kuadrantCR), kObj) + g.Expect(err).ToNot(HaveOccurred()) + cond := meta.FindStatusCondition(kObj.Status.Conditions, string(controllers.ReadyConditionType)) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(cond.Reason).To(Equal("Ready")) + g.Expect(cond.Message).To(Equal("Kuadrant is ready")) + }, time.Minute, 15*time.Second).WithContext(ctx).Should(Succeed()) }) }) })