From b20456ec6844d33795063ac31c17112f0190da66 Mon Sep 17 00:00:00 2001 From: ste Date: Sun, 17 Nov 2024 20:02:13 +0100 Subject: [PATCH 01/10] feat: Define GrafanaCommonSpec struct for non-Grafana CRDs --- api/v1beta1/common.go | 33 ++++- api/v1beta1/grafanaalertrulegroup_types.go | 16 +-- api/v1beta1/grafanacontactpoint_types.go | 16 +-- api/v1beta1/grafanadashboard_types.go | 35 +----- api/v1beta1/grafanadashboard_types_test.go | 8 +- api/v1beta1/grafanadatasource_types.go | 35 +----- api/v1beta1/grafanadatasource_types_test.go | 8 +- api/v1beta1/grafanafolder_types.go | 35 +----- .../grafananotificationpolicy_types.go | 11 +- api/v1beta1/zz_generated.deepcopy.go | 113 +++++++++--------- ...ntegreatly.org_grafanaalertrulegroups.yaml | 10 +- ....integreatly.org_grafanacontactpoints.yaml | 10 +- ...ana.integreatly.org_grafanadashboards.yaml | 14 +-- ...na.integreatly.org_grafanadatasources.yaml | 14 +-- ...rafana.integreatly.org_grafanafolders.yaml | 14 +-- ...eatly.org_grafananotificationpolicies.yaml | 12 +- controllers/dashboard_controller.go | 2 +- controllers/dashboard_controller_test.go | 16 ++- controllers/datasource_controller.go | 2 +- controllers/grafanafolder_controller.go | 2 +- ...ntegreatly.org_grafanaalertrulegroups.yaml | 10 +- ....integreatly.org_grafanacontactpoints.yaml | 10 +- ...ana.integreatly.org_grafanadashboards.yaml | 14 +-- ...na.integreatly.org_grafanadatasources.yaml | 14 +-- ...rafana.integreatly.org_grafanafolders.yaml | 14 +-- ...eatly.org_grafananotificationpolicies.yaml | 12 +- deploy/kustomize/base/crds.yaml | 74 +++++++----- docs/docs/api.md | 77 ++++++------ go.mod | 2 - 29 files changed, 298 insertions(+), 335 deletions(-) diff --git a/api/v1beta1/common.go b/api/v1beta1/common.go index e9825d05e..117a3b523 100644 --- a/api/v1beta1/common.go +++ b/api/v1beta1/common.go @@ -1,6 +1,9 @@ package v1beta1 -import v1 "k8s.io/api/core/v1" +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) type ValueFrom struct { TargetPath string `json:"targetPath"` @@ -16,3 +19,31 @@ type ValueFromSource struct { // +optional SecretKeyRef *v1.SecretKeySelector `json:"secretKeyRef,omitempty"` } + +type GrafanaCommonSpec struct { + // How often the resource is synced, defaults to 10m0s if not set + // +optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=duration + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" + // +kubebuilder:default="10m0s" + ResyncPeriod metav1.Duration `json:"resyncPeriod,omitempty"` + + // Selects Grafana instances for import + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.instanceSelector is immutable" + InstanceSelector *metav1.LabelSelector `json:"instanceSelector"` + + // Allow the Operator to match this resource with Grafanas outside the current namespace + // +optional + AllowCrossNamespaceImport *bool `json:"allowCrossNamespaceImport,omitempty"` +} + +// The most recent observed state of a Grafana resource +type GrafanaCommonStatus struct { + // Detect resource changes + Hash string `json:"hash,omitempty"` + // Last time the resource was synchronized + LastResync metav1.Time `json:"lastResync,omitempty"` + // Results when synchonizing resource with Grafana instances + Conditions []metav1.Condition `json:"conditions,omitempty"` +} diff --git a/api/v1beta1/grafanaalertrulegroup_types.go b/api/v1beta1/grafanaalertrulegroup_types.go index c9a1da46e..b407d5a7f 100644 --- a/api/v1beta1/grafanaalertrulegroup_types.go +++ b/api/v1beta1/grafanaalertrulegroup_types.go @@ -26,21 +26,12 @@ import ( // GrafanaAlertRuleGroupSpec defines the desired state of GrafanaAlertRuleGroup // +kubebuilder:validation:XValidation:rule="(has(self.folderUID) && !(has(self.folderRef))) || (has(self.folderRef) && !(has(self.folderUID)))", message="Only one of FolderUID or FolderRef can be set" type GrafanaAlertRuleGroupSpec struct { + GrafanaCommonSpec `json:",inline"` + // +optional // Name of the alert rule group. If not specified, the resource name will be used. Name string `json:"name,omitempty"` - // +optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=duration - // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" - // +kubebuilder:default="10m" - ResyncPeriod metav1.Duration `json:"resyncPeriod,omitempty"` - - // selects Grafanas for import - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - InstanceSelector *metav1.LabelSelector `json:"instanceSelector"` - // UID of the folder containing this rule group // Overrides the FolderSelector FolderUID string `json:"folderUID,omitempty"` @@ -56,9 +47,6 @@ type GrafanaAlertRuleGroupSpec struct { // +kubebuilder:validation:Required Interval metav1.Duration `json:"interval"` - // +optional - AllowCrossNamespaceImport *bool `json:"allowCrossNamespaceImport,omitempty"` - // Whether to enable or disable editing of the alert rule group in Grafana UI // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" // +optional diff --git a/api/v1beta1/grafanacontactpoint_types.go b/api/v1beta1/grafanacontactpoint_types.go index e32488c92..74c6dfed9 100644 --- a/api/v1beta1/grafanacontactpoint_types.go +++ b/api/v1beta1/grafanacontactpoint_types.go @@ -27,22 +27,13 @@ import ( // GrafanaContactPointSpec defines the desired state of GrafanaContactPoint // +kubebuilder:validation:XValidation:rule="((!has(oldSelf.uid) && !has(self.uid)) || (has(oldSelf.uid) && has(self.uid)))", message="spec.uid is immutable" type GrafanaContactPointSpec struct { + GrafanaCommonSpec `json:",inline"` + // Manually specify the UID the Contact Point is created with // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.uid is immutable" CustomUID string `json:"uid,omitempty"` - // +optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=duration - // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" - // +kubebuilder:default="10m" - ResyncPeriod metav1.Duration `json:"resyncPeriod,omitempty"` - - // selects Grafanas for import - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - InstanceSelector *metav1.LabelSelector `json:"instanceSelector"` - // +optional DisableResolveMessage bool `json:"disableResolveMessage,omitempty"` @@ -56,9 +47,6 @@ type GrafanaContactPointSpec struct { // +kubebuilder:validation:Enum=alertmanager;prometheus-alertmanager;dingding;discord;email;googlechat;kafka;line;opsgenie;pagerduty;pushover;sensugo;sensu;slack;teams;telegram;threema;victorops;webhook;wecom;hipchat;oncall Type string `json:"type,omitempty"` - - // +optional - AllowCrossNamespaceImport *bool `json:"allowCrossNamespaceImport,omitempty"` } // GrafanaContactPointStatus defines the observed state of GrafanaContactPoint diff --git a/api/v1beta1/grafanadashboard_types.go b/api/v1beta1/grafanadashboard_types.go index 1ce879a83..d9519176b 100644 --- a/api/v1beta1/grafanadashboard_types.go +++ b/api/v1beta1/grafanadashboard_types.go @@ -62,6 +62,8 @@ type GrafanaDashboardUrlAuthorization struct { // +kubebuilder:validation:XValidation:rule="(has(self.folder) && !(has(self.folderRef) || has(self.folderUID))) || !(has(self.folder))", message="folder field cannot be set when folderUID or folderRef is already declared" // +kubebuilder:validation:XValidation:rule="((!has(oldSelf.uid) && !has(self.uid)) || (has(oldSelf.uid) && has(self.uid)))", message="spec.uid is immutable" type GrafanaDashboardSpec struct { + GrafanaCommonSpec `json:",inline"` + // Manually specify the uid for the dashboard, overwrites uids already present in the json model // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.uid is immutable" @@ -98,10 +100,6 @@ type GrafanaDashboardSpec struct { // +optional ConfigMapRef *v1.ConfigMapKeySelector `json:"configMapRef,omitempty"` - // selects Grafanas for import - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - InstanceSelector *metav1.LabelSelector `json:"instanceSelector"` - // folder assignment for dashboard // +optional FolderTitle string `json:"folder,omitempty"` @@ -122,22 +120,10 @@ type GrafanaDashboardSpec struct { // +optional ContentCacheDuration metav1.Duration `json:"contentCacheDuration,omitempty"` - // how often the dashboard is refreshed, defaults to 5m if not set - // +optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=duration - // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" - // +kubebuilder:default="5m" - ResyncPeriod string `json:"resyncPeriod,omitempty"` - // maps required data sources to existing ones // +optional Datasources []GrafanaDashboardDatasource `json:"datasources,omitempty"` - // allow to import this resources from an operator in a different namespace - // +optional - AllowCrossNamespaceImport *bool `json:"allowCrossNamespaceImport,omitempty"` - // environments variables as a map // +optional Envs []GrafanaDashboardEnv `json:"envs,omitempty"` @@ -261,25 +247,10 @@ func (in *GrafanaDashboard) Unchanged(hash string) bool { } func (in *GrafanaDashboard) ResyncPeriodHasElapsed() bool { - deadline := in.Status.LastResync.Add(in.GetResyncPeriod()) + deadline := in.Status.LastResync.Add(in.Spec.ResyncPeriod.Duration) return time.Now().After(deadline) } -func (in *GrafanaDashboard) GetResyncPeriod() time.Duration { - if in.Spec.ResyncPeriod == "" { - in.Spec.ResyncPeriod = DefaultResyncPeriod - return in.GetResyncPeriod() - } - - duration, err := time.ParseDuration(in.Spec.ResyncPeriod) - if err != nil { - in.Spec.ResyncPeriod = DefaultResyncPeriod - return in.GetResyncPeriod() - } - - return duration -} - func (in *GrafanaDashboard) GetSourceTypes() []DashboardSourceType { var sourceTypes []DashboardSourceType diff --git a/api/v1beta1/grafanadashboard_types_test.go b/api/v1beta1/grafanadashboard_types_test.go index cff9d7b04..7cedca4b4 100644 --- a/api/v1beta1/grafanadashboard_types_test.go +++ b/api/v1beta1/grafanadashboard_types_test.go @@ -261,9 +261,11 @@ func getDashboardCR(t *testing.T, crUID string, statusUID string, specUID string UID: types.UID(crUID), }, Spec: GrafanaDashboardSpec{ - InstanceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "dashboard": "grafana", + GrafanaCommonSpec: GrafanaCommonSpec{ + InstanceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "dashboard": "grafana", + }, }, }, CustomUID: specUID, diff --git a/api/v1beta1/grafanadatasource_types.go b/api/v1beta1/grafanadatasource_types.go index dbdcd5a4e..ebc2b48e5 100644 --- a/api/v1beta1/grafanadatasource_types.go +++ b/api/v1beta1/grafanadatasource_types.go @@ -60,6 +60,8 @@ type GrafanaDatasourceInternal struct { // GrafanaDatasourceSpec defines the desired state of GrafanaDatasource // +kubebuilder:validation:XValidation:rule="((!has(oldSelf.uid) && !has(self.uid)) || (has(oldSelf.uid) && has(self.uid)))", message="spec.uid is immutable" type GrafanaDatasourceSpec struct { + GrafanaCommonSpec `json:",inline"` + // The UID, for the datasource, fallback to the deprecated spec.datasource.uid and metadata.uid // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.uid is immutable" @@ -67,10 +69,6 @@ type GrafanaDatasourceSpec struct { Datasource *GrafanaDatasourceInternal `json:"datasource"` - // selects Grafana instances for import - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - InstanceSelector *metav1.LabelSelector `json:"instanceSelector"` - // plugins // +optional Plugins PluginList `json:"plugins,omitempty"` @@ -79,18 +77,6 @@ type GrafanaDatasourceSpec struct { // +optional // +kubebuilder:validation:MaxItems=99 ValuesFrom []ValueFrom `json:"valuesFrom,omitempty"` - - // how often the datasource is refreshed, defaults to 5m if not set - // +optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=duration - // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" - // +kubebuilder:default="5m" - ResyncPeriod string `json:"resyncPeriod,omitempty"` - - // allow to import this resources from an operator in a different namespace - // +optional - AllowCrossNamespaceImport *bool `json:"allowCrossNamespaceImport,omitempty"` } // GrafanaDatasourceStatus defines the observed state of GrafanaDatasource @@ -129,23 +115,8 @@ type GrafanaDatasourceList struct { Items []GrafanaDatasource `json:"items"` } -func (in *GrafanaDatasource) GetResyncPeriod() time.Duration { - if in.Spec.ResyncPeriod == "" { - in.Spec.ResyncPeriod = DefaultResyncPeriod - return in.GetResyncPeriod() - } - - duration, err := time.ParseDuration(in.Spec.ResyncPeriod) - if err != nil { - in.Spec.ResyncPeriod = DefaultResyncPeriod - return in.GetResyncPeriod() - } - - return duration -} - func (in *GrafanaDatasource) ResyncPeriodHasElapsed() bool { - deadline := in.Status.LastResync.Add(in.GetResyncPeriod()) + deadline := in.Status.LastResync.Add(in.Spec.ResyncPeriod.Duration) return time.Now().After(deadline) } diff --git a/api/v1beta1/grafanadatasource_types_test.go b/api/v1beta1/grafanadatasource_types_test.go index 97daedc04..dd2800adf 100644 --- a/api/v1beta1/grafanadatasource_types_test.go +++ b/api/v1beta1/grafanadatasource_types_test.go @@ -20,9 +20,11 @@ func newDatasource(name string, uid string) *GrafanaDatasource { }, Spec: GrafanaDatasourceSpec{ CustomUID: uid, - InstanceSelector: &v1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "datasource", + GrafanaCommonSpec: GrafanaCommonSpec{ + InstanceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "datasource", + }, }, }, Datasource: &GrafanaDatasourceInternal{ diff --git a/api/v1beta1/grafanafolder_types.go b/api/v1beta1/grafanafolder_types.go index 88b3ac0da..cbde5117f 100644 --- a/api/v1beta1/grafanafolder_types.go +++ b/api/v1beta1/grafanafolder_types.go @@ -32,6 +32,8 @@ import ( // +kubebuilder:validation:XValidation:rule="(has(self.parentFolderUID) && !(has(self.parentFolderRef))) || (has(self.parentFolderRef) && !(has(self.parentFolderUID))) || !(has(self.parentFolderRef) && (has(self.parentFolderUID)))", message="Only one of parentFolderUID or parentFolderRef can be set" // +kubebuilder:validation:XValidation:rule="((!has(oldSelf.uid) && !has(self.uid)) || (has(oldSelf.uid) && has(self.uid)))", message="spec.uid is immutable" type GrafanaFolderSpec struct { + GrafanaCommonSpec `json:",inline"` + // Manually specify the UID the Folder is created with // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.uid is immutable" @@ -45,14 +47,6 @@ type GrafanaFolderSpec struct { // +optional Permissions string `json:"permissions,omitempty"` - // Selects Grafanas for import - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - InstanceSelector *metav1.LabelSelector `json:"instanceSelector"` - - // Enable matching Grafana instances outside the current namespace - // +optional - AllowCrossNamespaceImport *bool `json:"allowCrossNamespaceImport,omitempty"` - // UID of the folder in which the current folder should be created // +optional ParentFolderUID string `json:"parentFolderUID,omitempty"` @@ -60,14 +54,6 @@ type GrafanaFolderSpec struct { // Reference to an existing GrafanaFolder CR in the same namespace // +optional ParentFolderRef string `json:"parentFolderRef,omitempty"` - - // How often the folder is synced, defaults to 5m if not set - // +optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=duration - // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" - // +kubebuilder:default="5m" - ResyncPeriod string `json:"resyncPeriod,omitempty"` } // GrafanaFolderStatus defines the observed state of GrafanaFolder @@ -180,22 +166,7 @@ func (in *GrafanaFolder) GetTitle() string { return in.Name } -func (in *GrafanaFolder) GetResyncPeriod() time.Duration { - if in.Spec.ResyncPeriod == "" { - in.Spec.ResyncPeriod = DefaultResyncPeriod - return in.GetResyncPeriod() - } - - duration, err := time.ParseDuration(in.Spec.ResyncPeriod) - if err != nil { - in.Spec.ResyncPeriod = DefaultResyncPeriod - return in.GetResyncPeriod() - } - - return duration -} - func (in *GrafanaFolder) ResyncPeriodHasElapsed() bool { - deadline := in.Status.LastResync.Add(in.GetResyncPeriod()) + deadline := in.Status.LastResync.Add(in.Spec.ResyncPeriod.Duration) return time.Now().After(deadline) } diff --git a/api/v1beta1/grafananotificationpolicy_types.go b/api/v1beta1/grafananotificationpolicy_types.go index c2efef410..109a3dccc 100644 --- a/api/v1beta1/grafananotificationpolicy_types.go +++ b/api/v1beta1/grafananotificationpolicy_types.go @@ -25,16 +25,7 @@ import ( // GrafanaNotificationPolicySpec defines the desired state of GrafanaNotificationPolicy type GrafanaNotificationPolicySpec struct { - // +optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=duration - // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" - // +kubebuilder:default="10m" - ResyncPeriod metav1.Duration `json:"resyncPeriod,omitempty"` - - // selects Grafanas for import - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - InstanceSelector *metav1.LabelSelector `json:"instanceSelector"` + GrafanaCommonSpec `json:",inline"` // Routes for alerts to match against Route *Route `json:"route"` diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index d8c3f044b..c4b836053 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -477,12 +477,7 @@ func (in *GrafanaAlertRuleGroupList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaAlertRuleGroupSpec) DeepCopyInto(out *GrafanaAlertRuleGroupSpec) { *out = *in - out.ResyncPeriod = in.ResyncPeriod - if in.InstanceSelector != nil { - in, out := &in.InstanceSelector, &out.InstanceSelector - *out = new(metav1.LabelSelector) - (*in).DeepCopyInto(*out) - } + in.GrafanaCommonSpec.DeepCopyInto(&out.GrafanaCommonSpec) if in.Rules != nil { in, out := &in.Rules, &out.Rules *out = make([]AlertRule, len(*in)) @@ -491,11 +486,6 @@ func (in *GrafanaAlertRuleGroupSpec) DeepCopyInto(out *GrafanaAlertRuleGroupSpec } } out.Interval = in.Interval - if in.AllowCrossNamespaceImport != nil { - in, out := &in.AllowCrossNamespaceImport, &out.AllowCrossNamespaceImport - *out = new(bool) - **out = **in - } if in.Editable != nil { in, out := &in.Editable, &out.Editable *out = new(bool) @@ -585,6 +575,55 @@ func (in *GrafanaComDashboardReference) DeepCopy() *GrafanaComDashboardReference return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrafanaCommonSpec) DeepCopyInto(out *GrafanaCommonSpec) { + *out = *in + out.ResyncPeriod = in.ResyncPeriod + if in.InstanceSelector != nil { + in, out := &in.InstanceSelector, &out.InstanceSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.AllowCrossNamespaceImport != nil { + in, out := &in.AllowCrossNamespaceImport, &out.AllowCrossNamespaceImport + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaCommonSpec. +func (in *GrafanaCommonSpec) DeepCopy() *GrafanaCommonSpec { + if in == nil { + return nil + } + out := new(GrafanaCommonSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrafanaCommonStatus) DeepCopyInto(out *GrafanaCommonStatus) { + *out = *in + in.LastResync.DeepCopyInto(&out.LastResync) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaCommonStatus. +func (in *GrafanaCommonStatus) DeepCopy() *GrafanaCommonStatus { + if in == nil { + return nil + } + out := new(GrafanaCommonStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaContactPoint) DeepCopyInto(out *GrafanaContactPoint) { *out = *in @@ -647,12 +686,7 @@ func (in *GrafanaContactPointList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaContactPointSpec) DeepCopyInto(out *GrafanaContactPointSpec) { *out = *in - out.ResyncPeriod = in.ResyncPeriod - if in.InstanceSelector != nil { - in, out := &in.InstanceSelector, &out.InstanceSelector - *out = new(metav1.LabelSelector) - (*in).DeepCopyInto(*out) - } + in.GrafanaCommonSpec.DeepCopyInto(&out.GrafanaCommonSpec) if in.Settings != nil { in, out := &in.Settings, &out.Settings *out = new(apiextensionsv1.JSON) @@ -665,11 +699,6 @@ func (in *GrafanaContactPointSpec) DeepCopyInto(out *GrafanaContactPointSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.AllowCrossNamespaceImport != nil { - in, out := &in.AllowCrossNamespaceImport, &out.AllowCrossNamespaceImport - *out = new(bool) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaContactPointSpec. @@ -822,6 +851,7 @@ func (in *GrafanaDashboardList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaDashboardSpec) DeepCopyInto(out *GrafanaDashboardSpec) { *out = *in + in.GrafanaCommonSpec.DeepCopyInto(&out.GrafanaCommonSpec) if in.GzipJson != nil { in, out := &in.GzipJson, &out.GzipJson *out = make([]byte, len(*in)) @@ -847,11 +877,6 @@ func (in *GrafanaDashboardSpec) DeepCopyInto(out *GrafanaDashboardSpec) { *out = new(v1.ConfigMapKeySelector) (*in).DeepCopyInto(*out) } - if in.InstanceSelector != nil { - in, out := &in.InstanceSelector, &out.InstanceSelector - *out = new(metav1.LabelSelector) - (*in).DeepCopyInto(*out) - } if in.Plugins != nil { in, out := &in.Plugins, &out.Plugins *out = make(PluginList, len(*in)) @@ -863,11 +888,6 @@ func (in *GrafanaDashboardSpec) DeepCopyInto(out *GrafanaDashboardSpec) { *out = make([]GrafanaDashboardDatasource, len(*in)) copy(*out, *in) } - if in.AllowCrossNamespaceImport != nil { - in, out := &in.AllowCrossNamespaceImport, &out.AllowCrossNamespaceImport - *out = new(bool) - **out = **in - } if in.Envs != nil { in, out := &in.Envs, &out.Envs *out = make([]GrafanaDashboardEnv, len(*in)) @@ -1075,16 +1095,12 @@ func (in *GrafanaDatasourceList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaDatasourceSpec) DeepCopyInto(out *GrafanaDatasourceSpec) { *out = *in + in.GrafanaCommonSpec.DeepCopyInto(&out.GrafanaCommonSpec) if in.Datasource != nil { in, out := &in.Datasource, &out.Datasource *out = new(GrafanaDatasourceInternal) (*in).DeepCopyInto(*out) } - if in.InstanceSelector != nil { - in, out := &in.InstanceSelector, &out.InstanceSelector - *out = new(metav1.LabelSelector) - (*in).DeepCopyInto(*out) - } if in.Plugins != nil { in, out := &in.Plugins, &out.Plugins *out = make(PluginList, len(*in)) @@ -1097,11 +1113,6 @@ func (in *GrafanaDatasourceSpec) DeepCopyInto(out *GrafanaDatasourceSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.AllowCrossNamespaceImport != nil { - in, out := &in.AllowCrossNamespaceImport, &out.AllowCrossNamespaceImport - *out = new(bool) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaDatasourceSpec. @@ -1192,16 +1203,7 @@ func (in *GrafanaFolderList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaFolderSpec) DeepCopyInto(out *GrafanaFolderSpec) { *out = *in - if in.InstanceSelector != nil { - in, out := &in.InstanceSelector, &out.InstanceSelector - *out = new(metav1.LabelSelector) - (*in).DeepCopyInto(*out) - } - if in.AllowCrossNamespaceImport != nil { - in, out := &in.AllowCrossNamespaceImport, &out.AllowCrossNamespaceImport - *out = new(bool) - **out = **in - } + in.GrafanaCommonSpec.DeepCopyInto(&out.GrafanaCommonSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaFolderSpec. @@ -1331,12 +1333,7 @@ func (in *GrafanaNotificationPolicyList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaNotificationPolicySpec) DeepCopyInto(out *GrafanaNotificationPolicySpec) { *out = *in - out.ResyncPeriod = in.ResyncPeriod - if in.InstanceSelector != nil { - in, out := &in.InstanceSelector, &out.InstanceSelector - *out = new(metav1.LabelSelector) - (*in).DeepCopyInto(*out) - } + in.GrafanaCommonSpec.DeepCopyInto(&out.GrafanaCommonSpec) if in.Route != nil { in, out := &in.Route, &out.Route *out = new(Route) diff --git a/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml b/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml index 29f25d77d..a1f67032d 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -43,6 +43,8 @@ spec: description: GrafanaAlertRuleGroupSpec defines the desired state of GrafanaAlertRuleGroup properties: allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean editable: description: Whether to enable or disable editing of the alert rule @@ -60,7 +62,7 @@ spec: Overrides the FolderSelector type: string instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -106,7 +108,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf interval: format: duration @@ -117,7 +119,9 @@ spec: name will be used. type: string resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml b/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml index 056320b0e..0119938cd 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml @@ -43,11 +43,13 @@ spec: description: GrafanaContactPointSpec defines the desired state of GrafanaContactPoint properties: allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean disableResolveMessage: type: boolean instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -93,12 +95,14 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf name: type: string resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/config/crd/bases/grafana.integreatly.org_grafanadashboards.yaml b/config/crd/bases/grafana.integreatly.org_grafanadashboards.yaml index 0c4113303..29d8349f2 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanadashboards.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanadashboards.yaml @@ -53,8 +53,8 @@ spec: description: GrafanaDashboardSpec defines the desired state of GrafanaDashboard properties: allowCrossNamespaceImport: - description: allow to import this resources from an operator in a - different namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean configMapRef: description: dashboard from configmap @@ -238,7 +238,7 @@ spec: format: byte type: string instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -284,7 +284,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf json: description: dashboard json @@ -322,9 +322,9 @@ spec: type: object type: array resyncPeriod: - default: 5m - description: how often the dashboard is refreshed, defaults to 5m - if not set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml b/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml index 28596b2a5..86de49754 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml @@ -53,8 +53,8 @@ spec: description: GrafanaDatasourceSpec defines the desired state of GrafanaDatasource properties: allowCrossNamespaceImport: - description: allow to import this resources from an operator in a - different namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean datasource: properties: @@ -95,7 +95,7 @@ spec: type: string type: object instanceSelector: - description: selects Grafana instances for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -141,7 +141,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf plugins: description: plugins @@ -157,9 +157,9 @@ spec: type: object type: array resyncPeriod: - default: 5m - description: how often the datasource is refreshed, defaults to 5m - if not set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/config/crd/bases/grafana.integreatly.org_grafanafolders.yaml b/config/crd/bases/grafana.integreatly.org_grafanafolders.yaml index 080826c69..39d98e19d 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanafolders.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanafolders.yaml @@ -49,11 +49,11 @@ spec: description: GrafanaFolderSpec defines the desired state of GrafanaFolder properties: allowCrossNamespaceImport: - description: Enable matching Grafana instances outside the current - namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean instanceSelector: - description: Selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -99,7 +99,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf parentFolderRef: description: Reference to an existing GrafanaFolder CR in the same @@ -114,9 +114,9 @@ spec: from Grafana type: string resyncPeriod: - default: 5m - description: How often the folder is synced, defaults to 5m if not - set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml b/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml index c83cdc85b..a0243f19b 100644 --- a/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml @@ -43,6 +43,10 @@ spec: description: GrafanaNotificationPolicySpec defines the desired state of GrafanaNotificationPolicy properties: + allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace + type: boolean editable: description: Whether to enable or disable editing of the notification policy in Grafana UI @@ -51,7 +55,7 @@ spec: - message: Value is immutable rule: self == oldSelf instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -97,10 +101,12 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/controllers/dashboard_controller.go b/controllers/dashboard_controller.go index 210380b79..fb85ef0d6 100644 --- a/controllers/dashboard_controller.go +++ b/controllers/dashboard_controller.go @@ -289,7 +289,7 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req } cr.Status.Hash = hash cr.Status.UID = uid - return ctrl.Result{RequeueAfter: cr.GetResyncPeriod()}, r.Client.Status().Update(ctx, cr) + return ctrl.Result{RequeueAfter: cr.Spec.ResyncPeriod.Duration}, r.Client.Status().Update(ctx, cr) } return ctrl.Result{RequeueAfter: RequeueDelay}, nil diff --git a/controllers/dashboard_controller_test.go b/controllers/dashboard_controller_test.go index f14f919d3..a93495a99 100644 --- a/controllers/dashboard_controller_test.go +++ b/controllers/dashboard_controller_test.go @@ -42,9 +42,11 @@ func TestGetDashboardsToDelete(t *testing.T) { Namespace: "grafana-operator-system", }, Spec: v1beta1.GrafanaDashboardSpec{ - InstanceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "dashboard": "external", + GrafanaCommonSpec: v1beta1.GrafanaCommonSpec{ + InstanceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "dashboard": "external", + }, }, }, }, @@ -56,9 +58,11 @@ func TestGetDashboardsToDelete(t *testing.T) { Namespace: "grafana-operator-system", }, Spec: v1beta1.GrafanaDashboardSpec{ - InstanceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "dashboard": "internal", + GrafanaCommonSpec: v1beta1.GrafanaCommonSpec{ + InstanceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "dashboard": "external", + }, }, }, }, diff --git a/controllers/datasource_controller.go b/controllers/datasource_controller.go index c99a3cc66..951d27e5b 100644 --- a/controllers/datasource_controller.go +++ b/controllers/datasource_controller.go @@ -262,7 +262,7 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re cr.Status.LastResync = metav1.Time{Time: time.Now()} } cr.Status.UID = cr.CustomUIDOrUID() - return ctrl.Result{RequeueAfter: cr.GetResyncPeriod()}, r.Client.Status().Update(ctx, cr) + 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) diff --git a/controllers/grafanafolder_controller.go b/controllers/grafanafolder_controller.go index 139b44cde..0f3ddb034 100644 --- a/controllers/grafanafolder_controller.go +++ b/controllers/grafanafolder_controller.go @@ -238,7 +238,7 @@ func (r *GrafanaFolderReconciler) Reconcile(ctx context.Context, req ctrl.Reques if folder.ResyncPeriodHasElapsed() { folder.Status.LastResync = metav1.Time{Time: time.Now()} } - return ctrl.Result{RequeueAfter: folder.GetResyncPeriod()}, nil + return ctrl.Result{RequeueAfter: folder.Spec.ResyncPeriod.Duration}, nil } // SetupWithManager sets up the controller with the Manager. diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml index 29f25d77d..a1f67032d 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -43,6 +43,8 @@ spec: description: GrafanaAlertRuleGroupSpec defines the desired state of GrafanaAlertRuleGroup properties: allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean editable: description: Whether to enable or disable editing of the alert rule @@ -60,7 +62,7 @@ spec: Overrides the FolderSelector type: string instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -106,7 +108,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf interval: format: duration @@ -117,7 +119,9 @@ spec: name will be used. type: string resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml index 056320b0e..0119938cd 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml @@ -43,11 +43,13 @@ spec: description: GrafanaContactPointSpec defines the desired state of GrafanaContactPoint properties: allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean disableResolveMessage: type: boolean instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -93,12 +95,14 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf name: type: string resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadashboards.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadashboards.yaml index 0c4113303..29d8349f2 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadashboards.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadashboards.yaml @@ -53,8 +53,8 @@ spec: description: GrafanaDashboardSpec defines the desired state of GrafanaDashboard properties: allowCrossNamespaceImport: - description: allow to import this resources from an operator in a - different namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean configMapRef: description: dashboard from configmap @@ -238,7 +238,7 @@ spec: format: byte type: string instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -284,7 +284,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf json: description: dashboard json @@ -322,9 +322,9 @@ spec: type: object type: array resyncPeriod: - default: 5m - description: how often the dashboard is refreshed, defaults to 5m - if not set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string 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 28596b2a5..86de49754 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml @@ -53,8 +53,8 @@ spec: description: GrafanaDatasourceSpec defines the desired state of GrafanaDatasource properties: allowCrossNamespaceImport: - description: allow to import this resources from an operator in a - different namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean datasource: properties: @@ -95,7 +95,7 @@ spec: type: string type: object instanceSelector: - description: selects Grafana instances for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -141,7 +141,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf plugins: description: plugins @@ -157,9 +157,9 @@ spec: type: object type: array resyncPeriod: - default: 5m - description: how often the datasource is refreshed, defaults to 5m - if not set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanafolders.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanafolders.yaml index 080826c69..39d98e19d 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanafolders.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanafolders.yaml @@ -49,11 +49,11 @@ spec: description: GrafanaFolderSpec defines the desired state of GrafanaFolder properties: allowCrossNamespaceImport: - description: Enable matching Grafana instances outside the current - namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean instanceSelector: - description: Selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -99,7 +99,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf parentFolderRef: description: Reference to an existing GrafanaFolder CR in the same @@ -114,9 +114,9 @@ spec: from Grafana type: string resyncPeriod: - default: 5m - description: How often the folder is synced, defaults to 5m if not - set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml index c83cdc85b..a0243f19b 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml @@ -43,6 +43,10 @@ spec: description: GrafanaNotificationPolicySpec defines the desired state of GrafanaNotificationPolicy properties: + allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace + type: boolean editable: description: Whether to enable or disable editing of the notification policy in Grafana UI @@ -51,7 +55,7 @@ spec: - message: Value is immutable rule: self == oldSelf instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -97,10 +101,12 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/deploy/kustomize/base/crds.yaml b/deploy/kustomize/base/crds.yaml index 349e9b450..cecf5939f 100644 --- a/deploy/kustomize/base/crds.yaml +++ b/deploy/kustomize/base/crds.yaml @@ -42,6 +42,8 @@ spec: description: GrafanaAlertRuleGroupSpec defines the desired state of GrafanaAlertRuleGroup properties: allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean editable: description: Whether to enable or disable editing of the alert rule @@ -59,7 +61,7 @@ spec: Overrides the FolderSelector type: string instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -105,7 +107,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf interval: format: duration @@ -116,7 +118,9 @@ spec: name will be used. type: string resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string @@ -351,11 +355,13 @@ spec: description: GrafanaContactPointSpec defines the desired state of GrafanaContactPoint properties: allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean disableResolveMessage: type: boolean instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -401,12 +407,14 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf name: type: string resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string @@ -643,8 +651,8 @@ spec: description: GrafanaDashboardSpec defines the desired state of GrafanaDashboard properties: allowCrossNamespaceImport: - description: allow to import this resources from an operator in a - different namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean configMapRef: description: dashboard from configmap @@ -828,7 +836,7 @@ spec: format: byte type: string instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -874,7 +882,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf json: description: dashboard json @@ -912,9 +920,9 @@ spec: type: object type: array resyncPeriod: - default: 5m - description: how often the dashboard is refreshed, defaults to 5m - if not set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string @@ -1138,8 +1146,8 @@ spec: description: GrafanaDatasourceSpec defines the desired state of GrafanaDatasource properties: allowCrossNamespaceImport: - description: allow to import this resources from an operator in a - different namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean datasource: properties: @@ -1180,7 +1188,7 @@ spec: type: string type: object instanceSelector: - description: selects Grafana instances for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -1226,7 +1234,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf plugins: description: plugins @@ -1242,9 +1250,9 @@ spec: type: object type: array resyncPeriod: - default: 5m - description: how often the datasource is refreshed, defaults to 5m - if not set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string @@ -1403,11 +1411,11 @@ spec: description: GrafanaFolderSpec defines the desired state of GrafanaFolder properties: allowCrossNamespaceImport: - description: Enable matching Grafana instances outside the current - namespace + description: Allow the Operator to match this resource with Grafanas + outside the current namespace type: boolean instanceSelector: - description: Selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -1453,7 +1461,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf parentFolderRef: description: Reference to an existing GrafanaFolder CR in the same @@ -1468,9 +1476,9 @@ spec: from Grafana type: string resyncPeriod: - default: 5m - description: How often the folder is synced, defaults to 5m if not - set + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string @@ -1617,6 +1625,10 @@ spec: description: GrafanaNotificationPolicySpec defines the desired state of GrafanaNotificationPolicy properties: + allowCrossNamespaceImport: + description: Allow the Operator to match this resource with Grafanas + outside the current namespace + type: boolean editable: description: Whether to enable or disable editing of the notification policy in Grafana UI @@ -1625,7 +1637,7 @@ spec: - message: Value is immutable rule: self == oldSelf instanceSelector: - description: selects Grafanas for import + description: Selects Grafana instances for import properties: matchExpressions: description: matchExpressions is a list of label selector requirements. @@ -1671,10 +1683,12 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - message: Value is immutable + - message: spec.instanceSelector is immutable rule: self == oldSelf resyncPeriod: - default: 10m + default: 10m0s + description: How often the resource is synced, defaults to 10m0s if + not set format: duration pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string diff --git a/docs/docs/api.md b/docs/docs/api.md index afecad452..29f7aec4e 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -104,9 +104,9 @@ GrafanaAlertRuleGroupSpec defines the desired state of GrafanaAlertRuleGroup instanceSelector object - selects Grafanas for import
+ Selects Grafana instances for import

- Validations:
  • self == oldSelf: Value is immutable
  • + Validations:
  • self == oldSelf: spec.instanceSelector is immutable
  • true @@ -129,7 +129,7 @@ GrafanaAlertRuleGroupSpec defines the desired state of GrafanaAlertRuleGroup allowCrossNamespaceImport boolean -
    + Allow the Operator to match this resource with Grafanas outside the current namespace
    false @@ -167,10 +167,10 @@ Overrides the FolderSelector
    resyncPeriod string -
    + How often the resource is synced, defaults to 10m0s if not set

    Format: duration
    - Default: 10m
    + Default: 10m0s
    false @@ -182,7 +182,7 @@ Overrides the FolderSelector
    -selects Grafanas for import +Selects Grafana instances for import @@ -697,9 +697,9 @@ GrafanaContactPointSpec defines the desired state of GrafanaContactPoint @@ -720,7 +720,7 @@ GrafanaContactPointSpec defines the desired state of GrafanaContactPoint @@ -734,10 +734,10 @@ GrafanaContactPointSpec defines the desired state of GrafanaContactPoint @@ -774,7 +774,7 @@ GrafanaContactPointSpec defines the desired state of GrafanaContactPoint -selects Grafanas for import +Selects Grafana instances for import
    instanceSelector object - selects Grafanas for import
    + Selects Grafana instances for import

    - Validations:
  • self == oldSelf: Value is immutable
  • + Validations:
  • self == oldSelf: spec.instanceSelector is immutable
  • true
    allowCrossNamespaceImport boolean -
    + Allow the Operator to match this resource with Grafanas outside the current namespace
    false
    resyncPeriod string -
    + How often the resource is synced, defaults to 10m0s if not set

    Format: duration
    - Default: 10m
    + Default: 10m0s
    false
    @@ -1195,16 +1195,16 @@ GrafanaDashboardSpec defines the desired state of GrafanaDashboard @@ -1311,10 +1311,10 @@ GrafanaDashboardSpec defines the desired state of GrafanaDashboard @@ -1349,7 +1349,7 @@ GrafanaDashboardSpec defines the desired state of GrafanaDashboard -selects Grafanas for import +Selects Grafana instances for import
    instanceSelector object - selects Grafanas for import
    + Selects Grafana instances for import

    - Validations:
  • self == oldSelf: Value is immutable
  • + Validations:
  • self == oldSelf: spec.instanceSelector is immutable
  • true
    allowCrossNamespaceImport boolean - allow to import this resources from an operator in a different namespace
    + Allow the Operator to match this resource with Grafanas outside the current namespace
    false
    resyncPeriod string - how often the dashboard is refreshed, defaults to 5m if not set
    + How often the resource is synced, defaults to 10m0s if not set

    Format: duration
    - Default: 5m
    + Default: 10m0s
    false
    @@ -2311,16 +2311,16 @@ GrafanaDatasourceSpec defines the desired state of GrafanaDatasource @@ -2334,10 +2334,10 @@ GrafanaDatasourceSpec defines the desired state of GrafanaDatasource @@ -2485,7 +2485,7 @@ GrafanaDatasourceSpec defines the desired state of GrafanaDatasource -selects Grafana instances for import +Selects Grafana instances for import
    instanceSelector object - selects Grafana instances for import
    + Selects Grafana instances for import

    - Validations:
  • self == oldSelf: Value is immutable
  • + Validations:
  • self == oldSelf: spec.instanceSelector is immutable
  • true
    allowCrossNamespaceImport boolean - allow to import this resources from an operator in a different namespace
    + Allow the Operator to match this resource with Grafanas outside the current namespace
    false
    resyncPeriod string - how often the datasource is refreshed, defaults to 5m if not set
    + How often the resource is synced, defaults to 10m0s if not set

    Format: duration
    - Default: 5m
    + Default: 10m0s
    false
    @@ -2892,16 +2892,16 @@ GrafanaFolderSpec defines the desired state of GrafanaFolder @@ -2929,10 +2929,10 @@ GrafanaFolderSpec defines the desired state of GrafanaFolder @@ -2960,7 +2960,7 @@ GrafanaFolderSpec defines the desired state of GrafanaFolder -Selects Grafanas for import +Selects Grafana instances for import
    instanceSelector object - Selects Grafanas for import
    + Selects Grafana instances for import

    - Validations:
  • self == oldSelf: Value is immutable
  • + Validations:
  • self == oldSelf: spec.instanceSelector is immutable
  • true
    allowCrossNamespaceImport boolean - Enable matching Grafana instances outside the current namespace
    + Allow the Operator to match this resource with Grafanas outside the current namespace
    false
    resyncPeriod string - How often the folder is synced, defaults to 5m if not set
    + How often the resource is synced, defaults to 10m0s if not set

    Format: duration
    - Default: 5m
    + Default: 10m0s
    false
    @@ -3238,9 +3238,9 @@ GrafanaNotificationPolicySpec defines the desired state of GrafanaNotificationPo @@ -3250,6 +3250,13 @@ GrafanaNotificationPolicySpec defines the desired state of GrafanaNotificationPo Routes for alerts to match against
    + + + + + @@ -3263,10 +3270,10 @@ GrafanaNotificationPolicySpec defines the desired state of GrafanaNotificationPo @@ -3278,7 +3285,7 @@ GrafanaNotificationPolicySpec defines the desired state of GrafanaNotificationPo -selects Grafanas for import +Selects Grafana instances for import
    instanceSelector object - selects Grafanas for import
    + Selects Grafana instances for import

    - Validations:
  • self == oldSelf: Value is immutable
  • + Validations:
  • self == oldSelf: spec.instanceSelector is immutable
  • true
    true
    allowCrossNamespaceImportboolean + Allow the Operator to match this resource with Grafanas outside the current namespace
    +
    false
    editable booleanresyncPeriod string -
    + How often the resource is synced, defaults to 10m0s if not set

    Format: duration
    - Default: 10m
    + Default: 10m0s
    false
    diff --git a/go.mod b/go.mod index 551e24699..3d761430d 100644 --- a/go.mod +++ b/go.mod @@ -74,7 +74,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nxadm/tail v1.4.8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect @@ -91,7 +90,6 @@ require ( gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.31.2 From e64a50e1fa2b4429bfb536cff04cc5686f5f1ab4 Mon Sep 17 00:00:00 2001 From: ste Date: Sat, 16 Nov 2024 22:59:26 +0100 Subject: [PATCH 02/10] feat: Define GrafanaCommonStatus struct, add it to resources that are ready --- api/v1beta1/common.go | 2 ++ api/v1beta1/grafanaalertrulegroup_types.go | 2 ++ api/v1beta1/grafanacontactpoint_types.go | 4 ++-- api/v1beta1/grafanadashboard_types.go | 12 +++++------ api/v1beta1/grafanafolder_types.go | 9 +++------ .../grafananotificationpolicy_types.go | 2 ++ api/v1beta1/zz_generated.deepcopy.go | 18 ++--------------- ...ntegreatly.org_grafanaalertrulegroups.yaml | 1 + ....integreatly.org_grafanacontactpoints.yaml | 4 +--- ...ana.integreatly.org_grafanadashboards.yaml | 4 +++- ...rafana.integreatly.org_grafanafolders.yaml | 7 +++---- ...eatly.org_grafananotificationpolicies.yaml | 1 + ...ntegreatly.org_grafanaalertrulegroups.yaml | 1 + ....integreatly.org_grafanacontactpoints.yaml | 4 +--- ...ana.integreatly.org_grafanadashboards.yaml | 4 +++- ...rafana.integreatly.org_grafanafolders.yaml | 7 +++---- ...eatly.org_grafananotificationpolicies.yaml | 1 + deploy/kustomize/base/crds.yaml | 17 ++++++++-------- docs/docs/api.md | 20 +++++++++---------- 19 files changed, 54 insertions(+), 66 deletions(-) diff --git a/api/v1beta1/common.go b/api/v1beta1/common.go index 117a3b523..5b3ba6514 100644 --- a/api/v1beta1/common.go +++ b/api/v1beta1/common.go @@ -5,6 +5,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// WARN Run `make` on all file changes + type ValueFrom struct { TargetPath string `json:"targetPath"` ValueFrom ValueFromSource `json:"valueFrom"` diff --git a/api/v1beta1/grafanaalertrulegroup_types.go b/api/v1beta1/grafanaalertrulegroup_types.go index b407d5a7f..4d9bf8972 100644 --- a/api/v1beta1/grafanaalertrulegroup_types.go +++ b/api/v1beta1/grafanaalertrulegroup_types.go @@ -116,8 +116,10 @@ type AlertQuery struct { RelativeTimeRange *models.RelativeTimeRange `json:"relativeTimeRange,omitempty"` } +// TODO Implement GrafanaCommonStatus // GrafanaAlertRuleGroupStatus defines the observed state of GrafanaAlertRuleGroup type GrafanaAlertRuleGroupStatus struct { + // Results when synchonizing resource with Grafana instances Conditions []metav1.Condition `json:"conditions"` } diff --git a/api/v1beta1/grafanacontactpoint_types.go b/api/v1beta1/grafanacontactpoint_types.go index 74c6dfed9..ab1e671e9 100644 --- a/api/v1beta1/grafanacontactpoint_types.go +++ b/api/v1beta1/grafanacontactpoint_types.go @@ -49,10 +49,10 @@ type GrafanaContactPointSpec struct { Type string `json:"type,omitempty"` } +// TODO Implement GrafanaCommonStatus // GrafanaContactPointStatus defines the observed state of GrafanaContactPoint type GrafanaContactPointStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file + // Results when synchonizing resource with Grafana instances Conditions []metav1.Condition `json:"conditions"` } diff --git a/api/v1beta1/grafanadashboard_types.go b/api/v1beta1/grafanadashboard_types.go index d9519176b..c9fde6fbd 100644 --- a/api/v1beta1/grafanadashboard_types.go +++ b/api/v1beta1/grafanadashboard_types.go @@ -166,17 +166,15 @@ type GrafanaComDashboardReference struct { // GrafanaDashboardStatus defines the observed state of GrafanaDashboard type GrafanaDashboardStatus struct { + GrafanaCommonStatus `json:",inline"` + ContentCache []byte `json:"contentCache,omitempty"` ContentTimestamp metav1.Time `json:"contentTimestamp,omitempty"` ContentUrl string `json:"contentUrl,omitempty"` - Hash string `json:"hash,omitempty"` + // TODO(V1) Remove // The dashboard instanceSelector can't find matching grafana instances - NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` - // Last time the dashboard was resynced - LastResync metav1.Time `json:"lastResync,omitempty"` - UID string `json:"uid,omitempty"` - - Conditions []metav1.Condition `json:"conditions,omitempty"` + NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` + UID string `json:"uid,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1beta1/grafanafolder_types.go b/api/v1beta1/grafanafolder_types.go index cbde5117f..4f128546b 100644 --- a/api/v1beta1/grafanafolder_types.go +++ b/api/v1beta1/grafanafolder_types.go @@ -58,14 +58,11 @@ type GrafanaFolderSpec struct { // GrafanaFolderStatus defines the observed state of GrafanaFolder type GrafanaFolderStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Hash string `json:"hash,omitempty"` + GrafanaCommonStatus `json:",inline"` + + // TODO(V1) Remove // The folder instanceSelector can't find matching grafana instances NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` - // Last time the folder was resynced - LastResync metav1.Time `json:"lastResync,omitempty"` - Conditions []metav1.Condition `json:"conditions,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1beta1/grafananotificationpolicy_types.go b/api/v1beta1/grafananotificationpolicy_types.go index 109a3dccc..d633b3853 100644 --- a/api/v1beta1/grafananotificationpolicy_types.go +++ b/api/v1beta1/grafananotificationpolicy_types.go @@ -125,8 +125,10 @@ func (r *Route) ToModelRoute() *models.Route { return out } +// TODO Implement GrafanaCommonStatus // GrafanaNotificationPolicyStatus defines the observed state of GrafanaNotificationPolicy type GrafanaNotificationPolicyStatus struct { + // Results when synchonizing resource with Grafana instances Conditions []metav1.Condition `json:"conditions"` } diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index c4b836053..51cb91c35 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -917,20 +917,13 @@ func (in *GrafanaDashboardSpec) DeepCopy() *GrafanaDashboardSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaDashboardStatus) DeepCopyInto(out *GrafanaDashboardStatus) { *out = *in + in.GrafanaCommonStatus.DeepCopyInto(&out.GrafanaCommonStatus) if in.ContentCache != nil { in, out := &in.ContentCache, &out.ContentCache *out = make([]byte, len(*in)) copy(*out, *in) } in.ContentTimestamp.DeepCopyInto(&out.ContentTimestamp) - in.LastResync.DeepCopyInto(&out.LastResync) - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaDashboardStatus. @@ -1219,14 +1212,7 @@ func (in *GrafanaFolderSpec) DeepCopy() *GrafanaFolderSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaFolderStatus) DeepCopyInto(out *GrafanaFolderStatus) { *out = *in - in.LastResync.DeepCopyInto(&out.LastResync) - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } + in.GrafanaCommonStatus.DeepCopyInto(&out.GrafanaCommonStatus) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaFolderStatus. diff --git a/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml b/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml index a1f67032d..e26aa72d6 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -248,6 +248,7 @@ spec: GrafanaAlertRuleGroup properties: conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. diff --git a/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml b/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml index 0119938cd..ba6710ee0 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml @@ -218,9 +218,7 @@ spec: description: GrafanaContactPointStatus defines the observed state of GrafanaContactPoint properties: conditions: - description: |- - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - Important: Run "make" to regenerate code after modifying this file + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. diff --git a/config/crd/bases/grafana.integreatly.org_grafanadashboards.yaml b/config/crd/bases/grafana.integreatly.org_grafanadashboards.yaml index 29d8349f2..b88dae47d 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanadashboards.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanadashboards.yaml @@ -416,6 +416,7 @@ spec: instances type: boolean conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -480,9 +481,10 @@ spec: contentUrl: type: string hash: + description: Detect resource changes type: string lastResync: - description: Last time the dashboard was resynced + description: Last time the resource was synchronized format: date-time type: string uid: diff --git a/config/crd/bases/grafana.integreatly.org_grafanafolders.yaml b/config/crd/bases/grafana.integreatly.org_grafanafolders.yaml index 39d98e19d..39ad8a549 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanafolders.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanafolders.yaml @@ -148,6 +148,7 @@ spec: instances type: boolean conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -204,12 +205,10 @@ spec: type: object type: array hash: - description: |- - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - Important: Run "make" to regenerate code after modifying this file + description: Detect resource changes type: string lastResync: - description: Last time the folder was resynced + description: Last time the resource was synchronized format: date-time type: string type: object diff --git a/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml b/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml index a0243f19b..6477e5763 100644 --- a/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml @@ -191,6 +191,7 @@ spec: of GrafanaNotificationPolicy properties: conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml index a1f67032d..e26aa72d6 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -248,6 +248,7 @@ spec: GrafanaAlertRuleGroup properties: conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml index 0119938cd..ba6710ee0 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml @@ -218,9 +218,7 @@ spec: description: GrafanaContactPointStatus defines the observed state of GrafanaContactPoint properties: conditions: - description: |- - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - Important: Run "make" to regenerate code after modifying this file + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadashboards.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadashboards.yaml index 29d8349f2..b88dae47d 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadashboards.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadashboards.yaml @@ -416,6 +416,7 @@ spec: instances type: boolean conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -480,9 +481,10 @@ spec: contentUrl: type: string hash: + description: Detect resource changes type: string lastResync: - description: Last time the dashboard was resynced + description: Last time the resource was synchronized format: date-time type: string uid: diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanafolders.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanafolders.yaml index 39d98e19d..39ad8a549 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanafolders.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanafolders.yaml @@ -148,6 +148,7 @@ spec: instances type: boolean conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -204,12 +205,10 @@ spec: type: object type: array hash: - description: |- - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - Important: Run "make" to regenerate code after modifying this file + description: Detect resource changes type: string lastResync: - description: Last time the folder was resynced + description: Last time the resource was synchronized format: date-time type: string type: object diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml index a0243f19b..6477e5763 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml @@ -191,6 +191,7 @@ spec: of GrafanaNotificationPolicy properties: conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. diff --git a/deploy/kustomize/base/crds.yaml b/deploy/kustomize/base/crds.yaml index cecf5939f..7708f44dc 100644 --- a/deploy/kustomize/base/crds.yaml +++ b/deploy/kustomize/base/crds.yaml @@ -247,6 +247,7 @@ spec: GrafanaAlertRuleGroup properties: conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -530,9 +531,7 @@ spec: description: GrafanaContactPointStatus defines the observed state of GrafanaContactPoint properties: conditions: - description: |- - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - Important: Run "make" to regenerate code after modifying this file + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -1014,6 +1013,7 @@ spec: instances type: boolean conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -1078,9 +1078,10 @@ spec: contentUrl: type: string hash: + description: Detect resource changes type: string lastResync: - description: Last time the dashboard was resynced + description: Last time the resource was synchronized format: date-time type: string uid: @@ -1510,6 +1511,7 @@ spec: instances type: boolean conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -1566,12 +1568,10 @@ spec: type: object type: array hash: - description: |- - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - Important: Run "make" to regenerate code after modifying this file + description: Detect resource changes type: string lastResync: - description: Last time the folder was resynced + description: Last time the resource was synchronized format: date-time type: string type: object @@ -1773,6 +1773,7 @@ spec: of GrafanaNotificationPolicy properties: conditions: + description: Results when synchonizing resource with Grafana instances items: description: Condition contains details for one aspect of the current state of this API Resource. diff --git a/docs/docs/api.md b/docs/docs/api.md index 29f7aec4e..238d3f148 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -538,7 +538,7 @@ GrafanaAlertRuleGroupStatus defines the observed state of GrafanaAlertRuleGroup @@ -1035,8 +1035,7 @@ GrafanaContactPointStatus defines the observed state of GrafanaContactPoint @@ -2097,7 +2096,7 @@ GrafanaDashboardStatus defines the observed state of GrafanaDashboard @@ -2129,14 +2128,14 @@ GrafanaDashboardStatus defines the observed state of GrafanaDashboard @@ -3064,22 +3063,21 @@ GrafanaFolderStatus defines the observed state of GrafanaFolder @@ -3534,7 +3532,7 @@ GrafanaNotificationPolicyStatus defines the observed state of GrafanaNotificatio From cf65586229f0afccd403f5f0047057159fb9c3e3 Mon Sep 17 00:00:00 2001 From: ste Date: Sat, 16 Nov 2024 22:59:43 +0100 Subject: [PATCH 03/10] refactor: Add CommonSpec to resources missing status fields --- api/v1beta1/grafanaalertrulegroup_types.go | 11 +- api/v1beta1/grafanacontactpoint_types.go | 11 +- api/v1beta1/grafanadatasource_types.go | 9 +- .../grafananotificationpolicy_types.go | 11 +- api/v1beta1/zz_generated.deepcopy.go | 68 +------- ...ntegreatly.org_grafanaalertrulegroups.yaml | 12 +- ....integreatly.org_grafanacontactpoints.yaml | 11 +- ...na.integreatly.org_grafanadatasources.yaml | 60 ++++++- ...eatly.org_grafananotificationpolicies.yaml | 12 +- ...ntegreatly.org_grafanaalertrulegroups.yaml | 12 +- ....integreatly.org_grafanacontactpoints.yaml | 11 +- ...na.integreatly.org_grafanadatasources.yaml | 60 ++++++- ...eatly.org_grafananotificationpolicies.yaml | 12 +- deploy/kustomize/base/crds.yaml | 95 +++++++++-- docs/docs/api.md | 154 ++++++++++++++++-- 15 files changed, 403 insertions(+), 146 deletions(-) diff --git a/api/v1beta1/grafanaalertrulegroup_types.go b/api/v1beta1/grafanaalertrulegroup_types.go index 4d9bf8972..231e66503 100644 --- a/api/v1beta1/grafanaalertrulegroup_types.go +++ b/api/v1beta1/grafanaalertrulegroup_types.go @@ -116,13 +116,6 @@ type AlertQuery struct { RelativeTimeRange *models.RelativeTimeRange `json:"relativeTimeRange,omitempty"` } -// TODO Implement GrafanaCommonStatus -// GrafanaAlertRuleGroupStatus defines the observed state of GrafanaAlertRuleGroup -type GrafanaAlertRuleGroupStatus struct { - // Results when synchonizing resource with Grafana instances - Conditions []metav1.Condition `json:"conditions"` -} - //+kubebuilder:object:root=true //+kubebuilder:subresource:status @@ -132,8 +125,8 @@ type GrafanaAlertRuleGroup struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec GrafanaAlertRuleGroupSpec `json:"spec,omitempty"` - Status GrafanaAlertRuleGroupStatus `json:"status,omitempty"` + Spec GrafanaAlertRuleGroupSpec `json:"spec,omitempty"` + Status GrafanaCommonStatus `json:"status,omitempty"` } // GroupName returns the name of alert rule group. diff --git a/api/v1beta1/grafanacontactpoint_types.go b/api/v1beta1/grafanacontactpoint_types.go index ab1e671e9..c947b42ef 100644 --- a/api/v1beta1/grafanacontactpoint_types.go +++ b/api/v1beta1/grafanacontactpoint_types.go @@ -49,13 +49,6 @@ type GrafanaContactPointSpec struct { Type string `json:"type,omitempty"` } -// TODO Implement GrafanaCommonStatus -// GrafanaContactPointStatus defines the observed state of GrafanaContactPoint -type GrafanaContactPointStatus struct { - // Results when synchonizing resource with Grafana instances - Conditions []metav1.Condition `json:"conditions"` -} - //+kubebuilder:object:root=true //+kubebuilder:subresource:status @@ -65,8 +58,8 @@ type GrafanaContactPoint struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec GrafanaContactPointSpec `json:"spec,omitempty"` - Status GrafanaContactPointStatus `json:"status,omitempty"` + Spec GrafanaContactPointSpec `json:"spec,omitempty"` + Status GrafanaCommonStatus `json:"status,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1beta1/grafanadatasource_types.go b/api/v1beta1/grafanadatasource_types.go index ebc2b48e5..c76c3a318 100644 --- a/api/v1beta1/grafanadatasource_types.go +++ b/api/v1beta1/grafanadatasource_types.go @@ -81,13 +81,12 @@ type GrafanaDatasourceSpec struct { // GrafanaDatasourceStatus defines the observed state of GrafanaDatasource type GrafanaDatasourceStatus struct { - Hash string `json:"hash,omitempty"` + GrafanaCommonStatus `json:",inline"` + LastMessage string `json:"lastMessage,omitempty"` // The datasource instanceSelector can't find matching grafana instances - NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` - // Last time the datasource was resynced - LastResync metav1.Time `json:"lastResync,omitempty"` - UID string `json:"uid,omitempty"` + NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` + UID string `json:"uid,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1beta1/grafananotificationpolicy_types.go b/api/v1beta1/grafananotificationpolicy_types.go index d633b3853..f55b235d8 100644 --- a/api/v1beta1/grafananotificationpolicy_types.go +++ b/api/v1beta1/grafananotificationpolicy_types.go @@ -125,13 +125,6 @@ func (r *Route) ToModelRoute() *models.Route { return out } -// TODO Implement GrafanaCommonStatus -// GrafanaNotificationPolicyStatus defines the observed state of GrafanaNotificationPolicy -type GrafanaNotificationPolicyStatus struct { - // Results when synchonizing resource with Grafana instances - Conditions []metav1.Condition `json:"conditions"` -} - //+kubebuilder:object:root=true //+kubebuilder:subresource:status @@ -141,8 +134,8 @@ type GrafanaNotificationPolicy struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec GrafanaNotificationPolicySpec `json:"spec,omitempty"` - Status GrafanaNotificationPolicyStatus `json:"status,omitempty"` + Spec GrafanaNotificationPolicySpec `json:"spec,omitempty"` + Status GrafanaCommonStatus `json:"status,omitempty"` } func (np *GrafanaNotificationPolicy) NamespacedResource() string { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 51cb91c35..f3506c672 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -503,28 +503,6 @@ func (in *GrafanaAlertRuleGroupSpec) DeepCopy() *GrafanaAlertRuleGroupSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GrafanaAlertRuleGroupStatus) DeepCopyInto(out *GrafanaAlertRuleGroupStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaAlertRuleGroupStatus. -func (in *GrafanaAlertRuleGroupStatus) DeepCopy() *GrafanaAlertRuleGroupStatus { - if in == nil { - return nil - } - out := new(GrafanaAlertRuleGroupStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaClient) DeepCopyInto(out *GrafanaClient) { *out = *in @@ -711,28 +689,6 @@ func (in *GrafanaContactPointSpec) DeepCopy() *GrafanaContactPointSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GrafanaContactPointStatus) DeepCopyInto(out *GrafanaContactPointStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaContactPointStatus. -func (in *GrafanaContactPointStatus) DeepCopy() *GrafanaContactPointStatus { - if in == nil { - return nil - } - out := new(GrafanaContactPointStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaDashboard) DeepCopyInto(out *GrafanaDashboard) { *out = *in @@ -1121,7 +1077,7 @@ func (in *GrafanaDatasourceSpec) DeepCopy() *GrafanaDatasourceSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaDatasourceStatus) DeepCopyInto(out *GrafanaDatasourceStatus) { *out = *in - in.LastResync.DeepCopyInto(&out.LastResync) + in.GrafanaCommonStatus.DeepCopyInto(&out.GrafanaCommonStatus) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaDatasourceStatus. @@ -1342,28 +1298,6 @@ func (in *GrafanaNotificationPolicySpec) DeepCopy() *GrafanaNotificationPolicySp return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GrafanaNotificationPolicyStatus) DeepCopyInto(out *GrafanaNotificationPolicyStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaNotificationPolicyStatus. -func (in *GrafanaNotificationPolicyStatus) DeepCopy() *GrafanaNotificationPolicyStatus { - if in == nil { - return nil - } - out := new(GrafanaNotificationPolicyStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaPlugin) DeepCopyInto(out *GrafanaPlugin) { *out = *in diff --git a/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml b/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml index e26aa72d6..6c33cf830 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -244,8 +244,7 @@ spec: rule: (has(self.folderUID) && !(has(self.folderRef))) || (has(self.folderRef) && !(has(self.folderUID))) status: - description: GrafanaAlertRuleGroupStatus defines the observed state of - GrafanaAlertRuleGroup + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -304,8 +303,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true diff --git a/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml b/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml index ba6710ee0..017208265 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanacontactpoints.yaml @@ -215,7 +215,7 @@ spec: rule: ((!has(oldSelf.uid) && !has(self.uid)) || (has(oldSelf.uid) && has(self.uid))) status: - description: GrafanaContactPointStatus defines the observed state of GrafanaContactPoint + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -274,8 +274,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true diff --git a/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml b/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml index 86de49754..d067cf690 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml @@ -251,12 +251,70 @@ spec: description: The datasource instanceSelector can't find matching grafana instances type: boolean + conditions: + description: Results when synchonizing resource with Grafana instances + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array hash: + description: Detect resource changes type: string lastMessage: type: string lastResync: - description: Last time the datasource was resynced + description: Last time the resource was synchronized format: date-time type: string uid: diff --git a/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml b/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml index 6477e5763..a0e1aea79 100644 --- a/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafananotificationpolicies.yaml @@ -187,8 +187,7 @@ spec: - route type: object status: - description: GrafanaNotificationPolicyStatus defines the observed state - of GrafanaNotificationPolicy + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -247,8 +246,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml index e26aa72d6..6c33cf830 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -244,8 +244,7 @@ spec: rule: (has(self.folderUID) && !(has(self.folderRef))) || (has(self.folderRef) && !(has(self.folderUID))) status: - description: GrafanaAlertRuleGroupStatus defines the observed state of - GrafanaAlertRuleGroup + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -304,8 +303,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml index ba6710ee0..017208265 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanacontactpoints.yaml @@ -215,7 +215,7 @@ spec: rule: ((!has(oldSelf.uid) && !has(self.uid)) || (has(oldSelf.uid) && has(self.uid))) status: - description: GrafanaContactPointStatus defines the observed state of GrafanaContactPoint + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -274,8 +274,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true 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 86de49754..d067cf690 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml @@ -251,12 +251,70 @@ spec: description: The datasource instanceSelector can't find matching grafana instances type: boolean + conditions: + description: Results when synchonizing resource with Grafana instances + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array hash: + description: Detect resource changes type: string lastMessage: type: string lastResync: - description: Last time the datasource was resynced + description: Last time the resource was synchronized format: date-time type: string uid: diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml index 6477e5763..a0e1aea79 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafananotificationpolicies.yaml @@ -187,8 +187,7 @@ spec: - route type: object status: - description: GrafanaNotificationPolicyStatus defines the observed state - of GrafanaNotificationPolicy + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -247,8 +246,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true diff --git a/deploy/kustomize/base/crds.yaml b/deploy/kustomize/base/crds.yaml index 7708f44dc..fcacea3fe 100644 --- a/deploy/kustomize/base/crds.yaml +++ b/deploy/kustomize/base/crds.yaml @@ -243,8 +243,7 @@ spec: rule: (has(self.folderUID) && !(has(self.folderRef))) || (has(self.folderRef) && !(has(self.folderUID))) status: - description: GrafanaAlertRuleGroupStatus defines the observed state of - GrafanaAlertRuleGroup + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -303,8 +302,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true @@ -528,7 +532,7 @@ spec: rule: ((!has(oldSelf.uid) && !has(self.uid)) || (has(oldSelf.uid) && has(self.uid))) status: - description: GrafanaContactPointStatus defines the observed state of GrafanaContactPoint + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -587,8 +591,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true @@ -1345,12 +1354,70 @@ spec: description: The datasource instanceSelector can't find matching grafana instances type: boolean + conditions: + description: Results when synchonizing resource with Grafana instances + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array hash: + description: Detect resource changes type: string lastMessage: type: string lastResync: - description: Last time the datasource was resynced + description: Last time the resource was synchronized format: date-time type: string uid: @@ -1769,8 +1836,7 @@ spec: - route type: object status: - description: GrafanaNotificationPolicyStatus defines the observed state - of GrafanaNotificationPolicy + description: The most recent observed state of a Grafana resource properties: conditions: description: Results when synchonizing resource with Grafana instances @@ -1829,8 +1895,13 @@ spec: - type type: object type: array - required: - - conditions + hash: + description: Detect resource changes + type: string + lastResync: + description: Last time the resource was synchronized + format: date-time + type: string type: object type: object served: true diff --git a/docs/docs/api.md b/docs/docs/api.md index 238d3f148..6a09c71c5 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -77,7 +77,7 @@ GrafanaAlertRuleGroup is the Schema for the grafanaalertrulegroups API @@ -523,7 +523,7 @@ relative time range -GrafanaAlertRuleGroupStatus defines the observed state of GrafanaAlertRuleGroup +The most recent observed state of a Grafana resource
    conditions []object -
    + Results when synchonizing resource with Grafana instances
    true
    conditions []object - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster -Important: Run "make" to regenerate code after modifying this file
    + Results when synchonizing resource with Grafana instances
    true
    conditions []object -
    + Results when synchonizing resource with Grafana instances
    false
    hash string -
    + Detect resource changes
    false
    lastResync string - Last time the dashboard was resynced
    + Last time the resource was synchronized

    Format: date-time
    conditions []object -
    + Results when synchonizing resource with Grafana instances
    false
    hash string - INSERT ADDITIONAL STATUS FIELD - define observed state of cluster -Important: Run "make" to regenerate code after modifying this file
    + Detect resource changes
    false
    lastResync string - Last time the folder was resynced
    + Last time the resource was synchronized

    Format: date-time
    conditions []object -
    + Results when synchonizing resource with Grafana instances
    true
    status object - GrafanaAlertRuleGroupStatus defines the observed state of GrafanaAlertRuleGroup
    + The most recent observed state of a Grafana resource
    false
    @@ -540,7 +540,23 @@ GrafanaAlertRuleGroupStatus defines the observed state of GrafanaAlertRuleGroup - + + + + + + + + + + +
    Results when synchonizing resource with Grafana instances
    truefalse
    hashstring + Detect resource changes
    +
    false
    lastResyncstring + Last time the resource was synchronized
    +
    + Format: date-time
    +
    false
    @@ -670,7 +686,7 @@ GrafanaContactPoint is the Schema for the grafanacontactpoints API status object - GrafanaContactPointStatus defines the observed state of GrafanaContactPoint
    + The most recent observed state of a Grafana resource
    false @@ -1020,7 +1036,7 @@ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/nam -GrafanaContactPointStatus defines the observed state of GrafanaContactPoint +The most recent observed state of a Grafana resource @@ -1037,7 +1053,23 @@ GrafanaContactPointStatus defines the observed state of GrafanaContactPoint - + + + + + + + + + + +
    Results when synchonizing resource with Grafana instances
    truefalse
    hashstring + Detect resource changes
    +
    false
    lastResyncstring + Last time the resource was synchronized
    +
    + Format: date-time
    +
    false
    @@ -2782,11 +2814,18 @@ GrafanaDatasourceStatus defines the observed state of GrafanaDatasource The datasource instanceSelector can't find matching grafana instances
    false + + conditions + []object + + Results when synchonizing resource with Grafana instances
    + + false hash string -
    + Detect resource changes
    false @@ -2800,7 +2839,7 @@ GrafanaDatasourceStatus defines the observed state of GrafanaDatasource lastResync string - Last time the datasource was resynced
    + Last time the resource was synchronized

    Format: date-time
    @@ -2815,6 +2854,83 @@ GrafanaDatasourceStatus defines the observed state of GrafanaDatasource + +### GrafanaDatasource.status.conditions[index] +[↩ Parent](#grafanadatasourcestatus) + + + +Condition contains details for one aspect of the current state of this API Resource. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescriptionRequired
    lastTransitionTimestring + lastTransitionTime is the last time the condition transitioned from one status to another. +This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
    +
    + Format: date-time
    +
    true
    messagestring + message is a human readable message indicating details about the transition. +This may be an empty string.
    +
    true
    reasonstring + reason contains a programmatic identifier indicating the reason for the condition's last transition. +Producers of specific condition types may define expected values and meanings for this field, +and whether the values are considered a guaranteed API. +The value should be a CamelCase string. +This field may not be empty.
    +
    true
    statusenum + status of the condition, one of True, False, Unknown.
    +
    + Enum: True, False, Unknown
    +
    true
    typestring + type of condition in CamelCase or in foo.example.com/CamelCase.
    +
    true
    observedGenerationinteger + observedGeneration represents the .metadata.generation that the condition was set based upon. +For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date +with respect to the current state of the instance.
    +
    + Format: int64
    + Minimum: 0
    +
    false
    + ## GrafanaFolder [↩ Parent](#grafanaintegreatlyorgv1beta1 ) @@ -3209,7 +3325,7 @@ GrafanaNotificationPolicy is the Schema for the GrafanaNotificationPolicy API status object - GrafanaNotificationPolicyStatus defines the observed state of GrafanaNotificationPolicy
    + The most recent observed state of a Grafana resource
    false @@ -3517,7 +3633,7 @@ Routes for alerts to match against -GrafanaNotificationPolicyStatus defines the observed state of GrafanaNotificationPolicy +The most recent observed state of a Grafana resource @@ -3534,7 +3650,23 @@ GrafanaNotificationPolicyStatus defines the observed state of GrafanaNotificatio - + + + + + + + + + + +
    Results when synchonizing resource with Grafana instances
    truefalse
    hashstring + Detect resource changes
    +
    false
    lastResyncstring + Last time the resource was synchronized
    +
    + Format: date-time
    +
    false
    From c0c0587130cc2e917e239bbcc4d390fa397f17bc Mon Sep 17 00:00:00 2001 From: ste Date: Sun, 17 Nov 2024 19:52:03 +0100 Subject: [PATCH 04/10] refactor: Write shared GetMatchingInstances function and delete old ones --- api/v1beta1/grafanacontactpoint_types.go | 7 +++ api/v1beta1/grafanadashboard_types.go | 7 --- api/v1beta1/grafanadatasource_types.go | 7 --- api/v1beta1/grafanafolder_types.go | 7 --- controllers/controller_shared.go | 43 +++++++++++++------ controllers/dashboard_controller.go | 17 -------- controllers/datasource_controller.go | 17 -------- .../grafanaalertrulegroup_controller.go | 18 -------- controllers/grafanacontactpoint_controller.go | 18 -------- controllers/grafanafolder_controller.go | 17 -------- 10 files changed, 37 insertions(+), 121 deletions(-) diff --git a/api/v1beta1/grafanacontactpoint_types.go b/api/v1beta1/grafanacontactpoint_types.go index c947b42ef..996eb060a 100644 --- a/api/v1beta1/grafanacontactpoint_types.go +++ b/api/v1beta1/grafanacontactpoint_types.go @@ -17,6 +17,8 @@ limitations under the License. package v1beta1 import ( + "time" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -82,3 +84,8 @@ func (in *GrafanaContactPoint) CustomUIDOrUID() string { func init() { SchemeBuilder.Register(&GrafanaContactPoint{}, &GrafanaContactPointList{}) } + +func (in *GrafanaContactPoint) ResyncPeriodHasElapsed() bool { + deadline := in.Status.LastResync.Add(in.Spec.ResyncPeriod.Duration) + return time.Now().After(deadline) +} diff --git a/api/v1beta1/grafanadashboard_types.go b/api/v1beta1/grafanadashboard_types.go index c9fde6fbd..6a7578eb4 100644 --- a/api/v1beta1/grafanadashboard_types.go +++ b/api/v1beta1/grafanadashboard_types.go @@ -306,13 +306,6 @@ func (in *GrafanaDashboardStatus) getContentCache(url string, cacheDuration time return cache } -func (in *GrafanaDashboard) IsAllowCrossNamespaceImport() bool { - if in.Spec.AllowCrossNamespaceImport != nil { - return *in.Spec.AllowCrossNamespaceImport - } - return false -} - func (in *GrafanaDashboard) IsUpdatedUID(uid string) bool { // Dashboard has just been created, status is not yet updated if in.Status.UID == "" { diff --git a/api/v1beta1/grafanadatasource_types.go b/api/v1beta1/grafanadatasource_types.go index c76c3a318..ae8a6b4d3 100644 --- a/api/v1beta1/grafanadatasource_types.go +++ b/api/v1beta1/grafanadatasource_types.go @@ -145,13 +145,6 @@ func (in *GrafanaDatasource) CustomUIDOrUID() string { return string(in.ObjectMeta.UID) } -func (in *GrafanaDatasource) IsAllowCrossNamespaceImport() bool { - if in.Spec.AllowCrossNamespaceImport != nil { - return *in.Spec.AllowCrossNamespaceImport - } - return false -} - func (in *GrafanaDatasourceList) Find(namespace string, name string) *GrafanaDatasource { for _, datasource := range in.Items { if datasource.Namespace == namespace && datasource.Name == name { diff --git a/api/v1beta1/grafanafolder_types.go b/api/v1beta1/grafanafolder_types.go index 4f128546b..fd62a504a 100644 --- a/api/v1beta1/grafanafolder_types.go +++ b/api/v1beta1/grafanafolder_types.go @@ -148,13 +148,6 @@ func (in *GrafanaFolder) Unchanged() bool { return in.Hash() == in.Status.Hash } -func (in *GrafanaFolder) IsAllowCrossNamespaceImport() bool { - if in.Spec.AllowCrossNamespaceImport != nil { - return *in.Spec.AllowCrossNamespaceImport - } - return false -} - func (in *GrafanaFolder) GetTitle() string { if in.Spec.Title != "" { return in.Spec.Title diff --git a/controllers/controller_shared.go b/controllers/controller_shared.go index e3a4cc7d4..7645e6a3c 100644 --- a/controllers/controller_shared.go +++ b/controllers/controller_shared.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/go-logr/logr" operatorapi "github.com/grafana/grafana-operator/v5/api" "github.com/grafana/grafana-operator/v5/api/v1beta1" grafanav1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1" @@ -31,31 +32,47 @@ const ( conditionInvalidSpec = "InvalidSpec" ) -const annotationAppliedNotificationPolicy = "operator.grafana.com/applied-notificationpolicy" - //+kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch;delete -func GetMatchingInstances(ctx context.Context, k8sClient client.Client, labelSelector *metav1.LabelSelector) (v1beta1.GrafanaList, error) { - if labelSelector == nil { - return v1beta1.GrafanaList{}, nil +func GetMatchingInstances(log logr.Logger, ctx context.Context, k8sClient client.Client, commonSpec v1beta1.GrafanaCommonSpec, namespace string) ([]v1beta1.Grafana, error) { + if commonSpec.InstanceSelector.MatchLabels == nil { + return []v1beta1.Grafana{}, nil } - var list v1beta1.GrafanaList opts := []client.ListOption{ - client.MatchingLabels(labelSelector.MatchLabels), + client.MatchingLabels(commonSpec.InstanceSelector.MatchLabels), + } + if commonSpec.AllowCrossNamespaceImport != nil && !*commonSpec.AllowCrossNamespaceImport { + // Only query resource namespace + opts = append(opts, client.InNamespace(namespace)) } - err := k8sClient.List(ctx, &list, opts...) - var selectedList v1beta1.GrafanaList + var list v1beta1.GrafanaList + err := k8sClient.List(ctx, &list, opts...) + if err != nil || len(list.Items) == 0 { + return []v1beta1.Grafana{}, err + } + selectedList := []v1beta1.Grafana{} + var unready_instances []string for _, instance := range list.Items { - selected := labelsSatisfyMatchExpressions(instance.Labels, labelSelector.MatchExpressions) - if selected { - selectedList.Items = append(selectedList.Items, instance) + selected := labelsSatisfyMatchExpressions(instance.Labels, commonSpec.InstanceSelector.MatchExpressions) + if !selected { + continue } + // admin url is required to interact with Grafana + // the instance or route might not yet be ready + if instance.Status.Stage != v1beta1.OperatorStageComplete || instance.Status.StageStatus != v1beta1.OperatorStageResultSuccess { + unready_instances = append(unready_instances, instance.Name) + continue + } + selectedList = append(selectedList, instance) + } + if len(unready_instances) > 1 { + log.Info("Grafana instances not ready", "instances", unready_instances) } - return selectedList, err + return selectedList, nil } // getFolderUID fetches the folderUID from an existing GrafanaFolder CR declared in the specified namespace diff --git a/controllers/dashboard_controller.go b/controllers/dashboard_controller.go index fb85ef0d6..c6e2f6d6f 100644 --- a/controllers/dashboard_controller.go +++ b/controllers/dashboard_controller.go @@ -786,23 +786,6 @@ func (r *GrafanaDashboardReconciler) SetupWithManager(mgr ctrl.Manager, ctx cont return err } -func (r *GrafanaDashboardReconciler) GetMatchingDashboardInstances(ctx context.Context, dashboard *v1beta1.GrafanaDashboard, k8sClient client.Client) (v1beta1.GrafanaList, error) { - instances, err := GetMatchingInstances(ctx, k8sClient, dashboard.Spec.InstanceSelector) - if err != nil || len(instances.Items) == 0 { - dashboard.Status.NoMatchingInstances = true - if err := r.Client.Status().Update(ctx, dashboard); err != nil { - r.Log.Info("unable to update the status of %v, in %v", dashboard.Name, dashboard.Namespace) - } - return v1beta1.GrafanaList{}, err - } - dashboard.Status.NoMatchingInstances = false - if err := r.Client.Status().Update(ctx, dashboard); err != nil { - r.Log.Info("unable to update the status of %v, in %v", dashboard.Name, dashboard.Namespace) - } - - return instances, err -} - func (r *GrafanaDashboardReconciler) UpdateHomeDashboard(ctx context.Context, grafana v1beta1.Grafana, uid string, dashboard *v1beta1.GrafanaDashboard) error { grafanaClient, err := client2.NewGeneratedGrafanaClient(ctx, r.Client, &grafana) if err != nil { diff --git a/controllers/datasource_controller.go b/controllers/datasource_controller.go index 951d27e5b..f6248e549 100644 --- a/controllers/datasource_controller.go +++ b/controllers/datasource_controller.go @@ -420,23 +420,6 @@ 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) { - 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 { - r.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 { - r.Log.Info("unable to update the status of %v, in %v", datasource.Name, datasource.Namespace) - } - - return instances, err -} - func (r *GrafanaDatasourceReconciler) getDatasourceContent(ctx context.Context, cr *v1beta1.GrafanaDatasource) (*models.UpdateDataSourceCommand, string, error) { initialBytes, err := json.Marshal(cr.Spec.Datasource) if err != nil { diff --git a/controllers/grafanaalertrulegroup_controller.go b/controllers/grafanaalertrulegroup_controller.go index 443402017..9a381a35e 100644 --- a/controllers/grafanaalertrulegroup_controller.go +++ b/controllers/grafanaalertrulegroup_controller.go @@ -330,21 +330,3 @@ func (r *GrafanaAlertRuleGroupReconciler) removeFromInstance(ctx context.Context } return nil } - -func (r *GrafanaAlertRuleGroupReconciler) GetMatchingInstances(ctx context.Context, group *grafanav1beta1.GrafanaAlertRuleGroup, k8sClient client.Client) ([]grafanav1beta1.Grafana, error) { - instances, err := GetMatchingInstances(ctx, k8sClient, group.Spec.InstanceSelector) - if err != nil || len(instances.Items) == 0 { - return nil, err - } - if group.Spec.AllowCrossNamespaceImport != nil && *group.Spec.AllowCrossNamespaceImport { - return instances.Items, nil - } - items := []grafanav1beta1.Grafana{} - for _, i := range instances.Items { - if i.Namespace == group.Namespace { - items = append(items, i) - } - } - - return items, err -} diff --git a/controllers/grafanacontactpoint_controller.go b/controllers/grafanacontactpoint_controller.go index 28bd21212..8389475b4 100644 --- a/controllers/grafanacontactpoint_controller.go +++ b/controllers/grafanacontactpoint_controller.go @@ -287,24 +287,6 @@ func (r *GrafanaContactPointReconciler) removeFromInstance(ctx context.Context, return nil } -func (r *GrafanaContactPointReconciler) GetMatchingInstances(ctx context.Context, contactPoint *grafanav1beta1.GrafanaContactPoint, k8sClient client.Client) ([]grafanav1beta1.Grafana, error) { - instances, err := GetMatchingInstances(ctx, k8sClient, contactPoint.Spec.InstanceSelector) - if err != nil || len(instances.Items) == 0 { - return nil, err - } - if contactPoint.Spec.AllowCrossNamespaceImport != nil && *contactPoint.Spec.AllowCrossNamespaceImport { - return instances.Items, nil - } - items := []grafanav1beta1.Grafana{} - for _, i := range instances.Items { - if i.Namespace == contactPoint.Namespace { - items = append(items, i) - } - } - - return items, err -} - // SetupWithManager sets up the controller with the Manager. func (r *GrafanaContactPointReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/controllers/grafanafolder_controller.go b/controllers/grafanafolder_controller.go index 0f3ddb034..2b6173ad0 100644 --- a/controllers/grafanafolder_controller.go +++ b/controllers/grafanafolder_controller.go @@ -444,20 +444,3 @@ func (r *GrafanaFolderReconciler) Exists(client *genapi.GrafanaHTTPAPI, cr *graf page++ } } - -func (r *GrafanaFolderReconciler) GetMatchingFolderInstances(ctx context.Context, folder *grafanav1beta1.GrafanaFolder, k8sClient client.Client) (grafanav1beta1.GrafanaList, error) { - instances, err := GetMatchingInstances(ctx, k8sClient, folder.Spec.InstanceSelector) - if err != nil || len(instances.Items) == 0 { - folder.Status.NoMatchingInstances = true - if err := r.Client.Status().Update(ctx, folder); err != nil { - r.Log.Info("unable to update the status of %v, in %v", folder.Name, folder.Namespace) - } - return grafanav1beta1.GrafanaList{}, err - } - folder.Status.NoMatchingInstances = false - if err := r.Client.Status().Update(ctx, folder); err != nil { - r.Log.Info("unable to update the status of %v, in %v", folder.Name, folder.Namespace) - } - - return instances, err -} From 0c51087a53b7aadb3f561ff32bcc90d14b42ed28 Mon Sep 17 00:00:00 2001 From: ste Date: Sat, 16 Nov 2024 22:57:17 +0100 Subject: [PATCH 05/10] refactor: Align reconcile functions for all controllers --- controllers/controller_shared.go | 13 ++++ controllers/dashboard_controller.go | 46 ++++++-------- controllers/datasource_controller.go | 61 +++++++++---------- .../grafanaalertrulegroup_controller.go | 28 +++------ controllers/grafanacontactpoint_controller.go | 51 +++++----------- controllers/grafanafolder_controller.go | 37 ++++------- controllers/notificationpolicy_controller.go | 38 +++++------- 7 files changed, 112 insertions(+), 162 deletions(-) diff --git a/controllers/controller_shared.go b/controllers/controller_shared.go index 7645e6a3c..3101cabe8 100644 --- a/controllers/controller_shared.go +++ b/controllers/controller_shared.go @@ -222,6 +222,19 @@ func ignoreStatusUpdates() predicate.Predicate { } } +func NilOrEmptyInstanceListCondition(conditions *[]metav1.Condition, conditionCRSynchronized string, generation int64, err error) { + var reason, message string + if err != nil { + reason = "ErrFetchingInstances" + message = fmt.Sprintf("error occurred during fetching of instances: %s", err.Error()) + } else { + reason = "EmptyAPIReply" + message = "Instances could not be fetched, reconciliation will be retried" + } + setNoMatchingInstance(conditions, generation, reason, message) + meta.RemoveStatusCondition(conditions, conditionCRSynchronized) +} + func buildSynchronizedCondition(resource string, syncType string, generation int64, applyErrors map[string]string, total int) metav1.Condition { condition := metav1.Condition{ Type: syncType, diff --git a/controllers/dashboard_controller.go b/controllers/dashboard_controller.go index c6e2f6d6f..c6e4faf17 100644 --- a/controllers/dashboard_controller.go +++ b/controllers/dashboard_controller.go @@ -192,18 +192,26 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{RequeueAfter: RequeueDelay}, err } - instances, err := r.GetMatchingDashboardInstances(ctx, cr, r.Client) - if err != nil { + defer func() { + if err := r.Client.Status().Update(ctx, cr); err != nil { + r.Log.Error(err, "updating status") + } + }() + + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, cr.Spec.GrafanaCommonSpec, cr.ObjectMeta.Namespace) + if err != nil || len(instances) == 0 { + NilOrEmptyInstanceListCondition(&cr.Status.Conditions, conditionDashboardSynchronized, cr.Generation, err) controllerLog.Error(err, "could not find matching instances", "name", cr.Name, "namespace", cr.Namespace) - return ctrl.Result{RequeueAfter: RequeueDelay}, err + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } removeNoMatchingInstance(&cr.Status.Conditions) - controllerLog.Info("found matching Grafana instances for dashboard", "count", len(instances.Items)) + controllerLog.Info("found matching Grafana instances for dashboard", "count", len(instances)) dashboardJson, err := r.fetchDashboardJson(ctx, cr) if err != nil { controllerLog.Error(err, "error fetching dashboard", "dashboard", cr.Name) + meta.RemoveStatusCondition(&cr.Status.Conditions, conditionDashboardSynchronized) return ctrl.Result{RequeueAfter: RequeueDelay}, nil } @@ -211,6 +219,7 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req dashboardModel, hash, err := r.getDashboardModel(cr, dashboardJson) if err != nil { controllerLog.Error(err, "failed to prepare dashboard model", "dashboard", cr.Name) + meta.RemoveStatusCondition(&cr.Status.Conditions, conditionDashboardSynchronized) return ctrl.Result{Requeue: false}, nil } @@ -226,31 +235,14 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req // Clean up uid, so further reconciliations can track changes there cr.Status.UID = "" - err = r.Client.Status().Update(ctx, cr) - if err != nil { - return ctrl.Result{RequeueAfter: RequeueDelay}, err - } - // Status update should trigger the next reconciliation right away, no need to requeue for dashboard creation - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } success := true applyErrors := make(map[string]string) - for _, grafana := range instances.Items { - // check if this is a cross namespace import - if grafana.Namespace != cr.Namespace && !cr.IsAllowCrossNamespaceImport() { - continue - } - + 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 { - controllerLog.Info("grafana instance not ready", "grafana", grafana.Name) - success = false - continue - } if grafana.IsInternal() { // first reconcile the plugins @@ -266,12 +258,11 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req // then import the dashboard into the matching grafana instances err = r.onDashboardCreated(ctx, &grafana, cr, dashboardModel, hash) if err != nil { - controllerLog.Error(err, "error reconciling dashboard", "dashboard", cr.Name, "grafana", grafana.Name) applyErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() success = false } - condition := buildSynchronizedCondition("Dashboard", conditionDashboardSynchronized, cr.Generation, applyErrors, len(instances.Items)) + condition := buildSynchronizedCondition("Dashboard", conditionDashboardSynchronized, cr.Generation, applyErrors, len(instances)) meta.SetStatusCondition(&cr.Status.Conditions, condition) if grafana.Spec.Preferences != nil && uid == grafana.Spec.Preferences.HomeDashboardUID { @@ -282,6 +273,9 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req } } + if len(applyErrors) > 0 { + return ctrl.Result{}, fmt.Errorf("failed to apply to all instances: %v", applyErrors) + } // if the dashboard was successfully synced in all instances, wait for its re-sync period if success { if cr.ResyncPeriodHasElapsed() { @@ -289,7 +283,7 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req } cr.Status.Hash = hash cr.Status.UID = uid - return ctrl.Result{RequeueAfter: cr.Spec.ResyncPeriod.Duration}, r.Client.Status().Update(ctx, cr) + return ctrl.Result{RequeueAfter: cr.Spec.ResyncPeriod.Duration}, nil } return ctrl.Result{RequeueAfter: RequeueDelay}, nil diff --git a/controllers/datasource_controller.go b/controllers/datasource_controller.go index f6248e549..4298ace9f 100644 --- a/controllers/datasource_controller.go +++ b/controllers/datasource_controller.go @@ -35,6 +35,7 @@ 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" @@ -51,6 +52,10 @@ type GrafanaDatasourceReconciler struct { Log logr.Logger } +const ( + conditionDatasourceSynchronized = "DashboardSynchronized" +) + //+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanadatasources,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanadatasources/status,verbs=get;update;patch //+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanadatasources/finalizers,verbs=update @@ -176,6 +181,12 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{RequeueAfter: RequeueDelay}, err } + defer func() { + if err := r.Client.Status().Update(ctx, cr); err != nil { + r.Log.Error(err, "updating status") + } + }() + if cr.Spec.Datasource == nil { controllerLog.Info("skipped datasource with empty spec", cr.Name, cr.Namespace) // TODO: add a custom status around that? @@ -185,18 +196,20 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re // Overwrite OrgID to ensure the field is useless cr.Spec.Datasource.OrgID = nil - instances, err := r.GetMatchingDatasourceInstances(ctx, cr, r.Client) - if err != nil { + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, cr.Spec.GrafanaCommonSpec, cr.ObjectMeta.Namespace) + if err != nil || len(instances) == 0 { + NilOrEmptyInstanceListCondition(&cr.Status.Conditions, conditionDatasourceSynchronized, cr.Generation, err) controllerLog.Error(err, "could not find matching instances", "name", cr.Name, "namespace", cr.Namespace) - return ctrl.Result{RequeueAfter: RequeueDelay}, err + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } - controllerLog.Info("found matching Grafana instances for datasource", "count", len(instances.Items)) + removeNoMatchingInstance(&cr.Status.Conditions) + controllerLog.Info("found matching Grafana instances for datasource", "count", len(instances)) datasource, hash, err := r.getDatasourceContent(ctx, cr) if err != nil { controllerLog.Error(err, "could not retrieve datasource contents", "name", cr.Name, "namespace", cr.Namespace) - return ctrl.Result{RequeueAfter: RequeueDelay}, err + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } if cr.IsUpdatedUID() { @@ -209,30 +222,13 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re // 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 - } - - // Status update should trigger the next reconciliation right away, no need to requeue for dashboard creation - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } success := true - for _, grafana := range instances.Items { - // check if this is a cross namespace import - if grafana.Namespace != cr.Namespace && !cr.IsAllowCrossNamespaceImport() { - continue - } - + 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 { - controllerLog.Info("grafana instance not ready", "grafana", grafana.Name) - success = false - continue - } if grafana.IsInternal() { // first reconcile the plugins @@ -249,23 +245,26 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re err = r.onDatasourceCreated(ctx, &grafana, cr, datasource, hash) if err != nil { success = false - cr.Status.LastMessage = err.Error() - controllerLog.Error(err, "error reconciling datasource", "datasource", cr.Name, "grafana", grafana.Name) + applyErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() } } + condition := buildSynchronizedCondition("Datasource", conditionDatasourceSynchronized, cr.Generation, applyErrors, len(instances)) + meta.SetStatusCondition(&cr.Status.Conditions, condition) + + if len(applyErrors) > 0 { + return ctrl.Result{}, fmt.Errorf("failed to apply to all instances: %v", applyErrors) + } // 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()} } cr.Status.UID = cr.CustomUIDOrUID() - return ctrl.Result{RequeueAfter: cr.Spec.ResyncPeriod.Duration}, r.Client.Status().Update(ctx, cr) + return ctrl.Result{RequeueAfter: cr.Spec.ResyncPeriod.Duration}, nil } else { - // if there was an issue with the datasource, update the status - return ctrl.Result{RequeueAfter: RequeueDelay}, r.Client.Status().Update(ctx, cr) + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } } diff --git a/controllers/grafanaalertrulegroup_controller.go b/controllers/grafanaalertrulegroup_controller.go index 9a381a35e..75aeaf2f0 100644 --- a/controllers/grafanaalertrulegroup_controller.go +++ b/controllers/grafanaalertrulegroup_controller.go @@ -106,21 +106,15 @@ func (r *GrafanaAlertRuleGroupReconciler) Reconcile(ctx context.Context, req ctr } }() - instances, err := r.GetMatchingInstances(ctx, group, r.Client) - if err != nil { - setNoMatchingInstance(&group.Status.Conditions, group.Generation, "ErrFetchingInstances", fmt.Sprintf("error occurred during fetching of instances: %s", err.Error())) - meta.RemoveStatusCondition(&group.Status.Conditions, conditionAlertGroupSynchronized) - r.Log.Error(err, "could not find matching instances") - return ctrl.Result{}, err - } - - if len(instances) == 0 { - meta.RemoveStatusCondition(&group.Status.Conditions, conditionAlertGroupSynchronized) - setNoMatchingInstance(&group.Status.Conditions, group.Generation, "EmptyAPIReply", "Instances could not be fetched, reconciliation will be retried") - return ctrl.Result{}, fmt.Errorf("no instances found") + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, group.Spec.GrafanaCommonSpec, group.ObjectMeta.Namespace) + if err != nil || len(instances) == 0 { + NilOrEmptyInstanceListCondition(&group.Status.Conditions, conditionAlertGroupSynchronized, group.Generation, err) + controllerLog.Error(err, "could not find matching instances", "name", group.Name, "namespace", group.Namespace) + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } removeNoMatchingInstance(&group.Status.Conditions) + controllerLog.Info("found matching Grafana instances for alert rule group", "count", len(instances)) folderUID, err := getFolderUID(ctx, r.Client, group) if err != nil || folderUID == "" { @@ -131,18 +125,16 @@ func (r *GrafanaAlertRuleGroupReconciler) Reconcile(ctx context.Context, req ctr for _, grafana := range instances { // can be removed in go 1.22+ grafana := grafana - if grafana.Status.Stage != grafanav1beta1.OperatorStageComplete || grafana.Status.StageStatus != grafanav1beta1.OperatorStageResultSuccess { - controllerLog.Info("grafana instance not ready", "grafana", grafana.Name) - continue - } err := r.reconcileWithInstance(ctx, &grafana, group, folderUID) if err != nil { applyErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() } } + condition := buildSynchronizedCondition("Alert Rule Group", conditionAlertGroupSynchronized, group.Generation, applyErrors, len(instances)) meta.SetStatusCondition(&group.Status.Conditions, condition) + if len(applyErrors) > 0 { return ctrl.Result{}, fmt.Errorf("failed to apply to all instances: %v", applyErrors) } @@ -293,14 +285,14 @@ func (r *GrafanaAlertRuleGroupReconciler) finalize(ctx context.Context, group *g r.Log.Info("ignoring finalization logic as folder no longer exists") return nil //nolint:nilerr } - instances, err := r.GetMatchingInstances(ctx, group, r.Client) + instances, err := GetMatchingInstances(r.Log, ctx, r.Client, group.Spec.GrafanaCommonSpec, group.ObjectMeta.Namespace) if err != nil { return fmt.Errorf("fetching instances: %w", err) } for _, i := range instances { instance := i if err := r.removeFromInstance(ctx, &instance, group, folderUID); err != nil { - return fmt.Errorf("removing from instance") + return fmt.Errorf("removing from instance %w", err) } } return nil diff --git a/controllers/grafanacontactpoint_controller.go b/controllers/grafanacontactpoint_controller.go index 8389475b4..b4fcd6f45 100644 --- a/controllers/grafanacontactpoint_controller.go +++ b/controllers/grafanacontactpoint_controller.go @@ -110,60 +110,37 @@ func (r *GrafanaContactPointReconciler) Reconcile(ctx context.Context, req ctrl. } }() - instances, err := r.GetMatchingInstances(ctx, contactPoint, r.Client) - if err != nil { - setNoMatchingInstance(&contactPoint.Status.Conditions, contactPoint.Generation, "ErrFetchingInstances", fmt.Sprintf("error occurred during fetching of instances: %s", err.Error())) - meta.RemoveStatusCondition(&contactPoint.Status.Conditions, conditionContactPointSynchronized) - r.Log.Error(err, "could not find matching instances") - return ctrl.Result{RequeueAfter: RequeueDelay}, err - } - - if len(instances) == 0 { - meta.RemoveStatusCondition(&contactPoint.Status.Conditions, conditionContactPointSynchronized) - setNoMatchingInstance(&contactPoint.Status.Conditions, contactPoint.Generation, "EmptyAPIReply", "Instances could not be fetched, reconciliation will be retried") - return ctrl.Result{}, nil + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, contactPoint.Spec.GrafanaCommonSpec, contactPoint.ObjectMeta.Namespace) + if err != nil || len(instances) == 0 { + NilOrEmptyInstanceListCondition(&contactPoint.Status.Conditions, conditionContactPointSynchronized, contactPoint.Generation, err) + controllerLog.Error(err, "could not find matching instances", "name", contactPoint.Name, "namespace", contactPoint.Namespace) + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } removeNoMatchingInstance(&contactPoint.Status.Conditions) + controllerLog.Info("found matching Grafana instances for contact point", "count", len(instances)) applyErrors := make(map[string]string) for _, grafana := range instances { // can be removed in go 1.22+ grafana := grafana - if grafana.Status.Stage != grafanav1beta1.OperatorStageComplete || grafana.Status.StageStatus != grafanav1beta1.OperatorStageResultSuccess { - controllerLog.Info("grafana instance not ready", "grafana", grafana.Name) - continue - } err := r.reconcileWithInstance(ctx, &grafana, contactPoint) if err != nil { applyErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() } } - condition := metav1.Condition{ - Type: conditionContactPointSynchronized, - ObservedGeneration: contactPoint.Generation, - LastTransitionTime: metav1.Time{ - Time: time.Now(), - }, - } - if len(applyErrors) == 0 { - condition.Status = "True" - condition.Reason = "ApplySuccessful" - condition.Message = fmt.Sprintf("Contact point was successfully applied to %d instances", len(instances)) - } else { - condition.Status = "False" - condition.Reason = "ApplyFailed" + condition := buildSynchronizedCondition("ContactPoint", conditionContactPointSynchronized, contactPoint.Generation, applyErrors, len(instances)) + meta.SetStatusCondition(&contactPoint.Status.Conditions, condition) - var sb strings.Builder - for i, err := range applyErrors { - sb.WriteString(fmt.Sprintf("\n- %s: %s", i, err)) - } + if len(applyErrors) > 0 { + return ctrl.Result{}, fmt.Errorf("failed to apply to all instances: %v", applyErrors) + } - condition.Message = fmt.Sprintf("Contact point failed to be applied for %d out of %d instances. Errors:%s", len(applyErrors), len(instances), sb.String()) + if contactPoint.ResyncPeriodHasElapsed() { + contactPoint.Status.LastResync = metav1.Time{Time: time.Now()} } - meta.SetStatusCondition(&contactPoint.Status.Conditions, condition) return ctrl.Result{RequeueAfter: contactPoint.Spec.ResyncPeriod.Duration}, nil } @@ -255,7 +232,7 @@ func (r *GrafanaContactPointReconciler) getContactPointFromUID(ctx context.Conte func (r *GrafanaContactPointReconciler) finalize(ctx context.Context, contactPoint *grafanav1beta1.GrafanaContactPoint) error { r.Log.Info("Finalizing GrafanaContactPoint") - instances, err := r.GetMatchingInstances(ctx, contactPoint, r.Client) + instances, err := GetMatchingInstances(r.Log, ctx, r.Client, contactPoint.Spec.GrafanaCommonSpec, contactPoint.ObjectMeta.Namespace) if err != nil { return fmt.Errorf("fetching instances: %w", err) } diff --git a/controllers/grafanafolder_controller.go b/controllers/grafanafolder_controller.go index 2b6173ad0..0d3da2221 100644 --- a/controllers/grafanafolder_controller.go +++ b/controllers/grafanafolder_controller.go @@ -194,45 +194,30 @@ func (r *GrafanaFolderReconciler) Reconcile(ctx context.Context, req ctrl.Reques } removeInvalidSpec(&folder.Status.Conditions) - instances, err := r.GetMatchingFolderInstances(ctx, folder, r.Client) - if err != nil { - setNoMatchingInstance(&folder.Status.Conditions, folder.Generation, "ErrFetchingInstances", fmt.Sprintf("error occurred during fetching of instances: %s", err.Error())) - meta.RemoveStatusCondition(&folder.Status.Conditions, conditionFolderSynchronized) - r.Log.Error(err, "could not find matching instances") - return ctrl.Result{}, err - } - if len(instances.Items) == 0 { - setNoMatchingInstance(&folder.Status.Conditions, folder.Generation, "EmptyAPIReply", "Instances could not be fetched, reconciliation will be retried") - meta.RemoveStatusCondition(&folder.Status.Conditions, conditionFolderSynchronized) - return ctrl.Result{}, fmt.Errorf("no instances found") + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, folder.Spec.GrafanaCommonSpec, folder.ObjectMeta.Namespace) + if err != nil || len(instances) == 0 { + NilOrEmptyInstanceListCondition(&folder.Status.Conditions, conditionFolderSynchronized, folder.Generation, err) + controllerLog.Error(err, "could not find matching instances", "name", folder.Name, "namespace", folder.Namespace) + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } + removeNoMatchingInstance(&folder.Status.Conditions) - controllerLog.Info("found matching Grafana instances for folder", "count", len(instances.Items)) + controllerLog.Info("found matching Grafana instances for folder", "count", len(instances)) applyErrors := make(map[string]string) - for _, grafana := range instances.Items { - // check if this is a cross namespace import - if grafana.Namespace != folder.Namespace && !folder.IsAllowCrossNamespaceImport() { - continue - } - + for _, grafana := range instances { grafana := grafana - if grafana.Status.Stage != grafanav1beta1.OperatorStageComplete || grafana.Status.StageStatus != grafanav1beta1.OperatorStageResultSuccess { - controllerLog.Info("grafana instance not ready", "grafana", grafana.Name) - continue - } err = r.onFolderCreated(ctx, &grafana, folder) if err != nil { - controllerLog.Error(err, "error reconciling folder", "folder", folder.Name, "grafana", grafana.Name) applyErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() } } - condition := buildSynchronizedCondition("Folder", conditionFolderSynchronized, folder.Generation, applyErrors, len(instances.Items)) + condition := buildSynchronizedCondition("Folder", conditionFolderSynchronized, folder.Generation, applyErrors, len(instances)) meta.SetStatusCondition(&folder.Status.Conditions, condition) - if len(applyErrors) != 0 { - return ctrl.Result{RequeueAfter: RequeueDelay}, nil + if len(applyErrors) > 0 { + return ctrl.Result{}, fmt.Errorf("failed to apply to all instances: %v", applyErrors) } if folder.ResyncPeriodHasElapsed() { diff --git a/controllers/notificationpolicy_controller.go b/controllers/notificationpolicy_controller.go index d831f03eb..5a40bcd11 100644 --- a/controllers/notificationpolicy_controller.go +++ b/controllers/notificationpolicy_controller.go @@ -39,6 +39,7 @@ import ( const ( conditionNotificationPolicySynchronized = "NotificationPolicySynchronized" + annotationAppliedNotificationPolicy = "operator.grafana.com/applied-notificationpolicy" ) // GrafanaNotificationPolicyReconciler reconciles a GrafanaNotificationPolicy object @@ -107,25 +108,19 @@ func (r *GrafanaNotificationPolicyReconciler) Reconcile(ctx context.Context, req } }() - instances, err := GetMatchingInstances(ctx, r.Client, notificationPolicy.Spec.InstanceSelector) - if err != nil { - setNoMatchingInstance(¬ificationPolicy.Status.Conditions, notificationPolicy.Generation, "ErrFetchingInstances", fmt.Sprintf("error occurred during fetching of instances: %s", err.Error())) - meta.RemoveStatusCondition(¬ificationPolicy.Status.Conditions, conditionNotificationPolicySynchronized) - r.Log.Error(err, "could not find matching instances") - return ctrl.Result{RequeueAfter: RequeueDelay}, err - } - - if len(instances.Items) == 0 { - meta.RemoveStatusCondition(¬ificationPolicy.Status.Conditions, conditionNotificationPolicySynchronized) - setNoMatchingInstance(¬ificationPolicy.Status.Conditions, notificationPolicy.Generation, "EmptyAPIReply", "Instances could not be fetched, reconciliation will be retried") - return ctrl.Result{}, nil + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, notificationPolicy.Spec.GrafanaCommonSpec, notificationPolicy.ObjectMeta.Namespace) + if err != nil || len(instances) == 0 { + NilOrEmptyInstanceListCondition(¬ificationPolicy.Status.Conditions, conditionNotificationPolicySynchronized, notificationPolicy.Generation, err) + controllerLog.Error(err, "could not find matching instances", "name", notificationPolicy.Name, "namespace", notificationPolicy.Namespace) + return ctrl.Result{RequeueAfter: RequeueDelay}, nil } removeNoMatchingInstance(¬ificationPolicy.Status.Conditions) + controllerLog.Info("found matching Grafana instances for notification policy", "count", len(instances)) applyErrors := make(map[string]string) appliedCount := 0 - for _, grafana := range instances.Items { + for _, grafana := range instances { // can be removed in go 1.22+ grafana := grafana appliedPolicy := grafana.Annotations[annotationAppliedNotificationPolicy] @@ -135,11 +130,6 @@ func (r *GrafanaNotificationPolicyReconciler) Reconcile(ctx context.Context, req } appliedCount++ - if grafana.Status.Stage != grafanav1beta1.OperatorStageComplete || grafana.Status.StageStatus != grafanav1beta1.OperatorStageResultSuccess { - controllerLog.Info("grafana instance not ready", "grafana", grafana.Name) - continue - } - err := r.reconcileWithInstance(ctx, &grafana, notificationPolicy) if err != nil { applyErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() @@ -201,19 +191,19 @@ func (r *GrafanaNotificationPolicyReconciler) resetInstance(ctx context.Context, func (r *GrafanaNotificationPolicyReconciler) finalize(ctx context.Context, notificationPolicy *grafanav1beta1.GrafanaNotificationPolicy) error { r.Log.Info("Finalizing GrafanaNotificationPolicy") - instances, err := GetMatchingInstances(ctx, r.Client, notificationPolicy.Spec.InstanceSelector) + instances, err := GetMatchingInstances(r.Log, ctx, r.Client, notificationPolicy.Spec.GrafanaCommonSpec, notificationPolicy.ObjectMeta.Namespace) if err != nil { return fmt.Errorf("fetching instances: %w", err) } - for _, i := range instances.Items { - instance := i - appliedPolicy := i.Annotations[annotationAppliedNotificationPolicy] + for _, grafana := range instances { + grafana := grafana + appliedPolicy := grafana.Annotations[annotationAppliedNotificationPolicy] if appliedPolicy != "" && appliedPolicy != notificationPolicy.NamespacedResource() { - r.Log.Info("instance already has a different notification policy applied - skipping", "grafana", instance.Name) + r.Log.Info("instance already has a different notification policy applied - skipping", "grafana", grafana.Name) continue } - if err := r.resetInstance(ctx, &instance); err != nil { + if err := r.resetInstance(ctx, &grafana); err != nil { return fmt.Errorf("resetting instance notification policy: %w", err) } } From 6b96bb59eec0880cfd3c2c20906d1c31a053b35d Mon Sep 17 00:00:00 2001 From: ste Date: Sat, 16 Nov 2024 22:47:10 +0100 Subject: [PATCH 06/10] test: Fix datasource assertion of removed lastMessage status field --- tests/e2e/examples/crossnamespace/assertions.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From f07ddea2c30a4df168878cb842ef952eac0d3703 Mon Sep 17 00:00:00 2001 From: ste Date: Sat, 16 Nov 2024 22:26:30 +0100 Subject: [PATCH 07/10] chore: Remove excess fields in test resource --- tests/example-resources.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/example-resources.yaml b/tests/example-resources.yaml index 5027d0325..2bbd8ed5f 100644 --- a/tests/example-resources.yaml +++ b/tests/example-resources.yaml @@ -63,7 +63,6 @@ spec: - refId: A relativeTimeRange: from: 600 - to: 0 datasourceUid: grafana-testdata model: drop: 0 @@ -82,7 +81,6 @@ spec: - refId: B relativeTimeRange: from: 600 - to: 0 datasourceUid: __expr__ model: conditions: @@ -115,7 +113,6 @@ spec: - refId: C relativeTimeRange: from: 600 - to: 0 datasourceUid: __expr__ model: conditions: @@ -145,9 +142,6 @@ spec: noDataState: NoData execErrState: Error for: 5m - annotations: {} - labels: {} - isPaused: false --- apiVersion: grafana.integreatly.org/v1beta1 kind: GrafanaDashboard From 5b89461b41835300c14d41acca6cc9bdadbed74e Mon Sep 17 00:00:00 2001 From: ste Date: Sun, 17 Nov 2024 13:04:36 +0100 Subject: [PATCH 08/10] fix: Add back support for NoMatchingInstances --- api/v1beta1/grafanadashboard_types.go | 2 +- api/v1beta1/grafanadatasource_types.go | 2 ++ api/v1beta1/grafanafolder_types.go | 2 +- .../crd/bases/grafana.integreatly.org_grafanadatasources.yaml | 1 + controllers/dashboard_controller.go | 2 ++ controllers/datasource_controller.go | 2 ++ controllers/grafanafolder_controller.go | 2 ++ .../crds/grafana.integreatly.org_grafanadatasources.yaml | 1 + deploy/kustomize/base/crds.yaml | 1 + docs/docs/api.md | 2 +- 10 files changed, 14 insertions(+), 3 deletions(-) diff --git a/api/v1beta1/grafanadashboard_types.go b/api/v1beta1/grafanadashboard_types.go index 6a7578eb4..6d7adeb9c 100644 --- a/api/v1beta1/grafanadashboard_types.go +++ b/api/v1beta1/grafanadashboard_types.go @@ -164,6 +164,7 @@ type GrafanaComDashboardReference struct { Revision *int `json:"revision,omitempty"` } +// TODO(V1) Remove // GrafanaDashboardStatus defines the observed state of GrafanaDashboard type GrafanaDashboardStatus struct { GrafanaCommonStatus `json:",inline"` @@ -171,7 +172,6 @@ type GrafanaDashboardStatus struct { ContentCache []byte `json:"contentCache,omitempty"` ContentTimestamp metav1.Time `json:"contentTimestamp,omitempty"` ContentUrl string `json:"contentUrl,omitempty"` - // TODO(V1) Remove // The dashboard instanceSelector can't find matching grafana instances NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` UID string `json:"uid,omitempty"` diff --git a/api/v1beta1/grafanadatasource_types.go b/api/v1beta1/grafanadatasource_types.go index ae8a6b4d3..1506e9fe2 100644 --- a/api/v1beta1/grafanadatasource_types.go +++ b/api/v1beta1/grafanadatasource_types.go @@ -79,10 +79,12 @@ type GrafanaDatasourceSpec struct { ValuesFrom []ValueFrom `json:"valuesFrom,omitempty"` } +// TODO(V1) Remove // GrafanaDatasourceStatus defines the observed state of GrafanaDatasource type GrafanaDatasourceStatus struct { GrafanaCommonStatus `json:",inline"` + // Deprecated: See status.conditions LastMessage string `json:"lastMessage,omitempty"` // The datasource instanceSelector can't find matching grafana instances NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` diff --git a/api/v1beta1/grafanafolder_types.go b/api/v1beta1/grafanafolder_types.go index fd62a504a..e6aa6ca37 100644 --- a/api/v1beta1/grafanafolder_types.go +++ b/api/v1beta1/grafanafolder_types.go @@ -56,11 +56,11 @@ type GrafanaFolderSpec struct { ParentFolderRef string `json:"parentFolderRef,omitempty"` } +// TODO(V1) Remove // GrafanaFolderStatus defines the observed state of GrafanaFolder type GrafanaFolderStatus struct { GrafanaCommonStatus `json:",inline"` - // TODO(V1) Remove // The folder instanceSelector can't find matching grafana instances NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"` } diff --git a/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml b/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml index d067cf690..b43405164 100644 --- a/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml +++ b/config/crd/bases/grafana.integreatly.org_grafanadatasources.yaml @@ -312,6 +312,7 @@ spec: description: Detect resource changes type: string lastMessage: + description: 'Deprecated: See status.conditions' type: string lastResync: description: Last time the resource was synchronized diff --git a/controllers/dashboard_controller.go b/controllers/dashboard_controller.go index c6e4faf17..a997ec41c 100644 --- a/controllers/dashboard_controller.go +++ b/controllers/dashboard_controller.go @@ -201,11 +201,13 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, cr.Spec.GrafanaCommonSpec, cr.ObjectMeta.Namespace) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(&cr.Status.Conditions, conditionDashboardSynchronized, cr.Generation, err) + cr.Status.NoMatchingInstances = true controllerLog.Error(err, "could not find matching instances", "name", cr.Name, "namespace", cr.Namespace) return ctrl.Result{RequeueAfter: RequeueDelay}, nil } removeNoMatchingInstance(&cr.Status.Conditions) + cr.Status.NoMatchingInstances = false controllerLog.Info("found matching Grafana instances for dashboard", "count", len(instances)) dashboardJson, err := r.fetchDashboardJson(ctx, cr) diff --git a/controllers/datasource_controller.go b/controllers/datasource_controller.go index 4298ace9f..32877ef44 100644 --- a/controllers/datasource_controller.go +++ b/controllers/datasource_controller.go @@ -199,11 +199,13 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, cr.Spec.GrafanaCommonSpec, cr.ObjectMeta.Namespace) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(&cr.Status.Conditions, conditionDatasourceSynchronized, cr.Generation, err) + cr.Status.NoMatchingInstances = true controllerLog.Error(err, "could not find matching instances", "name", cr.Name, "namespace", cr.Namespace) return ctrl.Result{RequeueAfter: RequeueDelay}, nil } removeNoMatchingInstance(&cr.Status.Conditions) + cr.Status.NoMatchingInstances = false controllerLog.Info("found matching Grafana instances for datasource", "count", len(instances)) datasource, hash, err := r.getDatasourceContent(ctx, cr) diff --git a/controllers/grafanafolder_controller.go b/controllers/grafanafolder_controller.go index 0d3da2221..141919db2 100644 --- a/controllers/grafanafolder_controller.go +++ b/controllers/grafanafolder_controller.go @@ -197,11 +197,13 @@ func (r *GrafanaFolderReconciler) Reconcile(ctx context.Context, req ctrl.Reques instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, folder.Spec.GrafanaCommonSpec, folder.ObjectMeta.Namespace) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(&folder.Status.Conditions, conditionFolderSynchronized, folder.Generation, err) + folder.Status.NoMatchingInstances = true controllerLog.Error(err, "could not find matching instances", "name", folder.Name, "namespace", folder.Namespace) return ctrl.Result{RequeueAfter: RequeueDelay}, nil } removeNoMatchingInstance(&folder.Status.Conditions) + folder.Status.NoMatchingInstances = false controllerLog.Info("found matching Grafana instances for folder", "count", len(instances)) applyErrors := make(map[string]string) 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 d067cf690..b43405164 100644 --- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanadatasources.yaml @@ -312,6 +312,7 @@ spec: description: Detect resource changes type: string lastMessage: + description: 'Deprecated: See status.conditions' type: string lastResync: description: Last time the resource was synchronized diff --git a/deploy/kustomize/base/crds.yaml b/deploy/kustomize/base/crds.yaml index fcacea3fe..ce1b64e02 100644 --- a/deploy/kustomize/base/crds.yaml +++ b/deploy/kustomize/base/crds.yaml @@ -1415,6 +1415,7 @@ spec: description: Detect resource changes type: string lastMessage: + description: 'Deprecated: See status.conditions' type: string lastResync: description: Last time the resource was synchronized diff --git a/docs/docs/api.md b/docs/docs/api.md index 6a09c71c5..445ad20b8 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -2832,7 +2832,7 @@ GrafanaDatasourceStatus defines the observed state of GrafanaDatasource lastMessage string -
    + Deprecated: See status.conditions
    false From 2ec8f036de25e754bc9cd27dfda2e77dbdad3b6e Mon Sep 17 00:00:00 2001 From: ste Date: Sun, 17 Nov 2024 13:23:13 +0100 Subject: [PATCH 09/10] fix: allowCrossNamespaceImport is mutable leaving potential resources in unmatched instances --- controllers/controller_shared.go | 20 +++++++++++++++++++ .../grafanaalertrulegroup_controller.go | 8 ++++---- controllers/grafanacontactpoint_controller.go | 8 ++++---- controllers/notificationpolicy_controller.go | 2 +- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/controllers/controller_shared.go b/controllers/controller_shared.go index 3101cabe8..afc6220a3 100644 --- a/controllers/controller_shared.go +++ b/controllers/controller_shared.go @@ -75,6 +75,26 @@ func GetMatchingInstances(log logr.Logger, ctx context.Context, k8sClient client return selectedList, nil } +func GetAllInstances(ctx context.Context, k8sClient client.Client) ([]v1beta1.Grafana, error) { + var list v1beta1.GrafanaList + err := k8sClient.List(ctx, &list) + if err != nil || len(list.Items) == 0 { + return []v1beta1.Grafana{}, err + } + + selectedList := []v1beta1.Grafana{} + for _, instance := range list.Items { + // admin url is required to interact with Grafana + // the instance or route might not yet be ready + if instance.Status.Stage != v1beta1.OperatorStageComplete || instance.Status.StageStatus != v1beta1.OperatorStageResultSuccess { + continue + } + selectedList = append(selectedList, instance) + } + + return selectedList, nil +} + // getFolderUID fetches the folderUID from an existing GrafanaFolder CR declared in the specified namespace func getFolderUID(ctx context.Context, k8sClient client.Client, ref operatorapi.FolderReferencer) (string, error) { if ref.FolderUID() != "" { diff --git a/controllers/grafanaalertrulegroup_controller.go b/controllers/grafanaalertrulegroup_controller.go index 75aeaf2f0..348e61d14 100644 --- a/controllers/grafanaalertrulegroup_controller.go +++ b/controllers/grafanaalertrulegroup_controller.go @@ -285,13 +285,13 @@ func (r *GrafanaAlertRuleGroupReconciler) finalize(ctx context.Context, group *g r.Log.Info("ignoring finalization logic as folder no longer exists") return nil //nolint:nilerr } - instances, err := GetMatchingInstances(r.Log, ctx, r.Client, group.Spec.GrafanaCommonSpec, group.ObjectMeta.Namespace) + instances, err := GetAllInstances(ctx, r.Client) if err != nil { return fmt.Errorf("fetching instances: %w", err) } - for _, i := range instances { - instance := i - if err := r.removeFromInstance(ctx, &instance, group, folderUID); err != nil { + for _, grafana := range instances { + grafana := grafana + if err := r.removeFromInstance(ctx, &grafana, group, folderUID); err != nil { return fmt.Errorf("removing from instance %w", err) } } diff --git a/controllers/grafanacontactpoint_controller.go b/controllers/grafanacontactpoint_controller.go index b4fcd6f45..edf425aed 100644 --- a/controllers/grafanacontactpoint_controller.go +++ b/controllers/grafanacontactpoint_controller.go @@ -232,13 +232,13 @@ func (r *GrafanaContactPointReconciler) getContactPointFromUID(ctx context.Conte func (r *GrafanaContactPointReconciler) finalize(ctx context.Context, contactPoint *grafanav1beta1.GrafanaContactPoint) error { r.Log.Info("Finalizing GrafanaContactPoint") - instances, err := GetMatchingInstances(r.Log, ctx, r.Client, contactPoint.Spec.GrafanaCommonSpec, contactPoint.ObjectMeta.Namespace) + instances, err := GetAllInstances(ctx, r.Client) if err != nil { return fmt.Errorf("fetching instances: %w", err) } - for _, i := range instances { - instance := i - if err := r.removeFromInstance(ctx, &instance, contactPoint); err != nil { + for _, grafana := range instances { + grafana := grafana + if err := r.removeFromInstance(ctx, &grafana, contactPoint); err != nil { return fmt.Errorf("removing contact point from instance: %w", err) } } diff --git a/controllers/notificationpolicy_controller.go b/controllers/notificationpolicy_controller.go index 5a40bcd11..14622bb40 100644 --- a/controllers/notificationpolicy_controller.go +++ b/controllers/notificationpolicy_controller.go @@ -191,7 +191,7 @@ func (r *GrafanaNotificationPolicyReconciler) resetInstance(ctx context.Context, func (r *GrafanaNotificationPolicyReconciler) finalize(ctx context.Context, notificationPolicy *grafanav1beta1.GrafanaNotificationPolicy) error { r.Log.Info("Finalizing GrafanaNotificationPolicy") - instances, err := GetMatchingInstances(r.Log, ctx, r.Client, notificationPolicy.Spec.GrafanaCommonSpec, notificationPolicy.ObjectMeta.Namespace) + instances, err := GetAllInstances(ctx, r.Client) if err != nil { return fmt.Errorf("fetching instances: %w", err) } From d5d23eeb85e3e188452d20afe98130bba3af4f01 Mon Sep 17 00:00:00 2001 From: ste Date: Sun, 17 Nov 2024 14:07:17 +0100 Subject: [PATCH 10/10] refactor: GetMatchingInstances to take interface --- api/common_interfaces.go | 7 +++++++ api/v1beta1/grafanaalertrulegroup_types.go | 4 ++++ api/v1beta1/grafanacontactpoint_types.go | 4 ++++ api/v1beta1/grafanadashboard_types.go | 4 ++++ api/v1beta1/grafanadatasource_types.go | 4 ++++ api/v1beta1/grafanafolder_types.go | 4 ++++ api/v1beta1/grafananotificationpolicy_types.go | 4 ++++ controllers/controller_shared.go | 12 +++++++----- controllers/dashboard_controller.go | 2 +- controllers/datasource_controller.go | 2 +- controllers/grafanaalertrulegroup_controller.go | 2 +- controllers/grafanacontactpoint_controller.go | 2 +- controllers/grafanafolder_controller.go | 2 +- controllers/notificationpolicy_controller.go | 2 +- 14 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 api/common_interfaces.go diff --git a/api/common_interfaces.go b/api/common_interfaces.go new file mode 100644 index 000000000..fb819d5ad --- /dev/null +++ b/api/common_interfaces.go @@ -0,0 +1,7 @@ +package api + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +type CommonResource interface { + MatchConditions() (*metav1.LabelSelector, string, *bool) +} diff --git a/api/v1beta1/grafanaalertrulegroup_types.go b/api/v1beta1/grafanaalertrulegroup_types.go index 231e66503..de6a45808 100644 --- a/api/v1beta1/grafanaalertrulegroup_types.go +++ b/api/v1beta1/grafanaalertrulegroup_types.go @@ -177,3 +177,7 @@ type GrafanaAlertRuleGroupList struct { func init() { SchemeBuilder.Register(&GrafanaAlertRuleGroup{}, &GrafanaAlertRuleGroupList{}) } + +func (in *GrafanaAlertRuleGroup) MatchConditions() (*metav1.LabelSelector, string, *bool) { + return in.Spec.InstanceSelector, in.ObjectMeta.Namespace, in.Spec.AllowCrossNamespaceImport +} diff --git a/api/v1beta1/grafanacontactpoint_types.go b/api/v1beta1/grafanacontactpoint_types.go index 996eb060a..a2ca7b6ce 100644 --- a/api/v1beta1/grafanacontactpoint_types.go +++ b/api/v1beta1/grafanacontactpoint_types.go @@ -89,3 +89,7 @@ func (in *GrafanaContactPoint) ResyncPeriodHasElapsed() bool { deadline := in.Status.LastResync.Add(in.Spec.ResyncPeriod.Duration) return time.Now().After(deadline) } + +func (in *GrafanaContactPoint) MatchConditions() (*metav1.LabelSelector, string, *bool) { + return in.Spec.InstanceSelector, in.ObjectMeta.Namespace, in.Spec.AllowCrossNamespaceImport +} diff --git a/api/v1beta1/grafanadashboard_types.go b/api/v1beta1/grafanadashboard_types.go index 6d7adeb9c..2199e5b93 100644 --- a/api/v1beta1/grafanadashboard_types.go +++ b/api/v1beta1/grafanadashboard_types.go @@ -354,3 +354,7 @@ func (in *GrafanaDashboardList) Find(namespace string, name string) *GrafanaDash func init() { SchemeBuilder.Register(&GrafanaDashboard{}, &GrafanaDashboardList{}) } + +func (in *GrafanaDashboard) MatchConditions() (*metav1.LabelSelector, string, *bool) { + return in.Spec.InstanceSelector, in.ObjectMeta.Namespace, in.Spec.AllowCrossNamespaceImport +} diff --git a/api/v1beta1/grafanadatasource_types.go b/api/v1beta1/grafanadatasource_types.go index 1506e9fe2..359182c69 100644 --- a/api/v1beta1/grafanadatasource_types.go +++ b/api/v1beta1/grafanadatasource_types.go @@ -159,3 +159,7 @@ func (in *GrafanaDatasourceList) Find(namespace string, name string) *GrafanaDat func init() { SchemeBuilder.Register(&GrafanaDatasource{}, &GrafanaDatasourceList{}) } + +func (in *GrafanaDatasource) MatchConditions() (*metav1.LabelSelector, string, *bool) { + return in.Spec.InstanceSelector, in.ObjectMeta.Namespace, in.Spec.AllowCrossNamespaceImport +} diff --git a/api/v1beta1/grafanafolder_types.go b/api/v1beta1/grafanafolder_types.go index e6aa6ca37..eafcd3d68 100644 --- a/api/v1beta1/grafanafolder_types.go +++ b/api/v1beta1/grafanafolder_types.go @@ -160,3 +160,7 @@ func (in *GrafanaFolder) ResyncPeriodHasElapsed() bool { deadline := in.Status.LastResync.Add(in.Spec.ResyncPeriod.Duration) return time.Now().After(deadline) } + +func (in *GrafanaFolder) MatchConditions() (*metav1.LabelSelector, string, *bool) { + return in.Spec.InstanceSelector, in.ObjectMeta.Namespace, in.Spec.AllowCrossNamespaceImport +} diff --git a/api/v1beta1/grafananotificationpolicy_types.go b/api/v1beta1/grafananotificationpolicy_types.go index f55b235d8..a14133fa8 100644 --- a/api/v1beta1/grafananotificationpolicy_types.go +++ b/api/v1beta1/grafananotificationpolicy_types.go @@ -154,3 +154,7 @@ type GrafanaNotificationPolicyList struct { func init() { SchemeBuilder.Register(&GrafanaNotificationPolicy{}, &GrafanaNotificationPolicyList{}) } + +func (in *GrafanaNotificationPolicy) MatchConditions() (*metav1.LabelSelector, string, *bool) { + return in.Spec.InstanceSelector, in.ObjectMeta.Namespace, in.Spec.AllowCrossNamespaceImport +} diff --git a/controllers/controller_shared.go b/controllers/controller_shared.go index afc6220a3..04d18f8d2 100644 --- a/controllers/controller_shared.go +++ b/controllers/controller_shared.go @@ -34,15 +34,17 @@ const ( //+kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch;delete -func GetMatchingInstances(log logr.Logger, ctx context.Context, k8sClient client.Client, commonSpec v1beta1.GrafanaCommonSpec, namespace string) ([]v1beta1.Grafana, error) { - if commonSpec.InstanceSelector.MatchLabels == nil { +func GetMatchingInstances(log logr.Logger, ctx context.Context, k8sClient client.Client, cr operatorapi.CommonResource) ([]v1beta1.Grafana, error) { + instanceSelector, namespace, allowCrossNamespaceImport := cr.MatchConditions() + if instanceSelector.MatchLabels == nil { return []v1beta1.Grafana{}, nil } opts := []client.ListOption{ - client.MatchingLabels(commonSpec.InstanceSelector.MatchLabels), + client.MatchingLabels(instanceSelector.MatchLabels), } - if commonSpec.AllowCrossNamespaceImport != nil && !*commonSpec.AllowCrossNamespaceImport { + + if allowCrossNamespaceImport != nil && !*allowCrossNamespaceImport { // Only query resource namespace opts = append(opts, client.InNamespace(namespace)) } @@ -56,7 +58,7 @@ func GetMatchingInstances(log logr.Logger, ctx context.Context, k8sClient client selectedList := []v1beta1.Grafana{} var unready_instances []string for _, instance := range list.Items { - selected := labelsSatisfyMatchExpressions(instance.Labels, commonSpec.InstanceSelector.MatchExpressions) + selected := labelsSatisfyMatchExpressions(instance.Labels, instanceSelector.MatchExpressions) if !selected { continue } diff --git a/controllers/dashboard_controller.go b/controllers/dashboard_controller.go index a997ec41c..f36591223 100644 --- a/controllers/dashboard_controller.go +++ b/controllers/dashboard_controller.go @@ -198,7 +198,7 @@ func (r *GrafanaDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Req } }() - instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, cr.Spec.GrafanaCommonSpec, cr.ObjectMeta.Namespace) + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, cr) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(&cr.Status.Conditions, conditionDashboardSynchronized, cr.Generation, err) cr.Status.NoMatchingInstances = true diff --git a/controllers/datasource_controller.go b/controllers/datasource_controller.go index 32877ef44..e3b16f9e7 100644 --- a/controllers/datasource_controller.go +++ b/controllers/datasource_controller.go @@ -196,7 +196,7 @@ func (r *GrafanaDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Re // Overwrite OrgID to ensure the field is useless cr.Spec.Datasource.OrgID = nil - instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, cr.Spec.GrafanaCommonSpec, cr.ObjectMeta.Namespace) + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, cr) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(&cr.Status.Conditions, conditionDatasourceSynchronized, cr.Generation, err) cr.Status.NoMatchingInstances = true diff --git a/controllers/grafanaalertrulegroup_controller.go b/controllers/grafanaalertrulegroup_controller.go index 348e61d14..7d0f0356e 100644 --- a/controllers/grafanaalertrulegroup_controller.go +++ b/controllers/grafanaalertrulegroup_controller.go @@ -106,7 +106,7 @@ func (r *GrafanaAlertRuleGroupReconciler) Reconcile(ctx context.Context, req ctr } }() - instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, group.Spec.GrafanaCommonSpec, group.ObjectMeta.Namespace) + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, group) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(&group.Status.Conditions, conditionAlertGroupSynchronized, group.Generation, err) controllerLog.Error(err, "could not find matching instances", "name", group.Name, "namespace", group.Namespace) diff --git a/controllers/grafanacontactpoint_controller.go b/controllers/grafanacontactpoint_controller.go index edf425aed..73791cd9c 100644 --- a/controllers/grafanacontactpoint_controller.go +++ b/controllers/grafanacontactpoint_controller.go @@ -110,7 +110,7 @@ func (r *GrafanaContactPointReconciler) Reconcile(ctx context.Context, req ctrl. } }() - instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, contactPoint.Spec.GrafanaCommonSpec, contactPoint.ObjectMeta.Namespace) + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, contactPoint) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(&contactPoint.Status.Conditions, conditionContactPointSynchronized, contactPoint.Generation, err) controllerLog.Error(err, "could not find matching instances", "name", contactPoint.Name, "namespace", contactPoint.Namespace) diff --git a/controllers/grafanafolder_controller.go b/controllers/grafanafolder_controller.go index 141919db2..07f049e08 100644 --- a/controllers/grafanafolder_controller.go +++ b/controllers/grafanafolder_controller.go @@ -194,7 +194,7 @@ func (r *GrafanaFolderReconciler) Reconcile(ctx context.Context, req ctrl.Reques } removeInvalidSpec(&folder.Status.Conditions) - instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, folder.Spec.GrafanaCommonSpec, folder.ObjectMeta.Namespace) + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, folder) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(&folder.Status.Conditions, conditionFolderSynchronized, folder.Generation, err) folder.Status.NoMatchingInstances = true diff --git a/controllers/notificationpolicy_controller.go b/controllers/notificationpolicy_controller.go index 14622bb40..26541ad75 100644 --- a/controllers/notificationpolicy_controller.go +++ b/controllers/notificationpolicy_controller.go @@ -108,7 +108,7 @@ func (r *GrafanaNotificationPolicyReconciler) Reconcile(ctx context.Context, req } }() - instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, notificationPolicy.Spec.GrafanaCommonSpec, notificationPolicy.ObjectMeta.Namespace) + instances, err := GetMatchingInstances(controllerLog, ctx, r.Client, notificationPolicy) if err != nil || len(instances) == 0 { NilOrEmptyInstanceListCondition(¬ificationPolicy.Status.Conditions, conditionNotificationPolicySynchronized, notificationPolicy.Generation, err) controllerLog.Error(err, "could not find matching instances", "name", notificationPolicy.Name, "namespace", notificationPolicy.Namespace)