@@ -23,10 +23,8 @@ package apibindingtemplate
2323import (
2424 "context"
2525 "fmt"
26- "net/url"
2726 "strings"
2827
29- apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1"
3028 apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2"
3129 "k8s.io/apimachinery/pkg/api/errors"
3230 "k8s.io/apimachinery/pkg/runtime"
@@ -43,6 +41,7 @@ import (
4341 mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"
4442 mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile"
4543
44+ "github.com/kube-bind/kube-bind/backend/provider/kcp/controllers/shared"
4645 kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
4746)
4847
@@ -54,11 +53,8 @@ type APIBindingTemplateReconciler struct {
5453 manager mcmanager.Manager
5554 opts controller.TypedOptions [mcreconcile.Request ]
5655 ignorePrefixes []string
57-
58- // baseConfig is the kcp admin/root REST config used to construct
59- // VW clients for the apiresourceschema virtual workspace.
60- baseConfig * rest.Config
61- scheme * runtime.Scheme
56+ scheme * runtime.Scheme
57+ vwCache * shared.VWClientCache
6258}
6359
6460// New returns a new APIBindingTemplateReconciler.
@@ -74,8 +70,8 @@ func New(
7470 manager : mgr ,
7571 opts : opts ,
7672 ignorePrefixes : ignorePrefixes ,
77- baseConfig : baseConfig ,
7873 scheme : scheme ,
74+ vwCache : shared .NewVWClientCache (baseConfig , scheme ),
7975 }
8076
8177 return r , nil
@@ -92,39 +88,6 @@ func (r *APIBindingTemplateReconciler) shouldIgnore(name string) bool {
9288 return false
9389}
9490
95- // extractClusterID extracts the cluster ID from an apiexport virtual workspace
96- // URL. The URL format is:
97- // https://host:port/services/apiexport/root:org:ws/<apiexport-name>/clusters/{cluster-id}
98- func extractClusterID (clusterConfig * rest.Config ) (string , error ) {
99- u , err := url .Parse (clusterConfig .Host )
100- if err != nil {
101- return "" , fmt .Errorf ("failed to parse cluster host URL: %w" , err )
102- }
103-
104- pathParts := strings .Split (strings .Trim (u .Path , "/" ), "/" )
105- if len (pathParts ) < 6 || pathParts [4 ] != "clusters" {
106- return "" , fmt .Errorf ("unexpected apiexport URL format: %s" , u .Path )
107- }
108-
109- return pathParts [5 ], nil
110- }
111-
112- // newVWClient creates a client pointing at the apiresourceschema virtual workspace
113- // for the given cluster ID:
114- // https://host:port/services/apiresourceschema/{clusterID}/clusters/*/
115- func (r * APIBindingTemplateReconciler ) newVWClient (clusterID string ) (client.Client , error ) {
116- cfg := rest .CopyConfig (r .baseConfig )
117- u , err := url .Parse (cfg .Host )
118- if err != nil {
119- return nil , fmt .Errorf ("failed to parse base config host: %w" , err )
120- }
121-
122- u .Path = fmt .Sprintf ("/services/apiresourceschema/%s/clusters/*" , clusterID )
123- cfg .Host = u .String ()
124-
125- return client .New (cfg , client.Options {Scheme : r .scheme })
126- }
127-
12891// Reconcile implements reconcile.Reconciler for multicluster-runtime.
12992func (r * APIBindingTemplateReconciler ) Reconcile (ctx context.Context , req mcreconcile.Request ) (ctrl.Result , error ) {
13093 logger := log .FromContext (ctx )
@@ -152,8 +115,8 @@ func (r *APIBindingTemplateReconciler) Reconcile(ctx context.Context, req mcreco
152115 return ctrl.Result {}, fmt .Errorf ("failed to get APIBinding %q: %w" , req .Name , err )
153116 }
154117
155- // Build the schema getter with VW fallback.
156- getSchema := r . schemaGetterWithFallback (c , clusterConfig )
118+ // Build the schema getter with VW fallback using the shared cache .
119+ getSchema := shared . SchemaGetterWithFallback (c , clusterConfig , r . vwCache )
157120
158121 rec := reconciler {
159122 client : c ,
@@ -169,57 +132,19 @@ func (r *APIBindingTemplateReconciler) Reconcile(ctx context.Context, req mcreco
169132 return ctrl.Result {}, nil
170133}
171134
172- // schemaGetterWithFallback returns a function that first tries to get the
173- // APIResourceSchema from the current workspace, and if not found, falls back
174- // to the apiresourceschema virtual workspace.
175- func (r * APIBindingTemplateReconciler ) schemaGetterWithFallback (
176- workspaceClient client.Client ,
177- clusterConfig * rest.Config ,
178- ) func (ctx context.Context , name string ) (* apisv1alpha1.APIResourceSchema , error ) {
179- return func (ctx context.Context , name string ) (* apisv1alpha1.APIResourceSchema , error ) {
180- logger := log .FromContext (ctx )
181-
182- // 1. Try the current workspace first.
183- var schema apisv1alpha1.APIResourceSchema
184- err := workspaceClient .Get (ctx , client.ObjectKey {Name : name }, & schema )
185- if err == nil {
186- return & schema , nil
187- }
188- if ! errors .IsNotFound (err ) {
189- return nil , err
190- }
191-
192- // 2. Fallback: try the apiresourceschema virtual workspace.
193- logger .V (2 ).Info ("APIResourceSchema not found in workspace, trying VW fallback" , "schema" , name )
194-
195- clusterID , err := extractClusterID (clusterConfig )
196- if err != nil {
197- return nil , fmt .Errorf ("cannot build VW fallback client: %w" , err )
198- }
199-
200- vwClient , err := r .newVWClient (clusterID )
201- if err != nil {
202- return nil , fmt .Errorf ("failed to create VW client for cluster %q: %w" , clusterID , err )
203- }
204-
205- var vwSchema apisv1alpha1.APIResourceSchema
206- if err := vwClient .Get (ctx , client.ObjectKey {Name : name }, & vwSchema ); err != nil {
207- return nil , fmt .Errorf ("APIResourceSchema %q not found in workspace or VW: %w" , name , err )
208- }
209-
210- return & vwSchema , nil
211- }
212- }
213-
214135// getTemplateMapper returns a mapper that enqueues the owning APIBinding when
215136// an APIServiceExportTemplate changes.
137+ //
138+ // This function has the signature func(clusterName string, cl cluster.Cluster) handler.TypedEventHandler
139+ // because multicluster-runtime's mcbuilder.Watches accepts a "per-cluster event handler factory"
140+ // rather than a plain handler — it calls this factory for each cluster that is engaged.
216141func getTemplateMapper (clusterName string , cl cluster.Cluster ) handler.TypedEventHandler [client.Object , mcreconcile.Request ] {
217142 return handler .TypedEnqueueRequestsFromMapFunc (func (ctx context.Context , obj client.Object ) []mcreconcile.Request {
218143 annotations := obj .GetAnnotations ()
219144 if annotations == nil {
220145 return nil
221146 }
222- ownerName , ok := annotations [annotationOwnerBinding ]
147+ ownerName , ok := annotations [shared . AnnotationOwnerBinding ]
223148 if ! ok {
224149 return nil
225150 }
0 commit comments