@@ -31,6 +31,7 @@ import (
31
31
client2 "github.com/grafana/grafana-operator/v5/controllers/client"
32
32
"github.com/grafana/grafana-operator/v5/controllers/metrics"
33
33
kuberr "k8s.io/apimachinery/pkg/api/errors"
34
+ "k8s.io/apimachinery/pkg/api/meta"
34
35
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35
36
36
37
"k8s.io/apimachinery/pkg/runtime"
@@ -41,6 +42,10 @@ import (
41
42
grafanav1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1"
42
43
)
43
44
45
+ const (
46
+ conditionFolderSynchronized = "FolderSynchronized"
47
+ )
48
+
44
49
// GrafanaFolderReconciler reconciles a GrafanaFolder object
45
50
type GrafanaFolderReconciler struct {
46
51
client.Client
@@ -175,16 +180,35 @@ func (r *GrafanaFolderReconciler) Reconcile(ctx context.Context, req ctrl.Reques
175
180
controllerLog .Error (err , "error getting grafana folder cr" )
176
181
return ctrl.Result {RequeueAfter : RequeueDelay }, err
177
182
}
183
+ defer func () {
184
+ if err := r .UpdateStatus (ctx , folder ); err != nil {
185
+ r .Log .Error (err , "updating status" )
186
+ }
187
+ }()
188
+
189
+ if folder .Spec .ParentFolderUID == string (folder .UID ) {
190
+ setInvalidSpec (& folder .Status .Conditions , folder .Generation , "CyclicParent" , "The value of parentFolderUID must not be the uid of the current folder" )
191
+ meta .RemoveStatusCondition (& folder .Status .Conditions , conditionFolderSynchronized )
192
+ return ctrl.Result {}, fmt .Errorf ("cyclic folder reference" )
193
+ }
194
+ removeInvalidSpec (& folder .Status .Conditions )
178
195
179
196
instances , err := r .GetMatchingFolderInstances (ctx , folder , r .Client )
180
197
if err != nil {
181
- controllerLog .Error (err , "could not find matching instances" , "name" , folder .Name , "namespace" , folder .Namespace )
182
- return ctrl.Result {RequeueAfter : RequeueDelay }, err
198
+ setNoMatchingInstance (& folder .Status .Conditions , folder .Generation , "ErrFetchingInstances" , fmt .Sprintf ("error occurred during fetching of instances: %s" , err .Error ()))
199
+ meta .RemoveStatusCondition (& folder .Status .Conditions , conditionFolderSynchronized )
200
+ r .Log .Error (err , "could not find matching instances" )
201
+ return ctrl.Result {}, err
183
202
}
184
-
203
+ if len (instances .Items ) == 0 {
204
+ setNoMatchingInstance (& folder .Status .Conditions , folder .Generation , "EmptyAPIReply" , "Instances could not be fetched, reconciliation will be retried" )
205
+ meta .RemoveStatusCondition (& folder .Status .Conditions , conditionFolderSynchronized )
206
+ return ctrl.Result {}, fmt .Errorf ("no instances found" )
207
+ }
208
+ removeNoMatchingInstance (& folder .Status .Conditions )
185
209
controllerLog .Info ("found matching Grafana instances for folder" , "count" , len (instances .Items ))
186
210
187
- success := true
211
+ applyErrors := make ( map [ string ] string )
188
212
for _ , grafana := range instances .Items {
189
213
// check if this is a cross namespace import
190
214
if grafana .Namespace != folder .Namespace && ! folder .IsAllowCrossNamespaceImport () {
@@ -200,18 +224,20 @@ func (r *GrafanaFolderReconciler) Reconcile(ctx context.Context, req ctrl.Reques
200
224
err = r .onFolderCreated (ctx , & grafana , folder )
201
225
if err != nil {
202
226
controllerLog .Error (err , "error reconciling folder" , "folder" , folder .Name , "grafana" , grafana .Name )
203
- success = false
227
+ applyErrors [ fmt . Sprintf ( "%s/%s" , grafana . Namespace , grafana . Name )] = err . Error ()
204
228
}
205
229
}
230
+ condition := buildSynchronizedCondition ("Folder" , conditionFolderSynchronized , folder .Generation , applyErrors , len (instances .Items ))
231
+ meta .SetStatusCondition (& folder .Status .Conditions , condition )
206
232
207
- if ! success {
233
+ if len ( applyErrors ) != 0 {
208
234
return ctrl.Result {RequeueAfter : RequeueDelay }, nil
209
235
}
210
236
211
237
if folder .ResyncPeriodHasElapsed () {
212
238
folder .Status .LastResync = metav1.Time {Time : time .Now ()}
213
239
}
214
- return ctrl.Result {RequeueAfter : folder .GetResyncPeriod ()}, r . UpdateStatus ( ctx , folder )
240
+ return ctrl.Result {RequeueAfter : folder .GetResyncPeriod ()}, nil
215
241
}
216
242
217
243
// SetupWithManager sets up the controller with the Manager.
@@ -295,13 +321,13 @@ func (r *GrafanaFolderReconciler) onFolderCreated(ctx context.Context, grafana *
295
321
return err
296
322
}
297
323
298
- exists , remoteUID , err := r .Exists (grafanaClient , cr )
324
+ exists , remoteUID , remoteParent , err := r .Exists (grafanaClient , cr )
299
325
if err != nil {
300
326
return err
301
327
}
302
328
303
329
// always update after resync period has elapsed even if cr is unchanged.
304
- if exists && cr .Unchanged () && ! cr .ResyncPeriodHasElapsed () && ! cr .Moved () {
330
+ if exists && cr .Unchanged () && ! cr .ResyncPeriodHasElapsed () && cr .Spec . ParentFolderUID == remoteParent {
305
331
return nil
306
332
}
307
333
@@ -328,7 +354,7 @@ func (r *GrafanaFolderReconciler) onFolderCreated(ctx context.Context, grafana *
328
354
}
329
355
}
330
356
331
- if cr .Moved () {
357
+ if cr .Spec . ParentFolderUID != remoteParent {
332
358
_ , err = grafanaClient .Folders .MoveFolder (remoteUID , & models.MoveFolderCommand { //nolint
333
359
ParentUID : cr .Spec .ParentFolderUID ,
334
360
})
@@ -374,30 +400,35 @@ func (r *GrafanaFolderReconciler) onFolderCreated(ctx context.Context, grafana *
374
400
375
401
func (r * GrafanaFolderReconciler ) UpdateStatus (ctx context.Context , cr * grafanav1beta1.GrafanaFolder ) error {
376
402
cr .Status .Hash = cr .Hash ()
377
- cr .Status .ParentFolderUID = cr .Spec .ParentFolderUID
378
403
return r .Client .Status ().Update (ctx , cr )
379
404
}
380
405
381
- func (r * GrafanaFolderReconciler ) Exists (client * genapi.GrafanaHTTPAPI , cr * grafanav1beta1.GrafanaFolder ) (bool , string , error ) {
406
+ // Check if the folder exists. Matches UID first and fall back to title. Title matching only works for non-nested folders
407
+ func (r * GrafanaFolderReconciler ) Exists (client * genapi.GrafanaHTTPAPI , cr * grafanav1beta1.GrafanaFolder ) (bool , string , string , error ) {
382
408
title := cr .GetTitle ()
383
409
uid := string (cr .UID )
384
410
411
+ uidResp , err := client .Folders .GetFolderByUID (uid )
412
+ if err == nil {
413
+ return true , uidResp .Payload .UID , uidResp .Payload .ParentUID , nil
414
+ }
415
+
385
416
page := int64 (1 )
386
417
limit := int64 (10000 )
387
418
for {
388
- params := folders .NewGetFoldersParams ().WithPage (& page ).WithLimit (& limit ). WithParentUID ( & cr . Status . ParentFolderUID )
419
+ params := folders .NewGetFoldersParams ().WithPage (& page ).WithLimit (& limit )
389
420
390
421
foldersResp , err := client .Folders .GetFolders (params )
391
422
if err != nil {
392
- return false , "" , err
423
+ return false , "" , "" , err
393
424
}
394
425
for _ , folder := range foldersResp .Payload {
395
- if folder . UID == uid || strings .EqualFold (folder .Title , title ) {
396
- return true , folder .UID , nil
426
+ if strings .EqualFold (folder .Title , title ) {
427
+ return true , folder .UID , folder . ParentUID , nil
397
428
}
398
429
}
399
430
if len (foldersResp .Payload ) < int (limit ) {
400
- return false , "" , nil
431
+ return false , "" , "" , nil
401
432
}
402
433
page ++
403
434
}
0 commit comments