From cadf10827dee7a86f505333510aacdec4d08fb3e Mon Sep 17 00:00:00 2001 From: Steffen Baarsgaard Date: Thu, 30 Jan 2025 10:39:32 +0100 Subject: [PATCH] Refactor: Update `GrafanaDatasource` reconcile loop (#1826) * chore: Implement CommonResource for GrafanaDatasource * feat: GetScopedMatchingInstances for Reconcile feat: NoMatchingInstances condition * fix: Datasource logging of errors refactor: Call Update Status once in Defer feat: Add NoMatchingInstances condition * chore: Datasource field cannot be nil, field is neither nullable or optional test: Verify spec.Datasource is never nil * feat: Add finalizers and cleanup check chore: Update Datasource finalize function * fix: Ignore status updates * chore: Mark status.lastMessage Deprecated test: Fix e2e assert failing after lastMessage deprecation * chore: Move model mutation into getDatasourceContent * feat: InvalidSpec condition when json or valuesFrom are invalid refactor: BuildDatasourceModel aligned with contactpoint * test: Add GrafanaDatasource assert * fix: Ignore missing datasources on finalize * fix: Apply review comments --- api/v1beta1/grafanadatasource_types.go | 19 +- api/v1beta1/grafanadatasource_types_test.go | 24 ++ ...na.integreatly.org_grafanadatasources.yaml | 1 + controllers/datasource_controller.go | 303 ++++++++++-------- controllers/datasource_controller_test.go | 2 +- ...na.integreatly.org_grafanadatasources.yaml | 1 + deploy/kustomize/base/crds.yaml | 1 + docs/docs/api.md | 2 +- tests/e2e/example-test/01-assert.yaml | 9 + tests/e2e/example-test/06-assert.yaml | 2 +- tests/e2e/example-test/07-assert.yaml | 2 +- .../examples/crossnamespace/assertions.yaml | 2 +- 12 files changed, 229 insertions(+), 139 deletions(-) diff --git a/api/v1beta1/grafanadatasource_types.go b/api/v1beta1/grafanadatasource_types.go index 51c8afe97..8748c356a 100644 --- a/api/v1beta1/grafanadatasource_types.go +++ b/api/v1beta1/grafanadatasource_types.go @@ -86,7 +86,8 @@ type GrafanaDatasourceSpec struct { type GrafanaDatasourceStatus struct { GrafanaCommonStatus `json:",inline"` - Hash string `json:"hash,omitempty"` + Hash string `json:"hash,omitempty"` + // Deprecated: Check status.conditions or operator logs LastMessage string `json:"lastMessage,omitempty"` // The datasource instanceSelector can't find matching grafana instances NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` @@ -149,10 +150,6 @@ func (in *GrafanaDatasource) CustomUIDOrUID() string { return string(in.ObjectMeta.UID) } -func (in *GrafanaDatasource) IsAllowCrossNamespaceImport() bool { - return in.Spec.AllowCrossNamespaceImport -} - func (in *GrafanaDatasourceList) Find(namespace string, name string) *GrafanaDatasource { for _, datasource := range in.Items { if datasource.Namespace == namespace && datasource.Name == name { @@ -162,6 +159,18 @@ func (in *GrafanaDatasourceList) Find(namespace string, name string) *GrafanaDat return nil } +func (in *GrafanaDatasource) MatchLabels() *metav1.LabelSelector { + return in.Spec.InstanceSelector +} + +func (in *GrafanaDatasource) MatchNamespace() string { + return in.ObjectMeta.Namespace +} + +func (in *GrafanaDatasource) AllowCrossNamespace() bool { + return in.Spec.AllowCrossNamespaceImport +} + func init() { SchemeBuilder.Register(&GrafanaDatasource{}, &GrafanaDatasourceList{}) } diff --git a/api/v1beta1/grafanadatasource_types_test.go b/api/v1beta1/grafanadatasource_types_test.go index 571d13d78..6d94ca820 100644 --- a/api/v1beta1/grafanadatasource_types_test.go +++ b/api/v1beta1/grafanadatasource_types_test.go @@ -71,3 +71,27 @@ var _ = Describe("Datasource type", func() { }) }) }) + +var _ = Describe("Fail on field behavior changes", func() { + emptyDatasource := &GrafanaDatasource{ + TypeMeta: v1.TypeMeta{ + APIVersion: APIVersion, + Kind: "GrafanaDatasource", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "test-nil-datasource", + Namespace: "default", + }, + Spec: GrafanaDatasourceSpec{ + GrafanaCommonSpec: GrafanaCommonSpec{ + InstanceSelector: &v1.LabelSelector{}, + }, + Datasource: nil, + }, + } + + ctx := context.Background() + It("Fails creating GrafanaDatasource with undefined spec.datasource", func() { + Expect(k8sClient.Create(ctx, emptyDatasource)).To(HaveOccurred()) + }) +}) diff --git a/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml b/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml index dd387e3e5..0f032fc68 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml @@ -320,6 +320,7 @@ spec: hash: type: string lastMessage: + description: 'Deprecated: Check status.conditions or operator logs' type: string lastResync: description: Last time the resource was synchronized with Grafana diff --git a/controllers/datasource_controller.go b/controllers/datasource_controller.go index 9c4c52017..03bb2991e 100644 --- a/controllers/datasource_controller.go +++ b/controllers/datasource_controller.go @@ -34,15 +34,21 @@ import ( genapi "github.com/grafana/grafana-openapi-client-go/client" client2 "github.com/grafana/grafana-operator/v5/controllers/client" kuberr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" logf "sigs.k8s.io/controller-runtime/pkg/log" v1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1" ) +const ( + conditionDatasourceSynchronized = "DatasourceSynchronized" +) + // GrafanaDatasourceReconciler reconciles a GrafanaDatasource object type GrafanaDatasourceReconciler struct { client.Client @@ -62,9 +68,7 @@ func (r *GrafanaDatasourceReconciler) syncDatasources(ctx context.Context) (ctrl var opts []client.ListOption err := r.Client.List(ctx, grafanas, opts...) if err != nil { - return ctrl.Result{ - Requeue: true, - }, err + return ctrl.Result{}, err } // no instances, no need to sync @@ -76,9 +80,7 @@ func (r *GrafanaDatasourceReconciler) syncDatasources(ctx context.Context) (ctrl allDatasources := &v1beta1.GrafanaDatasourceList{} err = r.Client.List(ctx, allDatasources, opts...) if err != nil { - return ctrl.Result{ - Requeue: true, - }, err + return ctrl.Result{}, err } // sync datasources, delete datasources from grafana that do no longer have a cr @@ -97,7 +99,7 @@ func (r *GrafanaDatasourceReconciler) syncDatasources(ctx context.Context) (ctrl grafana := grafana grafanaClient, err := client2.NewGeneratedGrafanaClient(ctx, r.Client, grafana) if err != nil { - return ctrl.Result{Requeue: true}, err + return ctrl.Result{}, err } for _, datasource := range existingDatasources { @@ -113,7 +115,7 @@ func (r *GrafanaDatasourceReconciler) syncDatasources(ctx context.Context) (ctrl if err != nil { var notFound *datasources.GetDataSourceByUIDNotFound if errors.As(err, ¬Found) { - return ctrl.Result{Requeue: false}, err + return ctrl.Result{}, err } log.Info("datasource no longer exists", "namespace", namespace, "name", name) } else { @@ -121,7 +123,7 @@ func (r *GrafanaDatasourceReconciler) syncDatasources(ctx context.Context) (ctrl if err != nil { var notFound *datasources.DeleteDataSourceByUIDNotFound if errors.As(err, ¬Found) { - return ctrl.Result{Requeue: false}, err + return ctrl.Result{}, err } } } @@ -134,7 +136,7 @@ func (r *GrafanaDatasourceReconciler) syncDatasources(ctx context.Context) (ctrl // so we should minimize those updates err = r.Client.Status().Update(ctx, grafana) if err != nil { - return ctrl.Result{Requeue: false}, err + return ctrl.Result{}, err } } @@ -164,73 +166,85 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re }, cr) if err != nil { if kuberr.IsNotFound(err) { - err = r.onDatasourceDeleted(ctx, req.Namespace, req.Name) - if err != nil { - return ctrl.Result{RequeueAfter: RequeueDelay}, err - } return ctrl.Result{}, nil } - log.Error(err, "error getting grafana datasource cr") - return ctrl.Result{RequeueAfter: RequeueDelay}, err + return ctrl.Result{}, fmt.Errorf("error getting grafana datasource cr: %w", err) } - if cr.Spec.Datasource == nil { - log.Info("skipped datasource with empty spec", cr.Name, cr.Namespace) - // TODO: add a custom status around that? + if cr.GetDeletionTimestamp() != nil { + // Check if resource needs clean up + if controllerutil.ContainsFinalizer(cr, grafanaFinalizer) { + if err := r.finalize(ctx, cr); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to finalize GrafanaDatasource: %w", err) + } + if err := removeFinalizer(ctx, r.Client, cr); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to remove finalizer: %w", err) + } + } return ctrl.Result{}, nil } - // Overwrite OrgID to ensure the field is useless - cr.Spec.Datasource.OrgID = nil + defer func() { + cr.Status.LastResync = metav1.Time{Time: time.Now()} + if err := r.Status().Update(ctx, cr); err != nil { + log.Error(err, "updating status") + } + if meta.IsStatusConditionTrue(cr.Status.Conditions, conditionNoMatchingInstance) { + if err := removeFinalizer(ctx, r.Client, cr); err != nil { + log.Error(err, "failed to remove finalizer") + } + } else { + if err := addFinalizer(ctx, r.Client, cr); err != nil { + log.Error(err, "failed to set finalizer") + } + } + }() - instances, err := r.GetMatchingDatasourceInstances(ctx, cr, r.Client) + instances, err := GetScopedMatchingInstances(ctx, r.Client, cr) if err != nil { - log.Error(err, "could not find matching instances", "name", cr.Name, "namespace", cr.Namespace) - return ctrl.Result{RequeueAfter: RequeueDelay}, err + setNoMatchingInstancesCondition(&cr.Status.Conditions, cr.Generation, err) + meta.RemoveStatusCondition(&cr.Status.Conditions, conditionDatasourceSynchronized) + cr.Status.NoMatchingInstances = true + return ctrl.Result{}, fmt.Errorf("failed fetching instances: %w", err) } - log.Info("found matching Grafana instances for datasource", "count", len(instances.Items)) - - datasource, hash, err := r.getDatasourceContent(ctx, cr) - if err != nil { - log.Error(err, "could not retrieve datasource contents", "name", cr.Name, "namespace", cr.Namespace) - return ctrl.Result{RequeueAfter: RequeueDelay}, err + if len(instances) == 0 { + setNoMatchingInstancesCondition(&cr.Status.Conditions, cr.Generation, err) + meta.RemoveStatusCondition(&cr.Status.Conditions, conditionDatasourceSynchronized) + cr.Status.NoMatchingInstances = true + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } + removeNoMatchingInstance(&cr.Status.Conditions) + cr.Status.NoMatchingInstances = false + log.Info("found matching Grafana instances for datasource", "count", len(instances)) + if cr.IsUpdatedUID() { log.Info("datasource uid got updated, deleting datasources with the old uid") - err = r.onDatasourceDeleted(ctx, req.Namespace, req.Name) - if err != nil { - return ctrl.Result{RequeueAfter: RequeueDelay}, err + if err = r.deleteOldDatasource(ctx, cr); err != nil { + return ctrl.Result{}, err } // Clean up uid, so further reconcilications can track changes there cr.Status.UID = "" - err = r.Client.Status().Update(ctx, cr) - if err != nil { - return ctrl.Result{RequeueAfter: RequeueDelay}, err - } + // Force requeue for datasource creation + return ctrl.Result{Requeue: true}, nil + } - // Status update should trigger the next reconciliation right away, no need to requeue for dashboard creation - return ctrl.Result{}, nil + datasource, hash, err := r.buildDatasourceModel(ctx, cr) + if err != nil { + setInvalidSpec(&cr.Status.Conditions, cr.Generation, "InvalidModel", err.Error()) + meta.RemoveStatusCondition(&cr.Status.Conditions, conditionDatasourceSynchronized) + return ctrl.Result{}, fmt.Errorf("could not build datasource model: %w", err) } - success := true - for _, grafana := range instances.Items { - // check if this is a cross namespace import - if grafana.Namespace != cr.Namespace && !cr.IsAllowCrossNamespaceImport() { - continue - } + removeInvalidSpec(&cr.Status.Conditions) + pluginErrors := make(map[string]string) + applyErrors := make(map[string]string) + for _, grafana := range instances { grafana := grafana - // an admin url is required to interact with grafana - // the instance or route might not yet be ready - if grafana.Status.Stage != v1beta1.OperatorStageComplete || grafana.Status.StageStatus != v1beta1.OperatorStageResultSuccess { - log.Info("grafana instance not ready", "grafana", grafana.Name) - success = false - continue - } if grafana.IsInternal() { // first reconcile the plugins @@ -238,77 +252,117 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re // grafana reconciler will pick them upi err = ReconcilePlugins(ctx, r.Client, r.Scheme, &grafana, cr.Spec.Plugins, fmt.Sprintf("%v-datasource", cr.Name)) if err != nil { - success = false - log.Error(err, "error reconciling plugins", "datasource", cr.Name, "grafana", grafana.Name) + pluginErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() } } // then import the datasource into the matching grafana instances err = r.onDatasourceCreated(ctx, &grafana, cr, datasource, hash) if err != nil { - success = false - cr.Status.LastMessage = err.Error() - log.Error(err, "error reconciling datasource", "datasource", cr.Name, "grafana", grafana.Name) + applyErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() } } - // if the datasource was successfully synced in all instances, wait for its re-sync period - if success { - cr.Status.LastMessage = "" - cr.Status.Hash = hash - if cr.ResyncPeriodHasElapsed() { - cr.Status.LastResync = metav1.Time{Time: time.Now()} + // TODO Add new Condition displaing plugin reconciliation errors + // Specific to datasources + if len(pluginErrors) > 0 { + err := fmt.Errorf("%v", pluginErrors) + log.Error(err, "failed to apply plugins to all instances") + } + + if len(applyErrors) > 0 { + return ctrl.Result{}, fmt.Errorf("failed to apply to all instances: %v", applyErrors) + } + + condition := buildSynchronizedCondition("Datasource", conditionDatasourceSynchronized, cr.Generation, applyErrors, len(instances)) + meta.SetStatusCondition(&cr.Status.Conditions, condition) + + cr.Status.Hash = hash + cr.Status.LastMessage = "" // nolint:staticcheck + cr.Status.UID = cr.CustomUIDOrUID() + + return ctrl.Result{RequeueAfter: cr.Spec.ResyncPeriod.Duration}, nil +} + +func (r *GrafanaDatasourceReconciler) deleteOldDatasource(ctx context.Context, cr *v1beta1.GrafanaDatasource) error { + instances, err := GetScopedMatchingInstances(ctx, r.Client, cr) + if err != nil { + return fmt.Errorf("fetching instances: %w", err) + } + + for _, grafana := range instances { + grafana := grafana + + found, uid := grafana.Status.Datasources.Find(cr.Namespace, cr.Name) + if !found { + continue } - cr.Status.UID = cr.CustomUIDOrUID() - return ctrl.Result{RequeueAfter: cr.Spec.ResyncPeriod.Duration}, r.Client.Status().Update(ctx, cr) - } else { - // if there was an issue with the datasource, update the status - return ctrl.Result{RequeueAfter: RequeueDelay}, r.Client.Status().Update(ctx, cr) + + grafanaClient, err := client2.NewGeneratedGrafanaClient(ctx, r.Client, &grafana) + if err != nil { + return err + } + + datasource, err := grafanaClient.Datasources.GetDataSourceByUID(*uid) + var notFound *datasources.GetDataSourceByUIDNotFound + if errors.As(err, ¬Found) { + continue + } + + if err != nil { + return fmt.Errorf("fetching datasource: %w", err) + } + + _, err = grafanaClient.Datasources.DeleteDataSourceByUID(datasource.Payload.UID) //nolint + if err != nil { + return fmt.Errorf("deleting datasource to update uid %s: %w", *uid, err) + } + + grafana.Status.Datasources = grafana.Status.Datasources.Remove(cr.Namespace, cr.Name) + return r.Client.Status().Update(ctx, &grafana) } + + return nil } -func (r *GrafanaDatasourceReconciler) onDatasourceDeleted(ctx context.Context, namespace string, name string) error { - list := v1beta1.GrafanaList{} - opts := []client.ListOption{} - err := r.Client.List(ctx, &list, opts...) +func (r *GrafanaDatasourceReconciler) finalize(ctx context.Context, cr *v1beta1.GrafanaDatasource) error { + instances, err := GetScopedMatchingInstances(ctx, r.Client, cr) if err != nil { - return err + return fmt.Errorf("fetching instances: %w", err) } - for _, grafana := range list.Items { + for _, grafana := range instances { grafana := grafana - if found, uid := grafana.Status.Datasources.Find(namespace, name); found { - grafanaClient, err := client2.NewGeneratedGrafanaClient(ctx, r.Client, &grafana) - if err != nil { - return err - } - datasource, err := grafanaClient.Datasources.GetDataSourceByUID(*uid) - if err != nil { - var notFound *datasources.GetDataSourceByUIDNotFound - if errors.As(err, ¬Found) { - return err - } - } else { - _, err = grafanaClient.Datasources.DeleteDataSourceByUID(datasource.Payload.UID) //nolint - if err != nil { - var notFound *datasources.DeleteDataSourceByUIDNotFound - if errors.As(err, ¬Found) { - return err - } - } - } + found, uid := grafana.Status.Datasources.Find(cr.Namespace, cr.Name) + if !found { + continue + } - if grafana.IsInternal() { - err = ReconcilePlugins(ctx, r.Client, r.Scheme, &grafana, nil, fmt.Sprintf("%v-datasource", name)) - if err != nil { - return err - } - } + grafanaClient, err := client2.NewGeneratedGrafanaClient(ctx, r.Client, &grafana) + if err != nil { + return err + } - grafana.Status.Datasources = grafana.Status.Datasources.Remove(namespace, name) - return r.Client.Status().Update(ctx, &grafana) + _, err = grafanaClient.Datasources.DeleteDataSourceByUID(*uid) // nolint:errcheck + var notFound *datasources.DeleteDataSourceByUIDNotFound + if errors.As(err, ¬Found) { + continue } + + if err != nil { + return fmt.Errorf("deleting datasource %s: %w", *uid, err) + } + + if grafana.IsInternal() { + err = ReconcilePlugins(ctx, r.Client, r.Scheme, &grafana, nil, fmt.Sprintf("%v-datasource", cr.Name)) + if err != nil { + return err + } + } + + grafana.Status.Datasources = grafana.Status.Datasources.Remove(cr.Namespace, cr.Name) + return r.Client.Status().Update(ctx, &grafana) } return nil @@ -385,6 +439,7 @@ func (r *GrafanaDatasourceReconciler) Exists(client *genapi.GrafanaHTTPAPI, uid, func (r *GrafanaDatasourceReconciler) SetupWithManager(mgr ctrl.Manager, ctx context.Context) error { err := ctrl.NewControllerManagedBy(mgr). For(&v1beta1.GrafanaDatasource{}). + WithEventFilter(ignoreStatusUpdates()). Complete(r) if err == nil { @@ -414,45 +469,31 @@ func (r *GrafanaDatasourceReconciler) SetupWithManager(mgr ctrl.Manager, ctx con return err } -func (r *GrafanaDatasourceReconciler) GetMatchingDatasourceInstances(ctx context.Context, datasource *v1beta1.GrafanaDatasource, k8sClient client.Client) (v1beta1.GrafanaList, error) { +func (r *GrafanaDatasourceReconciler) buildDatasourceModel(ctx context.Context, cr *v1beta1.GrafanaDatasource) (*models.UpdateDataSourceCommand, string, error) { log := logf.FromContext(ctx) - instances, err := GetMatchingInstances(ctx, k8sClient, datasource.Spec.InstanceSelector) - if err != nil || len(instances.Items) == 0 { - datasource.Status.NoMatchingInstances = true - if err := r.Client.Status().Update(ctx, datasource); err != nil { - log.Info("unable to update the status of %v, in %v", datasource.Name, datasource.Namespace) - } - return v1beta1.GrafanaList{}, err - } - datasource.Status.NoMatchingInstances = false - if err := r.Client.Status().Update(ctx, datasource); err != nil { - log.Info("unable to update the status of %v, in %v", datasource.Name, datasource.Namespace) - } - - return instances, err -} + // Overwrite OrgID to ensure the field is useless + cr.Spec.Datasource.OrgID = nil -func (r *GrafanaDatasourceReconciler) getDatasourceContent(ctx context.Context, cr *v1beta1.GrafanaDatasource) (*models.UpdateDataSourceCommand, string, error) { initialBytes, err := json.Marshal(cr.Spec.Datasource) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("encoding existing datasource model as json: %w", err) } + // Unstructured object for mutating target paths simpleContent, err := simplejson.NewJson(initialBytes) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("parsing marshaled json as simplejson: %w", err) } simpleContent.Set("uid", cr.CustomUIDOrUID()) - for _, ref := range cr.Spec.ValuesFrom { - ref := ref - val, key, err := getReferencedValue(ctx, r.Client, cr, ref.ValueFrom) + for _, override := range cr.Spec.ValuesFrom { + val, key, err := getReferencedValue(ctx, r.Client, cr, override.ValueFrom) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("getting referenced value: %w", err) } - patternToReplace := simpleContent.GetPath(strings.Split(ref.TargetPath, ".")...) + patternToReplace := simpleContent.GetPath(strings.Split(override.TargetPath, ".")...) patternString, err := patternToReplace.String() if err != nil { return nil, "", fmt.Errorf("pattern must be a string") @@ -460,20 +501,24 @@ func (r *GrafanaDatasourceReconciler) getDatasourceContent(ctx context.Context, patternString = strings.ReplaceAll(patternString, fmt.Sprintf("${%v}", key), val) patternString = strings.ReplaceAll(patternString, fmt.Sprintf("$%v", key), val) - simpleContent.SetPath(strings.Split(ref.TargetPath, "."), patternString) + + log.V(1).Info("overriding value", "key", override.TargetPath, "value", val) + simpleContent.SetPath(strings.Split(override.TargetPath, "."), patternString) } newBytes, err := simpleContent.MarshalJSON() if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("encoding expanded datasource model as json: %w", err) } + // TODO models.DataSource has SecureJsonData field now, verify if below is still true // We use UpdateDataSourceCommand here because models.DataSource lacks the SecureJsonData field var res models.UpdateDataSourceCommand if err = json.Unmarshal(newBytes, &res); err != nil { - return nil, "", err + return nil, "", fmt.Errorf("deserializing expanded datasource model from json: %w", err) } + // TODO Remove hashing along with the Status.Hash field hash := sha256.New() hash.Write(newBytes) diff --git a/controllers/datasource_controller_test.go b/controllers/datasource_controller_test.go index 9ecae6522..46c356d70 100644 --- a/controllers/datasource_controller_test.go +++ b/controllers/datasource_controller_test.go @@ -28,7 +28,7 @@ func TestGetDatasourceContent(t *testing.T) { }, } - content, hash, err := reconciler.getDatasourceContent(context.TODO(), cr) + content, hash, err := reconciler.buildDatasourceModel(context.TODO(), cr) got := content.SecureJSONData["httpHeaderValue1"] assert.Nil(t, err) diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml index dd387e3e5..0f032fc68 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml @@ -320,6 +320,7 @@ spec: hash: type: string lastMessage: + description: 'Deprecated: Check status.conditions or operator logs' type: string lastResync: description: Last time the resource was synchronized with Grafana diff --git a/deploy/kustomize/base/crds.yaml b/deploy/kustomize/base/crds.yaml index 0613a1e81..a7a10d208 100644 --- a/deploy/kustomize/base/crds.yaml +++ b/deploy/kustomize/base/crds.yaml @@ -1465,6 +1465,7 @@ spec: hash: type: string lastMessage: + description: 'Deprecated: Check status.conditions or operator logs' type: string lastResync: description: Last time the resource was synchronized with Grafana diff --git a/docs/docs/api.md b/docs/docs/api.md index 4e737a6c3..4326f11e8 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -2836,7 +2836,7 @@ GrafanaDatasourceStatus defines the observed state of GrafanaDatasource lastMessage string -
+ Deprecated: Check status.conditions or operator logs
false diff --git a/tests/e2e/example-test/01-assert.yaml b/tests/e2e/example-test/01-assert.yaml index 370b74b9b..7b03e6183 100644 --- a/tests/e2e/example-test/01-assert.yaml +++ b/tests/e2e/example-test/01-assert.yaml @@ -4,3 +4,12 @@ metadata: name: grafana-deployment status: availableReplicas: 1 +--- +apiVersion: grafana.integreatly.org/v1beta1 +kind: GrafanaDatasource +metadata: + name: grafanadatasource-sample +status: + conditions: + - type: DatasourceSynchronized + status: "True" diff --git a/tests/e2e/example-test/06-assert.yaml b/tests/e2e/example-test/06-assert.yaml index b90d3df53..208d9520e 100644 --- a/tests/e2e/example-test/06-assert.yaml +++ b/tests/e2e/example-test/06-assert.yaml @@ -8,4 +8,4 @@ binaryData: apiVersion: grafana.integreatly.org/v1beta1 kind: GrafanaDashboard metadata: - name: grafana-dashboard-secret-envs + name: grafana-dashboard-secret-envs diff --git a/tests/e2e/example-test/07-assert.yaml b/tests/e2e/example-test/07-assert.yaml index 1c48cf56f..e4061c942 100644 --- a/tests/e2e/example-test/07-assert.yaml +++ b/tests/e2e/example-test/07-assert.yaml @@ -1,4 +1,4 @@ apiVersion: grafana.integreatly.org/v1beta1 kind: GrafanaDashboard metadata: - name: grafana-dashboard-jsonnet-project + name: grafana-dashboard-jsonnet-project diff --git a/tests/e2e/examples/crossnamespace/assertions.yaml b/tests/e2e/examples/crossnamespace/assertions.yaml index 52da7c785..f8607772e 100644 --- a/tests/e2e/examples/crossnamespace/assertions.yaml +++ b/tests/e2e/examples/crossnamespace/assertions.yaml @@ -42,4 +42,4 @@ metadata: name: example-grafanadatasource namespace: (join('-', ['cross', $namespace])) status: - (lastMessage != null): true + (conditions != null): true