From 794822be90f491d1405c79c0bd6533df21b1afbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20S=C3=BC=C3=9F?= Date: Tue, 13 Feb 2024 14:50:41 +0100 Subject: [PATCH] feat: initial alerting support --- PROJECT | 9 + api/v1beta1/grafanaalertrulegroup_types.go | 129 ++++++++ api/v1beta1/zz_generated.deepcopy.go | 101 ++++++ ...ntegreatly.org_grafanaalertrulegroups.yaml | 186 +++++++++++ config/crd/kustomization.yaml | 3 + ...cainjection_in_grafanaalertrulegroups.yaml | 7 + .../webhook_in_grafanaalertrulegroups.yaml | 16 + ...ntegreatly.org_grafanaalertrulegroups.yaml | 266 +++++++++++++++ config/manager/kustomization.yaml | 2 +- ...rafana-operator.clusterserviceversion.yaml | 6 + .../grafanaalertrulegroup_editor_role.yaml | 31 ++ .../grafanaalertrulegroup_viewer_role.yaml | 27 ++ config/rbac/role.yaml | 26 ++ ...grafana_v1beta1_grafanaalertrulegroup.yaml | 66 ++++ config/samples/kustomization.yaml | 1 + controllers/client/grafana_client.go | 48 +++ controllers/controller_shared.go | 26 ++ .../grafanaalertrulegroup_controller.go | 310 ++++++++++++++++++ ...ntegreatly.org_grafanaalertrulegroups.yaml | 186 +++++++++++ go.mod | 26 +- go.sum | 95 ++++-- main.go | 7 + 22 files changed, 1549 insertions(+), 25 deletions(-) create mode 100644 api/v1beta1/grafanaalertrulegroup_types.go create mode 100644 config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml create mode 100644 config/crd/patches/cainjection_in_grafanaalertrulegroups.yaml create mode 100644 config/crd/patches/webhook_in_grafanaalertrulegroups.yaml create mode 100644 config/grafana.integreatly.org_grafanaalertrulegroups.yaml create mode 100644 config/rbac/grafanaalertrulegroup_editor_role.yaml create mode 100644 config/rbac/grafanaalertrulegroup_viewer_role.yaml create mode 100644 config/samples/grafana_v1beta1_grafanaalertrulegroup.yaml create mode 100644 controllers/grafanaalertrulegroup_controller.go create mode 100644 deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml diff --git a/PROJECT b/PROJECT index 02a361eba..34920e852 100644 --- a/PROJECT +++ b/PROJECT @@ -42,4 +42,13 @@ resources: kind: GrafanaFolder path: github.com/grafana/grafana-operator/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: integreatly.org + group: grafana + kind: GrafanaAlertRuleGroup + path: github.com/grafana/grafana-operator/api/v1beta1 + version: v1beta1 version: "3" diff --git a/api/v1beta1/grafanaalertrulegroup_types.go b/api/v1beta1/grafanaalertrulegroup_types.go new file mode 100644 index 000000000..651b2e662 --- /dev/null +++ b/api/v1beta1/grafanaalertrulegroup_types.go @@ -0,0 +1,129 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "github.com/grafana/grafana-openapi-client-go/models" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GrafanaAlertRuleGroupSpec defines the desired state of GrafanaAlertRuleGroup +type GrafanaAlertRuleGroupSpec 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 + InstanceSelector *metav1.LabelSelector `json:"instanceSelector"` + + // UID of the folder containing this rule group + FolderUID string `json:"folderUID"` + + Rules []AlertRule `json:"rules"` + + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=duration + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" + // +kubebuilder:validation:Required + Interval metav1.Duration `json:"interval"` +} + +// AlertRule defines a specific rule to be evaluated. It is based on the upstream model with some k8s specific type mappings +type AlertRule struct { + Annotations map[string]string `json:"annotations,omitempty"` + + Condition string `json:"condition"` + + // +kubebuilder:validation:Required + Data []*AlertQuery `json:"data"` + + // +kubebuilder:validation:Enum=OK;Alerting;Error + ExecErrState string `json:"execErrState"` + + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=duration + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" + // +kubebuilder:validation:Required + For *metav1.Duration `json:"for"` + + IsPaused bool `json:"isPaused,omitempty"` + + Labels map[string]string `json:"labels,omitempty"` + + // +kubebuilder:validation:Enum=Alerting;NoData;OK + NoDataState *string `json:"noDataState"` + + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=190 + // +kubebuilder:example="Always firing" + Title string `json:"title"` + + // +kubebuilder:validation:Pattern="^[a-zA-Z0-9-_]+$" + UID string `json:"uid"` +} + +type AlertQuery struct { + // Grafana data source unique identifier; it should be '__expr__' for a Server Side Expression operation. + DatasourceUID string `json:"datasourceUid,omitempty"` + + // JSON is the raw JSON query and includes the above properties as well as custom properties. + Model *apiextensions.JSON `json:"model,omitempty"` + + // QueryType is an optional identifier for the type of query. + // It can be used to distinguish different types of queries. + QueryType string `json:"queryType,omitempty"` + + // RefID is the unique identifier of the query, set by the frontend call. + RefID string `json:"refId,omitempty"` + + // relative time range + RelativeTimeRange *models.RelativeTimeRange `json:"relativeTimeRange,omitempty"` +} + +// GrafanaAlertRuleGroupStatus defines the observed state of GrafanaAlertRuleGroup +type GrafanaAlertRuleGroupStatus struct { + Conditions []metav1.Condition `json:"conditions"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// GrafanaAlertRuleGroup is the Schema for the grafanaalertrulegroups API +type GrafanaAlertRuleGroup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GrafanaAlertRuleGroupSpec `json:"spec,omitempty"` + Status GrafanaAlertRuleGroupStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// GrafanaAlertRuleGroupList contains a list of GrafanaAlertRuleGroup +type GrafanaAlertRuleGroupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GrafanaAlertRuleGroup `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GrafanaAlertRuleGroup{}, &GrafanaAlertRuleGroupList{}) +} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 2241118f6..0a954c2d5 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -329,6 +329,107 @@ func (in *Grafana) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrafanaAlertRuleGroup) DeepCopyInto(out *GrafanaAlertRuleGroup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaAlertRuleGroup. +func (in *GrafanaAlertRuleGroup) DeepCopy() *GrafanaAlertRuleGroup { + if in == nil { + return nil + } + out := new(GrafanaAlertRuleGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GrafanaAlertRuleGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrafanaAlertRuleGroupList) DeepCopyInto(out *GrafanaAlertRuleGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GrafanaAlertRuleGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaAlertRuleGroupList. +func (in *GrafanaAlertRuleGroupList) DeepCopy() *GrafanaAlertRuleGroupList { + if in == nil { + return nil + } + out := new(GrafanaAlertRuleGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GrafanaAlertRuleGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// 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 + if in.InstanceSelector != nil { + in, out := &in.InstanceSelector, &out.InstanceSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaAlertRuleGroupSpec. +func (in *GrafanaAlertRuleGroupSpec) DeepCopy() *GrafanaAlertRuleGroupSpec { + if in == nil { + return nil + } + out := new(GrafanaAlertRuleGroupSpec) + in.DeepCopyInto(out) + 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 diff --git a/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml b/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml new file mode 100644 index 000000000..2a7a32696 --- /dev/null +++ b/config/crd/bases/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -0,0 +1,186 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: grafanaalertrulegroups.grafana.integreatly.org +spec: + group: grafana.integreatly.org + names: + kind: GrafanaAlertRuleGroup + listKind: GrafanaAlertRuleGroupList + plural: grafanaalertrulegroups + singular: grafanaalertrulegroup + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + folderUID: + type: string + instanceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + interval: + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + resyncPeriod: + default: 1m + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + rules: + items: + properties: + annotations: + additionalProperties: + type: string + type: object + condition: + type: string + data: + items: + properties: + datasourceUid: + type: string + model: + x-kubernetes-preserve-unknown-fields: true + queryType: + type: string + refId: + type: string + relativeTimeRange: + properties: + from: + format: int64 + type: integer + to: + format: int64 + type: integer + type: object + type: object + type: array + execErrState: + enum: + - OK + - Alerting + - Error + type: string + for: + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + isPaused: + type: boolean + labels: + additionalProperties: + type: string + type: object + noDataState: + enum: + - Alerting + - NoData + - OK + type: string + title: + example: Always firing + maxLength: 190 + minLength: 1 + type: string + uid: + pattern: ^[a-zA-Z0-9-_]+$ + type: string + required: + - condition + - data + - execErrState + - for + - noDataState + - title + - uid + type: object + type: array + required: + - folderUID + - instanceSelector + - interval + - rules + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + 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 + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index db580130c..2187eb794 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -6,6 +6,7 @@ resources: - bases/grafana.integreatly.org_grafanadashboards.yaml - bases/grafana.integreatly.org_grafanadatasources.yaml - bases/grafana.integreatly.org_grafanafolders.yaml +- bases/grafana.integreatly.org_grafanaalertrulegroups.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -15,6 +16,7 @@ patchesStrategicMerge: #- patches/webhook_in_grafanadashboards.yaml #- patches/webhook_in_grafanadatasources.yaml #- patches/webhook_in_grafanafolders.yaml +#- patches/webhook_in_grafanaalertrulegroups.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -23,6 +25,7 @@ patchesStrategicMerge: #- patches/cainjection_in_grafanadashboards.yaml #- patches/cainjection_in_grafanadatasources.yaml #- patches/cainjection_in_grafanafolders.yaml +#- patches/cainjection_in_grafanaalertrulegroups.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_grafanaalertrulegroups.yaml b/config/crd/patches/cainjection_in_grafanaalertrulegroups.yaml new file mode 100644 index 000000000..67579ffa1 --- /dev/null +++ b/config/crd/patches/cainjection_in_grafanaalertrulegroups.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: grafanaalertrulegroups.grafana.integreatly.org diff --git a/config/crd/patches/webhook_in_grafanaalertrulegroups.yaml b/config/crd/patches/webhook_in_grafanaalertrulegroups.yaml new file mode 100644 index 000000000..bddb899db --- /dev/null +++ b/config/crd/patches/webhook_in_grafanaalertrulegroups.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: grafanaalertrulegroups.grafana.integreatly.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/grafana.integreatly.org_grafanaalertrulegroups.yaml b/config/grafana.integreatly.org_grafanaalertrulegroups.yaml new file mode 100644 index 000000000..c3f6262a9 --- /dev/null +++ b/config/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -0,0 +1,266 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: grafanaalertrulegroups.grafana.integreatly.org +spec: + group: grafana.integreatly.org + names: + kind: GrafanaAlertRuleGroup + listKind: GrafanaAlertRuleGroupList + plural: grafanaalertrulegroups + singular: grafanaalertrulegroup + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: GrafanaAlertRuleGroup is the Schema for the grafanaalertrulegroups + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GrafanaAlertRuleGroupSpec defines the desired state of GrafanaAlertRuleGroup + properties: + folderUID: + description: UID of the folder containing this rule group + type: string + instanceSelector: + description: selects Grafanas for import + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + interval: + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + resyncPeriod: + default: 1m + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + rules: + items: + description: AlertRule defines a specific rule to be evaluated. + It is based on the upstream model with some k8s specific type + mappings + properties: + annotations: + additionalProperties: + type: string + type: object + condition: + type: string + data: + items: + properties: + datasourceUid: + description: Grafana data source unique identifier; it + should be '__expr__' for a Server Side Expression operation. + type: string + model: + description: JSON is the raw JSON query and includes the + above properties as well as custom properties. + x-kubernetes-preserve-unknown-fields: true + queryType: + description: QueryType is an optional identifier for the + type of query. It can be used to distinguish different + types of queries. + type: string + refId: + description: RefID is the unique identifier of the query, + set by the frontend call. + type: string + relativeTimeRange: + description: relative time range + properties: + from: + description: from + format: int64 + type: integer + to: + description: to + format: int64 + type: integer + type: object + type: object + type: array + execErrState: + enum: + - OK + - Alerting + - Error + type: string + for: + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + isPaused: + type: boolean + labels: + additionalProperties: + type: string + type: object + noDataState: + enum: + - Alerting + - NoData + - OK + type: string + title: + example: Always firing + maxLength: 190 + minLength: 1 + type: string + uid: + pattern: ^[a-zA-Z0-9-_]+$ + type: string + required: + - condition + - data + - execErrState + - for + - noDataState + - title + - uid + type: object + type: array + required: + - folderUID + - instanceSelector + - interval + - rules + type: object + status: + description: GrafanaAlertRuleGroupStatus defines the observed state of + GrafanaAlertRuleGroup + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + 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. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 15688959d..bfc3974cc 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -15,4 +15,4 @@ configMapGenerator: images: - name: controller newName: ghcr.io/grafana/grafana-operator - newTag: v5.6.1 + newTag: v5.6.3 diff --git a/config/manifests/bases/grafana-operator.clusterserviceversion.yaml b/config/manifests/bases/grafana-operator.clusterserviceversion.yaml index 5a07268df..c548df628 100644 --- a/config/manifests/bases/grafana-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/grafana-operator.clusterserviceversion.yaml @@ -16,6 +16,12 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: GrafanaAlertRuleGroup is the Schema for the grafanaalertrulegroups + API + displayName: Grafana Alert Rule Group + kind: GrafanaAlertRuleGroup + name: grafanaalertrulegroups.grafana.integreatly.org + version: v1beta1 - description: GrafanaDashboard is the Schema for the grafanadashboards API displayName: Grafana Dashboard kind: GrafanaDashboard diff --git a/config/rbac/grafanaalertrulegroup_editor_role.yaml b/config/rbac/grafanaalertrulegroup_editor_role.yaml new file mode 100644 index 000000000..61593a7b0 --- /dev/null +++ b/config/rbac/grafanaalertrulegroup_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit grafanaalertrulegroups. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: grafanaalertrulegroup-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: grafana-operator + app.kubernetes.io/part-of: grafana-operator + app.kubernetes.io/managed-by: kustomize + name: grafanaalertrulegroup-editor-role +rules: +- apiGroups: + - grafana.integreatly.org + resources: + - grafanaalertrulegroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - grafana.integreatly.org + resources: + - grafanaalertrulegroups/status + verbs: + - get diff --git a/config/rbac/grafanaalertrulegroup_viewer_role.yaml b/config/rbac/grafanaalertrulegroup_viewer_role.yaml new file mode 100644 index 000000000..844c30357 --- /dev/null +++ b/config/rbac/grafanaalertrulegroup_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view grafanaalertrulegroups. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: grafanaalertrulegroup-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: grafana-operator + app.kubernetes.io/part-of: grafana-operator + app.kubernetes.io/managed-by: kustomize + name: grafanaalertrulegroup-viewer-role +rules: +- apiGroups: + - grafana.integreatly.org + resources: + - grafanaalertrulegroups + verbs: + - get + - list + - watch +- apiGroups: + - grafana.integreatly.org + resources: + - grafanaalertrulegroups/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 408c87b1b..c0c4dd603 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -42,6 +42,32 @@ rules: - patch - update - watch +- apiGroups: + - grafana.integreatly.org + resources: + - grafanaalertrulegroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - grafana.integreatly.org + resources: + - grafanaalertrulegroups/finalizers + verbs: + - update +- apiGroups: + - grafana.integreatly.org + resources: + - grafanaalertrulegroups/status + verbs: + - get + - patch + - update - apiGroups: - grafana.integreatly.org resources: diff --git a/config/samples/grafana_v1beta1_grafanaalertrulegroup.yaml b/config/samples/grafana_v1beta1_grafanaalertrulegroup.yaml new file mode 100644 index 000000000..070daaa3f --- /dev/null +++ b/config/samples/grafana_v1beta1_grafanaalertrulegroup.yaml @@ -0,0 +1,66 @@ +apiVersion: grafana.integreatly.org/v1beta1 +kind: GrafanaAlertRuleGroup +metadata: + labels: + app.kubernetes.io/name: grafanaalertrulegroup + app.kubernetes.io/instance: grafanaalertrulegroup-sample + app.kubernetes.io/part-of: grafana-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: grafana-operator + name: grafanaalertrulegroup-sample +spec: + folderUID: f9b0a98d-2ed3-45a6-9521-18679c74d4f1 + instanceSelector: + dashboards: "grafana" + interval: 5m + rules: + - condition: B + data: + - datasourceUid: grafanacloud-demoinfra-prom + model: + datasource: + type: prometheus + uid: grafanacloud-demoinfra-prom + editorMode: code + expr: weather_temp_c{} + instant: true + intervalMs: 1000 + legendFormat: __auto + maxDataPoints: 43200 + range: false + refId: A + refId: A + relativeTimeRange: + from: 600 + - datasourceUid: __expr__ + model: + conditions: + - evaluator: + params: + - 0 + type: gt + operator: + type: and + query: + params: + - C + reducer: + params: [] + type: last + type: query + datasource: + type: __expr__ + uid: __expr__ + expression: A + intervalMs: 1000 + maxDataPoints: 43200 + refId: B + type: threshold + refId: B + relativeTimeRange: + from: 600 + execErrState: Error + for: 5m0s + noDataState: NoData + title: Temperature below freezing + uid: 4843de5c-4f8a-4af0-9509-23526a04faf8 diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 11f287f25..b67312ec7 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -4,4 +4,5 @@ resources: - grafana_v1beta1_grafanadashboard.yaml - grafana_v1beta1_grafanadatasource.yaml - grafana_v1beta1_grafanafolder.yaml +- grafana_v1beta1_grafanaalertrulegroup.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/client/grafana_client.go b/controllers/client/grafana_client.go index 9dc877b0b..d8f5e948e 100644 --- a/controllers/client/grafana_client.go +++ b/controllers/client/grafana_client.go @@ -10,7 +10,9 @@ import ( "github.com/grafana/grafana-operator/v5/controllers/metrics" v1 "k8s.io/api/core/v1" + httptransport "github.com/go-openapi/runtime/client" grapi "github.com/grafana/grafana-api-golang-client" + genapi "github.com/grafana/grafana-openapi-client-go/client" "github.com/grafana/grafana-operator/v5/api/v1beta1" "github.com/grafana/grafana-operator/v5/controllers/config" "github.com/grafana/grafana-operator/v5/controllers/model" @@ -167,3 +169,49 @@ func NewGrafanaClient(ctx context.Context, c client.Client, grafana *v1beta1.Gra return grafanaClient, nil } + +func NewGeneratedGrafanaClient(ctx context.Context, c client.Client, grafana *v1beta1.Grafana) (*genapi.GrafanaHTTPAPI, error) { + var timeout time.Duration + if grafana.Spec.Client != nil && grafana.Spec.Client.TimeoutSeconds != nil { + timeout = time.Duration(*grafana.Spec.Client.TimeoutSeconds) + if timeout < 0 { + timeout = 0 + } + } else { + timeout = 10 + } + + credentials, err := getAdminCredentials(ctx, c, grafana) + if err != nil { + return nil, err + } + + gURL, err := url.Parse(grafana.Status.AdminUrl) + if err != nil { + return nil, fmt.Errorf("parsing url for client: %w", err) + } + + cfg := &genapi.TransportConfig{ + Schemes: []string{gURL.Scheme}, + BasePath: "/api", + Host: gURL.Host, + // APIKey is an optional API key or service account token. + APIKey: credentials.apikey, + // NumRetries contains the optional number of attempted retries + NumRetries: 0, + } + if credentials.username != "" { + cfg.BasicAuth = url.UserPassword(credentials.username, credentials.password) + } + cl := genapi.NewHTTPClientWithConfig(nil, cfg) + transport := cl.Transport.(*httptransport.Runtime) + wrapped := transport.Transport + transport.Transport = &instrumentedRoundTripper{ + wrapped: wrapped, + metric: metrics.GrafanaApiRequests, + relatedResource: grafana.Name, + } + cl.SetTransport(transport) + + return cl, nil +} diff --git a/controllers/controller_shared.go b/controllers/controller_shared.go index 287ecd8b1..7863eb17d 100644 --- a/controllers/controller_shared.go +++ b/controllers/controller_shared.go @@ -4,14 +4,23 @@ import ( "bytes" "context" "encoding/json" + "time" "github.com/grafana/grafana-operator/v5/api/v1beta1" "github.com/grafana/grafana-operator/v5/controllers/model" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) +const grafanaFinalizer = "operator.grafana.com/finalizer" + +const ( + conditionNoMatchingInstance = "NoMatchingInstance" +) + func GetMatchingInstances(ctx context.Context, k8sClient client.Client, labelSelector *v1.LabelSelector) (v1beta1.GrafanaList, error) { if labelSelector == nil { return v1beta1.GrafanaList{}, nil @@ -54,3 +63,20 @@ func ReconcilePlugins(ctx context.Context, k8sClient client.Client, scheme *runt return nil } + +func setNoMatchingInstance(conditions *[]metav1.Condition, generation int64, reason, message string) { + meta.SetStatusCondition(conditions, metav1.Condition{ + Type: conditionNoMatchingInstance, + Status: "True", + ObservedGeneration: generation, + LastTransitionTime: metav1.Time{ + Time: time.Now(), + }, + Reason: reason, + Message: message, + }) +} + +func removeNoMatchingInstance(conditions *[]metav1.Condition) { + meta.RemoveStatusCondition(conditions, conditionNoMatchingInstance) +} diff --git a/controllers/grafanaalertrulegroup_controller.go b/controllers/grafanaalertrulegroup_controller.go new file mode 100644 index 000000000..d554357a8 --- /dev/null +++ b/controllers/grafanaalertrulegroup_controller.go @@ -0,0 +1,310 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + 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" + + "github.com/go-openapi/strfmt" + "github.com/grafana/grafana-openapi-client-go/client/provisioning" + "github.com/grafana/grafana-openapi-client-go/models" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/go-logr/logr" + grafanav1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1" + client2 "github.com/grafana/grafana-operator/v5/controllers/client" +) + +// GrafanaAlertRuleGroupReconciler reconciles a GrafanaAlertRuleGroup object +type GrafanaAlertRuleGroupReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanaalertrulegroups,verbs=get;list;watch;create;update;patch;delete + +//+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanaalertrulegroups/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanaalertrulegroups/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +//+kubebuilder:rbac:groups=grafana.integreatly.org,resources=grafanaalertrulegroups/finalizers,verbs=update + +func (r *GrafanaAlertRuleGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + controllerLog := log.FromContext(ctx).WithName("GrafanaAlertRuleGroupReconciler") + r.Log = log.FromContext(ctx) + + group := &grafanav1beta1.GrafanaAlertRuleGroup{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: req.Namespace, + Name: req.Name, + }, group) + if err != nil { + if kuberr.IsNotFound(err) { + return ctrl.Result{}, nil + } + controllerLog.Error(err, "error getting grafana folder cr") + return ctrl.Result{RequeueAfter: RequeueDelay}, err + } + + if group.GetDeletionTimestamp() != nil { + if controllerutil.ContainsFinalizer(group, grafanaFinalizer) { + // still need to clean up + err := r.finalize(ctx, group) + if err != nil { + return ctrl.Result{RequeueAfter: RequeueDelay}, fmt.Errorf("cleaning up alert rule group: %w", err) + } + controllerutil.RemoveFinalizer(group, grafanaFinalizer) + if err := r.Update(ctx, group); err != nil { + r.Log.Error(err, "failed to remove finalizer") + return ctrl.Result{RequeueAfter: RequeueDelay}, err + } + } + return ctrl.Result{}, nil + } + if !controllerutil.ContainsFinalizer(group, grafanaFinalizer) { + controllerutil.AddFinalizer(group, grafanaFinalizer) + if err := r.Update(ctx, group); err != nil { + r.Log.Error(err, "failed to set finalizer") + return ctrl.Result{RequeueAfter: RequeueDelay}, err + } + } + + defer func() { + if err := r.Client.Status().Update(ctx, group); err != nil { + r.Log.Error(err, "updating status") + } + }() + + instances, err := r.GetMatchingInstances(ctx, group.Spec.InstanceSelector, r.Client) + if err != nil { + setNoMatchingInstance(&group.Status.Conditions, group.Generation, "ErrFetchingInstances", fmt.Sprintf("error occured during fetching of instances: %s", err.Error())) + r.Log.Error(err, "could not find matching instances") + return ctrl.Result{RequeueAfter: RequeueDelay}, err + } + if len(instances.Items) == 0 { + setNoMatchingInstance(&group.Status.Conditions, group.Generation, "EmptyAPIReply", "Instances could not be fetched, reconciliation will be retried") + return ctrl.Result{}, nil + } + + removeNoMatchingInstance(&group.Status.Conditions) + + applyErrors := make(map[string]string) + for _, grafana := range instances.Items { + // 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) + if err != nil { + applyErrors[fmt.Sprintf("%s/%s", grafana.Namespace, grafana.Name)] = err.Error() + } + + } + condition := metav1.Condition{ + Type: "AlertGroupSynchronized", + ObservedGeneration: group.Generation, + LastTransitionTime: metav1.Time{ + Time: time.Now(), + }, + } + + if len(applyErrors) == 0 { + condition.Status = "True" + condition.Reason = "ApplySuccesfull" + condition.Message = fmt.Sprintf("Alert Rule Group was succesfully applied to %d instances", len(instances.Items)) + } else { + condition.Status = "False" + condition.Reason = "ApplyFailed" + + var sb strings.Builder + for i, err := range applyErrors { + sb.WriteString(fmt.Sprintf("\n- %s: %s", i, err)) + } + + condition.Message = fmt.Sprintf("Alert Rule Group failed to be applied for %d out of %d instances. Errors:%s", len(applyErrors), len(instances.Items), sb.String()) + } + meta.SetStatusCondition(&group.Status.Conditions, condition) + + return ctrl.Result{RequeueAfter: group.Spec.ResyncPeriod.Duration}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GrafanaAlertRuleGroupReconciler) SetupWithManager(mgr ctrl.Manager, ctx context.Context) error { + return ctrl.NewControllerManagedBy(mgr). + For(&grafanav1beta1.GrafanaAlertRuleGroup{}). + Complete(r) +} + +func (r *GrafanaAlertRuleGroupReconciler) reconcileWithInstance(ctx context.Context, instance *grafanav1beta1.Grafana, group *grafanav1beta1.GrafanaAlertRuleGroup) error { + cl, err := client2.NewGeneratedGrafanaClient(ctx, r.Client, instance) + if err != nil { + return fmt.Errorf("building grafana client: %w", err) + } + strue := "true" + + applied, err := cl.Provisioning.GetAlertRuleGroup(group.Name, group.Spec.FolderUID) + var ruleNotFound *provisioning.GetAlertRuleGroupNotFound + if err != nil && !errors.As(err, &ruleNotFound) { + return fmt.Errorf("fetching existing alert rule group: %w", err) + } + + currentRules := make(map[string]bool) + if applied != nil { + for _, rule := range applied.Payload.Rules { + currentRules[rule.UID] = false + } + } + + for _, rule := range group.Spec.Rules { + apiRule := &models.ProvisionedAlertRule{ + Annotations: rule.Annotations, + Condition: &rule.Condition, + Data: make([]*models.AlertQuery, len(rule.Data)), + ExecErrState: &rule.ExecErrState, + FolderUID: &group.Spec.FolderUID, + For: (*strfmt.Duration)(&rule.For.Duration), + IsPaused: rule.IsPaused, + Labels: rule.Labels, + NoDataState: rule.NoDataState, + RuleGroup: &group.Name, + Title: &rule.Title, + UID: rule.UID, + } + for idx, q := range rule.Data { + apiRule.Data[idx] = &models.AlertQuery{ + DatasourceUID: q.DatasourceUID, + Model: q.Model, + QueryType: q.QueryType, + RefID: q.RefID, + RelativeTimeRange: q.RelativeTimeRange, + } + } + + if _, ok := currentRules[rule.UID]; ok { + params := provisioning.NewPutAlertRuleParams(). + WithBody(apiRule). + WithXDisableProvenance(&strue). + WithUID(rule.UID) + _, err = cl.Provisioning.PutAlertRule(params) + if err != nil { + return fmt.Errorf("updating rule: %w", err) + } + } else { + params := provisioning.NewPostAlertRuleParams(). + WithBody(apiRule). + WithXDisableProvenance(&strue) + _, err = cl.Provisioning.PostAlertRule(params) + if err != nil { + return fmt.Errorf("creating rule: %w", err) + } + } + + currentRules[rule.UID] = true + } + + for uid, present := range currentRules { + if !present { + params := provisioning.NewDeleteAlertRuleParams(). + WithUID(uid). + WithXDisableProvenance(&strue) + _, err := cl.Provisioning.DeleteAlertRule(params) + if err != nil { + return fmt.Errorf("deleting old alert rule %s: %w", uid, err) + } + } + } + + mGroup := &models.AlertRuleGroup{ + FolderUID: group.Spec.FolderUID, + Interval: int64(group.Spec.Interval.Seconds()), + Rules: []*models.ProvisionedAlertRule{}, + Title: "", + } + params := provisioning.NewPutAlertRuleGroupParams(). + WithBody(mGroup). + WithGroup(group.Name). + WithFolderUID(group.Spec.FolderUID). + WithXDisableProvenance(&strue) + _, err = cl.Provisioning.PutAlertRuleGroup(params) + if err != nil { + return fmt.Errorf("updating group: %s", err.Error()) + } + return nil +} + +func (r *GrafanaAlertRuleGroupReconciler) finalize(ctx context.Context, group *grafanav1beta1.GrafanaAlertRuleGroup) error { + instances, err := r.GetMatchingInstances(ctx, group.Spec.InstanceSelector, r.Client) + if err != nil { + return fmt.Errorf("fetching instances: %w", err) + } + for _, i := range instances.Items { + instance := i + if err := r.removeFromInstance(ctx, &instance, group); err != nil { + return fmt.Errorf("removing from instance") + } + } + return nil +} + +func (r *GrafanaAlertRuleGroupReconciler) removeFromInstance(ctx context.Context, instance *grafanav1beta1.Grafana, group *grafanav1beta1.GrafanaAlertRuleGroup) error { + cl, err := client2.NewGeneratedGrafanaClient(ctx, r.Client, instance) + if err != nil { + return fmt.Errorf("building grafana client: %w", err) + } + remote, err := cl.Provisioning.GetAlertRuleGroup(group.Name, group.Spec.FolderUID) + if err != nil { + var notFound *provisioning.GetAlertRuleGroupNotFound + if errors.As(err, ¬Found) { + // nothing to do + return nil + } + return fmt.Errorf("fetching alert rule group from instance %s: %w", instance.Status.AdminUrl, err) + } + for _, rule := range remote.Payload.Rules { + params := provisioning.NewDeleteAlertRuleParams().WithUID(rule.UID) + _, err := cl.Provisioning.DeleteAlertRule(params) + if err != nil { + return fmt.Errorf("deleting alert rule %s: %w", rule.UID, err) + } + } + return nil +} + +func (r *GrafanaAlertRuleGroupReconciler) GetMatchingInstances(ctx context.Context, selector *metav1.LabelSelector, k8sClient client.Client) (grafanav1beta1.GrafanaList, error) { + instances, err := GetMatchingInstances(ctx, k8sClient, selector) + if err != nil || len(instances.Items) == 0 { + return grafanav1beta1.GrafanaList{}, err + } + return instances, err +} diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml new file mode 100644 index 000000000..2a7a32696 --- /dev/null +++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanaalertrulegroups.yaml @@ -0,0 +1,186 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: grafanaalertrulegroups.grafana.integreatly.org +spec: + group: grafana.integreatly.org + names: + kind: GrafanaAlertRuleGroup + listKind: GrafanaAlertRuleGroupList + plural: grafanaalertrulegroups + singular: grafanaalertrulegroup + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + folderUID: + type: string + instanceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + interval: + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + resyncPeriod: + default: 1m + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + rules: + items: + properties: + annotations: + additionalProperties: + type: string + type: object + condition: + type: string + data: + items: + properties: + datasourceUid: + type: string + model: + x-kubernetes-preserve-unknown-fields: true + queryType: + type: string + refId: + type: string + relativeTimeRange: + properties: + from: + format: int64 + type: integer + to: + format: int64 + type: integer + type: object + type: object + type: array + execErrState: + enum: + - OK + - Alerting + - Error + type: string + for: + format: duration + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + isPaused: + type: boolean + labels: + additionalProperties: + type: string + type: object + noDataState: + enum: + - Alerting + - NoData + - OK + type: string + title: + example: Always firing + maxLength: 190 + minLength: 1 + type: string + uid: + pattern: ^[a-zA-Z0-9-_]+$ + type: string + required: + - condition + - data + - execErrState + - for + - noDataState + - title + - uid + type: object + type: array + required: + - folderUID + - instanceSelector + - interval + - rules + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + 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 + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/go.mod b/go.mod index 2ecc88b21..5faac1d12 100644 --- a/go.mod +++ b/go.mod @@ -23,9 +23,27 @@ require ( ) require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.22.2 // indirect + github.com/go-openapi/errors v0.21.0 // indirect + github.com/go-openapi/loads v0.21.5 // indirect + github.com/go-openapi/runtime v0.27.1 // indirect + github.com/go-openapi/spec v0.20.14 // indirect + github.com/go-openapi/strfmt v0.22.0 // indirect + github.com/go-openapi/validate v0.23.0 // indirect github.com/google/gnostic-models v0.6.8 // indirect + github.com/grafana/grafana-openapi-client-go v0.0.0-20240205122950-d8758043064f // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + go.mongodb.org/mongo-driver v1.13.1 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/sync v0.5.0 // indirect ) require ( @@ -36,15 +54,15 @@ require ( github.com/evanphx/json-patch/v5 v5.8.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect; indirectn github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/go.sum b/go.sum index 3789c9737..4c97be1e3 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= @@ -6,7 +8,6 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -20,17 +21,34 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/analysis v0.22.2 h1:ZBmNoP2h5omLKr/srIC9bfqrUGzT6g6gNv03HE9Vpj0= +github.com/go-openapi/analysis v0.22.2/go.mod h1:pDF4UbZsQTo/oNuRfAWWd4dAh4yuYf//LYorPTjrpvo= +github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= +github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/loads v0.21.5 h1:jDzF4dSoHw6ZFADCGltDb2lE4F6De7aWSpe+IcsRzT0= +github.com/go-openapi/loads v0.21.5/go.mod h1:PxTsnFBoBe+z89riT+wYt3prmSBP6GDAQh2l9H1Flz8= +github.com/go-openapi/runtime v0.27.1 h1:ae53yaOoh+fx/X5Eaq8cRmavHgDma65XPZuvBqvJYto= +github.com/go-openapi/runtime v0.27.1/go.mod h1:fijeJEiEclyS8BRurYE1DE5TLb9/KZl6eAdbzjsrlLU= +github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= +github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= +github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgAcaI= +github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-openapi/validate v0.23.0 h1:2l7PJLzCis4YUGEoW6eoQw3WhyM65WSIcjX6SQnlfDw= +github.com/go-openapi/validate v0.23.0/go.mod h1:EeiAZ5bmpSIOJV1WLfyYF9qp/B1ZgSaEpHTJHtN5cbE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -51,11 +69,13 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -67,10 +87,12 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/grafana-api-golang-client v0.27.0 h1:zIwMXcbCB4n588i3O2N6HfNcQogCNTd/vPkEXTr7zX8= github.com/grafana/grafana-api-golang-client v0.27.0/go.mod h1:uNLZEmgKtTjHBtCQMwNn3qsx2mpMb8zU+7T4Xv3NR9Y= +github.com/grafana/grafana-openapi-client-go v0.0.0-20240205122950-d8758043064f h1:3DoHm253biJZehX8F/hVZEEtuhhhLz3OOd/Ehos84bY= +github.com/grafana/grafana-openapi-client-go v0.0.0-20240205122950-d8758043064f/go.mod h1:J+/va7PHxPwcbwvoXlK6ZpocYuolEb0kht3IfALng9s= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -82,27 +104,30 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -115,6 +140,8 @@ github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/openshift/api v0.0.0-20190924102528-32369d4db2ad h1:MiZEukiPd7ll8BQDwBfc3LKBxbqyeXIx+wl4CzVj5EQ= github.com/openshift/api v0.0.0-20190924102528-32369d4db2ad/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -127,24 +154,32 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -154,10 +189,13 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -165,6 +203,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= @@ -173,6 +214,9 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -181,14 +225,25 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -198,6 +253,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -232,7 +288,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 11d016f98..7c994a7c1 100644 --- a/main.go +++ b/main.go @@ -187,6 +187,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "GrafanaFolder") os.Exit(1) } + if err = (&controllers.GrafanaAlertRuleGroupReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr, ctx); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "GrafanaAlertRuleGroup") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {