Skip to content

Commit

Permalink
Merge pull request #3132 from prometheus/webex-notifier
Browse files Browse the repository at this point in the history
Notifier: Webex
  • Loading branch information
gotjosh committed Dec 15, 2022
2 parents 217524d + c3aaca8 commit 907f7d3
Show file tree
Hide file tree
Showing 12 changed files with 424 additions and 13 deletions.
4 changes: 2 additions & 2 deletions asset/assets_vfsdata.go

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

4 changes: 4 additions & 0 deletions cmd/alertmanager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import (
"github.com/prometheus/alertmanager/notify/sns"
"github.com/prometheus/alertmanager/notify/telegram"
"github.com/prometheus/alertmanager/notify/victorops"
"github.com/prometheus/alertmanager/notify/webex"
"github.com/prometheus/alertmanager/notify/webhook"
"github.com/prometheus/alertmanager/notify/wechat"
"github.com/prometheus/alertmanager/provider/mem"
Expand Down Expand Up @@ -177,6 +178,9 @@ func buildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template, log
for i, c := range nc.DiscordConfigs {
add("discord", i, c, func(l log.Logger) (notify.Notifier, error) { return discord.New(c, tmpl, l) })
}
for i, c := range nc.WebexConfigs {
add("webex", i, c, func(l log.Logger) (notify.Notifier, error) { return webex.New(c, tmpl, l) })
}

if errs.Len() > 0 {
return nil, &errs
Expand Down
18 changes: 18 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ func resolveFilepaths(baseDir string, cfg *Config) {
for _, cfg := range receiver.DiscordConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
for _, cfg := range receiver.WebexConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
}
}

Expand Down Expand Up @@ -513,6 +516,18 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
return fmt.Errorf("no discord webhook URL provided")
}
}
for _, webex := range rcv.WebexConfigs {
if webex.HTTPConfig == nil {
webex.HTTPConfig = c.Global.HTTPConfig
}
if webex.APIURL == nil {
if c.Global.WebexAPIURL == nil {
return fmt.Errorf("no global Webex URL set")
}

webex.APIURL = c.Global.WebexAPIURL
}
}

names[rcv.Name] = struct{}{}
}
Expand Down Expand Up @@ -613,6 +628,7 @@ func DefaultGlobalConfig() GlobalConfig {
WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"),
VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"),
TelegramAPIUrl: mustParseURL("https://api.telegram.org"),
WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"),
}
}

