From 332c932d987b718fdae04e8afcc3b84f39ca3366 Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Fri, 17 Dec 2021 17:12:38 +0300 Subject: [PATCH 01/30] Slack web api (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Куча проблем * прикрепил аттачменты * gitignore * Delete .gitignore * asdf * fix config * рабочий ресивер * Vladimir EDITED * рабочая логика(почти) * добавил проверки на резолв, добавил очистку * добавил бесконечный цикл для очистки * Delete slack.tmpl * fix old mistakes * fix gorutine, islomal logic * поправил горутину, логику наполнения массива ТСок, переопределение значений алертов в сторадже * Внес небольшие правки в форматирование и поправил пару багов * add ci * add ci * add ci Co-authored-by: Vladimir Buyanov --- .gitignore | 3 +- asset/asset.go | 1 + asset/asset_generate.go | 1 + asset/assets_vfsdata.go | 1 + ci/docker/Dockerfile | 28 +++++++ cmd/alertmanager/main.go | 4 + config/config.go | 1 + config/notifiers.go | 54 ++++++++++++ go.mod | 1 + go.sum | 6 ++ notify/slackV2/slackV2.go | 170 ++++++++++++++++++++++++++++++++++++++ scripts/tools.go | 1 + 12 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 ci/docker/Dockerfile create mode 100644 notify/slackV2/slackV2.go diff --git a/.gitignore b/.gitignore index aa4b780003..a2f6cc3828 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,8 @@ /.release /.tarballs /vendor - +/.idea/workspace.xml +*.idea !.golangci.yml !/cli/testdata/*.yml !/cli/config/testdata/*.yml diff --git a/asset/asset.go b/asset/asset.go index 87ad35dc11..c098f38d8c 100644 --- a/asset/asset.go +++ b/asset/asset.go @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build dev // +build dev package asset diff --git a/asset/asset_generate.go b/asset/asset_generate.go index 2b8a4d3b05..5d933a6d2f 100644 --- a/asset/asset_generate.go +++ b/asset/asset_generate.go @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build ignore // +build ignore package main diff --git a/asset/assets_vfsdata.go b/asset/assets_vfsdata.go index 9a6b33e87d..d689f26082 100644 --- a/asset/assets_vfsdata.go +++ b/asset/assets_vfsdata.go @@ -1,5 +1,6 @@ // Code generated by vfsgen; DO NOT EDIT. +//go:build !dev // +build !dev package asset diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile new file mode 100644 index 0000000000..763b48bcfe --- /dev/null +++ b/ci/docker/Dockerfile @@ -0,0 +1,28 @@ +# Build image +FROM nexus.adsrv.wtf/click/golang:1.17.1-buster as build + +WORKDIR /app +COPY ./ ./ +RUN make build + +# Main image + + +FROM quay.io/prometheus/busybox-linux-amd64:latest +LABEL maintainer="The Prometheus Authors " + + +COPY --from=build /app/amtool /bin/amtool +COPY --from=build /app/alertmanager /bin/alertmanager +COPY --from=build /app/examples/ha/alertmanager.yml /etc/alertmanager/alertmanager.yml + +RUN mkdir -p /alertmanager && \ + chown -R nobody:nobody etc/alertmanager /alertmanager + +USER nobody +EXPOSE 9093 +VOLUME [ "/alertmanager" ] +WORKDIR /alertmanager +ENTRYPOINT [ "/bin/alertmanager" ] +CMD [ "--config.file=/etc/alertmanager/alertmanager.yml", \ + "--storage.path=/alertmanager" ] diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 87a0116db1..eb1a1df8d1 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -16,6 +16,7 @@ package main import ( "context" "fmt" + "github.com/prometheus/alertmanager/notify/slackV2" "net" "net/http" "net/url" @@ -160,6 +161,9 @@ func buildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template, log for i, c := range nc.SlackConfigs { add("slack", i, c, func(l log.Logger) (notify.Notifier, error) { return slack.New(c, tmpl, l) }) } + for i, c := range nc.SlackConfigV2 { + add("slackV2", i, c, func(l log.Logger) (notify.Notifier, error) { return slackV2.New(c, tmpl, l) }) + } for i, c := range nc.VictorOpsConfigs { add("victorops", i, c, func(l log.Logger) (notify.Notifier, error) { return victorops.New(c, tmpl, l) }) } diff --git a/config/config.go b/config/config.go index f1af191fef..cc4494d217 100644 --- a/config/config.go +++ b/config/config.go @@ -793,6 +793,7 @@ type Receiver struct { EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"` PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"` SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"` + SlackConfigV2 []*SlackConfigV2 `yaml:"slackV2_configs,omitempty" json:"slackV2_configs,omitempty"` WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"` OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"` WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"` diff --git a/config/notifiers.go b/config/notifiers.go index 41735f3c00..42bf276d52 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -81,6 +81,24 @@ var ( Footer: `{{ template "slack.default.footer" . }}`, } + // DefaultSlackV2Config defines default values for Slack configurations. + DefaultSlackV2Config = SlackConfigV2{ + NotifierConfig: NotifierConfig{ + VSendResolved: true, + }, + Color: `{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}`, + Username: `{{ template "slack.default.username" . }}`, + Title: `{{ template "slack.default.title" . }}`, + TitleLink: `{{ template "slack.default.titlelink" . }}`, + IconEmoji: `{{ template "slack.default.iconemoji" . }}`, + IconURL: `{{ template "slack.default.iconurl" . }}`, + Pretext: `{{ template "slack.default.pretext" . }}`, + Text: `{{ template "slack.default.text" . }}`, + Fallback: `{{ template "slack.default.fallback" . }}`, + CallbackID: `{{ template "slack.default.callbackid" . }}`, + Footer: `{{ template "slack.default.footer" . }}`, + } + // DefaultOpsGenieConfig defines default values for OpsGenie configurations. DefaultOpsGenieConfig = OpsGenieConfig{ NotifierConfig: NotifierConfig{ @@ -364,6 +382,32 @@ type SlackConfig struct { Actions []*SlackAction `yaml:"actions,omitempty" json:"actions,omitempty"` } +type SlackConfigV2 struct { + NotifierConfig `yaml:",inline" json:",inline"` + + Token string `yaml:"token,omitempty" json:"token,omitempty"` + Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` + Username string `yaml:"username,omitempty" json:"username,omitempty"` + Color string `yaml:"color,omitempty" json:"color,omitempty"` + + Title string `yaml:"title,omitempty" json:"title,omitempty"` + TitleLink string `yaml:"title_link,omitempty" json:"title_link,omitempty"` + Pretext string `yaml:"pretext,omitempty" json:"pretext,omitempty"` + Text string `yaml:"text,omitempty" json:"text,omitempty"` + Fields []*SlackField `yaml:"fields,omitempty" json:"fields,omitempty"` + ShortFields bool `yaml:"short_fields" json:"short_fields,omitempty"` + Footer string `yaml:"footer,omitempty" json:"footer,omitempty"` + Fallback string `yaml:"fallback,omitempty" json:"fallback,omitempty"` + CallbackID string `yaml:"callback_id,omitempty" json:"callback_id,omitempty"` + IconEmoji string `yaml:"icon_emoji,omitempty" json:"icon_emoji,omitempty"` + IconURL string `yaml:"icon_url,omitempty" json:"icon_url,omitempty"` + ImageURL string `yaml:"image_url,omitempty" json:"image_url,omitempty"` + ThumbURL string `yaml:"thumb_url,omitempty" json:"thumb_url,omitempty"` + LinkNames bool `yaml:"link_names" json:"link_names,omitempty"` + MrkdwnIn []string `yaml:"mrkdwn_in,omitempty" json:"mrkdwn_in,omitempty"` + Actions []*SlackAction `yaml:"actions,omitempty" json:"actions,omitempty"` +} + // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSlackConfig @@ -379,6 +423,16 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +func (c *SlackConfigV2) UnmarshalYAML(unmarshal func(interface{}) error) error { + *c = DefaultSlackV2Config + type plain SlackConfigV2 + if err := unmarshal((*plain)(c)); err != nil { + return err + } + + return nil +} + // WebhookConfig configures notifications via a generic webhook. type WebhookConfig struct { NotifierConfig `yaml:",inline" json:",inline"` diff --git a/go.mod b/go.mod index ada815e1e0..5e09622567 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/rs/cors v1.8.0 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 + github.com/slack-go/slack v0.10.0 github.com/stretchr/testify v1.7.0 github.com/xlab/treeprint v1.1.0 go.uber.org/atomic v1.9.0 diff --git a/go.sum b/go.sum index e6ecfddca2..e9c45522f9 100644 --- a/go.sum +++ b/go.sum @@ -201,6 +201,8 @@ github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEK github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -287,6 +289,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= @@ -443,6 +447,8 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/slack-go/slack v0.10.0 h1:L16Eqg3QZzRKGXIVsFSZdJdygjOphb2FjRUwH6VrFu8= +github.com/slack-go/slack v0.10.0/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go new file mode 100644 index 0000000000..dfee1dd0ad --- /dev/null +++ b/notify/slackV2/slackV2.go @@ -0,0 +1,170 @@ +// Copyright 2019 Prometheus Team +// 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 slackV2 + +import ( + "context" + "github.com/go-kit/log" + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" + "github.com/slack-go/slack" + "sync" + "time" +) + +// Notifier implements a Notifier for Slack notifications. +type Notifier struct { + conf *config.SlackConfigV2 + tmpl *template.Template + logger log.Logger + client *slack.Client + storage map[string]Data + mu sync.RWMutex +} + +type Data struct { + *template.Data +} + +// New returns a new Slack notification handler. +func New(c *config.SlackConfigV2, t *template.Template, l log.Logger) (*Notifier, error) { + token := c.Token + client := slack.New(token) + notifier := &Notifier{ + conf: c, + tmpl: t, + logger: l, + client: client, + storage: make(map[string]Data), + } + go notifier.storageCleaner() + return notifier, nil +} + +// Notify implements the Notifier interface. +func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { + data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger) + + changedMessages := make([]string, 0) + for _, newAlert := range data.Alerts { + messages := n.getMessagesByFingerprint(newAlert.Fingerprint) + changedMessages = append(changedMessages, messages...) + if len(messages) > 0 { + n.mu.Lock() + for _, ts := range messages { + for i := range n.storage[ts].Alerts { + if n.storage[ts].Alerts[i].Fingerprint == newAlert.Fingerprint { + n.storage[ts].Alerts[i].Status = newAlert.Status + n.storage[ts].Alerts[i].EndsAt = newAlert.EndsAt + } + } + } + n.mu.Unlock() + } else { + ts, err := n.send(data, "", false) + if err != nil { + return false, err + } + n.mu.Lock() + n.storage[ts] = Data{Data: data} + n.mu.Unlock() + } + } + + n.mu.RLock() + defer n.mu.RUnlock() + + for _, msg := range changedMessages { + _, err := n.send(n.storage[msg].Data, msg, false) + if err != nil { + return false, err + } + } + + return true, nil +} + +func (n *Notifier) send(data *template.Data, ts string, here bool) (string, error) { + var ( + err error + tmplText = notify.TmplText(n.tmpl, data, &err) + ) + + attachmets := &slack.Attachment{ + TitleLink: tmplText(n.conf.TitleLink), + Text: tmplText(n.conf.Text), + ImageURL: tmplText(n.conf.ImageURL), + Footer: tmplText(n.conf.Footer), + Color: n.conf.Color, + } + + attachmets.Actions = make([]slack.AttachmentAction, len(n.conf.Actions)) + for i, action := range n.conf.Actions { + attachmets.Actions[i] = slack.AttachmentAction{ + Type: slack.ActionType(action.Type), + Text: tmplText(action.Text), + URL: tmplText(action.URL), + Style: tmplText(action.Style), + Name: action.Name, + Value: tmplText(action.Value), + } + } + + if len(data.Alerts.Firing()) == 0 { + attachmets.Color = "good" + } + + if ts != "" && here == true { + attachmets.Pretext = "" + } + + att := slack.MsgOptionAttachments(*attachmets) + + if ts != "" { + _, _, messageTs, err := n.client.UpdateMessage(n.conf.Channel, ts, att) + return messageTs, err + } else { + _, messageTs, err := n.client.PostMessage(n.conf.Channel, att) + return messageTs, err + } +} + +func (n *Notifier) getMessagesByFingerprint(fp string) []string { + n.mu.RLock() + defer n.mu.RUnlock() + + ts := make([]string, 0) + for i, msg := range n.storage { + for _, alert := range msg.Alerts { + if fp == alert.Fingerprint { + ts = append(ts, i) + } + } + } + return ts +} + +func (n *Notifier) storageCleaner() { + for range time.Tick(time.Minute * 10) { + n.mu.Lock() + for k, data := range n.storage { + if len(data.Alerts.Firing()) == 0 { + delete(n.storage, k) + } + } + n.mu.Unlock() + } +} diff --git a/scripts/tools.go b/scripts/tools.go index 59277554f1..e076174c9f 100644 --- a/scripts/tools.go +++ b/scripts/tools.go @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build tools // +build tools // Package tools tracks dependencies for tools that are required to generate the protobuf code. From bc70ad76b3d10a322bf9849a0588032026c95a34 Mon Sep 17 00:00:00 2001 From: M-Indyukov <74921162+M-Indyukov@users.noreply.github.com> Date: Mon, 20 Dec 2021 18:46:32 +0300 Subject: [PATCH 02/30] Sigma notyfiers (#2) * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Delete .gitignore * Delete alertmanager.iml * Delete modules.xml * Delete vcs.xml --- .gitignore | 1 + cmd/alertmanager/main.go | 4 ++ config/config.go | 4 ++ config/notifiers.go | 44 +++++++++++++- notify/sigma/sigma.go | 123 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 notify/sigma/sigma.go diff --git a/.gitignore b/.gitignore index a2f6cc3828..738d551586 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /vendor /.idea/workspace.xml *.idea +/.idea/* !.golangci.yml !/cli/testdata/*.yml !/cli/config/testdata/*.yml diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index eb1a1df8d1..1d8c1fa9d4 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -54,6 +54,7 @@ import ( "github.com/prometheus/alertmanager/notify/opsgenie" "github.com/prometheus/alertmanager/notify/pagerduty" "github.com/prometheus/alertmanager/notify/pushover" + "github.com/prometheus/alertmanager/notify/sigma" "github.com/prometheus/alertmanager/notify/slack" "github.com/prometheus/alertmanager/notify/sns" "github.com/prometheus/alertmanager/notify/victorops" @@ -173,6 +174,9 @@ func buildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template, log for i, c := range nc.SNSConfigs { add("sns", i, c, func(l log.Logger) (notify.Notifier, error) { return sns.New(c, tmpl, l) }) } + for i, c := range nc.SigmaConfigs { + add("sigma", i, c, func(l log.Logger) (notify.Notifier, error) { return sigma.New(c, tmpl, l) }) + } if errs.Len() > 0 { return nil, &errs } diff --git a/config/config.go b/config/config.go index cc4494d217..8550c4548c 100644 --- a/config/config.go +++ b/config/config.go @@ -245,6 +245,9 @@ func resolveFilepaths(baseDir string, cfg *Config) { for _, cfg := range receiver.SNSConfigs { cfg.HTTPConfig.SetDirectory(baseDir) } + for _, cfg := range receiver.SigmaConfigs { + cfg.HTTPConfig.SetDirectory(baseDir) + } } } @@ -800,6 +803,7 @@ type Receiver struct { PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"` VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"` SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"` + SigmaConfigs []*SigmaConfig `yaml:"sigma_configs,omitempty" json:"sigma_configs,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver. diff --git a/config/notifiers.go b/config/notifiers.go index 42bf276d52..23975357a4 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -15,6 +15,7 @@ package config import ( "fmt" + "net/url" "regexp" "strings" "time" @@ -33,6 +34,12 @@ var ( }, } + DefaultSigmaConfig = SigmaConfig{ + NotifierConfig: NotifierConfig{ + VSendResolved: true, + }, + } + // DefaultEmailConfig defines default values for Email configurations. DefaultEmailConfig = EmailConfig{ NotifierConfig: NotifierConfig{ @@ -107,7 +114,6 @@ var ( Message: `{{ template "opsgenie.default.message" . }}`, Description: `{{ template "opsgenie.default.description" . }}`, Source: `{{ template "opsgenie.default.source" . }}`, - // TODO: Add a details field with all the alerts. } // DefaultWechatConfig defines default values for wechat configurations. @@ -463,6 +469,42 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +type SigmaConfig struct { + NotifierConfig `yaml:",inline" json:",inline"` + HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` + // URL to send POST request to. + URL *URL `yaml:"url" json:"url"` + APIKey Secret `yaml:"api_key,omitempty" json:"api_key,omitempty"` + Recipients []string `yaml:"recipients"` + NotificationType string `yaml:"notification_type"` + SenderName string `yaml:"sender_name"` + Text string `yaml:"text"` + TTS string `yaml:"tts"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (c *SigmaConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + *c = DefaultSigmaConfig + type plain SigmaConfig + if err := unmarshal((*plain)(c)); err != nil { + return err + } + if c.URL == nil { + defaultUrl, _ := url.Parse("https://online.sigmasms.ru/api/sendings") + c.URL = &URL{URL: defaultUrl} + } + if c.APIKey != "" { + return fmt.Errorf("api_key must be configured") + } + if c.NotificationType == "" { + c.NotificationType = "sms" + } + if c.TTS == "" { + c.TTS = "yandex:alena" + } + return nil +} + // WechatConfig configures notifications via Wechat. type WechatConfig struct { NotifierConfig `yaml:",inline" json:",inline"` diff --git a/notify/sigma/sigma.go b/notify/sigma/sigma.go new file mode 100644 index 0000000000..8909a7c32c --- /dev/null +++ b/notify/sigma/sigma.go @@ -0,0 +1,123 @@ +// Copyright 2019 Prometheus Team +// 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 sigma + +import ( + "bytes" + "context" + "encoding/json" + "github.com/go-kit/log" + "github.com/pkg/errors" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/types" + commoncfg "github.com/prometheus/common/config" + "io/ioutil" + "net/http" + + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/template" +) + +// Notifier implements a Notifier for generic sigma. +type Notifier struct { + conf *config.SigmaConfig + tmpl *template.Template + logger log.Logger + client *http.Client +} + +// New returns a new Sigma. +func New(conf *config.SigmaConfig, t *template.Template, l log.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) { + client, err := commoncfg.NewClientFromConfig(*conf.HTTPConfig, "sigma", httpOpts...) + if err != nil { + return nil, err + } + return &Notifier{ + conf: conf, + tmpl: t, + logger: l, + client: client, + }, nil +} + +// Message defines the JSON object send to Sigma endpoints. +type Request struct { + Recipient []string `json:"recipient"` + Type string `json:"type"` + Payload RequestPayload `json:"payload,omitempty"` +} + +type RequestPayload struct { + Sender string `json:"sender"` + Text string `json:"text"` +} + +type Response struct { + Id string `json:"id"` + Status string `json:"status"` + Error int `json:"error"` + Name string `json:"name"` + Message string `json:"message"` +} + +func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { + var err error + var ( + data = notify.GetTemplateData(ctx, n.tmpl, as, n.logger) + tmplText = notify.TmplText(n.tmpl, data, &err) + ) + + msg := Request{ + Recipient: n.conf.Recipients, + Type: n.conf.NotificationType, + Payload: RequestPayload{ + Sender: n.conf.SenderName, + Text: tmplText(n.conf.Text), + }, + } + body, err := json.Marshal(msg) + if err != nil { + return false, err + } + bodyReader := bytes.NewReader(body) + + req, err := http.NewRequest("POST", n.conf.URL.String(), bodyReader) + if err != nil { + return false, errors.Wrap(err, "request error") + } + req.Header.Set("Authorization", string(n.conf.APIKey)) + req.Header.Set("Content-NotificationType", "application/json") + + resp, err := n.client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + + respBody, err := ioutil.ReadAll(req.Body) + if err != nil { + return false, err + } + + r := Response{} + if err := json.Unmarshal(respBody, &r); err != nil { + return false, err + } + + if resp.StatusCode != http.StatusOK { + n.logger.Log("Sigma error. Type: %s; Code: %s; Message: %+v", n.conf.NotificationType, resp.StatusCode, r) + } + + return false, nil +} From e5756489e4f93fc037c95bb1c5668b105f41ed52 Mon Sep 17 00:00:00 2001 From: M-Indyukov <74921162+M-Indyukov@users.noreply.github.com> Date: Tue, 21 Dec 2021 18:17:15 +0300 Subject: [PATCH 03/30] Iti 0/feature/sigmasms (#4) * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Delete .gitignore * Delete alertmanager.iml * Delete modules.xml * Delete vcs.xml * fix pull * fix run --- config/config.go | 5 +++++ config/notifiers.go | 2 +- notify/sigma/sigma.go | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 8550c4548c..3497c25fdb 100644 --- a/config/config.go +++ b/config/config.go @@ -463,6 +463,11 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { sns.HTTPConfig = c.Global.HTTPConfig } } + for _, sigma := range rcv.SigmaConfigs { + if sigma.HTTPConfig == nil { + sigma.HTTPConfig = c.Global.HTTPConfig + } + } names[rcv.Name] = struct{}{} } diff --git a/config/notifiers.go b/config/notifiers.go index 23975357a4..c92512b7e8 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -493,7 +493,7 @@ func (c *SigmaConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { defaultUrl, _ := url.Parse("https://online.sigmasms.ru/api/sendings") c.URL = &URL{URL: defaultUrl} } - if c.APIKey != "" { + if c.APIKey == "" { return fmt.Errorf("api_key must be configured") } if c.NotificationType == "" { diff --git a/notify/sigma/sigma.go b/notify/sigma/sigma.go index 8909a7c32c..fc58097284 100644 --- a/notify/sigma/sigma.go +++ b/notify/sigma/sigma.go @@ -51,7 +51,7 @@ func New(conf *config.SigmaConfig, t *template.Template, l log.Logger, httpOpts }, nil } -// Message defines the JSON object send to Sigma endpoints. +// Request Message defines the JSON object send to Sigma endpoints. type Request struct { Recipient []string `json:"recipient"` Type string `json:"type"` From 98f64ac5e0304d8ab11bf2ad0927cf690e6f7672 Mon Sep 17 00:00:00 2001 From: M-Indyukov <74921162+M-Indyukov@users.noreply.github.com> Date: Tue, 21 Dec 2021 18:53:08 +0300 Subject: [PATCH 04/30] Iti 0/feature/sigmasms (#5) * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Delete .gitignore * Delete alertmanager.iml * Delete modules.xml * Delete vcs.xml * fix pull * fix run * fix string api_key --- config/notifiers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/notifiers.go b/config/notifiers.go index c92512b7e8..14bbe5255f 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -493,7 +493,7 @@ func (c *SigmaConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { defaultUrl, _ := url.Parse("https://online.sigmasms.ru/api/sendings") c.URL = &URL{URL: defaultUrl} } - if c.APIKey == "" { + if string(c.APIKey) == "" { return fmt.Errorf("api_key must be configured") } if c.NotificationType == "" { From c4eb56e7788a3bde563f18a7b6f762ba01ec8374 Mon Sep 17 00:00:00 2001 From: M-Indyukov <74921162+M-Indyukov@users.noreply.github.com> Date: Tue, 21 Dec 2021 19:12:04 +0300 Subject: [PATCH 05/30] Iti 0/feature/sigmasms (#6) * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Delete .gitignore * Delete alertmanager.iml * Delete modules.xml * Delete vcs.xml * fix pull * fix run * fix string api_key * fix string api_key * fix string api_key * check push --- notify/sigma/sigma.go | 1 + 1 file changed, 1 insertion(+) diff --git a/notify/sigma/sigma.go b/notify/sigma/sigma.go index fc58097284..a1816db632 100644 --- a/notify/sigma/sigma.go +++ b/notify/sigma/sigma.go @@ -86,6 +86,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) Text: tmplText(n.conf.Text), }, } + body, err := json.Marshal(msg) if err != nil { return false, err From 452b304926d17b5b0153f3d6644c349b9c3079c9 Mon Sep 17 00:00:00 2001 From: M-Indyukov <74921162+M-Indyukov@users.noreply.github.com> Date: Tue, 21 Dec 2021 19:15:20 +0300 Subject: [PATCH 06/30] Iti 0/feature/sigmasms (#7) * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Delete .gitignore * Delete alertmanager.iml * Delete modules.xml * Delete vcs.xml * fix pull * fix run * fix string api_key * fix string api_key * fix string api_key * check push --- config/notifiers.go | 2 +- notify/sigma/sigma.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index 14bbe5255f..c92512b7e8 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -493,7 +493,7 @@ func (c *SigmaConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { defaultUrl, _ := url.Parse("https://online.sigmasms.ru/api/sendings") c.URL = &URL{URL: defaultUrl} } - if string(c.APIKey) == "" { + if c.APIKey == "" { return fmt.Errorf("api_key must be configured") } if c.NotificationType == "" { diff --git a/notify/sigma/sigma.go b/notify/sigma/sigma.go index a1816db632..fc58097284 100644 --- a/notify/sigma/sigma.go +++ b/notify/sigma/sigma.go @@ -86,7 +86,6 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) Text: tmplText(n.conf.Text), }, } - body, err := json.Marshal(msg) if err != nil { return false, err From c0f818b3644a8269c736743ba764f0f2044f7c86 Mon Sep 17 00:00:00 2001 From: M-Indyukov <74921162+M-Indyukov@users.noreply.github.com> Date: Wed, 22 Dec 2021 19:40:51 +0300 Subject: [PATCH 07/30] Iti 0/feature/sigmasms (#8) * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Delete .gitignore * Delete alertmanager.iml * Delete modules.xml * Delete vcs.xml * fix pull * fix run * fix string api_key * fix string api_key * fix string api_key * check push --- config/notifiers.go | 12 ++++++------ notify/sigma/sigma.go | 10 ++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index c92512b7e8..3205ea3e10 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -474,12 +474,12 @@ type SigmaConfig struct { HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` // URL to send POST request to. URL *URL `yaml:"url" json:"url"` - APIKey Secret `yaml:"api_key,omitempty" json:"api_key,omitempty"` - Recipients []string `yaml:"recipients"` - NotificationType string `yaml:"notification_type"` - SenderName string `yaml:"sender_name"` - Text string `yaml:"text"` - TTS string `yaml:"tts"` + APIKey Secret `yaml:"api_key" json:"api_key"` + Recipient []string `yaml:"recipient" json:"recipient"` + NotificationType string `yaml:"notification_type" json:"notification_type"` + SenderName string `yaml:"sender_name" json:"sender_name"` + Text string `yaml:"text" json:"text"` + TTS string `yaml:"tts" json:"tts"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/notify/sigma/sigma.go b/notify/sigma/sigma.go index fc58097284..d84ad70d56 100644 --- a/notify/sigma/sigma.go +++ b/notify/sigma/sigma.go @@ -55,7 +55,7 @@ func New(conf *config.SigmaConfig, t *template.Template, l log.Logger, httpOpts type Request struct { Recipient []string `json:"recipient"` Type string `json:"type"` - Payload RequestPayload `json:"payload,omitempty"` + Payload RequestPayload `json:"payload"` } type RequestPayload struct { @@ -79,7 +79,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) ) msg := Request{ - Recipient: n.conf.Recipients, + Recipient: n.conf.Recipient, Type: n.conf.NotificationType, Payload: RequestPayload{ Sender: n.conf.SenderName, @@ -97,7 +97,8 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) return false, errors.Wrap(err, "request error") } req.Header.Set("Authorization", string(n.conf.APIKey)) - req.Header.Set("Content-NotificationType", "application/json") + req.Header.Set("Content-Type", "application/json") + req.WithContext(ctx) resp, err := n.client.Do(req) if err != nil { @@ -105,7 +106,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } defer resp.Body.Close() - respBody, err := ioutil.ReadAll(req.Body) + respBody, err := ioutil.ReadAll(resp.Body) if err != nil { return false, err } @@ -120,4 +121,5 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } return false, nil + } From fd7e22296d2e0aa7d0561157714aa9ea9b023e55 Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Wed, 22 Dec 2021 23:04:38 +0300 Subject: [PATCH 08/30] Itt 0/feature/slack v2 (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Куча проблем * прикрепил аттачменты * gitignore * Delete .gitignore * asdf * fix config * рабочий ресивер * Vladimir EDITED * рабочая логика(почти) * добавил проверки на резолв, добавил очистку * добавил бесконечный цикл для очистки * Delete slack.tmpl * fix old mistakes * fix gorutine, islomal logic * поправил горутину, логику наполнения массива ТСок, переопределение значений алертов в сторадже * Внес небольшие правки в форматирование и поправил пару багов * add ci * add ci * add ci * почистил конфиг от лишних полей, добавил логику here * fix merge conflicts ci * удалил говнокод * подумать еще над here * ignore tempalte * пытаюсь в блоки * Add blocks * fix url encode * add mentions * fix mentions Co-authored-by: Vladimir Buyanov --- .gitignore | 1 + ci/docker/Dockerfile | 2 - config/notifiers.go | 48 ++++++--------- notify/slackV2/blocks.go | 121 ++++++++++++++++++++++++++++++++++++++ notify/slackV2/slackV2.go | 74 +++++++++++++---------- notify/slackV2/utils.go | 24 ++++++++ 6 files changed, 206 insertions(+), 64 deletions(-) create mode 100644 notify/slackV2/blocks.go create mode 100644 notify/slackV2/utils.go diff --git a/.gitignore b/.gitignore index 738d551586..4d9e6f8031 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /.idea/workspace.xml *.idea /.idea/* +/template/slack.tmpl !.golangci.yml !/cli/testdata/*.yml !/cli/config/testdata/*.yml diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile index 763b48bcfe..afda679014 100644 --- a/ci/docker/Dockerfile +++ b/ci/docker/Dockerfile @@ -6,8 +6,6 @@ COPY ./ ./ RUN make build # Main image - - FROM quay.io/prometheus/busybox-linux-amd64:latest LABEL maintainer="The Prometheus Authors " diff --git a/config/notifiers.go b/config/notifiers.go index 3205ea3e10..b0bd1a5ab4 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -93,17 +93,7 @@ var ( NotifierConfig: NotifierConfig{ VSendResolved: true, }, - Color: `{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}`, - Username: `{{ template "slack.default.username" . }}`, - Title: `{{ template "slack.default.title" . }}`, - TitleLink: `{{ template "slack.default.titlelink" . }}`, - IconEmoji: `{{ template "slack.default.iconemoji" . }}`, - IconURL: `{{ template "slack.default.iconurl" . }}`, - Pretext: `{{ template "slack.default.pretext" . }}`, - Text: `{{ template "slack.default.text" . }}`, - Fallback: `{{ template "slack.default.fallback" . }}`, - CallbackID: `{{ template "slack.default.callbackid" . }}`, - Footer: `{{ template "slack.default.footer" . }}`, + Color: `{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}`, } // DefaultOpsGenieConfig defines default values for OpsGenie configurations. @@ -391,27 +381,18 @@ type SlackConfig struct { type SlackConfigV2 struct { NotifierConfig `yaml:",inline" json:",inline"` - Token string `yaml:"token,omitempty" json:"token,omitempty"` - Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` - Username string `yaml:"username,omitempty" json:"username,omitempty"` - Color string `yaml:"color,omitempty" json:"color,omitempty"` + Token string `yaml:"token,omitempty" json:"token,omitempty"` + Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` + Color string `yaml:"color,omitempty" json:"color,omitempty"` - Title string `yaml:"title,omitempty" json:"title,omitempty"` - TitleLink string `yaml:"title_link,omitempty" json:"title_link,omitempty"` - Pretext string `yaml:"pretext,omitempty" json:"pretext,omitempty"` - Text string `yaml:"text,omitempty" json:"text,omitempty"` - Fields []*SlackField `yaml:"fields,omitempty" json:"fields,omitempty"` - ShortFields bool `yaml:"short_fields" json:"short_fields,omitempty"` - Footer string `yaml:"footer,omitempty" json:"footer,omitempty"` - Fallback string `yaml:"fallback,omitempty" json:"fallback,omitempty"` - CallbackID string `yaml:"callback_id,omitempty" json:"callback_id,omitempty"` - IconEmoji string `yaml:"icon_emoji,omitempty" json:"icon_emoji,omitempty"` - IconURL string `yaml:"icon_url,omitempty" json:"icon_url,omitempty"` - ImageURL string `yaml:"image_url,omitempty" json:"image_url,omitempty"` - ThumbURL string `yaml:"thumb_url,omitempty" json:"thumb_url,omitempty"` - LinkNames bool `yaml:"link_names" json:"link_names,omitempty"` - MrkdwnIn []string `yaml:"mrkdwn_in,omitempty" json:"mrkdwn_in,omitempty"` - Actions []*SlackAction `yaml:"actions,omitempty" json:"actions,omitempty"` + Mentions []SlackMention `yaml:"mentions,omitempty" json:"mentions,omitempty"` + AlertmanagerUrl *URL `yaml:"alertmanagerUrl,omitempty" json:"alertmanagerUrl,omitempty"` +} + +type SlackMention struct { + Type string `yaml:"type" json:"type"` + Name string `yaml:"name" json:"name"` + ID string `yaml:"id" json:"id"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. @@ -436,6 +417,11 @@ func (c *SlackConfigV2) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } + if c.AlertmanagerUrl == nil { + defaultUrl, _ := url.Parse("http://localhost:9093") + c.AlertmanagerUrl = &URL{URL: defaultUrl} + } + return nil } diff --git a/notify/slackV2/blocks.go b/notify/slackV2/blocks.go new file mode 100644 index 0000000000..4a7053995f --- /dev/null +++ b/notify/slackV2/blocks.go @@ -0,0 +1,121 @@ +package slackV2 + +import ( + "fmt" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/common/model" + "github.com/slack-go/slack" + "strings" +) + +type Text struct { + Type string `json:"type"` + Text string `json:"text"` +} +type Element struct { + Type string `json:"type"` + Text string `json:"text"` +} +type Field struct { + Type string `json:"type"` + Text string `json:"text"` +} +type Block struct { + Type slack.MessageBlockType `json:"type"` + Text *Text `json:"text,omitempty"` + Fields []*Field `json:"fields,omitempty"` + Elements []*Element `json:"elements,omitempty"` +} + +func (b Block) BlockType() slack.MessageBlockType { + return b.Type +} + +func (n *Notifier) formatMessage(data *template.Data) slack.Blocks { + firing := make([]string, 0) + resolved := make([]string, 0) + severity := make([]string, 0) + envs := make([]string, 0) + + blocks := make([]slack.Block, 0) + + for _, alert := range data.Alerts { + for _, v := range alert.Labels.SortedPairs() { + switch v.Name { + case "host_name": + switch model.AlertStatus(alert.Status) { + case model.AlertFiring: + firing = append(firing, v.Value) + case model.AlertResolved: + resolved = append(resolved, v.Value) + } + case "severity": + severity = append(severity, v.Value) + case "env": + envs = append(envs, v.Value) + } + } + } + + severity = UniqStr(severity) + resolved = UniqStr(resolved) + firing = UniqStr(firing) + envs = UniqStr(envs) + + blocks = append(blocks, Block{Type: slack.MBTHeader, Text: &Text{Type: slack.PlainTextType, Text: getMapValue(data.CommonLabels, "alertname")}}) + + { + url := n.conf.AlertmanagerUrl.Copy() + url.Path = "/#/silences/new" + args := url.Query() + filters := make([]string, 0) + for _, v := range data.CommonLabels.SortedPairs() { + filters = append(filters, fmt.Sprintf("%s=\"%s\"", v.Name, v.Value)) + } + args.Add("filter", fmt.Sprintf("{%s}", strings.Join(filters, ","))) + url.RawQuery = args.Encode() + url.EscapedFragment() + + fields := make([]*Field, 0) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Env: %s*", strings.ToUpper(strings.Join(envs, ", ")))}) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Severety: %s*", strings.ToUpper(strings.Join(severity, ", ")))}) + if getMapValue(data.CommonLabels, "GeneratorURL") != "" { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:chart_with_upwards_trend:Graph>*", getMapValue(data.CommonLabels, "GeneratorURL"))}) + } else { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf(":chart_with_upwards_trend:~Graph~")}) + } + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:no_bell:Silence>*", strings.Replace(url.String(), "%23", "#", 1))}) + blocks = append(blocks, Block{Type: slack.MBTSection, Fields: fields}) + } + + if len(firing) > 0 && len(resolved) > 0 { + fields := make([]*Field, 0) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Firing:* `%s`", strings.Join(firing, ", "))}) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Resolved:* `%s`", strings.Join(firing, ", "))}) + blocks = append(blocks, Block{Type: slack.MBTSection, Fields: fields}) + } else { + fields := make([]*Field, 0) + if len(resolved) > 0 { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Resolved: *`%s`", strings.Join(resolved, ", "))}) + } else { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Firing: *`%s`", strings.Join(firing, ", "))}) + } + blocks = append(blocks, Block{Type: slack.MBTSection, Fields: fields}) + } + + { + block := Block{Type: slack.MBTContext, Elements: make([]*Element, 0)} + + if summary := getMapValue(data.CommonAnnotations, "description"); len(summary) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", summary)}) + } + if desc := getMapValue(data.CommonAnnotations, "description"); len(desc) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s", desc)}) + } + blocks = append(blocks, block) + } + + result := slack.Blocks{BlockSet: blocks} + + return result +} diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go index dfee1dd0ad..bd7997c63c 100644 --- a/notify/slackV2/slackV2.go +++ b/notify/slackV2/slackV2.go @@ -15,12 +15,14 @@ package slackV2 import ( "context" + "fmt" "github.com/go-kit/log" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" "github.com/slack-go/slack" + "strings" "sync" "time" ) @@ -43,6 +45,8 @@ type Data struct { func New(c *config.SlackConfigV2, t *template.Template, l log.Logger) (*Notifier, error) { token := c.Token client := slack.New(token) + //client.Debug() + notifier := &Notifier{ conf: c, tmpl: t, @@ -69,18 +73,23 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) if n.storage[ts].Alerts[i].Fingerprint == newAlert.Fingerprint { n.storage[ts].Alerts[i].Status = newAlert.Status n.storage[ts].Alerts[i].EndsAt = newAlert.EndsAt + n.storage[ts].Data.CommonAnnotations = data.CommonAnnotations } } } n.mu.Unlock() } else { - ts, err := n.send(data, "", false) + ts, err := n.send(data, "") if err != nil { return false, err } n.mu.Lock() n.storage[ts] = Data{Data: data} n.mu.Unlock() + + if err := n.sendNotify(ts); err != nil { + return false, err + } } } @@ -88,7 +97,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) defer n.mu.RUnlock() for _, msg := range changedMessages { - _, err := n.send(n.storage[msg].Data, msg, false) + _, err := n.send(n.storage[msg].Data, msg) if err != nil { return false, err } @@ -97,41 +106,20 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) return true, nil } -func (n *Notifier) send(data *template.Data, ts string, here bool) (string, error) { - var ( - err error - tmplText = notify.TmplText(n.tmpl, data, &err) - ) - - attachmets := &slack.Attachment{ - TitleLink: tmplText(n.conf.TitleLink), - Text: tmplText(n.conf.Text), - ImageURL: tmplText(n.conf.ImageURL), - Footer: tmplText(n.conf.Footer), - Color: n.conf.Color, - } +func (n *Notifier) send(data *template.Data, ts string) (string, error) { - attachmets.Actions = make([]slack.AttachmentAction, len(n.conf.Actions)) - for i, action := range n.conf.Actions { - attachmets.Actions[i] = slack.AttachmentAction{ - Type: slack.ActionType(action.Type), - Text: tmplText(action.Text), - URL: tmplText(action.URL), - Style: tmplText(action.Style), - Name: action.Name, - Value: tmplText(action.Value), - } - } + fmt.Printf("%+v\n", data) - if len(data.Alerts.Firing()) == 0 { - attachmets.Color = "good" + attachment := slack.Attachment{ + Color: n.conf.Color, + Blocks: n.formatMessage(data), } - if ts != "" && here == true { - attachmets.Pretext = "" + if len(data.Alerts.Firing()) == 0 { + attachment.Color = "#1aad21" } - att := slack.MsgOptionAttachments(*attachmets) + att := slack.MsgOptionAttachments(attachment) if ts != "" { _, _, messageTs, err := n.client.UpdateMessage(n.conf.Channel, ts, att) @@ -142,6 +130,30 @@ func (n *Notifier) send(data *template.Data, ts string, here bool) (string, erro } } +func (n *Notifier) sendNotify(ts string) error { + if len(n.conf.Mentions) == 0 { + return nil + } + users := make([]string, len(n.conf.Mentions)) + for i, val := range n.conf.Mentions { + switch strings.ToLower(val.Type) { + case "group": + users[i] = fmt.Sprintf("", val.ID, val.Name) + case "user": + users[i] = fmt.Sprintf("<@%s>", val.ID) + } + } + + text := fmt.Sprintf("Look here %s", strings.Join(users, " ")) + opts := make([]slack.MsgOption, 0) + opts = append(opts, slack.MsgOptionTS(ts)) + opts = append(opts, slack.MsgOptionText(text, false)) + // + _, _, err := n.client.PostMessage(n.conf.Channel, opts...) + return err + +} + func (n *Notifier) getMessagesByFingerprint(fp string) []string { n.mu.RLock() defer n.mu.RUnlock() diff --git a/notify/slackV2/utils.go b/notify/slackV2/utils.go new file mode 100644 index 0000000000..0ec6bcff7a --- /dev/null +++ b/notify/slackV2/utils.go @@ -0,0 +1,24 @@ +package slackV2 + +import "github.com/prometheus/alertmanager/template" + +func UniqStr(input []string) []string { + u := make([]string, 0, len(input)) + m := make(map[string]bool) + + for _, val := range input { + if _, ok := m[val]; !ok { + m[val] = true + u = append(u, val) + } + } + return u +} + +func getMapValue(data template.KV, key string) string { + if value, ok := data[key]; ok { + return value + } else { + return "" + } +} From 89087a291e20122fbc5341e1780f944bcf8b1344 Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Thu, 23 Dec 2021 12:07:07 +0300 Subject: [PATCH 09/30] Itt 0/feature/slack v2 (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Куча проблем * прикрепил аттачменты * gitignore * Delete .gitignore * asdf * fix config * рабочий ресивер * Vladimir EDITED * рабочая логика(почти) * добавил проверки на резолв, добавил очистку * добавил бесконечный цикл для очистки * Delete slack.tmpl * fix old mistakes * fix gorutine, islomal logic * поправил горутину, логику наполнения массива ТСок, переопределение значений алертов в сторадже * Внес небольшие правки в форматирование и поправил пару багов * add ci * add ci * add ci * почистил конфиг от лишних полей, добавил логику here * fix merge conflicts ci * удалил говнокод * подумать еще над here * ignore tempalte * пытаюсь в блоки * Add blocks * fix url encode * add mentions * fix mentions * дебаг на проде Co-authored-by: Vladimir Buyanov --- notify/slackV2/slackV2.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go index bd7997c63c..a90516c527 100644 --- a/notify/slackV2/slackV2.go +++ b/notify/slackV2/slackV2.go @@ -44,8 +44,8 @@ type Data struct { // New returns a new Slack notification handler. func New(c *config.SlackConfigV2, t *template.Template, l log.Logger) (*Notifier, error) { token := c.Token - client := slack.New(token) - //client.Debug() + client := slack.New(token, slack.OptionDebug(true)) + client.Debug() notifier := &Notifier{ conf: c, @@ -108,8 +108,6 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) func (n *Notifier) send(data *template.Data, ts string) (string, error) { - fmt.Printf("%+v\n", data) - attachment := slack.Attachment{ Color: n.conf.Color, Blocks: n.formatMessage(data), From fe25f0a414f29e2a1f224408a3e3ff287a49625e Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Thu, 23 Dec 2021 12:58:14 +0300 Subject: [PATCH 10/30] Add alert deduplication --- api/v2/api.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/api/v2/api.go b/api/v2/api.go index da4797dc08..5df2afa701 100644 --- a/api/v2/api.go +++ b/api/v2/api.go @@ -386,6 +386,8 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams res := make(open_api_models.AlertGroups, 0, len(alertGroups)) + dedup := make(map[prometheus_model.Fingerprint]bool) + for _, alertGroup := range alertGroups { ag := &open_api_models.AlertGroup{ Receiver: &open_api_models.Receiver{Name: &alertGroup.Receiver}, @@ -395,12 +397,17 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams for _, alert := range alertGroup.Alerts { fp := alert.Fingerprint() - receivers := allReceivers[fp] - status := api.getAlertStatus(fp) - apiAlert := AlertToOpenAPIAlert(alert, status, receivers) - ag.Alerts = append(ag.Alerts, apiAlert) + if _, ok := dedup[fp]; !ok { + dedup[fp] = true + receivers := allReceivers[fp] + status := api.getAlertStatus(fp) + apiAlert := AlertToOpenAPIAlert(alert, status, receivers) + ag.Alerts = append(ag.Alerts, apiAlert) + } + } + if len(ag.Alerts) > 0 { + res = append(res, ag) } - res = append(res, ag) } return alertgroup_ops.NewGetAlertGroupsOK().WithPayload(res) From 7ca3e77f0b8dbd204f318469e8f9237d6762b499 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Thu, 23 Dec 2021 13:25:20 +0300 Subject: [PATCH 11/30] Fix slack v2 alerts --- notify/slackV2/blocks.go | 67 ++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/notify/slackV2/blocks.go b/notify/slackV2/blocks.go index 4a7053995f..881b2a3d5a 100644 --- a/notify/slackV2/blocks.go +++ b/notify/slackV2/blocks.go @@ -5,6 +5,7 @@ import ( "github.com/prometheus/alertmanager/template" "github.com/prometheus/common/model" "github.com/slack-go/slack" + url2 "net/url" "strings" ) @@ -65,26 +66,42 @@ func (n *Notifier) formatMessage(data *template.Data) slack.Blocks { blocks = append(blocks, Block{Type: slack.MBTHeader, Text: &Text{Type: slack.PlainTextType, Text: getMapValue(data.CommonLabels, "alertname")}}) { - url := n.conf.AlertmanagerUrl.Copy() - url.Path = "/#/silences/new" - args := url.Query() - filters := make([]string, 0) - for _, v := range data.CommonLabels.SortedPairs() { - filters = append(filters, fmt.Sprintf("%s=\"%s\"", v.Name, v.Value)) + url := "" + if urlParsed, err := url2.Parse(data.ExternalURL); err == nil { + urlParsed.Path = "/#/silences/new" + args := urlParsed.Query() + filters := make([]string, 0) + for _, v := range data.CommonLabels.SortedPairs() { + filters = append(filters, fmt.Sprintf("%s=\"%s\"", v.Name, v.Value)) + } + args.Add("filter", fmt.Sprintf("{%s}", strings.Join(filters, ","))) + urlParsed.RawQuery = args.Encode() + url = urlParsed.String() + url = strings.Replace(url, "%23", "#", 1) + } + + graphUrl := "" + for _, alert := range data.Alerts { + if alert.GeneratorURL != "" { + graphUrl = alert.GeneratorURL + break + } } - args.Add("filter", fmt.Sprintf("{%s}", strings.Join(filters, ","))) - url.RawQuery = args.Encode() - url.EscapedFragment() fields := make([]*Field, 0) fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Env: %s*", strings.ToUpper(strings.Join(envs, ", ")))}) fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Severety: %s*", strings.ToUpper(strings.Join(severity, ", ")))}) - if getMapValue(data.CommonLabels, "GeneratorURL") != "" { + if graphUrl != "" { fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:chart_with_upwards_trend:Graph>*", getMapValue(data.CommonLabels, "GeneratorURL"))}) } else { fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf(":chart_with_upwards_trend:~Graph~")}) } - fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:no_bell:Silence>*", strings.Replace(url.String(), "%23", "#", 1))}) + if url != "" { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:no_bell:Silence>*", url)}) + } else { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*:no_bell:~Silence~")}) + } + blocks = append(blocks, Block{Type: slack.MBTSection, Fields: fields}) } @@ -106,13 +123,31 @@ func (n *Notifier) formatMessage(data *template.Data) slack.Blocks { { block := Block{Type: slack.MBTContext, Elements: make([]*Element, 0)} - if summary := getMapValue(data.CommonAnnotations, "description"); len(summary) > 0 { - block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", summary)}) + if val := getMapValue(data.CommonAnnotations, "summary"); len(val) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", val)}) + } else { + for _, al := range data.Alerts { + if val, ok := al.Annotations["summary"]; ok && len(val) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", val)}) + break + } + } + } + + if val := getMapValue(data.CommonAnnotations, "description"); len(val) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s", val)}) + } else { + for _, al := range data.Alerts { + if val, ok := al.Annotations["description"]; ok && len(val) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", val)}) + break + } + } } - if desc := getMapValue(data.CommonAnnotations, "description"); len(desc) > 0 { - block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s", desc)}) + + if len(block.Elements) > 0 { + blocks = append(blocks, block) } - blocks = append(blocks, block) } result := slack.Blocks{BlockSet: blocks} From 669f7f7d05e5d945c98fd7d2db3d990466ab92dc Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Thu, 23 Dec 2021 13:30:35 +0300 Subject: [PATCH 12/30] Remove unneede config param --- config/notifiers.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index b0bd1a5ab4..8bf1c88d4c 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -385,8 +385,7 @@ type SlackConfigV2 struct { Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` Color string `yaml:"color,omitempty" json:"color,omitempty"` - Mentions []SlackMention `yaml:"mentions,omitempty" json:"mentions,omitempty"` - AlertmanagerUrl *URL `yaml:"alertmanagerUrl,omitempty" json:"alertmanagerUrl,omitempty"` + Mentions []SlackMention `yaml:"mentions,omitempty" json:"mentions,omitempty"` } type SlackMention struct { @@ -417,11 +416,6 @@ func (c *SlackConfigV2) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } - if c.AlertmanagerUrl == nil { - defaultUrl, _ := url.Parse("http://localhost:9093") - c.AlertmanagerUrl = &URL{URL: defaultUrl} - } - return nil } From b08bb71f8293612ae4828dc0a5f9b29c9df2d416 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Thu, 23 Dec 2021 14:36:20 +0300 Subject: [PATCH 13/30] Implement resend notification logic --- notify/slackV2/blocks.go | 2 +- notify/slackV2/slackV2.go | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/notify/slackV2/blocks.go b/notify/slackV2/blocks.go index 881b2a3d5a..ea36bee6a8 100644 --- a/notify/slackV2/blocks.go +++ b/notify/slackV2/blocks.go @@ -92,7 +92,7 @@ func (n *Notifier) formatMessage(data *template.Data) slack.Blocks { fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Env: %s*", strings.ToUpper(strings.Join(envs, ", ")))}) fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Severety: %s*", strings.ToUpper(strings.Join(severity, ", ")))}) if graphUrl != "" { - fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:chart_with_upwards_trend:Graph>*", getMapValue(data.CommonLabels, "GeneratorURL"))}) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:chart_with_upwards_trend:Graph>*", graphUrl)}) } else { fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf(":chart_with_upwards_trend:~Graph~")}) } diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go index a90516c527..b8a8eab183 100644 --- a/notify/slackV2/slackV2.go +++ b/notify/slackV2/slackV2.go @@ -63,19 +63,27 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger) changedMessages := make([]string, 0) + notifyMessages := make([]string, 0) for _, newAlert := range data.Alerts { messages := n.getMessagesByFingerprint(newAlert.Fingerprint) changedMessages = append(changedMessages, messages...) if len(messages) > 0 { n.mu.Lock() for _, ts := range messages { + changed := false for i := range n.storage[ts].Alerts { if n.storage[ts].Alerts[i].Fingerprint == newAlert.Fingerprint { - n.storage[ts].Alerts[i].Status = newAlert.Status + if n.storage[ts].Alerts[i].Status != newAlert.Status { + n.storage[ts].Alerts[i].Status = newAlert.Status + changed = true + } n.storage[ts].Alerts[i].EndsAt = newAlert.EndsAt n.storage[ts].Data.CommonAnnotations = data.CommonAnnotations } } + if !changed { + notifyMessages = append(notifyMessages, ts) + } } n.mu.Unlock() } else { @@ -86,7 +94,10 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) n.mu.Lock() n.storage[ts] = Data{Data: data} n.mu.Unlock() + notifyMessages = append(notifyMessages, ts) + } + for _, ts := range UniqStr(notifyMessages) { if err := n.sendNotify(ts); err != nil { return false, err } From 4d64eb034a2cce039a90c018b73c5997859d50a0 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Thu, 23 Dec 2021 14:48:21 +0300 Subject: [PATCH 14/30] Small fixes --- config/notifiers.go | 32 ++++++++++++++++---------------- notify/sigma/sigma.go | 12 +++++++++++- notify/slackV2/slackV2.go | 8 ++++++-- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index 8bf1c88d4c..2bf7a67f91 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -378,22 +378,6 @@ type SlackConfig struct { Actions []*SlackAction `yaml:"actions,omitempty" json:"actions,omitempty"` } -type SlackConfigV2 struct { - NotifierConfig `yaml:",inline" json:",inline"` - - Token string `yaml:"token,omitempty" json:"token,omitempty"` - Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` - Color string `yaml:"color,omitempty" json:"color,omitempty"` - - Mentions []SlackMention `yaml:"mentions,omitempty" json:"mentions,omitempty"` -} - -type SlackMention struct { - Type string `yaml:"type" json:"type"` - Name string `yaml:"name" json:"name"` - ID string `yaml:"id" json:"id"` -} - // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSlackConfig @@ -409,6 +393,22 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +type SlackConfigV2 struct { + NotifierConfig `yaml:",inline" json:",inline"` + + Token string `yaml:"token,omitempty" json:"token,omitempty"` + Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` + Color string `yaml:"color,omitempty" json:"color,omitempty"` + Debug bool `yaml:"debug" json:"debug"` + Mentions []SlackMention `yaml:"mentions,omitempty" json:"mentions,omitempty"` +} + +type SlackMention struct { + Type string `yaml:"type" json:"type"` + Name string `yaml:"name" json:"name"` + ID string `yaml:"id" json:"id"` +} + func (c *SlackConfigV2) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSlackV2Config type plain SlackConfigV2 diff --git a/notify/sigma/sigma.go b/notify/sigma/sigma.go index d84ad70d56..d91b8107d3 100644 --- a/notify/sigma/sigma.go +++ b/notify/sigma/sigma.go @@ -17,6 +17,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "github.com/go-kit/log" "github.com/pkg/errors" "github.com/prometheus/alertmanager/notify" @@ -117,9 +118,18 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } if resp.StatusCode != http.StatusOK { - n.logger.Log("Sigma error. Type: %s; Code: %s; Message: %+v", n.conf.NotificationType, resp.StatusCode, r) + return false, &Error{NotificationType: n.conf.NotificationType, StatusCode: resp.StatusCode, Response: r} } return false, nil +} + +type Error struct { + NotificationType string + StatusCode int + Response +} +func (e *Error) Error() string { + return fmt.Sprintf("Sigma error. Type: %s; Code: %d; Response: %+v", e.NotificationType, e.StatusCode, e.Response) } diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go index b8a8eab183..57225a0899 100644 --- a/notify/slackV2/slackV2.go +++ b/notify/slackV2/slackV2.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "github.com/go-kit/log" + "github.com/go-kit/log/level" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/template" @@ -44,8 +45,7 @@ type Data struct { // New returns a new Slack notification handler. func New(c *config.SlackConfigV2, t *template.Template, l log.Logger) (*Notifier, error) { token := c.Token - client := slack.New(token, slack.OptionDebug(true)) - client.Debug() + client := slack.New(token, slack.OptionDebug(c.Debug)) notifier := &Notifier{ conf: c, @@ -62,6 +62,10 @@ func New(c *config.SlackConfigV2, t *template.Template, l log.Logger) (*Notifier func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger) + if n.conf.Debug { + level.Debug(n.logger).Log("Alert Data", data) + } + changedMessages := make([]string, 0) notifyMessages := make([]string, 0) for _, newAlert := range data.Alerts { From 805bc6c24e5d7464f0719b40f96d0d805398d34b Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Thu, 23 Dec 2021 17:57:20 +0300 Subject: [PATCH 15/30] Fix description name --- notify/slackV2/blocks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notify/slackV2/blocks.go b/notify/slackV2/blocks.go index ea36bee6a8..c865d2baea 100644 --- a/notify/slackV2/blocks.go +++ b/notify/slackV2/blocks.go @@ -139,7 +139,7 @@ func (n *Notifier) formatMessage(data *template.Data) slack.Blocks { } else { for _, al := range data.Alerts { if val, ok := al.Annotations["description"]; ok && len(val) > 0 { - block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", val)}) + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s", val)}) break } } From a010f6209794ba06324d848f358fd664af737419 Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Thu, 23 Dec 2021 18:01:25 +0300 Subject: [PATCH 16/30] Itt 0/feature/slack v2 (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Куча проблем * прикрепил аттачменты * gitignore * Delete .gitignore * asdf * fix config * рабочий ресивер * Vladimir EDITED * рабочая логика(почти) * добавил проверки на резолв, добавил очистку * добавил бесконечный цикл для очистки * Delete slack.tmpl * fix old mistakes * fix gorutine, islomal logic * поправил горутину, логику наполнения массива ТСок, переопределение значений алертов в сторадже * Внес небольшие правки в форматирование и поправил пару багов * add ci * add ci * add ci * почистил конфиг от лишних полей, добавил логику here * fix merge conflicts ci * удалил говнокод * подумать еще над here * ignore tempalte * пытаюсь в блоки * Add blocks * fix url encode * add mentions * fix mentions * дебаг на проде * добавил каст для старых алертов * add divider * удалил старый код Co-authored-by: Vladimir Buyanov --- notify/slackV2/blocks.go | 1 + 1 file changed, 1 insertion(+) diff --git a/notify/slackV2/blocks.go b/notify/slackV2/blocks.go index c865d2baea..532f5f8ace 100644 --- a/notify/slackV2/blocks.go +++ b/notify/slackV2/blocks.go @@ -64,6 +64,7 @@ func (n *Notifier) formatMessage(data *template.Data) slack.Blocks { envs = UniqStr(envs) blocks = append(blocks, Block{Type: slack.MBTHeader, Text: &Text{Type: slack.PlainTextType, Text: getMapValue(data.CommonLabels, "alertname")}}) + blocks = append(blocks, Block{Type: slack.MBTDivider}) { url := "" From b37bb976609591667c00b4b161421572d7143a56 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Fri, 24 Dec 2021 13:03:47 +0300 Subject: [PATCH 17/30] Add check for orhaped alerts --- notify/slackV2/slackV2.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go index 57225a0899..271024d700 100644 --- a/notify/slackV2/slackV2.go +++ b/notify/slackV2/slackV2.go @@ -91,14 +91,17 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } n.mu.Unlock() } else { - ts, err := n.send(data, "") - if err != nil { - return false, err + // Делаем проверку, что бы не отправлять резолвы на "осиратевшие алерты", у которых 0 firing + if len(data.Alerts.Firing()) > 0 { + ts, err := n.send(data, "") + if err != nil { + return false, err + } + n.mu.Lock() + n.storage[ts] = Data{Data: data} + n.mu.Unlock() + notifyMessages = append(notifyMessages, ts) } - n.mu.Lock() - n.storage[ts] = Data{Data: data} - n.mu.Unlock() - notifyMessages = append(notifyMessages, ts) } for _, ts := range UniqStr(notifyMessages) { From eed33b2869d596bdf0e47c218f82329ca918abf6 Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Mon, 27 Dec 2021 16:46:02 +0300 Subject: [PATCH 18/30] Itt 0/feature/mention interval (#12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Куча проблем * прикрепил аттачменты * gitignore * Delete .gitignore * asdf * fix config * рабочий ресивер * Vladimir EDITED * рабочая логика(почти) * добавил проверки на резолв, добавил очистку * добавил бесконечный цикл для очистки * Delete slack.tmpl * fix old mistakes * fix gorutine, islomal logic * поправил горутину, логику наполнения массива ТСок, переопределение значений алертов в сторадже * Внес небольшие правки в форматирование и поправил пару багов * add ci * add ci * add ci * почистил конфиг от лишних полей, добавил логику here * fix merge conflicts ci * удалил говнокод * подумать еще над here * ignore tempalte * пытаюсь в блоки * Add blocks * fix url encode * add mentions * fix mentions * дебаг на проде * добавил каст для старых алертов * add divider * удалил старый код * add mentionDelay Co-authored-by: Vladimir Buyanov --- config/notifiers.go | 11 ++++++----- notify/slackV2/slackV2.go | 11 +++++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index 2bf7a67f91..3a3a6b9a07 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -396,11 +396,12 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { type SlackConfigV2 struct { NotifierConfig `yaml:",inline" json:",inline"` - Token string `yaml:"token,omitempty" json:"token,omitempty"` - Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` - Color string `yaml:"color,omitempty" json:"color,omitempty"` - Debug bool `yaml:"debug" json:"debug"` - Mentions []SlackMention `yaml:"mentions,omitempty" json:"mentions,omitempty"` + Token string `yaml:"token,omitempty" json:"token,omitempty"` + Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` + Color string `yaml:"color,omitempty" json:"color,omitempty"` + Debug bool `yaml:"debug" json:"debug"` + Mentions []SlackMention `yaml:"mentions,omitempty" json:"mentions,omitempty"` + MentionDelay duration `yaml:"mentionDelay" json:"mentionDelay"` } type SlackMention struct { diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go index 271024d700..da34db4f84 100644 --- a/notify/slackV2/slackV2.go +++ b/notify/slackV2/slackV2.go @@ -40,6 +40,7 @@ type Notifier struct { type Data struct { *template.Data + SendAt time.Time } // New returns a new Slack notification handler. @@ -83,6 +84,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } n.storage[ts].Alerts[i].EndsAt = newAlert.EndsAt n.storage[ts].Data.CommonAnnotations = data.CommonAnnotations + } } if !changed { @@ -105,8 +107,13 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } for _, ts := range UniqStr(notifyMessages) { - if err := n.sendNotify(ts); err != nil { - return false, err + if n.storage[ts].SendAt.IsZero() || n.storage[ts].SendAt.Add(time.Duration(n.conf.MentionDelay)).Before(time.Now()) { + if err := n.sendNotify(ts); err != nil { + return false, err + } + n.mu.Lock() + n.storage[ts] = Data{Data: n.storage[ts].Data, SendAt: time.Now()} + n.mu.Unlock() } } } From 230e1ead923e313cfaa9e45befdcefd5afded998 Mon Sep 17 00:00:00 2001 From: M-Indyukov <74921162+M-Indyukov@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:02:05 +0300 Subject: [PATCH 19/30] Iti 0/feature/sigmasms (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Sigma notyfiers * Delete .gitignore * Delete alertmanager.iml * Delete modules.xml * Delete vcs.xml * fix pull * fix run * fix string api_key * fix string api_key * fix string api_key * check push * Добавил голосовые сообщения * mini fix * Убрал exclude для получателей --- config/notifiers.go | 7 ++++- notify/sigma/sigma.go | 65 ++++++++++++++++++++++++++++++++----------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index 3a3a6b9a07..9c1745972b 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -477,8 +477,13 @@ func (c *SigmaConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if c.APIKey == "" { return fmt.Errorf("api_key must be configured") } - if c.NotificationType == "" { + switch strings.ToLower(c.NotificationType) { + case "sms", "": c.NotificationType = "sms" + case "voice": + c.NotificationType = "voice" + default: + return fmt.Errorf("unknown notification type: %s", c.NotificationType) } if c.TTS == "" { c.TTS = "yandex:alena" diff --git a/notify/sigma/sigma.go b/notify/sigma/sigma.go index d91b8107d3..314c4d7247 100644 --- a/notify/sigma/sigma.go +++ b/notify/sigma/sigma.go @@ -20,14 +20,13 @@ import ( "fmt" "github.com/go-kit/log" "github.com/pkg/errors" + "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" commoncfg "github.com/prometheus/common/config" "io/ioutil" "net/http" - - "github.com/prometheus/alertmanager/config" - "github.com/prometheus/alertmanager/template" ) // Notifier implements a Notifier for generic sigma. @@ -52,8 +51,19 @@ func New(conf *config.SigmaConfig, t *template.Template, l log.Logger, httpOpts }, nil } -// Request Message defines the JSON object send to Sigma endpoints. -type Request struct { +// RequestSms Message defines the JSON object send to Sigma endpoints. +type RecipientVoice struct { + Include []string `json:"include"` + Exclude []string `json:"exclude"` +} + +type RequestVoice struct { + Recipient RecipientVoice `json:"recipient"` + Type string `json:"type"` + Payload RequestPayload `json:"payload"` +} + +type RequestSms struct { Recipient []string `json:"recipient"` Type string `json:"type"` Payload RequestPayload `json:"payload"` @@ -79,24 +89,44 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) tmplText = notify.TmplText(n.tmpl, data, &err) ) - msg := Request{ - Recipient: n.conf.Recipient, - Type: n.conf.NotificationType, - Payload: RequestPayload{ - Sender: n.conf.SenderName, - Text: tmplText(n.conf.Text), - }, - } - body, err := json.Marshal(msg) - if err != nil { - return false, err + var body []byte + switch n.conf.NotificationType { + case "sms": + msg := RequestSms{ + Recipient: n.conf.Recipient, + Type: n.conf.NotificationType, + Payload: RequestPayload{ + Sender: n.conf.SenderName, + Text: tmplText(n.conf.Text), + }, + } + body, err = json.Marshal(msg) + if err != nil { + return false, err + } + case "voice": + msg := RequestVoice{ + Recipient: RecipientVoice{ + Include: n.conf.Recipient, + }, + Type: n.conf.NotificationType, + Payload: RequestPayload{ + Sender: n.conf.SenderName, + Text: tmplText(n.conf.Text), + }, + } + body, err = json.Marshal(msg) + if err != nil { + return false, err + } } - bodyReader := bytes.NewReader(body) + bodyReader := bytes.NewReader(body) req, err := http.NewRequest("POST", n.conf.URL.String(), bodyReader) if err != nil { return false, errors.Wrap(err, "request error") } + req.Header.Set("Authorization", string(n.conf.APIKey)) req.Header.Set("Content-Type", "application/json") req.WithContext(ctx) @@ -122,6 +152,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } return false, nil + } type Error struct { From 515b2873765bfb3091ce6e203bc7221d00d8023a Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Wed, 12 Jan 2022 17:51:40 +0300 Subject: [PATCH 20/30] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=8B=D1=82=D0=BA=D0=B0=20=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8=20=D1=81=D0=B0?= =?UTF-8?q?=D0=BC=D0=BC=D0=B0=D1=80=D0=B8=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Куча проблем * прикрепил аттачменты * gitignore * Delete .gitignore * asdf * fix config * рабочий ресивер * Vladimir EDITED * рабочая логика(почти) * добавил проверки на резолв, добавил очистку * добавил бесконечный цикл для очистки * Delete slack.tmpl * fix old mistakes * fix gorutine, islomal logic * поправил горутину, логику наполнения массива ТСок, переопределение значений алертов в сторадже * Внес небольшие правки в форматирование и поправил пару багов * add ci * add ci * add ci * почистил конфиг от лишних полей, добавил логику here * fix merge conflicts ci * удалил говнокод * подумать еще над here * ignore tempalte * пытаюсь в блоки * Add blocks * fix url encode * add mentions * fix mentions * дебаг на проде * добавил каст для старых алертов * add divider * удалил старый код * Первая попытка модификации саммари * небольшая косметика * Update summary merger * добавил обрезку длинны саммари до 500 строк Co-authored-by: Vladimir Buyanov --- config/notifiers.go | 2 +- notify/slackV2/blocks.go | 8 +++- notify/slackV2/slackV2.go | 2 +- notify/slackV2/utils.go | 85 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 5 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index 9c1745972b..14fc8fbe32 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -396,7 +396,7 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { type SlackConfigV2 struct { NotifierConfig `yaml:",inline" json:",inline"` - Token string `yaml:"token,omitempty" json:"token,omitempty"` + Token Secret `yaml:"token,omitempty" json:"token,omitempty"` Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` Color string `yaml:"color,omitempty" json:"color,omitempty"` Debug bool `yaml:"debug" json:"debug"` diff --git a/notify/slackV2/blocks.go b/notify/slackV2/blocks.go index 532f5f8ace..6d73835452 100644 --- a/notify/slackV2/blocks.go +++ b/notify/slackV2/blocks.go @@ -127,12 +127,16 @@ func (n *Notifier) formatMessage(data *template.Data) slack.Blocks { if val := getMapValue(data.CommonAnnotations, "summary"); len(val) > 0 { block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", val)}) } else { + summary := make([]string, 0) for _, al := range data.Alerts { if val, ok := al.Annotations["summary"]; ok && len(val) > 0 { - block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", val)}) - break + summary = append(summary, val) } } + summary = mergeSameMessages(summary) + if len(summary) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", cut(strings.Join(summary, ";\n"), 500))}) + } } if val := getMapValue(data.CommonAnnotations, "description"); len(val) > 0 { diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go index da34db4f84..4a1b2438f8 100644 --- a/notify/slackV2/slackV2.go +++ b/notify/slackV2/slackV2.go @@ -45,7 +45,7 @@ type Data struct { // New returns a new Slack notification handler. func New(c *config.SlackConfigV2, t *template.Template, l log.Logger) (*Notifier, error) { - token := c.Token + token := string(c.Token) client := slack.New(token, slack.OptionDebug(c.Debug)) notifier := &Notifier{ diff --git a/notify/slackV2/utils.go b/notify/slackV2/utils.go index 0ec6bcff7a..92d669f485 100644 --- a/notify/slackV2/utils.go +++ b/notify/slackV2/utils.go @@ -1,6 +1,11 @@ package slackV2 -import "github.com/prometheus/alertmanager/template" +import ( + "github.com/prometheus/alertmanager/template" + "unicode/utf8" +) + +const SummaryMessageDiffThreshold = 3 func UniqStr(input []string) []string { u := make([]string, 0, len(input)) @@ -22,3 +27,81 @@ func getMapValue(data template.KV, key string) string { return "" } } + +func levenshteinDistance(s1, s2 string) int { + if len(s1) == 0 { + return utf8.RuneCountInString(s2) + } else if len(s2) == 0 { + return utf8.RuneCountInString(s1) + } else if s1 == s2 { + return 0 + } + + min := func(values ...int) int { + m := values[0] + for _, v := range values { + if v < m { + m = v + } + } + return m + } + r1, r2 := []rune(s1), []rune(s2) + n, m := len(r1), len(r2) + if n > m { + r1, r2 = r2, r1 + n, m = m, n + } + currentRow := make([]int, n+1) + previousRow := make([]int, n+1) + for i := range currentRow { + currentRow[i] = i + } + for i := 1; i <= m; i++ { + for j := range currentRow { + previousRow[j] = currentRow[j] + if j == 0 { + currentRow[j] = i + continue + } else { + currentRow[j] = 0 + } + add, del, change := previousRow[j]+1, currentRow[j-1]+1, previousRow[j-1] + if r1[j-1] != r2[i-1] { + change++ + } + currentRow[j] = min(add, del, change) + } + } + return currentRow[n] +} + +func mergeSameMessages(arr []string) []string { + result := make([]string, 0) + if len(arr) > 0 { + result = append(result, arr[0]) + } + + for _, val := range arr { + differs := 0 + for _, res := range result { + if levenshteinDistance(val, res) > SummaryMessageDiffThreshold { + differs++ + } + } + if differs == len(result) { + result = append(result, val) + } + } + + result = UniqStr(result) + return result +} + +func cut(text string, limit int) string { + runes := []rune(text) + if len(runes) >= limit { + return string(runes[:limit]) + } + return text +} From d84e7a400f7f8ec000705175b6243f78bd0105ef Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Wed, 12 Jan 2022 19:10:46 +0300 Subject: [PATCH 21/30] Itt 0/feature/fix alerts (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Куча проблем * прикрепил аттачменты * gitignore * Delete .gitignore * asdf * fix config * рабочий ресивер * Vladimir EDITED * рабочая логика(почти) * добавил проверки на резолв, добавил очистку * добавил бесконечный цикл для очистки * Delete slack.tmpl * fix old mistakes * fix gorutine, islomal logic * поправил горутину, логику наполнения массива ТСок, переопределение значений алертов в сторадже * Внес небольшие правки в форматирование и поправил пару багов * add ci * add ci * add ci * почистил конфиг от лишних полей, добавил логику here * fix merge conflicts ci * удалил говнокод * подумать еще над here * ignore tempalte * пытаюсь в блоки * Add blocks * fix url encode * add mentions * fix mentions * дебаг на проде * добавил каст для старых алертов * add divider * удалил старый код * Первая попытка модификации саммари * небольшая косметика * Update summary merger * добавил обрезку длинны саммари до 500 строк * поправил небольшую опечатку Co-authored-by: Vladimir Buyanov --- notify/slackV2/blocks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notify/slackV2/blocks.go b/notify/slackV2/blocks.go index 6d73835452..b363c26251 100644 --- a/notify/slackV2/blocks.go +++ b/notify/slackV2/blocks.go @@ -109,7 +109,7 @@ func (n *Notifier) formatMessage(data *template.Data) slack.Blocks { if len(firing) > 0 && len(resolved) > 0 { fields := make([]*Field, 0) fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Firing:* `%s`", strings.Join(firing, ", "))}) - fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Resolved:* `%s`", strings.Join(firing, ", "))}) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Resolved:* `%s`", strings.Join(resolved, ", "))}) blocks = append(blocks, Block{Type: slack.MBTSection, Fields: fields}) } else { fields := make([]*Field, 0) From d0d2e718457475122d0e1b0fce3c63784d26d105 Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Mon, 23 May 2022 11:30:37 +0300 Subject: [PATCH 22/30] new grafana notify (#16) * new grafana notify * working grafana alerts * fix mistakes * blocks update * change image h*w * update orgid feild * fix id remove grafana host * small fixes, remove channel override --- config/notifiers.go | 3 + go.mod | 4 +- go.sum | 10 ++ notify/slackV2/blocks.go | 3 + notify/slackV2/grafana.go | 313 ++++++++++++++++++++++++++++++++++++++ notify/slackV2/slackV2.go | 20 ++- 6 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 notify/slackV2/grafana.go diff --git a/config/notifiers.go b/config/notifiers.go index 14fc8fbe32..d66f9a79dd 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -397,6 +397,9 @@ type SlackConfigV2 struct { NotifierConfig `yaml:",inline" json:",inline"` Token Secret `yaml:"token,omitempty" json:"token,omitempty"` + GrafanaToken Secret `yaml:"grafana_token,omitempty" json:"grafana_token,omitempty"` + UserToken Secret `yaml:"user_token,omitempty" json:"user_token,omitempty"` + GrafanaHost string `yaml:"grafana_host,omitempty" json:"grafana_host,omitempty"` Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` Color string `yaml:"color,omitempty" json:"color,omitempty"` Debug bool `yaml:"debug" json:"debug"` diff --git a/go.mod b/go.mod index 5e09622567..11d50285df 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-openapi/validate v0.20.2 github.com/gofrs/uuid v4.0.0+incompatible github.com/gogo/protobuf v1.3.2 + github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/go-sockaddr v1.0.2 github.com/hashicorp/go-uuid v1.0.1 // indirect github.com/hashicorp/golang-lru v0.5.4 @@ -30,9 +31,10 @@ require ( github.com/prometheus/common/sigv4 v0.1.0 github.com/prometheus/exporter-toolkit v0.7.0 github.com/rs/cors v1.8.0 + github.com/satori/go.uuid v1.2.0 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 - github.com/slack-go/slack v0.10.0 + github.com/slack-go/slack v0.10.4-0.20220503131901-0e14c9d4a15c github.com/stretchr/testify v1.7.0 github.com/xlab/treeprint v1.1.0 go.uber.org/atomic v1.9.0 diff --git a/go.sum b/go.sum index e9c45522f9..67c04187fd 100644 --- a/go.sum +++ b/go.sum @@ -273,6 +273,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -291,6 +293,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= @@ -435,6 +439,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -449,6 +455,10 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/slack-go/slack v0.10.0 h1:L16Eqg3QZzRKGXIVsFSZdJdygjOphb2FjRUwH6VrFu8= github.com/slack-go/slack v0.10.0/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= +github.com/slack-go/slack v0.10.3 h1:kKYwlKY73AfSrtAk9UHWCXXfitudkDztNI9GYBviLxw= +github.com/slack-go/slack v0.10.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= +github.com/slack-go/slack v0.10.4-0.20220503131901-0e14c9d4a15c h1:fOqgV3BG04rDivs8IKXQATLgQHfsWaOfYu4XZK3XNMM= +github.com/slack-go/slack v0.10.4-0.20220503131901-0e14c9d4a15c/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/notify/slackV2/blocks.go b/notify/slackV2/blocks.go index b363c26251..3d1d02b63e 100644 --- a/notify/slackV2/blocks.go +++ b/notify/slackV2/blocks.go @@ -21,11 +21,14 @@ type Field struct { Type string `json:"type"` Text string `json:"text"` } + type Block struct { Type slack.MessageBlockType `json:"type"` Text *Text `json:"text,omitempty"` Fields []*Field `json:"fields,omitempty"` Elements []*Element `json:"elements,omitempty"` + ImageURL string `json:"image_url,omitempty"` + AltText string `json:"alt_text,omitempty"` } func (b Block) BlockType() slack.MessageBlockType { diff --git a/notify/slackV2/grafana.go b/notify/slackV2/grafana.go new file mode 100644 index 0000000000..d88c582a2e --- /dev/null +++ b/notify/slackV2/grafana.go @@ -0,0 +1,313 @@ +package slackV2 + +import ( + "fmt" + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/common/model" + "github.com/satori/go.uuid" + "github.com/slack-go/slack" + "net/http" + url2 "net/url" + "strconv" + "strings" + "time" +) + +func genGrafanaRenderUrl(dash string, panel string, org string, host string) string { + + const unixMinute = 1000 * 60 + const unixSec = 1000 + const imageWidth = "999" + const imageHeight = "333" + const timeZone = "Europe/Moscow" + const urlPath = "/render/d-solo/" + const urlScheme = "https" + + url := "" + + if u, err := url2.Parse(""); err == nil { + u.Scheme = urlScheme + u.Host = host + u.Path = urlPath + dash + "/" + q := u.Query() + q.Set("orgId", org) + q.Set("from", strconv.FormatInt(time.Now().UnixMilli()-(unixMinute*60), 10)) + q.Set("to", strconv.FormatInt(time.Now().UnixMilli()-(unixSec*10), 10)) + q.Set("panelId", panel) + q.Set("width", imageWidth) + q.Set("height", imageHeight) + q.Set("tz", timeZone) + u.RawQuery = q.Encode() + url = u.String() + } + return url +} +func genGrafanaUrl(dash string, panel string, org string, host string) string { + + const urlScheme = "https" + + DashUrl := "" + + if u, err := url2.Parse(""); err == nil { + u.Scheme = urlScheme + u.Host = host + u.Path = "d/" + dash + q := u.Query() + q.Set("orgId", org) + if panel != "" { + q.Set("viewPanel", panel) + } + u.RawQuery = q.Encode() + DashUrl = u.String() + } + return DashUrl +} +func urlMerger(kUrl string, pUrl string) string { + imageLink := "" + key := "" + if u, err := url2.Parse(kUrl); err == nil { + key = u.Path + } + + trunc := []rune(key) + key = string(trunc[len(trunc)-10:]) + + if u2, err := url2.Parse(pUrl); err == nil { + q := u2.Query() + q.Set("pub_secret", key) + u2.RawQuery = q.Encode() + imageLink = u2.String() + } + return imageLink +} + +func getUploadedImageUrl(url string, token config.Secret, grafanaToken config.Secret) string { + + client := &http.Client{} + req, err := http.NewRequest("GET", url, nil) + req.Header.Set("Authorization", "Bearer "+string(grafanaToken)) + response, err := client.Do(req) + + defer response.Body.Close() + + if err != nil { + fmt.Printf("invalid req: %v\n", err) + return "" + } + + if response.StatusCode != 200 { + fmt.Printf("received non 200 response code, code: %v\n", response.StatusCode) + return "" + } + + uuid := uuid.NewV4() + fileName := strings.Replace(uuid.String(), "-", "", -1) + api := slack.New(string(token)) + params := slack.FileUploadParameters{ + Reader: response.Body, + Filetype: "jpg", + Filename: fileName + ".jpg", + } + image, err := api.UploadFile(params) + + if err != nil { + fmt.Printf("UPLOAD ERROR. Name: %s\n", image.Name) + return "" + } + sharedUrl, _, _, err := api.ShareFilePublicURL(image.ID) + + if err != nil { + fmt.Printf("SharedError :%v\n", sharedUrl) + return "" + } + + imageUrl := urlMerger(sharedUrl.PermalinkPublic, sharedUrl.URLPrivate) + + return imageUrl + +} + +func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { + + dashboardUid := "" + panelId := "" + orgId := "" + //grafanaValues := "" + runBook := "" + firing := make([]string, 0) + resolved := make([]string, 0) + severity := make([]string, 0) + envs := make([]string, 0) + blocks := make([]slack.Block, 0) + + for _, alert := range data.Alerts { + for _, v := range alert.Labels.SortedPairs() { + switch v.Name { + case "host_name": + switch model.AlertStatus(alert.Status) { + case model.AlertFiring: + firing = append(firing, v.Value) + case model.AlertResolved: + resolved = append(resolved, v.Value) + } + case "severity": + severity = append(severity, v.Value) + case "env": + envs = append(envs, v.Value) + } + } + for _, v := range alert.Annotations.SortedPairs() { + switch v.Name { + + case "__dashboardUid__": + dashboardUid = v.Value + case "__panelId__": + panelId = v.Value + case "orgid": + orgId = v.Value + //case "__value_string__": + // grafanaValues = v.Value + case "runbook_url": + runBook = v.Value + } + } + } + + severity = UniqStr(severity) + resolved = UniqStr(resolved) + firing = UniqStr(firing) + envs = UniqStr(envs) + + grafanaDashUrl := genGrafanaUrl(dashboardUid, "", orgId, n.conf.GrafanaHost) + grafanaPanelUrl := genGrafanaUrl(dashboardUid, panelId, orgId, n.conf.GrafanaHost) + grafanaImageUrl := genGrafanaRenderUrl(dashboardUid, panelId, orgId, n.conf.GrafanaHost) + slackImageUrl := getUploadedImageUrl(grafanaImageUrl, n.conf.UserToken, n.conf.GrafanaToken) + + { + url := "" + if urlParsed, err := url2.Parse(data.ExternalURL); err == nil { + urlParsed.Path = "/#/silences/new" + args := urlParsed.Query() + filters := make([]string, 0) + for _, v := range data.CommonLabels.SortedPairs() { + filters = append(filters, fmt.Sprintf("%s=\"%s\"", v.Name, v.Value)) + } + args.Add("filter", fmt.Sprintf("{%s}", strings.Join(filters, ","))) + urlParsed.RawQuery = args.Encode() + url = urlParsed.String() + url = strings.Replace(url, "%23", "#", 1) + } + + alertEditUrl := "" + for _, alert := range data.Alerts { + if alert.GeneratorURL != "" { + alertEditUrl = alert.GeneratorURL + "?orgId=" + orgId + break + } + } + + //Header + blocks = append(blocks, Block{Type: slack.MBTHeader, Text: &Text{Type: slack.PlainTextType, Text: getMapValue(data.CommonLabels, "alertname")}}) + + //Divider + //blocks = append(blocks, Block{Type: slack.MBTDivider}) + + //Env and severity + fields := make([]*Field, 0) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Env: %s*", strings.ToUpper(strings.Join(envs, ", ")))}) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Severety: %s*", strings.ToUpper(strings.Join(severity, ", ")))}) + + //Buttons + if grafanaPanelUrl != "" { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:chart_with_upwards_trend:Panel>*", grafanaPanelUrl)}) + } else { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf(":chart_with_upwards_trend:~Panel~")}) + } + + if url != "" { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:no_bell:Silence>*", url)}) + } else { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:no_bell:Silence>*", url)}) + } + if grafanaDashUrl != "" { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:dashboard:Dash>*", grafanaDashUrl)}) + } else { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf(":dashboard:~Dash~")}) + } + if alertEditUrl != "" { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:gear:Edit>*", alertEditUrl)}) + } else { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*:gear:~Edit~")}) + } + + blocks = append(blocks, Block{Type: slack.MBTSection, Fields: fields}) + } + + //Firing > Resolved + if len(firing) > 0 && len(resolved) > 0 { + fields := make([]*Field, 0) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Firing:* `%s`", strings.Join(firing, ", "))}) + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Resolved:* `%s`", strings.Join(resolved, ", "))}) + blocks = append(blocks, Block{Type: slack.MBTSection, Fields: fields}) + } else { + fields := make([]*Field, 0) + if len(resolved) > 0 { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Resolved: *`%s`", strings.Join(resolved, ", "))}) + } else { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Firing: *`%s`", strings.Join(firing, ", "))}) + } + blocks = append(blocks, Block{Type: slack.MBTSection, Fields: fields}) + } + + //GrafanaImage + if slackImageUrl != "" { + blocks = append(blocks, Block{Type: slack.MBTImage, ImageURL: slackImageUrl, AltText: "inspiration"}) + } + + //Summary and description + { + block := Block{Type: slack.MBTContext, Elements: make([]*Element, 0)} + + if val := getMapValue(data.CommonAnnotations, "description"); len(val) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s\n\n", val)}) + } else { + for _, al := range data.Alerts { + if val, ok := al.Annotations["description"]; ok && len(val) > 0 { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s\n\n", val)}) + break + } + } + } + + if val := getMapValue(data.CommonAnnotations, "summary"); len(val) > 0 { + if runBook != "" { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|Summary:>* %s", runBook, val)}) + } else { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", val)}) + } + } else { + summary := make([]string, 0) + for _, al := range data.Alerts { + if val, ok := al.Annotations["summary"]; ok && len(val) > 0 { + summary = append(summary, val) + } + } + summary = mergeSameMessages(summary) + if len(summary) > 0 { + if runBook != "" { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|Summary:>* %s", runBook, cut(strings.Join(summary, ";\n"), 500))}) + } else { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Summary:* %s", cut(strings.Join(summary, ";\n"), 500))}) + } + } + } + + if len(block.Elements) > 0 { + blocks = append(blocks, block) + } + } + + result := slack.Blocks{BlockSet: blocks} + return result +} diff --git a/notify/slackV2/slackV2.go b/notify/slackV2/slackV2.go index 4a1b2438f8..7f374439c4 100644 --- a/notify/slackV2/slackV2.go +++ b/notify/slackV2/slackV2.go @@ -133,9 +133,19 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) func (n *Notifier) send(data *template.Data, ts string) (string, error) { - attachment := slack.Attachment{ - Color: n.conf.Color, - Blocks: n.formatMessage(data), + channel := n.conf.Channel + attachment := slack.Attachment{} + + if n.conf.GrafanaToken != "" { + attachment = slack.Attachment{ + Color: n.conf.Color, + Blocks: n.formatGrafanaMessage(data), + } + } else { + attachment = slack.Attachment{ + Color: n.conf.Color, + Blocks: n.formatMessage(data), + } } if len(data.Alerts.Firing()) == 0 { @@ -145,10 +155,10 @@ func (n *Notifier) send(data *template.Data, ts string) (string, error) { att := slack.MsgOptionAttachments(attachment) if ts != "" { - _, _, messageTs, err := n.client.UpdateMessage(n.conf.Channel, ts, att) + _, _, messageTs, err := n.client.UpdateMessage(channel, ts, att) return messageTs, err } else { - _, messageTs, err := n.client.PostMessage(n.conf.Channel, att) + _, messageTs, err := n.client.PostMessage(channel, att) return messageTs, err } } From 894b08977323285445799681b2c4bd3ef084689d Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov <81759784+cl-bvl@users.noreply.github.com> Date: Mon, 23 May 2022 13:55:15 +0300 Subject: [PATCH 23/30] Small improvements (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * new grafana notify * working grafana alerts * fix mistakes * blocks update * change image h*w * update orgid feild * fix id remove grafana host * small fixes, remove channel override * Причесал код * Small fixes Co-authored-by: Aleksandr Borisov --- config/notifiers.go | 3 +- notify/slackV2/grafana.go | 170 +++++++++++++++++++------------------- 2 files changed, 87 insertions(+), 86 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index d66f9a79dd..01e0c1c0ea 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -399,7 +399,8 @@ type SlackConfigV2 struct { Token Secret `yaml:"token,omitempty" json:"token,omitempty"` GrafanaToken Secret `yaml:"grafana_token,omitempty" json:"grafana_token,omitempty"` UserToken Secret `yaml:"user_token,omitempty" json:"user_token,omitempty"` - GrafanaHost string `yaml:"grafana_host,omitempty" json:"grafana_host,omitempty"` + GrafanaUrl string `yaml:"grafana_url,omitempty" json:"grafana_url,omitempty"` + GrafanaTZ string `yaml:"grafana_tz,omitempty" json:"grafana_tz,omitempty"` Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` Color string `yaml:"color,omitempty" json:"color,omitempty"` Debug bool `yaml:"debug" json:"debug"` diff --git a/notify/slackV2/grafana.go b/notify/slackV2/grafana.go index d88c582a2e..7bc469e398 100644 --- a/notify/slackV2/grafana.go +++ b/notify/slackV2/grafana.go @@ -9,127 +9,131 @@ import ( "github.com/slack-go/slack" "net/http" url2 "net/url" + "path" "strconv" "strings" "time" ) -func genGrafanaRenderUrl(dash string, panel string, org string, host string) string { +func genGrafanaRenderUrl(grafanaUrl string, grafanaTZ string, org string, dash string, panel string) (string, error) { - const unixMinute = 1000 * 60 - const unixSec = 1000 + const fromShift = -time.Hour + const toShift = -time.Second * 10 const imageWidth = "999" const imageHeight = "333" - const timeZone = "Europe/Moscow" const urlPath = "/render/d-solo/" - const urlScheme = "https" - - url := "" - - if u, err := url2.Parse(""); err == nil { - u.Scheme = urlScheme - u.Host = host - u.Path = urlPath + dash + "/" - q := u.Query() - q.Set("orgId", org) - q.Set("from", strconv.FormatInt(time.Now().UnixMilli()-(unixMinute*60), 10)) - q.Set("to", strconv.FormatInt(time.Now().UnixMilli()-(unixSec*10), 10)) - q.Set("panelId", panel) - q.Set("width", imageWidth) - q.Set("height", imageHeight) - q.Set("tz", timeZone) - u.RawQuery = q.Encode() - url = u.String() + + if grafanaUrl == "" { + return "", fmt.Errorf("grafanaUrl is empty") } - return url + + u, err := url2.Parse(grafanaUrl) + if err != nil { + return "", err + } + + u.Path = path.Join(u.Path, urlPath, dash) + q := u.Query() + q.Set("orgId", org) + q.Set("from", strconv.Itoa(int(time.Now().Add(fromShift).UnixMilli()))) + q.Set("to", strconv.Itoa(int(time.Now().Add(toShift).UnixMilli()))) + q.Set("panelId", panel) + q.Set("width", imageWidth) + q.Set("height", imageHeight) + q.Set("tz", grafanaTZ) + u.RawQuery = q.Encode() + return u.String(), nil + } -func genGrafanaUrl(dash string, panel string, org string, host string) string { - const urlScheme = "https" +func genGrafanaUrl(grafanaUrl string, org string, dash string, panel string) (string, error) { - DashUrl := "" + if grafanaUrl == "" { + return "", fmt.Errorf("grafanaUrl is empty") + } - if u, err := url2.Parse(""); err == nil { - u.Scheme = urlScheme - u.Host = host - u.Path = "d/" + dash - q := u.Query() - q.Set("orgId", org) - if panel != "" { - q.Set("viewPanel", panel) - } - u.RawQuery = q.Encode() - DashUrl = u.String() + u, err := url2.Parse(grafanaUrl) + if err != nil { + return "", err } - return DashUrl + + u.Path = path.Join(u.Path, "/d/"+dash) + q := u.Query() + q.Set("orgId", org) + if panel != "" { + q.Set("viewPanel", panel) + } + u.RawQuery = q.Encode() + return u.String(), nil } -func urlMerger(kUrl string, pUrl string) string { - imageLink := "" - key := "" - if u, err := url2.Parse(kUrl); err == nil { - key = u.Path + +func urlMerger(publicUrl string, privateUrl string) (string, error) { + u, err := url2.Parse(publicUrl) + if err != nil { + return "", err } - trunc := []rune(key) - key = string(trunc[len(trunc)-10:]) + trunc := []rune(u.Path) + key := string(trunc[len(trunc)-10:]) - if u2, err := url2.Parse(pUrl); err == nil { - q := u2.Query() - q.Set("pub_secret", key) - u2.RawQuery = q.Encode() - imageLink = u2.String() + u, err = url2.Parse(privateUrl) + if err != nil { + return "", err } - return imageLink -} + q := u.Query() + q.Set("pub_secret", key) + u.RawQuery = q.Encode() -func getUploadedImageUrl(url string, token config.Secret, grafanaToken config.Secret) string { + return u.String(), nil +} +func getUploadedImageUrl(url string, token config.Secret, grafanaToken config.Secret) (string, error) { + const imageExtension = "jpg" client := &http.Client{} req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", err + } req.Header.Set("Authorization", "Bearer "+string(grafanaToken)) - response, err := client.Do(req) - - defer response.Body.Close() + response, err := client.Do(req) if err != nil { - fmt.Printf("invalid req: %v\n", err) - return "" + return "", err } + defer response.Body.Close() - if response.StatusCode != 200 { - fmt.Printf("received non 200 response code, code: %v\n", response.StatusCode) - return "" + if response.StatusCode != http.StatusOK { + return "", fmt.Errorf("request status code %d != %d", response.StatusCode, http.StatusOK) } - uuid := uuid.NewV4() - fileName := strings.Replace(uuid.String(), "-", "", -1) + fileName := fmt.Sprintf("%s.%s", strings.Replace(uuid.NewV4().String(), "-", "", -1), imageExtension) api := slack.New(string(token)) params := slack.FileUploadParameters{ Reader: response.Body, Filetype: "jpg", - Filename: fileName + ".jpg", + Filename: fileName, } - image, err := api.UploadFile(params) + image, err := api.UploadFile(params) if err != nil { - fmt.Printf("UPLOAD ERROR. Name: %s\n", image.Name) - return "" + return "", fmt.Errorf("upload error, image: %s, error: %w", image.Name, err) } - sharedUrl, _, _, err := api.ShareFilePublicURL(image.ID) + sharedUrl, _, _, err := api.ShareFilePublicURL(image.ID) if err != nil { - fmt.Printf("SharedError :%v\n", sharedUrl) - return "" + return "", fmt.Errorf("share error: %w", err) } - imageUrl := urlMerger(sharedUrl.PermalinkPublic, sharedUrl.URLPrivate) + imageUrl, err := urlMerger(sharedUrl.PermalinkPublic, sharedUrl.URLPrivate) + if err != nil { + return "", fmt.Errorf("url merge error: %w", err) + } - return imageUrl + return imageUrl, nil } func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { - dashboardUid := "" panelId := "" orgId := "" @@ -159,7 +163,6 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { } for _, v := range alert.Annotations.SortedPairs() { switch v.Name { - case "__dashboardUid__": dashboardUid = v.Value case "__panelId__": @@ -179,11 +182,6 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { firing = UniqStr(firing) envs = UniqStr(envs) - grafanaDashUrl := genGrafanaUrl(dashboardUid, "", orgId, n.conf.GrafanaHost) - grafanaPanelUrl := genGrafanaUrl(dashboardUid, panelId, orgId, n.conf.GrafanaHost) - grafanaImageUrl := genGrafanaRenderUrl(dashboardUid, panelId, orgId, n.conf.GrafanaHost) - slackImageUrl := getUploadedImageUrl(grafanaImageUrl, n.conf.UserToken, n.conf.GrafanaToken) - { url := "" if urlParsed, err := url2.Parse(data.ExternalURL); err == nil { @@ -219,8 +217,8 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*Severety: %s*", strings.ToUpper(strings.Join(severity, ", ")))}) //Buttons - if grafanaPanelUrl != "" { - fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:chart_with_upwards_trend:Panel>*", grafanaPanelUrl)}) + if url, err := genGrafanaUrl(n.conf.GrafanaUrl, orgId, dashboardUid, panelId); err == nil { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:chart_with_upwards_trend:Panel>*", url)}) } else { fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf(":chart_with_upwards_trend:~Panel~")}) } @@ -230,8 +228,8 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { } else { fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:no_bell:Silence>*", url)}) } - if grafanaDashUrl != "" { - fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:dashboard:Dash>*", grafanaDashUrl)}) + if url, err := genGrafanaUrl(n.conf.GrafanaUrl, orgId, dashboardUid, ""); err == nil { + fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf("*<%s|:dashboard:Dash>*", url)}) } else { fields = append(fields, &Field{Type: slack.MarkdownType, Text: fmt.Sprintf(":dashboard:~Dash~")}) } @@ -261,8 +259,10 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { } //GrafanaImage - if slackImageUrl != "" { - blocks = append(blocks, Block{Type: slack.MBTImage, ImageURL: slackImageUrl, AltText: "inspiration"}) + if imageUrl, err := genGrafanaRenderUrl(n.conf.GrafanaUrl, n.conf.GrafanaTZ, orgId, dashboardUid, panelId); err == nil { + if slackImageUrl, err := getUploadedImageUrl(imageUrl, n.conf.UserToken, n.conf.GrafanaToken); err == nil { + blocks = append(blocks, Block{Type: slack.MBTImage, ImageURL: slackImageUrl, AltText: "inspiration"}) + } } //Summary and description From c0edbca775d8b7dfc317f83b4d75baa9f7bd26f6 Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Thu, 7 Jul 2022 13:10:22 +0300 Subject: [PATCH 24/30] added grafana metrics (#18) --- notify/slackV2/grafana.go | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/notify/slackV2/grafana.go b/notify/slackV2/grafana.go index 7bc469e398..99bbc1d9c3 100644 --- a/notify/slackV2/grafana.go +++ b/notify/slackV2/grafana.go @@ -10,6 +10,7 @@ import ( "net/http" url2 "net/url" "path" + "regexp" "strconv" "strings" "time" @@ -137,7 +138,7 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { dashboardUid := "" panelId := "" orgId := "" - //grafanaValues := "" + grafanaValues := "" runBook := "" firing := make([]string, 0) resolved := make([]string, 0) @@ -169,8 +170,8 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { panelId = v.Value case "orgid": orgId = v.Value - //case "__value_string__": - // grafanaValues = v.Value + case "__value_string__": + grafanaValues = v.Value case "runbook_url": runBook = v.Value } @@ -265,16 +266,31 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { } } - //Summary and description + //Summary Description and metrics { block := Block{Type: slack.MBTContext, Elements: make([]*Element, 0)} + if (grafanaValues != "[no value]") || (grafanaValues != "") { + regexpForParseMetric := regexp.MustCompile(`(?m) labels={[a-zA-z0-9=:,_@{ -.]+} value=`) + valueStringCollection := regexpForParseMetric.ReplaceAllString(grafanaValues, ", value=") + regexpForParseParams := regexp.MustCompile(`(?m)metric='(?P.*)', value=(?P.*)`) + + grafanaMapParams := make(map[string]string) + for _, parsedCollection := range strings.Split(valueStringCollection, "], [ ") { + match := regexpForParseParams.FindStringSubmatch(parsedCollection) + if len(match) >= 3 { + grafanaMapParams[match[1]] = match[2] + } + } + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Metric:* %s\n", valueStringCollection)}) + } + if val := getMapValue(data.CommonAnnotations, "description"); len(val) > 0 { - block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s\n\n", val)}) + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s\n", val)}) } else { for _, al := range data.Alerts { if val, ok := al.Annotations["description"]; ok && len(val) > 0 { - block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s\n\n", val)}) + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Description:* %s\n", val)}) break } } From 75c65d551107c422047493cd20d309c5f89541ba Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Tue, 12 Jul 2022 13:13:39 +0300 Subject: [PATCH 25/30] Make Annotations case insentive --- notify/slackV2/grafana.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/notify/slackV2/grafana.go b/notify/slackV2/grafana.go index 99bbc1d9c3..387a918558 100644 --- a/notify/slackV2/grafana.go +++ b/notify/slackV2/grafana.go @@ -163,16 +163,16 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { } } for _, v := range alert.Annotations.SortedPairs() { - switch v.Name { - case "__dashboardUid__": + switch strings.ToLower(v.Name) { + case strings.ToLower("__dashboardUid__"): dashboardUid = v.Value - case "__panelId__": + case strings.ToLower("__panelId__"): panelId = v.Value - case "orgid": + case strings.ToLower("OrgID"): orgId = v.Value - case "__value_string__": + case strings.ToLower("__value_string__"): grafanaValues = v.Value - case "runbook_url": + case strings.ToLower("runbook_url"): runBook = v.Value } } From 79923f28a3c1258b7a2a5dc6a1c442009410111d Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Tue, 12 Jul 2022 13:18:46 +0300 Subject: [PATCH 26/30] Update golang image --- ci/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile index afda679014..4ef7dd72a1 100644 --- a/ci/docker/Dockerfile +++ b/ci/docker/Dockerfile @@ -1,5 +1,5 @@ # Build image -FROM nexus.adsrv.wtf/click/golang:1.17.1-buster as build +FROM nexus.adsrv.wtf/click/golang:1.18.3-202206230758 as build WORKDIR /app COPY ./ ./ From bcf026fc36aee044989fcdd61dfc5549cadae4e0 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Wed, 17 Aug 2022 13:57:11 +0300 Subject: [PATCH 27/30] Twilio integration --- api/v2/client/alert/alert_client.go | 4 +- api/v2/client/alert/get_alerts_parameters.go | 3 +- api/v2/client/alert/get_alerts_responses.go | 9 +- api/v2/client/alert/post_alerts_parameters.go | 3 +- api/v2/client/alert/post_alerts_responses.go | 9 +- api/v2/client/alertgroup/alertgroup_client.go | 2 +- .../alertgroup/get_alert_groups_parameters.go | 3 +- .../alertgroup/get_alert_groups_responses.go | 9 +- api/v2/client/general/general_client.go | 2 +- .../client/general/get_status_parameters.go | 3 +- api/v2/client/general/get_status_responses.go | 3 +- .../receiver/get_receivers_parameters.go | 3 +- .../receiver/get_receivers_responses.go | 3 +- api/v2/client/receiver/receiver_client.go | 2 +- .../silence/delete_silence_parameters.go | 3 +- .../silence/delete_silence_responses.go | 6 +- .../client/silence/get_silence_parameters.go | 3 +- .../client/silence/get_silence_responses.go | 9 +- .../client/silence/get_silences_parameters.go | 3 +- .../client/silence/get_silences_responses.go | 6 +- .../silence/post_silences_parameters.go | 3 +- .../client/silence/post_silences_responses.go | 12 +- api/v2/client/silence/silence_client.go | 8 +- api/v2/restapi/doc.go | 22 ++-- api/v2/restapi/operations/alert/get_alerts.go | 4 +- .../operations/alert/get_alerts_responses.go | 9 +- .../restapi/operations/alert/post_alerts.go | 4 +- .../operations/alert/post_alerts_responses.go | 9 +- .../operations/alertgroup/get_alert_groups.go | 4 +- .../alertgroup/get_alert_groups_responses.go | 9 +- .../restapi/operations/general/get_status.go | 4 +- .../general/get_status_responses.go | 3 +- .../operations/receiver/get_receivers.go | 4 +- .../receiver/get_receivers_responses.go | 3 +- .../operations/silence/delete_silence.go | 4 +- .../silence/delete_silence_responses.go | 6 +- .../restapi/operations/silence/get_silence.go | 4 +- .../silence/get_silence_responses.go | 9 +- .../operations/silence/get_silences.go | 4 +- .../silence/get_silences_responses.go | 6 +- .../operations/silence/post_silences.go | 4 +- .../silence/post_silences_responses.go | 9 +- cmd/alertmanager/main.go | 18 +++ config/config.go | 1 + config/notifiers.go | 70 ++++++++-- go.mod | 1 + go.sum | 12 +- notify/twilio/main.go | 123 ++++++++++++++++++ notify/twilio/storage.go | 56 ++++++++ pkg/labels/matcher_test.go | 6 +- pkg/labels/parse.go | 11 +- scripts/test_alert.sh | 4 + store/store.go | 2 +- timeinterval/timeinterval.go | 2 +- 54 files changed, 420 insertions(+), 118 deletions(-) create mode 100644 notify/twilio/main.go create mode 100644 notify/twilio/storage.go create mode 100755 scripts/test_alert.sh diff --git a/api/v2/client/alert/alert_client.go b/api/v2/client/alert/alert_client.go index 24cbe8c950..4dfa2564be 100644 --- a/api/v2/client/alert/alert_client.go +++ b/api/v2/client/alert/alert_client.go @@ -49,7 +49,7 @@ type ClientService interface { } /* - GetAlerts Get a list of alerts +GetAlerts Get a list of alerts */ func (a *Client) GetAlerts(params *GetAlertsParams) (*GetAlertsOK, error) { // TODO: Validate the params before sending @@ -83,7 +83,7 @@ func (a *Client) GetAlerts(params *GetAlertsParams) (*GetAlertsOK, error) { } /* - PostAlerts Create new Alerts +PostAlerts Create new Alerts */ func (a *Client) PostAlerts(params *PostAlertsParams) (*PostAlertsOK, error) { // TODO: Validate the params before sending diff --git a/api/v2/client/alert/get_alerts_parameters.go b/api/v2/client/alert/get_alerts_parameters.go index de05f6889e..03d04d54fc 100644 --- a/api/v2/client/alert/get_alerts_parameters.go +++ b/api/v2/client/alert/get_alerts_parameters.go @@ -106,7 +106,8 @@ func NewGetAlertsParamsWithHTTPClient(client *http.Client) *GetAlertsParams { } } -/*GetAlertsParams contains all the parameters to send to the API endpoint +/* +GetAlertsParams contains all the parameters to send to the API endpoint for the get alerts operation typically these are written to a http.Request */ type GetAlertsParams struct { diff --git a/api/v2/client/alert/get_alerts_responses.go b/api/v2/client/alert/get_alerts_responses.go index 0fd6e282be..d5e5c074fe 100644 --- a/api/v2/client/alert/get_alerts_responses.go +++ b/api/v2/client/alert/get_alerts_responses.go @@ -66,7 +66,8 @@ func NewGetAlertsOK() *GetAlertsOK { return &GetAlertsOK{} } -/*GetAlertsOK handles this case with default header values. +/* +GetAlertsOK handles this case with default header values. Get alerts response */ @@ -97,7 +98,8 @@ func NewGetAlertsBadRequest() *GetAlertsBadRequest { return &GetAlertsBadRequest{} } -/*GetAlertsBadRequest handles this case with default header values. +/* +GetAlertsBadRequest handles this case with default header values. Bad request */ @@ -128,7 +130,8 @@ func NewGetAlertsInternalServerError() *GetAlertsInternalServerError { return &GetAlertsInternalServerError{} } -/*GetAlertsInternalServerError handles this case with default header values. +/* +GetAlertsInternalServerError handles this case with default header values. Internal server error */ diff --git a/api/v2/client/alert/post_alerts_parameters.go b/api/v2/client/alert/post_alerts_parameters.go index 3e5c99805a..a81735b1c7 100644 --- a/api/v2/client/alert/post_alerts_parameters.go +++ b/api/v2/client/alert/post_alerts_parameters.go @@ -71,7 +71,8 @@ func NewPostAlertsParamsWithHTTPClient(client *http.Client) *PostAlertsParams { } } -/*PostAlertsParams contains all the parameters to send to the API endpoint +/* +PostAlertsParams contains all the parameters to send to the API endpoint for the post alerts operation typically these are written to a http.Request */ type PostAlertsParams struct { diff --git a/api/v2/client/alert/post_alerts_responses.go b/api/v2/client/alert/post_alerts_responses.go index 693efd5b19..46a3ab6f61 100644 --- a/api/v2/client/alert/post_alerts_responses.go +++ b/api/v2/client/alert/post_alerts_responses.go @@ -64,7 +64,8 @@ func NewPostAlertsOK() *PostAlertsOK { return &PostAlertsOK{} } -/*PostAlertsOK handles this case with default header values. +/* +PostAlertsOK handles this case with default header values. Create alerts response */ @@ -85,7 +86,8 @@ func NewPostAlertsBadRequest() *PostAlertsBadRequest { return &PostAlertsBadRequest{} } -/*PostAlertsBadRequest handles this case with default header values. +/* +PostAlertsBadRequest handles this case with default header values. Bad request */ @@ -116,7 +118,8 @@ func NewPostAlertsInternalServerError() *PostAlertsInternalServerError { return &PostAlertsInternalServerError{} } -/*PostAlertsInternalServerError handles this case with default header values. +/* +PostAlertsInternalServerError handles this case with default header values. Internal server error */ diff --git a/api/v2/client/alertgroup/alertgroup_client.go b/api/v2/client/alertgroup/alertgroup_client.go index f7c4a1faa7..ca81de0fa3 100644 --- a/api/v2/client/alertgroup/alertgroup_client.go +++ b/api/v2/client/alertgroup/alertgroup_client.go @@ -47,7 +47,7 @@ type ClientService interface { } /* - GetAlertGroups Get a list of alert groups +GetAlertGroups Get a list of alert groups */ func (a *Client) GetAlertGroups(params *GetAlertGroupsParams) (*GetAlertGroupsOK, error) { // TODO: Validate the params before sending diff --git a/api/v2/client/alertgroup/get_alert_groups_parameters.go b/api/v2/client/alertgroup/get_alert_groups_parameters.go index b76d0b1b94..767bd61c6c 100644 --- a/api/v2/client/alertgroup/get_alert_groups_parameters.go +++ b/api/v2/client/alertgroup/get_alert_groups_parameters.go @@ -98,7 +98,8 @@ func NewGetAlertGroupsParamsWithHTTPClient(client *http.Client) *GetAlertGroupsP } } -/*GetAlertGroupsParams contains all the parameters to send to the API endpoint +/* +GetAlertGroupsParams contains all the parameters to send to the API endpoint for the get alert groups operation typically these are written to a http.Request */ type GetAlertGroupsParams struct { diff --git a/api/v2/client/alertgroup/get_alert_groups_responses.go b/api/v2/client/alertgroup/get_alert_groups_responses.go index 6c686ef3ee..fef8ba9416 100644 --- a/api/v2/client/alertgroup/get_alert_groups_responses.go +++ b/api/v2/client/alertgroup/get_alert_groups_responses.go @@ -66,7 +66,8 @@ func NewGetAlertGroupsOK() *GetAlertGroupsOK { return &GetAlertGroupsOK{} } -/*GetAlertGroupsOK handles this case with default header values. +/* +GetAlertGroupsOK handles this case with default header values. Get alert groups response */ @@ -97,7 +98,8 @@ func NewGetAlertGroupsBadRequest() *GetAlertGroupsBadRequest { return &GetAlertGroupsBadRequest{} } -/*GetAlertGroupsBadRequest handles this case with default header values. +/* +GetAlertGroupsBadRequest handles this case with default header values. Bad request */ @@ -128,7 +130,8 @@ func NewGetAlertGroupsInternalServerError() *GetAlertGroupsInternalServerError { return &GetAlertGroupsInternalServerError{} } -/*GetAlertGroupsInternalServerError handles this case with default header values. +/* +GetAlertGroupsInternalServerError handles this case with default header values. Internal server error */ diff --git a/api/v2/client/general/general_client.go b/api/v2/client/general/general_client.go index 35304b1790..3d9b14dc36 100644 --- a/api/v2/client/general/general_client.go +++ b/api/v2/client/general/general_client.go @@ -47,7 +47,7 @@ type ClientService interface { } /* - GetStatus Get current status of an Alertmanager instance and its cluster +GetStatus Get current status of an Alertmanager instance and its cluster */ func (a *Client) GetStatus(params *GetStatusParams) (*GetStatusOK, error) { // TODO: Validate the params before sending diff --git a/api/v2/client/general/get_status_parameters.go b/api/v2/client/general/get_status_parameters.go index 814e8b31ab..e0cb35170f 100644 --- a/api/v2/client/general/get_status_parameters.go +++ b/api/v2/client/general/get_status_parameters.go @@ -69,7 +69,8 @@ func NewGetStatusParamsWithHTTPClient(client *http.Client) *GetStatusParams { } } -/*GetStatusParams contains all the parameters to send to the API endpoint +/* +GetStatusParams contains all the parameters to send to the API endpoint for the get status operation typically these are written to a http.Request */ type GetStatusParams struct { diff --git a/api/v2/client/general/get_status_responses.go b/api/v2/client/general/get_status_responses.go index d457ccc92b..54227f7464 100644 --- a/api/v2/client/general/get_status_responses.go +++ b/api/v2/client/general/get_status_responses.go @@ -54,7 +54,8 @@ func NewGetStatusOK() *GetStatusOK { return &GetStatusOK{} } -/*GetStatusOK handles this case with default header values. +/* +GetStatusOK handles this case with default header values. Get status response */ diff --git a/api/v2/client/receiver/get_receivers_parameters.go b/api/v2/client/receiver/get_receivers_parameters.go index 090b9e4283..32a0ce4a4b 100644 --- a/api/v2/client/receiver/get_receivers_parameters.go +++ b/api/v2/client/receiver/get_receivers_parameters.go @@ -69,7 +69,8 @@ func NewGetReceiversParamsWithHTTPClient(client *http.Client) *GetReceiversParam } } -/*GetReceiversParams contains all the parameters to send to the API endpoint +/* +GetReceiversParams contains all the parameters to send to the API endpoint for the get receivers operation typically these are written to a http.Request */ type GetReceiversParams struct { diff --git a/api/v2/client/receiver/get_receivers_responses.go b/api/v2/client/receiver/get_receivers_responses.go index 3bc473d867..369ff9cdb2 100644 --- a/api/v2/client/receiver/get_receivers_responses.go +++ b/api/v2/client/receiver/get_receivers_responses.go @@ -54,7 +54,8 @@ func NewGetReceiversOK() *GetReceiversOK { return &GetReceiversOK{} } -/*GetReceiversOK handles this case with default header values. +/* +GetReceiversOK handles this case with default header values. Get receivers response */ diff --git a/api/v2/client/receiver/receiver_client.go b/api/v2/client/receiver/receiver_client.go index 1cda82018c..0f4915f988 100644 --- a/api/v2/client/receiver/receiver_client.go +++ b/api/v2/client/receiver/receiver_client.go @@ -47,7 +47,7 @@ type ClientService interface { } /* - GetReceivers Get list of all receivers (name of notification integrations) +GetReceivers Get list of all receivers (name of notification integrations) */ func (a *Client) GetReceivers(params *GetReceiversParams) (*GetReceiversOK, error) { // TODO: Validate the params before sending diff --git a/api/v2/client/silence/delete_silence_parameters.go b/api/v2/client/silence/delete_silence_parameters.go index 2b4e9b8c83..bdfd385cbe 100644 --- a/api/v2/client/silence/delete_silence_parameters.go +++ b/api/v2/client/silence/delete_silence_parameters.go @@ -69,7 +69,8 @@ func NewDeleteSilenceParamsWithHTTPClient(client *http.Client) *DeleteSilencePar } } -/*DeleteSilenceParams contains all the parameters to send to the API endpoint +/* +DeleteSilenceParams contains all the parameters to send to the API endpoint for the delete silence operation typically these are written to a http.Request */ type DeleteSilenceParams struct { diff --git a/api/v2/client/silence/delete_silence_responses.go b/api/v2/client/silence/delete_silence_responses.go index 848c53dc03..704b26ace7 100644 --- a/api/v2/client/silence/delete_silence_responses.go +++ b/api/v2/client/silence/delete_silence_responses.go @@ -58,7 +58,8 @@ func NewDeleteSilenceOK() *DeleteSilenceOK { return &DeleteSilenceOK{} } -/*DeleteSilenceOK handles this case with default header values. +/* +DeleteSilenceOK handles this case with default header values. Delete silence response */ @@ -79,7 +80,8 @@ func NewDeleteSilenceInternalServerError() *DeleteSilenceInternalServerError { return &DeleteSilenceInternalServerError{} } -/*DeleteSilenceInternalServerError handles this case with default header values. +/* +DeleteSilenceInternalServerError handles this case with default header values. Internal server error */ diff --git a/api/v2/client/silence/get_silence_parameters.go b/api/v2/client/silence/get_silence_parameters.go index e8cb7f00e2..5ecc340a9c 100644 --- a/api/v2/client/silence/get_silence_parameters.go +++ b/api/v2/client/silence/get_silence_parameters.go @@ -69,7 +69,8 @@ func NewGetSilenceParamsWithHTTPClient(client *http.Client) *GetSilenceParams { } } -/*GetSilenceParams contains all the parameters to send to the API endpoint +/* +GetSilenceParams contains all the parameters to send to the API endpoint for the get silence operation typically these are written to a http.Request */ type GetSilenceParams struct { diff --git a/api/v2/client/silence/get_silence_responses.go b/api/v2/client/silence/get_silence_responses.go index 7fc3f53da6..4d7f88afea 100644 --- a/api/v2/client/silence/get_silence_responses.go +++ b/api/v2/client/silence/get_silence_responses.go @@ -66,7 +66,8 @@ func NewGetSilenceOK() *GetSilenceOK { return &GetSilenceOK{} } -/*GetSilenceOK handles this case with default header values. +/* +GetSilenceOK handles this case with default header values. Get silence response */ @@ -99,7 +100,8 @@ func NewGetSilenceNotFound() *GetSilenceNotFound { return &GetSilenceNotFound{} } -/*GetSilenceNotFound handles this case with default header values. +/* +GetSilenceNotFound handles this case with default header values. A silence with the specified ID was not found */ @@ -120,7 +122,8 @@ func NewGetSilenceInternalServerError() *GetSilenceInternalServerError { return &GetSilenceInternalServerError{} } -/*GetSilenceInternalServerError handles this case with default header values. +/* +GetSilenceInternalServerError handles this case with default header values. Internal server error */ diff --git a/api/v2/client/silence/get_silences_parameters.go b/api/v2/client/silence/get_silences_parameters.go index 940fd8e9ef..5d0d5ec1a3 100644 --- a/api/v2/client/silence/get_silences_parameters.go +++ b/api/v2/client/silence/get_silences_parameters.go @@ -70,7 +70,8 @@ func NewGetSilencesParamsWithHTTPClient(client *http.Client) *GetSilencesParams } } -/*GetSilencesParams contains all the parameters to send to the API endpoint +/* +GetSilencesParams contains all the parameters to send to the API endpoint for the get silences operation typically these are written to a http.Request */ type GetSilencesParams struct { diff --git a/api/v2/client/silence/get_silences_responses.go b/api/v2/client/silence/get_silences_responses.go index 819a242793..827847f5e2 100644 --- a/api/v2/client/silence/get_silences_responses.go +++ b/api/v2/client/silence/get_silences_responses.go @@ -60,7 +60,8 @@ func NewGetSilencesOK() *GetSilencesOK { return &GetSilencesOK{} } -/*GetSilencesOK handles this case with default header values. +/* +GetSilencesOK handles this case with default header values. Get silences response */ @@ -91,7 +92,8 @@ func NewGetSilencesInternalServerError() *GetSilencesInternalServerError { return &GetSilencesInternalServerError{} } -/*GetSilencesInternalServerError handles this case with default header values. +/* +GetSilencesInternalServerError handles this case with default header values. Internal server error */ diff --git a/api/v2/client/silence/post_silences_parameters.go b/api/v2/client/silence/post_silences_parameters.go index 5fadff8e2e..d63b9bfbd5 100644 --- a/api/v2/client/silence/post_silences_parameters.go +++ b/api/v2/client/silence/post_silences_parameters.go @@ -71,7 +71,8 @@ func NewPostSilencesParamsWithHTTPClient(client *http.Client) *PostSilencesParam } } -/*PostSilencesParams contains all the parameters to send to the API endpoint +/* +PostSilencesParams contains all the parameters to send to the API endpoint for the post silences operation typically these are written to a http.Request */ type PostSilencesParams struct { diff --git a/api/v2/client/silence/post_silences_responses.go b/api/v2/client/silence/post_silences_responses.go index c71931f73a..0f15f0d3f9 100644 --- a/api/v2/client/silence/post_silences_responses.go +++ b/api/v2/client/silence/post_silences_responses.go @@ -65,7 +65,8 @@ func NewPostSilencesOK() *PostSilencesOK { return &PostSilencesOK{} } -/*PostSilencesOK handles this case with default header values. +/* +PostSilencesOK handles this case with default header values. Create / update silence response */ @@ -98,7 +99,8 @@ func NewPostSilencesBadRequest() *PostSilencesBadRequest { return &PostSilencesBadRequest{} } -/*PostSilencesBadRequest handles this case with default header values. +/* +PostSilencesBadRequest handles this case with default header values. Bad request */ @@ -129,7 +131,8 @@ func NewPostSilencesNotFound() *PostSilencesNotFound { return &PostSilencesNotFound{} } -/*PostSilencesNotFound handles this case with default header values. +/* +PostSilencesNotFound handles this case with default header values. A silence with the specified ID was not found */ @@ -155,7 +158,8 @@ func (o *PostSilencesNotFound) readResponse(response runtime.ClientResponse, con return nil } -/*PostSilencesOKBody post silences o k body +/* +PostSilencesOKBody post silences o k body swagger:model PostSilencesOKBody */ type PostSilencesOKBody struct { diff --git a/api/v2/client/silence/silence_client.go b/api/v2/client/silence/silence_client.go index b601f9d8c9..cba92be67d 100644 --- a/api/v2/client/silence/silence_client.go +++ b/api/v2/client/silence/silence_client.go @@ -53,7 +53,7 @@ type ClientService interface { } /* - DeleteSilence Delete a silence by its ID +DeleteSilence Delete a silence by its ID */ func (a *Client) DeleteSilence(params *DeleteSilenceParams) (*DeleteSilenceOK, error) { // TODO: Validate the params before sending @@ -87,7 +87,7 @@ func (a *Client) DeleteSilence(params *DeleteSilenceParams) (*DeleteSilenceOK, e } /* - GetSilence Get a silence by its ID +GetSilence Get a silence by its ID */ func (a *Client) GetSilence(params *GetSilenceParams) (*GetSilenceOK, error) { // TODO: Validate the params before sending @@ -121,7 +121,7 @@ func (a *Client) GetSilence(params *GetSilenceParams) (*GetSilenceOK, error) { } /* - GetSilences Get a list of silences +GetSilences Get a list of silences */ func (a *Client) GetSilences(params *GetSilencesParams) (*GetSilencesOK, error) { // TODO: Validate the params before sending @@ -155,7 +155,7 @@ func (a *Client) GetSilences(params *GetSilencesParams) (*GetSilencesOK, error) } /* - PostSilences Post a new silence or update an existing one +PostSilences Post a new silence or update an existing one */ func (a *Client) PostSilences(params *PostSilencesParams) (*PostSilencesOK, error) { // TODO: Validate the params before sending diff --git a/api/v2/restapi/doc.go b/api/v2/restapi/doc.go index d8ee687c48..ea249db26c 100644 --- a/api/v2/restapi/doc.go +++ b/api/v2/restapi/doc.go @@ -15,19 +15,19 @@ // Package restapi Alertmanager API // -// API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) -// Schemes: -// http -// Host: localhost -// BasePath: /api/v2/ -// Version: 0.0.1 -// License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0.html +// API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) +// Schemes: +// http +// Host: localhost +// BasePath: /api/v2/ +// Version: 0.0.1 +// License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0.html // -// Consumes: -// - application/json +// Consumes: +// - application/json // -// Produces: -// - application/json +// Produces: +// - application/json // // swagger:meta package restapi diff --git a/api/v2/restapi/operations/alert/get_alerts.go b/api/v2/restapi/operations/alert/get_alerts.go index 27caf27b11..df6d4d64ec 100644 --- a/api/v2/restapi/operations/alert/get_alerts.go +++ b/api/v2/restapi/operations/alert/get_alerts.go @@ -43,10 +43,10 @@ func NewGetAlerts(ctx *middleware.Context, handler GetAlertsHandler) *GetAlerts return &GetAlerts{Context: ctx, Handler: handler} } -/*GetAlerts swagger:route GET /alerts alert getAlerts +/* +GetAlerts swagger:route GET /alerts alert getAlerts Get a list of alerts - */ type GetAlerts struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/alert/get_alerts_responses.go b/api/v2/restapi/operations/alert/get_alerts_responses.go index 8c4f4b8f55..1a4d19415c 100644 --- a/api/v2/restapi/operations/alert/get_alerts_responses.go +++ b/api/v2/restapi/operations/alert/get_alerts_responses.go @@ -30,7 +30,8 @@ import ( // GetAlertsOKCode is the HTTP code returned for type GetAlertsOK const GetAlertsOKCode int = 200 -/*GetAlertsOK Get alerts response +/* +GetAlertsOK Get alerts response swagger:response getAlertsOK */ @@ -77,7 +78,8 @@ func (o *GetAlertsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Pro // GetAlertsBadRequestCode is the HTTP code returned for type GetAlertsBadRequest const GetAlertsBadRequestCode int = 400 -/*GetAlertsBadRequest Bad request +/* +GetAlertsBadRequest Bad request swagger:response getAlertsBadRequest */ @@ -119,7 +121,8 @@ func (o *GetAlertsBadRequest) WriteResponse(rw http.ResponseWriter, producer run // GetAlertsInternalServerErrorCode is the HTTP code returned for type GetAlertsInternalServerError const GetAlertsInternalServerErrorCode int = 500 -/*GetAlertsInternalServerError Internal server error +/* +GetAlertsInternalServerError Internal server error swagger:response getAlertsInternalServerError */ diff --git a/api/v2/restapi/operations/alert/post_alerts.go b/api/v2/restapi/operations/alert/post_alerts.go index 858db94bbc..25860564f1 100644 --- a/api/v2/restapi/operations/alert/post_alerts.go +++ b/api/v2/restapi/operations/alert/post_alerts.go @@ -43,10 +43,10 @@ func NewPostAlerts(ctx *middleware.Context, handler PostAlertsHandler) *PostAler return &PostAlerts{Context: ctx, Handler: handler} } -/*PostAlerts swagger:route POST /alerts alert postAlerts +/* +PostAlerts swagger:route POST /alerts alert postAlerts Create new Alerts - */ type PostAlerts struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/alert/post_alerts_responses.go b/api/v2/restapi/operations/alert/post_alerts_responses.go index 7e273c3106..87302a9800 100644 --- a/api/v2/restapi/operations/alert/post_alerts_responses.go +++ b/api/v2/restapi/operations/alert/post_alerts_responses.go @@ -28,7 +28,8 @@ import ( // PostAlertsOKCode is the HTTP code returned for type PostAlertsOK const PostAlertsOKCode int = 200 -/*PostAlertsOK Create alerts response +/* +PostAlertsOK Create alerts response swagger:response postAlertsOK */ @@ -52,7 +53,8 @@ func (o *PostAlertsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Pr // PostAlertsBadRequestCode is the HTTP code returned for type PostAlertsBadRequest const PostAlertsBadRequestCode int = 400 -/*PostAlertsBadRequest Bad request +/* +PostAlertsBadRequest Bad request swagger:response postAlertsBadRequest */ @@ -94,7 +96,8 @@ func (o *PostAlertsBadRequest) WriteResponse(rw http.ResponseWriter, producer ru // PostAlertsInternalServerErrorCode is the HTTP code returned for type PostAlertsInternalServerError const PostAlertsInternalServerErrorCode int = 500 -/*PostAlertsInternalServerError Internal server error +/* +PostAlertsInternalServerError Internal server error swagger:response postAlertsInternalServerError */ diff --git a/api/v2/restapi/operations/alertgroup/get_alert_groups.go b/api/v2/restapi/operations/alertgroup/get_alert_groups.go index 3ea3975d53..f355c45d00 100644 --- a/api/v2/restapi/operations/alertgroup/get_alert_groups.go +++ b/api/v2/restapi/operations/alertgroup/get_alert_groups.go @@ -43,10 +43,10 @@ func NewGetAlertGroups(ctx *middleware.Context, handler GetAlertGroupsHandler) * return &GetAlertGroups{Context: ctx, Handler: handler} } -/*GetAlertGroups swagger:route GET /alerts/groups alertgroup getAlertGroups +/* +GetAlertGroups swagger:route GET /alerts/groups alertgroup getAlertGroups Get a list of alert groups - */ type GetAlertGroups struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/alertgroup/get_alert_groups_responses.go b/api/v2/restapi/operations/alertgroup/get_alert_groups_responses.go index bb0d90faee..3b9227aaaa 100644 --- a/api/v2/restapi/operations/alertgroup/get_alert_groups_responses.go +++ b/api/v2/restapi/operations/alertgroup/get_alert_groups_responses.go @@ -30,7 +30,8 @@ import ( // GetAlertGroupsOKCode is the HTTP code returned for type GetAlertGroupsOK const GetAlertGroupsOKCode int = 200 -/*GetAlertGroupsOK Get alert groups response +/* +GetAlertGroupsOK Get alert groups response swagger:response getAlertGroupsOK */ @@ -77,7 +78,8 @@ func (o *GetAlertGroupsOK) WriteResponse(rw http.ResponseWriter, producer runtim // GetAlertGroupsBadRequestCode is the HTTP code returned for type GetAlertGroupsBadRequest const GetAlertGroupsBadRequestCode int = 400 -/*GetAlertGroupsBadRequest Bad request +/* +GetAlertGroupsBadRequest Bad request swagger:response getAlertGroupsBadRequest */ @@ -119,7 +121,8 @@ func (o *GetAlertGroupsBadRequest) WriteResponse(rw http.ResponseWriter, produce // GetAlertGroupsInternalServerErrorCode is the HTTP code returned for type GetAlertGroupsInternalServerError const GetAlertGroupsInternalServerErrorCode int = 500 -/*GetAlertGroupsInternalServerError Internal server error +/* +GetAlertGroupsInternalServerError Internal server error swagger:response getAlertGroupsInternalServerError */ diff --git a/api/v2/restapi/operations/general/get_status.go b/api/v2/restapi/operations/general/get_status.go index 8b6a6f5c7c..8579c67eb8 100644 --- a/api/v2/restapi/operations/general/get_status.go +++ b/api/v2/restapi/operations/general/get_status.go @@ -43,10 +43,10 @@ func NewGetStatus(ctx *middleware.Context, handler GetStatusHandler) *GetStatus return &GetStatus{Context: ctx, Handler: handler} } -/*GetStatus swagger:route GET /status general getStatus +/* +GetStatus swagger:route GET /status general getStatus Get current status of an Alertmanager instance and its cluster - */ type GetStatus struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/general/get_status_responses.go b/api/v2/restapi/operations/general/get_status_responses.go index 60d9e62075..ddfc323df6 100644 --- a/api/v2/restapi/operations/general/get_status_responses.go +++ b/api/v2/restapi/operations/general/get_status_responses.go @@ -30,7 +30,8 @@ import ( // GetStatusOKCode is the HTTP code returned for type GetStatusOK const GetStatusOKCode int = 200 -/*GetStatusOK Get status response +/* +GetStatusOK Get status response swagger:response getStatusOK */ diff --git a/api/v2/restapi/operations/receiver/get_receivers.go b/api/v2/restapi/operations/receiver/get_receivers.go index 40197940a2..35da8baf44 100644 --- a/api/v2/restapi/operations/receiver/get_receivers.go +++ b/api/v2/restapi/operations/receiver/get_receivers.go @@ -43,10 +43,10 @@ func NewGetReceivers(ctx *middleware.Context, handler GetReceiversHandler) *GetR return &GetReceivers{Context: ctx, Handler: handler} } -/*GetReceivers swagger:route GET /receivers receiver getReceivers +/* +GetReceivers swagger:route GET /receivers receiver getReceivers Get list of all receivers (name of notification integrations) - */ type GetReceivers struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/receiver/get_receivers_responses.go b/api/v2/restapi/operations/receiver/get_receivers_responses.go index ee0e6f2c31..0727afc9fb 100644 --- a/api/v2/restapi/operations/receiver/get_receivers_responses.go +++ b/api/v2/restapi/operations/receiver/get_receivers_responses.go @@ -30,7 +30,8 @@ import ( // GetReceiversOKCode is the HTTP code returned for type GetReceiversOK const GetReceiversOKCode int = 200 -/*GetReceiversOK Get receivers response +/* +GetReceiversOK Get receivers response swagger:response getReceiversOK */ diff --git a/api/v2/restapi/operations/silence/delete_silence.go b/api/v2/restapi/operations/silence/delete_silence.go index d0a227484a..5692e931b5 100644 --- a/api/v2/restapi/operations/silence/delete_silence.go +++ b/api/v2/restapi/operations/silence/delete_silence.go @@ -43,10 +43,10 @@ func NewDeleteSilence(ctx *middleware.Context, handler DeleteSilenceHandler) *De return &DeleteSilence{Context: ctx, Handler: handler} } -/*DeleteSilence swagger:route DELETE /silence/{silenceID} silence deleteSilence +/* +DeleteSilence swagger:route DELETE /silence/{silenceID} silence deleteSilence Delete a silence by its ID - */ type DeleteSilence struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/silence/delete_silence_responses.go b/api/v2/restapi/operations/silence/delete_silence_responses.go index de87e29a3b..0878e35dd3 100644 --- a/api/v2/restapi/operations/silence/delete_silence_responses.go +++ b/api/v2/restapi/operations/silence/delete_silence_responses.go @@ -28,7 +28,8 @@ import ( // DeleteSilenceOKCode is the HTTP code returned for type DeleteSilenceOK const DeleteSilenceOKCode int = 200 -/*DeleteSilenceOK Delete silence response +/* +DeleteSilenceOK Delete silence response swagger:response deleteSilenceOK */ @@ -52,7 +53,8 @@ func (o *DeleteSilenceOK) WriteResponse(rw http.ResponseWriter, producer runtime // DeleteSilenceInternalServerErrorCode is the HTTP code returned for type DeleteSilenceInternalServerError const DeleteSilenceInternalServerErrorCode int = 500 -/*DeleteSilenceInternalServerError Internal server error +/* +DeleteSilenceInternalServerError Internal server error swagger:response deleteSilenceInternalServerError */ diff --git a/api/v2/restapi/operations/silence/get_silence.go b/api/v2/restapi/operations/silence/get_silence.go index afaf6dd5f4..e19b12b63b 100644 --- a/api/v2/restapi/operations/silence/get_silence.go +++ b/api/v2/restapi/operations/silence/get_silence.go @@ -43,10 +43,10 @@ func NewGetSilence(ctx *middleware.Context, handler GetSilenceHandler) *GetSilen return &GetSilence{Context: ctx, Handler: handler} } -/*GetSilence swagger:route GET /silence/{silenceID} silence getSilence +/* +GetSilence swagger:route GET /silence/{silenceID} silence getSilence Get a silence by its ID - */ type GetSilence struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/silence/get_silence_responses.go b/api/v2/restapi/operations/silence/get_silence_responses.go index 5ece7ad73c..51255fe83c 100644 --- a/api/v2/restapi/operations/silence/get_silence_responses.go +++ b/api/v2/restapi/operations/silence/get_silence_responses.go @@ -30,7 +30,8 @@ import ( // GetSilenceOKCode is the HTTP code returned for type GetSilenceOK const GetSilenceOKCode int = 200 -/*GetSilenceOK Get silence response +/* +GetSilenceOK Get silence response swagger:response getSilenceOK */ @@ -74,7 +75,8 @@ func (o *GetSilenceOK) WriteResponse(rw http.ResponseWriter, producer runtime.Pr // GetSilenceNotFoundCode is the HTTP code returned for type GetSilenceNotFound const GetSilenceNotFoundCode int = 404 -/*GetSilenceNotFound A silence with the specified ID was not found +/* +GetSilenceNotFound A silence with the specified ID was not found swagger:response getSilenceNotFound */ @@ -98,7 +100,8 @@ func (o *GetSilenceNotFound) WriteResponse(rw http.ResponseWriter, producer runt // GetSilenceInternalServerErrorCode is the HTTP code returned for type GetSilenceInternalServerError const GetSilenceInternalServerErrorCode int = 500 -/*GetSilenceInternalServerError Internal server error +/* +GetSilenceInternalServerError Internal server error swagger:response getSilenceInternalServerError */ diff --git a/api/v2/restapi/operations/silence/get_silences.go b/api/v2/restapi/operations/silence/get_silences.go index 0346a421b8..be03f28ca1 100644 --- a/api/v2/restapi/operations/silence/get_silences.go +++ b/api/v2/restapi/operations/silence/get_silences.go @@ -43,10 +43,10 @@ func NewGetSilences(ctx *middleware.Context, handler GetSilencesHandler) *GetSil return &GetSilences{Context: ctx, Handler: handler} } -/*GetSilences swagger:route GET /silences silence getSilences +/* +GetSilences swagger:route GET /silences silence getSilences Get a list of silences - */ type GetSilences struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/silence/get_silences_responses.go b/api/v2/restapi/operations/silence/get_silences_responses.go index 314edf7b21..f2f7b88f74 100644 --- a/api/v2/restapi/operations/silence/get_silences_responses.go +++ b/api/v2/restapi/operations/silence/get_silences_responses.go @@ -30,7 +30,8 @@ import ( // GetSilencesOKCode is the HTTP code returned for type GetSilencesOK const GetSilencesOKCode int = 200 -/*GetSilencesOK Get silences response +/* +GetSilencesOK Get silences response swagger:response getSilencesOK */ @@ -77,7 +78,8 @@ func (o *GetSilencesOK) WriteResponse(rw http.ResponseWriter, producer runtime.P // GetSilencesInternalServerErrorCode is the HTTP code returned for type GetSilencesInternalServerError const GetSilencesInternalServerErrorCode int = 500 -/*GetSilencesInternalServerError Internal server error +/* +GetSilencesInternalServerError Internal server error swagger:response getSilencesInternalServerError */ diff --git a/api/v2/restapi/operations/silence/post_silences.go b/api/v2/restapi/operations/silence/post_silences.go index 98cc0576d4..e8f8dc131d 100644 --- a/api/v2/restapi/operations/silence/post_silences.go +++ b/api/v2/restapi/operations/silence/post_silences.go @@ -45,10 +45,10 @@ func NewPostSilences(ctx *middleware.Context, handler PostSilencesHandler) *Post return &PostSilences{Context: ctx, Handler: handler} } -/*PostSilences swagger:route POST /silences silence postSilences +/* +PostSilences swagger:route POST /silences silence postSilences Post a new silence or update an existing one - */ type PostSilences struct { Context *middleware.Context diff --git a/api/v2/restapi/operations/silence/post_silences_responses.go b/api/v2/restapi/operations/silence/post_silences_responses.go index 7570855d27..a20e89a6ee 100644 --- a/api/v2/restapi/operations/silence/post_silences_responses.go +++ b/api/v2/restapi/operations/silence/post_silences_responses.go @@ -28,7 +28,8 @@ import ( // PostSilencesOKCode is the HTTP code returned for type PostSilencesOK const PostSilencesOKCode int = 200 -/*PostSilencesOK Create / update silence response +/* +PostSilencesOK Create / update silence response swagger:response postSilencesOK */ @@ -72,7 +73,8 @@ func (o *PostSilencesOK) WriteResponse(rw http.ResponseWriter, producer runtime. // PostSilencesBadRequestCode is the HTTP code returned for type PostSilencesBadRequest const PostSilencesBadRequestCode int = 400 -/*PostSilencesBadRequest Bad request +/* +PostSilencesBadRequest Bad request swagger:response postSilencesBadRequest */ @@ -114,7 +116,8 @@ func (o *PostSilencesBadRequest) WriteResponse(rw http.ResponseWriter, producer // PostSilencesNotFoundCode is the HTTP code returned for type PostSilencesNotFound const PostSilencesNotFoundCode int = 404 -/*PostSilencesNotFound A silence with the specified ID was not found +/* +PostSilencesNotFound A silence with the specified ID was not found swagger:response postSilencesNotFound */ diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 1d8c1fa9d4..c531c1bcea 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "github.com/prometheus/alertmanager/notify/slackV2" + "github.com/prometheus/alertmanager/notify/twilio" "net" "net/http" "net/url" @@ -177,6 +178,9 @@ func buildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template, log for i, c := range nc.SigmaConfigs { add("sigma", i, c, func(l log.Logger) (notify.Notifier, error) { return sigma.New(c, tmpl, l) }) } + for i, c := range nc.TwilioConfigs { + add("twilio", i, c, func(l log.Logger) (notify.Notifier, error) { return twilio.New(c, tmpl, l) }) + } if errs.Len() > 0 { return nil, &errs } @@ -526,6 +530,20 @@ func run() int { ui.Register(router, webReload, logger) + router.Get("/callback/twilio", func(w http.ResponseWriter, req *http.Request) { + id := req.URL.Query().Get("id") + twilio.Storage.Get(id) + w.Header().Add("Content-Type", "text/xml") + if val := twilio.Storage.Get(id); val == nil { + w.WriteHeader(http.StatusNotFound) + return + } else { + w.WriteHeader(http.StatusOK) + w.Write(val.Data) + return + } + }) + mux := api.Register(router, *routePrefix) srv := &http.Server{Addr: *listenAddress, Handler: mux} diff --git a/config/config.go b/config/config.go index 3497c25fdb..a138e9bafb 100644 --- a/config/config.go +++ b/config/config.go @@ -809,6 +809,7 @@ type Receiver struct { VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"` SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"` SigmaConfigs []*SigmaConfig `yaml:"sigma_configs,omitempty" json:"sigma_configs,omitempty"` + TwilioConfigs []*TwilioConfig `yaml:"twilio_configs,omitempty" json:"twilio_configs,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver. diff --git a/config/notifiers.go b/config/notifiers.go index 01e0c1c0ea..ac34f8d619 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -40,6 +40,12 @@ var ( }, } + DefaultTwilioConfig = TwilioConfig{ + NotifierConfig: NotifierConfig{ + VSendResolved: true, + }, + } + // DefaultEmailConfig defines default values for Email configurations. DefaultEmailConfig = EmailConfig{ NotifierConfig: NotifierConfig{ @@ -495,21 +501,61 @@ func (c *SigmaConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +type TwilioConfig struct { + NotifierConfig `yaml:",inline" json:",inline"` + HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` + AccountID string `yaml:"account_id" json:"account_id"` + Token Secret `yaml:"token" json:"token"` + AlertManagerUrl *URL `yaml:"alert_manager_url" json:"alert_manager_url"` + PlayFileUrl *URL `yaml:"play_file_url" json:"play_file_url"` + Recipient []string `yaml:"recipient" json:"recipient"` + NotificationType string `yaml:"notification_type" json:"notification_type"` + SenderName string `yaml:"sender_name" json:"sender_name"` + Text string `yaml:"text" json:"text"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (c *TwilioConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + *c = DefaultTwilioConfig + type plain TwilioConfig + if err := unmarshal((*plain)(c)); err != nil { + return err + } + + if c.AccountID == "" { + return fmt.Errorf("account_id must be configured") + } + if c.Token == "" { + return fmt.Errorf("token must be configured") + } + switch strings.ToLower(c.NotificationType) { + case "sms", "": + c.NotificationType = "sms" + case "voice": + c.NotificationType = "voice" + if c.AlertManagerUrl == nil { + return fmt.Errorf("missing Alert manager URL in Twilio voice config") + } + default: + return fmt.Errorf("unknown notification type: %s", c.NotificationType) + } + + return nil +} + // WechatConfig configures notifications via Wechat. type WechatConfig struct { NotifierConfig `yaml:",inline" json:",inline"` - - HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` - - APISecret Secret `yaml:"api_secret,omitempty" json:"api_secret,omitempty"` - CorpID string `yaml:"corp_id,omitempty" json:"corp_id,omitempty"` - Message string `yaml:"message,omitempty" json:"message,omitempty"` - APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"` - ToUser string `yaml:"to_user,omitempty" json:"to_user,omitempty"` - ToParty string `yaml:"to_party,omitempty" json:"to_party,omitempty"` - ToTag string `yaml:"to_tag,omitempty" json:"to_tag,omitempty"` - AgentID string `yaml:"agent_id,omitempty" json:"agent_id,omitempty"` - MessageType string `yaml:"message_type,omitempty" json:"message_type,omitempty"` + HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` + APISecret Secret `yaml:"api_secret,omitempty" json:"api_secret,omitempty"` + CorpID string `yaml:"corp_id,omitempty" json:"corp_id,omitempty"` + Message string `yaml:"message,omitempty" json:"message,omitempty"` + APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"` + ToUser string `yaml:"to_user,omitempty" json:"to_user,omitempty"` + ToParty string `yaml:"to_party,omitempty" json:"to_party,omitempty"` + ToTag string `yaml:"to_tag,omitempty" json:"to_tag,omitempty"` + AgentID string `yaml:"agent_id,omitempty" json:"agent_id,omitempty"` + MessageType string `yaml:"message_type,omitempty" json:"message_type,omitempty"` } const wechatValidTypesRe = `^(text|markdown)$` diff --git a/go.mod b/go.mod index 11d50285df..ab1ba1ef95 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 github.com/slack-go/slack v0.10.4-0.20220503131901-0e14c9d4a15c github.com/stretchr/testify v1.7.0 + github.com/twilio/twilio-go v0.26.0 github.com/xlab/treeprint v1.1.0 go.uber.org/atomic v1.9.0 golang.org/x/mod v0.4.2 diff --git a/go.sum b/go.sum index 67c04187fd..5a91b8e46d 100644 --- a/go.sum +++ b/go.sum @@ -232,6 +232,7 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -243,6 +244,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -271,7 +274,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= @@ -291,7 +293,6 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -453,10 +454,6 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/slack-go/slack v0.10.0 h1:L16Eqg3QZzRKGXIVsFSZdJdygjOphb2FjRUwH6VrFu8= -github.com/slack-go/slack v0.10.0/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= -github.com/slack-go/slack v0.10.3 h1:kKYwlKY73AfSrtAk9UHWCXXfitudkDztNI9GYBviLxw= -github.com/slack-go/slack v0.10.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/slack-go/slack v0.10.4-0.20220503131901-0e14c9d4a15c h1:fOqgV3BG04rDivs8IKXQATLgQHfsWaOfYu4XZK3XNMM= github.com/slack-go/slack v0.10.4-0.20220503131901-0e14c9d4a15c/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= @@ -472,6 +469,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/twilio/twilio-go v0.26.0 h1:wFW4oTe3/LKt6bvByP7eio8JsjtaLHjMQKOUEzQry7U= +github.com/twilio/twilio-go v0.26.0/go.mod h1:lz62Hopu4vicpQ056H5TJ0JE4AP0rS3sQ35/ejmgOwE= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= @@ -729,6 +728,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/notify/twilio/main.go b/notify/twilio/main.go new file mode 100644 index 0000000000..a1792a9860 --- /dev/null +++ b/notify/twilio/main.go @@ -0,0 +1,123 @@ +// Copyright 2019 Prometheus Team +// 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 twilio + +import ( + "context" + "encoding/json" + "encoding/xml" + "fmt" + "github.com/go-kit/log" + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" + "github.com/twilio/twilio-go" + twapi "github.com/twilio/twilio-go/rest/api/v2010" +) + +// Notifier implements a Notifier for generic sigma. +type Notifier struct { + conf *config.TwilioConfig + tmpl *template.Template + logger log.Logger +} + +// New returns a new Sigma. +func New(conf *config.TwilioConfig, t *template.Template, l log.Logger) (*Notifier, error) { + return &Notifier{ + conf: conf, + tmpl: t, + logger: l, + }, nil +} + +func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { + var err error + var ( + data = notify.GetTemplateData(ctx, n.tmpl, as, n.logger) + tmplText = notify.TmplText(n.tmpl, data, &err) + ) + + tw := twilio.NewRestClientWithParams(twilio.ClientParams{ + Username: n.conf.AccountID, + Password: string(n.conf.Token), + }) + + allErrors := make([]error, 0) + switch n.conf.NotificationType { + case "sms": + for _, recepient := range n.conf.Recipient { + req := &twapi.CreateMessageParams{From: &n.conf.SenderName, To: &recepient} + req.SetBody(tmplText(n.conf.Text)) + resp, err := tw.Api.CreateMessage(req) + if err != nil { + allErrors = append(allErrors, err) + } else { + r, _ := json.Marshal(*resp) + n.logger.Log("Twilio response", r) + } + } + case "voice": + voiceReq := VoiceRequest{Say: tmplText(n.conf.Text)} + if n.conf.PlayFileUrl != nil { + voiceReq.Play = n.conf.PlayFileUrl.String() + } + voiceData, err := xml.Marshal(voiceReq) + if err != nil { + return false, err + } + + id := Storage.Put(voiceData) + url := *n.conf.AlertManagerUrl + url.Path = "/callback/twilio" + args := url.Query() + args.Set("id", id) + url.RawQuery = args.Encode() + + for _, recepient := range n.conf.Recipient { + req := &twapi.CreateCallParams{From: &n.conf.SenderName, To: &recepient} + req.SetMethod("GET") + req.SetUrl(url.String()) + resp, err := tw.Api.CreateCall(req) + if err != nil { + allErrors = append(allErrors, err) + } else { + r, _ := json.Marshal(*resp) + n.logger.Log("callback_url", url.String(), "Twilio response", r) + } + } + } + + if len(allErrors) > 0 { + return false, &Error{Errors: allErrors} + } + + return false, nil +} + +type Error struct { + Errors []error +} + +func (e *Error) Error() string { + return fmt.Sprintf("Errors count: %d", len(e.Errors)) +} + +type VoiceRequest struct { + XMLName xml.Name `xml:"Response"` + Text string `xml:",chardata"` + Play string `xml:"Play"` + Say string `xml:"Say"` +} diff --git a/notify/twilio/storage.go b/notify/twilio/storage.go new file mode 100644 index 0000000000..716b908cc4 --- /dev/null +++ b/notify/twilio/storage.go @@ -0,0 +1,56 @@ +package twilio + +import ( + "github.com/gofrs/uuid" + "sync" + "time" +) + +var Storage = NewStorage() + +type Entity struct { + Data []byte + CreatedAt time.Time +} + +type storage struct { + store map[string]Entity + mutex sync.RWMutex + Lifetime time.Duration +} + +func (st *storage) cleaner() { + for range time.Tick(time.Minute * 10) { + st.mutex.Lock() + for k, v := range st.store { + if v.CreatedAt.Add(st.Lifetime).Before(time.Now()) { + delete(st.store, k) + } + } + st.mutex.Unlock() + } +} + +func (st *storage) Get(id string) *Entity { + st.mutex.RLock() + defer st.mutex.RUnlock() + if val, ok := st.store[id]; !ok { + return nil + } else { + return &val + } +} + +func (st *storage) Put(data []byte) string { + st.mutex.Lock() + defer st.mutex.Unlock() + id, _ := uuid.NewV1() + st.store[id.String()] = Entity{Data: data, CreatedAt: time.Now()} + return id.String() +} + +func NewStorage() *storage { + st := &storage{store: make(map[string]Entity), mutex: sync.RWMutex{}, Lifetime: time.Hour * 24} + go st.cleaner() + return st +} diff --git a/pkg/labels/matcher_test.go b/pkg/labels/matcher_test.go index bd89a2b7ea..21d1b7f089 100644 --- a/pkg/labels/matcher_test.go +++ b/pkg/labels/matcher_test.go @@ -177,10 +177,10 @@ line`, want: `foo="new\nline"`, }, { - name: `foo`, - op: MatchEqual, + name: `foo`, + op: MatchEqual, value: `tab stop`, - want: `foo="tab stop"`, + want: `foo="tab stop"`, }, } diff --git a/pkg/labels/parse.go b/pkg/labels/parse.go index 0a242d506f..4652a7b7e2 100644 --- a/pkg/labels/parse.go +++ b/pkg/labels/parse.go @@ -45,11 +45,12 @@ var ( // this comma and whitespace will be trimmed. // // Examples for valid input strings: -// {foo = "bar", dings != "bums", } -// foo=bar,dings!=bums -// foo=bar, dings!=bums -// {quote="She said: \"Hi, ladies! That's gender-neutral…\""} -// statuscode=~"5.." +// +// {foo = "bar", dings != "bums", } +// foo=bar,dings!=bums +// foo=bar, dings!=bums +// {quote="She said: \"Hi, ladies! That's gender-neutral…\""} +// statuscode=~"5.." // // See ParseMatcher for details on how an individual Matcher is parsed. func ParseMatchers(s string) ([]*Matcher, error) { diff --git a/scripts/test_alert.sh b/scripts/test_alert.sh new file mode 100755 index 0000000000..b174183d08 --- /dev/null +++ b/scripts/test_alert.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail + +curl -H 'Content-Type: application/json' -d '[{"labels":{"alertname":"Test Alert", "severity": "critical", "env": "prod", "host_name": "testmachine"}}]' http://127.0.0.1:9093/api/v1/alerts diff --git a/store/store.go b/store/store.go index 488cb45161..cd5c6437fe 100644 --- a/store/store.go +++ b/store/store.go @@ -24,7 +24,7 @@ import ( ) var ( - // ErrNotFound is returned if a Store cannot find the Alert. + // ErrNotFound is returned if a store cannot find the Alert. ErrNotFound = errors.New("alert not found") ) diff --git a/timeinterval/timeinterval.go b/timeinterval/timeinterval.go index 24c4a4ea03..035874d9cd 100644 --- a/timeinterval/timeinterval.go +++ b/timeinterval/timeinterval.go @@ -373,7 +373,7 @@ func (ir InclusiveRange) MarshalText() ([]byte, error) { return []byte(out), nil } -//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange. +// MarshalYAML implements the yaml.Marshaler interface for InclusiveRange. func (ir InclusiveRange) MarshalYAML() (interface{}, error) { bytes, err := ir.MarshalText() return string(bytes), err From 9771d70bae5cf1f958255d608b085449ea869067 Mon Sep 17 00:00:00 2001 From: BorisTheRazor <86835302+AlexandrClick@users.noreply.github.com> Date: Wed, 17 Aug 2022 14:09:37 +0300 Subject: [PATCH 28/30] don't adding metric field if there don't have a value (#19) --- notify/slackV2/grafana.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/notify/slackV2/grafana.go b/notify/slackV2/grafana.go index 387a918558..b71529babf 100644 --- a/notify/slackV2/grafana.go +++ b/notify/slackV2/grafana.go @@ -266,7 +266,7 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { } } - //Summary Description and metrics + //Summary Description and Metrics { block := Block{Type: slack.MBTContext, Elements: make([]*Element, 0)} @@ -282,7 +282,9 @@ func (n *Notifier) formatGrafanaMessage(data *template.Data) slack.Blocks { grafanaMapParams[match[1]] = match[2] } } - block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Metric:* %s\n", valueStringCollection)}) + if valueStringCollection != "" { + block.Elements = append(block.Elements, &Element{Type: slack.MarkdownType, Text: fmt.Sprintf("*Metric:* %s\n", valueStringCollection)}) + } } if val := getMapValue(data.CommonAnnotations, "description"); len(val) > 0 { From 8074ac3f91e326c9369e6f5f0e21634dba2d0f34 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Wed, 17 Aug 2022 17:06:29 +0300 Subject: [PATCH 29/30] Fix merge --- cmd/alertmanager/main.go | 18 ++++++++++++------ config/config.go | 8 ++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 1248be6a0b..f0bc02a2f7 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -323,7 +323,7 @@ func run() int { // Start providers before router potentially sends updates. wg.Add(1) go func() { - silences.Maintenance(15*time.Minute, filepath.Join(*dataDir, "silences"), stopc, nil) + silences.Maintenance(*maintenanceInterval, filepath.Join(*dataDir, "silences"), stopc, nil) wg.Done() }() @@ -359,7 +359,9 @@ func run() int { defer alerts.Close() var disp *dispatch.Dispatcher - defer disp.Stop() + defer func() { + disp.Stop() + }() groupFn := func(routeFilter func(*dispatch.Route) bool, alertFilter func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) { return disp.Groups(routeFilter, alertFilter) @@ -453,10 +455,14 @@ func run() int { integrationsNum += len(integrations) } - // Build the map of time interval names to mute time definitions. - muteTimes := make(map[string][]timeinterval.TimeInterval, len(conf.MuteTimeIntervals)) + // Build the map of time interval names to time interval definitions. + timeIntervals := make(map[string][]timeinterval.TimeInterval, len(conf.MuteTimeIntervals)+len(conf.TimeIntervals)) for _, ti := range conf.MuteTimeIntervals { - muteTimes[ti.Name] = ti.TimeIntervals + timeIntervals[ti.Name] = ti.TimeIntervals + } + + for _, ti := range conf.TimeIntervals { + timeIntervals[ti.Name] = ti.TimeIntervals } inhibitor.Stop() @@ -478,7 +484,7 @@ func run() int { waitFunc, inhibitor, silencer, - muteTimes, + timeIntervals, notificationLog, pipelinePeer, ) diff --git a/config/config.go b/config/config.go index cc75d0475f..60bc72bfc0 100644 --- a/config/config.go +++ b/config/config.go @@ -486,6 +486,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { sns.HTTPConfig = c.Global.HTTPConfig } } + for _, telegram := range rcv.TelegramConfigs { + if telegram.HTTPConfig == nil { + telegram.HTTPConfig = c.Global.HTTPConfig + } + if telegram.APIUrl == nil { + telegram.APIUrl = c.Global.TelegramAPIUrl + } + } names[rcv.Name] = struct{}{} } From 1181e7a7d0d52691e356d8cfe831a8a51d65466c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 14:16:57 +0000 Subject: [PATCH 30/30] Bump github.com/go-openapi/errors from 0.20.2 to 0.20.3 Bumps [github.com/go-openapi/errors](https://github.com/go-openapi/errors) from 0.20.2 to 0.20.3. - [Release notes](https://github.com/go-openapi/errors/releases) - [Commits](https://github.com/go-openapi/errors/compare/v0.20.2...v0.20.3) --- updated-dependencies: - dependency-name: github.com/go-openapi/errors dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 503138da99..44628e71b1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/cenkalti/backoff/v4 v4.1.3 github.com/cespare/xxhash/v2 v2.1.2 github.com/go-kit/log v0.2.1 - github.com/go-openapi/errors v0.20.2 + github.com/go-openapi/errors v0.20.3 github.com/go-openapi/loads v0.21.1 github.com/go-openapi/runtime v0.24.1 github.com/go-openapi/spec v0.20.6 diff --git a/go.sum b/go.sum index 158f7d2d0b..e5db3179df 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,9 @@ github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= +github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=