From 4731ea64685befe828bbdcdc648d6f5e11660271 Mon Sep 17 00:00:00 2001 From: Mattia Lavacca Date: Wed, 11 Sep 2024 15:47:14 +0200 Subject: [PATCH] feat: `KongPluginBinding` reconciler watchers (#571) * feat: targets watchers for KongPluginBinding Signed-off-by: Mattia Lavacca * chore: CHANGELOG updated Signed-off-by: Mattia Lavacca * add inNamespace list filter Signed-off-by: Mattia Lavacca --------- Signed-off-by: Mattia Lavacca --- CHANGELOG.md | 9 +- controller/konnect/index_kongpluginbinding.go | 80 ++++++++ controller/konnect/watch_kongpluginbinding.go | 172 +++++++++++++++++- 3 files changed, 255 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d254ff9bf..95d5ac42f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,10 +57,13 @@ - The `DataPlaneKonnectExtension` CRD has been introduced. Such a CRD can be attached to a `DataPlane` via the extensions field to have a konnect-flavored `DataPlane`. [#453](https://github.com/Kong/gateway-operator/pull/453) -- Entities created in Konnect are now labeled (or tagged for those that does not support labels) - with origin Kubernetes object's metadata: `k8s-name`, `k8s-namespace`, `k8s-uid`, `k8s-generation`, - `k8s-kind`, `k8s-group`, `k8s-version`. +- Entities created in Konnect are now labeled (or tagged for those that does not + support labels) with origin Kubernetes object's metadata: `k8s-name`, `k8s-namespace`, + `k8s-uid`, `k8s-generation`, `k8s-kind`, `k8s-group`, `k8s-version`. [#565](https://github.com/Kong/gateway-operator/pull/565) +- Add `KongService`, `KongRoute`, `KongConsumer`, and `KongConsumerGroup` watchers + in the `KongPluginBinding` reconciler. + [#571](https://github.com/Kong/gateway-operator/pull/571) ### Fixed diff --git a/controller/konnect/index_kongpluginbinding.go b/controller/konnect/index_kongpluginbinding.go index d36717592..f89bc3b4f 100644 --- a/controller/konnect/index_kongpluginbinding.go +++ b/controller/konnect/index_kongpluginbinding.go @@ -11,6 +11,14 @@ const ( IndexFieldKongPluginBindingKongPluginReference = "kongPluginRef" // IndexFieldKongPluginBindingKongClusterPluginReference is the index field for KongClusterPlugin -> KongPluginBinding. IndexFieldKongPluginBindingKongClusterPluginReference = "kongClusterPluginRef" + // IndexFieldKongPluginBindingKongServiceReference is the index field for KongService -> KongPluginBinding. + IndexFieldKongPluginBindingKongServiceReference = "kongServiceRef" + // IndexFieldKongPluginBindingKongServiceReference is the index field for KongRoute -> KongPluginBinding. + IndexFieldKongPluginBindingKongRouteReference = "kongRouteRef" + // IndexFieldKongPluginBindingKongServiceReference is the index field for KongConsumer -> KongPluginBinding. + IndexFieldKongPluginBindingKongConsumerReference = "kongConsumerRef" + // IndexFieldKongPluginBindingKongServiceReference is the index field for KongConsumerGroup -> KongPluginBinding. + IndexFieldKongPluginBindingKongConsumerGroupReference = "kongConsumerGroupRef" ) // IndexOptionsForKongPluginBinding returns required Index options for KongPluginBinding reconclier. @@ -26,6 +34,26 @@ func IndexOptionsForKongPluginBinding() []ReconciliationIndexOption { IndexField: IndexFieldKongPluginBindingKongClusterPluginReference, ExtractValue: kongClusterPluginReferencesFromKongPluginBinding, }, + { + IndexObject: &configurationv1alpha1.KongPluginBinding{}, + IndexField: IndexFieldKongPluginBindingKongServiceReference, + ExtractValue: kongServiceReferencesFromKongPluginBinding, + }, + { + IndexObject: &configurationv1alpha1.KongPluginBinding{}, + IndexField: IndexFieldKongPluginBindingKongRouteReference, + ExtractValue: kongRouteReferencesFromKongPluginBinding, + }, + { + IndexObject: &configurationv1alpha1.KongPluginBinding{}, + IndexField: IndexFieldKongPluginBindingKongConsumerReference, + ExtractValue: kongConsumerReferencesFromKongPluginBinding, + }, + { + IndexObject: &configurationv1alpha1.KongPluginBinding{}, + IndexField: IndexFieldKongPluginBindingKongConsumerGroupReference, + ExtractValue: kongConsumerGroupReferencesFromKongPluginBinding, + }, } } @@ -52,3 +80,55 @@ func kongClusterPluginReferencesFromKongPluginBinding(obj client.Object) []strin } return []string{binding.Spec.PluginReference.Name} } + +// kongServiceReferencesFromKongPluginBinding returns name of referenced KongService in KongPluginBinding spec. +func kongServiceReferencesFromKongPluginBinding(obj client.Object) []string { + binding, ok := obj.(*configurationv1alpha1.KongPluginBinding) + if !ok { + return nil + } + if binding.Spec.Targets.ServiceReference == nil || + binding.Spec.Targets.ServiceReference.Group != configurationv1alpha1.GroupVersion.Group || + binding.Spec.Targets.ServiceReference.Kind != "KongService" { + return nil + } + return []string{binding.Spec.Targets.ServiceReference.Name} +} + +// kongRouteReferencesFromKongPluginBinding returns name of referenced KongRoute in KongPluginBinding spec. +func kongRouteReferencesFromKongPluginBinding(obj client.Object) []string { + binding, ok := obj.(*configurationv1alpha1.KongPluginBinding) + if !ok { + return nil + } + if binding.Spec.Targets.RouteReference == nil || + binding.Spec.Targets.RouteReference.Group != configurationv1alpha1.GroupVersion.Group || + binding.Spec.Targets.RouteReference.Kind != "KongRoute" { + return nil + } + return []string{binding.Spec.Targets.RouteReference.Name} +} + +// kongConsumerReferencesFromKongPluginBinding returns name of referenced KongConsumer in KongPluginBinding spec. +func kongConsumerReferencesFromKongPluginBinding(obj client.Object) []string { + binding, ok := obj.(*configurationv1alpha1.KongPluginBinding) + if !ok { + return nil + } + if binding.Spec.Targets.ConsumerReference == nil { + return nil + } + return []string{binding.Spec.Targets.ConsumerReference.Name} +} + +// kongConsumerGroupReferencesFromKongPluginBinding returns name of referenced KongConsumerGroup in KongPluginBinding spec. +func kongConsumerGroupReferencesFromKongPluginBinding(obj client.Object) []string { + binding, ok := obj.(*configurationv1alpha1.KongPluginBinding) + if !ok { + return nil + } + if binding.Spec.Targets.ConsumerGroupReference == nil { + return nil + } + return []string{binding.Spec.Targets.ConsumerGroupReference.Name} +} diff --git a/controller/konnect/watch_kongpluginbinding.go b/controller/konnect/watch_kongpluginbinding.go index 7d3d12a0d..e23a22f6e 100644 --- a/controller/konnect/watch_kongpluginbinding.go +++ b/controller/konnect/watch_kongpluginbinding.go @@ -20,6 +20,7 @@ import ( configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" + configurationv1beta1 "github.com/kong/kubernetes-configuration/api/configuration/v1beta1" konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" ) @@ -80,9 +81,38 @@ func KongPluginBindingReconciliationWatchOptions( ), ) }, - // TODO(mlavacca): add KongService watch - // TODO(mlavacca): add KongConsumer watch - // TODO(mlavacca): add KongRoute watch + func(b *ctrl.Builder) *ctrl.Builder { + return b.Watches( + &configurationv1alpha1.KongService{}, + handler.EnqueueRequestsFromMapFunc( + enqueueKongPluginBindingForKongService(cl), + ), + ) + }, + func(b *ctrl.Builder) *ctrl.Builder { + return b.Watches( + &configurationv1alpha1.KongRoute{}, + handler.EnqueueRequestsFromMapFunc( + enqueueKongPluginBindingForKongRoute(cl), + ), + ) + }, + func(b *ctrl.Builder) *ctrl.Builder { + return b.Watches( + &configurationv1.KongConsumer{}, + handler.EnqueueRequestsFromMapFunc( + enqueueKongPluginBindingForKongConsumer(cl), + ), + ) + }, + func(b *ctrl.Builder) *ctrl.Builder { + return b.Watches( + &configurationv1beta1.KongConsumerGroup{}, + handler.EnqueueRequestsFromMapFunc( + enqueueKongPluginBindingForKongConsumerGroup(cl), + ), + ) + }, } } @@ -306,3 +336,139 @@ func enqueueKongPluginBindingForKongClusterPlugin(cl client.Client) func( }) } } + +func enqueueKongPluginBindingForKongService(cl client.Client) func( + ctx context.Context, obj client.Object) []reconcile.Request { + return func(ctx context.Context, obj client.Object) []reconcile.Request { + kongService, ok := obj.(*configurationv1alpha1.KongService) + if !ok { + return nil + } + + pluginBindingList := configurationv1alpha1.KongPluginBindingList{} + err := cl.List(ctx, &pluginBindingList, + client.InNamespace(kongService.Namespace), + client.MatchingFields{ + IndexFieldKongPluginBindingKongServiceReference: kongService.Name, + }, + ) + if err != nil { + ctrllog.FromContext(ctx).Error(err, "failed to list KongPluginBindings referencing KongServices") + } + + return lo.FilterMap(pluginBindingList.Items, func(pb configurationv1alpha1.KongPluginBinding, _ int) (reconcile.Request, bool) { + // Only put KongPluginBindings referencing to a Konnect control plane, + if pb.Spec.ControlPlaneRef == nil || pb.Spec.ControlPlaneRef.Type != configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef { + return reconcile.Request{}, false + } + return reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: pb.Namespace, + Name: pb.Name, + }, + }, true + }) + } +} + +func enqueueKongPluginBindingForKongRoute(cl client.Client) func( + ctx context.Context, obj client.Object) []reconcile.Request { + return func(ctx context.Context, obj client.Object) []reconcile.Request { + kongRoute, ok := obj.(*configurationv1alpha1.KongRoute) + if !ok { + return nil + } + + pluginBindingList := configurationv1alpha1.KongPluginBindingList{} + err := cl.List(ctx, &pluginBindingList, + client.InNamespace(kongRoute.Namespace), + client.MatchingFields{ + IndexFieldKongPluginBindingKongRouteReference: kongRoute.Name, + }, + ) + if err != nil { + ctrllog.FromContext(ctx).Error(err, "failed to list KongPluginBindings referencing KongRoutes") + } + + return lo.FilterMap(pluginBindingList.Items, func(pb configurationv1alpha1.KongPluginBinding, _ int) (reconcile.Request, bool) { + // Only put KongPluginBindings referencing to a Konnect control plane, + if pb.Spec.ControlPlaneRef == nil || pb.Spec.ControlPlaneRef.Type != configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef { + return reconcile.Request{}, false + } + return reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: pb.Namespace, + Name: pb.Name, + }, + }, true + }) + } +} + +func enqueueKongPluginBindingForKongConsumer(cl client.Client) func( + ctx context.Context, obj client.Object) []reconcile.Request { + return func(ctx context.Context, obj client.Object) []reconcile.Request { + kongConsumer, ok := obj.(*configurationv1.KongConsumer) + if !ok { + return nil + } + + pluginBindingList := configurationv1alpha1.KongPluginBindingList{} + err := cl.List(ctx, &pluginBindingList, + client.InNamespace(kongConsumer.Namespace), + client.MatchingFields{ + IndexFieldKongPluginBindingKongConsumerReference: kongConsumer.Name, + }, + ) + if err != nil { + ctrllog.FromContext(ctx).Error(err, "failed to list KongPluginBindings referencing KongConsumers") + } + + return lo.FilterMap(pluginBindingList.Items, func(pb configurationv1alpha1.KongPluginBinding, _ int) (reconcile.Request, bool) { + // Only put KongPluginBindings referencing to a Konnect control plane, + if pb.Spec.ControlPlaneRef == nil || pb.Spec.ControlPlaneRef.Type != configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef { + return reconcile.Request{}, false + } + return reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: pb.Namespace, + Name: pb.Name, + }, + }, true + }) + } +} + +func enqueueKongPluginBindingForKongConsumerGroup(cl client.Client) func( + ctx context.Context, obj client.Object) []reconcile.Request { + return func(ctx context.Context, obj client.Object) []reconcile.Request { + kongConsumerGroup, ok := obj.(*configurationv1beta1.KongConsumerGroup) + if !ok { + return nil + } + + pluginBindingList := configurationv1alpha1.KongPluginBindingList{} + err := cl.List(ctx, &pluginBindingList, + client.InNamespace(kongConsumerGroup.Namespace), + client.MatchingFields{ + IndexFieldKongPluginBindingKongConsumerGroupReference: kongConsumerGroup.Name, + }, + ) + if err != nil { + ctrllog.FromContext(ctx).Error(err, "failed to list KongPluginBindings referencing KongConsumerGroups") + } + + return lo.FilterMap(pluginBindingList.Items, func(pb configurationv1alpha1.KongPluginBinding, _ int) (reconcile.Request, bool) { + // Only put KongPluginBindings referencing to a Konnect control plane, + if pb.Spec.ControlPlaneRef == nil || pb.Spec.ControlPlaneRef.Type != configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef { + return reconcile.Request{}, false + } + return reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: pb.Namespace, + Name: pb.Name, + }, + }, true + }) + } +}