diff --git a/controllers/auth_policies_validator.go b/controllers/auth_policies_validator.go index ac232735b..8f66dec2b 100644 --- a/controllers/auth_policies_validator.go +++ b/controllers/auth_policies_validator.go @@ -12,10 +12,14 @@ import ( "k8s.io/utils/ptr" kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" - kuadrant "github.com/kuadrant/kuadrant-operator/pkg/kuadrant" + "github.com/kuadrant/kuadrant-operator/pkg/kuadrant" ) -type AuthPolicyValidator struct{} +type AuthPolicyValidator struct { + isGatewayAPIInstalled bool + isGatewayProviderInstalled bool + isAuthorinoOperatorInstalled bool +} // AuthPolicyValidator subscribes to events with potential to flip the validity of auth policies func (r *AuthPolicyValidator) Subscription() controller.Subscription { @@ -41,6 +45,18 @@ func (r *AuthPolicyValidator) Validate(ctx context.Context, _ []controller.Resou defer logger.V(1).Info("finished validating auth policies") state.Store(StateAuthPolicyValid, lo.SliceToMap(policies, func(policy machinery.Policy) (string, error) { + if !r.isGatewayAPIInstalled { + return policy.GetLocator(), kuadrant.MissingGatewayAPIError() + } + + if !r.isGatewayProviderInstalled { + return policy.GetLocator(), kuadrant.MissingGatewayProviderError() + } + + if !r.isAuthorinoOperatorInstalled { + return policy.GetLocator(), kuadrant.MissingAuthorinoOperatorError() + } + var err error if len(policy.GetTargetRefs()) > 0 && len(topology.Targetables().Children(policy)) == 0 { ref := policy.GetTargetRefs()[0] diff --git a/controllers/data_plane_policies_workflow.go b/controllers/data_plane_policies_workflow.go index ca7849a19..6dc5c17b2 100644 --- a/controllers/data_plane_policies_workflow.go +++ b/controllers/data_plane_policies_workflow.go @@ -54,11 +54,12 @@ var ( //+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies/status,verbs=get;update;patch //+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies/finalizers,verbs=update -func NewDataPlanePoliciesWorkflow(client *dynamic.DynamicClient, isIstioInstalled, isEnvoyGatewayInstalled bool) *controller.Workflow { +func NewDataPlanePoliciesWorkflow(client *dynamic.DynamicClient, isGatewayAPInstalled, isIstioInstalled, isEnvoyGatewayInstalled, isLimitadorOperatorInstalled, isAuthorinoOperatorInstalled bool) *controller.Workflow { + isGatewayProviderInstalled := isIstioInstalled || isEnvoyGatewayInstalled dataPlanePoliciesValidation := &controller.Workflow{ Tasks: []controller.ReconcileFunc{ - (&AuthPolicyValidator{}).Subscription().Reconcile, - (&RateLimitPolicyValidator{}).Subscription().Reconcile, + (&AuthPolicyValidator{isGatewayAPIInstalled: isGatewayAPInstalled, isAuthorinoOperatorInstalled: isAuthorinoOperatorInstalled, isGatewayProviderInstalled: isGatewayProviderInstalled}).Subscription().Reconcile, + (&RateLimitPolicyValidator{isGatewayAPIInstalled: isGatewayAPInstalled, isLimitadorOperatorInstalled: isLimitadorOperatorInstalled, isGatewayProviderInstalled: isGatewayProviderInstalled}).Subscription().Reconcile, }, } diff --git a/controllers/dns_workflow.go b/controllers/dns_workflow.go index 6ec58ff06..91e40d468 100644 --- a/controllers/dns_workflow.go +++ b/controllers/dns_workflow.go @@ -46,9 +46,9 @@ var ( //+kubebuilder:rbac:groups=kuadrant.io,resources=dnsrecords,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=kuadrant.io,resources=dnsrecords/status,verbs=get -func NewDNSWorkflow(client *dynamic.DynamicClient, scheme *runtime.Scheme) *controller.Workflow { +func NewDNSWorkflow(client *dynamic.DynamicClient, scheme *runtime.Scheme, isGatewayAPIInstalled, isDNSOperatorInstalled bool) *controller.Workflow { return &controller.Workflow{ - Precondition: NewDNSPoliciesValidator().Subscription().Reconcile, + Precondition: NewDNSPoliciesValidator(isGatewayAPIInstalled, isDNSOperatorInstalled).Subscription().Reconcile, Tasks: []controller.ReconcileFunc{ NewEffectiveDNSPoliciesReconciler(client, scheme).Subscription().Reconcile, }, diff --git a/controllers/dnspolicies_validator.go b/controllers/dnspolicies_validator.go index 776d98b4d..a84dccdbb 100644 --- a/controllers/dnspolicies_validator.go +++ b/controllers/dnspolicies_validator.go @@ -18,11 +18,17 @@ import ( "github.com/kuadrant/kuadrant-operator/pkg/kuadrant" ) -func NewDNSPoliciesValidator() *DNSPoliciesValidator { - return &DNSPoliciesValidator{} +func NewDNSPoliciesValidator(isGatewayAPIInstalled, isDNSOperatorInstalled bool) *DNSPoliciesValidator { + return &DNSPoliciesValidator{ + isGatewayAPIInstalled: isGatewayAPIInstalled, + isDNSOperatorInstalled: isDNSOperatorInstalled, + } } -type DNSPoliciesValidator struct{} +type DNSPoliciesValidator struct { + isGatewayAPIInstalled bool + isDNSOperatorInstalled bool +} func (r *DNSPoliciesValidator) Subscription() controller.Subscription { return controller.Subscription{ @@ -45,6 +51,14 @@ func (r *DNSPoliciesValidator) validate(ctx context.Context, _ []controller.Reso logger.V(1).Info("validating dns policies", "policies", len(policies)) state.Store(StateDNSPolicyAcceptedKey, lo.SliceToMap(policies, func(p machinery.Policy) (string, error) { + if !r.isGatewayAPIInstalled { + return p.GetLocator(), kuadrant.MissingGatewayAPIError() + } + + if !r.isDNSOperatorInstalled { + return p.GetLocator(), kuadrant.MissingDNSOperatorError() + } + policy := p.(*kuadrantv1.DNSPolicy) if err := isTargetRefsFound(topology, policy); err != nil { diff --git a/controllers/kuadrant_status_updater.go b/controllers/kuadrant_status_updater.go index 4f96068b6..7fae5b3e4 100644 --- a/controllers/kuadrant_status_updater.go +++ b/controllers/kuadrant_status_updater.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/go-logr/logr" + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" "github.com/kuadrant/policy-machinery/controller" "github.com/kuadrant/policy-machinery/machinery" corev1 "k8s.io/api/core/v1" @@ -18,6 +19,7 @@ import ( kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" "github.com/kuadrant/kuadrant-operator/pkg/authorino" + "github.com/kuadrant/kuadrant-operator/pkg/kuadrant" ) const ( @@ -25,12 +27,21 @@ const ( ) type KuadrantStatusUpdater struct { - Client *dynamic.DynamicClient - HasGateway bool + Client *dynamic.DynamicClient + isGatwayAPIInstalled bool + isGatewayProviderInstalled bool + isLimitadorOperatorInstalled bool + isAuthorinoOperatorInstalled bool } -func NewKuadrantStatusUpdater(client *dynamic.DynamicClient, isIstioInstalled, isEnvoyGatewayInstalled bool) *KuadrantStatusUpdater { - return &KuadrantStatusUpdater{Client: client, HasGateway: isIstioInstalled || isEnvoyGatewayInstalled} +func NewKuadrantStatusUpdater(client *dynamic.DynamicClient, isGatewayAPIInstalled, isGatewayProviderInstalled, isLimitadorOperatorInstalled, isAuthorinoOperatorInstalled bool) *KuadrantStatusUpdater { + return &KuadrantStatusUpdater{ + Client: client, + isGatwayAPIInstalled: isGatewayAPIInstalled, + isGatewayProviderInstalled: isGatewayProviderInstalled, + isLimitadorOperatorInstalled: isLimitadorOperatorInstalled, + isAuthorinoOperatorInstalled: isAuthorinoOperatorInstalled, + } } func (r *KuadrantStatusUpdater) Subscription() *controller.Subscription { @@ -117,10 +128,35 @@ func (r *KuadrantStatusUpdater) readyCondition(topology *machinery.Topology, log Message: "Kuadrant is ready", } - if !r.HasGateway { + if !r.isGatwayAPIInstalled { + err := kuadrant.MissingGatewayAPIError() + cond.Status = metav1.ConditionFalse + cond.Reason = string(err.Reason()) + cond.Message = err.Error() + return cond + } + + if !r.isGatewayProviderInstalled { + err := kuadrant.MissingGatewayProviderError() + cond.Status = metav1.ConditionFalse + cond.Reason = string(err.Reason()) + cond.Message = err.Error() + return cond + } + + if !r.isLimitadorOperatorInstalled { + err := kuadrant.MissingLimitadorOperatorError() + cond.Status = metav1.ConditionFalse + cond.Reason = string(err.Reason()) + cond.Message = err.Error() + return cond + } + + if !r.isAuthorinoOperatorInstalled { + err := kuadrant.MissingAuthorinoOperatorError() cond.Status = metav1.ConditionFalse - cond.Reason = "GatewayAPIProviderNotFound" - cond.Message = "GatewayAPI provider not found" + cond.Reason = string(err.Reason()) + cond.Message = err.Error() return cond } @@ -148,7 +184,7 @@ func checkLimitadorReady(topology *machinery.Topology, logger logr.Logger) *stri return ptr.To("limitador resoure not in topology") } - statusConditionReady := meta.FindStatusCondition(limitadorObj.Status.Conditions, "Ready") + statusConditionReady := meta.FindStatusCondition(limitadorObj.Status.Conditions, limitadorv1alpha1.StatusConditionReady) if statusConditionReady == nil { return ptr.To("Ready condition not found") } diff --git a/controllers/ratelimit_policies_validator.go b/controllers/ratelimit_policies_validator.go index 4992950a0..9657c9b12 100644 --- a/controllers/ratelimit_policies_validator.go +++ b/controllers/ratelimit_policies_validator.go @@ -12,10 +12,14 @@ import ( "k8s.io/utils/ptr" kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" - kuadrant "github.com/kuadrant/kuadrant-operator/pkg/kuadrant" + "github.com/kuadrant/kuadrant-operator/pkg/kuadrant" ) -type RateLimitPolicyValidator struct{} +type RateLimitPolicyValidator struct { + isLimitadorOperatorInstalled bool + isGatewayAPIInstalled bool + isGatewayProviderInstalled bool +} // RateLimitPolicyValidator subscribes to events with potential to flip the validity of rate limit policies func (r *RateLimitPolicyValidator) Subscription() controller.Subscription { @@ -41,6 +45,18 @@ func (r *RateLimitPolicyValidator) Validate(ctx context.Context, _ []controller. defer logger.V(1).Info("finished validating rate limit policies") state.Store(StateRateLimitPolicyValid, lo.SliceToMap(policies, func(policy machinery.Policy) (string, error) { + if !r.isGatewayAPIInstalled { + return policy.GetLocator(), kuadrant.MissingGatewayAPIError() + } + + if !r.isGatewayProviderInstalled { + return policy.GetLocator(), kuadrant.MissingGatewayProviderError() + } + + if !r.isLimitadorOperatorInstalled { + return policy.GetLocator(), kuadrant.MissingLimitadorOperatorError() + } + var err error if len(policy.GetTargetRefs()) > 0 && len(topology.Targetables().Children(policy)) == 0 { ref := policy.GetTargetRefs()[0] diff --git a/controllers/state_of_the_world.go b/controllers/state_of_the_world.go index df25bcb3a..be04407ea 100644 --- a/controllers/state_of_the_world.go +++ b/controllers/state_of_the_world.go @@ -39,6 +39,7 @@ import ( "github.com/kuadrant/kuadrant-operator/pkg/kuadrant" "github.com/kuadrant/kuadrant-operator/pkg/openshift" "github.com/kuadrant/kuadrant-operator/pkg/openshift/consoleplugin" + "github.com/kuadrant/kuadrant-operator/pkg/utils" ) var ( @@ -111,23 +112,6 @@ func NewPolicyMachineryController(manager ctrlruntime.Manager, client *dynamic.D controller.WithPredicates(&ctrlruntimepredicate.TypedGenerationChangedPredicate[*corev1.ConfigMap]{}), controller.FilterResourcesByLabel[*corev1.ConfigMap](fmt.Sprintf("%s=true", kuadrant.TopologyLabel)), )), - // TODO: Move as boot options for Limitador and Authorino as there can be a possibility that the operators are not installed - controller.WithRunnable("limitador watcher", controller.Watch( - &limitadorv1alpha1.Limitador{}, - kuadrantv1beta1.LimitadorsResource, - metav1.NamespaceAll, - )), - controller.WithRunnable("authorino watcher", controller.Watch( - &authorinooperatorv1beta1.Authorino{}, - kuadrantv1beta1.AuthorinosResource, - metav1.NamespaceAll, - )), - controller.WithRunnable("authconfig watcher", controller.Watch( - &authorinov1beta3.AuthConfig{}, - authorino.AuthConfigsResource, - metav1.NamespaceAll, - controller.FilterResourcesByLabel[*authorinov1beta3.AuthConfig](fmt.Sprintf("%s=true", kuadrantManagedLabelKey)), - )), controller.WithPolicyKinds( kuadrantv1.DNSPolicyGroupKind, kuadrantv1.TLSPolicyGroupKind, @@ -137,15 +121,9 @@ func NewPolicyMachineryController(manager ctrlruntime.Manager, client *dynamic.D controller.WithObjectKinds( kuadrantv1beta1.KuadrantGroupKind, ConfigMapGroupKind, - kuadrantv1beta1.LimitadorGroupKind, - kuadrantv1beta1.AuthorinoGroupKind, - authorino.AuthConfigGroupKind, ), controller.WithObjectLinks( kuadrantv1beta1.LinkKuadrantToGatewayClasses, - kuadrantv1beta1.LinkKuadrantToLimitador, - kuadrantv1beta1.LinkKuadrantToAuthorino, - authorino.LinkHTTPRouteRuleToAuthConfig, ), } @@ -173,11 +151,14 @@ type BootOptionsBuilder struct { client *dynamic.DynamicClient // Internal configurations - isGatewayAPIInstalled bool - isEnvoyGatewayInstalled bool - isIstioInstalled bool - isCertManagerInstalled bool - isConsolePluginInstalled bool + isGatewayAPIInstalled bool + isEnvoyGatewayInstalled bool + isIstioInstalled bool + isCertManagerInstalled bool + isConsolePluginInstalled bool + isDNSOperatorInstalled bool + isLimitadorOperatorInstalled bool + isAuthorinoOperatorInstalled bool } func (b *BootOptionsBuilder) getOptions() []controller.ControllerOption { @@ -188,6 +169,8 @@ func (b *BootOptionsBuilder) getOptions() []controller.ControllerOption { opts = append(opts, b.getCertManagerOptions()...) opts = append(opts, b.getConsolePluginOptions()...) opts = append(opts, b.getDNSOperatorOptions()...) + opts = append(opts, b.getLimitadorOperatorOptions()...) + opts = append(opts, b.getAuthorinoOperatorOptions()...) return opts } @@ -250,7 +233,6 @@ func (b *BootOptionsBuilder) getEnvoyGatewayOptions() []controller.ControllerOpt envoygateway.LinkGatewayToEnvoyExtensionPolicy, ), ) - // TODO: add specific tasks to workflow } return opts @@ -285,7 +267,6 @@ func (b *BootOptionsBuilder) getIstioOptions() []controller.ControllerOption { istio.LinkGatewayToWasmPlugin, ), ) - // TODO: add istio specific tasks to workflow } return opts @@ -324,34 +305,101 @@ func (b *BootOptionsBuilder) getConsolePluginOptions() []controller.ControllerOp func (b *BootOptionsBuilder) getDNSOperatorOptions() []controller.ControllerOption { var opts []controller.ControllerOption + var err error + b.isDNSOperatorInstalled, err = utils.IsCRDInstalled(b.manager.GetRESTMapper(), DNSRecordGroupKind.Group, DNSRecordGroupKind.Kind, kuadrantdnsv1alpha1.GroupVersion.Version) + if err != nil || !b.isDNSOperatorInstalled { + b.logger.Info("dns operator is not installed, skipping related watches and reconcilers", "err", err) + } else { + opts = append(opts, + controller.WithRunnable("dnsrecord watcher", controller.Watch( + &kuadrantdnsv1alpha1.DNSRecord{}, DNSRecordResource, metav1.NamespaceAll, + controller.FilterResourcesByLabel[*kuadrantdnsv1alpha1.DNSRecord](fmt.Sprintf("%s=%s", AppLabelKey, AppLabelValue)))), + controller.WithObjectKinds( + DNSRecordGroupKind, + ), + controller.WithObjectLinks( + LinkListenerToDNSRecord, + LinkDNSPolicyToDNSRecord, + ), + ) + } + + return opts +} + +func (b *BootOptionsBuilder) getLimitadorOperatorOptions() []controller.ControllerOption { + var opts []controller.ControllerOption + var err error + b.isLimitadorOperatorInstalled, err = utils.IsCRDInstalled(b.manager.GetRESTMapper(), kuadrantv1beta1.LimitadorGroupKind.Group, kuadrantv1beta1.LimitadorGroupKind.Kind, limitadorv1alpha1.GroupVersion.Version) + if err != nil || !b.isLimitadorOperatorInstalled { + b.logger.Info("limitador operator is not installed, skipping related watches and reconcilers", "err", err) + } else { + opts = append(opts, + controller.WithRunnable("limitador watcher", controller.Watch( + &limitadorv1alpha1.Limitador{}, + kuadrantv1beta1.LimitadorsResource, + metav1.NamespaceAll, + )), + controller.WithObjectKinds( + kuadrantv1beta1.LimitadorGroupKind, + ), + controller.WithObjectLinks( + kuadrantv1beta1.LinkKuadrantToLimitador, + ), + ) + } + + return opts +} + +func (b *BootOptionsBuilder) getAuthorinoOperatorOptions() []controller.ControllerOption { + var opts []controller.ControllerOption + var err error + b.isAuthorinoOperatorInstalled, err = authorino.IsAuthorinoOperatorInstalled(b.manager.GetRESTMapper(), b.logger) + if err != nil || !b.isAuthorinoOperatorInstalled { + b.logger.Info("authorino operator is not installed, skipping related watches and reconcilers", "err", err) + return opts + } + opts = append(opts, - controller.WithRunnable("dnsrecord watcher", controller.Watch( - &kuadrantdnsv1alpha1.DNSRecord{}, DNSRecordResource, metav1.NamespaceAll, - controller.FilterResourcesByLabel[*kuadrantdnsv1alpha1.DNSRecord](fmt.Sprintf("%s=%s", AppLabelKey, AppLabelValue)))), + controller.WithRunnable("authorino watcher", controller.Watch( + &authorinooperatorv1beta1.Authorino{}, + kuadrantv1beta1.AuthorinosResource, + metav1.NamespaceAll, + )), + controller.WithRunnable("authconfig watcher", controller.Watch( + &authorinov1beta3.AuthConfig{}, + authorino.AuthConfigsResource, + metav1.NamespaceAll, + controller.FilterResourcesByLabel[*authorinov1beta3.AuthConfig](fmt.Sprintf("%s=true", kuadrantManagedLabelKey)), + )), controller.WithObjectKinds( - DNSRecordGroupKind, + kuadrantv1beta1.AuthorinoGroupKind, + authorino.AuthConfigGroupKind, ), controller.WithObjectLinks( - LinkListenerToDNSRecord, - LinkDNSPolicyToDNSRecord, + kuadrantv1beta1.LinkKuadrantToAuthorino, + authorino.LinkHTTPRouteRuleToAuthConfig, ), ) return opts } +func (b *BootOptionsBuilder) isGatewayProviderInstalled() bool { + return b.isIstioInstalled || b.isEnvoyGatewayInstalled +} + func (b *BootOptionsBuilder) Reconciler() controller.ReconcileFunc { mainWorkflow := &controller.Workflow{ Precondition: initWorkflow(b.client).Run, Tasks: []controller.ReconcileFunc{ - NewAuthorinoReconciler(b.client).Subscription().Reconcile, - NewLimitadorReconciler(b.client).Subscription().Reconcile, - NewDNSWorkflow(b.client, b.manager.GetScheme()).Run, - NewTLSWorkflow(b.client, b.manager.GetScheme(), b.isCertManagerInstalled).Run, - NewDataPlanePoliciesWorkflow(b.client, b.isIstioInstalled, b.isEnvoyGatewayInstalled).Run, - NewKuadrantStatusUpdater(b.client, b.isIstioInstalled, b.isEnvoyGatewayInstalled).Subscription().Reconcile, + NewDNSWorkflow(b.client, b.manager.GetScheme(), b.isGatewayAPIInstalled, b.isDNSOperatorInstalled).Run, + NewTLSWorkflow(b.client, b.manager.GetScheme(), b.isGatewayAPIInstalled, b.isCertManagerInstalled).Run, + NewDataPlanePoliciesWorkflow(b.client, b.isGatewayAPIInstalled, b.isIstioInstalled, b.isEnvoyGatewayInstalled, b.isLimitadorOperatorInstalled, b.isAuthorinoOperatorInstalled).Run, + NewKuadrantStatusUpdater(b.client, b.isGatewayAPIInstalled, b.isGatewayProviderInstalled(), b.isLimitadorOperatorInstalled, b.isAuthorinoOperatorInstalled).Subscription().Reconcile, }, - Postcondition: finalStepsWorkflow(b.client, b.isIstioInstalled, b.isGatewayAPIInstalled).Run, + Postcondition: finalStepsWorkflow(b.client, b.isGatewayAPIInstalled, b.isIstioInstalled, b.isEnvoyGatewayInstalled).Run, } if b.isConsolePluginInstalled { @@ -360,6 +408,17 @@ func (b *BootOptionsBuilder) Reconciler() controller.ReconcileFunc { ) } + if b.isLimitadorOperatorInstalled { + mainWorkflow.Tasks = append(mainWorkflow.Tasks, + NewLimitadorReconciler(b.client).Subscription().Reconcile, + ) + } + + if b.isAuthorinoOperatorInstalled { + mainWorkflow.Tasks = append(mainWorkflow.Tasks, + NewAuthorinoReconciler(b.client).Subscription().Reconcile) + } + return mainWorkflow.Run } @@ -434,12 +493,16 @@ func initWorkflow(client *dynamic.DynamicClient) *controller.Workflow { } } -func finalStepsWorkflow(client *dynamic.DynamicClient, isIstioInstalled, isEnvoyGatewayInstalled bool) *controller.Workflow { +func finalStepsWorkflow(client *dynamic.DynamicClient, isGatewayAPIInstalled, isIstioInstalled, isEnvoyGatewayInstalled bool) *controller.Workflow { workflow := &controller.Workflow{ - Tasks: []controller.ReconcileFunc{ + Tasks: []controller.ReconcileFunc{}, + } + + if isGatewayAPIInstalled { + workflow.Tasks = append(workflow.Tasks, NewGatewayPolicyDiscoverabilityReconciler(client).Subscription().Reconcile, NewHTTPRoutePolicyDiscoverabilityReconciler(client).Subscription().Reconcile, - }, + ) } if isIstioInstalled { @@ -453,8 +516,6 @@ func finalStepsWorkflow(client *dynamic.DynamicClient, isIstioInstalled, isEnvoy return workflow } -var ErrMissingKuadrant = fmt.Errorf("missing kuadrant object in topology") - func GetKuadrantFromTopology(topology *machinery.Topology) *kuadrantv1beta1.Kuadrant { kuadrants := lo.FilterMap(topology.Objects().Roots(), func(root machinery.Object, _ int) (controller.Object, bool) { o, isSortable := root.(controller.Object) diff --git a/controllers/tls_workflow.go b/controllers/tls_workflow.go index 7665e77c7..bb64993ba 100644 --- a/controllers/tls_workflow.go +++ b/controllers/tls_workflow.go @@ -40,9 +40,9 @@ var ( //+kubebuilder:rbac:groups="cert-manager.io",resources=clusterissuers,verbs=get;list;watch; //+kubebuilder:rbac:groups="cert-manager.io",resources=certificates,verbs=get;list;watch;create;update;patch;delete -func NewTLSWorkflow(client *dynamic.DynamicClient, scheme *runtime.Scheme, isCertManagerInstalled bool) *controller.Workflow { +func NewTLSWorkflow(client *dynamic.DynamicClient, scheme *runtime.Scheme, isGatewayAPIInstalled, isCertManagerInstalled bool) *controller.Workflow { return &controller.Workflow{ - Precondition: NewTLSPoliciesValidator(isCertManagerInstalled).Subscription().Reconcile, + Precondition: NewTLSPoliciesValidator(isGatewayAPIInstalled, isCertManagerInstalled).Subscription().Reconcile, Tasks: []controller.ReconcileFunc{ NewEffectiveTLSPoliciesReconciler(client, scheme).Subscription().Reconcile, }, diff --git a/controllers/tlspolicies_validator.go b/controllers/tlspolicies_validator.go index 61e0df16c..3432c584f 100644 --- a/controllers/tlspolicies_validator.go +++ b/controllers/tlspolicies_validator.go @@ -19,13 +19,15 @@ import ( "github.com/kuadrant/kuadrant-operator/pkg/kuadrant" ) -func NewTLSPoliciesValidator(isCertManagerInstalled bool) *TLSPoliciesValidator { +func NewTLSPoliciesValidator(isGatewayAPIInstalled, isCertManagerInstalled bool) *TLSPoliciesValidator { return &TLSPoliciesValidator{ + isGatewayAPIInstalled: isGatewayAPIInstalled, isCertManagerInstalled: isCertManagerInstalled, } } type TLSPoliciesValidator struct { + isGatewayAPIInstalled bool isCertManagerInstalled bool } @@ -42,53 +44,46 @@ func (t *TLSPoliciesValidator) Subscription() *controller.Subscription { } } -func (t *TLSPoliciesValidator) Validate(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, s *sync.Map) error { +func (t *TLSPoliciesValidator) Validate(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, state *sync.Map) error { logger := controller.LoggerFromContext(ctx).WithName("TLSPoliciesValidator").WithName("Validate") policies := lo.Filter(topology.Policies().Items(), filterForTLSPolicies) + logger.V(1).Info("validating tls policies", "policies", len(policies)) - isPolicyValidErrorMap := make(map[string]error, len(policies)) - - for _, policy := range policies { - p := policy.(*kuadrantv1.TLSPolicy) - if p.DeletionTimestamp != nil { - logger.V(1).Info("tls policy is marked for deletion, skipping", "name", p.Name, "namespace", p.Namespace) - continue + state.Store(TLSPolicyAcceptedKey, lo.SliceToMap(policies, func(p machinery.Policy) (string, error) { + if !t.isGatewayAPIInstalled { + return p.GetLocator(), kuadrant.MissingGatewayAPIError() } if !t.isCertManagerInstalled { - isPolicyValidErrorMap[p.GetLocator()] = kuadrant.NewErrDependencyNotInstalled("Cert Manager") - continue + return p.GetLocator(), kuadrant.MissingCertManagerError() } + policy := p.(*kuadrantv1.TLSPolicy) // Validate target ref - if err := t.isTargetRefsFound(topology, p); err != nil { - isPolicyValidErrorMap[p.GetLocator()] = err - continue + if err := t.isTargetRefsFound(topology, policy); err != nil { + return p.GetLocator(), err } // Validate if there's a conflicting policy - if err := t.isConflict(policies, p); err != nil { - isPolicyValidErrorMap[p.GetLocator()] = err - continue + if err := t.isConflict(policies, policy); err != nil { + return p.GetLocator(), err } // Validate IssuerRef kind is correct - if err := t.isValidIssuerKind(p); err != nil { - isPolicyValidErrorMap[p.GetLocator()] = err - continue + if err := t.isValidIssuerKind(policy); err != nil { + return p.GetLocator(), err } // Validate Issuer is present on cluster through the topology - if err := t.isIssuerFound(topology, p); err != nil { - isPolicyValidErrorMap[p.GetLocator()] = err - continue + if err := t.isIssuerFound(topology, policy); err != nil { + return p.GetLocator(), err } - isPolicyValidErrorMap[p.GetLocator()] = nil - } + return p.GetLocator(), nil + })) - s.Store(TLSPolicyAcceptedKey, isPolicyValidErrorMap) + logger.V(1).Info("finished validating tls policies") return nil } diff --git a/pkg/authorino/conditions.go b/pkg/authorino/conditions.go index 49010b476..fd8a2ca5c 100644 --- a/pkg/authorino/conditions.go +++ b/pkg/authorino/conditions.go @@ -1,15 +1,36 @@ package authorino import ( - authorinov1beta1 "github.com/kuadrant/authorino-operator/api/v1beta1" + "github.com/go-logr/logr" + authorinooperatorv1beta1 "github.com/kuadrant/authorino-operator/api/v1beta1" + authorinov1beta3 "github.com/kuadrant/authorino/api/v1beta3" + "k8s.io/apimachinery/pkg/api/meta" + + "github.com/kuadrant/kuadrant-operator/pkg/utils" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" ) -func FindAuthorinoStatusCondition(conditions []authorinov1beta1.Condition, conditionType string) *authorinov1beta1.Condition { +func FindAuthorinoStatusCondition(conditions []authorinooperatorv1beta1.Condition, conditionType string) *authorinooperatorv1beta1.Condition { for i := range conditions { - if conditions[i].Type == authorinov1beta1.ConditionType(conditionType) { + if conditions[i].Type == authorinooperatorv1beta1.ConditionType(conditionType) { return &conditions[i] } } return nil } + +func IsAuthorinoOperatorInstalled(restMapper meta.RESTMapper, logger logr.Logger) (bool, error) { + if ok, err := utils.IsCRDInstalled(restMapper, kuadrantv1beta1.AuthorinoGroupKind.Group, kuadrantv1beta1.AuthorinoGroupKind.Kind, authorinooperatorv1beta1.GroupVersion.Version); !ok || err != nil { + logger.V(1).Error(err, "Authorino Operator CRD was not installed", "group", kuadrantv1beta1.AuthorinoGroupKind.Group, "kind", kuadrantv1beta1.AuthorinoGroupKind.Kind, "version", authorinooperatorv1beta1.GroupVersion.Version) + return false, err + } + + if ok, err := utils.IsCRDInstalled(restMapper, AuthConfigGroupKind.Group, AuthConfigGroupKind.Kind, authorinov1beta3.GroupVersion.Version); !ok || err != nil { + logger.V(1).Error(err, "Authorino Operator CRD was not installed", "group", AuthConfigGroupKind.Group, "kind", AuthConfigGroupKind.Kind, "version", authorinov1beta3.GroupVersion.Version) + return false, err + } + + return true, nil +} diff --git a/pkg/kuadrant/errors.go b/pkg/kuadrant/errors.go index 3c3229101..7c28a4249 100644 --- a/pkg/kuadrant/errors.go +++ b/pkg/kuadrant/errors.go @@ -258,3 +258,29 @@ func (e ErrSystemResource) Error() string { func (e ErrSystemResource) Reason() gatewayapiv1alpha2.PolicyConditionReason { return PolicyReasonMissingResource } + +// Common ErrDependencyNotInstalled errors + +func MissingGatewayAPIError() PolicyError { + return NewErrDependencyNotInstalled("Gateway API") +} + +func MissingGatewayProviderError() PolicyError { + return NewErrDependencyNotInstalled("Gateway API provider (istio / envoy gateway)") +} + +func MissingAuthorinoOperatorError() PolicyError { + return NewErrDependencyNotInstalled("Authorino Operator") +} + +func MissingLimitadorOperatorError() PolicyError { + return NewErrDependencyNotInstalled("Limitador Operator") +} + +func MissingDNSOperatorError() PolicyError { + return NewErrDependencyNotInstalled("DNS Operator") +} + +func MissingCertManagerError() PolicyError { + return NewErrDependencyNotInstalled("Cert Manager") +} diff --git a/tests/bare_k8s/kuadrant_controller_test.go b/tests/bare_k8s/kuadrant_controller_test.go index b9cf6461c..7d42b9c53 100644 --- a/tests/bare_k8s/kuadrant_controller_test.go +++ b/tests/bare_k8s/kuadrant_controller_test.go @@ -5,18 +5,23 @@ package bare_k8s_test import ( "time" + authorinoapi "github.com/kuadrant/authorino/api/v1beta3" + kuadrantdnsv1alpha1 "github.com/kuadrant/dns-operator/api/v1alpha1" . "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" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" "github.com/kuadrant/kuadrant-operator/controllers" "github.com/kuadrant/kuadrant-operator/tests" ) -var _ = Describe("Controller", func() { +var _ = Describe("Kuadrant controller when Gateway API is missing", func() { var ( testNamespace string testTimeOut = SpecTimeout(15 * time.Second) @@ -32,7 +37,7 @@ var _ = Describe("Controller", func() { }, afterEachTimeOut) Context("when default kuadrant CR is created", func() { - It("Status is populated with missing GatewayProvide", func(ctx SpecContext) { + It("Status is populated with missing Gateway API", func(ctx SpecContext) { kuadrantCR := &kuadrantv1beta1.Kuadrant{ TypeMeta: metav1.TypeMeta{ Kind: "Kuadrant", @@ -51,8 +56,140 @@ var _ = Describe("Controller", func() { cond := meta.FindStatusCondition(kuadrantCR.Status.Conditions, 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")) + g.Expect(cond.Reason).To(Equal("MissingDependency")) + g.Expect(cond.Message).To(Equal("Gateway API is not installed, please restart pod once dependency has been installed")) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + }) + + Context("when dns policy is created", func() { + It("Status is populated with missing Gateway API", func(ctx SpecContext) { + policy := kuadrantv1.NewDNSPolicy("dns", testNamespace).WithTargetGateway("test") + policy.Spec.ProviderRefs = append(policy.Spec.ProviderRefs, kuadrantdnsv1alpha1.ProviderRef{ + Name: "dnsProviderSecret", + }) + Expect(testClient().Create(ctx, policy)).To(Succeed()) + + Eventually(func(g Gomega) { + err := testClient().Get(ctx, client.ObjectKeyFromObject(policy), policy) + g.Expect(err).ToNot(HaveOccurred()) + + cond := meta.FindStatusCondition(policy.Status.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + g.Expect(cond.Reason).To(Equal("MissingDependency")) + g.Expect(cond.Message).To(Equal("Gateway API is not installed, please restart pod once dependency has been installed")) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + }) + + Context("when tls policy is created", func() { + It("Status is populated with missing Gateway API", func(ctx SpecContext) { + policy := kuadrantv1.NewTLSPolicy("tls", testNamespace). + WithTargetGateway("test") + + Expect(testClient().Create(ctx, policy)).To(Succeed()) + + Eventually(func(g Gomega) { + err := testClient().Get(ctx, client.ObjectKeyFromObject(policy), policy) + g.Expect(err).ToNot(HaveOccurred()) + + cond := meta.FindStatusCondition(policy.Status.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + g.Expect(cond.Reason).To(Equal("MissingDependency")) + g.Expect(cond.Message).To(Equal("Gateway API is not installed, please restart pod once dependency has been installed")) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + }) + + Context("when rate limit policy is created", func() { + It("Status is populated with missing Gateway API", func(ctx SpecContext) { + policy := &kuadrantv1.RateLimitPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rlp", + Namespace: testNamespace, + }, + Spec: kuadrantv1.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Kind: "Gateway", + Group: gatewayapiv1.GroupName, + Name: "test", + }, + }, + RateLimitPolicySpecProper: kuadrantv1.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1.Limit{ + "test": { + Rates: []kuadrantv1.Rate{ + { + Limit: 10, + Window: "10s", + }, + }, + }, + }, + }, + }, + } + + Expect(testClient().Create(ctx, policy)).To(Succeed()) + + Eventually(func(g Gomega) { + err := testClient().Get(ctx, client.ObjectKeyFromObject(policy), policy) + g.Expect(err).ToNot(HaveOccurred()) + + cond := meta.FindStatusCondition(policy.Status.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + g.Expect(cond.Reason).To(Equal("MissingDependency")) + g.Expect(cond.Message).To(Equal("Gateway API is not installed, please restart pod once dependency has been installed")) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + }) + + Context("when auth policy is created", func() { + It("Status is populated with missing Gateway API", func(ctx SpecContext) { + policy := &kuadrantv1.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "auth", + Namespace: testNamespace, + }, + Spec: kuadrantv1.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Kind: "Gateway", + Group: gatewayapiv1.GroupName, + Name: "test", + }, + }, + AuthPolicySpecProper: kuadrantv1.AuthPolicySpecProper{ + AuthScheme: &kuadrantv1.AuthSchemeSpec{ + Authentication: map[string]kuadrantv1.MergeableAuthenticationSpec{ + "anyonmous": { + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + }, + }, + }, + }, + }, + }, + }, + } + + Expect(testClient().Create(ctx, policy)).To(Succeed()) + + Eventually(func(g Gomega) { + err := testClient().Get(ctx, client.ObjectKeyFromObject(policy), policy) + g.Expect(err).ToNot(HaveOccurred()) + + cond := meta.FindStatusCondition(policy.Status.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + g.Expect(cond.Reason).To(Equal("MissingDependency")) + g.Expect(cond.Message).To(Equal("Gateway API is not installed, please restart pod once dependency has been installed")) }).WithContext(ctx).Should(Succeed()) }, testTimeOut) }) diff --git a/tests/gatewayapi/kuadrant_controller_test.go b/tests/gatewayapi/kuadrant_controller_test.go index fdf101b13..38e8ce94d 100644 --- a/tests/gatewayapi/kuadrant_controller_test.go +++ b/tests/gatewayapi/kuadrant_controller_test.go @@ -5,12 +5,16 @@ package gatewayapi_test import ( "time" + authorinoapi "github.com/kuadrant/authorino/api/v1beta3" . "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" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" "github.com/kuadrant/kuadrant-operator/controllers" "github.com/kuadrant/kuadrant-operator/tests" @@ -19,8 +23,8 @@ import ( var _ = Describe("Kuadrant controller when gateway provider is missing", func() { var ( testNamespace string - kuadrantName string = "local" - afterEachTimeOut = NodeTimeout(3 * time.Minute) + testTimeOut = SpecTimeout(15 * time.Second) + afterEachTimeOut = NodeTimeout(3 * time.Minute) ) BeforeEach(func(ctx SpecContext) { @@ -32,29 +36,118 @@ var _ = Describe("Kuadrant controller when gateway provider is missing", func() }, afterEachTimeOut) Context("when default kuadrant CR is created", func() { - It("Status reports error", func(ctx SpecContext) { + It("Status reports missing Gateway API provider (istio / envoy gateway)", func(ctx SpecContext) { kuadrantCR := &kuadrantv1beta1.Kuadrant{ TypeMeta: metav1.TypeMeta{ Kind: "Kuadrant", APIVersion: kuadrantv1beta1.GroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ - Name: kuadrantName, + Name: "local", Namespace: testNamespace, }, } Expect(testClient().Create(ctx, kuadrantCR)).ToNot(HaveOccurred()) Eventually(func(g Gomega) { - kObj := &kuadrantv1beta1.Kuadrant{} - err := testClient().Get(ctx, client.ObjectKeyFromObject(kuadrantCR), kObj) + g.Expect(testClient().Get(ctx, client.ObjectKeyFromObject(kuadrantCR), kuadrantCR)).ToNot(HaveOccurred()) + cond := meta.FindStatusCondition(kuadrantCR.Status.Conditions, controllers.ReadyConditionType) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + g.Expect(cond.Reason).To(Equal("MissingDependency")) + g.Expect(cond.Message).To(Equal("Gateway API provider (istio / envoy gateway) is not installed, please restart pod once dependency has been installed")) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + }) + + Context("when rate limit policy is created", func() { + It("Status is populated with missing Gateway API provider (istio / envoy gateway)", func(ctx SpecContext) { + policy := &kuadrantv1.RateLimitPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rlp", + Namespace: testNamespace, + }, + Spec: kuadrantv1.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Kind: "Gateway", + Group: gatewayapiv1.GroupName, + Name: "test", + }, + }, + RateLimitPolicySpecProper: kuadrantv1.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1.Limit{ + "test": { + Rates: []kuadrantv1.Rate{ + { + Limit: 10, + Window: "10s", + }, + }, + }, + }, + }, + }, + } + + Expect(testClient().Create(ctx, policy)).To(Succeed()) + + Eventually(func(g Gomega) { + err := testClient().Get(ctx, client.ObjectKeyFromObject(policy), policy) g.Expect(err).ToNot(HaveOccurred()) - cond := meta.FindStatusCondition(kObj.Status.Conditions, string(controllers.ReadyConditionType)) + + cond := meta.FindStatusCondition(policy.Status.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + g.Expect(cond.Reason).To(Equal("MissingDependency")) + g.Expect(cond.Message).To(Equal("Gateway API provider (istio / envoy gateway) is not installed, please restart pod once dependency has been installed")) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + }) + + Context("when auth policy is created", func() { + It("Status is populated with missing Gateway API provider (istio / envoy gateway)", func(ctx SpecContext) { + policy := &kuadrantv1.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "auth", + Namespace: testNamespace, + }, + Spec: kuadrantv1.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Kind: "Gateway", + Group: gatewayapiv1.GroupName, + Name: "test", + }, + }, + AuthPolicySpecProper: kuadrantv1.AuthPolicySpecProper{ + AuthScheme: &kuadrantv1.AuthSchemeSpec{ + Authentication: map[string]kuadrantv1.MergeableAuthenticationSpec{ + "anyonmous": { + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + }, + }, + }, + }, + }, + }, + }, + } + + Expect(testClient().Create(ctx, policy)).To(Succeed()) + + Eventually(func(g Gomega) { + err := testClient().Get(ctx, client.ObjectKeyFromObject(policy), policy) + g.Expect(err).ToNot(HaveOccurred()) + + cond := meta.FindStatusCondition(policy.Status.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) 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()) - }) + g.Expect(cond.Reason).To(Equal("MissingDependency")) + g.Expect(cond.Message).To(Equal("Gateway API provider (istio / envoy gateway) is not installed, please restart pod once dependency has been installed")) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) }) })