From f0b3d2442061333d63be0f5e04935f224eb19ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gomez?= Date: Sat, 9 Oct 2021 01:13:22 +0200 Subject: [PATCH 1/2] Update GrafanaDashboard status on synchronization --- internal/controller.go | 36 +++++++++++++++++-- internal/pkg/apis/controller/v1/types.go | 1 + .../controller/v1/zz_generated.deepcopy.go | 1 + k8s/crd.yaml | 15 ++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/internal/controller.go b/internal/controller.go index c9e247df..235106bb 100644 --- a/internal/controller.go +++ b/internal/controller.go @@ -1,6 +1,7 @@ package internal import ( + "context" "fmt" "strings" "time" @@ -12,6 +13,7 @@ import ( listers "github.com/K-Phoen/dark/internal/pkg/generated/listers/controller/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" @@ -165,6 +167,9 @@ func (c *Controller) processNextWorkItem() bool { // put back on the workqueue and attempted again after a back-off // period. defer c.workqueue.Done(obj) + + ctx := context.Background() + var key string var ok bool // We expect strings to come off the workqueue. These are of the @@ -191,7 +196,7 @@ func (c *Controller) processNextWorkItem() bool { } else { // Run the syncHandler, passing it the namespace/name string of the // GrafanaDashboard resource to be synced. - if err := c.syncHandler(key); err != nil { + if err := c.syncHandler(ctx, key); err != nil { // Put the item back on the workqueue to handle any transient errors. c.workqueue.AddRateLimited(key) return fmt.Errorf("error syncing '%s': %w, requeuing", key, err) @@ -217,7 +222,7 @@ func (c *Controller) processNextWorkItem() bool { // syncHandler compares the actual state with the desired, and attempts to // converge the two. It then updates the Status block of the GrafanaDashboard resource // with the current status of the resource. -func (c *Controller) syncHandler(key string) error { +func (c *Controller) syncHandler(ctx context.Context, key string) error { // Convert the namespace/name string into a distinct namespace and name namespace, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { @@ -242,14 +247,41 @@ func (c *Controller) syncHandler(key string) error { if err := c.dashboardCreator.FromRawSpec(dashboard.Folder, dashboard.ObjectMeta.Name, dashboard.Spec.Raw); err != nil { utilruntime.HandleError(fmt.Errorf("could not create '%s' dashboard from spec: %w", dashboard.ObjectMeta.Name, err)) c.recorder.Event(dashboard, corev1.EventTypeWarning, WarningNotSynced, fmt.Sprintf("could not create dashboard from spec: %s", err)) + c.updateDashboardStatus(ctx, dashboard, err) + return nil } c.recorder.Event(dashboard, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced) + c.updateDashboardStatus(ctx, dashboard, nil) return nil } +func (c *Controller) updateDashboardStatus(ctx context.Context, dashboard *v1.GrafanaDashboard, err error) { + // NEVER modify objects from the store. It's a read-only, local cache. + // You can use DeepCopy() to make a deep copy of original object and modify this copy + // Or create a copy manually for better performance + dashboardCopy := dashboard.DeepCopy() + + if err == nil { + dashboardCopy.Status.Status = "OK" + dashboardCopy.Status.Message = "Synchronized" + } else { + dashboardCopy.Status.Status = "Error" + dashboardCopy.Status.Message = err.Error() + } + + // If the CustomResourceSubresources feature gate is not enabled, + // we must use Update instead of UpdateStatus to update the Status block of the Foo resource. + // UpdateStatus will not allow changes to the Spec of the resource, + // which is ideal for ensuring nothing other than resource status has been updated. + _, err = c.darkClientSet.ControllerV1().GrafanaDashboards(dashboardCopy.Namespace).UpdateStatus(ctx, dashboardCopy, metav1.UpdateOptions{}) + if err != nil { + klog.Info(fmt.Sprintf("error while updating dashboard status: %s", err)) + } +} + func (c *Controller) deletionHandler(uid string) { if err := c.dashboardCreator.Delete(uid); err != nil { utilruntime.HandleError(fmt.Errorf("dashboard '%s' in work queue could not be deleted", uid)) diff --git a/internal/pkg/apis/controller/v1/types.go b/internal/pkg/apis/controller/v1/types.go index 3f925b54..46908c3e 100644 --- a/internal/pkg/apis/controller/v1/types.go +++ b/internal/pkg/apis/controller/v1/types.go @@ -23,6 +23,7 @@ type GrafanaDashboard struct { // GrafanaDashboardStatus is the status for a GrafanaDashboard resource type GrafanaDashboardStatus struct { + Status string `json:"status"` Message string `json:"message"` } diff --git a/internal/pkg/apis/controller/v1/zz_generated.deepcopy.go b/internal/pkg/apis/controller/v1/zz_generated.deepcopy.go index c44f2964..995cbe3b 100644 --- a/internal/pkg/apis/controller/v1/zz_generated.deepcopy.go +++ b/internal/pkg/apis/controller/v1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated // Code generated by deepcopy-gen. DO NOT EDIT. diff --git a/k8s/crd.yaml b/k8s/crd.yaml index 92aa637a..dba94dde 100644 --- a/k8s/crd.yaml +++ b/k8s/crd.yaml @@ -18,6 +18,14 @@ spec: # One and only one version must be marked as the storage version. storage: true + additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.message + name: Message + type: string + subresources: status: {} @@ -31,6 +39,13 @@ spec: spec: type: object x-kubernetes-preserve-unknown-fields: true + status: + type: object + properties: + message: + type: string + status: + type: string names: # plural name to be used in the URL: /apis/// From 493ac1f3ec69222e14973381707b8dda3d7c931a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gomez?= Date: Sat, 9 Oct 2021 01:37:41 +0200 Subject: [PATCH 2/2] Document ArgoCD health check --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index e9e19cdf..7065ab9c 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,32 @@ docker run --rm -it -v $(pwd):/workspace kphoen/dark-converter:latest convert-ya docker run --rm -it -v $(pwd):/workspace kphoen/dark-converter:latest convert-k8s-manifest -i dashboard.json -o converted-dashboard.yaml --folder Dark --namespace monitoring test-dashboard ``` +## Integrating with ArgoCD + +ArgoCD supports [health checks for custom resources](https://argo-cd.readthedocs.io/en/stable/operator-manual/health/#way-1-define-a-custom-health-check-in-argocd-cm-configmap). +To enable it for GrafanaDashboards, add the following code to your `argo-cm` ConfigMap: + +``` +data: + resource.customizations.health.k8s.kevingomez.fr_GrafanaDashboard: | + hs = {} + if obj.status ~= nil then + if obj.status.status ~= "OK" then + hs.status = "Degraded" + hs.message = obj.status.message + return hs + else + hs.status = "Healthy" + hs.message = obj.status.message + return hs + end + end + + hs.status = "Progressing" + hs.message = "Status unknown" + return hs +``` + ## Adopters [Companies using DARK](ADOPTERS.md).