diff --git a/probes/alerting/notifier/notifier.go b/probes/alerting/notifier/notifier.go index b49d2bb474c..0032d999624 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" ) @@ -54,6 +55,7 @@ type Notifier struct { cmdNotifier *commandNotifier emailNotifier *emailNotifier pagerdutyNotifier *pagerduty.Client + slackNotifier *slack.Client } // AlertInfo contains information about an alert. @@ -141,6 +143,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 } @@ -195,5 +205,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..e3ea588e71b --- /dev/null +++ b/probes/alerting/notifier/slack/slack_test.go @@ -0,0 +1,147 @@ +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) + } + }) + } +} + +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) + } +} diff --git a/probes/alerting/proto/config.pb.go b/probes/alerting/proto/config.pb.go index 9ef639ecfa2..d1544b432d2 100644 --- a/probes/alerting/proto/config.pb.go +++ b/probes/alerting/proto/config.pb.go @@ -112,10 +112,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. @@ -175,6 +176,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[2] + 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[2] + 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{2} +} + +func (x *Slack) GetWebhookUrl() string { + if x != nil { + return x.WebhookUrl + } + return "" +} + +func (x *Slack) GetWebhookUrlEnvVar() string { + if x != nil { + return x.WebhookUrlEnvVar + } + return "" +} + type NotifyConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -200,12 +264,14 @@ type NotifyConfig struct { Email *Email `protobuf:"bytes,11,opt,name=email,proto3" json:"email,omitempty"` // PagerDuty configuration. PagerDuty *PagerDuty `protobuf:"bytes,12,opt,name=pager_duty,json=pagerDuty,proto3" json:"pager_duty,omitempty"` + // Slack configuration. + Slack *Slack `protobuf:"bytes,13,opt,name=slack,proto3" json:"slack,omitempty"` } func (x *NotifyConfig) Reset() { *x = NotifyConfig{} if protoimpl.UnsafeEnabled { - mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[2] + mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -218,7 +284,7 @@ func (x *NotifyConfig) String() string { func (*NotifyConfig) ProtoMessage() {} func (x *NotifyConfig) ProtoReflect() protoreflect.Message { - mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[2] + mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -231,7 +297,7 @@ func (x *NotifyConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use NotifyConfig.ProtoReflect.Descriptor instead. func (*NotifyConfig) Descriptor() ([]byte, []int) { - return file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDescGZIP(), []int{2} + return file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDescGZIP(), []int{3} } func (x *NotifyConfig) GetCommand() string { @@ -255,6 +321,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 @@ -267,7 +340,7 @@ type Condition struct { func (x *Condition) Reset() { *x = Condition{} if protoimpl.UnsafeEnabled { - mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[3] + mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -280,7 +353,7 @@ func (x *Condition) String() string { func (*Condition) ProtoMessage() {} func (x *Condition) ProtoReflect() protoreflect.Message { - mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[3] + 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 { @@ -293,7 +366,7 @@ func (x *Condition) ProtoReflect() protoreflect.Message { // Deprecated: Use Condition.ProtoReflect.Descriptor instead. func (*Condition) Descriptor() ([]byte, []int) { - return file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDescGZIP(), []int{3} + return file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDescGZIP(), []int{4} } func (x *Condition) GetFailures() int32 { @@ -351,7 +424,7 @@ type AlertConf struct { func (x *AlertConf) Reset() { *x = AlertConf{} if protoimpl.UnsafeEnabled { - mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[4] + mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -364,7 +437,7 @@ func (x *AlertConf) String() string { func (*AlertConf) ProtoMessage() {} func (x *AlertConf) ProtoReflect() protoreflect.Message { - mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[4] + mi := &file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -377,7 +450,7 @@ func (x *AlertConf) ProtoReflect() protoreflect.Message { // Deprecated: Use AlertConf.ProtoReflect.Descriptor instead. func (*AlertConf) Descriptor() ([]byte, []int) { - return file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDescGZIP(), []int{4} + return file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDescGZIP(), []int{5} } func (x *AlertConf) GetName() string { @@ -462,54 +535,63 @@ var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_r 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, 0xa9, 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, 0x38, - 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x0b, 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, 0x45, 0x6d, 0x61, 0x69, - 0x6c, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x45, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x72, 0x5f, 0x64, 0x75, 0x74, 0x79, 0x18, 0x0c, 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, 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, + 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, 0x22, 0xe3, 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, 0x38, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x18, 0x0b, 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, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x45, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x64, 0x75, 0x74, 0x79, 0x18, + 0x0c, 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, 0x12, 0x38, 0x0a, 0x05, 0x73, 0x6c, 0x61, 0x63, + 0x6b, 0x18, 0x0d, 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, 0x52, 0x05, 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, 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 ( @@ -524,24 +606,26 @@ 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, 5) +var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_goTypes = []interface{}{ (*Email)(nil), // 0: cloudprober.probes.alerting.Email (*PagerDuty)(nil), // 1: cloudprober.probes.alerting.PagerDuty - (*NotifyConfig)(nil), // 2: cloudprober.probes.alerting.NotifyConfig - (*Condition)(nil), // 3: cloudprober.probes.alerting.Condition - (*AlertConf)(nil), // 4: cloudprober.probes.alerting.AlertConf + (*Slack)(nil), // 2: cloudprober.probes.alerting.Slack + (*NotifyConfig)(nil), // 3: cloudprober.probes.alerting.NotifyConfig + (*Condition)(nil), // 4: cloudprober.probes.alerting.Condition + (*AlertConf)(nil), // 5: cloudprober.probes.alerting.AlertConf } var file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_depIdxs = []int32{ 0, // 0: cloudprober.probes.alerting.NotifyConfig.email:type_name -> cloudprober.probes.alerting.Email 1, // 1: cloudprober.probes.alerting.NotifyConfig.pager_duty:type_name -> cloudprober.probes.alerting.PagerDuty - 3, // 2: cloudprober.probes.alerting.AlertConf.condition:type_name -> cloudprober.probes.alerting.Condition - 2, // 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 + 2, // 2: cloudprober.probes.alerting.NotifyConfig.slack:type_name -> cloudprober.probes.alerting.Slack + 4, // 3: cloudprober.probes.alerting.AlertConf.condition:type_name -> cloudprober.probes.alerting.Condition + 3, // 4: cloudprober.probes.alerting.AlertConf.notify:type_name -> cloudprober.probes.alerting.NotifyConfig + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_init() } @@ -575,7 +659,7 @@ func file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_ } } file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NotifyConfig); i { + switch v := v.(*Slack); i { case 0: return &v.state case 1: @@ -587,7 +671,7 @@ func file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_ } } file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Condition); i { + switch v := v.(*NotifyConfig); i { case 0: return &v.state case 1: @@ -599,6 +683,18 @@ func file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_ } } file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Condition); 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[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AlertConf); i { case 0: return &v.state @@ -611,14 +707,14 @@ func file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_ } } } - file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[4].OneofWrappers = []interface{}{} + file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_msgTypes[5].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_github_com_cloudprober_cloudprober_probes_alerting_proto_config_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/probes/alerting/proto/config.proto b/probes/alerting/proto/config.proto index 6478f315e95..d5e3eae6aea 100644 --- a/probes/alerting/proto/config.proto +++ b/probes/alerting/proto/config.proto @@ -24,16 +24,32 @@ 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; } message NotifyConfig { @@ -58,6 +74,9 @@ message NotifyConfig { // PagerDuty configuration. PagerDuty pager_duty = 12; + + // Slack configuration. + Slack slack = 13; } message Condition { diff --git a/probes/alerting/proto/config_proto_gen.cue b/probes/alerting/proto/config_proto_gen.cue index 256f3356494..3e905b063f2 100644 --- a/probes/alerting/proto/config_proto_gen.cue +++ b/probes/alerting/proto/config_proto_gen.cue @@ -20,11 +20,12 @@ 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. @@ -32,6 +33,20 @@ package proto 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; +} + #NotifyConfig: { // Command to run when alert is fired. In the command line following fields // are substituted: @@ -54,6 +69,9 @@ package proto // PagerDuty configuration. pagerDuty?: #PagerDuty @protobuf(12,PagerDuty,name=pager_duty) + + // Slack configuration. + slack?: #Slack @protobuf(13,Slack) } #Condition: {