From d7942787f547b3e98178bf3d06e64582716df315 Mon Sep 17 00:00:00 2001 From: Saswata Mukherjee Date: Tue, 7 May 2024 09:23:42 +0100 Subject: [PATCH] Add some query options Signed-off-by: Saswata Mukherjee --- PROJECT | 8 + api/v1alpha1/thanosquery_types.go | 199 +++++++++++++++ api/v1alpha1/types.go | 38 +++ api/v1alpha1/zz_generated.deepcopy.go | 168 +++++++++++++ cmd/main.go | 7 + .../monitoring.thanos.io_thanosqueries.yaml | 230 ++++++++++++++++++ config/crd/kustomization.yaml | 3 + config/rbac/role.yaml | 26 ++ config/rbac/thanosquery_editor_role.yaml | 31 +++ config/rbac/thanosquery_viewer_role.yaml | 27 ++ config/samples/_v1alpha1_thanosquery.yaml | 12 + config/samples/kustomization.yaml | 1 + go.mod | 4 +- internal/controller/thanosquery_controller.go | 62 +++++ .../controller/thanosquery_controller_test.go | 84 +++++++ pkg/cmdopt/cmdopt_test.go | 2 +- pkg/query/query.go | 1 + 17 files changed, 900 insertions(+), 3 deletions(-) create mode 100644 api/v1alpha1/thanosquery_types.go create mode 100644 api/v1alpha1/types.go create mode 100644 config/crd/bases/monitoring.thanos.io_thanosqueries.yaml create mode 100644 config/rbac/thanosquery_editor_role.yaml create mode 100644 config/rbac/thanosquery_viewer_role.yaml create mode 100644 config/samples/_v1alpha1_thanosquery.yaml create mode 100644 internal/controller/thanosquery_controller.go create mode 100644 internal/controller/thanosquery_controller_test.go create mode 100644 pkg/query/query.go diff --git a/PROJECT b/PROJECT index 2c818a4a..f649af6a 100644 --- a/PROJECT +++ b/PROJECT @@ -16,4 +16,12 @@ resources: kind: ThanosService path: github.com/thanos-community/thanos-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: monitoring.thanos.io + kind: ThanosQuery + path: github.com/thanos-community/thanos-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/thanosquery_types.go b/api/v1alpha1/thanosquery_types.go new file mode 100644 index 00000000..f571735f --- /dev/null +++ b/api/v1alpha1/thanosquery_types.go @@ -0,0 +1,199 @@ +/* +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 ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:openapi-gen=true +// +kubebuilder:resource:categories="thanos-operator",shortName="query" +// +kubebuilder:subresource:status + +// ThanosQuerySpec defines the desired state of Thanos Query. +// +k8s:openapi-gen=true +type ThanosQuerySpec struct { + CommonThanosFields `json:",inline"` + + // The external Thanos Query URL that would be set in all alerts 'Source' field. + // +optional + AlertQueryURL *string `json:"alertQueryURL,omitempty" opt:"alert.query-url"` + + // Addresses of statically configured Thanos API servers. + // The scheme may be prefixed with 'dns+' or 'dnssrv+' to detect Thanos API + // servers through respective DNS lookups. + // Only to be used for out-of-cluster connections. + // +optional + Endpoint []string `json:"endpoint" opt:"endpoint"` + // Experimental: DNS name of statically configured Thanos API server groups. + // Targets resolved from the DNS name will be queried in a round-robin, instead of a fanout manner. + // This flag should be used when connecting a Thanos Query to HA groups of Thanos components. + // Only to be used for out-of-cluster connections. + // +optional + EndpointGroup []string `json:"endpointGroup" opt:"endpoint-group"` + // Addresses of only statically configured Thanos API servers that are always used, + // even if the health check fails. Useful if you have a caching layer on top. + // Only to be used for out-of-cluster connections. + // +optional + EndpointStrict []string `json:"endpointStrict" opt:"endpoint-strict"` + // Experimental: Addresses of only statically configured Thanos API servers groups that are always used, + // even if the health check fails. Useful if you have a caching layer on top. + // Only to be used for out-of-cluster connections. + // +optional + EndpointGroupStrict []string `json:"endpointGroupStrict" opt:"endpoint-group-strict"` + + // Default behavior of the Operator is to add new Thanos API servers to an SD file. + // Setting this to true, ensures that the operator will instead discover Thanos API + // servers and configure them on querier as --endpoint flags and redeploy. + // +kubebuilder:default:=false + UseFlagForAutoSD bool `json:"useFileForAutoSD"` + + // TODO(saswatamcode): Figure out a neat way to handle this. + // GRPCClientsServerName string `json:"grpcClientServer-name"` + // GRPCClientsTLSCA string `json:"grpcClientTLSCA"` + // GRPCClientsTLSCert string `json:"grpcClientTLSCert"` + // GRPCClientsTLSKey string `json:"grpcClientTLSKey"` + // GRPCClientsTLSSecure bool `json:"grpcClientTLSSecure"` + // GRPCClientsTLSSkipVerify bool `json:"grpcClientTLSSkipVerify"` + + // GRPCClientsCompression string `json:"grpcCompression"` + + // GRPCAddress string `json:"grpcAddress"` + // GRPCGracePeriod time.Duration `json:"grpcGracePeriod"` + // GRPCMMaxConnectionAge time.Duration `json:"grpcServerMaxConnectionAge"` + // GRPCServerTLSCert string `json:"grpcServerTLSCert"` + // GRPCServerTLSClientCA string `json:"grpcServerTLSClientCA"` + // GRPCServerTLSKey string `json:"grpcServerTLSKey"` + + // HTTPAddress string `json:"httpAddress"` + // HTTPGracePeriod time.Duration `json:"httpGracePeriod"` + // HTTPConfig string `json:"httpConfig"` + + // Directory to log currently active queries in the queries.active file. + // +optional + ActiveQueryPath *string `json:"activeQueryPath,omitempty" opt:"query.active-query-path"` + // Enable automatic adjustment (step / 5) to what source of data should be used in store gateways if no max_source_resolution param is specified. + // +kubebuilder:default:=true + AutoDownsampling bool `json:"autoDownsampling,omitempty" opt:"query.auto-downsampling"` + // Default evaluation interval for sub queries. + // +kubebuilder:default:="1m" + // +optional + DefaultEvaluationInterval *time.Duration `json:"defaultEvaluationInterval,omitempty" opt:"query.default-evaluation-interval"` + // Set default step for range queries. Default step is only used when step is not set in UI. + // In such cases, Thanos UI will use default step to calculate resolution (resolution = max(rangeSeconds / 250, defaultStep)). + // This will not work from Grafana, but Grafana has __step variable which can be used + // +kubebuilder:default:="1s" + // +optional + DefaultStep *time.Duration `json:"defaultStep,omitempty" opt:"query.default-step"` + // The maximum lookback duration for retrieving metrics during expression evaluations. + // PromQL always evaluates the query for the certain timestamp (query range timestamps are deduced by step). + // Since scrape intervals might be different, PromQL looks back for given amount of time to get latest sample. + // If it exceeds the maximum lookback delta it assumes series is stale and returns none (a gap). + // This is why lookback delta should be set to at least 2 times of the slowest scrape interval. If unset it will use the promql default of 5m. + // +optional + LookbackDelta *time.Duration `json:"lookbackDelta,omitempty" opt:"query.lookback-delta"` + // Allow for larger lookback duration for queries based on resolution. + DynamicLookbackDelta bool `json:"dynamicLookbackDelta,omitempty" opt:"query.dynamic-lookback-delta"` + // Maximum number of queries processed concurrently by query node. + // +kubebuilder:default:="20" + MaxConcurrent int `json:"maxConcurrent,omitempty" opt:"query.max-concurrent"` + // Maximum number of select requests made concurrently per a query. + // +kubebuilder:default:="4" + MaxConcurrentSelect int `json:"maxConcurrentSelect,omitempty" opt:"query.max-concurrent-select"` + // The default metadata time range duration for retrieving labels through Labels and Series API + // when the range parameters are not specified. The zero value means range covers the time since the beginning. + // +kubebuilder:default:="0s" + MetadataDefaultTimeRange time.Duration `json:"metadataDefaultTimeRange,omitempty" opt:"query.metadata.default-time-range"` + // Enable partial response for queries if no partial_response param is specified. --no-query.partial-response for disabling. + // +kubebuilder:default:=true + PartialResponse bool `json:"partialResponse,omitempty" opt:"query.partial-response"` + // Default PromQL engine to use. + // +kubebuilder:validation:Enum=thanos;prometheus + // +kubebuilder:default:="thanos" + PromQLEngine string `json:"promQLEngine,omitempty" opt:"query.promql-engine"` + // Labels to treat as a replica indicator along which data is deduplicated. + // Still you will be able to query without deduplication using 'dedup=false' parameter. + // Data includes time series, recording rules, and alerting rules. + ReplicaLabels []string `json:"replicaLabels,omitempty" opt:"query.replica-label"` + + // QueryTelemetryRequestDurationSecondsQuantiles []float64 `opt:"query.telemetry.request-duration-seconds-quantiles"` + // QueryTelemetryRequestSamplesQuantiles []float64 `opt:"query.telemetry.request-samples-quantiles"` + // QueryTelemetryRequestSeriesSecondsQuantiles []float64 `opt:"query.telemetry.request-series-seconds-quantiles"` + // QueryTenantCertificateField string `opt:"query.tenant-certificate-field"` + // QueryTenantHeader string `opt:"query.tenant-header"` + // QueryTimeout time.Duration `opt:"query.timeout"` + + // TODO(saswatamcode): Figure out a neat way to handle this. + // RequestLoggingConfig *reqlogging.RequestConfig `json:"request.logging-config"` + // RequestLoggingConfigFile containeropts.ContainerUpdater `json:"request.logging-config-file"` + + // SelectorLabel []string `json:"selector-label"` + // StoreLimitsRequestSamples int `json:"store.limits.request-samples"` + // StoreLimitsRequestSeries int `json:"store.limits.request-series"` + // StoreResponseTimeout time.Duration `json:"store.response-timeout"` + // StoreSDDNSResolver string `json:"store.sd-dns-resolver"` + // StoreSDDNSInterval time.Duration `json:"store.sd-dns-interval"` + // StoreSDFiles []string `json:"store.sd-files"` + // StoreSDInterval time.Duration `json:"store.sd-interval"` + // StoreUnhealthyTimeout time.Duration `json:"store.unhealthy-timeout"` + + // TODO(saswatamcode): Figure out a neat way to handle this. + // TracingConfig *trclient.TracingConfig `json:"tracing.config"` + // TracingConfigFile containeropts.ContainerUpdater `json:"tracing.config-file"` + + // SelectorRelabelConfig string `json:"selector.relabel-config"` + + // TODO(saswatamcode): Figure out a neat way to handle this. + // WebDisableCORS bool `json:"web.disable-cors"` + // WebExternalPrefix string `json:"web.external-prefix"` + // WebPrefixHeader string `json:"web.prefix-header"` + // WebRoutePrefix string `json:"web.route-prefix"` +} + +// ThanosQueryStatus defines the observed state of ThanosQuery +type ThanosQueryStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// ThanosQuery is the Schema for the thanosqueries API +type ThanosQuery struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ThanosQuerySpec `json:"spec,omitempty"` + Status ThanosQueryStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// ThanosQueryList contains a list of ThanosQuery +type ThanosQueryList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ThanosQuery `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ThanosQuery{}, &ThanosQueryList{}) +} diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go new file mode 100644 index 00000000..61d0508d --- /dev/null +++ b/api/v1alpha1/types.go @@ -0,0 +1,38 @@ +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" +) + +// CommonThanosFields are the options available to all Thanos components. +// +k8s:deepcopy-gen=true +type CommonThanosFields struct { + // Version of Thanos to be deployed. + // If not specified, the operator assumes the latest upstream version of + // Thanos available at the time when the version of the operator was + // released. + Version string `json:"version,omitempty"` + // Container image to use for the Thanos components. + // +optional + Image *string `json:"image,omitempty"` + + // Image pull policy for the Thanos containers. + // See https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy for more details. + // +kubebuilder:validation:Enum="";Always;Never;IfNotPresent + ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + // An optional list of references to Secrets in the same namespace + // to use for pulling images from registries. + // See http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` + + // When a Thanos deployment is paused, no actions except for deletion + // will be performed on the underlying objects. + Paused bool `json:"paused,omitempty"` + + // Log level for Thanos. + // +kubebuilder:validation:Enum="";debug;info;warn;error + LogLevel string `json:"logLevel,omitempty" opt:"log.level"` + // Log format for Thanos. + // +kubebuilder:validation:Enum="";logfmt;json + LogFormat string `json:"logFormat,omitempty" opt:"log.format"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 289840d7..6d262be9 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -21,9 +21,177 @@ limitations under the License. package v1alpha1 import ( + timex "time" + + v1 "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CommonThanosFields) DeepCopyInto(out *CommonThanosFields) { + *out = *in + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(string) + **out = **in + } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]v1.LocalObjectReference, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonThanosFields. +func (in *CommonThanosFields) DeepCopy() *CommonThanosFields { + if in == nil { + return nil + } + out := new(CommonThanosFields) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ThanosQuery) DeepCopyInto(out *ThanosQuery) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThanosQuery. +func (in *ThanosQuery) DeepCopy() *ThanosQuery { + if in == nil { + return nil + } + out := new(ThanosQuery) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ThanosQuery) 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 *ThanosQueryList) DeepCopyInto(out *ThanosQueryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ThanosQuery, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThanosQueryList. +func (in *ThanosQueryList) DeepCopy() *ThanosQueryList { + if in == nil { + return nil + } + out := new(ThanosQueryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ThanosQueryList) 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 *ThanosQuerySpec) DeepCopyInto(out *ThanosQuerySpec) { + *out = *in + in.CommonThanosFields.DeepCopyInto(&out.CommonThanosFields) + if in.AlertQueryURL != nil { + in, out := &in.AlertQueryURL, &out.AlertQueryURL + *out = new(string) + **out = **in + } + if in.Endpoint != nil { + in, out := &in.Endpoint, &out.Endpoint + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.EndpointGroup != nil { + in, out := &in.EndpointGroup, &out.EndpointGroup + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.EndpointStrict != nil { + in, out := &in.EndpointStrict, &out.EndpointStrict + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.EndpointGroupStrict != nil { + in, out := &in.EndpointGroupStrict, &out.EndpointGroupStrict + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ActiveQueryPath != nil { + in, out := &in.ActiveQueryPath, &out.ActiveQueryPath + *out = new(string) + **out = **in + } + if in.DefaultEvaluationInterval != nil { + in, out := &in.DefaultEvaluationInterval, &out.DefaultEvaluationInterval + *out = new(timex.Duration) + **out = **in + } + if in.DefaultStep != nil { + in, out := &in.DefaultStep, &out.DefaultStep + *out = new(timex.Duration) + **out = **in + } + if in.LookbackDelta != nil { + in, out := &in.LookbackDelta, &out.LookbackDelta + *out = new(timex.Duration) + **out = **in + } + if in.ReplicaLabels != nil { + in, out := &in.ReplicaLabels, &out.ReplicaLabels + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThanosQuerySpec. +func (in *ThanosQuerySpec) DeepCopy() *ThanosQuerySpec { + if in == nil { + return nil + } + out := new(ThanosQuerySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ThanosQueryStatus) DeepCopyInto(out *ThanosQueryStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThanosQueryStatus. +func (in *ThanosQueryStatus) DeepCopy() *ThanosQueryStatus { + if in == nil { + return nil + } + out := new(ThanosQueryStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ThanosService) DeepCopyInto(out *ThanosService) { *out = *in diff --git a/cmd/main.go b/cmd/main.go index 63e376c0..37ee8f04 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -129,6 +129,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "ThanosService") os.Exit(1) } + if err = (&controller.ThanosQueryReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ThanosQuery") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/monitoring.thanos.io_thanosqueries.yaml b/config/crd/bases/monitoring.thanos.io_thanosqueries.yaml new file mode 100644 index 00000000..c5579fbb --- /dev/null +++ b/config/crd/bases/monitoring.thanos.io_thanosqueries.yaml @@ -0,0 +1,230 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: thanosqueries.monitoring.thanos.io +spec: + group: monitoring.thanos.io + names: + kind: ThanosQuery + listKind: ThanosQueryList + plural: thanosqueries + singular: thanosquery + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ThanosQuery is the Schema for the thanosqueries API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ThanosQuerySpec defines the desired state of Thanos Query. + properties: + activeQueryPath: + description: Directory to log currently active queries in the queries.active + file. + type: string + alertQueryURL: + description: The external Thanos Query URL that would be set in all + alerts 'Source' field. + type: string + autoDownsampling: + default: true + description: Enable automatic adjustment (step / 5) to what source + of data should be used in store gateways if no max_source_resolution + param is specified. + type: boolean + defaultEvaluationInterval: + default: 1m + description: Default evaluation interval for sub queries. + format: int64 + type: integer + defaultStep: + default: 1s + description: |- + Set default step for range queries. Default step is only used when step is not set in UI. + In such cases, Thanos UI will use default step to calculate resolution (resolution = max(rangeSeconds / 250, defaultStep)). + This will not work from Grafana, but Grafana has __step variable which can be used + format: int64 + type: integer + dynamicLookbackDelta: + description: Allow for larger lookback duration for queries based + on resolution. + type: boolean + endpoint: + description: |- + Addresses of statically configured Thanos API servers. + The scheme may be prefixed with 'dns+' or 'dnssrv+' to detect Thanos API + servers through respective DNS lookups. + Only to be used for out-of-cluster connections. + items: + type: string + type: array + endpointGroup: + description: |- + Experimental: DNS name of statically configured Thanos API server groups. + Targets resolved from the DNS name will be queried in a round-robin, instead of a fanout manner. + This flag should be used when connecting a Thanos Query to HA groups of Thanos components. + Only to be used for out-of-cluster connections. + items: + type: string + type: array + endpointGroupStrict: + description: |- + Experimental: Addresses of only statically configured Thanos API servers groups that are always used, + even if the health check fails. Useful if you have a caching layer on top. + Only to be used for out-of-cluster connections. + items: + type: string + type: array + endpointStrict: + description: |- + Addresses of only statically configured Thanos API servers that are always used, + even if the health check fails. Useful if you have a caching layer on top. + Only to be used for out-of-cluster connections. + items: + type: string + type: array + image: + description: Container image to use for the Thanos components. + type: string + imagePullPolicy: + description: |- + Image pull policy for the Thanos containers. + See https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy for more details. + enum: + - "" + - Always + - Never + - IfNotPresent + type: string + imagePullSecrets: + description: |- + An optional list of references to Secrets in the same namespace + to use for pulling images from registries. + See http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + type: array + logFormat: + description: Log format for Thanos. + enum: + - "" + - logfmt + - json + type: string + logLevel: + description: Log level for Thanos. + enum: + - "" + - debug + - info + - warn + - error + type: string + lookbackDelta: + description: |- + The maximum lookback duration for retrieving metrics during expression evaluations. + PromQL always evaluates the query for the certain timestamp (query range timestamps are deduced by step). + Since scrape intervals might be different, PromQL looks back for given amount of time to get latest sample. + If it exceeds the maximum lookback delta it assumes series is stale and returns none (a gap). + This is why lookback delta should be set to at least 2 times of the slowest scrape interval. If unset it will use the promql default of 5m. + format: int64 + type: integer + maxConcurrent: + default: "20" + description: Maximum number of queries processed concurrently by query + node. + type: integer + maxConcurrentSelect: + default: "4" + description: Maximum number of select requests made concurrently per + a query. + type: integer + metadataDefaultTimeRange: + default: 0s + description: |- + The default metadata time range duration for retrieving labels through Labels and Series API + when the range parameters are not specified. The zero value means range covers the time since the beginning. + format: int64 + type: integer + partialResponse: + default: true + description: Enable partial response for queries if no partial_response + param is specified. --no-query.partial-response for disabling. + type: boolean + paused: + description: |- + When a Thanos deployment is paused, no actions except for deletion + will be performed on the underlying objects. + type: boolean + promQLEngine: + default: thanos + description: Default PromQL engine to use. + enum: + - thanos + - prometheus + type: string + replicaLabels: + description: |- + Labels to treat as a replica indicator along which data is deduplicated. + Still you will be able to query without deduplication using 'dedup=false' parameter. + Data includes time series, recording rules, and alerting rules. + items: + type: string + type: array + useFileForAutoSD: + default: false + description: |- + Default behavior of the Operator is to add new Thanos API servers to an SD file. + Setting this to true, ensures that the operator will instead discover Thanos API + servers and configure them on querier as --endpoint flags and redeploy. + type: boolean + version: + description: |- + Version of Thanos to be deployed. + If not specified, the operator assumes the latest upstream version of + Thanos available at the time when the version of the operator was + released. + type: string + required: + - useFileForAutoSD + type: object + status: + description: ThanosQueryStatus defines the observed state of ThanosQuery + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index c98fe78b..a24bb267 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,17 +3,20 @@ # It should be run by config/default resources: - bases/monitoring.thanos.io_thanosservices.yaml +- bases/monitoring.thanos.io_thanosqueries.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- path: patches/webhook_in_thanosservices.yaml +#- path: patches/webhook_in_thanosqueries.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- path: patches/cainjection_in_thanosservices.yaml +#- path: patches/cainjection_in_thanosqueries.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # [WEBHOOK] To enable webhook, uncomment the following section diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ba6b4723..968802c2 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,6 +4,32 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - monitoring.thanos.io + resources: + - thanosqueries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - monitoring.thanos.io + resources: + - thanosqueries/finalizers + verbs: + - update +- apiGroups: + - monitoring.thanos.io + resources: + - thanosqueries/status + verbs: + - get + - patch + - update - apiGroups: - monitoring.thanos.io resources: diff --git a/config/rbac/thanosquery_editor_role.yaml b/config/rbac/thanosquery_editor_role.yaml new file mode 100644 index 00000000..00bccc58 --- /dev/null +++ b/config/rbac/thanosquery_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit thanosqueries. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: thanosquery-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: thanos-operator + app.kubernetes.io/part-of: thanos-operator + app.kubernetes.io/managed-by: kustomize + name: thanosquery-editor-role +rules: +- apiGroups: + - monitoring.thanos.io + resources: + - thanosqueries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - monitoring.thanos.io + resources: + - thanosqueries/status + verbs: + - get diff --git a/config/rbac/thanosquery_viewer_role.yaml b/config/rbac/thanosquery_viewer_role.yaml new file mode 100644 index 00000000..ecb3047f --- /dev/null +++ b/config/rbac/thanosquery_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view thanosqueries. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: thanosquery-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: thanos-operator + app.kubernetes.io/part-of: thanos-operator + app.kubernetes.io/managed-by: kustomize + name: thanosquery-viewer-role +rules: +- apiGroups: + - monitoring.thanos.io + resources: + - thanosqueries + verbs: + - get + - list + - watch +- apiGroups: + - monitoring.thanos.io + resources: + - thanosqueries/status + verbs: + - get diff --git a/config/samples/_v1alpha1_thanosquery.yaml b/config/samples/_v1alpha1_thanosquery.yaml new file mode 100644 index 00000000..bc3ed43b --- /dev/null +++ b/config/samples/_v1alpha1_thanosquery.yaml @@ -0,0 +1,12 @@ +apiVersion: monitoring.thanos.io/v1alpha1 +kind: ThanosQuery +metadata: + labels: + app.kubernetes.io/name: thanosquery + app.kubernetes.io/instance: thanosquery-sample + app.kubernetes.io/part-of: thanos-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: thanos-operator + name: thanosquery-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 055340f5..5d972939 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,4 +1,5 @@ ## Append samples of your project ## resources: - _v1alpha1_thanosservice.yaml +- _v1alpha1_thanosquery.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/go.mod b/go.mod index d867a7b2..28b522fb 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,11 @@ module github.com/thanos-community/thanos-operator -go 1.21 +go 1.22 require ( github.com/onsi/ginkgo/v2 v2.14.0 github.com/onsi/gomega v1.30.0 + k8s.io/api v0.29.0 k8s.io/apimachinery v0.29.0 k8s.io/client-go v0.29.0 sigs.k8s.io/controller-runtime v0.17.0 @@ -61,7 +62,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.29.0 // indirect k8s.io/apiextensions-apiserver v0.29.0 // indirect k8s.io/component-base v0.29.0 // indirect k8s.io/klog/v2 v2.110.1 // indirect diff --git a/internal/controller/thanosquery_controller.go b/internal/controller/thanosquery_controller.go new file mode 100644 index 00000000..e7fd5cfb --- /dev/null +++ b/internal/controller/thanosquery_controller.go @@ -0,0 +1,62 @@ +/* +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 controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + monitoringthanosiov1alpha1 "github.com/thanos-community/thanos-operator/api/v1alpha1" +) + +// ThanosQueryReconciler reconciles a ThanosQuery object +type ThanosQueryReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=monitoring.thanos.io,resources=thanosqueries,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=monitoring.thanos.io,resources=thanosqueries/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=monitoring.thanos.io,resources=thanosqueries/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the ThanosQuery object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.0/pkg/reconcile +func (r *ThanosQueryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx).WithValues("thanos-query", req.NamespacedName) + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ThanosQueryReconciler) SetupWithManager(mgr ctrl.Manager) error { + mgr.GetFieldIndexer() + + return ctrl.NewControllerManagedBy(mgr). + For(&monitoringthanosiov1alpha1.ThanosQuery{}). + Complete(r) +} diff --git a/internal/controller/thanosquery_controller_test.go b/internal/controller/thanosquery_controller_test.go new file mode 100644 index 00000000..a034b0b8 --- /dev/null +++ b/internal/controller/thanosquery_controller_test.go @@ -0,0 +1,84 @@ +/* +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 controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + monitoringthanosiov1alpha1 "github.com/thanos-community/thanos-operator/api/v1alpha1" +) + +var _ = Describe("ThanosQuery Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + thanosquery := &monitoringthanosiov1alpha1.ThanosQuery{} + + BeforeEach(func() { + By("creating the custom resource for the Kind ThanosQuery") + err := k8sClient.Get(ctx, typeNamespacedName, thanosquery) + if err != nil && errors.IsNotFound(err) { + resource := &monitoringthanosiov1alpha1.ThanosQuery{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &monitoringthanosiov1alpha1.ThanosQuery{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance ThanosQuery") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &ThanosQueryReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/pkg/cmdopt/cmdopt_test.go b/pkg/cmdopt/cmdopt_test.go index c28d8d24..28257133 100644 --- a/pkg/cmdopt/cmdopt_test.go +++ b/pkg/cmdopt/cmdopt_test.go @@ -29,7 +29,7 @@ func (d Dummy) GoString() string { } type TestOptions struct { - String string `opt:"string"` + String string `json:"string"` Int int `opt:"int"` // Zero value is ignored IntPtr *int `opt:"intptr"` // Zero value is not ignored Float float64 `opt:"float"` diff --git a/pkg/query/query.go b/pkg/query/query.go new file mode 100644 index 00000000..f6b34a8d --- /dev/null +++ b/pkg/query/query.go @@ -0,0 +1 @@ +package query