@@ -44,7 +44,6 @@ type GenericReconciler struct {
4444 cmWatcher * configmap.Watcher
4545 validationEngine validations.Interface
4646 apiResources []metav1.APIResource
47- resourceGVKs []schema.GroupVersionKind
4847}
4948
5049// NewGenericReconciler returns a GenericReconciler struct
@@ -163,27 +162,10 @@ func (gr *GenericReconciler) reconcileEverything(ctx context.Context) error {
163162 once .Do (func () {
164163 apiResources , err := reconcileResourceList (gr .discovery , gr .client .Scheme ())
165164 if err != nil {
166- gr .logger .Error (
167- err ,
168- "cannot read API resources" ,
169- )
165+ gr .logger .Error (err , "retrieving API resources to reconcile" )
170166 return
171167 }
172168 gr .apiResources = apiResources
173- gvks := getNamespacedResourcesGVK (gr .apiResources )
174- // sorting GVKs is very important for getting the consistent results
175- // when trying to match the 'app' label values. We must be sure that
176- // resources from the group apps/v1 are processed between first.
177- sort .SliceStable (gvks , func (i , j int ) bool {
178- f := gvks [i ]
179- s := gvks [j ]
180- // sort resource by Kind in the same group
181- if f .Group == s .Group {
182- return f .Kind < s .Kind
183- }
184- return f .Group < s .Group
185- })
186- gr .resourceGVKs = gvks
187169 })
188170
189171 for i , resource := range gr .apiResources {
@@ -198,7 +180,8 @@ func (gr *GenericReconciler) reconcileEverything(ctx context.Context) error {
198180 return fmt .Errorf ("getting watched namespaces: %w" , err )
199181 }
200182
201- errNR := gr .processNamespacedResources (ctx , namespaces )
183+ gvkResources := gr .getNamespacedResourcesGVK (gr .apiResources )
184+ errNR := gr .processNamespacedResources (ctx , gvkResources , namespaces )
202185 if errNR != nil {
203186 return fmt .Errorf ("processing namespace scoped resources: %w" , errNR )
204187 }
@@ -208,27 +191,26 @@ func (gr *GenericReconciler) reconcileEverything(ctx context.Context) error {
208191 return nil
209192}
210193
211- type unstructuredWithSelector struct {
212- unstructured * unstructured.Unstructured
213- selector labels.Selector
214- }
215-
216- type groupOfObjects struct {
217- objects []* unstructured.Unstructured
218- label string
219- }
220-
221194// groupAppObjects iterates over provided GroupVersionKind in given namespace
222195// and returns map of objects grouped by their "app" label
223196func (gr * GenericReconciler ) groupAppObjects (ctx context.Context ,
224- namespace string , ch chan groupOfObjects ) {
225- defer close (ch )
197+ namespace string , gvks []schema.GroupVersionKind ) (map [string ][]* unstructured.Unstructured , error ) {
226198 relatedObjects := make (map [string ][]* unstructured.Unstructured )
227- labelToLabelSet := make (map [string ]* labels.Set )
228199
229- var objectsWithNonEmptySelector []* unstructuredWithSelector
200+ // sorting GVKs is very important for getting the consistent results
201+ // when trying to match the 'app' label values. We must be sure that
202+ // resources from the group apps/v1 are processed between first.
203+ sort .Slice (gvks , func (i , j int ) bool {
204+ f := gvks [i ]
205+ s := gvks [j ]
206+ // sort resource by Kind in the same group
207+ if f .Group == s .Group {
208+ return f .Kind < s .Kind
209+ }
210+ return f .Group < s .Group
211+ })
230212
231- for _ , gvk := range gr . resourceGVKs {
213+ for _ , gvk := range gvks {
232214 list := unstructured.UnstructuredList {}
233215 listOptions := & client.ListOptions {
234216 Limit : gr .listLimit ,
@@ -238,20 +220,15 @@ func (gr *GenericReconciler) groupAppObjects(ctx context.Context,
238220 for {
239221
240222 if err := gr .client .List (ctx , & list , listOptions ); err != nil {
241- continue
223+ return nil , fmt . Errorf ( "listing %s: %w" , gvk . String (), err )
242224 }
243225
244226 for i := range list .Items {
245227 obj := & list .Items [i ]
246228 unstructured .RemoveNestedField (obj .Object , "metadata" , "managedFields" )
247229 unstructured .RemoveNestedField (obj .Object , "status" )
248- processResourceLabels (obj , relatedObjects , labelToLabelSet )
249- sel , err := getLabelSelector (obj )
250- if err != nil || sel == labels .Nothing () {
251- continue
252- }
253- objectsWithNonEmptySelector = append (objectsWithNonEmptySelector ,
254- & unstructuredWithSelector {unstructured : obj , selector : sel })
230+ processResourceLabels (obj , relatedObjects )
231+ gr .processResourceSelectors (obj , relatedObjects )
255232 }
256233
257234 listContinue := list .GetContinue ()
@@ -261,63 +238,72 @@ func (gr *GenericReconciler) groupAppObjects(ctx context.Context,
261238 listOptions .Continue = listContinue
262239 }
263240 }
264- for label := range relatedObjects {
265- labelsSet := labelToLabelSet [label ]
266- for _ , o := range objectsWithNonEmptySelector {
267- if o .selector .Matches (labelsSet ) {
268- relatedObjects [label ] = append (relatedObjects [label ], o .unstructured )
269- }
270- }
271- ch <- groupOfObjects {label : label , objects : relatedObjects [label ]}
272- }
273- }
274-
275- func getLabelSelector (obj * unstructured.Unstructured ) (labels.Selector , error ) {
276- labelSelector := utils .GetLabelSelector (obj )
277- return metav1 .LabelSelectorAsSelector (labelSelector )
241+ return relatedObjects , nil
278242}
279243
280244// processResourceLabels reads resource labels and if the labels
281245// are not empty then format them into string and put the string value
282246// as key and the object as a value into "relatedObjects" map
283247func processResourceLabels (obj * unstructured.Unstructured ,
284- relatedObjects map [string ][]* unstructured.Unstructured , labelSetMapping map [ string ] * labels. Set ) {
248+ relatedObjects map [string ][]* unstructured.Unstructured ) {
285249
286250 objLabels := utils .GetLabels (obj )
287251 if len (objLabels ) == 0 {
288252 return
289253 }
290254 labelsString := labels .FormatLabels (objLabels )
291255 relatedObjects [labelsString ] = append (relatedObjects [labelsString ], obj )
292- labelSetMapping [labelsString ] = & objLabels
256+ }
257+
258+ // processResourceSelectors reads resource selector and then tries to match
259+ // the selector to known labels (keys in the relatedObjects map). If a match is found then
260+ // the object is added to the corresponding group (values in the relatedObjects map).
261+ func (gr * GenericReconciler ) processResourceSelectors (obj * unstructured.Unstructured ,
262+ relatedObjects map [string ][]* unstructured.Unstructured ) {
263+ labelSelector := utils .GetLabelSelector (obj )
264+ selector , err := metav1 .LabelSelectorAsSelector (labelSelector )
265+ if err != nil {
266+ gr .logger .Error (err , "cannot convert label selector for object" , obj .GetKind (), obj .GetName ())
267+ return
268+ }
269+
270+ if selector == labels .Nothing () {
271+ return
272+ }
273+
274+ for k := range relatedObjects {
275+ labelsSet , err := labels .ConvertSelectorToLabelsMap (k )
276+ if err != nil {
277+ gr .logger .Error (err , "cannot convert selector to labels map for" , obj .GetKind (), obj .GetName ())
278+ continue
279+ }
280+ if selector .Matches (labelsSet ) {
281+ relatedObjects [k ] = append (relatedObjects [k ], obj )
282+ }
283+ }
293284}
294285
295286func (gr * GenericReconciler ) processNamespacedResources (
296- ctx context.Context , namespaces * []namespace ) error {
287+ ctx context.Context , gvks []schema. GroupVersionKind , namespaces * []namespace ) error {
297288
298- var wg sync.WaitGroup
299- wg .Add (len (* namespaces ))
300289 for _ , ns := range * namespaces {
301- namespace := ns .name
302- go func () {
303- ch := make (chan groupOfObjects )
304- go gr .groupAppObjects (ctx , namespace , ch )
305-
306- for groupOfObjects := range ch {
307- gr .logger .Info ("reconcileNamespaceResources" ,
308- "Reconciling group of" , len (groupOfObjects .objects ),
309- "objects with labels" , groupOfObjects .label ,
310- "in the namespace" , namespace )
311- err := gr .reconcileGroupOfObjects (ctx , groupOfObjects .objects , namespace )
312- if err != nil {
313- gr .logger .Error (err , "error reconciling group of " ,
314- len (groupOfObjects .objects ), "objects " , "in the namespace" , namespace )
315- }
290+ relatedObjects , err := gr .groupAppObjects (ctx , ns .name , gvks )
291+ if err != nil {
292+ return err
293+ }
294+ for label , objects := range relatedObjects {
295+ gr .logger .Info ("reconcileNamespaceResources" ,
296+ "Reconciling group of" , len (objects ), "objects with labels" , label ,
297+ "in the namespace" , ns .name )
298+ err := gr .reconcileGroupOfObjects (ctx , objects , ns .name )
299+ if err != nil {
300+ return fmt .Errorf (
301+ "reconciling related objects with labels '%s': %w" , label , err ,
302+ )
316303 }
317- wg .Done ()
318- }()
304+ }
319305 }
320- wg . Wait ()
306+
321307 return nil
322308}
323309
@@ -411,7 +397,7 @@ func (gr *GenericReconciler) handleResourceDeletions() {
411397}
412398
413399// getNamespacedResourcesGVK filters APIResources and returns the ones within a namespace
414- func getNamespacedResourcesGVK (resources []metav1.APIResource ) []schema.GroupVersionKind {
400+ func ( gr GenericReconciler ) getNamespacedResourcesGVK (resources []metav1.APIResource ) []schema.GroupVersionKind {
415401 namespacedResources := make ([]schema.GroupVersionKind , 0 )
416402 for _ , resource := range resources {
417403 if resource .Namespaced {
0 commit comments