Expand Down Expand Up @@ -736,6 +752,7 @@ type GlobalConfig struct {
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
VictorOpsAPIKeyFile string `yaml:"victorops_api_key_file,omitempty" json:"victorops_api_key_file,omitempty"`
TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"`
WebexAPIURL *URL `yaml:"webex_api_url,omitempty" json:"webex_api_url,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig.
Expand Down Expand Up @@ -878,6 +895,7 @@ type Receiver struct {
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"`
TelegramConfigs []*TelegramConfig `yaml:"telegram_configs,omitempty" json:"telegram_configs,omitempty"`
WebexConfigs []*WebexConfig `yaml:"webex_configs,omitempty" json:"webex_configs,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver.
Expand Down
1 change: 1 addition & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,7 @@ func TestEmptyFieldsAndRegex(t *testing.T) {
WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"),
VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"),
TelegramAPIUrl: mustParseURL("https://api.telegram.org"),
WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"),
},

Templates: []string{
Expand Down
37 changes: 37 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ var (
},
}

// DefaultWebexConfig defines default values for Webex configurations.
DefaultWebexConfig = WebexConfig{
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
Message: `{{ template "webex.default.message" . }}`,
}

// DefaultDiscordConfig defines default values for Discord configurations.
DefaultDiscordConfig = DiscordConfig{
NotifierConfig: NotifierConfig{
Expand Down Expand Up @@ -167,6 +175,35 @@ func (nc *NotifierConfig) SendResolved() bool {
return nc.VSendResolved
}

// WebexConfig configures notifications via Webex.
type WebexConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"`

Message string `yaml:"message,omitempty" json:"message,omitempty"`
RoomID string `yaml:"room_id" json:"room_id"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *WebexConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultWebexConfig
type plain WebexConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}

if c.RoomID == "" {
return fmt.Errorf("missing room_id on webex_config")
}

if c.HTTPConfig == nil || c.HTTPConfig.Authorization == nil {
return fmt.Errorf("missing webex_configs.http_config.authorization")
}

return nil
}

// DiscordConfig configures notifications via Discord.
type DiscordConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
Expand Down
36 changes: 36 additions & 0 deletions config/notifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package config

import (
"errors"
"strings"
"testing"

Expand Down Expand Up @@ -885,6 +886,41 @@ func TestWeChatTypeMatcher(t *testing.T) {
}
}

func TestWebexConfiguration(t *testing.T) {
tc := []struct {
name string

in string
expected error
}{
{
name: "with no room_id - it fails",
in: `
message: xyz123
`,
expected: errors.New("missing room_id on webex_config"),
},
{
name: "with room_id and http_config.authorization set - it succeeds",
in: `
room_id: 2
http_config:
authorization:
credentials: "xxxyyyzz"
`,
},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
var cfg WebexConfig
err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)

require.Equal(t, tt.expected, err)
})
}
}

func newBoolPointer(b bool) *bool {
return &b
}
22 changes: 22 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ global:
[ wechat_api_secret: <secret> ]
[ wechat_api_corp_id: <string> ]
[ telegram_api_url: <string> | default = "https://api.telegram.org" ]
[ webex_api_url: <string> | default = "https://webexapis.com/v1/messages" ]
# The default HTTP client configuration
[ http_config: <http_config> ]

Expand Down Expand Up @@ -516,6 +517,8 @@ wechat_configs:
[ - <wechat_config>, ... ]
telegram_configs:
[ - <telegram_config>, ... ]
webex_configs:
[ - <webex_config>, ... ]
```

## `<email_config>`
Expand Down Expand Up @@ -1112,3 +1115,22 @@ API](http://admin.wechat.com/wiki/index.php?title=Customer_Service_Messages).
# The HTTP client's configuration.
[ http_config: <http_config> | default = global.http_config ]
```

## `<webex_config>`
```yaml
# Whether to notify about resolved alerts.
[ send_resolved: <boolean> | default = true ]

# The Webex Teams API URL i.e. https://webexapis.com/v1/messages
# If not specified, default API URL will be used.
[ api_url: <string> | default = global.webex_api_url ]

# ID of the Webex Teams room where to send the messages.
room_id: <string>

# Message template
[ message: <tmpl_string> default = '{{ template "webex.default.message" .}}' ]

# The HTTP client's configuration. You must use this configuration to supply the bot token as part of the HTTP `Authorization` header.
[ http_config: <http_config> | default = global.http_config ]
```
22 changes: 20 additions & 2 deletions notify/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"io"
"net/http"
"net/url"
"strings"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
Expand Down Expand Up @@ -100,15 +101,32 @@ func TruncateInRunes(s string, n int) (string, bool) {

// TruncateInBytes truncates a string to fit the given size in Bytes.
func TruncateInBytes(s string, n int) (string, bool) {
// First, measure the string the w/o a to-rune conversion.
if len(s) <= n {
return s, false
}

// The truncationMarker itself is 3 bytes, we can't return any part of the string when it's less than 3.
if n <= 3 {
return string(s[:n]), true
switch n {
case 3:
return truncationMarker, true
default:
return strings.Repeat(".", n), true
}
}

// Now, to ensure we don't butcher the string we need to remove using runes.
r := []rune(s)
truncationTarget := n - 3

// Next, let's truncate the runes to the lower possible number.
truncatedRunes := r[:truncationTarget]
for len(string(truncatedRunes)) > truncationTarget {
truncatedRunes = r[:len(truncatedRunes)-1]
}

return string(s[:n-3]) + truncationMarker, true // In bytes, the truncation marker is 3 bytes.
return string(truncatedRunes) + truncationMarker, true
}

// TmplText is using monadic error handling in order to make string templating
Expand Down
18 changes: 9 additions & 9 deletions notify/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestTruncate(t *testing.T) {
in: "abcde",
n: 2,
runes: expect{out: "ab", trunc: true},
bytes: expect{out: "ab", trunc: true},
bytes: expect{out: "..", trunc: true},
},
{
in: "abcde",
Expand All @@ -73,25 +73,25 @@ func TestTruncate(t *testing.T) {
in: "a⌘cde",
n: 5,
runes: expect{out: "a⌘cde", trunc: false},
bytes: expect{out: "a\xe2…", trunc: true},
bytes: expect{out: "a…", trunc: true},
},
{
in: "a⌘cdef",
n: 5,
runes: expect{out: "a⌘cd…", trunc: true},
bytes: expect{out: "a\xe2…", trunc: true},
bytes: expect{out: "a…", trunc: true},
},
{
in: "世界cdef",
n: 3,
runes: expect{out: "世界c", trunc: true},
bytes: expect{out: "", trunc: true},
bytes: expect{out: "", trunc: true},
},
{
in: "❤️✅🚀🔥❌",
n: 4,
runes: expect{out: "❤️✅…", trunc: true},
bytes: expect{out: "\xe2…", trunc: true},
in: "❤️✅🚀🔥❌❤️✅🚀🔥❌❤️✅🚀🔥❌❤️✅🚀🔥❌",
n: 19,
runes: expect{out: "❤️✅🚀🔥❌❤️✅🚀🔥❌❤️✅🚀🔥❌…", trunc: true},
bytes: expect{out: "❤️✅🚀…", trunc: true},
},
}

Expand All @@ -117,8 +117,8 @@ func TestTruncate(t *testing.T) {

t.Run(fmt.Sprintf("%s(%s,%d)", fnName, tc.in, tc.n), func(t *testing.T) {
s, trunc := fn(tc.in, tc.n)
require.Equal(t, truncated, trunc)
require.Equal(t, out, s)
require.Equal(t, truncated, trunc)
})
}
}
Expand Down

0 comments on commit 907f7d3

Please sign in to comment.