From 152f0046146ad87c4e207e9374e16e88d1d5c70a Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Thu, 7 Aug 2025 17:10:14 +0200 Subject: [PATCH 1/4] Decouple CRD and APIResourceSchema in service export request reconciler Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com Exchange CRDInformers with APIResourceSchemaInformers Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com rebase controller-runtime migration Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com modify crd indexers Signed-off-by: Karol Szwaj --- .../serviceexport/serviceexport_controller.go | 9 ++--- .../serviceexport/serviceexport_reconcile.go | 2 -- .../serviceexportrequest_controller.go | 2 -- .../serviceexportrequest_reconcile.go | 34 +++---------------- pkg/indexers/serviceexport.go | 6 ++-- pkg/indexers/serviceexportrequest.go | 32 +++-------------- 6 files changed, 14 insertions(+), 71 deletions(-) diff --git a/contrib/example-backend/controllers/serviceexport/serviceexport_controller.go b/contrib/example-backend/controllers/serviceexport/serviceexport_controller.go index 15e744ecc..b217c2cc0 100644 --- a/contrib/example-backend/controllers/serviceexport/serviceexport_controller.go +++ b/contrib/example-backend/controllers/serviceexport/serviceexport_controller.go @@ -20,7 +20,6 @@ import ( "context" "fmt" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -79,13 +78,10 @@ func NewAPIServiceExportReconciler( getCRD: func(ctx context.Context, cache cache.Cache, name string) (*apiextensionsv1.CustomResourceDefinition, error) { var crd apiextensionsv1.CustomResourceDefinition key := types.NamespacedName{Name: name} - if err := cache.Get(ctx, key, &crd); err != nil { + if err := cache.Get(ctx, key, &schema); err != nil { return nil, err } - return &crd, nil - }, - getAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - return bindClient.KubeBindV1alpha2().APIResourceSchemas().Get(ctx, name, metav1.GetOptions{}) + return &schema, nil }, deleteServiceExport: func(ctx context.Context, ns, name string) error { return bindClient.KubeBindV1alpha2().APIServiceExports(ns).Delete(ctx, name, metav1.DeleteOptions{}) @@ -99,7 +95,6 @@ func NewAPIServiceExportReconciler( //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports/status,verbs=get;update;patch //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports/finalizers,verbs=update -//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. diff --git a/contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go b/contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go index b9962d64a..01c2fc8bd 100644 --- a/contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go +++ b/contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go @@ -23,7 +23,6 @@ import ( "slices" "sort" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/klog/v2" @@ -35,7 +34,6 @@ import ( ) type reconciler struct { - getCRD func(ctx context.Context, cache cache.Cache, name string) (*apiextensionsv1.CustomResourceDefinition, error) getAPIResourceSchema func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) deleteServiceExport func(ctx context.Context, namespace, name string) error } diff --git a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go index 7570e928b..711b63c07 100644 --- a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go +++ b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go @@ -21,7 +21,6 @@ import ( "fmt" "reflect" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -202,7 +201,6 @@ func getCRDMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHand //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexportrequests/finalizers,verbs=update //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiresourceschemas,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. diff --git a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go index 353e7a1cd..3f6eeb023 100644 --- a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go +++ b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go @@ -61,42 +61,21 @@ func (r *reconciler) ensureExports(ctx context.Context, cache cache.Cache, req * logger := klog.FromContext(ctx) if req.Status.Phase == kubebindv1alpha2.APIServiceExportRequestPhasePending { - failure := false for _, res := range req.Spec.Resources { - // backend is created using CRD's as backup. But this is not required. name := res.Resource + "." + res.Group - apiResourceSchema, err := r.getAPIResourceSchema(ctx, cache, name) - switch { - case apierrors.IsNotFound(err): - logger.V(1).Info("APIResourceSchema not found, continuing with fallback to CRD conversion to APIResourceSchema", "name", name) - crd, err := r.getCRD(ctx, cache, name) - if err != nil && !apierrors.IsNotFound(err) { - return err - } + if err != nil { if apierrors.IsNotFound(err) { conditions.MarkFalse( req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, - "CRDNotFound", + "APIResourceSchemaNotFound", conditionsapi.ConditionSeverityError, - "CustomResourceDefinition %s in the service provider cluster not found", + "APIResourceSchema %s in the service provider cluster not found", name, ) - failure = true - break - } - schema, err := helpers.CRDToAPIResourceSchema(crd, "") - if err != nil { return err } - schema.Namespace = req.Namespace - - logger.V(1).Info("Creating APIResourceSchema", "name", schema.Name, "namespace", schema.Namespace) - if apiResourceSchema, err = r.createAPIResourceSchema(ctx, schema); err != nil { - return err - } - case err != nil: return err } @@ -135,11 +114,8 @@ func (r *reconciler) ensureExports(ctx context.Context, cache cache.Cache, req * } } - if !failure { - conditions.MarkTrue(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) - req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseSucceeded - return nil - } + conditions.MarkTrue(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) + req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseSucceeded if time.Since(req.CreationTimestamp.Time) > time.Minute { req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseFailed diff --git a/pkg/indexers/serviceexport.go b/pkg/indexers/serviceexport.go index 69b743409..f449af6a1 100644 --- a/pkg/indexers/serviceexport.go +++ b/pkg/indexers/serviceexport.go @@ -24,6 +24,7 @@ import ( const ( ServiceExportByCustomResourceDefinition = "serviceExportByCustomResourceDefinition" + ServiceExportByAPIResourceSchema = "ServiceExportByAPIResourceSchema" ) func IndexServiceExportByCustomResourceDefinition(obj any) ([]string, error) { @@ -35,9 +36,8 @@ func IndexServiceExportByCustomResourceDefinition(obj any) ([]string, error) { return []string{export.Name}, nil } -// IndexServiceExportByCustomResourceDefinitionControllerRuntime is a controller-runtime compatible indexer function -// that indexes APIServiceExports by their CustomResourceDefinition name. -func IndexServiceExportByCustomResourceDefinitionControllerRuntime(obj client.Object) []string { +// IndexServiceExportByAPIResourceSchema is a controller-runtime compatible indexer function. +func IndexServiceExportByAPIResourceSchema(obj client.Object) []string { export, ok := obj.(*v1alpha2.APIServiceExport) if !ok { return nil diff --git a/pkg/indexers/serviceexportrequest.go b/pkg/indexers/serviceexportrequest.go index f03382cf2..fd7a04c5e 100644 --- a/pkg/indexers/serviceexportrequest.go +++ b/pkg/indexers/serviceexportrequest.go @@ -27,21 +27,9 @@ const ( ServiceExportRequestByServiceExport = "ServiceExportRequestByServiceExport" ) -func IndexServiceExportRequestByGroupResource(obj any) ([]string, error) { - apiServiceExportRequest, ok := obj.(*kubebindv1alpha2.APIServiceExportRequest) - if !ok { - return nil, nil - } - keys := []string{} - for _, gr := range apiServiceExportRequest.Spec.Resources { - keys = append(keys, gr.Resource+"."+gr.Group) - } - return keys, nil -} - -// IndexServiceExportRequestByGroupResourceControllerRuntime is a controller-runtime compatible indexer function +// IndexServiceExportRequestByGroupResource is a controller-runtime compatible indexer function // that indexes APIServiceExportRequests by their Group.Resource name. -func IndexServiceExportRequestByGroupResourceControllerRuntime(obj client.Object) []string { +func IndexServiceExportRequestByGroupResource(obj client.Object) []string { apiServiceExportRequest, ok := obj.(*kubebindv1alpha2.APIServiceExportRequest) if !ok { return nil @@ -53,21 +41,9 @@ func IndexServiceExportRequestByGroupResourceControllerRuntime(obj client.Object return keys } -func IndexServiceExportRequestByServiceExport(obj any) ([]string, error) { - apiServiceExportRequest, ok := obj.(*kubebindv1alpha2.APIServiceExportRequest) - if !ok { - return nil, nil - } - keys := []string{} - for _, gr := range apiServiceExportRequest.Spec.Resources { - keys = append(keys, apiServiceExportRequest.Namespace+"/"+gr.Resource+"."+gr.Group) - } - return keys, nil -} - -// IndexServiceExportRequestByServiceExportControllerRuntime is a controller-runtime compatible indexer function +// IndexServiceExportRequestByServiceExport is a controller-runtime compatible indexer function // that indexes APIServiceExportRequests by their related ServiceExport name. -func IndexServiceExportRequestByServiceExportControllerRuntime(obj client.Object) []string { +func IndexServiceExportRequestByServiceExport(obj client.Object) []string { apiServiceExportRequest, ok := obj.(*kubebindv1alpha2.APIServiceExportRequest) if !ok { return nil From b42febb236249331a8d37b176bf0ee7f43a31ae3 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Tue, 12 Aug 2025 16:33:28 +0200 Subject: [PATCH 2/4] add apiresourceschemas to e2e test Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com --- .../provider/apiresourceschema-foo.yaml | 43 ++++++++++++++ .../provider/apiresourceschema-mangodb.yaml | 57 +++++++++++++++++++ test/e2e/bind/happy-case_test.go | 2 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 test/e2e/bind/fixtures/provider/apiresourceschema-foo.yaml create mode 100644 test/e2e/bind/fixtures/provider/apiresourceschema-mangodb.yaml diff --git a/test/e2e/bind/fixtures/provider/apiresourceschema-foo.yaml b/test/e2e/bind/fixtures/provider/apiresourceschema-foo.yaml new file mode 100644 index 000000000..14f9cb50a --- /dev/null +++ b/test/e2e/bind/fixtures/provider/apiresourceschema-foo.yaml @@ -0,0 +1,43 @@ +apiVersion: kube-bind.io/v1alpha2 +kind: APIResourceSchema +metadata: + name: foos.bar.io +spec: + group: bar.io + informerScope: Namespaced + names: + kind: Foo + plural: foos + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + deploymentName: + type: string + replicas: + maximum: 10 + minimum: 1 + type: integer + type: object + status: + properties: + availableReplicas: + type: integer + phase: + enum: + - Pending + - Running + - Succeeded + - Failed + - Unknown + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/test/e2e/bind/fixtures/provider/apiresourceschema-mangodb.yaml b/test/e2e/bind/fixtures/provider/apiresourceschema-mangodb.yaml new file mode 100644 index 000000000..c16eb33a9 --- /dev/null +++ b/test/e2e/bind/fixtures/provider/apiresourceschema-mangodb.yaml @@ -0,0 +1,57 @@ +apiVersion: kube-bind.io/v1alpha2 +kind: APIResourceSchema +metadata: + name: mangodbs.mangodb.com +spec: + group: mangodb.com + informerScope: Namespaced + names: + kind: MangoDB + listKind: MangoDBList + plural: mangodbs + singular: mangodb + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + backup: + default: false + type: boolean + region: + default: us-east-1 + minLength: 1 + type: string + tier: + default: Shared + enum: + - Dedicated + - Shared + type: string + tokenSecret: + minLength: 1 + type: string + required: + - tokenSecret + type: object + status: + properties: + phase: + enum: + - Pending + - Running + - Succeeded + - Failed + - Unknown + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/test/e2e/bind/happy-case_test.go b/test/e2e/bind/happy-case_test.go index d71344f3d..f460f5179 100644 --- a/test/e2e/bind/happy-case_test.go +++ b/test/e2e/bind/happy-case_test.go @@ -62,7 +62,7 @@ func testHappyCase(t *testing.T, resourceScope apiextensionsv1.ResourceScope, in t.Logf("Creating provider workspace") providerConfig, providerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-happy-case-provider")) - t.Logf("Creating CRDs on provider side") + t.Logf("Creating APIResourceSchemas on provider side") providerfixtures.Bootstrap(t, framework.DiscoveryClient(t, providerConfig), framework.DynamicClient(t, providerConfig), nil) t.Logf("Starting backend with random port") From 297ec621af90f3bff086704e3cf75234f345bf19 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Tue, 12 Aug 2025 18:47:29 +0200 Subject: [PATCH 3/4] add kubebind scheme Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com --- test/e2e/bind/fixtures/provider/crd-foo.yaml | 47 --------------- .../bind/fixtures/provider/crd-mangodb.yaml | 58 ------------------- test/e2e/framework/kcp.go | 3 + 3 files changed, 3 insertions(+), 105 deletions(-) delete mode 100644 test/e2e/bind/fixtures/provider/crd-foo.yaml delete mode 100644 test/e2e/bind/fixtures/provider/crd-mangodb.yaml diff --git a/test/e2e/bind/fixtures/provider/crd-foo.yaml b/test/e2e/bind/fixtures/provider/crd-foo.yaml deleted file mode 100644 index 32dbcb06e..000000000 --- a/test/e2e/bind/fixtures/provider/crd-foo.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: foos.bar.io - labels: - kube-bind.io/exported: "true" -spec: - group: bar.io - versions: - - name: v1alpha1 - served: true - storage: true - schema: - # schema used for validation - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - deploymentName: - type: string - replicas: - type: integer - minimum: 1 - maximum: 10 - status: - type: object - properties: - availableReplicas: - type: integer - phase: - type: string - enum: - - Pending - - Running - - Succeeded - - Failed - - Unknown - # subresources for the custom resource - subresources: - # enables the status subresource - status: {} - names: - kind: Foo - plural: foos - scope: Cluster diff --git a/test/e2e/bind/fixtures/provider/crd-mangodb.yaml b/test/e2e/bind/fixtures/provider/crd-mangodb.yaml deleted file mode 100644 index 0ec795060..000000000 --- a/test/e2e/bind/fixtures/provider/crd-mangodb.yaml +++ /dev/null @@ -1,58 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: mangodbs.mangodb.com - labels: - kube-bind.io/exported: "true" -spec: - group: mangodb.com - names: - kind: MangoDB - listKind: MangoDBList - plural: mangodbs - singular: mangodb - scope: Namespaced - versions: - - name: v1alpha1 - served: true - storage: true - subresources: - status: {} - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - tier: - type: string - enum: - - Dedicated - - Shared - default: Shared - region: - type: string - default: us-east-1 - minLength: 1 - backup: - type: boolean - default: false - tokenSecret: - type: string - minLength: 1 - required: - - tokenSecret - status: - type: object - properties: - phase: - type: string - enum: - - Pending - - Running - - Succeeded - - Failed - - Unknown - required: - - spec diff --git a/test/e2e/framework/kcp.go b/test/e2e/framework/kcp.go index 8e603bdd1..84be8e8ae 100644 --- a/test/e2e/framework/kcp.go +++ b/test/e2e/framework/kcp.go @@ -39,6 +39,8 @@ import ( "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + + kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" ) type ( @@ -51,6 +53,7 @@ var ( func init() { utilruntime.Must(tenancyv1alpha1.AddToScheme(kcpScheme)) + utilruntime.Must(kubebindv1alpha2.AddToScheme(kcpScheme)) } func WithName(s string, formatArgs ...any) ClusterWorkspaceOption { From aef963c3e5ea14007ea2619cfed22bb4abe4415d Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Mon, 18 Aug 2025 21:26:34 +0200 Subject: [PATCH 4/4] rebase multicontroller runtime Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com add cache Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com --- .../clusterbinding_controller.go | 6 +- .../serviceexport/serviceexport_controller.go | 27 ++++----- .../serviceexport/serviceexport_reconcile.go | 8 +-- .../serviceexportrequest_controller.go | 32 ++++------ .../serviceexportrequest_reconcile.go | 1 - deploy/examples/crd-mangodb.yaml | 1 - deploy/examples/servicenamespace-example.yaml | 2 +- test/e2e/bind/fixtures/provider/crd-foo.yaml | 47 +++++++++++++++ .../bind/fixtures/provider/crd-mangodb.yaml | 58 +++++++++++++++++++ test/e2e/bind/happy-case_test.go | 6 +- test/e2e/framework/konnector.go | 2 - 11 files changed, 141 insertions(+), 49 deletions(-) create mode 100644 test/e2e/bind/fixtures/provider/crd-foo.yaml create mode 100644 test/e2e/bind/fixtures/provider/crd-mangodb.yaml diff --git a/contrib/example-backend/controllers/clusterbinding/clusterbinding_controller.go b/contrib/example-backend/controllers/clusterbinding/clusterbinding_controller.go index 5d9cde08e..834dcf5b1 100644 --- a/contrib/example-backend/controllers/clusterbinding/clusterbinding_controller.go +++ b/contrib/example-backend/controllers/clusterbinding/clusterbinding_controller.go @@ -242,14 +242,14 @@ func (r *ClusterBindingReconciler) SetupWithManager(mgr mcmanager.Manager) error Owns(&rbacv1.RoleBinding{}). Watches( &kubebindv1alpha2.APIServiceExport{}, - mapCRD, + mapAPIResourceSchema, ). Named(controllerName). Complete(r) } -func mapCRD(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { - return handler.TypedEnqueueRequestsFromMapFunc[client.Object, mcreconcile.Request](func(ctx context.Context, obj client.Object) []mcreconcile.Request { +func mapAPIResourceSchema(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { + return handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []mcreconcile.Request { serviceExport, ok := obj.(*kubebindv1alpha2.APIServiceExport) if !ok { return nil diff --git a/contrib/example-backend/controllers/serviceexport/serviceexport_controller.go b/contrib/example-backend/controllers/serviceexport/serviceexport_controller.go index b217c2cc0..701d1bd07 100644 --- a/contrib/example-backend/controllers/serviceexport/serviceexport_controller.go +++ b/contrib/example-backend/controllers/serviceexport/serviceexport_controller.go @@ -66,17 +66,18 @@ func NewAPIServiceExportReconciler( if err != nil { return nil, err } - if err := mgr.GetFieldIndexer().IndexField(ctx, &kubebindv1alpha2.APIServiceExport{}, indexers.ServiceExportByCustomResourceDefinition, - indexers.IndexServiceExportByCustomResourceDefinitionControllerRuntime); err != nil { - return nil, fmt.Errorf("failed to setup ServiceExportByCustomResourceDefinition indexer: %w", err) + + if err := mgr.GetFieldIndexer().IndexField(ctx, &kubebindv1alpha2.APIServiceExport{}, indexers.ServiceExportByAPIResourceSchema, + indexers.IndexServiceExportByAPIResourceSchema); err != nil { + return nil, fmt.Errorf("failed to setup ServiceExportByAPIResourceSchema indexer: %w", err) } r := &APIServiceExportReconciler{ manager: mgr, bindClient: bindClient, reconciler: reconciler{ - getCRD: func(ctx context.Context, cache cache.Cache, name string) (*apiextensionsv1.CustomResourceDefinition, error) { - var crd apiextensionsv1.CustomResourceDefinition + getAPIResourceSchema: func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) { + var schema kubebindv1alpha2.APIResourceSchema key := types.NamespacedName{Name: name} if err := cache.Get(ctx, key, &schema); err != nil { return nil, err @@ -143,15 +144,15 @@ func (r *APIServiceExportReconciler) Reconcile(ctx context.Context, req mcreconc return ctrl.Result{}, nil } -// getCRDMapper returns a mapper function that uses the manager to find related APIServiceExports. -func getCRDMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { - return handler.TypedEnqueueRequestsFromMapFunc[client.Object, mcreconcile.Request](func(ctx context.Context, obj client.Object) []mcreconcile.Request { - crd := obj.(*apiextensionsv1.CustomResourceDefinition) - crdKey := crd.Name +// getAPIResourceSchemaMapper returns a mapper function that uses the manager to find related APIServiceExports. +func getAPIResourceSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { + return handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []mcreconcile.Request { + apiResourceSchema := obj.(*kubebindv1alpha2.APIResourceSchema) + apiResourceSchemaKey := apiResourceSchema.Name c := cl.GetClient() var exports kubebindv1alpha2.APIServiceExportList - if err := c.List(ctx, &exports, client.MatchingFields{indexers.ServiceExportByCustomResourceDefinition: crdKey}); err != nil { + if err := c.List(ctx, &exports, client.MatchingFields{indexers.ServiceExportByAPIResourceSchema: apiResourceSchemaKey}); err != nil { return []mcreconcile.Request{} } @@ -174,8 +175,8 @@ func (r *APIServiceExportReconciler) SetupWithManager(mgr mcmanager.Manager) err return mcbuilder.ControllerManagedBy(mgr). For(&kubebindv1alpha2.APIServiceExport{}). Watches( - &apiextensionsv1.CustomResourceDefinition{}, - getCRDMapper, + &kubebindv1alpha2.APIResourceSchema{}, + getAPIResourceSchemaMapper, ). Named(controllerName). Complete(r) diff --git a/contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go b/contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go index 01c2fc8bd..52f10df7f 100644 --- a/contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go +++ b/contrib/example-backend/controllers/serviceexport/serviceexport_reconcile.go @@ -34,14 +34,14 @@ import ( ) type reconciler struct { - getAPIResourceSchema func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) + getAPIResourceSchema func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) deleteServiceExport func(ctx context.Context, namespace, name string) error } func (r *reconciler) reconcile(ctx context.Context, cache cache.Cache, export *kubebindv1alpha2.APIServiceExport) error { var errs []error - if specChanged, err := r.ensureSchema(ctx, export); err != nil { + if specChanged, err := r.ensureSchema(ctx, cache, export); err != nil { errs = append(errs, err) } else if specChanged { // TODO: This should be separate controller for apiresourceschemas. @@ -53,7 +53,7 @@ func (r *reconciler) reconcile(ctx context.Context, cache cache.Cache, export *k return utilerrors.NewAggregate(errs) } -func (r *reconciler) ensureSchema(ctx context.Context, export *kubebindv1alpha2.APIServiceExport) (specChanged bool, err error) { +func (r *reconciler) ensureSchema(ctx context.Context, cache cache.Cache, export *kubebindv1alpha2.APIServiceExport) (specChanged bool, err error) { logger := klog.FromContext(ctx) leafHashes := make([]string, 0, len(export.Spec.Resources)) for _, resourceRef := range export.Spec.Resources { @@ -62,7 +62,7 @@ func (r *reconciler) ensureSchema(ctx context.Context, export *kubebindv1alpha2. continue } - schema, err := r.getAPIResourceSchema(ctx, resourceRef.Name) + schema, err := r.getAPIResourceSchema(ctx, cache, resourceRef.Name) if err != nil { if errors.IsNotFound(err) { continue diff --git a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go index 711b63c07..392d8d54c 100644 --- a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go +++ b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go @@ -79,12 +79,12 @@ func NewAPIServiceExportRequestReconciler( // Set up field indexers for APIServiceExportRequests if err := mgr.GetFieldIndexer().IndexField(ctx, &kubebindv1alpha2.APIServiceExportRequest{}, indexers.ServiceExportRequestByServiceExport, - indexers.IndexServiceExportRequestByServiceExportControllerRuntime); err != nil { + indexers.IndexServiceExportRequestByServiceExport); err != nil { return nil, fmt.Errorf("failed to setup ServiceExportRequestByServiceExport indexer: %w", err) } if err := mgr.GetFieldIndexer().IndexField(ctx, &kubebindv1alpha2.APIServiceExportRequest{}, indexers.ServiceExportRequestByGroupResource, - indexers.IndexServiceExportRequestByGroupResourceControllerRuntime); err != nil { + indexers.IndexServiceExportRequestByGroupResource); err != nil { return nil, fmt.Errorf("failed to setup ServiceExportRequestByGroupResource indexer: %w", err) } @@ -97,14 +97,6 @@ func NewAPIServiceExportRequestReconciler( reconciler: reconciler{ informerScope: scope, clusterScopedIsolation: isolation, - getCRD: func(ctx context.Context, cache cache.Cache, name string) (*apiextensionsv1.CustomResourceDefinition, error) { - var crd apiextensionsv1.CustomResourceDefinition - key := types.NamespacedName{Name: name} - if err := cache.Get(ctx, key, &crd); err != nil { - return nil, err - } - return &crd, nil - }, getAPIResourceSchema: func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) { var schema kubebindv1alpha2.APIResourceSchema key := types.NamespacedName{Name: name} @@ -137,9 +129,8 @@ func NewAPIServiceExportRequestReconciler( } // getServiceExportRequestMapper creates a mapping function for ServiceExport changes. - func getServiceExportRequestMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { - return handler.TypedEnqueueRequestsFromMapFunc[client.Object, mcreconcile.Request](func(ctx context.Context, obj client.Object) []mcreconcile.Request { + return handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []mcreconcile.Request { serviceExport := obj.(*kubebindv1alpha2.APIServiceExport) seKey := serviceExport.Namespace + "/" + serviceExport.Name @@ -167,16 +158,15 @@ func getServiceExportRequestMapper(clusterName string, cl cluster.Cluster) handl }) } -// getCRDMapper creates a mapping function for CRD changes. -func getCRDMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { - return handler.TypedEnqueueRequestsFromMapFunc[client.Object, mcreconcile.Request](func(ctx context.Context, obj client.Object) []mcreconcile.Request { - crd := obj.(*apiextensionsv1.CustomResourceDefinition) - crdKey := crd.Name // CRDs are cluster-scoped - +// getAPIResourceSchemaMapper creates a mapping function for APIResourceSchema changes. +func getAPIResourceSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { + return handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []mcreconcile.Request { + apiResourceSchema := obj.(*kubebindv1alpha2.APIResourceSchema) + apiResourceSchemaKey := apiResourceSchema.Name c := cl.GetClient() var requests kubebindv1alpha2.APIServiceExportRequestList - if err := c.List(ctx, &requests, client.MatchingFields{indexers.ServiceExportRequestByGroupResource: crdKey}); err != nil { + if err := c.List(ctx, &requests, client.MatchingFields{indexers.ServiceExportRequestByGroupResource: apiResourceSchemaKey}); err != nil { return []mcreconcile.Request{} } @@ -259,8 +249,8 @@ func (r *APIServiceExportRequestReconciler) SetupWithManager(mgr mcmanager.Manag getServiceExportRequestMapper, ). Watches( - &apiextensionsv1.CustomResourceDefinition{}, - getCRDMapper, + &kubebindv1alpha2.APIResourceSchema{}, + getAPIResourceSchemaMapper, ). Named(controllerName). Complete(r) diff --git a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go index 3f6eeb023..d858c4953 100644 --- a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go +++ b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go @@ -37,7 +37,6 @@ type reconciler struct { informerScope kubebindv1alpha2.InformerScope clusterScopedIsolation kubebindv1alpha2.Isolation - getCRD func(ctx context.Context, cache cache.Cache, name string) (*apiextensionsv1.CustomResourceDefinition, error) getAPIResourceSchema func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) getServiceExport func(ctx context.Context, cache cache.Cache, ns, name string) (*kubebindv1alpha2.APIServiceExport, error) createServiceExport func(ctx context.Context, resource *kubebindv1alpha2.APIServiceExport) (*kubebindv1alpha2.APIServiceExport, error) diff --git a/deploy/examples/crd-mangodb.yaml b/deploy/examples/crd-mangodb.yaml index e349cf73a..d6a62b688 100644 --- a/deploy/examples/crd-mangodb.yaml +++ b/deploy/examples/crd-mangodb.yaml @@ -38,4 +38,3 @@ spec: - spec subresources: status: {} - diff --git a/deploy/examples/servicenamespace-example.yaml b/deploy/examples/servicenamespace-example.yaml index a031fc752..fc4b82089 100644 --- a/deploy/examples/servicenamespace-example.yaml +++ b/deploy/examples/servicenamespace-example.yaml @@ -2,4 +2,4 @@ apiVersion: kube-bind.io/v1alpha1 kind: APIServiceNamespace metadata: name: example -spec: {} \ No newline at end of file +spec: {} diff --git a/test/e2e/bind/fixtures/provider/crd-foo.yaml b/test/e2e/bind/fixtures/provider/crd-foo.yaml new file mode 100644 index 000000000..32dbcb06e --- /dev/null +++ b/test/e2e/bind/fixtures/provider/crd-foo.yaml @@ -0,0 +1,47 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: foos.bar.io + labels: + kube-bind.io/exported: "true" +spec: + group: bar.io + versions: + - name: v1alpha1 + served: true + storage: true + schema: + # schema used for validation + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + deploymentName: + type: string + replicas: + type: integer + minimum: 1 + maximum: 10 + status: + type: object + properties: + availableReplicas: + type: integer + phase: + type: string + enum: + - Pending + - Running + - Succeeded + - Failed + - Unknown + # subresources for the custom resource + subresources: + # enables the status subresource + status: {} + names: + kind: Foo + plural: foos + scope: Cluster diff --git a/test/e2e/bind/fixtures/provider/crd-mangodb.yaml b/test/e2e/bind/fixtures/provider/crd-mangodb.yaml new file mode 100644 index 000000000..0ec795060 --- /dev/null +++ b/test/e2e/bind/fixtures/provider/crd-mangodb.yaml @@ -0,0 +1,58 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: mangodbs.mangodb.com + labels: + kube-bind.io/exported: "true" +spec: + group: mangodb.com + names: + kind: MangoDB + listKind: MangoDBList + plural: mangodbs + singular: mangodb + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + tier: + type: string + enum: + - Dedicated + - Shared + default: Shared + region: + type: string + default: us-east-1 + minLength: 1 + backup: + type: boolean + default: false + tokenSecret: + type: string + minLength: 1 + required: + - tokenSecret + status: + type: object + properties: + phase: + type: string + enum: + - Pending + - Running + - Succeeded + - Failed + - Unknown + required: + - spec diff --git a/test/e2e/bind/happy-case_test.go b/test/e2e/bind/happy-case_test.go index f460f5179..7f2da31d1 100644 --- a/test/e2e/bind/happy-case_test.go +++ b/test/e2e/bind/happy-case_test.go @@ -62,12 +62,12 @@ func testHappyCase(t *testing.T, resourceScope apiextensionsv1.ResourceScope, in t.Logf("Creating provider workspace") providerConfig, providerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-happy-case-provider")) - t.Logf("Creating APIResourceSchemas on provider side") - providerfixtures.Bootstrap(t, framework.DiscoveryClient(t, providerConfig), framework.DynamicClient(t, providerConfig), nil) - t.Logf("Starting backend with random port") addr, _ := framework.StartBackend(t, providerConfig, "--kubeconfig="+providerKubeconfig, "--listen-port=0", "--consumer-scope="+string(informerScope)) + t.Logf("Creating APIResourceSchemas on provider side") + providerfixtures.Bootstrap(t, framework.DiscoveryClient(t, providerConfig), framework.DynamicClient(t, providerConfig), nil) + t.Logf("Creating consumer workspace and starting konnector") consumerConfig, consumerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-happy-case-consumer")) framework.StartKonnector(t, consumerConfig, "--kubeconfig="+consumerKubeconfig) diff --git a/test/e2e/framework/konnector.go b/test/e2e/framework/konnector.go index ba2ad1475..d55db7dd9 100644 --- a/test/e2e/framework/konnector.go +++ b/test/e2e/framework/konnector.go @@ -29,7 +29,6 @@ import ( "github.com/kube-bind/kube-bind/deploy/crd" "github.com/kube-bind/kube-bind/pkg/konnector" "github.com/kube-bind/kube-bind/pkg/konnector/options" - kubebindv1alpha1 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha1" kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" ) @@ -42,7 +41,6 @@ func StartKonnector(t *testing.T, clientConfig *rest.Config, args ...string) *ko err = crd.Create(ctx, crdClient.ApiextensionsV1().CustomResourceDefinitions(), metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "apiservicebindings"}, - metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiservicebindings"}, ) require.NoError(t, err)