Skip to content

Commit

Permalink
feat: ClusterQueryConnector and ClusterRulerAction (#9)
Browse files Browse the repository at this point in the history
* feat(): Cluster level queryconnector and ruleraction

---------

Co-authored-by: Alby Hernández <donfumero@gmail.com>
  • Loading branch information
dfradehubs and achetronic authored Jan 14, 2025
1 parent b5b9b9c commit 57b3566
Show file tree
Hide file tree
Showing 43 changed files with 1,350 additions and 589 deletions.
17 changes: 17 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,21 @@ resources:
kind: QueryConnector
path: prosimcorp.com/SearchRuler/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: prosimcorp.com
group: searchruler
kind: ClusterQueryConnector
path: prosimcorp.com/SearchRuler/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
domain: prosimcorp.com
group: searchruler
kind: ClusterRulerAction
path: prosimcorp.com/SearchRuler/api/v1alpha1
version: v1alpha1
version: "3"
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ Well, no more! searchruler is here to save the day. This Kubernetes operator let
### 🛠️ How It Works
Setting up searchruler is a breeze. Here are the three main building blocks that’ll make your log life so much easier:

* 🔗 **QueryConnector**: This is where the magic starts. Connect to your log source—whether it’s Elasticsearch, Opensearch, or something cool we’re cooking up for the future.
* 🔗 **QueryConnector**: This is where the magic starts. Connect to your log source—whether it’s Elasticsearch, Opensearch, or something cool we’re cooking up for the future. The clustered scope solution is named **ClusterQueryConnector**.

* 🚀 **RulerAction**: When a rule is triggered, where should the alert go? Set up webhooks, Slack channels, or anything else you need. We keep it simple, starting with a generic webhook (because everyone loves webhooks).
* 🚀 **RulerAction**: When a rule is triggered, where should the alert go? Set up webhooks, Slack channels, or anything else you need. We keep it simple, starting with a generic webhook (because everyone loves webhooks). The clustered scope solution is named **ClusterRulerAction**.

* 📜 **SearchRule**: The heart of it all! Define your rules, set the conditions, and craft the message to send when something’s off. This is where you turn log data into actionable alerts.

Expand Down Expand Up @@ -112,9 +112,12 @@ spec:
secretRef:
name: elasticsearch-main-credentials
namespace: default
keyUsername: username
keyPassword: password
```

For cluster scope just change **QueryConnector** for **ClusterQueryConenctor**.
### 🚀 RulerAction

A RulerAction defines where your alerts will be sent when a SearchRule is triggered (a.k.a. "firing"). Whether it’s a Slack channel, a webhook endpoint, alertmanager or another notification service—you’re in control! 🛠️
Expand Down Expand Up @@ -156,10 +159,12 @@ spec:
# credentials:
# secretRef:
# name: alertmanager-credentials
# namespace: default
# keyUsername: username
# keyPassword: password
```

For cluster scope just change **QueryConnector** for **ClusterRulerAction**.
### 📜 SearchRule

This is where the magic happens! SearchRules define the conditions to check in your log sources (via queryconnectors) and specify where to send alerts (using ruleractions). You get to decide what matters and how to act on it. 🎯
Expand All @@ -185,6 +190,8 @@ spec:
# QueryConnector reference to execute the queries for the rule evaluation.
queryConnectorRef:
name: queryconnector-sample
# Empty namespace it searchs for a clusterqueryconnector resource
namespace: "default"
# Interval time for checking the value of the query. For example, every 30s we will
# execute the query value to elasticsearch
Expand Down Expand Up @@ -253,6 +260,8 @@ spec:
# RuleAction reference to execute when the condition is true.
actionRef:
name: ruleraction-sample
# Empty namespace it searchs for a clusterruleraction resource
namespace: "default"
# Message template to send in the RuleAction execution. It is a Go template with the
# object, value and, if exists, elasticsearch aggregations field variables. The object
# variable is the SearchRule object and the value variable is the value of the conditionField.
Expand Down Expand Up @@ -356,6 +365,7 @@ spec:
# QueryConnector reference to execute the queries for the rule evaluation.
queryConnectorRef:
name: queryconnector-sample
namespace: default
# Interval time for checking the value of the query. For example, every 30s we will
# execute the query value to elasticsearch
Expand Down Expand Up @@ -398,6 +408,7 @@ spec:
# RuleAction reference to execute when the condition is true.
actionRef:
name: ruleraction-sample
namespace: default
# Message template to send in the RuleAction execution. It is a Go template with the
# object, value and, if exists, elasticsearch aggregations field variables. The object
# variable is the SearchRule object and the value variable is the value of the conditionField.
Expand Down
50 changes: 50 additions & 0 deletions api/v1alpha1/clusterqueryconnector_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
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"
)

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"ResourceSynced\")].status",description=""
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"State\")].reason",description=""
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""

