From 6edc141a2772d34b5552cb7e48ee06e58605c484 Mon Sep 17 00:00:00 2001 From: Rob Pickerill Date: Wed, 23 Aug 2023 08:54:53 +0100 Subject: [PATCH 1/2] add slack alert implementation --- probes/alerting/notifier/notifier.go | 18 ++ probes/alerting/notifier/slack/slack.go | 110 ++++++++++ probes/alerting/notifier/slack/slack_test.go | 127 +++++++++++ probes/alerting/proto/config.pb.go | 213 ++++++++++++++----- probes/alerting/proto/config.proto | 22 +- probes/alerting/proto/config_proto_gen.cue | 22 +- 6 files changed, 451 insertions(+), 61 deletions(-) create mode 100644 probes/alerting/notifier/slack/slack.go create mode 100644 probes/alerting/notifier/slack/slack_test.go diff --git a/probes/alerting/notifier/notifier.go b/probes/alerting/notifier/notifier.go index 4e1facd5c68..07d6e8b5439 100644 --- a/probes/alerting/notifier/notifier.go +++ b/probes/alerting/notifier/notifier.go @@ -25,6 +25,7 @@ import ( "github.com/cloudprober/cloudprober/common/strtemplate" "github.com/cloudprober/cloudprober/logger" "github.com/cloudprober/cloudprober/probes/alerting/notifier/pagerduty" + "github.com/cloudprober/cloudprober/probes/alerting/notifier/slack" configpb "github.com/cloudprober/cloudprober/probes/alerting/proto" "github.com/cloudprober/cloudprober/targets/endpoint" ) @@ -35,6 +36,7 @@ type Notifier struct { cmdNotifier *commandNotifier emailNotifier *emailNotifier pagerdutyNotifier *pagerduty.Client + slackNotifier *slack.Client } // AlertInfo contains information about an alert. @@ -122,6 +124,14 @@ func (n *Notifier) Notify(ctx context.Context, alertInfo *AlertInfo) error { } } + if n.slackNotifier != nil { + slackErr := n.slackNotifier.Notify(ctx, fields) + if slackErr != nil { + n.l.Errorf("Error sending Slack message: %v", slackErr) + err = errors.Join(err, slackErr) + } + } + return err } @@ -163,5 +173,13 @@ func New(alertcfg *configpb.AlertConf, l *logger.Logger) (*Notifier, error) { n.pagerdutyNotifier = pd } + if n.alertcfg.GetNotify().GetSlack() != nil { + slack, err := slack.New(n.alertcfg.Notify.GetSlack(), l) + if err != nil { + return nil, fmt.Errorf("error configuring Slack notifier: %v", err) + } + n.slackNotifier = slack + } + return n, nil } diff --git a/probes/alerting/notifier/slack/slack.go b/probes/alerting/notifier/slack/slack.go new file mode 100644 index 00000000000..84f0241ea1f --- /dev/null +++ b/probes/alerting/notifier/slack/slack.go @@ -0,0 +1,110 @@ +// Package slack implements slack notifications for Cloudprober alert events. +package slack + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "os" + + "github.com/cloudprober/cloudprober/logger" + configpb "github.com/cloudprober/cloudprober/probes/alerting/proto" +) + +const ( + // DEFAULT_SLACK_WEBHOOK_URL_ENV_VAR is the default environment variable + // to use for the Slack webhook URL. + DEFAULT_SLACK_WEBHOOK_URL_ENV_VAR = "SLACK_WEBHOOK_URL" +) + +// Client is a Slack client. +type Client struct { + httpClient *http.Client + logger *logger.Logger + webhookURL string +} + +// New creates a new Slack client. +func New(slackcfg *configpb.Slack, l *logger.Logger) (*Client, error) { + webhookURL, err := lookupWebhookUrl(slackcfg) + if err != nil { + return nil, err + } + + return &Client{ + httpClient: &http.Client{}, + logger: l, + webhookURL: webhookURL, + }, nil +} + +// lookupWebhookUrl looks up the webhook URL to use for the Slack client, +// in order of precendence: +// 1. Webhook URL in the config +// 2. Webhook URL environment variable +func lookupWebhookUrl(slackcfg *configpb.Slack) (string, error) { + // check if the webhook URL is set in the config + if slackcfg.GetWebhookUrl() != "" { + return slackcfg.GetWebhookUrl(), nil + } + + // check if the environment variable is set for the webhook URL + if webhookURL, exists := os.LookupEnv(webhookUrlEnvVar(slackcfg)); exists { + return webhookURL, nil + } + + return "", fmt.Errorf("no Slack webhook URL found") +} + +// webhookUrlEnvVar returns the environment variable to use for the Slack +func webhookUrlEnvVar(slackcfg *configpb.Slack) string { + if slackcfg.GetWebhookUrlEnvVar() != "" { + return slackcfg.GetWebhookUrlEnvVar() + } + + return DEFAULT_SLACK_WEBHOOK_URL_ENV_VAR +} + +// webhookMessage is the message that is sent to the Slack webhook. +type webhookMessage struct { + Text string `json:"text"` +} + +// Notify sends a notification to Slack. +func (c *Client) Notify(ctx context.Context, alertFields map[string]string) error { + message := createMessage(alertFields) + + jsonBody, err := json.Marshal(message) + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodPost, c.webhookURL, bytes.NewBuffer(jsonBody)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + // check status code, return error if not 200 + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("Slack webhook returned status code %d", resp.StatusCode) + } + + return nil +} + +// createMessage creates a new Slack webhook message, from the alertFields +func createMessage(alertFields map[string]string) webhookMessage { + return webhookMessage{ + Text: alertFields["details"], + } +} diff --git a/probes/alerting/notifier/slack/slack_test.go b/probes/alerting/notifier/slack/slack_test.go new file mode 100644 index 00000000000..4585994a241 --- /dev/null +++ b/probes/alerting/notifier/slack/slack_test.go @@ -0,0 +1,127 @@ +package slack + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + configpb "github.com/cloudprober/cloudprober/probes/alerting/proto" +) + +func TestSlackNew(t *testing.T) { + tests := map[string]struct { + cfg *configpb.Slack + envVars map[string]string + wantWebhook string + }{ + "valid config": { + cfg: &configpb.Slack{ + WebhookUrl: "test-webhook-url", + }, + envVars: map[string]string{}, + wantWebhook: "test-webhook-url", + }, + "valid config with env var": { + cfg: &configpb.Slack{ + WebhookUrl: "test-webhook-url", + }, + envVars: map[string]string{ + "SLACK_WEBHOOK_URL": "test-webhook-url-env-var", + }, + wantWebhook: "test-webhook-url", + }, + "env var": { + cfg: &configpb.Slack{}, + envVars: map[string]string{ + "SLACK_WEBHOOK_URL": "test-webhook-url-env-var", + }, + wantWebhook: "test-webhook-url-env-var", + }, + "env var override": { + cfg: &configpb.Slack{ + WebhookUrlEnvVar: "SLACK_WEBHOOK_URL_OVERRIDE", + }, + envVars: map[string]string{ + "SLACK_WEBHOOK_URL_OVERRIDE": "test-webhook-url-env-var", + "SLACK_WEBHOOK_URL": "test-webhook-url-env-var-2", + }, + wantWebhook: "test-webhook-url-env-var", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + + c, err := New(tc.cfg, nil) + if err != nil { + t.Errorf("New() error = %v", err) + } + + if c.webhookURL != tc.wantWebhook { + t.Errorf("New() = %v, want %v", c.webhookURL, tc.wantWebhook) + } + }) + } +} + +func TestSlackCreateWebhookMessage(t *testing.T) { + tests := map[string]struct { + alertFields map[string]string + want webhookMessage + }{ + "valid": { + alertFields: map[string]string{ + "details": "test-details", + }, + want: webhookMessage{ + Text: "test-details", + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := createMessage(tc.alertFields) + if got != tc.want { + t.Errorf("createMessage() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestSlackNotify(t *testing.T) { + httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer httpServer.Close() + + tests := map[string]struct { + alertFields map[string]string + wantErr bool + }{ + "valid": { + alertFields: map[string]string{ + "summary": "test-summary", + }, + wantErr: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + c := &Client{ + httpClient: httpServer.Client(), + webhookURL: httpServer.URL, + } + + err := c.Notify(context.Background(), tc.alertFields) + if (err != nil) != tc.wantErr { + t.Errorf("Notify() error = %v, wantErr %v", err, tc.wantErr) + } + }) + } +} diff --git a/probes/alerting/proto/config.pb.go b/probes/alerting/proto/config.pb.go index d6d73603968..ac36343e00a 100644 --- a/probes/alerting/proto/config.pb.go +++ b/probes/alerting/proto/config.pb.go @@ -50,6 +50,8 @@ type NotifyConfig struct { EmailFrom string `protobuf:"bytes,12,opt,name=email_from,json=emailFrom,proto3" json:"email_from,omitempty"` // Default: SMTP_USERNAME // PagerDuty configuration. PagerDuty *PagerDuty `protobuf:"bytes,30,opt,name=pager_duty,json=pagerDuty,proto3" json:"pager_duty,omitempty"` + // Slack configuration. + Slack *Slack `protobuf:"bytes,31,opt,name=slack,proto3,oneof" json:"slack,omitempty"` } func (x *NotifyConfig) Reset() { @@ -112,6 +114,13 @@ func (x *NotifyConfig) GetPagerDuty() *PagerDuty { return nil } +func (x *NotifyConfig) GetSlack() *Slack { + if x != nil { + return x.Slack + } + return nil +} + type Condition struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -303,10 +312,11 @@ type PagerDuty struct { // and is generated with the service. The routing key is found under the // service, when the events v2 integration is enabled, under integrations, // in the pagerduty console. - // Note: set either routing_key or routing_key_env_var. + // Note: set either routing_key or routing_key_env_var. routing_key + // takes precedence over routing_key_env_var. RoutingKey string `protobuf:"bytes,1,opt,name=routing_key,json=routingKey,proto3" json:"routing_key,omitempty"` // The environment variable that is used to contain the pagerduty routing - // key. If this is set, the routing_key field is ignored. + // key. RoutingKeyEnvVar string `protobuf:"bytes,2,opt,name=routing_key_env_var,json=routingKeyEnvVar,proto3" json:"routing_key_env_var,omitempty"` // Default: PAGERDUTY_ROUTING_KEY; // PagerDuty API URL. // Used to overwrite the default PagerDuty API URL. @@ -366,6 +376,69 @@ func (x *PagerDuty) GetApiUrl() string { return "" } +type Slack struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Webhook URL + // The Slack notifications use a webhook URL to send the notifications to + // a Slack channel. The webhook URL can be found in the Slack console under + // the "Incoming Webhooks" section. + // https://api.slack.com/messaging/webhooks + // Note: set either webhook_url or webhook_url_env_var. webhook_url + // takes precedence over webhook_url_env_var. + WebhookUrl string `protobuf:"bytes,1,opt,name=webhook_url,json=webhookUrl,proto3" json:"webhook_url,omitempty"` + // The environment variable that is used to contain the slack webhook URL. + WebhookUrlEnvVar string `protobuf:"bytes,2,opt,name=webhook_url_env_var,json=webhookUrlEnvVar,proto3" json:"webhook_url_env_var,omitempty"` // Default: SLACK_WEBHOOK_URL; +} + +func (x *Slack) Reset() { + *x = Slack{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Slack) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Slack) ProtoMessage() {} + +func (x *Slack) ProtoReflect() protoreflect.Message { + mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Slack.ProtoReflect.Descriptor instead. +func (*Slack) Descriptor() ([]byte, []int) { + return file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDescGZIP(), []int{4} +} + +func (x *Slack) GetWebhookUrl() string { + if x != nil { + return x.WebhookUrl + } + return "" +} + +func (x *Slack) GetWebhookUrlEnvVar() string { + if x != nil { + return x.WebhookUrlEnvVar + } + return "" +} + var File_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto protoreflect.FileDescriptor var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDesc = []byte{ @@ -375,7 +448,7 @@ var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_r 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x65, 0x72, - 0x74, 0x69, 0x6e, 0x67, 0x22, 0xa4, 0x01, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x43, + 0x74, 0x69, 0x6e, 0x67, 0x22, 0xed, 0x01, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, @@ -385,51 +458,62 @@ var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_r 0x74, 0x79, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x72, 0x44, 0x75, 0x74, 0x79, - 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x72, 0x44, 0x75, 0x74, 0x79, 0x22, 0x3d, 0x0a, 0x09, 0x43, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xc8, 0x03, 0x0a, 0x09, 0x41, - 0x6c, 0x65, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x49, 0x0a, 0x09, - 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x26, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2e, 0x70, 0x72, - 0x6f, 0x62, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x43, 0x6f, - 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x41, 0x0a, 0x06, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, - 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x65, - 0x72, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x06, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x64, 0x61, - 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x5f, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x61, 0x73, 0x68, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x72, 0x6c, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6c, 0x61, 0x79, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, - 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x13, 0x70, 0x6c, 0x61, 0x79, 0x62, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, - 0x29, 0x0a, 0x10, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x13, 0x72, 0x65, - 0x70, 0x65, 0x61, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, - 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x11, 0x72, 0x65, 0x70, 0x65, 0x61, - 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x88, 0x01, 0x01, 0x42, - 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x16, 0x0a, - 0x14, 0x5f, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x22, 0x74, 0x0a, 0x09, 0x50, 0x61, 0x67, 0x65, 0x72, 0x44, 0x75, - 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, - 0x4b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6b, - 0x65, 0x79, 0x5f, 0x65, 0x6e, 0x76, 0x5f, 0x76, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x10, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x45, 0x6e, 0x76, 0x56, - 0x61, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x70, 0x69, 0x55, 0x72, 0x6c, 0x42, 0x3a, 0x5a, 0x38, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, - 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x62, 0x65, - 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x73, 0x2f, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x69, 0x6e, - 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x72, 0x44, 0x75, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x05, 0x73, + 0x6c, 0x61, 0x63, 0x6b, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x73, 0x2e, + 0x61, 0x6c, 0x65, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x48, 0x00, + 0x52, 0x05, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, + 0x6c, 0x61, 0x63, 0x6b, 0x22, 0x3d, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x22, 0xc8, 0x03, 0x0a, 0x09, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x49, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x70, 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x73, 0x2e, 0x61, 0x6c, + 0x65, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x00, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, + 0x12, 0x41, 0x0a, 0x06, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x29, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2e, 0x70, + 0x72, 0x6f, 0x62, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x6e, 0x6f, 0x74, + 0x69, 0x66, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x5f, 0x75, 0x72, 0x6c, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x72, + 0x6c, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6c, 0x61, + 0x79, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x6c, 0x61, 0x79, 0x62, 0x6f, + 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x13, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x5f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, + 0x48, 0x01, 0x52, 0x11, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x53, 0x65, 0x63, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6f, 0x6e, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x72, 0x65, 0x70, 0x65, 0x61, + 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x22, 0x74, + 0x0a, 0x09, 0x50, 0x61, 0x67, 0x65, 0x72, 0x44, 0x75, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x72, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x13, + 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x6e, 0x76, 0x5f, + 0x76, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, 0x6f, 0x75, 0x74, 0x69, + 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x61, + 0x70, 0x69, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x70, + 0x69, 0x55, 0x72, 0x6c, 0x22, 0x57, 0x0a, 0x05, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x1f, 0x0a, + 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x2d, + 0x0a, 0x13, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x5f, 0x65, 0x6e, + 0x76, 0x5f, 0x76, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x65, 0x62, + 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x42, 0x3a, 0x5a, + 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x72, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, + 0x62, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x73, 0x2f, 0x61, 0x6c, 0x65, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -444,22 +528,24 @@ func file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_ return file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDescData } -var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_goTypes = []interface{}{ (*NotifyConfig)(nil), // 0: cloudprober.probes.alerting.NotifyConfig (*Condition)(nil), // 1: cloudprober.probes.alerting.Condition (*AlertConf)(nil), // 2: cloudprober.probes.alerting.AlertConf (*PagerDuty)(nil), // 3: cloudprober.probes.alerting.PagerDuty + (*Slack)(nil), // 4: cloudprober.probes.alerting.Slack } var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_depIdxs = []int32{ 3, // 0: cloudprober.probes.alerting.NotifyConfig.pager_duty:type_name -> cloudprober.probes.alerting.PagerDuty - 1, // 1: cloudprober.probes.alerting.AlertConf.condition:type_name -> cloudprober.probes.alerting.Condition - 0, // 2: cloudprober.probes.alerting.AlertConf.notify:type_name -> cloudprober.probes.alerting.NotifyConfig - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 4, // 1: cloudprober.probes.alerting.NotifyConfig.slack:type_name -> cloudprober.probes.alerting.Slack + 1, // 2: cloudprober.probes.alerting.AlertConf.condition:type_name -> cloudprober.probes.alerting.Condition + 0, // 3: cloudprober.probes.alerting.AlertConf.notify:type_name -> cloudprober.probes.alerting.NotifyConfig + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_init() } @@ -516,7 +602,20 @@ func file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_ return nil } } + file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Slack); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } + file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[0].OneofWrappers = []interface{}{} file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[2].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ @@ -524,7 +623,7 @@ func file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/probes/alerting/proto/config.proto b/probes/alerting/proto/config.proto index ffc97076977..d7b9825ca22 100644 --- a/probes/alerting/proto/config.proto +++ b/probes/alerting/proto/config.proto @@ -31,6 +31,9 @@ message NotifyConfig { // PagerDuty configuration. PagerDuty pager_duty = 30; + + // Slack configuration. + optional Slack slack = 31; } message Condition { @@ -84,15 +87,30 @@ message PagerDuty { // and is generated with the service. The routing key is found under the // service, when the events v2 integration is enabled, under integrations, // in the pagerduty console. - // Note: set either routing_key or routing_key_env_var. + // Note: set either routing_key or routing_key_env_var. routing_key + // takes precedence over routing_key_env_var. string routing_key = 1; // The environment variable that is used to contain the pagerduty routing - // key. If this is set, the routing_key field is ignored. + // key. string routing_key_env_var = 2; // Default: PAGERDUTY_ROUTING_KEY; // PagerDuty API URL. // Used to overwrite the default PagerDuty API URL. string api_url = 3; // Default: https://event.pagerduty.com +} + +message Slack { + // Webhook URL + // The Slack notifications use a webhook URL to send the notifications to + // a Slack channel. The webhook URL can be found in the Slack console under + // the "Incoming Webhooks" section. + // https://api.slack.com/messaging/webhooks + // Note: set either webhook_url or webhook_url_env_var. webhook_url + // takes precedence over webhook_url_env_var. + string webhook_url = 1; + + // The environment variable that is used to contain the slack webhook URL. + string webhook_url_env_var = 2; // Default: SLACK_WEBHOOK_URL; } \ No newline at end of file diff --git a/probes/alerting/proto/config_proto_gen.cue b/probes/alerting/proto/config_proto_gen.cue index 8f4d6a175b4..6f07e0f04b8 100644 --- a/probes/alerting/proto/config_proto_gen.cue +++ b/probes/alerting/proto/config_proto_gen.cue @@ -27,6 +27,9 @@ package proto // PagerDuty configuration. pagerDuty?: #PagerDuty @protobuf(30,PagerDuty,name=pager_duty) + + // Slack configuration. + slack?: #Slack @protobuf(31,Slack) } #Condition: { @@ -79,14 +82,29 @@ package proto // and is generated with the service. The routing key is found under the // service, when the events v2 integration is enabled, under integrations, // in the pagerduty console. - // Note: set either routing_key or routing_key_env_var. + // Note: set either routing_key or routing_key_env_var. routing_key + // takes precedence over routing_key_env_var. routingKey?: string @protobuf(1,string,name=routing_key) // The environment variable that is used to contain the pagerduty routing - // key. If this is set, the routing_key field is ignored. + // key. routingKeyEnvVar?: string @protobuf(2,string,name=routing_key_env_var) // Default: PAGERDUTY_ROUTING_KEY; // PagerDuty API URL. // Used to overwrite the default PagerDuty API URL. apiUrl?: string @protobuf(3,string,name=api_url) // Default: https://event.pagerduty.com } + +#Slack: { + // Webhook URL + // The Slack notifications use a webhook URL to send the notifications to + // a Slack channel. The webhook URL can be found in the Slack console under + // the "Incoming Webhooks" section. + // https://api.slack.com/messaging/webhooks + // Note: set either webhook_url or webhook_url_env_var. webhook_url + // takes precedence over webhook_url_env_var. + webhookUrl?: string @protobuf(1,string,name=webhook_url) + + // The environment variable that is used to contain the slack webhook URL. + webhookUrlEnvVar?: string @protobuf(2,string,name=webhook_url_env_var) // Default: SLACK_WEBHOOK_URL; +} From 6b334fdcc8a0366b2b0240d4a29ca797ac8ffd8e Mon Sep 17 00:00:00 2001 From: Rob Pickerill Date: Fri, 25 Aug 2023 07:46:29 +0100 Subject: [PATCH 2/2] add test for notify error --- probes/alerting/notifier/slack/slack_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/probes/alerting/notifier/slack/slack_test.go b/probes/alerting/notifier/slack/slack_test.go index 4585994a241..e3ea588e71b 100644 --- a/probes/alerting/notifier/slack/slack_test.go +++ b/probes/alerting/notifier/slack/slack_test.go @@ -125,3 +125,23 @@ func TestSlackNotify(t *testing.T) { }) } } + +func TestSlackNotifyError(t *testing.T) { + httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer httpServer.Close() + + c := &Client{ + httpClient: httpServer.Client(), + webhookURL: httpServer.URL, + } + + err := c.Notify(context.Background(), map[string]string{ + "details": "test-details", + }) + + if err == nil { + t.Errorf("Notify() error = %v, wantErr %v", err, true) + } +}