@@ -18,7 +18,10 @@ package controllers
18
18
19
19
import (
20
20
"context"
21
+ "embed"
21
22
"fmt"
23
+ "path/filepath"
24
+ "strings"
22
25
23
26
argoapp "github.com/argoproj-labs/argocd-operator/api/v1alpha1"
24
27
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
@@ -41,6 +44,8 @@ const (
41
44
readRoleNameFormat = "%s-read"
42
45
readRoleBindingNameFormat = "%s-prometheus-k8s-read-binding"
43
46
alertRuleName = "gitops-operator-argocd-alerts"
47
+ dashboardNamespace = "openshift-config-managed"
48
+ dashboardFolder = "dashboards"
44
49
)
45
50
46
51
type ArgoCDMetricsReconciler struct {
@@ -50,6 +55,12 @@ type ArgoCDMetricsReconciler struct {
50
55
Scheme * runtime.Scheme
51
56
}
52
57
58
+ // embed json dashboards
59
+ var (
60
+ //go:embed dashboards
61
+ dashboards embed.FS
62
+ )
63
+
53
64
// blank assignment to verify that ReconcileArgoCDRoute implements reconcile.Reconciler
54
65
var _ reconcile.Reconciler = & ArgoCDMetricsReconciler {}
55
66
@@ -154,6 +165,11 @@ func (r *ArgoCDMetricsReconciler) Reconcile(ctx context.Context, request reconci
154
165
return reconcile.Result {}, err
155
166
}
156
167
168
+ err = r .reconcileDashboards (reqLogger )
169
+ if err != nil {
170
+ return reconcile.Result {}, err
171
+ }
172
+
157
173
return reconcile.Result {}, nil
158
174
}
159
175
@@ -292,6 +308,91 @@ func (r *ArgoCDMetricsReconciler) createPrometheusRuleIfAbsent(namespace string,
292
308
return err
293
309
}
294
310
311
+ func (r * ArgoCDMetricsReconciler ) reconcileDashboards (reqLogger logr.Logger ) error {
312
+ err := r .Client .Get (context .TODO (), types.NamespacedName {Name : dashboardNamespace }, & corev1.Namespace {})
313
+ if err != nil {
314
+ reqLogger .Info ("Monitoring dashboards are not supported on this cluster, skipping dashboard installation" ,
315
+ "Namespace" , dashboardNamespace )
316
+ return nil
317
+ }
318
+
319
+ entries , err := dashboards .ReadDir (dashboardFolder )
320
+ if err != nil {
321
+ reqLogger .Error (err , "Could not read list of embedded dashboards" )
322
+ return err
323
+ }
324
+
325
+ for _ , entry := range entries {
326
+ reqLogger .Info ("Processing dashboard" , "Namespace" , dashboardNamespace , "Name" , entry .Name ())
327
+
328
+ if ! entry .IsDir () {
329
+ dashboard , err := newDashboardConfigMap (entry .Name (), dashboardNamespace )
330
+ if err != nil {
331
+ reqLogger .Info ("There was an error creating dashboard " , "Namespace" , dashboardNamespace , "Name" , entry .Name ())
332
+ continue
333
+ }
334
+
335
+ existingDashboard := & corev1.ConfigMap {}
336
+
337
+ err = r .Client .Get (context .TODO (), types.NamespacedName {Name : dashboard .Name , Namespace : dashboardNamespace }, existingDashboard )
338
+ if err == nil {
339
+ reqLogger .Info ("A dashboard instance already exists" ,
340
+ "Namespace" , existingDashboard .Namespace , "Name" , existingDashboard .Name )
341
+
342
+ // See if we need to reconcile based on dashboard data only to allow users
343
+ // to disable dashboard via label if so desired. Note that disabling it
344
+ // will be reset if dashboard changes in newer version of operator.
345
+ if existingDashboard .Data [entry .Name ()] != dashboard .Data [entry .Name ()] {
346
+ reqLogger .Info ("Dashboard data does not match expectation, reconciling" ,
347
+ "Namespace" , dashboard .Namespace , "Name" , dashboard .Name )
348
+ err := r .Client .Update (context .TODO (), dashboard )
349
+ if err != nil {
350
+ reqLogger .Error (err , "Error updating dashboard" ,
351
+ "Namespace" , dashboard .Namespace , "Name" , dashboard .Name )
352
+ }
353
+ }
354
+ continue
355
+ }
356
+
357
+ if errors .IsNotFound (err ) {
358
+ reqLogger .Info ("Creating new dashboard" ,
359
+ "Namespace" , dashboard .Namespace , "Name" , dashboard .Name )
360
+ err := r .Client .Create (context .TODO (), dashboard )
361
+ if err != nil {
362
+ reqLogger .Error (err , "Error creating a new dashboard" ,
363
+ "Namespace" , dashboard .Namespace , "Name" , dashboard .Name )
364
+ }
365
+ }
366
+ }
367
+ }
368
+ return nil
369
+ }
370
+
371
+ func newDashboardConfigMap (filename string , namespace string ) (* corev1.ConfigMap , error ) {
372
+
373
+ name := strings .TrimSuffix (filename , filepath .Ext (filename ))
374
+
375
+ objectMeta := metav1.ObjectMeta {
376
+ Name : name ,
377
+ Namespace : namespace ,
378
+ Labels : map [string ]string {
379
+ "console.openshift.io/dashboard" : "true" ,
380
+ },
381
+ }
382
+
383
+ content , err := dashboards .ReadFile (dashboardFolder + "/" + filename )
384
+ if err != nil {
385
+ return nil , err
386
+ }
387
+
388
+ return & corev1.ConfigMap {
389
+ ObjectMeta : objectMeta ,
390
+ Data : map [string ]string {
391
+ filename : string (content ),
392
+ },
393
+ }, nil
394
+ }
395
+
295
396
func newReadRole (namespace string ) * rbacv1.Role {
296
397
objectMeta := metav1.ObjectMeta {
297
398
Name : fmt .Sprintf (readRoleNameFormat , namespace ),
0 commit comments