// ClusterQueryConnector is the Schema for the clusterqueryconnectors API.
type ClusterQueryConnector struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec QueryConnectorSpec `json:"spec,omitempty"`
Status QueryConnectorStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// ClusterQueryConnectorList contains a list of ClusterQueryConnector.
type ClusterQueryConnectorList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ClusterQueryConnector `json:"items"`
}

func init() {
SchemeBuilder.Register(&ClusterQueryConnector{}, &ClusterQueryConnectorList{})
}
50 changes: 50 additions & 0 deletions api/v1alpha1/clusterruleraction_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
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"
)

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"ResourceSynced\")].status",description=""
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"State\")].reason",description=""
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""

// ClusterRulerAction is the Schema for the clusterruleractions API.
type ClusterRulerAction struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec RulerActionSpec `json:"spec,omitempty"`
Status RulerActionStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// ClusterRulerActionList contains a list of ClusterRulerAction.
type ClusterRulerActionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ClusterRulerAction `json:"items"`
}

func init() {
SchemeBuilder.Register(&ClusterRulerAction{}, &ClusterRulerActionList{})
}
1 change: 1 addition & 0 deletions api/v1alpha1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1alpha1
// SecretRef TODO
type SecretRef struct {
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
KeyUsername string `json:"keyUsername"`
KeyPassword string `json:"keyPassword"`
}
8 changes: 5 additions & 3 deletions api/v1alpha1/searchrule_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ type Condition struct {

// ActionRef TODO
type ActionRef struct {
Name string `json:"name"`
Data string `json:"data,omitempty"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Data string `json:"data"`
}

// QueryConnectorRef TODO
type QueryConnectorRef struct {
Name string `json:"name"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}

// SearchRuleSpec defines the desired state of SearchRule.
Expand Down
118 changes: 118 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
"crypto/tls"
"flag"
"os"
"prosimcorp.com/SearchRuler/internal/controller/queryconnector"
"prosimcorp.com/SearchRuler/internal/controller/ruleraction"
"prosimcorp.com/SearchRuler/internal/controller/searchrule"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
Expand All @@ -37,7 +40,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook"

searchrulerv1alpha1 "prosimcorp.com/SearchRuler/api/v1alpha1"
"prosimcorp.com/SearchRuler/internal/controller"
"prosimcorp.com/SearchRuler/internal/globals"
"prosimcorp.com/SearchRuler/internal/pools"
"prosimcorp.com/SearchRuler/internal/webserver"
Expand Down Expand Up @@ -177,7 +179,7 @@ func main() {
os.Exit(1)
}

if err = (&controller.RulerActionReconciler{
if err = (&ruleraction.RulerActionReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
AlertsPool: AlertsPool,
Expand All @@ -186,7 +188,7 @@ func main() {
os.Exit(1)
}
mgr.GetEventRecorderFor("CREATE")
if err = (&controller.SearchRuleReconciler{
if err = (&searchrule.SearchRuleReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
QueryConnectorCredentialsPool: QueryConnectorCredentialsPool,
Expand All @@ -196,7 +198,7 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "SearchRule")
os.Exit(1)
}
if err = (&controller.QueryConnectorReconciler{
if err = (&queryconnector.QueryConnectorReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
CredentialsPool: QueryConnectorCredentialsPool,
Expand Down
Loading

0 comments on commit 57b3566

Please sign in to comment.