@@ -19,43 +19,47 @@ package serviceexport
1919import (
2020 "context"
2121 "fmt"
22- "time "
22+ "reflect "
2323
2424 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
25- apiextensionsinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
26- apiextensionslisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
2725 "k8s.io/apimachinery/pkg/api/errors"
2826 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29- utilerrors "k8s.io/apimachinery/pkg/util/errors"
30- "k8s.io/apimachinery/pkg/util/runtime"
31- "k8s.io/apimachinery/pkg/util/wait"
27+ "k8s.io/apimachinery/pkg/runtime"
28+ "k8s.io/apimachinery/pkg/types"
3229 "k8s.io/client-go/rest"
33- "k8s.io/client-go/tools/cache"
34- "k8s.io/client-go/util/workqueue"
35- "k8s.io/klog/v2"
30+ ctrl "sigs.k8s.io/controller-runtime"
31+ "sigs.k8s.io/controller-runtime/pkg/cache"
32+ "sigs.k8s.io/controller-runtime/pkg/client"
33+ "sigs.k8s.io/controller-runtime/pkg/handler"
34+ "sigs.k8s.io/controller-runtime/pkg/log"
35+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
3636
37- "github.com/kube-bind/kube-bind/pkg/committer"
3837 "github.com/kube-bind/kube-bind/pkg/indexers"
3938 kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
4039 bindclient "github.com/kube-bind/kube-bind/sdk/client/clientset/versioned"
41- bindinformers "github.com/kube-bind/kube-bind/sdk/client/informers/externalversions/kubebind/v1alpha2"
42- bindlisters "github.com/kube-bind/kube-bind/sdk/client/listers/kubebind/v1alpha2"
4340)
4441
4542const (
4643 controllerName = "kube-bind-example-backend-serviceexport"
4744)
4845
49- // NewController returns a new controller to reconcile ServiceExports.
50- func NewController (
51- config * rest.Config ,
52- serviceExportInformer bindinformers.APIServiceExportInformer ,
53- crdInformer apiextensionsinformers.CustomResourceDefinitionInformer ,
54- ) (* Controller , error ) {
55- queue := workqueue .NewTypedRateLimitingQueueWithConfig (workqueue .DefaultTypedControllerRateLimiter [string ](), workqueue.TypedRateLimitingQueueConfig [string ]{Name : controllerName })
46+ // APIServiceExportReconciler reconciles a APIServiceExport object.
47+ type APIServiceExportReconciler struct {
48+ client.Client
49+ Scheme * runtime.Scheme
5650
57- logger := klog .Background ().WithValues ("controller" , controllerName )
51+ bindClient bindclient.Interface
52+ reconciler reconciler
53+ }
5854
55+ // NewAPIServiceExportReconciler returns a new APIServiceExportReconciler to reconcile APIServiceExports.
56+ func NewAPIServiceExportReconciler (
57+ ctx context.Context ,
58+ client client.Client ,
59+ scheme * runtime.Scheme ,
60+ config * rest.Config ,
61+ cache cache.Cache ,
62+ ) (* APIServiceExportReconciler , error ) {
5963 config = rest .CopyConfig (config )
6064 config = rest .AddUserAgent (config , controllerName )
6165
@@ -64,219 +68,119 @@ func NewController(
6468 return nil , err
6569 }
6670
67- c := & Controller {
68- queue : queue ,
71+ // Set up field indexer for APIServiceExports by CustomResourceDefinition name
72+ // This replicates the behavior of the original indexer setup
73+ if err := cache .IndexField (ctx , & kubebindv1alpha2.APIServiceExport {}, indexers .ServiceExportByCustomResourceDefinition ,
74+ indexers .IndexServiceExportByCustomResourceDefinitionControllerRuntime ); err != nil {
75+ return nil , fmt .Errorf ("failed to setup ServiceExportByCustomResourceDefinition indexer: %w" , err )
76+ }
6977
78+ r := & APIServiceExportReconciler {
79+ Client : client ,
80+ Scheme : scheme ,
7081 bindClient : bindClient ,
71-
72- serviceExportLister : serviceExportInformer .Lister (),
73- serviceExportIndexer : serviceExportInformer .Informer ().GetIndexer (),
74-
75- crdLister : crdInformer .Lister (),
76- crdIndexer : crdInformer .Informer ().GetIndexer (),
77-
7882 reconciler : reconciler {
79- getCRD : func (name string ) (* apiextensionsv1.CustomResourceDefinition , error ) {
80- return crdInformer .Lister ().Get (name )
83+ getCRD : func (ctx context.Context , name string ) (* apiextensionsv1.CustomResourceDefinition , error ) {
84+ var crd apiextensionsv1.CustomResourceDefinition
85+ key := types.NamespacedName {Name : name }
86+ if err := cache .Get (ctx , key , & crd ); err != nil {
87+ return nil , err
88+ }
89+ return & crd , nil
8190 },
8291 getAPIResourceSchema : func (ctx context.Context , name string ) (* kubebindv1alpha2.APIResourceSchema , error ) {
8392 return bindClient .KubeBindV1alpha2 ().APIResourceSchemas ().Get (ctx , name , metav1.GetOptions {})
8493 },
8594 deleteServiceExport : func (ctx context.Context , ns , name string ) error {
8695 return bindClient .KubeBindV1alpha2 ().APIServiceExports (ns ).Delete (ctx , name , metav1.DeleteOptions {})
8796 },
88- requeue : func (export * kubebindv1alpha2.APIServiceExport ) {
89- key , err := cache .MetaNamespaceKeyFunc (export )
90- if err != nil {
91- runtime .HandleError (err )
92- return
93- }
94- queue .Add (key )
95- },
96- },
97-
98- commit : committer.NewCommitter [* kubebindv1alpha2.APIServiceExport , * kubebindv1alpha2.APIServiceExportSpec , * kubebindv1alpha2.APIServiceExportStatus ](
99- func (ns string ) committer.Patcher [* kubebindv1alpha2.APIServiceExport ] {
100- return bindClient .KubeBindV1alpha2 ().APIServiceExports (ns )
101- },
102- ),
103- }
104-
105- indexers .AddIfNotPresentOrDie (serviceExportInformer .Informer ().GetIndexer (), cache.Indexers {
106- indexers .ServiceExportByCustomResourceDefinition : indexers .IndexServiceExportByCustomResourceDefinition ,
107- })
108-
109- if _ , err := serviceExportInformer .Informer ().AddEventHandler (cache.ResourceEventHandlerFuncs {
110- AddFunc : func (obj any ) {
111- c .enqueueServiceExport (logger , obj )
11297 },
113- UpdateFunc : func (old , newObj any ) {
114- c .enqueueServiceExport (logger , newObj )
115- },
116- DeleteFunc : func (obj any ) {
117- c .enqueueServiceExport (logger , obj )
118- },
119- }); err != nil {
120- return nil , err
121- }
122-
123- if _ , err := crdInformer .Informer ().AddEventHandler (cache.ResourceEventHandlerFuncs {
124- AddFunc : func (obj any ) {
125- c .enqueueCRD (logger , obj )
126- },
127- UpdateFunc : func (old , newObj any ) {
128- c .enqueueCRD (logger , newObj )
129- },
130- DeleteFunc : func (obj any ) {
131- c .enqueueCRD (logger , obj )
132- },
133- }); err != nil {
134- return nil , err
13598 }
13699
137- return c , nil
100+ return r , nil
138101}
139102
140- type Resource = committer.Resource [* kubebindv1alpha2.APIServiceExportSpec , * kubebindv1alpha2.APIServiceExportStatus ]
141- type CommitFunc = func (context.Context , * Resource , * Resource ) error
103+ // createCRDMapper creates a mapping function that can access the client for looking up related APIServiceExports.
104+ func (r * APIServiceExportReconciler ) createCRDMapper () handler.MapFunc {
105+ return func (ctx context.Context , obj client.Object ) []reconcile.Request {
106+ crd := obj .(* apiextensionsv1.CustomResourceDefinition )
142107
143- // Controller reconciles ServiceNamespaces by creating a Namespace for each, and deleting it if
144- // the APIServiceNamespace is deleted.
145- type Controller struct {
146- queue workqueue.TypedRateLimitingInterface [string ]
108+ // Use the field indexer to find APIServiceExports related to this CRD
109+ // The indexer key should match the CRD name (namespace/name format for namespaced objects)
110+ crdKey := crd .Name // CRDs are cluster-scoped, so just the name
147111
148- bindClient bindclient.Interface
149-
150- serviceExportLister bindlisters.APIServiceExportLister
151- serviceExportIndexer cache.Indexer
152-
153- crdLister apiextensionslisters.CustomResourceDefinitionLister
154- crdIndexer cache.Indexer
155-
156- reconciler
112+ var exports kubebindv1alpha2.APIServiceExportList
113+ if err := r .List (ctx , & exports , client.MatchingFields {indexers .ServiceExportByCustomResourceDefinition : crdKey }); err != nil {
114+ return []reconcile.Request {}
115+ }
157116
158- commit CommitFunc
159- }
117+ var requests []reconcile.Request
118+ for _ , export := range exports .Items {
119+ requests = append (requests , reconcile.Request {
120+ NamespacedName : types.NamespacedName {
121+ Namespace : export .Namespace ,
122+ Name : export .Name ,
123+ },
124+ })
125+ }
160126
161- func (c * Controller ) enqueueServiceExport (logger klog.Logger , obj any ) {
162- key , err := cache .DeletionHandlingMetaNamespaceKeyFunc (obj )
163- if err != nil {
164- runtime .HandleError (err )
165- return
127+ return requests
166128 }
167-
168- logger .V (2 ).Info ("queueing APIServiceExport" , "key" , key )
169- c .queue .Add (key )
170129}
171130
172- func (c * Controller ) enqueueCRD (logger klog.Logger , obj any ) {
173- crdKey , err := cache .DeletionHandlingMetaNamespaceKeyFunc (obj )
174- if err != nil {
175- runtime .HandleError (err )
176- return
177- }
178-
179- exports , err := c .serviceExportIndexer .ByIndex (indexers .ServiceExportByCustomResourceDefinition , crdKey )
180- if err != nil {
181- runtime .HandleError (err )
182- return
183- }
184-
185- for _ , obj := range exports {
186- export , ok := obj .(* kubebindv1alpha2.APIServiceExport )
187- if ! ok {
188- runtime .HandleError (fmt .Errorf ("unexpected type %T" , obj ))
189- return
190- }
191- key , err := cache .MetaNamespaceKeyFunc (export )
192- if err != nil {
193- runtime .HandleError (err )
194- continue
131+ //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete
132+ //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports/status,verbs=get;update;patch
133+ //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports/finalizers,verbs=update
134+ //+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch
135+
136+ // Reconcile is part of the main kubernetes reconciliation loop which aims to
137+ // move the current state of the cluster closer to the desired state.
138+ func (r * APIServiceExportReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
139+ logger := log .FromContext (ctx )
140+ logger .Info ("Reconciling APIServiceExport" , "namespace" , req .Namespace , "name" , req .Name )
141+
142+ // Fetch the APIServiceExport instance
143+ apiServiceExport := & kubebindv1alpha2.APIServiceExport {}
144+ if err := r .Get (ctx , req .NamespacedName , apiServiceExport ); err != nil {
145+ if errors .IsNotFound (err ) {
146+ // Request object not found, could have been deleted after reconcile request.
147+ logger .Info ("APIServiceExport not found, ignoring" )
148+ return ctrl.Result {}, nil
195149 }
196- logger . V ( 2 ). Info ( "queueing APIServiceExport" , "key" , key , "reason" , "CustomResourceDefinition" , "CustomResourceDefinitionKey" , crdKey )
197- c . queue . Add ( key )
150+ // Error reading the object - requeue the request.
151+ return ctrl. Result {}, fmt . Errorf ( "failed to get APIServiceExport: %w" , err )
198152 }
199- }
200-
201- // Start starts the controller, which stops when ctx.Done() is closed.
202- func (c * Controller ) Start (ctx context.Context , numThreads int ) {
203- defer runtime .HandleCrash ()
204- defer c .queue .ShutDown ()
205-
206- logger := klog .FromContext (ctx ).WithValues ("controller" , controllerName )
207-
208- logger .Info ("Starting controller" )
209- defer logger .Info ("Shutting down controller" )
210-
211- for i := 0 ; i < numThreads ; i ++ {
212- go wait .UntilWithContext (ctx , c .startWorker , time .Second )
213- }
214-
215- <- ctx .Done ()
216- }
217153
218- func ( c * Controller ) startWorker ( ctx context. Context ) {
219- defer runtime . HandleCrash ()
154+ // Create a copy to modify
155+ original := apiServiceExport . DeepCopy ()
220156
221- for c .processNextWorkItem (ctx ) {
157+ // Run the reconciliation logic
158+ if err := r .reconciler .reconcile (ctx , apiServiceExport ); err != nil {
159+ logger .Error (err , "Failed to reconcile APIServiceExport" )
160+ return ctrl.Result {}, err
222161 }
223- }
224162
225- func (c * Controller ) processNextWorkItem (ctx context.Context ) bool {
226- // Wait until there is a new item in the working queue
227- key , quit := c .queue .Get ()
228- if quit {
229- return false
163+ // Update status if it has changed
164+ if ! reflect .DeepEqual (original , apiServiceExport ) {
165+ err := r .Update (ctx , apiServiceExport )
166+ if err != nil {
167+ logger .Error (err , "Failed to update APIServiceExport status" )
168+ return ctrl.Result {}, fmt .Errorf ("failed to update APIServiceExport status: %w" , err )
169+ }
170+ logger .Info ("APIServiceExport status updated" , "namespace" , apiServiceExport .Namespace , "name" , apiServiceExport .Name )
230171 }
231172
232- logger := klog .FromContext (ctx ).WithValues ("key" , key )
233- ctx = klog .NewContext (ctx , logger )
234- logger .V (2 ).Info ("processing key" )
235-
236- // No matter what, tell the queue we're done with this key, to unblock
237- // other workers.
238- defer c .queue .Done (key )
239-
240- if err := c .process (ctx , key ); err != nil {
241- runtime .HandleError (fmt .Errorf ("%q controller failed to sync %q, err: %w" , controllerName , key , err ))
242- c .queue .AddRateLimited (key )
243- return true
244- }
245- c .queue .Forget (key )
246- return true
173+ return ctrl.Result {}, nil
247174}
248175
249- func (c * Controller ) process (ctx context.Context , key string ) error {
250- ns , name , err := cache .SplitMetaNamespaceKey (key )
251- if err != nil {
252- runtime .HandleError (err )
253- return nil // we cannot do anything
254- }
255-
256- obj , err := c .serviceExportLister .APIServiceExports (ns ).Get (name )
257- if err != nil && ! errors .IsNotFound (err ) {
258- return err
259- } else if errors .IsNotFound (err ) {
260- return nil // nothing we can do
261- }
262-
263- old := obj
264- obj = obj .DeepCopy ()
265-
266- var errs []error
267- if err := c .reconcile (ctx , obj ); err != nil {
268- errs = append (errs , err )
269- }
270-
271- // Regardless of whether reconcile returned an error or not, always try to patch status if needed. Return the
272- // reconciliation error at the end.
273-
274- // If the object being reconciled changed as a result, update it.
275- oldResource := & Resource {ObjectMeta : old .ObjectMeta , Spec : & old .Spec , Status : & old .Status }
276- newResource := & Resource {ObjectMeta : obj .ObjectMeta , Spec : & obj .Spec , Status : & obj .Status }
277- if err := c .commit (ctx , oldResource , newResource ); err != nil {
278- errs = append (errs , err )
279- }
280-
281- return utilerrors .NewAggregate (errs )
176+ // SetupWithManager sets up the controller with the Manager.
177+ func (r * APIServiceExportReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
178+ return ctrl .NewControllerManagedBy (mgr ).
179+ For (& kubebindv1alpha2.APIServiceExport {}).
180+ Watches (
181+ & apiextensionsv1.CustomResourceDefinition {},
182+ handler .EnqueueRequestsFromMapFunc (r .createCRDMapper ()),
183+ ).
184+ Named (controllerName ).
185+ Complete (r )
282186}
0 commit comments