From 5568219e0406ecb72824ebb9155e8fd244e58075 Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Thu, 15 Feb 2024 11:03:26 +0100 Subject: [PATCH 1/8] move perses controller into its own folder Signed-off-by: Gabriel Bernal --- controllers/{ => perses}/configmap_controller.go | 2 +- controllers/{ => perses}/deployment_controller.go | 2 +- controllers/{ => perses}/perses_controller.go | 2 +- controllers/{ => perses}/service_controller.go | 2 +- controllers/perses_controller_test.go | 3 ++- controllers/suite_test.go | 4 +++- main.go | 2 +- 7 files changed, 10 insertions(+), 7 deletions(-) rename controllers/{ => perses}/configmap_controller.go (99%) rename controllers/{ => perses}/deployment_controller.go (99%) rename controllers/{ => perses}/perses_controller.go (99%) rename controllers/{ => perses}/service_controller.go (99%) diff --git a/controllers/configmap_controller.go b/controllers/perses/configmap_controller.go similarity index 99% rename from controllers/configmap_controller.go rename to controllers/perses/configmap_controller.go index cd2caa6..6cb5a0a 100644 --- a/controllers/configmap_controller.go +++ b/controllers/perses/configmap_controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package perses import ( "context" diff --git a/controllers/deployment_controller.go b/controllers/perses/deployment_controller.go similarity index 99% rename from controllers/deployment_controller.go rename to controllers/perses/deployment_controller.go index d212a78..b061dc3 100644 --- a/controllers/deployment_controller.go +++ b/controllers/perses/deployment_controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package perses import ( "context" diff --git a/controllers/perses_controller.go b/controllers/perses/perses_controller.go similarity index 99% rename from controllers/perses_controller.go rename to controllers/perses/perses_controller.go index 5822eef..2e56aba 100644 --- a/controllers/perses_controller.go +++ b/controllers/perses/perses_controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package perses import ( "context" diff --git a/controllers/service_controller.go b/controllers/perses/service_controller.go similarity index 99% rename from controllers/service_controller.go rename to controllers/perses/service_controller.go index da68c11..53f0e2c 100644 --- a/controllers/service_controller.go +++ b/controllers/perses/service_controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package perses import ( "context" diff --git a/controllers/perses_controller_test.go b/controllers/perses_controller_test.go index bb14ce2..db192bd 100644 --- a/controllers/perses_controller_test.go +++ b/controllers/perses_controller_test.go @@ -9,6 +9,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" persesv1alpha1 "github.com/perses/perses-operator/api/v1alpha1" + persescontroller "github.com/perses/perses-operator/controllers/perses" common "github.com/perses/perses-operator/internal/perses/common" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -79,7 +80,7 @@ var _ = Describe("Perses controller", func() { }, time.Minute, time.Second).Should(Succeed()) By("Reconciling the custom resource created") - persesReconciler := &PersesReconciler{ + persesReconciler := &persescontroller.PersesReconciler{ Client: k8sClient, Scheme: k8sClient.Scheme(), } diff --git a/controllers/suite_test.go b/controllers/suite_test.go index d02409a..5f9f7b2 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -32,6 +32,8 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + persesController "github.com/perses/perses-operator/controllers/perses" + "github.com/perses/perses-operator/api/v1alpha1" //+kubebuilder:scaffold:imports ) @@ -82,7 +84,7 @@ var _ = BeforeSuite(func() { }) Expect(err).ToNot(HaveOccurred()) - err = (&PersesReconciler{ + err = (&persesController.PersesReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), }).SetupWithManager(k8sManager) diff --git a/main.go b/main.go index 5004148..cdd584a 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" persesv1alpha1 "github.com/perses/perses-operator/api/v1alpha1" - "github.com/perses/perses-operator/controllers" + controllers "github.com/perses/perses-operator/controllers/perses" //+kubebuilder:scaffold:imports ) From 45e3c38800a4200f00f74badbf8aebf28867aa82 Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Thu, 15 Feb 2024 11:19:47 +0100 Subject: [PATCH 2/8] generate dashboards crd Signed-off-by: Gabriel Bernal --- api/v1alpha1/perses_dashboard.go | 20 + api/v1alpha1/persesdashboard_types.go | 52 ++ api/v1alpha1/zz_generated.deepcopy.go | 91 +++ config/crd/bases/perses.dev_perses.yaml | 6 + .../bases/perses.dev_persesdashboards.yaml | 282 +++++++++ config/crd/kustomization.yaml | 1 + .../cainjection_in_persesdashboards.yaml | 7 + .../patches/webhook_in_persesdashboards.yaml | 16 + config/rbac/persesdashboard_editor_role.yaml | 31 + config/rbac/persesdashboard_viewer_role.yaml | 27 + config/rbac/role.yaml | 34 +- ...s.yaml => perses.dev_v1alpha1_perses.yaml} | 1 + ...perses.dev_v1alpha1_persesdashboard-2.yaml | 573 ++++++++++++++++++ .../perses.dev_v1alpha1_persesdashboard.yaml | 573 ++++++++++++++++++ go.mod | 13 +- go.sum | 29 +- 16 files changed, 1722 insertions(+), 34 deletions(-) create mode 100644 api/v1alpha1/perses_dashboard.go create mode 100644 api/v1alpha1/persesdashboard_types.go create mode 100644 config/crd/bases/perses.dev_persesdashboards.yaml create mode 100644 config/crd/patches/cainjection_in_persesdashboards.yaml create mode 100644 config/crd/patches/webhook_in_persesdashboards.yaml create mode 100644 config/rbac/persesdashboard_editor_role.yaml create mode 100644 config/rbac/persesdashboard_viewer_role.yaml rename config/samples/{v1alpha1_perses.yaml => perses.dev_v1alpha1_perses.yaml} (95%) create mode 100644 config/samples/perses.dev_v1alpha1_persesdashboard-2.yaml create mode 100644 config/samples/perses.dev_v1alpha1_persesdashboard.yaml diff --git a/api/v1alpha1/perses_dashboard.go b/api/v1alpha1/perses_dashboard.go new file mode 100644 index 0000000..d2f25f8 --- /dev/null +++ b/api/v1alpha1/perses_dashboard.go @@ -0,0 +1,20 @@ +package v1alpha1 + +import ( + "github.com/barkimedes/go-deepcopy" + persesv1 "github.com/perses/perses/pkg/model/api/v1" +) + +type Dashboard struct { + persesv1.Dashboard `json:",inline"` +} + +func (in *Dashboard) DeepCopyInto(out *Dashboard) { + temp, err := deepcopy.Anything(in) + + if err != nil { + panic(err) + } + + *out = *(temp.(*Dashboard)) +} diff --git a/api/v1alpha1/persesdashboard_types.go b/api/v1alpha1/persesdashboard_types.go new file mode 100644 index 0000000..a7213e3 --- /dev/null +++ b/api/v1alpha1/persesdashboard_types.go @@ -0,0 +1,52 @@ +/* +Copyright 2024. + +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// PersesDashboardStatus defines the observed state of PersesDashboard +type PersesDashboardStatus struct { + // +operator-sdk:csv:customresourcedefinitions:type=status + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// PersesDashboard is the Schema for the persesdashboards API +type PersesDashboard struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec Dashboard `json:"spec,omitempty"` + Status PersesDashboardStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// PersesDashboardList contains a list of PersesDashboard +type PersesDashboardList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PersesDashboard `json:"items"` +} + +func init() { + SchemeBuilder.Register(&PersesDashboard{}, &PersesDashboardList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7d2f190..8781ba9 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -26,6 +26,16 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Dashboard. +func (in *Dashboard) DeepCopy() *Dashboard { + if in == nil { + return nil + } + out := new(Dashboard) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Perses) DeepCopyInto(out *Perses) { *out = *in @@ -63,6 +73,87 @@ func (in *PersesConfig) DeepCopy() *PersesConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersesDashboard) DeepCopyInto(out *PersesDashboard) { + *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 PersesDashboard. +func (in *PersesDashboard) DeepCopy() *PersesDashboard { + if in == nil { + return nil + } + out := new(PersesDashboard) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PersesDashboard) 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 *PersesDashboardList) DeepCopyInto(out *PersesDashboardList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PersesDashboard, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersesDashboardList. +func (in *PersesDashboardList) DeepCopy() *PersesDashboardList { + if in == nil { + return nil + } + out := new(PersesDashboardList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PersesDashboardList) 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 *PersesDashboardStatus) DeepCopyInto(out *PersesDashboardStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersesDashboardStatus. +func (in *PersesDashboardStatus) DeepCopy() *PersesDashboardStatus { + if in == nil { + return nil + } + out := new(PersesDashboardStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PersesList) DeepCopyInto(out *PersesList) { *out = *in diff --git a/config/crd/bases/perses.dev_perses.yaml b/config/crd/bases/perses.dev_perses.yaml index 0989827..c383d7e 100644 --- a/config/crd/bases/perses.dev_perses.yaml +++ b/config/crd/bases/perses.dev_perses.yaml @@ -43,11 +43,14 @@ spec: properties: file: properties: + case_sensitive: + type: boolean extension: type: string folder: type: string required: + - case_sensitive - extension - folder type: object @@ -74,6 +77,8 @@ spec: allow_old_passwords: description: Allows the old insecure password method type: boolean + case_sensitive: + type: boolean check_conn_liveness: description: Check connections for liveness before using them @@ -180,6 +185,7 @@ spec: - allow_fallback_to_plaintext - allow_native_passwords - allow_old_passwords + - case_sensitive - check_conn_liveness - client_found_rows - columns_with_alias diff --git a/config/crd/bases/perses.dev_persesdashboards.yaml b/config/crd/bases/perses.dev_persesdashboards.yaml new file mode 100644 index 0000000..2e58a8e --- /dev/null +++ b/config/crd/bases/perses.dev_persesdashboards.yaml @@ -0,0 +1,282 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: persesdashboards.perses.dev +spec: + group: perses.dev + names: + kind: PersesDashboard + listKind: PersesDashboardList + plural: persesdashboards + singular: persesdashboard + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: PersesDashboard is the Schema for the persesdashboards 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: + properties: + kind: + type: string + metadata: + description: ProjectMetadata is the metadata struct for resources + that belongs to a project. + properties: + createdAt: + format: date-time + type: string + name: + type: string + project: + type: string + updatedAt: + format: date-time + type: string + version: + format: int64 + type: integer + required: + - name + - project + - version + type: object + spec: + properties: + datasources: + additionalProperties: + properties: + default: + type: boolean + display: + properties: + description: + type: string + name: + type: string + type: object + plugin: + description: Plugin will contain the datasource configuration. + The data typed is available in Cue. + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + required: + - default + - plugin + type: object + description: Datasources is an optional list of datasource definition. + type: object + display: + properties: + description: + type: string + name: + type: string + type: object + duration: + description: Duration is the default time range to use when getting + data to fill the dashboard + format: duration + type: string + layouts: + items: + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + type: array + panels: + additionalProperties: + properties: + kind: + type: string + spec: + properties: + display: + properties: + description: + type: string + name: + type: string + required: + - name + type: object + plugin: + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + queries: + items: + properties: + kind: + type: string + spec: + properties: + plugin: + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + required: + - plugin + type: object + required: + - kind + - spec + type: object + type: array + required: + - display + - plugin + type: object + required: + - kind + - spec + type: object + type: object + refreshInterval: + description: RefreshInterval is the default refresh interval to + use when landing on the dashboard + format: duration + type: string + variables: + items: + properties: + kind: + description: Kind is the type of the variable. Depending + on the value of Kind, it will change the content of Spec. + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + type: array + required: + - duration + - layouts + - panels + type: object + required: + - kind + - metadata + - spec + type: object + status: + description: PersesDashboardStatus defines the observed state of PersesDashboard + 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 + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 456f3aa..2bcd879 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,6 +3,7 @@ # It should be run by config/default resources: - bases/perses.dev_perses.yaml + - bases/perses.dev_persesdashboards.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/crd/patches/cainjection_in_persesdashboards.yaml b/config/crd/patches/cainjection_in_persesdashboards.yaml new file mode 100644 index 0000000..a08ce58 --- /dev/null +++ b/config/crd/patches/cainjection_in_persesdashboards.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: persesdashboards.perses.dev diff --git a/config/crd/patches/webhook_in_persesdashboards.yaml b/config/crd/patches/webhook_in_persesdashboards.yaml new file mode 100644 index 0000000..9739c51 --- /dev/null +++ b/config/crd/patches/webhook_in_persesdashboards.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: persesdashboards.perses.dev +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/persesdashboard_editor_role.yaml b/config/rbac/persesdashboard_editor_role.yaml new file mode 100644 index 0000000..12977a8 --- /dev/null +++ b/config/rbac/persesdashboard_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit persesdashboards. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: persesdashboard-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: perses-operator + app.kubernetes.io/part-of: perses-operator + app.kubernetes.io/managed-by: kustomize + name: persesdashboard-editor-role +rules: + - apiGroups: + - perses.dev + resources: + - persesdashboards + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - perses.dev + resources: + - persesdashboards/status + verbs: + - get diff --git a/config/rbac/persesdashboard_viewer_role.yaml b/config/rbac/persesdashboard_viewer_role.yaml new file mode 100644 index 0000000..eb49f7f --- /dev/null +++ b/config/rbac/persesdashboard_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view persesdashboards. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: persesdashboard-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: perses-operator + app.kubernetes.io/part-of: perses-operator + app.kubernetes.io/managed-by: kustomize + name: persesdashboard-viewer-role +rules: + - apiGroups: + - perses.dev + resources: + - persesdashboards + verbs: + - get + - list + - watch + - apiGroups: + - perses.dev + resources: + - persesdashboards/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 5037da7..c58a7f1 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -27,9 +27,15 @@ rules: - apiGroups: - "" resources: - - pods + - deployments + - services + - configmaps verbs: - get + - patch + - update + - create + - delete - list - watch - apiGroups: @@ -58,3 +64,29 @@ rules: - get - patch - update + - apiGroups: + - perses.dev + resources: + - persesdashboards + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - perses.dev + resources: + - persesdashboards/finalizers + verbs: + - update + - apiGroups: + - perses.dev + resources: + - persesdashboards/status + verbs: + - get + - patch + - update diff --git a/config/samples/v1alpha1_perses.yaml b/config/samples/perses.dev_v1alpha1_perses.yaml similarity index 95% rename from config/samples/v1alpha1_perses.yaml rename to config/samples/perses.dev_v1alpha1_perses.yaml index 3e96fce..6515029 100644 --- a/config/samples/v1alpha1_perses.yaml +++ b/config/samples/perses.dev_v1alpha1_perses.yaml @@ -14,6 +14,7 @@ spec: file: folder: "/etc/perses/storage" extension: "yaml" + case_sensitive: false schemas: panels_path: "/etc/perses/schemas/panels" queries_path: "/etc/perses/schemas/queries" diff --git a/config/samples/perses.dev_v1alpha1_persesdashboard-2.yaml b/config/samples/perses.dev_v1alpha1_persesdashboard-2.yaml new file mode 100644 index 0000000..f0e4be2 --- /dev/null +++ b/config/samples/perses.dev_v1alpha1_persesdashboard-2.yaml @@ -0,0 +1,573 @@ +apiVersion: perses.dev/v1alpha1 +kind: PersesDashboard +metadata: + labels: + app.kubernetes.io/name: perses-dashboard-2 + app.kubernetes.io/instance: perses-dashboard-sample-2 + app.kubernetes.io/part-of: perses-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: perses-operator + name: perses-dashboard-sample-2 +spec: + kind: Dashboard + metadata: + name: Demo-2 + project: default + createdAt: "2021-11-09T00:00:00Z" + updatedAt: "2021-11-09T00:00:00Z" + version: 0 + spec: + duration: 5m + datasources: + PrometheusLocal: + default: false + plugin: + kind: PrometheusDatasource + spec: + proxy: + kind: HTTPProxy + spec: + url: http://localhost:9090 + variables: + - kind: ListVariable + spec: + name: job + allowMultiple: false + allowAllValue: false + plugin: + kind: PrometheusLabelValuesVariable + spec: + labelName: job + - kind: ListVariable + spec: + name: instance + allowMultiple: false + allowAllValue: false + plugin: + kind: PrometheusLabelValuesVariable + spec: + labelName: instance + matchers: + - up{job=~"$job"} + - kind: ListVariable + spec: + name: interval + plugin: + kind: StaticListVariable + spec: + values: + - 1m + - 5m + - kind: TextVariable + spec: + name: text + value: test + constant: true + panels: + defaultTimeSeriesChart: + kind: Panel + spec: + display: + name: Default Time Series Panel + plugin: + kind: TimeSeriesChart + spec: {} + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: up + seriesTest: + kind: Panel + spec: + display: + name: "~130 Series" + description: This is a line chart + plugin: + kind: TimeSeriesChart + spec: + yAxis: + format: + unit: bytes + shortValues: true + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: rate(caddy_http_response_duration_seconds_sum[$interval]) + basicEx: + kind: Panel + spec: + display: + name: Single Query + plugin: + kind: TimeSeriesChart + spec: + yAxis: + format: + unit: decimal + legend: + position: right + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: Node memory - {{device}} {{instance}} + query: + 1 - node_filesystem_free_bytes{job='$job',instance=~'$instance',fstype!="rootfs",mountpoint!~"/(run|var).*",mountpoint!=""} + / node_filesystem_size_bytes{job='$job',instance=~'$instance'} + legendEx: + kind: Panel + spec: + display: + name: Legend Example + plugin: + kind: TimeSeriesChart + spec: + legend: + position: bottom + yAxis: + show: true + format: + unit: bytes + shortValues: true + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: Node memory total + query: + node_memory_MemTotal_bytes{job='$job',instance=~'$instance'} + - node_memory_MemFree_bytes{job='$job',instance=~'$instance'} - + node_memory_Buffers_bytes{job='$job',instance=~'$instance'} - node_memory_Cached_bytes{job='$job',instance=~'$instance'} + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: Memory (buffers) - {{instance}} + query: node_memory_Buffers_bytes{job='$job',instance=~'$instance'} + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: Cached Bytes + query: node_memory_Cached_bytes{job='$job',instance=~'$instance'} + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: MemFree Bytes + query: node_memory_MemFree_bytes{job='$job',instance=~'$instance'} + testNodeQuery: + kind: Panel + spec: + display: + name: Test Query + description: Description text + plugin: + kind: TimeSeriesChart + spec: + yAxis: + format: + unit: decimal + decimalPlaces: 2 + legend: + position: right + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_load15{instance=~"(demo.do.prometheus.io:9100)",job='$job'} + seriesNameFormat: Test {{job}} {{instance}} + testQueryAlt: + kind: Panel + spec: + display: + name: Test Query Alt + description: Description text + plugin: + kind: TimeSeriesChart + spec: + legend: + position: right + yAxis: + format: + unit: percent-decimal + decimalPlaces: 1 + thresholds: + steps: + - value: 0.4 + name: "Alert: Warning condition example" + - value: 0.75 + name: "Alert: Critical condition example" + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_load1{instance=~"(demo.do.prometheus.io:9100)",job='$job'} + cpuLine: + kind: Panel + spec: + display: + name: CPU - Line (Multi Series) + description: This is a line chart test + plugin: + kind: TimeSeriesChart + spec: + yAxis: + show: false + label: CPU Label + format: + unit: percent-decimal + decimalPlaces: 0 + legend: + position: bottom + thresholds: + steps: + - value: 0.2 + - value: 0.35 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: "{{mode}} mode - {{job}} {{instance}}" + query: avg without (cpu)(rate(node_cpu_seconds_total{job='$job',instance=~'$instance',mode!="nice",mode!="steal",mode!="irq"}[$interval])) + cpuGauge: + kind: Panel + spec: + display: + name: CPU - Gauge (Multi Series) + description: This is a gauge chart test + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: percent-decimal + thresholds: + steps: + - value: 0.2 + - value: 0.35 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: "{{mode}} mode - {{job}} {{instance}}" + query: avg without (cpu)(rate(node_cpu_seconds_total{job='$job',instance=~'$instance',mode!="nice",mode!="steal",mode!="irq"}[$interval])) + statSm: + kind: Panel + spec: + display: + name: Stat Sm + plugin: + kind: StatChart + spec: + calculation: mean + format: + unit: decimal + decimalPlaces: 1 + shortValues: true + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_time_seconds{job='$job',instance=~'$instance'} - node_boot_time_seconds{job='$job',instance=~'$instance'} + gaugeRAM: + kind: Panel + spec: + display: + name: RAM Used + description: This is a stat chart + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: percent + thresholds: + steps: + - value: 85 + - value: 95 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + 100 - ((node_memory_MemAvailable_bytes{job='$job',instance=~'$instance'} + * 100) / node_memory_MemTotal_bytes{job='$job',instance=~'$instance'}) + statRAM: + kind: Panel + spec: + display: + name: RAM Used + description: This is a stat chart + plugin: + kind: StatChart + spec: + calculation: last-number + format: + unit: percent + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + 100 - ((node_memory_MemAvailable_bytes{job='$job',instance=~'$instance'} + * 100) / node_memory_MemTotal_bytes{job='$job',instance=~'$instance'}) + statTotalRAM: + kind: Panel + spec: + display: + name: RAM Total + description: This is a stat chart + plugin: + kind: StatChart + spec: + calculation: last-number + format: + unit: bytes + decimalPlaces: 1 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_memory_MemTotal_bytes{job='$job',instance=~'$instance'} + statMd: + kind: Panel + spec: + display: + name: Stat Md + plugin: + kind: StatChart + spec: + calculation: sum + format: + unit: decimal + decimalPlaces: 2 + shortValues: true + sparkline: + color: "#e65013" + width: 1.5 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + avg(node_load15{job='node',instance=~'$instance'}) / count(count(node_cpu_seconds_total{job='node',instance=~'$instance'}) + by (cpu)) * 100 + statLg: + kind: Panel + spec: + display: + name: Stat Lg + description: This is a stat chart + plugin: + kind: StatChart + spec: + calculation: mean + format: + unit: percent + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + (((count(count(node_cpu_seconds_total{job='$job',instance=~'$instance'}) + by (cpu))) - avg(sum by (mode)(rate(node_cpu_seconds_total{mode="idle",job='$job',instance=~'$instance'}[$interval])))) + * 100) / count(count(node_cpu_seconds_total{job='$job',instance=~'$instance'}) + by (cpu)) + gaugeEx: + kind: Panel + spec: + display: + name: Gauge Ex + description: This is a gauge chart + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: percent + thresholds: + steps: + - value: 85 + - value: 95 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + (((count(count(node_cpu_seconds_total{job='$job',instance=~'$instance'}) + by (cpu))) - avg(sum by (mode)(rate(node_cpu_seconds_total{mode="idle",job='$job',instance=~'$instance'}[$interval])))) + * 100) / count(count(node_cpu_seconds_total{job='$job',instance=~'$instance'}) + by (cpu)) + gaugeAltEx: + kind: Panel + spec: + display: + name: Gauge Alt Ex + description: GaugeChart description text + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: percent-decimal + decimalPlaces: 1 + thresholds: + steps: + - value: 0.5 + name: "Alert: Warning condition example" + - value: 0.75 + name: "Alert: Critical condition example" + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_load15{instance=~'$instance',job='$job'} + gaugeFormatTest: + kind: Panel + spec: + display: + name: Gauge Format Test + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: bytes + max: 95000000 + thresholds: + steps: + - value: 71000000 + - value: 82000000 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_time_seconds{job='$job',instance=~'$instance'} - node_boot_time_seconds{job='$job',instance=~'$instance'} + layouts: + - kind: Grid + spec: + display: + title: Row 1 + collapse: + open: true + items: + - x: 0 + "y": 0 + width: 2 + height: 3 + content: + "$ref": "#/spec/panels/statRAM" + - x: 0 + "y": 4 + width: 2 + height: 3 + content: + "$ref": "#/spec/panels/statTotalRAM" + - x: 2 + "y": 0 + width: 4 + height: 6 + content: + "$ref": "#/spec/panels/statMd" + - x: 6 + "y": 0 + width: 10 + height: 6 + content: + "$ref": "#/spec/panels/statLg" + - x: 16 + "y": 0 + width: 4 + height: 6 + content: + "$ref": "#/spec/panels/gaugeFormatTest" + - x: 20 + "y": 0 + width: 4 + height: 6 + content: + "$ref": "#/spec/panels/gaugeRAM" + - kind: Grid + spec: + display: + title: Row 2 + collapse: + open: true + items: + - x: 0 + "y": 0 + width: 12 + height: 6 + content: + "$ref": "#/spec/panels/legendEx" + - x: 12 + "y": 0 + width: 12 + height: 6 + content: + "$ref": "#/spec/panels/basicEx" + - kind: Grid + spec: + display: + title: Row 3 + collapse: + open: false + items: + - x: 0 + "y": 0 + width: 24 + height: 6 + content: + "$ref": "#/spec/panels/cpuGauge" + - x: 0 + "y": 6 + width: 12 + height: 8 + content: + "$ref": "#/spec/panels/cpuLine" + - x: 12 + "y": 0 + width: 12 + height: 8 + content: + "$ref": "#/spec/panels/defaultTimeSeriesChart" diff --git a/config/samples/perses.dev_v1alpha1_persesdashboard.yaml b/config/samples/perses.dev_v1alpha1_persesdashboard.yaml new file mode 100644 index 0000000..878dcf4 --- /dev/null +++ b/config/samples/perses.dev_v1alpha1_persesdashboard.yaml @@ -0,0 +1,573 @@ +apiVersion: perses.dev/v1alpha1 +kind: PersesDashboard +metadata: + labels: + app.kubernetes.io/name: perses-dashboard + app.kubernetes.io/instance: perses-dashboard-sample + app.kubernetes.io/part-of: perses-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: perses-operator + name: perses-dashboard-sample +spec: + kind: Dashboard + metadata: + name: Demo + project: default + createdAt: "2021-11-09T00:00:00Z" + updatedAt: "2021-11-09T00:00:00Z" + version: 0 + spec: + duration: 5m + datasources: + PrometheusLocal: + default: false + plugin: + kind: PrometheusDatasource + spec: + proxy: + kind: HTTPProxy + spec: + url: http://localhost:9090 + variables: + - kind: ListVariable + spec: + name: job + allowMultiple: false + allowAllValue: false + plugin: + kind: PrometheusLabelValuesVariable + spec: + labelName: job + - kind: ListVariable + spec: + name: instance + allowMultiple: false + allowAllValue: false + plugin: + kind: PrometheusLabelValuesVariable + spec: + labelName: instance + matchers: + - up{job=~"$job"} + - kind: ListVariable + spec: + name: interval + plugin: + kind: StaticListVariable + spec: + values: + - 1m + - 5m + - kind: TextVariable + spec: + name: text + value: test + constant: true + panels: + defaultTimeSeriesChart: + kind: Panel + spec: + display: + name: Default Time Series Panel + plugin: + kind: TimeSeriesChart + spec: {} + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: up + seriesTest: + kind: Panel + spec: + display: + name: "~130 Series" + description: This is a line chart + plugin: + kind: TimeSeriesChart + spec: + yAxis: + format: + unit: bytes + shortValues: true + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: rate(caddy_http_response_duration_seconds_sum[$interval]) + basicEx: + kind: Panel + spec: + display: + name: Single Query + plugin: + kind: TimeSeriesChart + spec: + yAxis: + format: + unit: decimal + legend: + position: right + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: Node memory - {{device}} {{instance}} + query: + 1 - node_filesystem_free_bytes{job='$job',instance=~'$instance',fstype!="rootfs",mountpoint!~"/(run|var).*",mountpoint!=""} + / node_filesystem_size_bytes{job='$job',instance=~'$instance'} + legendEx: + kind: Panel + spec: + display: + name: Legend Example + plugin: + kind: TimeSeriesChart + spec: + legend: + position: bottom + yAxis: + show: true + format: + unit: bytes + shortValues: true + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: Node memory total + query: + node_memory_MemTotal_bytes{job='$job',instance=~'$instance'} + - node_memory_MemFree_bytes{job='$job',instance=~'$instance'} - + node_memory_Buffers_bytes{job='$job',instance=~'$instance'} - node_memory_Cached_bytes{job='$job',instance=~'$instance'} + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: Memory (buffers) - {{instance}} + query: node_memory_Buffers_bytes{job='$job',instance=~'$instance'} + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: Cached Bytes + query: node_memory_Cached_bytes{job='$job',instance=~'$instance'} + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: MemFree Bytes + query: node_memory_MemFree_bytes{job='$job',instance=~'$instance'} + testNodeQuery: + kind: Panel + spec: + display: + name: Test Query + description: Description text + plugin: + kind: TimeSeriesChart + spec: + yAxis: + format: + unit: decimal + decimalPlaces: 2 + legend: + position: right + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_load15{instance=~"(demo.do.prometheus.io:9100)",job='$job'} + seriesNameFormat: Test {{job}} {{instance}} + testQueryAlt: + kind: Panel + spec: + display: + name: Test Query Alt + description: Description text + plugin: + kind: TimeSeriesChart + spec: + legend: + position: right + yAxis: + format: + unit: percent-decimal + decimalPlaces: 1 + thresholds: + steps: + - value: 0.4 + name: "Alert: Warning condition example" + - value: 0.75 + name: "Alert: Critical condition example" + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_load1{instance=~"(demo.do.prometheus.io:9100)",job='$job'} + cpuLine: + kind: Panel + spec: + display: + name: CPU - Line (Multi Series) + description: This is a line chart test + plugin: + kind: TimeSeriesChart + spec: + yAxis: + show: false + label: CPU Label + format: + unit: percent-decimal + decimalPlaces: 0 + legend: + position: bottom + thresholds: + steps: + - value: 0.2 + - value: 0.35 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: "{{mode}} mode - {{job}} {{instance}}" + query: avg without (cpu)(rate(node_cpu_seconds_total{job='$job',instance=~'$instance',mode!="nice",mode!="steal",mode!="irq"}[$interval])) + cpuGauge: + kind: Panel + spec: + display: + name: CPU - Gauge (Multi Series) + description: This is a gauge chart test + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: percent-decimal + thresholds: + steps: + - value: 0.2 + - value: 0.35 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + seriesNameFormat: "{{mode}} mode - {{job}} {{instance}}" + query: avg without (cpu)(rate(node_cpu_seconds_total{job='$job',instance=~'$instance',mode!="nice",mode!="steal",mode!="irq"}[$interval])) + statSm: + kind: Panel + spec: + display: + name: Stat Sm + plugin: + kind: StatChart + spec: + calculation: mean + format: + unit: decimal + decimalPlaces: 1 + shortValues: true + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_time_seconds{job='$job',instance=~'$instance'} - node_boot_time_seconds{job='$job',instance=~'$instance'} + gaugeRAM: + kind: Panel + spec: + display: + name: RAM Used + description: This is a stat chart + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: percent + thresholds: + steps: + - value: 85 + - value: 95 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + 100 - ((node_memory_MemAvailable_bytes{job='$job',instance=~'$instance'} + * 100) / node_memory_MemTotal_bytes{job='$job',instance=~'$instance'}) + statRAM: + kind: Panel + spec: + display: + name: RAM Used + description: This is a stat chart + plugin: + kind: StatChart + spec: + calculation: last-number + format: + unit: percent + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + 100 - ((node_memory_MemAvailable_bytes{job='$job',instance=~'$instance'} + * 100) / node_memory_MemTotal_bytes{job='$job',instance=~'$instance'}) + statTotalRAM: + kind: Panel + spec: + display: + name: RAM Total + description: This is a stat chart + plugin: + kind: StatChart + spec: + calculation: last-number + format: + unit: bytes + decimalPlaces: 1 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_memory_MemTotal_bytes{job='$job',instance=~'$instance'} + statMd: + kind: Panel + spec: + display: + name: Stat Md + plugin: + kind: StatChart + spec: + calculation: sum + format: + unit: decimal + decimalPlaces: 2 + shortValues: true + sparkline: + color: "#e65013" + width: 1.5 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + avg(node_load15{job='node',instance=~'$instance'}) / count(count(node_cpu_seconds_total{job='node',instance=~'$instance'}) + by (cpu)) * 100 + statLg: + kind: Panel + spec: + display: + name: Stat Lg + description: This is a stat chart + plugin: + kind: StatChart + spec: + calculation: mean + format: + unit: percent + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + (((count(count(node_cpu_seconds_total{job='$job',instance=~'$instance'}) + by (cpu))) - avg(sum by (mode)(rate(node_cpu_seconds_total{mode="idle",job='$job',instance=~'$instance'}[$interval])))) + * 100) / count(count(node_cpu_seconds_total{job='$job',instance=~'$instance'}) + by (cpu)) + gaugeEx: + kind: Panel + spec: + display: + name: Gauge Ex + description: This is a gauge chart + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: percent + thresholds: + steps: + - value: 85 + - value: 95 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: + (((count(count(node_cpu_seconds_total{job='$job',instance=~'$instance'}) + by (cpu))) - avg(sum by (mode)(rate(node_cpu_seconds_total{mode="idle",job='$job',instance=~'$instance'}[$interval])))) + * 100) / count(count(node_cpu_seconds_total{job='$job',instance=~'$instance'}) + by (cpu)) + gaugeAltEx: + kind: Panel + spec: + display: + name: Gauge Alt Ex + description: GaugeChart description text + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: percent-decimal + decimalPlaces: 1 + thresholds: + steps: + - value: 0.5 + name: "Alert: Warning condition example" + - value: 0.75 + name: "Alert: Critical condition example" + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_load15{instance=~'$instance',job='$job'} + gaugeFormatTest: + kind: Panel + spec: + display: + name: Gauge Format Test + plugin: + kind: GaugeChart + spec: + calculation: last-number + format: + unit: bytes + max: 95000000 + thresholds: + steps: + - value: 71000000 + - value: 82000000 + queries: + - kind: TimeSeriesQuery + spec: + plugin: + kind: PrometheusTimeSeriesQuery + spec: + query: node_time_seconds{job='$job',instance=~'$instance'} - node_boot_time_seconds{job='$job',instance=~'$instance'} + layouts: + - kind: Grid + spec: + display: + title: Row 1 + collapse: + open: true + items: + - x: 0 + "y": 0 + width: 2 + height: 3 + content: + "$ref": "#/spec/panels/statRAM" + - x: 0 + "y": 4 + width: 2 + height: 3 + content: + "$ref": "#/spec/panels/statTotalRAM" + - x: 2 + "y": 0 + width: 4 + height: 6 + content: + "$ref": "#/spec/panels/statMd" + - x: 6 + "y": 0 + width: 10 + height: 6 + content: + "$ref": "#/spec/panels/statLg" + - x: 16 + "y": 0 + width: 4 + height: 6 + content: + "$ref": "#/spec/panels/gaugeFormatTest" + - x: 20 + "y": 0 + width: 4 + height: 6 + content: + "$ref": "#/spec/panels/gaugeRAM" + - kind: Grid + spec: + display: + title: Row 2 + collapse: + open: true + items: + - x: 0 + "y": 0 + width: 12 + height: 6 + content: + "$ref": "#/spec/panels/legendEx" + - x: 12 + "y": 0 + width: 12 + height: 6 + content: + "$ref": "#/spec/panels/basicEx" + - kind: Grid + spec: + display: + title: Row 3 + collapse: + open: false + items: + - x: 0 + "y": 0 + width: 24 + height: 6 + content: + "$ref": "#/spec/panels/cpuGauge" + - x: 0 + "y": 6 + width: 12 + height: 8 + content: + "$ref": "#/spec/panels/cpuLine" + - x: 12 + "y": 0 + width: 12 + height: 8 + content: + "$ref": "#/spec/panels/defaultTimeSeriesChart" diff --git a/go.mod b/go.mod index b338088..72ea8b5 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 - github.com/perses/perses v0.43.0 + github.com/perses/perses v0.43.1-0.20240212140825-8efdfab0f93d github.com/sirupsen/logrus v1.9.3 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.28.3 @@ -22,7 +22,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -35,16 +35,12 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/labstack/echo/v4 v4.11.4 // indirect - github.com/labstack/gommon v0.4.2 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect 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 @@ -57,11 +53,8 @@ require ( github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect - golang.org/x/crypto v0.18.0 // indirect golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect golang.org/x/net v0.20.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect diff --git a/go.sum b/go.sum index 660a786..d14965f 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -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/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -55,8 +55,8 @@ 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.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -77,17 +77,8 @@ 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/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= -github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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= @@ -105,8 +96,8 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/perses/common v0.22.0 h1:G5nCKjBbgu0Y/sosRWe5VfgFy9XoECVv2KcfAnKEqsM= github.com/perses/common v0.22.0/go.mod h1:FjtkQ3IYxithLrgTNbuG3rdusl8XXoE60a2DaX4FEHk= -github.com/perses/perses v0.43.0 h1:Gi0FITgCPo4NkBLZJCU8mfurhEdugzgXfrJpqoGNmIM= -github.com/perses/perses v0.43.0/go.mod h1:N/K57Ro+kZP8oWaq+zOG4dZCpe4gNEKujtJ5e5Hy1sk= +github.com/perses/perses v0.43.1-0.20240212140825-8efdfab0f93d h1:so+n1SsmsPGF/XqPyZlkVAf87XdAgfIlLQAb+Wbo2Nc= +github.com/perses/perses v0.43.1-0.20240212140825-8efdfab0f93d/go.mod h1:EC6c3vxDuRGdD/5rkEk22Q3dTvJDb7NfUJmwZsDdqIU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -138,10 +129,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO 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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -160,8 +147,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -199,8 +184,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/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= From bc12485f436d0cbabb8056e8d03aabb1faba410b Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Fri, 16 Feb 2024 09:38:28 +0100 Subject: [PATCH 3/8] add dashboards controller Signed-off-by: Gabriel Bernal --- .../bases/perses.dev_persesdashboards.yaml | 576 +++++++++++------- controllers/dashboard_controller_test.go | 164 +++++ controllers/dashboards/dasboard_controller.go | 177 ++++++ .../dashboards/persesdashboard_controller.go | 140 +++++ .../perses/common/perses_client_factory.go | 55 ++ main.go | 17 +- 6 files changed, 900 insertions(+), 229 deletions(-) create mode 100644 controllers/dashboard_controller_test.go create mode 100644 controllers/dashboards/dasboard_controller.go create mode 100644 controllers/dashboards/persesdashboard_controller.go create mode 100644 internal/perses/common/perses_client_factory.go diff --git a/config/crd/bases/perses.dev_persesdashboards.yaml b/config/crd/bases/perses.dev_persesdashboards.yaml index 2e58a8e..541009d 100644 --- a/config/crd/bases/perses.dev_persesdashboards.yaml +++ b/config/crd/bases/perses.dev_persesdashboards.yaml @@ -15,268 +15,392 @@ spec: singular: persesdashboard scope: Namespaced versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: PersesDashboard is the Schema for the persesdashboards 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: - properties: - kind: - type: string - metadata: - description: ProjectMetadata is the metadata struct for resources - that belongs to a project. - properties: - createdAt: - format: date-time - type: string - name: - type: string - project: - type: string - updatedAt: - format: date-time - type: string - version: - format: int64 - type: integer - required: - - name - - project - - version - type: object - spec: - properties: - datasources: - additionalProperties: - properties: - default: - type: boolean - display: - properties: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: PersesDashboard is the Schema for the persesdashboards 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: + properties: + kind: + type: string + metadata: + description: + ProjectMetadata is the metadata struct for resources + that belongs to a project. + properties: + createdAt: + format: date-time + type: string + name: + type: string + project: + type: string + updatedAt: + format: date-time + type: string + version: + format: int64 + type: integer + required: + - name + - project + - version + type: object + spec: + properties: + datasources: + additionalProperties: + properties: + default: + type: boolean + display: + properties: + description: + type: string + name: + type: string + type: object + plugin: description: - type: string - name: - type: string - type: object - plugin: - description: Plugin will contain the datasource configuration. - The data typed is available in Cue. - properties: - kind: - type: string - spec: - x-kubernetes-preserve-unknown-fields: true - required: - - kind - - spec - type: object - required: - - default - - plugin + Plugin will contain the datasource configuration. + The data typed is available in Cue. + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + required: + - default + - plugin + type: object + description: Datasources is an optional list of datasource definition. type: object - description: Datasources is an optional list of datasource definition. - type: object - display: - properties: + display: + properties: + description: + type: string + name: + type: string + type: object + duration: description: - type: string + Duration is the default time range to use when getting + data to fill the dashboard + type: string + layouts: + items: + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object name: type: string + project: + type: string + updatedAt: + description: + "A Time represents an instant in time with nanosecond + precision. \n Programs using times should typically store and + pass them as values, not pointers. That is, time variables and + struct fields should be of type time.Time, not *time.Time. \n + A Time value can be used by multiple goroutines simultaneously + except that the methods GobDecode, UnmarshalBinary, UnmarshalJSON + and UnmarshalText are not concurrency-safe. \n Time instants + can be compared using the Before, After, and Equal methods. + The Sub method subtracts two instants, producing a Duration. + The Add method adds a Time and a Duration, producing a Time. + \n The zero value of type Time is January 1, year 1, 00:00:00.000000000 + UTC. As this time is unlikely to come up in practice, the IsZero + method gives a simple way of detecting a time that has not been + initialized explicitly. \n Each Time has associated with it + a Location, consulted when computing the presentation form of + the time, such as in the Format, Hour, and Year methods. The + methods Local, UTC, and In return a Time with a specific location. + Changing the location in this way changes only the presentation; + it does not change the instant in time being denoted and therefore + does not affect the computations described in earlier paragraphs. + \n Representations of a Time value saved by the GobEncode, MarshalBinary, + MarshalJSON, and MarshalText methods store the Time.Location's + offset, but not the location name. They therefore lose information + about Daylight Saving Time. \n In addition to the required “wall + clock” reading, a Time may contain an optional reading of the + current process's monotonic clock, to provide additional precision + for comparison or subtraction. See the “Monotonic Clocks” section + in the package documentation for details. \n Note that the Go + == operator compares not just the time instant but also the + Location and the monotonic clock reading. Therefore, Time values + should not be used as map or database keys without first guaranteeing + that the identical Location has been set for all values, which + can be achieved through use of the UTC or Local method, and + that the monotonic clock reading has been stripped by setting + t = t.Round(0). In general, prefer t.Equal(u) to t == u, since + t.Equal uses the most accurate comparison available and correctly + handles the case when only one of its arguments has a monotonic + clock reading." + type: object + version: + format: int64 + type: integer + required: + - createdAt + - name + - project + - updatedAt + - version type: object - duration: - description: Duration is the default time range to use when getting - data to fill the dashboard - format: duration - type: string - layouts: - items: - properties: - kind: - type: string - spec: - x-kubernetes-preserve-unknown-fields: true - required: - - kind - - spec - type: object - type: array - panels: - additionalProperties: - properties: - kind: - type: string - spec: + spec: + properties: + datasources: + additionalProperties: properties: + default: + type: boolean display: properties: description: type: string name: type: string - required: - - name type: object plugin: + description: + Plugin will contain the datasource configuration. + The data typed is available in Cue. properties: kind: type: string spec: x-kubernetes-preserve-unknown-fields: true required: - - kind - - spec + - kind + - spec type: object - queries: - items: - properties: - kind: - type: string - spec: + required: + - default + - plugin + type: object + description: Datasources is an optional list of datasource definition. + type: object + display: + properties: + description: + type: string + name: + type: string + type: object + duration: + description: + Duration is the default time range to use when getting + data to fill the dashboard + format: duration + type: string + layouts: + items: + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + type: array + panels: + additionalProperties: + properties: + kind: + type: string + spec: + properties: + display: + properties: + description: + type: string + name: + type: string + required: + - name + type: object + plugin: + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + queries: + items: properties: - plugin: + kind: + type: string + spec: properties: - kind: - type: string - spec: - x-kubernetes-preserve-unknown-fields: true + plugin: + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object required: - - kind - - spec + - plugin type: object required: - - plugin + - kind + - spec type: object - required: - - kind - - spec - type: object - type: array + type: array + required: + - display + - plugin + type: object required: - - display - - plugin + - kind + - spec type: object - required: - - kind - - spec - type: object + type: object + refreshInterval: + description: + RefreshInterval is the default refresh interval to + use when landing on the dashboard + format: duration + type: string + variables: + items: + properties: + kind: + description: + Kind is the type of the variable. Depending + on the value of Kind, it will change the content of Spec. + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + type: array + required: + - duration + - layouts + - panels type: object - refreshInterval: - description: RefreshInterval is the default refresh interval to - use when landing on the dashboard - format: duration - type: string - variables: + required: + - kind + - metadata + - spec + type: object + status: + description: PersesDashboardStatus defines the observed state of PersesDashboard + 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: - kind: - description: Kind is the type of the variable. Depending - on the value of Kind, it will change the content of Spec. + 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 - spec: - x-kubernetes-preserve-unknown-fields: true required: - - kind - - spec + - lastTransitionTime + - message + - reason + - status + - type type: object type: array - required: - - duration - - layouts - - panels type: object - required: - - kind - - metadata - - spec - type: object - status: - description: PersesDashboardStatus defines the observed state of PersesDashboard - 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 type: object - type: object - served: true - storage: true - subresources: - status: {} + served: true + storage: true + subresources: + status: {} diff --git a/controllers/dashboard_controller_test.go b/controllers/dashboard_controller_test.go new file mode 100644 index 0000000..36298ab --- /dev/null +++ b/controllers/dashboard_controller_test.go @@ -0,0 +1,164 @@ +package controllers + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + persesv1alpha1 "github.com/perses/perses-operator/api/v1alpha1" + dashboardcontroller "github.com/perses/perses-operator/controllers/dashboards" + common "github.com/perses/perses-operator/internal/perses/common" + persesv1 "github.com/perses/perses/pkg/model/api/v1" + persescommon "github.com/perses/perses/pkg/model/api/v1/common" + persesdashboard "github.com/perses/perses/pkg/model/api/v1/dashboard" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var _ = Describe("Dashboard controller", func() { + Context("Dashboard controller test", func() { + const PersesName = "test-perses-dashboard" + + ctx := context.Background() + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: PersesName, + Namespace: PersesName, + }, + } + + typeNamespaceName := types.NamespacedName{Name: PersesName, Namespace: PersesName} + + BeforeEach(func() { + By("Creating the Namespace to perform the tests") + err := k8sClient.Create(ctx, namespace) + Expect(err).To(Not(HaveOccurred())) + }) + + AfterEach(func() { + By("Deleting the Namespace to perform the tests") + _ = k8sClient.Delete(ctx, namespace) + }) + + It("should successfully reconcile a custom resource dashboard for Perses", func() { + By("Creating the custom resource for the Kind PersesDashboard") + dashboard := &persesv1alpha1.PersesDashboard{} + err := k8sClient.Get(ctx, typeNamespaceName, dashboard) + if err != nil && errors.IsNotFound(err) { + perses := &persesv1alpha1.PersesDashboard{ + ObjectMeta: metav1.ObjectMeta{ + Name: PersesName, + Namespace: namespace.Name, + }, + Spec: persesv1alpha1.Dashboard{ + Dashboard: persesv1.Dashboard{ + Kind: "Dashboard", + Spec: persesv1.DashboardSpec{ + Display: &persescommon.Display{ + Name: "test-dashboard", + }, + Layouts: []persesdashboard.Layout{}, + Panels: map[string]*persesv1.Panel{ + "panel1": { + Kind: "Panel", + Spec: persesv1.PanelSpec{ + Display: persesv1.PanelDisplay{ + Name: "test-panel", + }, + Plugin: persescommon.Plugin{ + Kind: "PrometheusPlugin", + Spec: map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, perses) + Expect(err).To(Not(HaveOccurred())) + } + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &persesv1alpha1.PersesDashboard{} + return k8sClient.Get(ctx, typeNamespaceName, found) + }, time.Minute, time.Second).Should(Succeed()) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "expected response") + })) + defer svr.Close() + + By("Reconciling the custom resource created") + dashboardReconciler := &dashboardcontroller.PersesDashboardReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ClientFactory: common.NewWithURL(svr.URL), + } + + // Errors might arise during reconciliation, but we are checking the final state of the resources + _, err = dashboardReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespaceName, + }) + + Expect(err).To(Not(HaveOccurred())) + + // By("Checking if the Perses API was called to create a dashboard") + // Eventually(func() error { + // return err + // }, time.Minute, time.Second).Should(Succeed()) + + By("Checking the latest Status Condition added to the Perses instance") + Eventually(func() error { + if dashboard.Status.Conditions != nil && len(dashboard.Status.Conditions) != 0 { + latestStatusCondition := dashboard.Status.Conditions[len(dashboard.Status.Conditions)-1] + expectedLatestStatusCondition := metav1.Condition{Type: common.TypeAvailablePerses, + Status: metav1.ConditionTrue, Reason: "Reconciling", + Message: fmt.Sprintf("Dashboard (%s) created successfully", dashboard.Name)} + if latestStatusCondition != expectedLatestStatusCondition { + return fmt.Errorf("The latest status condition added to the perses dashboard instance is not as expected") + } + } + return nil + }, time.Minute, time.Second).Should(Succeed()) + + persesToDelete := &persesv1alpha1.PersesDashboard{} + err = k8sClient.Get(ctx, typeNamespaceName, persesToDelete) + Expect(err).To(Not(HaveOccurred())) + + By("Deleting the custom resource") + err = k8sClient.Delete(ctx, persesToDelete) + Expect(err).To(Not(HaveOccurred())) + + // By("Checking if the Perses API was called to delete a dashboard") + // Eventually(func() error { + // return err + // }, time.Minute, time.Second).Should(Succeed()) + + By("Checking the latest Status Condition added to the Perses instance") + Eventually(func() error { + if dashboard.Status.Conditions != nil && len(dashboard.Status.Conditions) != 0 { + latestStatusCondition := dashboard.Status.Conditions[len(dashboard.Status.Conditions)-1] + expectedLatestStatusCondition := metav1.Condition{Type: common.TypeAvailablePerses, + Status: metav1.ConditionTrue, Reason: "Finalizing", + Message: fmt.Sprintf("Finalizer operations for custom resource %s name were successfully accomplished", dashboard.Name)} + if latestStatusCondition != expectedLatestStatusCondition { + return fmt.Errorf("The latest status condition added to the perses instance is not as expected") + } + } + return nil + }, time.Minute, time.Second).Should(Succeed()) + }) + }) +}) diff --git a/controllers/dashboards/dasboard_controller.go b/controllers/dashboards/dasboard_controller.go new file mode 100644 index 0000000..9e88881 --- /dev/null +++ b/controllers/dashboards/dasboard_controller.go @@ -0,0 +1,177 @@ +/* +Copyright 2023 The Perses Authors. + +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 dashboards + +import ( + "context" + "errors" + "time" + + persesv1alpha1 "github.com/perses/perses-operator/api/v1alpha1" + subreconciler "github.com/perses/perses-operator/internal/subreconciler" + perseshttp "github.com/perses/perses/pkg/client/perseshttp" + persesv1 "github.com/perses/perses/pkg/model/api/v1" + logger "github.com/sirupsen/logrus" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var dlog = logger.WithField("module", "dashboard_controller") + +func (r *PersesDashboardReconciler) reconcileDashboardInAllInstances(ctx context.Context, req ctrl.Request) (*ctrl.Result, error) { + persesInstances := &persesv1alpha1.PersesList{} + var opts []client.ListOption + err := r.Client.List(ctx, persesInstances, opts...) + if err != nil { + dlog.WithError(err).Error("Failed to get perses instances") + return subreconciler.RequeueWithError(err) + } + + if len(persesInstances.Items) == 0 { + dlog.Info("No Perses instances found") + return subreconciler.DoNotRequeue() + } + + dashboard := &persesv1alpha1.PersesDashboard{} + + if r, err := r.getLatestPersesDashboard(ctx, req, dashboard); subreconciler.ShouldHaltOrRequeue(r, err) { + return r, err + } + + for _, persesInstance := range persesInstances.Items { + if r, err := r.syncPersesDashboard(ctx, persesInstance, dashboard); subreconciler.ShouldHaltOrRequeue(r, err) { + return r, err + } + } + + return subreconciler.ContinueReconciling() +} + +func (r *PersesDashboardReconciler) syncPersesDashboard(ctx context.Context, perses persesv1alpha1.Perses, dashboard *persesv1alpha1.PersesDashboard) (*ctrl.Result, error) { + persesClient, err := r.ClientFactory.CreateClient(perses) + + if err != nil { + dlog.WithError(err).Error("Failed to create perses rest client") + return subreconciler.RequeueWithError(err) + } + + _, err = persesClient.Project().Get(dashboard.Namespace) + + if err != nil { + dlog.WithError(err).Errorf("project error: %s", dashboard.Namespace) + if errors.Is(err, perseshttp.RequestNotFoundError) { + _, err := persesClient.Project().Create(&persesv1.Project{ + Kind: "Project", + Metadata: persesv1.Metadata{ + Name: dashboard.Namespace, + }, + }) + + if err != nil { + dlog.WithError(err).Errorf("Failed to create perses project: %s", dashboard.Namespace) + return subreconciler.RequeueWithError(err) + } + + dlog.Infof("Project created: %s", dashboard.Namespace) + } + + return subreconciler.RequeueWithError(err) + } + + _, err = persesClient.Dashboard(dashboard.Namespace).Get(dashboard.Name) + + dashboardWithName := &dashboard.Spec.Dashboard + dashboardWithName.Metadata.Name = dashboard.Name + + if err != nil { + if errors.Is(err, perseshttp.RequestNotFoundError) { + _, err = persesClient.Dashboard(dashboard.Namespace).Create(dashboardWithName) + + if err != nil { + dlog.WithError(err).Errorf("Failed to create dashboard: %s", dashboard.Name) + return subreconciler.RequeueWithDelayAndError(time.Minute, err) + } + + dlog.Infof("Dashboard created: %s", dashboard.Name) + + return subreconciler.ContinueReconciling() + } + + return subreconciler.RequeueWithError(err) + } else { + _, err = persesClient.Dashboard(dashboard.Namespace).Update(dashboardWithName) + + if err != nil { + dlog.WithError(err).Errorf("Failed to update dashboard: %s", dashboard.Name) + return subreconciler.RequeueWithDelayAndError(time.Minute, err) + } + + dlog.Infof("Dashboard updated: %s", dashboard.Name) + } + + return subreconciler.ContinueReconciling() +} + +func (r *PersesDashboardReconciler) deleteDashboardInAllInstances(ctx context.Context, req ctrl.Request, dashbboardNamespace string, dashboardName string) (*ctrl.Result, error) { + persesInstances := &persesv1alpha1.PersesList{} + var opts []client.ListOption + err := r.Client.List(ctx, persesInstances, opts...) + if err != nil { + dlog.WithError(err).Error("Failed to get perses instances") + return subreconciler.RequeueWithError(err) + } + + if len(persesInstances.Items) == 0 { + dlog.Info("No Perses instances found") + return subreconciler.DoNotRequeue() + } + + for _, persesInstance := range persesInstances.Items { + if r, err := r.deleteDashboard(ctx, persesInstance, dashbboardNamespace, dashboardName); subreconciler.ShouldHaltOrRequeue(r, err) { + return r, err + } + } + + return subreconciler.DoNotRequeue() +} + +func (r *PersesDashboardReconciler) deleteDashboard(ctx context.Context, perses persesv1alpha1.Perses, dashboardNamespace string, dashboardName string) (*ctrl.Result, error) { + persesClient, err := r.ClientFactory.CreateClient(perses) + + if err != nil { + dlog.WithError(err).Error("Failed to create perses rest client") + return subreconciler.RequeueWithError(err) + } + + _, err = persesClient.Project().Get(dashboardNamespace) + + if err != nil { + dlog.WithError(err).Errorf("project error: %s", dashboardNamespace) + + return subreconciler.RequeueWithError(err) + } + + err = persesClient.Dashboard(dashboardNamespace).Delete(dashboardName) + + if err != nil && errors.Is(err, perseshttp.RequestNotFoundError) { + dlog.Infof("Dashboard not found: %s", dashboardName) + } + + dlog.Infof("Dashboard deleted: %s", dashboardName) + + return subreconciler.ContinueReconciling() +} diff --git a/controllers/dashboards/persesdashboard_controller.go b/controllers/dashboards/persesdashboard_controller.go new file mode 100644 index 0000000..eddb6a6 --- /dev/null +++ b/controllers/dashboards/persesdashboard_controller.go @@ -0,0 +1,140 @@ +/* +Copyright 2024. + +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 dashboards + +import ( + "context" + "fmt" + "time" + + logger "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + persesv1alpha1 "github.com/perses/perses-operator/api/v1alpha1" + common "github.com/perses/perses-operator/internal/perses/common" + subreconciler "github.com/perses/perses-operator/internal/subreconciler" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// PersesDashboardReconciler reconciles a PersesDashboard object +type PersesDashboardReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder + ClientFactory common.PersesClientFactory +} + +var log = logger.WithField("module", "perses_dashboards_controller") + +// +kubebuilder:rbac:groups=perses.dev,resources=persesdashboards,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=perses.dev,resources=persesdashboards/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=perses.dev,resources=persesdashboards/finalizers,verbs=update +func (r *PersesDashboardReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log.Infof("Reconciling PersesDashboard: %s/%s", req.Namespace, req.Name) + subreconcilersForPerses := []subreconciler.FnWithRequest{ + r.handleDelete, + r.setStatusToUnknown, + r.reconcileDashboardInAllInstances, + r.updateStatus, + } + + for _, f := range subreconcilersForPerses { + if r, err := f(ctx, req); subreconciler.ShouldHaltOrRequeue(r, err) { + return subreconciler.Evaluate(r, err) + } + } + + return subreconciler.Evaluate(subreconciler.DoNotRequeue()) +} + +func (r *PersesDashboardReconciler) getLatestPersesDashboard(ctx context.Context, req ctrl.Request, dashboard *persesv1alpha1.PersesDashboard) (*ctrl.Result, error) { + if err := r.Get(ctx, req.NamespacedName, dashboard); err != nil { + if apierrors.IsNotFound(err) { + log.Info("perses dashboard resource not found. Ignoring since object must be deleted") + return subreconciler.DoNotRequeue() + } + log.WithError(err).Error("Failed to get perses dashboard") + return subreconciler.RequeueWithDelayAndError(time.Second, err) + } + + return subreconciler.ContinueReconciling() +} + +func (r *PersesDashboardReconciler) handleDelete(ctx context.Context, req ctrl.Request) (*ctrl.Result, error) { + dashboard := &persesv1alpha1.PersesDashboard{} + + if err := r.Get(ctx, req.NamespacedName, dashboard); err != nil { + if !apierrors.IsNotFound(err) { + log.WithError(err).Error("Failed to get perses dashboard") + return subreconciler.RequeueWithError(err) + } + + log.Infof("perses dashboard resource not found. Deleting '%s' in '%s'", req.Name, req.Namespace) + + return r.deleteDashboardInAllInstances(ctx, req, req.NamespacedName.Namespace, req.NamespacedName.Name) + } + + return subreconciler.ContinueReconciling() +} + +func (r *PersesDashboardReconciler) setStatusToUnknown(ctx context.Context, req ctrl.Request) (*ctrl.Result, error) { + dashboard := &persesv1alpha1.PersesDashboard{} + + if r, err := r.getLatestPersesDashboard(ctx, req, dashboard); subreconciler.ShouldHaltOrRequeue(r, err) { + return r, err + } + + if dashboard.Status.Conditions == nil || len(dashboard.Status.Conditions) == 0 { + meta.SetStatusCondition(&dashboard.Status.Conditions, metav1.Condition{Type: common.TypeAvailablePerses, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) + if err := r.Status().Update(ctx, dashboard); err != nil { + log.WithError(err).Error("Failed to update Perses dashboard status") + return subreconciler.RequeueWithDelayAndError(time.Second*10, err) + } + } + + return subreconciler.ContinueReconciling() +} + +func (r *PersesDashboardReconciler) updateStatus(ctx context.Context, req ctrl.Request) (*ctrl.Result, error) { + dashboard := &persesv1alpha1.PersesDashboard{} + + if r, err := r.getLatestPersesDashboard(ctx, req, dashboard); subreconciler.ShouldHaltOrRequeue(r, err) { + return r, err + } + + meta.SetStatusCondition(&dashboard.Status.Conditions, metav1.Condition{Type: common.TypeAvailablePerses, + Status: metav1.ConditionTrue, Reason: "Reconciling", + Message: fmt.Sprintf("Dashboard (%s) created successfully", dashboard.Name)}) + + if err := r.Status().Update(ctx, dashboard); err != nil { + log.Error(err, "Failed to update Perses dashboard status") + return subreconciler.RequeueWithError(err) + } + + return subreconciler.ContinueReconciling() +} + +func (r *PersesDashboardReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&persesv1alpha1.PersesDashboard{}). + Complete(r) +} diff --git a/internal/perses/common/perses_client_factory.go b/internal/perses/common/perses_client_factory.go new file mode 100644 index 0000000..77366af --- /dev/null +++ b/internal/perses/common/perses_client_factory.go @@ -0,0 +1,55 @@ +package common + +import ( + "fmt" + + persesv1alpha1 "github.com/perses/perses-operator/api/v1alpha1" + v1 "github.com/perses/perses/pkg/client/api/v1" + perseshttp "github.com/perses/perses/pkg/client/perseshttp" +) + +type PersesClientFactory interface { + CreateClient(perses persesv1alpha1.Perses) (v1.ClientInterface, error) +} + +type PersesClientFactoryWithConfig struct{} + +func NewWithConfig() PersesClientFactory { + return &PersesClientFactoryWithConfig{} +} + +func (f *PersesClientFactoryWithConfig) CreateClient(perses persesv1alpha1.Perses) (v1.ClientInterface, error) { + restClient, err := perseshttp.NewFromConfig(perseshttp.RestConfigClient{ + URL: fmt.Sprintf("http://%s.%s.svc.cluster.local:%d", perses.Name, perses.Namespace, perses.Spec.ContainerPort), + }) + + if err != nil { + return nil, err + } + + persesClient := v1.NewWithClient(restClient) + + return persesClient, nil +} + +type PersesClientFactoryWithURL struct { + url string +} + +func NewWithURL(url string) PersesClientFactory { + return &PersesClientFactoryWithURL{url: url} +} + +func (f *PersesClientFactoryWithURL) CreateClient(config persesv1alpha1.Perses) (v1.ClientInterface, error) { + restClient, err := perseshttp.NewFromConfig(perseshttp.RestConfigClient{ + URL: f.url, + }) + + if err != nil { + return nil, err + } + + persesClient := v1.NewWithClient(restClient) + + return persesClient, nil +} diff --git a/main.go b/main.go index cdd584a..b28ad5d 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" persesv1alpha1 "github.com/perses/perses-operator/api/v1alpha1" - controllers "github.com/perses/perses-operator/controllers/perses" + dashboardcontroller "github.com/perses/perses-operator/controllers/dashboards" + persescontroller "github.com/perses/perses-operator/controllers/perses" + "github.com/perses/perses-operator/internal/perses/common" //+kubebuilder:scaffold:imports ) @@ -110,16 +112,25 @@ func main() { os.Exit(1) } - if err = (&controllers.PersesReconciler{ + if err = (&persescontroller.PersesReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Config: controllers.Config{ + Config: persescontroller.Config{ PersesImage: persesImage, }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Perses") os.Exit(1) } + + if err = (&dashboardcontroller.PersesDashboardReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ClientFactory: common.NewWithConfig(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "PersesDashboard") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { From eb46dee3d6ac248b755238fd243c10d6d7a7412b Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Fri, 16 Feb 2024 09:38:46 +0100 Subject: [PATCH 4/8] fix docker build Signed-off-by: Gabriel Bernal --- Dockerfile | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6de4d83..e8eeb6d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ RUN go mod download COPY main.go main.go COPY api/ api/ COPY controllers/ controllers/ +COPY internal/ internal/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/Makefile b/Makefile index 25b9444..340dd1f 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) # # For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both # perses.dev/perses-operator-bundle:$VERSION and perses.dev/perses-operator-catalog:$VERSION. -IMAGE_TAG_BASE ?= persesdev/perses-operator +IMAGE_TAG_BASE ?= quay.io/gbernal/perses-operator # BUNDLE_IMG defines the image:tag used for the bundle. # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) From c236775b222efb696a3e560906f2f34d44f4b29a Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Fri, 16 Feb 2024 10:10:05 +0100 Subject: [PATCH 5/8] make case_sensitive optional Signed-off-by: Gabriel Bernal --- config/crd/bases/perses.dev_perses.yaml | 1 - .../bases/perses.dev_persesdashboards.yaml | 576 +++++++----------- .../samples/perses.dev_v1alpha1_perses.yaml | 1 - 3 files changed, 226 insertions(+), 352 deletions(-) diff --git a/config/crd/bases/perses.dev_perses.yaml b/config/crd/bases/perses.dev_perses.yaml index c383d7e..690e612 100644 --- a/config/crd/bases/perses.dev_perses.yaml +++ b/config/crd/bases/perses.dev_perses.yaml @@ -50,7 +50,6 @@ spec: folder: type: string required: - - case_sensitive - extension - folder type: object diff --git a/config/crd/bases/perses.dev_persesdashboards.yaml b/config/crd/bases/perses.dev_persesdashboards.yaml index 541009d..2e58a8e 100644 --- a/config/crd/bases/perses.dev_persesdashboards.yaml +++ b/config/crd/bases/perses.dev_persesdashboards.yaml @@ -15,392 +15,268 @@ spec: singular: persesdashboard scope: Namespaced versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: PersesDashboard is the Schema for the persesdashboards 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: - properties: - kind: - type: string - metadata: - description: - ProjectMetadata is the metadata struct for resources - that belongs to a project. - properties: - createdAt: - format: date-time - type: string - name: - type: string - project: - type: string - updatedAt: - format: date-time - type: string - version: - format: int64 - type: integer - required: - - name - - project - - version - type: object - spec: - properties: - datasources: - additionalProperties: - properties: - default: - type: boolean - display: - properties: - description: - type: string - name: - type: string - type: object - plugin: - description: - Plugin will contain the datasource configuration. - The data typed is available in Cue. - properties: - kind: - type: string - spec: - x-kubernetes-preserve-unknown-fields: true - required: - - kind - - spec - type: object - required: - - default - - plugin - type: object - description: Datasources is an optional list of datasource definition. - type: object - display: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: PersesDashboard is the Schema for the persesdashboards 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: + properties: + kind: + type: string + metadata: + description: ProjectMetadata is the metadata struct for resources + that belongs to a project. + properties: + createdAt: + format: date-time + type: string + name: + type: string + project: + type: string + updatedAt: + format: date-time + type: string + version: + format: int64 + type: integer + required: + - name + - project + - version + type: object + spec: + properties: + datasources: + additionalProperties: properties: - description: - type: string - name: - type: string - type: object - duration: - description: - Duration is the default time range to use when getting - data to fill the dashboard - type: string - layouts: - items: - properties: - kind: - type: string - spec: - x-kubernetes-preserve-unknown-fields: true - required: + default: + type: boolean + display: + properties: + description: + type: string + name: + type: string + type: object + plugin: + description: Plugin will contain the datasource configuration. + The data typed is available in Cue. + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: - kind - spec - type: object - name: + type: object + required: + - default + - plugin + type: object + description: Datasources is an optional list of datasource definition. + type: object + display: + properties: + description: type: string - project: + name: type: string - updatedAt: - description: - "A Time represents an instant in time with nanosecond - precision. \n Programs using times should typically store and - pass them as values, not pointers. That is, time variables and - struct fields should be of type time.Time, not *time.Time. \n - A Time value can be used by multiple goroutines simultaneously - except that the methods GobDecode, UnmarshalBinary, UnmarshalJSON - and UnmarshalText are not concurrency-safe. \n Time instants - can be compared using the Before, After, and Equal methods. - The Sub method subtracts two instants, producing a Duration. - The Add method adds a Time and a Duration, producing a Time. - \n The zero value of type Time is January 1, year 1, 00:00:00.000000000 - UTC. As this time is unlikely to come up in practice, the IsZero - method gives a simple way of detecting a time that has not been - initialized explicitly. \n Each Time has associated with it - a Location, consulted when computing the presentation form of - the time, such as in the Format, Hour, and Year methods. The - methods Local, UTC, and In return a Time with a specific location. - Changing the location in this way changes only the presentation; - it does not change the instant in time being denoted and therefore - does not affect the computations described in earlier paragraphs. - \n Representations of a Time value saved by the GobEncode, MarshalBinary, - MarshalJSON, and MarshalText methods store the Time.Location's - offset, but not the location name. They therefore lose information - about Daylight Saving Time. \n In addition to the required “wall - clock” reading, a Time may contain an optional reading of the - current process's monotonic clock, to provide additional precision - for comparison or subtraction. See the “Monotonic Clocks” section - in the package documentation for details. \n Note that the Go - == operator compares not just the time instant but also the - Location and the monotonic clock reading. Therefore, Time values - should not be used as map or database keys without first guaranteeing - that the identical Location has been set for all values, which - can be achieved through use of the UTC or Local method, and - that the monotonic clock reading has been stripped by setting - t = t.Round(0). In general, prefer t.Equal(u) to t == u, since - t.Equal uses the most accurate comparison available and correctly - handles the case when only one of its arguments has a monotonic - clock reading." - type: object - version: - format: int64 - type: integer - required: - - createdAt - - name - - project - - updatedAt - - version type: object - spec: - properties: - datasources: - additionalProperties: + duration: + description: Duration is the default time range to use when getting + data to fill the dashboard + format: duration + type: string + layouts: + items: + properties: + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + type: array + panels: + additionalProperties: + properties: + kind: + type: string + spec: properties: - default: - type: boolean display: properties: description: type: string name: type: string + required: + - name type: object plugin: - description: - Plugin will contain the datasource configuration. - The data typed is available in Cue. properties: kind: type: string spec: x-kubernetes-preserve-unknown-fields: true required: - - kind - - spec + - kind + - spec type: object - required: - - default - - plugin - type: object - description: Datasources is an optional list of datasource definition. - type: object - display: - properties: - description: - type: string - name: - type: string - type: object - duration: - description: - Duration is the default time range to use when getting - data to fill the dashboard - format: duration - type: string - layouts: - items: - properties: - kind: - type: string - spec: - x-kubernetes-preserve-unknown-fields: true - required: - - kind - - spec - type: object - type: array - panels: - additionalProperties: - properties: - kind: - type: string - spec: - properties: - display: - properties: - description: - type: string - name: - type: string - required: - - name - type: object - plugin: - properties: - kind: - type: string - spec: - x-kubernetes-preserve-unknown-fields: true - required: - - kind - - spec - type: object - queries: - items: + queries: + items: + properties: + kind: + type: string + spec: properties: - kind: - type: string - spec: + plugin: properties: - plugin: - properties: - kind: - type: string - spec: - x-kubernetes-preserve-unknown-fields: true - required: - - kind - - spec - type: object + kind: + type: string + spec: + x-kubernetes-preserve-unknown-fields: true required: - - plugin + - kind + - spec type: object required: - - kind - - spec + - plugin type: object - type: array - required: - - display - - plugin - type: object - required: - - kind - - spec - type: object - type: object - refreshInterval: - description: - RefreshInterval is the default refresh interval to - use when landing on the dashboard - format: duration - type: string - variables: - items: - properties: - kind: - description: - Kind is the type of the variable. Depending - on the value of Kind, it will change the content of Spec. - type: string - spec: - x-kubernetes-preserve-unknown-fields: true + required: + - kind + - spec + type: object + type: array required: - - kind - - spec + - display + - plugin type: object - type: array - required: - - duration - - layouts - - panels + required: + - kind + - spec + type: object type: object - required: - - kind - - metadata - - spec - type: object - status: - description: PersesDashboardStatus defines the observed state of PersesDashboard - properties: - conditions: + refreshInterval: + description: RefreshInterval is the default refresh interval to + use when landing on the dashboard + format: duration + type: string + variables: 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])$ + kind: + description: Kind is the type of the variable. Depending + on the value of Kind, it will change the content of Spec. type: string + spec: + x-kubernetes-preserve-unknown-fields: true required: - - lastTransitionTime - - message - - reason - - status - - type + - kind + - spec type: object type: array + required: + - duration + - layouts + - panels type: object + required: + - kind + - metadata + - spec + type: object + status: + description: PersesDashboardStatus defines the observed state of PersesDashboard + 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 type: object - served: true - storage: true - subresources: - status: {} + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/samples/perses.dev_v1alpha1_perses.yaml b/config/samples/perses.dev_v1alpha1_perses.yaml index 6515029..3e96fce 100644 --- a/config/samples/perses.dev_v1alpha1_perses.yaml +++ b/config/samples/perses.dev_v1alpha1_perses.yaml @@ -14,7 +14,6 @@ spec: file: folder: "/etc/perses/storage" extension: "yaml" - case_sensitive: false schemas: panels_path: "/etc/perses/schemas/panels" queries_path: "/etc/perses/schemas/queries" From 75f56df707c233bf438b6874c6b49d3bf813018c Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Mon, 19 Feb 2024 12:28:21 +0100 Subject: [PATCH 6/8] inject perses client and mock it for dashboard CRD tests Signed-off-by: Gabriel Bernal --- controllers/dashboard_controller_test.go | 184 +++++++++++------- go.mod | 3 + go.sum | 1 + .../perses/common/perses_client_factory.go | 12 ++ internal/perses/perses_mock_api.go | 54 +++++ 5 files changed, 187 insertions(+), 67 deletions(-) create mode 100644 internal/perses/perses_mock_api.go diff --git a/controllers/dashboard_controller_test.go b/controllers/dashboard_controller_test.go index 36298ab..4abf300 100644 --- a/controllers/dashboard_controller_test.go +++ b/controllers/dashboard_controller_test.go @@ -3,15 +3,16 @@ package controllers import ( "context" "fmt" - "net/http" - "net/http/httptest" + "os" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" persesv1alpha1 "github.com/perses/perses-operator/api/v1alpha1" dashboardcontroller "github.com/perses/perses-operator/controllers/dashboards" + internal "github.com/perses/perses-operator/internal/perses" common "github.com/perses/perses-operator/internal/perses/common" + "github.com/perses/perses/pkg/client/perseshttp" persesv1 "github.com/perses/perses/pkg/model/api/v1" persescommon "github.com/perses/perses/pkg/model/api/v1/common" persesdashboard "github.com/perses/perses/pkg/model/api/v1/dashboard" @@ -24,64 +25,101 @@ import ( var _ = Describe("Dashboard controller", func() { Context("Dashboard controller test", func() { - const PersesName = "test-perses-dashboard" + const PersesName = "perses-for-dashboard" + const PersesNamespace = "perses-dashboard-test" + const DashboardName = "my-custom-dashboard" ctx := context.Background() namespace := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ - Name: PersesName, - Namespace: PersesName, + Name: PersesNamespace, + Namespace: PersesNamespace, }, } - typeNamespaceName := types.NamespacedName{Name: PersesName, Namespace: PersesName} + persesNamespaceName := types.NamespacedName{Name: PersesName, Namespace: PersesNamespace} + dashboardNamespaceName := types.NamespacedName{Name: DashboardName, Namespace: PersesNamespace} + + persesImage := "perses-dev.io/perses:test" + + newDashboard := &persesv1.Dashboard{ + Kind: "Dashboard", + Metadata: persesv1.ProjectMetadata{ + Metadata: persesv1.Metadata{ + Name: DashboardName, + }, + }, + Spec: persesv1.DashboardSpec{ + Display: &persescommon.Display{ + Name: DashboardName, + }, + Layouts: []persesdashboard.Layout{}, + Panels: map[string]*persesv1.Panel{ + "panel1": { + Kind: "Panel", + Spec: persesv1.PanelSpec{ + Display: persesv1.PanelDisplay{ + Name: "test-panel", + }, + Plugin: persescommon.Plugin{ + Kind: "PrometheusPlugin", + Spec: map[string]interface{}{}, + }, + }, + }, + }, + }, + } BeforeEach(func() { By("Creating the Namespace to perform the tests") err := k8sClient.Create(ctx, namespace) Expect(err).To(Not(HaveOccurred())) + + By("Setting the Image ENV VAR which stores the Operand image") + err = os.Setenv("PERSES_IMAGE", persesImage) + Expect(err).To(Not(HaveOccurred())) }) AfterEach(func() { By("Deleting the Namespace to perform the tests") _ = k8sClient.Delete(ctx, namespace) + + By("Removing the Image ENV VAR which stores the Operand image") + _ = os.Unsetenv("PERSES_IMAGE") }) It("should successfully reconcile a custom resource dashboard for Perses", func() { + By("Creating the custom resource for the Kind Perses") + perses := &persesv1alpha1.Perses{} + err := k8sClient.Get(ctx, persesNamespaceName, perses) + if err != nil && errors.IsNotFound(err) { + perses := &persesv1alpha1.Perses{ + ObjectMeta: metav1.ObjectMeta{ + Name: PersesName, + Namespace: PersesNamespace, + }, + Spec: persesv1alpha1.PersesSpec{ + ContainerPort: 8080, + }, + } + + err = k8sClient.Create(ctx, perses) + Expect(err).To(Not(HaveOccurred())) + } + By("Creating the custom resource for the Kind PersesDashboard") dashboard := &persesv1alpha1.PersesDashboard{} - err := k8sClient.Get(ctx, typeNamespaceName, dashboard) + err = k8sClient.Get(ctx, dashboardNamespaceName, dashboard) if err != nil && errors.IsNotFound(err) { perses := &persesv1alpha1.PersesDashboard{ ObjectMeta: metav1.ObjectMeta{ - Name: PersesName, - Namespace: namespace.Name, + Name: DashboardName, + Namespace: PersesNamespace, }, Spec: persesv1alpha1.Dashboard{ - Dashboard: persesv1.Dashboard{ - Kind: "Dashboard", - Spec: persesv1.DashboardSpec{ - Display: &persescommon.Display{ - Name: "test-dashboard", - }, - Layouts: []persesdashboard.Layout{}, - Panels: map[string]*persesv1.Panel{ - "panel1": { - Kind: "Panel", - Spec: persesv1.PanelSpec{ - Display: persesv1.PanelDisplay{ - Name: "test-panel", - }, - Plugin: persescommon.Plugin{ - Kind: "PrometheusPlugin", - Spec: map[string]interface{}{}, - }, - }, - }, - }, - }, - }, + Dashboard: *newDashboard, }, } @@ -92,70 +130,82 @@ var _ = Describe("Dashboard controller", func() { By("Checking if the custom resource was successfully created") Eventually(func() error { found := &persesv1alpha1.PersesDashboard{} - return k8sClient.Get(ctx, typeNamespaceName, found) + return k8sClient.Get(ctx, dashboardNamespaceName, found) }, time.Minute, time.Second).Should(Succeed()) - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "expected response") - })) - defer svr.Close() + // Mock the Perses API to assert that Is creating a new dashboard when reconciling + mockPersesClient := new(internal.MockClient) + mockDashboard := new(internal.MockDashboard) + + mockPersesClient.On("Dashboard", PersesNamespace).Return(mockDashboard) + getDashboard := mockDashboard.On("Get", DashboardName).Return(&persesv1.Dashboard{}, perseshttp.RequestNotFoundError) + mockDashboard.On("Create", newDashboard).Return(&persesv1.Dashboard{}, nil) By("Reconciling the custom resource created") dashboardReconciler := &dashboardcontroller.PersesDashboardReconciler{ Client: k8sClient, Scheme: k8sClient.Scheme(), - ClientFactory: common.NewWithURL(svr.URL), + ClientFactory: common.NewWithClient(mockPersesClient), } - // Errors might arise during reconciliation, but we are checking the final state of the resources _, err = dashboardReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespaceName, + NamespacedName: dashboardNamespaceName, }) Expect(err).To(Not(HaveOccurred())) - // By("Checking if the Perses API was called to create a dashboard") - // Eventually(func() error { - // return err - // }, time.Minute, time.Second).Should(Succeed()) + // The dashboard was created in the Perses API + getDashboard.Unset() + mockDashboard.On("Get", DashboardName).Return(&persesv1.Dashboard{}, nil) + + By("Checking if the Perses API was called to create a dashboard") + Eventually(func() error { + if !mockDashboard.AssertExpectations(GinkgoT()) { + return fmt.Errorf("The Perses API was not called to create a dashboard") + } + return nil + }, time.Minute, time.Second).Should(Succeed()) - By("Checking the latest Status Condition added to the Perses instance") + By("Checking the latest Status Condition added to the Perses dashboard instance") Eventually(func() error { - if dashboard.Status.Conditions != nil && len(dashboard.Status.Conditions) != 0 { - latestStatusCondition := dashboard.Status.Conditions[len(dashboard.Status.Conditions)-1] + dashboardWithStatus := &persesv1alpha1.PersesDashboard{} + err = k8sClient.Get(ctx, dashboardNamespaceName, dashboardWithStatus) + + if dashboardWithStatus.Status.Conditions == nil || len(dashboardWithStatus.Status.Conditions) == 0 { + return fmt.Errorf("No status condition was added to the perses dashboard instance") + } else { + latestStatusCondition := dashboardWithStatus.Status.Conditions[len(dashboardWithStatus.Status.Conditions)-1] expectedLatestStatusCondition := metav1.Condition{Type: common.TypeAvailablePerses, Status: metav1.ConditionTrue, Reason: "Reconciling", - Message: fmt.Sprintf("Dashboard (%s) created successfully", dashboard.Name)} - if latestStatusCondition != expectedLatestStatusCondition { - return fmt.Errorf("The latest status condition added to the perses dashboard instance is not as expected") + Message: fmt.Sprintf("Dashboard (%s) created successfully", dashboardWithStatus.Name)} + if latestStatusCondition.Message != expectedLatestStatusCondition.Message && latestStatusCondition.Reason != expectedLatestStatusCondition.Reason && latestStatusCondition.Status != expectedLatestStatusCondition.Status && latestStatusCondition.Type != expectedLatestStatusCondition.Type { + return fmt.Errorf("The latest status condition added to the perses dashboard instance is not as expected, got: %v", expectedLatestStatusCondition) } } - return nil + + return err }, time.Minute, time.Second).Should(Succeed()) - persesToDelete := &persesv1alpha1.PersesDashboard{} - err = k8sClient.Get(ctx, typeNamespaceName, persesToDelete) + mockDashboard.On("Delete", DashboardName).Return(nil) + + dashboardToDelete := &persesv1alpha1.PersesDashboard{} + err = k8sClient.Get(ctx, dashboardNamespaceName, dashboardToDelete) Expect(err).To(Not(HaveOccurred())) By("Deleting the custom resource") - err = k8sClient.Delete(ctx, persesToDelete) + err = k8sClient.Delete(ctx, dashboardToDelete) Expect(err).To(Not(HaveOccurred())) - // By("Checking if the Perses API was called to delete a dashboard") - // Eventually(func() error { - // return err - // }, time.Minute, time.Second).Should(Succeed()) + _, err = dashboardReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: dashboardNamespaceName, + }) + + Expect(err).To(Not(HaveOccurred())) - By("Checking the latest Status Condition added to the Perses instance") + By("Checking if the Perses API was called to delete a dashboard") Eventually(func() error { - if dashboard.Status.Conditions != nil && len(dashboard.Status.Conditions) != 0 { - latestStatusCondition := dashboard.Status.Conditions[len(dashboard.Status.Conditions)-1] - expectedLatestStatusCondition := metav1.Condition{Type: common.TypeAvailablePerses, - Status: metav1.ConditionTrue, Reason: "Finalizing", - Message: fmt.Sprintf("Finalizer operations for custom resource %s name were successfully accomplished", dashboard.Name)} - if latestStatusCondition != expectedLatestStatusCondition { - return fmt.Errorf("The latest status condition added to the perses instance is not as expected") - } + if !mockDashboard.AssertExpectations(GinkgoT()) { + return fmt.Errorf("The Perses API was not called to create a dashboard") } return nil }, time.Minute, time.Second).Should(Succeed()) diff --git a/go.mod b/go.mod index 72ea8b5..1d94524 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/onsi/gomega v1.27.10 github.com/perses/perses v0.43.1-0.20240212140825-8efdfab0f93d github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.8.4 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 @@ -48,11 +49,13 @@ require ( github.com/nexucis/lamenv v0.5.2 // indirect github.com/perses/common v0.22.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect diff --git a/go.sum b/go.sum index d14965f..ab204bf 100644 --- a/go.sum +++ b/go.sum @@ -120,6 +120,7 @@ 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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/internal/perses/common/perses_client_factory.go b/internal/perses/common/perses_client_factory.go index 77366af..7345206 100644 --- a/internal/perses/common/perses_client_factory.go +++ b/internal/perses/common/perses_client_factory.go @@ -53,3 +53,15 @@ func (f *PersesClientFactoryWithURL) CreateClient(config persesv1alpha1.Perses) return persesClient, nil } + +type PersesClientFactoryWithClient struct { + client v1.ClientInterface +} + +func NewWithClient(client v1.ClientInterface) PersesClientFactory { + return &PersesClientFactoryWithClient{client: client} +} + +func (f *PersesClientFactoryWithClient) CreateClient(config persesv1alpha1.Perses) (v1.ClientInterface, error) { + return f.client, nil +} diff --git a/internal/perses/perses_mock_api.go b/internal/perses/perses_mock_api.go new file mode 100644 index 0000000..895737a --- /dev/null +++ b/internal/perses/perses_mock_api.go @@ -0,0 +1,54 @@ +package perses + +import ( + v1 "github.com/perses/perses/pkg/client/api/v1" + modelv1 "github.com/perses/perses/pkg/model/api/v1" + "github.com/stretchr/testify/mock" +) + +type MockClient struct { + v1.ClientInterface + mock.Mock +} + +type project struct { + v1.ProjectInterface +} + +func (p *project) Get(name string) (*modelv1.Project, error) { + return nil, nil +} + +func (c *MockClient) Project() v1.ProjectInterface { + return &project{} +} + +type MockDashboard struct { + v1.DashboardInterface + mock.Mock +} + +func (c *MockClient) Dashboard(project string) v1.DashboardInterface { + args := c.Called(project) + return args.Get(0).(v1.DashboardInterface) +} + +func (d *MockDashboard) Get(name string) (*modelv1.Dashboard, error) { + args := d.Called(name) + return args.Get(0).(*modelv1.Dashboard), args.Error(1) +} + +func (d *MockDashboard) Update(dashboard *modelv1.Dashboard) (*modelv1.Dashboard, error) { + args := d.Called(dashboard) + return args.Get(0).(*modelv1.Dashboard), args.Error(1) +} + +func (d *MockDashboard) Delete(name string) error { + args := d.Called(name) + return args.Error(0) +} + +func (d *MockDashboard) Create(dashboard *modelv1.Dashboard) (*modelv1.Dashboard, error) { + args := d.Called(dashboard) + return args.Get(0).(*modelv1.Dashboard), args.Error(1) +} From 468f8d6e62db0a6c252c6abe96b3fe2a1bb144d1 Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Tue, 20 Feb 2024 09:40:17 +0100 Subject: [PATCH 7/8] regenerate CR definition Signed-off-by: Gabriel Bernal --- Makefile | 2 +- config/crd/bases/perses.dev_perses.yaml | 1 - controllers/dashboards/dasboard_controller.go | 8 ++++---- go.mod | 2 +- go.sum | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 340dd1f..25b9444 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) # # For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both # perses.dev/perses-operator-bundle:$VERSION and perses.dev/perses-operator-catalog:$VERSION. -IMAGE_TAG_BASE ?= quay.io/gbernal/perses-operator +IMAGE_TAG_BASE ?= persesdev/perses-operator # BUNDLE_IMG defines the image:tag used for the bundle. # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) diff --git a/config/crd/bases/perses.dev_perses.yaml b/config/crd/bases/perses.dev_perses.yaml index 690e612..14f2543 100644 --- a/config/crd/bases/perses.dev_perses.yaml +++ b/config/crd/bases/perses.dev_perses.yaml @@ -50,7 +50,6 @@ spec: folder: type: string required: - - extension - folder type: object sql: diff --git a/controllers/dashboards/dasboard_controller.go b/controllers/dashboards/dasboard_controller.go index 9e88881..c3d1a2e 100644 --- a/controllers/dashboards/dasboard_controller.go +++ b/controllers/dashboards/dasboard_controller.go @@ -94,12 +94,12 @@ func (r *PersesDashboardReconciler) syncPersesDashboard(ctx context.Context, per _, err = persesClient.Dashboard(dashboard.Namespace).Get(dashboard.Name) - dashboardWithName := &dashboard.Spec.Dashboard - dashboardWithName.Metadata.Name = dashboard.Name + persesDashboard := &dashboard.Spec.Dashboard + persesDashboard.Metadata.Name = dashboard.Name if err != nil { if errors.Is(err, perseshttp.RequestNotFoundError) { - _, err = persesClient.Dashboard(dashboard.Namespace).Create(dashboardWithName) + _, err = persesClient.Dashboard(dashboard.Namespace).Create(persesDashboard) if err != nil { dlog.WithError(err).Errorf("Failed to create dashboard: %s", dashboard.Name) @@ -113,7 +113,7 @@ func (r *PersesDashboardReconciler) syncPersesDashboard(ctx context.Context, per return subreconciler.RequeueWithError(err) } else { - _, err = persesClient.Dashboard(dashboard.Namespace).Update(dashboardWithName) + _, err = persesClient.Dashboard(dashboard.Namespace).Update(persesDashboard) if err != nil { dlog.WithError(err).Errorf("Failed to update dashboard: %s", dashboard.Name) diff --git a/go.mod b/go.mod index 1d94524..928764e 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 - github.com/perses/perses v0.43.1-0.20240212140825-8efdfab0f93d + github.com/perses/perses v0.43.1-0.20240219142255-6799f0a7cffc github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index ab204bf..25962fc 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/perses/common v0.22.0 h1:G5nCKjBbgu0Y/sosRWe5VfgFy9XoECVv2KcfAnKEqsM= github.com/perses/common v0.22.0/go.mod h1:FjtkQ3IYxithLrgTNbuG3rdusl8XXoE60a2DaX4FEHk= -github.com/perses/perses v0.43.1-0.20240212140825-8efdfab0f93d h1:so+n1SsmsPGF/XqPyZlkVAf87XdAgfIlLQAb+Wbo2Nc= -github.com/perses/perses v0.43.1-0.20240212140825-8efdfab0f93d/go.mod h1:EC6c3vxDuRGdD/5rkEk22Q3dTvJDb7NfUJmwZsDdqIU= +github.com/perses/perses v0.43.1-0.20240219142255-6799f0a7cffc h1:ruT3UEorqS9Pb3EfzBVqdTdeMt0m2xXlf4D1u1HPsrQ= +github.com/perses/perses v0.43.1-0.20240219142255-6799f0a7cffc/go.mod h1:EC6c3vxDuRGdD/5rkEk22Q3dTvJDb7NfUJmwZsDdqIU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 2f6f998910c3351c19ad2cea78830c4187b2ce1c Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Tue, 26 Mar 2024 12:30:22 +0100 Subject: [PATCH 8/8] use perses v0.44 Signed-off-by: Gabriel Bernal --- config/crd/bases/perses.dev_perses.yaml | 108 +++++++++++++++++++++--- go.mod | 31 ++++--- go.sum | 82 ++++++++++++------ 3 files changed, 167 insertions(+), 54 deletions(-) diff --git a/config/crd/bases/perses.dev_perses.yaml b/config/crd/bases/perses.dev_perses.yaml index 14f2543..69966e3 100644 --- a/config/crd/bases/perses.dev_perses.yaml +++ b/config/crd/bases/perses.dev_perses.yaml @@ -199,6 +199,15 @@ spec: - write_timeout type: object type: object + deactivate_front: + description: When it is true, Perses won't serve the frontend + anymore. + type: boolean + ephemeral_dashboards_cleanup_interval: + description: EphemeralDashboardsCleanupInterval is the interval + at which the ephemeral dashboards are cleaned up + format: int64 + type: integer important_dashboards: description: ImportantDashboards contains important dashboard selectors @@ -220,6 +229,8 @@ spec: on the home page type: string provisioning: + description: Provisioning contains the provisioning config that + can be used if you want to provide default resources. properties: folders: items: @@ -279,6 +290,25 @@ spec: properties: auth_url: type: object + client_credentials: + properties: + client_id: + description: Hidden special type for storing + secrets. + type: string + client_secret: + description: Hidden special type for storing + secrets. + type: string + scopes: + items: + type: string + type: array + required: + - client_id + - client_secret + - scopes + type: object client_id: description: Hidden special type for storing secrets. @@ -289,6 +319,27 @@ spec: type: string custom_login_property: type: string + device_auth_url: + type: object + device_code: + properties: + client_id: + description: Hidden special type for storing + secrets. + type: string + client_secret: + description: Hidden special type for storing + secrets. + type: string + scopes: + items: + type: string + type: array + required: + - client_id + - client_secret + - scopes + type: object disable_pkce: type: boolean name: @@ -309,11 +360,9 @@ spec: - auth_url - client_id - client_secret - - custom_login_property + - device_auth_url - disable_pkce - name - - redirect_uri - - scopes - slug_id - token_url - user_infos_url @@ -322,6 +371,25 @@ spec: oidc: items: properties: + client_credentials: + properties: + client_id: + description: Hidden special type for storing + secrets. + type: string + client_secret: + description: Hidden special type for storing + secrets. + type: string + scopes: + items: + type: string + type: array + required: + - client_id + - client_secret + - scopes + type: object client_id: description: Hidden special type for storing secrets. @@ -330,6 +398,25 @@ spec: description: Hidden special type for storing secrets. type: string + device_code: + properties: + client_id: + description: Hidden special type for storing + secrets. + type: string + client_secret: + description: Hidden special type for storing + secrets. + type: string + scopes: + items: + type: string + type: array + required: + - client_id + - client_secret + - scopes + type: object disable_pkce: type: boolean discovery_url: @@ -354,13 +441,9 @@ spec: - client_id - client_secret - disable_pkce - - discovery_url - issuer - name - - redirect_uri - - scopes - slug_id - - url_params type: object type: array required: @@ -420,12 +503,9 @@ spec: description: EncryptionKey is the secret key used to encrypt and decrypt sensitive data stored in the database such as the password of the basic auth for a datasource. Note that - if it is not provided, it will use a default value. When - Perses is used in a multi instance mode, you should provide - the key. Otherwise, each instance will have a different - key and therefore won't be able to decrypt what the other - is encrypting. Also note the key must be at least 32 bytes - long. + if it is not provided, it will use a default value. On a + production instance, you should set this key. Also note + the key must be at least 32 bytes long. type: string encryption_key_file: description: EncryptionKeyFile is the path to file containing @@ -439,6 +519,8 @@ spec: - enable_auth - readonly type: object + required: + - deactivate_front type: object containerPort: format: int32 diff --git a/go.mod b/go.mod index 928764e..21d3ffe 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ require ( github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 - github.com/perses/perses v0.43.1-0.20240219142255-6799f0a7cffc + github.com/perses/perses v0.44.0 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 @@ -23,6 +23,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -33,7 +34,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.6.0 // indirect @@ -44,31 +45,35 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/muhlemmer/gu v0.3.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/nexucis/lamenv v0.5.2 // indirect - github.com/perses/common v0.22.0 // indirect + github.com/perses/common v0.23.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.46.0 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.50.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/zitadel/oidc/v3 v3.18.0 // indirect + github.com/zitadel/schema v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.16.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.28.3 // indirect diff --git a/go.sum b/go.sum index 25962fc..78f6c0e 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= 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-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= @@ -48,8 +50,9 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu 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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -84,6 +87,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w 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/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= +github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= 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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= @@ -94,46 +99,51 @@ github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= -github.com/perses/common v0.22.0 h1:G5nCKjBbgu0Y/sosRWe5VfgFy9XoECVv2KcfAnKEqsM= -github.com/perses/common v0.22.0/go.mod h1:FjtkQ3IYxithLrgTNbuG3rdusl8XXoE60a2DaX4FEHk= -github.com/perses/perses v0.43.1-0.20240219142255-6799f0a7cffc h1:ruT3UEorqS9Pb3EfzBVqdTdeMt0m2xXlf4D1u1HPsrQ= -github.com/perses/perses v0.43.1-0.20240219142255-6799f0a7cffc/go.mod h1:EC6c3vxDuRGdD/5rkEk22Q3dTvJDb7NfUJmwZsDdqIU= +github.com/perses/common v0.23.1 h1:agRsLOOFMoecwATZ0sb414ulOYC70z5vUvsi2RCdWQM= +github.com/perses/common v0.23.1/go.mod h1:CZ4xpwLMZ61vBM32dfSflIBpqxbnHOlNknIziFAjU0c= +github.com/perses/perses v0.44.0 h1:5OZRiT4+mfFyQ3fK2p89WIJzSseB5oQLr4ZWXG3kO4g= +github.com/perses/perses v0.44.0/go.mod h1:vSJpzFS1gjolahl+Of7buj38xohOC4euuLTjUWPnlOY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= -github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ= +github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ= 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zitadel/oidc/v3 v3.18.0 h1:NGdxLIYbuvaIqc/Na1fu61wBXIbqufp7LsFNV1bXOQw= +github.com/zitadel/oidc/v3 v3.18.0/go.mod h1:tY75hMcm07McpPXzvgvFTNPefPYDnHRYZQZVn9gtAps= +github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0= +github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= @@ -148,6 +158,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -155,8 +168,9 @@ 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -165,15 +179,18 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= 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-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -185,16 +202,24 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -206,6 +231,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -218,8 +244,8 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=