@@ -22,7 +22,6 @@ import (
2222 "strings"
2323 "text/template"
2424
25- log "github.com/sirupsen/logrus"
2625 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2726 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2827 "k8s.io/apimachinery/pkg/labels"
@@ -59,7 +58,7 @@ type unstructuredSource struct {
5958 combineFqdnAnnotation bool
6059 fqdnTemplate * template.Template
6160 targetFqdnTemplate * template.Template
62- resources []resourceConfig
61+ informers []kubeinformers. GenericInformer
6362}
6463
6564// NewUnstructuredFQDNSource creates a new unstructuredSource.
@@ -83,21 +82,9 @@ func NewUnstructuredFQDNSource(
8382 return nil , err
8483 }
8584
86- // Discover and validate all resources using cached discovery client
87- cachedDiscovery := memory .NewMemCacheClient (kubeClient .Discovery ())
88- resourceConfigs := make ([]resourceConfig , 0 , len (resources ))
89- for _ , r := range resources {
90- gvr , err := parseResourceIdentifier (r )
91- if err != nil {
92- return nil , err
93- }
94-
95- rc , err := discoverResource (cachedDiscovery , gvr )
96- if err != nil {
97- return nil , err
98- }
99-
100- resourceConfigs = append (resourceConfigs , * rc )
85+ gvrs , err := discoverResources (kubeClient , resources )
86+ if err != nil {
87+ return nil , err
10188 }
10289
10390 // Create a single informer factory for all resources
@@ -109,11 +96,12 @@ func NewUnstructuredFQDNSource(
10996 )
11097
11198 // Create informers for each resource
112- for i := range resourceConfigs {
113- resourceConfigs [i ].informer = informerFactory .ForResource (resourceConfigs [i ].gvr )
99+ resourceInformers := make ([]kubeinformers.GenericInformer , 0 , len (gvrs ))
100+ for _ , gvr := range gvrs {
101+ informer := informerFactory .ForResource (gvr )
114102
115103 // Add indexers for efficient lookups by namespace and labels (must be before AddEventHandler)
116- err := resourceConfigs [ i ]. informer .Informer ().AddIndexers (
104+ err := informer .Informer ().AddIndexers (
117105 informers .IndexerWithOptions [* unstructured.Unstructured ](
118106 informers .IndexSelectorWithAnnotationFilter (annotationFilter ),
119107 informers .IndexSelectorWithLabelSelector (labelSelector ),
@@ -123,7 +111,8 @@ func NewUnstructuredFQDNSource(
123111 return nil , err
124112 }
125113
126- _ , _ = resourceConfigs [i ].informer .Informer ().AddEventHandler (informers .DefaultEventHandler ())
114+ _ , _ = informer .Informer ().AddEventHandler (informers .DefaultEventHandler ())
115+ resourceInformers = append (resourceInformers , informer )
127116 }
128117
129118 informerFactory .Start (ctx .Done ())
@@ -137,17 +126,17 @@ func NewUnstructuredFQDNSource(
137126 labelSelector : labelSelector ,
138127 fqdnTemplate : fqdnTmpl ,
139128 targetFqdnTemplate : targetTmpl ,
140- resources : resourceConfigs ,
129+ informers : resourceInformers ,
141130 combineFqdnAnnotation : combineFqdnAnnotation ,
142131 }, nil
143132}
144133
145134// Endpoints returns the list of endpoints from unstructured resources.
146- func (us * unstructuredSource ) Endpoints (ctx context.Context ) ([]* endpoint.Endpoint , error ) {
135+ func (us * unstructuredSource ) Endpoints (_ context.Context ) ([]* endpoint.Endpoint , error ) {
147136 var endpoints []* endpoint.Endpoint
148137
149- for _ , rc := range us .resources {
150- resourceEndpoints , err := us .endpointsForResource ( ctx , rc )
138+ for _ , informer := range us .informers {
139+ resourceEndpoints , err := us .endpointsFromInformer ( informer )
151140 if err != nil {
152141 return nil , err
153142 }
@@ -157,17 +146,16 @@ func (us *unstructuredSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoi
157146 return endpoints , nil
158147}
159148
160- // endpointsForResource returns endpoints for a single resource type.
161- func (us * unstructuredSource ) endpointsForResource ( _ context. Context , rc resourceConfig ) ([]* endpoint.Endpoint , error ) {
149+ // endpointsFromInformer returns endpoints for a single resource type.
150+ func (us * unstructuredSource ) endpointsFromInformer ( informer kubeinformers. GenericInformer ) ([]* endpoint.Endpoint , error ) {
162151 var endpoints []* endpoint.Endpoint
163152
164153 // Get objects that match the indexer filter (annotation and label selectors)
165- indexKeys := rc . informer .Informer ().GetIndexer ().ListIndexFuncValues (informers .IndexWithSelectors )
154+ indexKeys := informer .Informer ().GetIndexer ().ListIndexFuncValues (informers .IndexWithSelectors )
166155
167156 for _ , key := range indexKeys {
168- obj , err := informers .GetByKey [* unstructured.Unstructured ](rc . informer .Informer ().GetIndexer (), key )
157+ obj , err := informers .GetByKey [* unstructured.Unstructured ](informer .Informer ().GetIndexer (), key )
169158 if err != nil {
170- log .Debugf ("failed to get object by key %q: %v" , key , err )
171159 continue
172160 }
173161
@@ -240,17 +228,11 @@ func (us *unstructuredSource) endpointsFromTemplate(el *unstructuredWrapper) ([]
240228
241229// AddEventHandler adds an event handler that is called when resources change.
242230func (us * unstructuredSource ) AddEventHandler (_ context.Context , handler func ()) {
243- for _ , rc := range us .resources {
244- _ , _ = rc . informer .Informer ().AddEventHandler (eventHandlerFunc (handler ))
231+ for _ , informer := range us .informers {
232+ _ , _ = informer .Informer ().AddEventHandler (eventHandlerFunc (handler ))
245233 }
246234}
247235
248- // resourceConfig holds the parsed configuration for a single resource type.
249- type resourceConfig struct {
250- gvr schema.GroupVersionResource
251- informer kubeinformers.GenericInformer
252- }
253-
254236// unstructuredWrapper wraps an unstructured.Unstructured to provide both
255237// typed-style template access ({{ .Name }}, {{ .Namespace }}) and raw map access
256238// ({{ .Spec.field }}, {{ index .Status.interfaces 0 "ipAddress" }}).
@@ -312,66 +294,48 @@ func newUnstructuredWrapper(obj runtime.Object) *unstructuredWrapper {
312294 return w
313295}
314296
315- // parseResourceIdentifier parses a resource identifier in the format "resource.version.group"
316- // (e.g., "virtualmachineinstances.v1.kubevirt.io") and returns a GroupVersionResource.
317- //
318- // Format: resource.version.group
319- // - resource: plural resource name (e.g., "vmachines")
320- // - version: API version (e.g., "v1", "v1beta1")
321- // - group: API group (e.g., "kubevirt.io", "apps")
322- //
323- // For core API resources (e.g., pods.v1), the group is empty.
324- func parseResourceIdentifier (identifier string ) (schema.GroupVersionResource , error ) {
325- parts := strings .SplitN (identifier , "." , 3 )
326- if len (parts ) < 2 {
327- return schema.GroupVersionResource {}, fmt .Errorf ("invalid resource identifier %q: expected format resource.version.group (e.g., virtualmachineinstances.v1.kubevirt.io)" , identifier )
328- }
297+ // discoverResources parses and validates resource identifiers against the cluster.
298+ // It uses a cached discovery client to minimize API calls.
299+ func discoverResources (kubeClient kubernetes.Interface , resources []string ) ([]schema.GroupVersionResource , error ) {
300+ cachedDiscovery := memory .NewMemCacheClient (kubeClient .Discovery ())
301+ gvrs := make ([]schema.GroupVersionResource , 0 , len (resources ))
329302
330- resource := parts [0 ]
331- version := parts [1 ]
332- group := ""
333- if len (parts ) == 3 {
334- group = parts [2 ]
335- }
303+ for _ , r := range resources {
304+ // Handle core API resources (e.g., "configmaps.v1" -> "configmaps.v1.")
305+ if strings .Count (r , "." ) == 1 {
306+ r += "."
307+ }
336308
337- if resource == "" {
338- return schema.GroupVersionResource {}, fmt .Errorf ("invalid resource identifier %q: resource name cannot be empty" , identifier )
339- }
340- if version == "" {
341- return schema.GroupVersionResource {}, fmt .Errorf ("invalid resource identifier %q: version cannot be empty" , identifier )
309+ gvr , _ := schema .ParseResourceArg (r )
310+ if gvr == nil {
311+ return nil , fmt .Errorf ("invalid resource identifier %q: expected format resource.version.group (e.g., certificates.v1.cert-manager.io)" , r )
312+ }
313+
314+ if err := validateResource (cachedDiscovery , * gvr ); err != nil {
315+ return nil , err
316+ }
317+
318+ gvrs = append (gvrs , * gvr )
342319 }
343320
344- return schema.GroupVersionResource {
345- Group : group ,
346- Version : version ,
347- Resource : resource ,
348- }, nil
321+ return gvrs , nil
349322}
350323
351- // discoverResource validates that a resource exists in the cluster and returns its configuration .
352- // It uses the Discovery API to verify the resource and determine if it's namespaced .
353- func discoverResource (discoveryClient discovery.DiscoveryInterface , gvr schema.GroupVersionResource ) ( * resourceConfig , error ) {
324+ // validateResource validates that a resource exists in the cluster.
325+ // It uses the Discovery API to verify the resource is available .
326+ func validateResource (discoveryClient discovery.DiscoveryInterface , gvr schema.GroupVersionResource ) error {
354327 gv := gvr .GroupVersion ().String ()
355328
356329 apiResourceList , err := discoveryClient .ServerResourcesForGroupVersion (gv )
357330 if err != nil {
358- return nil , fmt .Errorf ("failed to discover resources for %q: %w" , gv , err )
331+ return fmt .Errorf ("failed to discover resources for %q: %w" , gv , err )
359332 }
360333
361- var apiResource * metav1.APIResource
362334 for i := range apiResourceList .APIResources {
363- ar := & apiResourceList .APIResources [i ]
364- if ar .Name == gvr .Resource {
365- apiResource = ar
366- break
335+ if apiResourceList .APIResources [i ].Name == gvr .Resource {
336+ return nil
367337 }
368338 }
369339
370- if apiResource == nil {
371- return nil , fmt .Errorf ("resource %q not found in %q" , gvr .Resource , gv )
372- }
373-
374- return & resourceConfig {
375- gvr : gvr ,
376- }, nil
340+ return fmt .Errorf ("resource %q not found in %q" , gvr .Resource , gv )
377341}
0 commit comments