feat: add rocketchat notifier (#3600)

* feat: add rocketchat notifier

Signed-off-by: Christoph Maser <christoph.maser+github@gmail.com>
Co-authored-by: George Robinson <george.robinson@grafana.com>

* Update notify/rocketchat/rocketchat.go

Co-authored-by: George Robinson <george.robinson@grafana.com>
Signed-off-by: gotjosh <josue.abreu@gmail.com>

---------

Signed-off-by: Christoph Maser <christoph.maser+github@gmail.com>
Signed-off-by: gotjosh <josue.abreu@gmail.com>
Co-authored-by: George Robinson <george.robinson@grafana.com>
Co-authored-by: gotjosh <josue.abreu@gmail.com>
This commit is contained in:
Christoph Maser 2024-10-23 16:38:22 +02:00 committed by GitHub
parent e6cb11ce4d
commit 8572fe849c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 793 additions and 65 deletions

View File

@ -163,9 +163,9 @@ var Assets = func() http.FileSystem {
"/templates/default.tmpl": &vfsgen۰CompressedFileInfo{ "/templates/default.tmpl": &vfsgen۰CompressedFileInfo{
name: "default.tmpl", name: "default.tmpl",
modTime: time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC), modTime: time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC),
uncompressedSize: 7656, uncompressedSize: 8053,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x59\x4d\x6f\xe3\x36\x10\xbd\xfb\x57\x0c\xb4\x3d\xc4\x87\x68\x8b\x1e\x03\x04\xc5\xa2\xe8\xc7\x21\x2d\x0a\x2f\xd2\x4b\x51\x08\x8c\x34\x56\x98\x50\xa4\x42\x8e\xec\x18\x8e\xfe\x7b\x41\x49\x96\xa9\x0f\xdb\x94\xd7\x3d\xd5\xb7\x98\x9a\x79\x33\x7c\x6f\x34\x43\x2a\xdb\x2d\x24\xb8\xe4\x12\x21\x88\x22\x26\x50\x53\xc6\x24\x4b\x51\x07\x50\x96\x5f\x9c\xdf\xdb\x2d\xa0\x4c\xa0\x2c\x67\x07\x5d\x1e\x17\x0f\xd6\x6b\xbb\x85\xf0\xe7\x77\x42\x2d\x99\x78\x5c\x3c\x40\x59\x7e\xfe\xf4\xb9\xb2\x33\x3f\x6a\x8c\x91\xaf\x50\xdf\x5b\xa3\x45\xf3\x03\x3e\xa0\xd0\xe2\xad\x40\xbd\xa9\xdd\x9b\x40\xdd\x48\xa6\x78\x7a\xc1\x98\x6c\x84\xbf\xad\xf7\x57\x62\x54\x18\xf8\x00\x52\x8f\x79\x8e\xba\x76\xe5\x4b\xc0\xb7\xf6\x61\xb0\xe4\x9a\xcb\xd4\xfa\xdc\x59\x9f\x6a\x43\x26\xfc\xa5\x5a\x85\x0f\x10\x28\xdd\x88\xff\x80\x35\xfa\x55\xab\x22\x7f\x60\x4f\x28\x4c\xf8\x55\x69\xc2\xe4\x4f\xc6\xb5\x09\xff\x62\xa2\x40\x1b\xf0\x45\x71\x09\x01\x58\x54\xa8\x43\xa6\x04\x37\x16\x2b\xfc\x49\x65\x99\x92\xb5\xf3\xbc\x59\x73\xf0\xe6\x50\x96\x37\xdb\x2d\xac\x39\x3d\x77\x8d\xc3\x05\x66\x6a\x85\xdd\xe8\x7f\xb0\x0c\x4d\xc3\xe8\x58\xf4\x36\xf1\x79\xfb\xd7\x01\x99\x12\x34\xb1\xe6\x39\x71\x25\x83\x23\x1c\x13\xbe\x53\x2d\x69\x24\xb8\xa1\xc6\x54\x33\x99\x22\x84\x50\x96\x75\x5e\x77\xb3\xfd\xe2\x90\x27\xcb\xca\x6d\x45\xa4\x4d\xdf\xfe\xba\x87\x76\x03\x4d\x62\x75\xf0\x2f\x52\x2a\x62\x36\xa7\x0e\xa4\xb3\x7c\x1e\xee\x57\x55\xe8\x18\xef\x6a\x31\x51\xa2\x66\xa4\x74\x5d\x89\xb3\x11\xa2\x8e\x52\x10\x65\x4c\xbf\x26\x6a\x2d\x07\x5c\xcc\x7c\xc9\xf0\xcc\x7a\x36\x9d\x0e\x5f\x64\x2f\x42\x66\xe3\x8c\x18\xc1\xe2\xd7\x30\xc1\x25\x2b\x04\x85\xc4\x49\x60\x43\x05\x61\x96\x0b\x46\xdd\x97\x33\x3c\x54\x83\x5d\x9c\xc2\xd8\xf6\x90\x8d\x41\x75\x9b\x90\x27\xde\x92\x09\xf1\xc4\xe2\xd7\x01\xde\x68\xfa\x16\x14\x3e\xe0\x94\xa1\xe0\xf2\xd5\x3b\x83\xb8\xc9\x80\x27\x81\x9f\x43\xae\xd1\xd6\x9a\xa7\xb5\x93\xd0\x51\xc6\xaa\x1e\xec\x99\x32\x8f\x95\xc4\x4c\xbd\xf0\xc0\xdf\xbe\xd0\xc2\x37\x63\xff\xcd\x2d\x95\xa2\x7a\xe2\x38\x35\xe8\x9a\xe7\x76\x6b\x49\x41\x9b\xd6\x65\xd8\xd0\xa6\x95\xe3\x10\x31\x16\x1c\x25\x9d\x5f\x90\x87\x10\xf7\x53\xf1\x3c\xcd\x86\xb8\x5c\x1a\x62\x32\x46\x33\x82\x3b\xe8\xe0\xe1\x61\x56\x55\x6e\x52\x94\x1c\x5b\xe0\x0c\x8d\x61\xe9\x79\xef\xf7\x00\x6c\xa8\x50\x33\xf0\x0e\x34\xb4\xd1\x09\x37\xeb\xcd\xd7\xce\x00\x9f\xc3\xf7\x70\x6b\x1b\x67\xb5\x08\xf5\x62\xd5\x3a\x8f\x33\xd2\x3d\x05\x54\x41\x6e\x9d\x1d\x8d\xc4\x5b\xa0\x51\x62\x85\x49\x2f\xe2\x6e\xd9\x3f\xe6\xce\x63\x10\xf5\xd6\x87\x52\x53\xf5\xf1\xe9\xd5\xd4\x51\x7d\x8d\xf1\x33\xa3\xa9\x9a\xcf\xae\xfa\x1d\xd1\xcf\x3d\x28\x3f\x6a\x31\xc0\x1b\xd5\xe7\x80\xea\x3d\x7d\x48\x45\x76\x58\x1e\xec\xa4\x43\xf3\x9c\x69\xda\x4c\xb0\x27\x96\xfa\x5a\xb3\x14\x25\x45\xfd\x11\xd7\xad\xaf\x15\x8f\x49\x69\x95\x9b\x7d\xd9\x12\x23\x8c\xba\x85\x76\xad\xa5\x69\xbd\x60\xc8\x2a\x4a\xe2\xb4\x89\x12\x6e\x72\xc1\x36\xd1\x81\xd3\xd4\xe9\xc6\x3d\x44\xce\x94\xe4\xa4\x2c\x21\x11\x29\x25\x26\x8e\xc4\xce\xec\x2a\xcc\xb3\x5a\xa1\xbe\xc0\xf9\x71\x00\xf5\xdf\xd7\xd3\x65\xca\xc9\xbf\x9a\x2e\x57\x4c\xc3\x23\xfd\x31\x26\xf7\x67\xba\x29\x33\xc5\x3d\xcd\x49\xe7\x65\xdf\x5f\xd3\xa7\xdf\x11\x1c\x9c\xab\xbc\x53\xe4\x75\x59\x24\x14\x98\x6a\x96\x8d\x51\xf9\xbf\x25\x25\xe1\x26\x56\x3a\xb9\x40\x23\xea\x23\x5d\xd9\xb5\xc7\x84\x27\x7c\xbf\xbe\xba\xdf\xcc\x63\x66\x08\x59\xe6\x36\xd3\x2c\x63\x7a\x73\x56\x9d\xf6\xb1\xce\xaf\xf8\x01\x52\x73\xb3\xf7\x91\xe9\x13\x4c\x12\xca\xf9\xdc\xf6\xcd\x8a\xb5\xa1\x7d\x35\x1b\x09\x7e\x86\x78\xab\x1f\x2e\x47\xb9\x8b\x75\x25\x7d\x8c\xf4\x17\xae\xd9\x45\x5e\x97\x0e\x50\xef\xdb\xc5\x95\xf3\x59\x75\x2d\x19\xe5\x2a\xd7\x5c\x69\x6e\x6f\x9c\xb7\xcd\xed\xe5\xbb\xdd\x12\xdc\xdd\x43\x10\xec\x2e\x35\xbb\xef\xd9\x9d\xdd\x5a\x1f\x00\x80\xca\xcf\xe0\x0a\x77\x7e\x5c\x26\xf8\xbe\xfb\xa4\x0e\xc1\xee\x51\xd0\xf1\xe0\x4b\xb8\xc1\x37\xc7\x31\x88\x35\x27\x1e\x33\x11\xcc\x5b\xc3\x16\xbe\x4d\xeb\x1e\x82\xdf\x78\xfa\xdc\xc5\x42\x61\xb0\x02\x64\x32\xe9\xa3\xae\x99\x96\x5c\xa6\xc1\x1c\x6e\x24\x3a\x40\x35\xcc\xfc\x44\xac\xdf\x31\xe1\x45\xe6\x1f\x8d\xcb\xa5\xb2\xa1\xec\xea\x3e\xd4\xc9\x30\x0f\x6a\xdd\x8b\x21\x93\x56\x13\xf7\xef\xfa\x7f\x64\x2e\x74\xc7\xad\xab\x53\x5b\x18\x83\xd8\x93\xd4\x9a\xac\x98\x87\x6a\x17\x57\xce\x4b\xbd\xcb\x29\x78\x5a\xc5\xbe\x92\xa7\x94\xdd\x23\xf5\x9f\xfe\x1b\x00\x00\xff\xff\x7a\xa6\xa9\x68\xe8\x1d\x00\x00"), compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x59\xcf\x6f\xeb\x36\x0c\xbe\xe7\xaf\x20\xfc\x76\x68\x0e\xf5\x1b\x76\x2c\x50\x0c\x0f\xc3\x7e\x1c\xba\x61\x68\xd1\x5d\x86\x21\x50\x6d\xc6\x55\x2b\x4b\xae\x44\x27\x0d\xd2\xfc\xef\x83\x6c\xc7\x91\x6c\x27\x91\xd3\xec\xb4\xdc\x12\x99\xfc\x48\x7f\x1f\x4d\x4a\xf6\x7a\x0d\x29\xce\xb9\x44\x88\x66\x33\x26\x50\x53\xce\x24\xcb\x50\x47\xb0\xd9\x7c\x73\xfe\xaf\xd7\x80\x32\x85\xcd\x66\xb2\xd7\xe5\xf1\xfe\xce\x7a\xad\xd7\x10\xff\xfc\x4e\xa8\x25\x13\x8f\xf7\x77\xb0\xd9\x7c\xfd\xf2\xb5\xb2\x33\x3f\x6a\x4c\x90\x2f\x50\xdf\x5a\xa3\xfb\xe6\x0f\x7c\x40\xa9\xc5\x5b\x89\x7a\x55\xbb\x37\x81\xfc\x48\xa6\x7c\x7a\xc1\x84\x6c\x84\xbf\xad\xf7\x03\x31\x2a\x0d\x7c\x00\xa9\xc7\xa2\x40\x5d\xbb\xf2\x39\xe0\x5b\x7b\x31\x9a\x73\xcd\x65\x66\x7d\x6e\xac\x4f\x75\x43\x26\xfe\xa5\x5a\x85\x0f\x10\x28\xdd\x88\xff\x80\x35\xfa\x55\xab\xb2\xb8\x63\x4f\x28\x4c\xfc\xa0\x34\x61\xfa\x27\xe3\xda\xc4\x7f\x31\x51\xa2\x0d\xf8\xa2\xb8\x84\x08\x2c\x2a\xd4\x21\x33\x82\x2b\x8b\x15\xff\xa4\xf2\x5c\xc9\xda\x79\xda\xac\x39\x78\x53\xd8\x6c\xae\xd6\x6b\x58\x72\x7a\xf6\x8d\xe3\x7b\xcc\xd5\x02\xfd\xe8\x7f\xb0\x1c\x4d\xc3\xe8\x50\xf4\x36\xf1\x69\xfb\x6b\x8f\x4c\x29\x9a\x44\xf3\x82\xb8\x92\xd1\x01\x8e\x09\xdf\xa9\x96\x74\x26\xb8\xa1\xc6\x54\x33\x99\x21\xc4\xb0\xd9\xd4\x79\xdd\x4c\x76\x8b\x7d\x9e\x2c\x2b\xd7\x15\x91\x36\x7d\xfb\xef\x16\xda\x1b\x68\x12\xab\x83\x7f\x93\x52\x11\xb3\x39\x79\x90\xce\xf2\x69\xb8\x0f\xaa\xd4\x09\xde\xd4\x62\xa2\x44\xcd\x48\xe9\xba\x12\x27\x03\x44\x1d\xa4\x60\x96\x33\xfd\x9a\xaa\xa5\xec\x71\x31\x09\x25\x23\x30\xeb\xc9\x78\x3a\x42\x91\x83\x08\x99\x0c\x33\x62\x04\x4b\x5e\xe3\x14\xe7\xac\x14\x14\x13\x27\x81\x0d\x15\x84\x79\x21\x18\xf9\x0f\x67\xbc\xaf\x06\x7d\x9c\xd2\xd8\xf6\x90\x0f\x41\xf9\x4d\x28\x10\x6f\xce\x84\x78\x62\xc9\x6b\x0f\x6f\x30\x7d\x0b\x0a\x1f\x70\xcc\x50\x70\xf9\x1a\x9c\x41\xd2\x64\xc0\xd3\x28\xcc\xa1\xd0\x68\x6b\x2d\xd0\xda\x49\xe8\x20\x63\x55\x0f\x0e\x4c\x99\x27\x4a\x62\xae\x5e\x78\x14\x6e\x5f\x6a\x11\x9a\x71\xf8\xcd\xcd\x95\xa2\x7a\xe2\x38\x35\xe8\x9a\x17\xf6\xd6\xd2\x92\x56\xad\x4b\xbf\xa1\x8d\x2b\xc7\x3e\x62\x22\x38\x4a\x3a\xbd\x20\xf7\x21\xee\xa6\xe2\x69\x9a\xf5\x71\xb9\x34\xc4\x64\x82\x66\x00\xb7\xd7\xc1\xe3\xfd\xac\xaa\xc2\x64\x28\x39\xb6\xc0\x39\x1a\xc3\xb2\xd3\x9e\xef\x1e\x58\x5f\xa1\x66\xe0\xed\x69\x68\x83\x13\x6e\xd2\x99\xaf\xde\x00\x9f\xc2\xf7\x70\x6d\x1b\x67\xb5\x08\xf5\x62\xd5\x3a\x0f\x33\xe2\xef\x02\xaa\x20\xd7\xce\x1d\x0d\xc4\xbb\x47\xa3\xc4\x02\xd3\x4e\xc4\xed\x72\x78\xcc\xad\x47\x2f\xea\x75\x08\xa5\xa6\xea\xe3\xe3\xab\xc9\x53\x7d\x89\xc9\x33\xa3\xb1\x9a\x4f\x2e\xfa\x1d\xd0\xcf\xdd\x28\x3f\x6a\xd1\xc3\x1b\xd4\x67\x8f\xea\x1d\x7d\x48\xcd\xec\xb0\xdc\xdb\x49\xfb\xe6\x05\xd3\xb4\x1a\x61\x4f\x2c\x0b\xb5\x66\x19\x4a\x9a\x75\x47\x9c\x5f\x5f\x0b\x9e\x90\xd2\xaa\x30\xbb\xb2\x25\x46\x38\xf3\x0b\xed\x52\x4b\xe3\x7a\x41\x9f\x55\x94\xc4\x69\x35\x4b\xb9\x29\x04\x5b\xcd\xf6\xec\xa6\x8e\x37\xee\x3e\x72\xae\x24\x27\x65\x09\x99\x91\x52\x62\xe4\x48\xf4\x66\x57\x69\x9e\xd5\x02\xf5\x19\xf6\x8f\x3d\xa8\xff\xbe\x9e\xce\x53\x4e\xe1\xd5\x74\xbe\x62\xea\x6f\xe9\x0f\x31\xb9\xdb\xd3\x8d\x99\x29\xee\x6e\x4e\x3a\x0f\xfb\xee\x98\x3e\xfe\x8c\xe0\xe0\x5c\xe4\x1d\x23\xaf\xcb\x22\xa1\xc0\x4c\xb3\x7c\x88\xca\xff\x2d\x29\x29\x37\x89\xd2\xe9\x19\x1a\x51\x17\xe9\xc2\xae\xdd\x26\x3c\xe1\xfb\xe5\xd1\xfd\x34\x8f\xb9\x21\x64\xb9\xdb\x4c\xf3\x9c\xe9\xd5\x49\x75\xda\xc5\x3a\xbd\xe2\x7b\x48\xcd\xc9\x3e\x44\xa6\x2f\x30\x4a\x28\xe7\x75\xdb\xa7\x15\x6b\x43\x87\x6a\x36\x10\xfc\x04\xf1\x16\x3f\x9c\x8f\x72\x17\xeb\x42\xfa\x10\xe9\x2f\x5c\xb3\xb3\x3c\x2e\x1e\x50\xe7\xdd\xc5\x85\xf3\x49\x75\x2c\x19\xe4\xaa\xd0\x5c\x69\x6e\x4f\x9c\xd7\xcd\xe9\xe5\xbb\xed\x12\xdc\xdc\x42\x14\x6d\x0f\x35\xdb\xf7\xd9\xde\xdd\x5a\x1f\x00\x80\xca\xcf\xe0\x02\xb7\x7e\x5c\xa6\xf8\xbe\x7d\xa5\x0e\xd1\xf6\x52\xe4\x79\xf0\x39\x5c\xe1\x9b\xe3\x18\x25\x9a\x13\x4f\x98\x88\xa6\xad\x61\x0b\xdf\xa6\x75\x0b\xd1\x6f\x3c\x7b\xf6\xb1\x50\x18\xac\x00\x99\x4c\xbb\xa8\x4b\xa6\x25\x97\x59\x34\x85\x2b\x89\x0e\x50\x0d\x33\x3d\x12\xeb\x77\x4c\x79\x99\x87\x47\xe3\x72\xae\x6c\x28\xbb\xba\x0b\x75\x34\xcc\x9d\x5a\x76\x62\xc8\xb4\xd5\xc4\xfd\x5d\x7f\x23\x73\xa1\x3d\x37\x5f\xa7\xb6\x30\x7a\xb1\x47\xa9\x35\x5a\xb1\x00\xd5\xce\xae\x5c\x90\x7a\xe7\x53\xf0\xb8\x8a\x5d\x25\x8f\x29\xbb\x43\xea\x5e\x75\x5b\x9d\x56\xc9\x2b\x92\xff\x1a\xe8\xe4\x49\x35\x00\xc6\x04\x67\xe6\xf4\x17\xe9\xfb\xd2\xfb\xf4\xd7\x8f\x01\xe0\xc3\x9f\x3f\x06\x1c\x8e\x7d\x03\x19\x4a\xbe\xf7\x21\xe4\xdf\x00\x00\x00\xff\xff\xde\xd1\xa3\x9d\x75\x1f\x00\x00"),
}, },
"/templates/email.tmpl": &vfsgen۰CompressedFileInfo{ "/templates/email.tmpl": &vfsgen۰CompressedFileInfo{
name: "email.tmpl", name: "email.tmpl",

View File

@ -269,6 +269,9 @@ func resolveFilepaths(baseDir string, cfg *Config) {
for _, cfg := range receiver.JiraConfigs { for _, cfg := range receiver.JiraConfigs {
cfg.HTTPConfig.SetDirectory(baseDir) cfg.HTTPConfig.SetDirectory(baseDir)
} }
for _, cfg := range receiver.RocketchatConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
} }
} }
@ -364,6 +367,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
return errors.New("at most one of smtp_auth_password & smtp_auth_password_file must be configured") return errors.New("at most one of smtp_auth_password & smtp_auth_password_file must be configured")
} }
if c.Global.RocketchatToken != nil && len(c.Global.RocketchatTokenFile) > 0 {
return errors.New("at most one of rocketchat_token & rocketchat_token_file must be configured")
}
if c.Global.RocketchatTokenID != nil && len(c.Global.RocketchatTokenIDFile) > 0 {
return errors.New("at most one of rocketchat_token_id & rocketchat_token_id_file must be configured")
}
names := map[string]struct{}{} names := map[string]struct{}{}
for _, rcv := range c.Receivers { for _, rcv := range c.Receivers {
@ -573,6 +584,28 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
jira.APIURL = c.Global.JiraAPIURL jira.APIURL = c.Global.JiraAPIURL
} }
} }
for _, rocketchat := range rcv.RocketchatConfigs {
if rocketchat.HTTPConfig == nil {
rocketchat.HTTPConfig = c.Global.HTTPConfig
}
if rocketchat.APIURL == nil {
rocketchat.APIURL = c.Global.RocketchatAPIURL
}
if rocketchat.TokenID == nil && len(rocketchat.TokenIDFile) == 0 {
if c.Global.RocketchatTokenID == nil && len(c.Global.RocketchatTokenIDFile) == 0 {
return errors.New("no global Rocketchat TokenID set either inline or in a file")
}
rocketchat.TokenID = c.Global.RocketchatTokenID
rocketchat.TokenIDFile = c.Global.RocketchatTokenIDFile
}
if rocketchat.Token == nil && len(rocketchat.TokenFile) == 0 {
if c.Global.RocketchatToken == nil && len(c.Global.RocketchatTokenFile) == 0 {
return errors.New("no global Rocketchat Token set either inline or in a file")
}
rocketchat.Token = c.Global.RocketchatToken
rocketchat.TokenFile = c.Global.RocketchatTokenFile
}
}
names[rcv.Name] = struct{}{} names[rcv.Name] = struct{}{}
} }
@ -665,17 +698,18 @@ func DefaultGlobalConfig() GlobalConfig {
defaultSMTPTLSConfig := commoncfg.TLSConfig{} defaultSMTPTLSConfig := commoncfg.TLSConfig{}
return GlobalConfig{ return GlobalConfig{
ResolveTimeout: model.Duration(5 * time.Minute), ResolveTimeout: model.Duration(5 * time.Minute),
HTTPConfig: &defaultHTTPConfig, HTTPConfig: &defaultHTTPConfig,
SMTPHello: "localhost", SMTPHello: "localhost",
SMTPRequireTLS: true, SMTPRequireTLS: true,
SMTPTLSConfig: &defaultSMTPTLSConfig, SMTPTLSConfig: &defaultSMTPTLSConfig,
PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"), PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"),
OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"), OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"),
WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"), WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"),
VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"), VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"),
TelegramAPIUrl: mustParseURL("https://api.telegram.org"), TelegramAPIUrl: mustParseURL("https://api.telegram.org"),
WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"), WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"),
RocketchatAPIURL: mustParseURL("https://open.rocket.chat/"),
} }
} }
@ -777,31 +811,36 @@ type GlobalConfig struct {
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
JiraAPIURL *URL `yaml:"jira_api_url,omitempty" json:"jira_api_url,omitempty"` JiraAPIURL *URL `yaml:"jira_api_url,omitempty" json:"jira_api_url,omitempty"`
SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"` SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"` SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"`
SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"` SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"`
SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"` SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"`
SMTPAuthPassword Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"` SMTPAuthPassword Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"`
SMTPAuthPasswordFile string `yaml:"smtp_auth_password_file,omitempty" json:"smtp_auth_password_file,omitempty"` SMTPAuthPasswordFile string `yaml:"smtp_auth_password_file,omitempty" json:"smtp_auth_password_file,omitempty"`
SMTPAuthSecret Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"` SMTPAuthSecret Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"`
SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"` SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"`
SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls,omitempty"` SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls,omitempty"`
SMTPTLSConfig *commoncfg.TLSConfig `yaml:"smtp_tls_config,omitempty" json:"smtp_tls_config,omitempty"` SMTPTLSConfig *commoncfg.TLSConfig `yaml:"smtp_tls_config,omitempty" json:"smtp_tls_config,omitempty"`
SlackAPIURL *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"` SlackAPIURL *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"`
SlackAPIURLFile string `yaml:"slack_api_url_file,omitempty" json:"slack_api_url_file,omitempty"` SlackAPIURLFile string `yaml:"slack_api_url_file,omitempty" json:"slack_api_url_file,omitempty"`
PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"` PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"`
OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"` OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"`
OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"` OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"`
OpsGenieAPIKeyFile string `yaml:"opsgenie_api_key_file,omitempty" json:"opsgenie_api_key_file,omitempty"` OpsGenieAPIKeyFile string `yaml:"opsgenie_api_key_file,omitempty" json:"opsgenie_api_key_file,omitempty"`
WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"` WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"`
WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"` WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"`
WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"` WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"`
VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"` VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"` 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"` 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"` TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"`
WebexAPIURL *URL `yaml:"webex_api_url,omitempty" json:"webex_api_url,omitempty"` WebexAPIURL *URL `yaml:"webex_api_url,omitempty" json:"webex_api_url,omitempty"`
RocketchatAPIURL *URL `yaml:"rocketchat_api_url,omitempty" json:"rocketchat_api_url,omitempty"`
RocketchatToken *Secret `yaml:"rocketchat_token,omitempty" json:"rocketchat_token,omitempty"`
RocketchatTokenFile string `yaml:"rocketchat_token_file,omitempty" json:"rocketchat_token_file,omitempty"`
RocketchatTokenID *Secret `yaml:"rocketchat_token_id,omitempty" json:"rocketchat_token_id,omitempty"`
RocketchatTokenIDFile string `yaml:"rocketchat_token_id_file,omitempty" json:"rocketchat_token_id_file,omitempty"`
} }
// UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig. // UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig.
@ -933,21 +972,22 @@ type Receiver struct {
// A unique identifier for this receiver. // A unique identifier for this receiver.
Name string `yaml:"name" json:"name"` Name string `yaml:"name" json:"name"`
DiscordConfigs []*DiscordConfig `yaml:"discord_configs,omitempty" json:"discord_configs,omitempty"` DiscordConfigs []*DiscordConfig `yaml:"discord_configs,omitempty" json:"discord_configs,omitempty"`
EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"` EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"`
PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"` PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"` SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"` WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"`
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"` OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"` WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"`
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"` PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"` VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"` SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"`
TelegramConfigs []*TelegramConfig `yaml:"telegram_configs,omitempty" json:"telegram_configs,omitempty"` TelegramConfigs []*TelegramConfig `yaml:"telegram_configs,omitempty" json:"telegram_configs,omitempty"`
WebexConfigs []*WebexConfig `yaml:"webex_configs,omitempty" json:"webex_configs,omitempty"` WebexConfigs []*WebexConfig `yaml:"webex_configs,omitempty" json:"webex_configs,omitempty"`
MSTeamsConfigs []*MSTeamsConfig `yaml:"msteams_configs,omitempty" json:"msteams_configs,omitempty"` MSTeamsConfigs []*MSTeamsConfig `yaml:"msteams_configs,omitempty" json:"msteams_configs,omitempty"`
MSTeamsV2Configs []*MSTeamsV2Config `yaml:"msteamsv2_configs,omitempty" json:"msteamsv2_configs,omitempty"` MSTeamsV2Configs []*MSTeamsV2Config `yaml:"msteamsv2_configs,omitempty" json:"msteamsv2_configs,omitempty"`
JiraConfigs []*JiraConfig `yaml:"jira_configs,omitempty" json:"jira_configs,omitempty"` JiraConfigs []*JiraConfig `yaml:"jira_configs,omitempty" json:"jira_configs,omitempty"`
RocketchatConfigs []*RocketchatConfig `yaml:"rocketchat_configs,omitempty" json:"rocketchat_configs,omitempty"`
} }
// UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver. // UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver.

View File

@ -872,14 +872,15 @@ func TestEmptyFieldsAndRegex(t *testing.T) {
SMTPTLSConfig: &commoncfg.TLSConfig{ SMTPTLSConfig: &commoncfg.TLSConfig{
InsecureSkipVerify: false, InsecureSkipVerify: false,
}, },
SlackAPIURL: (*SecretURL)(mustParseURL("http://slack.example.com/")), SlackAPIURL: (*SecretURL)(mustParseURL("http://slack.example.com/")),
SMTPRequireTLS: true, SMTPRequireTLS: true,
PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"), PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"),
OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"), OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"),
WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"), WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"),
VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"), VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"),
TelegramAPIUrl: mustParseURL("https://api.telegram.org"), TelegramAPIUrl: mustParseURL("https://api.telegram.org"),
WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"), WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"),
RocketchatAPIURL: mustParseURL("https://open.rocket.chat/"),
}, },
Templates: []string{ Templates: []string{
@ -1203,6 +1204,100 @@ func TestInvalidSNSConfig(t *testing.T) {
} }
} }
func TestRocketchatDefaultToken(t *testing.T) {
conf, err := LoadFile("testdata/conf.rocketchat-default-token.yml")
if err != nil {
t.Fatalf("Error parsing %s: %s", "testdata/conf.rocketchat-default-token.yml", err)
}
defaultToken := conf.Global.RocketchatToken
overrideToken := Secret("token456")
if defaultToken != conf.Receivers[0].RocketchatConfigs[0].Token {
t.Fatalf("Invalid rocketchat key: %s\nExpected: %s", string(*conf.Receivers[0].RocketchatConfigs[0].Token), string(*defaultToken))
}
if overrideToken != *conf.Receivers[1].RocketchatConfigs[0].Token {
t.Errorf("Invalid rocketchat key: %s\nExpected: %s", string(*conf.Receivers[0].RocketchatConfigs[0].Token), string(overrideToken))
}
}
func TestRocketchatDefaultTokenID(t *testing.T) {
conf, err := LoadFile("testdata/conf.rocketchat-default-token.yml")
if err != nil {
t.Fatalf("Error parsing %s: %s", "testdata/conf.rocketchat-default-token.yml", err)
}
defaultTokenID := conf.Global.RocketchatTokenID
overrideTokenID := Secret("id456")
if defaultTokenID != conf.Receivers[0].RocketchatConfigs[0].TokenID {
t.Fatalf("Invalid rocketchat key: %s\nExpected: %s", string(*conf.Receivers[0].RocketchatConfigs[0].TokenID), string(*defaultTokenID))
}
if overrideTokenID != *conf.Receivers[1].RocketchatConfigs[0].TokenID {
t.Errorf("Invalid rocketchat key: %s\nExpected: %s", string(*conf.Receivers[0].RocketchatConfigs[0].TokenID), string(overrideTokenID))
}
}
func TestRocketchatDefaultTokenFile(t *testing.T) {
conf, err := LoadFile("testdata/conf.rocketchat-default-token-file.yml")
if err != nil {
t.Fatalf("Error parsing %s: %s", "testdata/conf.rocketchat-default-token-file.yml", err)
}
defaultTokenFile := conf.Global.RocketchatTokenFile
overrideTokenFile := "/override_file"
if defaultTokenFile != conf.Receivers[0].RocketchatConfigs[0].TokenFile {
t.Fatalf("Invalid Rocketchat key_file: %s\nExpected: %s", conf.Receivers[0].RocketchatConfigs[0].TokenFile, defaultTokenFile)
}
if overrideTokenFile != conf.Receivers[1].RocketchatConfigs[0].TokenFile {
t.Errorf("Invalid Rocketchat key_file: %s\nExpected: %s", conf.Receivers[0].RocketchatConfigs[0].TokenFile, overrideTokenFile)
}
}
func TestRocketchatDefaultIDTokenFile(t *testing.T) {
conf, err := LoadFile("testdata/conf.rocketchat-default-token-file.yml")
if err != nil {
t.Fatalf("Error parsing %s: %s", "testdata/conf.rocketchat-default-token-file.yml", err)
}
defaultTokenIDFile := conf.Global.RocketchatTokenIDFile
overrideTokenIDFile := "/override_file"
if defaultTokenIDFile != conf.Receivers[0].RocketchatConfigs[0].TokenIDFile {
t.Fatalf("Invalid Rocketchat key_file: %s\nExpected: %s", conf.Receivers[0].RocketchatConfigs[0].TokenIDFile, defaultTokenIDFile)
}
if overrideTokenIDFile != conf.Receivers[1].RocketchatConfigs[0].TokenIDFile {
t.Errorf("Invalid Rocketchat key_file: %s\nExpected: %s", conf.Receivers[0].RocketchatConfigs[0].TokenIDFile, overrideTokenIDFile)
}
}
func TestRocketchatBothTokenAndTokenFile(t *testing.T) {
_, err := LoadFile("testdata/conf.rocketchat-both-token-and-tokenfile.yml")
if err == nil {
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.rocketchat-both-token-and-tokenfile.yml", err)
}
if err.Error() != "at most one of rocketchat_token & rocketchat_token_file must be configured" {
t.Errorf("Expected: %s\nGot: %s", "at most one of rocketchat_token & rocketchat_token_file must be configured", err.Error())
}
}
func TestRocketchatBothTokenIDAndTokenIDFile(t *testing.T) {
_, err := LoadFile("testdata/conf.rocketchat-both-tokenid-and-tokenidfile.yml")
if err == nil {
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.rocketchat-both-tokenid-and-tokenidfile.yml", err)
}
if err.Error() != "at most one of rocketchat_token_id & rocketchat_token_id_file must be configured" {
t.Errorf("Expected: %s\nGot: %s", "at most one of rocketchat_token_id & rocketchat_token_id_file must be configured", err.Error())
}
}
func TestRocketchatNoToken(t *testing.T) {
_, err := LoadFile("testdata/conf.rocketchat-no-token.yml")
if err == nil {
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.rocketchat-no-token.yml", err)
}
if err.Error() != "no global Rocketchat Token set either inline or in a file" {
t.Errorf("Expected: %s\nGot: %s", "no global Rocketchat Token set either inline or in a file", err.Error())
}
}
func TestUnmarshalHostPort(t *testing.T) { func TestUnmarshalHostPort(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
in string in string

View File

@ -99,6 +99,18 @@ var (
CallbackID: `{{ template "slack.default.callbackid" . }}`, CallbackID: `{{ template "slack.default.callbackid" . }}`,
Footer: `{{ template "slack.default.footer" . }}`, Footer: `{{ template "slack.default.footer" . }}`,
} }
// DefaultRocketchatConfig defines default values for Rocketchat configurations.
DefaultRocketchatConfig = RocketchatConfig{
NotifierConfig: NotifierConfig{
VSendResolved: false,
},
Color: `{{ if eq .Status "firing" }}red{{ else }}green{{ end }}`,
Emoji: `{{ template "rocketchat.default.emoji" . }}`,
IconURL: `{{ template "rocketchat.default.iconurl" . }}`,
Text: `{{ template "rocketchat.default.text" . }}`,
Title: `{{ template "rocketchat.default.title" . }}`,
TitleLink: `{{ template "rocketchat.default.titlelink" . }}`,
}
// DefaultOpsGenieConfig defines default values for OpsGenie configurations. // DefaultOpsGenieConfig defines default values for OpsGenie configurations.
DefaultOpsGenieConfig = OpsGenieConfig{ DefaultOpsGenieConfig = OpsGenieConfig{
@ -907,6 +919,73 @@ func (c *JiraConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
if c.IssueType == "" { if c.IssueType == "" {
return errors.New("missing issue_type in jira_config") return errors.New("missing issue_type in jira_config")
} }
return nil
}
type RocketchatAttachmentField struct {
Short *bool `json:"short"`
Title string `json:"title,omitempty"`
Value string `json:"value,omitempty"`
}
const (
ProcessingTypeSendMessage = "sendMessage"
ProcessingTypeRespondWithMessage = "respondWithMessage"
)
type RocketchatAttachmentAction struct {
Type string `json:"type,omitempty"`
Text string `json:"text,omitempty"`
URL string `json:"url,omitempty"`
ImageURL string `json:"image_url,omitempty"`
IsWebView bool `json:"is_webview"`
WebviewHeightRatio string `json:"webview_height_ratio,omitempty"`
Msg string `json:"msg,omitempty"`
MsgInChatWindow bool `json:"msg_in_chat_window"`
MsgProcessingType string `json:"msg_processing_type,omitempty"`
}
// RocketchatConfig configures notifications via Rocketchat.
type RocketchatConfig 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"`
TokenID *Secret `yaml:"token_id,omitempty" json:"token_id,omitempty"`
TokenIDFile string `yaml:"token_id_file,omitempty" json:"token_id_file,omitempty"`
Token *Secret `yaml:"token,omitempty" json:"token,omitempty"`
TokenFile string `yaml:"token_file,omitempty" json:"token_file,omitempty"`
// RocketChat channel override, (like #other-channel or @username).
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"`
Text string `yaml:"text,omitempty" json:"text,omitempty"`
Fields []*RocketchatAttachmentField `yaml:"fields,omitempty" json:"fields,omitempty"`
ShortFields bool `yaml:"short_fields" json:"short_fields,omitempty"`
Emoji string `yaml:"emoji,omitempty" json:"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"`
Actions []*RocketchatAttachmentAction `yaml:"actions,omitempty" json:"actions,omitempty"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *RocketchatConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultRocketchatConfig
type plain RocketchatConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Token != nil && len(c.TokenFile) > 0 {
return errors.New("at most one of token & token_file must be configured")
}
if c.TokenID != nil && len(c.TokenIDFile) > 0 {
return errors.New("at most one of token_id & token_id_file must be configured")
}
return nil return nil
} }

View File

@ -28,6 +28,7 @@ import (
"github.com/prometheus/alertmanager/notify/opsgenie" "github.com/prometheus/alertmanager/notify/opsgenie"
"github.com/prometheus/alertmanager/notify/pagerduty" "github.com/prometheus/alertmanager/notify/pagerduty"
"github.com/prometheus/alertmanager/notify/pushover" "github.com/prometheus/alertmanager/notify/pushover"
"github.com/prometheus/alertmanager/notify/rocketchat"
"github.com/prometheus/alertmanager/notify/slack" "github.com/prometheus/alertmanager/notify/slack"
"github.com/prometheus/alertmanager/notify/sns" "github.com/prometheus/alertmanager/notify/sns"
"github.com/prometheus/alertmanager/notify/telegram" "github.com/prometheus/alertmanager/notify/telegram"
@ -100,6 +101,9 @@ func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logg
for i, c := range nc.JiraConfigs { for i, c := range nc.JiraConfigs {
add("jira", i, c, func(l log.Logger) (notify.Notifier, error) { return jira.New(c, tmpl, l, httpOpts...) }) add("jira", i, c, func(l log.Logger) (notify.Notifier, error) { return jira.New(c, tmpl, l, httpOpts...) })
} }
for i, c := range nc.RocketchatConfigs {
add("rocketchat", i, c, func(l log.Logger) (notify.Notifier, error) { return rocketchat.New(c, tmpl, l, httpOpts...) })
}
if errs.Len() > 0 { if errs.Len() > 0 {
return nil, &errs return nil, &errs

View File

@ -0,0 +1,20 @@
global:
rocketchat_token_file: /global_file
rocketchat_token: token123
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: team-Y-rocketchat
routes:
- match:
service: foo
receiver: team-X-rocketchat
receivers:
- name: 'team-X-rocketchat'
rocketchat_configs:
- channel: '#team-X'
- name: 'team-Y-rocketchat'
rocketchat_configs:
- channel: '#team-Y'

View File

@ -0,0 +1,20 @@
global:
rocketchat_token_id_file: /global_file
rocketchat_token_id: id123
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: team-Y-rocketchat
routes:
- match:
service: foo
receiver: team-X-rocketchat
receivers:
- name: 'team-X-rocketchat'
rocketchat_configs:
- channel: '#team-X'
- name: 'team-Y-rocketchat'
rocketchat_configs:
- channel: '#team-Y'

View File

@ -0,0 +1,22 @@
global:
rocketchat_token_file: /global_file
rocketchat_token_id_file: /etc/alertmanager/rocketchat_token_id
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: team-Y-rocketchat
routes:
- match:
service: foo
receiver: team-X-rocketchat
receivers:
- name: 'team-X-rocketchat'
rocketchat_configs:
- channel: '#team-X'
- name: 'team-Y-rocketchat'
rocketchat_configs:
- channel: '#team-Y'
token_file: /override_file
token_id_file: /override_file

View File

@ -0,0 +1,22 @@
global:
rocketchat_token: token123
rocketchat_token_id: id123
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: team-Y-rocketchat
routes:
- match:
service: foo
receiver: team-X-rocketchat
receivers:
- name: 'team-X-rocketchat'
rocketchat_configs:
- channel: '#team-X'
- name: 'team-Y-rocketchat'
rocketchat_configs:
- channel: '#team-Y'
token: token456
token_id: id456

View File

@ -0,0 +1,19 @@
global:
rocketchat_token_id: id123
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: team-Y-rocketchat
routes:
- match:
service: foo
receiver: team-X-rocketchat
receivers:
- name: 'team-X-rocketchat'
rocketchat_configs:
- channel: '#team-X'
- name: 'team-Y-rocketchat'
rocketchat_configs:
- channel: '#team-Y'

View File

@ -97,7 +97,7 @@ global:
# The default TLS configuration for SMTP receivers # The default TLS configuration for SMTP receivers
[ smtp_tls_config: <tls_config> ] [ smtp_tls_config: <tls_config> ]
# Default settings for the JIRA integration. # Default settings for the JIRA integration.
[ jira_api_url: <string> ] [ jira_api_url: <string> ]
# The API URL to use for Slack notifications. # The API URL to use for Slack notifications.
@ -110,6 +110,11 @@ global:
[ opsgenie_api_key: <secret> ] [ opsgenie_api_key: <secret> ]
[ opsgenie_api_key_file: <filepath> ] [ opsgenie_api_key_file: <filepath> ]
[ opsgenie_api_url: <string> | default = "https://api.opsgenie.com/" ] [ opsgenie_api_url: <string> | default = "https://api.opsgenie.com/" ]
[ rocketchat_api_url: <string> | default = "https://open.rocket.chat/" ]
[ rocketchat_token: <secret> ]
[ rocketchat_token_file: <filepath> ]
[ rocketchat_token_id: <secret> ]
[ rocketchat_token_id_file: <filepath> ]
[ wechat_api_url: <string> | default = "https://qyapi.weixin.qq.com/cgi-bin/" ] [ wechat_api_url: <string> | default = "https://qyapi.weixin.qq.com/cgi-bin/" ]
[ wechat_api_secret: <secret> ] [ wechat_api_secret: <secret> ]
[ wechat_api_corp_id: <string> ] [ wechat_api_corp_id: <string> ]
@ -710,6 +715,8 @@ pagerduty_configs:
[ - <pagerduty_config>, ... ] [ - <pagerduty_config>, ... ]
pushover_configs: pushover_configs:
[ - <pushover_config>, ... ] [ - <pushover_config>, ... ]
rocket_configs:
[ - <rocketchat_config>, ... ]
slack_configs: slack_configs:
[ - <slack_config>, ... ] [ - <slack_config>, ... ]
sns_configs: sns_configs:
@ -981,7 +988,7 @@ Microsoft Teams v2 notifications using the new message format with adaptive card
JIRA notifications are sent via [JIRA Rest API v2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/) JIRA notifications are sent via [JIRA Rest API v2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/)
or [JIRA REST API v3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#version). or [JIRA REST API v3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#version).
Note: This integration is only tested against a Jira Cloud instance. Note: This integration is only tested against a Jira Cloud instance.
Jira Data Center (on premise instance) can work, but it's not guaranteed. Jira Data Center (on premise instance) can work, but it's not guaranteed.
Both APIs have the same feature set. The difference is that V2 supports [Wiki Markup](https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=all) Both APIs have the same feature set. The difference is that V2 supports [Wiki Markup](https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=all)
@ -1006,7 +1013,7 @@ project: <string>
[ description: <tmpl_string> | default = '{{ template "jira.default.description" . }}' ] [ description: <tmpl_string> | default = '{{ template "jira.default.description" . }}' ]
# Labels to be added to the issue. # Labels to be added to the issue.
labels: labels:
[ - <tmpl_string> ... ] [ - <tmpl_string> ... ]
# Priority of the issue. # Priority of the issue.
@ -1283,6 +1290,62 @@ token_file: <filepath>
[ http_config: <http_config> | default = global.http_config ] [ http_config: <http_config> | default = global.http_config ]
``` ```
### `<rocketchat_config>`
Rocketchat notifications are sent via the [Rocketchat REST API](https://developer.rocket.chat/reference/api/rest-api/endpoints/messaging/chat-endpoints/postmessage).
```yaml
# Whether to notify about resolved alerts.
[ send_resolved: <boolean> | default = true ]
[ api_url: <string> | default = global.rocketchat_api_url ]
[ channel: <tmpl_string> | default = global.rocketchat_api_url ]
# The sender token and token_id
# See https://docs.rocket.chat/use-rocket.chat/user-guides/user-panel/my-account#personal-access-tokens
# token and token_file are mutually exclusive.
# token_id and token_id_file are mutually exclusive.
token: <secret>
token_file: <filepath>
token_id: <secret>
token_id_file: <filepath>
[ color: <tmpl_string | default '{{ if eq .Status "firing" }}red{{ else }}green{{ end }}' ]
[ emoji <tmpl_string | default = '{{ template "rocketchat.default.emoji" . }}'
[ icon_url <tmpl_string | default = '{{ template "rocketchat.default.iconurl" . }}'
[ text <tmpl_string | default = '{{ template "rocketchat.default.text" . }}'
[ title <tmpl_string | default = '{{ template "rocketchat.default.title" . }}'
[ titleLink <tmpl_string | default = '{{ template "rocketchat.default.titlelink" . }}'
[ text: <tmpl_string | default = '{{ template "rocketchat.default.text" . }}'
fields:
[ <rocketchat_field_config> ... ]
[ image_url <tmpl_string> ]
[ thumb_url <tmpl_string> ]
[ link_names <tmpl_string> ]
[ short_fields: <boolean> | default = false ]
actions:
[ <rocketchat_action_config> ... ]
```
#### `<rocketchat_field_config>`
The fields are documented in the [Rocketchat API documentation](https://developer.rocket.chat/reference/api/rest-api/endpoints/messaging/chat-endpoints/postmessage#attachment-field-objects).
```yaml
[ title: <tmpl_string> ]
[ value: <tmpl_string> ]
[ short: <boolean> | default = rocketchat_config.short_fields ]
```
#### `<rocketchat_action_config>`
The fields are documented in the [Rocketchat API api models](https://github.com/RocketChat/Rocket.Chat.Go.SDK/blob/master/models/message.go).
```yaml
[ type: <tmpl_string> | ignored, only "button" is supported ]
[ text: <tmpl_string> ]
[ url: <tmpl_string> ]
[ msg: <tmpl_string> ]
### `<slack_config>` ### `<slack_config>`
Slack notifications can be sent via [Incoming webhooks](https://api.slack.com/messaging/webhooks) or [Bot tokens](https://api.slack.com/authentication/token-types). Slack notifications can be sent via [Incoming webhooks](https://api.slack.com/messaging/webhooks) or [Bot tokens](https://api.slack.com/authentication/token-types).

View File

@ -367,6 +367,7 @@ func (m *Metrics) InitializeFor(receiver map[string][]Integration) {
"msteams", "msteams",
"msteamsv2", "msteamsv2",
"jira", "jira",
"rocketchat",
} { } {
m.numNotifications.WithLabelValues(integration) m.numNotifications.WithLabelValues(integration)
m.numNotificationRequestsTotal.WithLabelValues(integration) m.numNotificationRequestsTotal.WithLabelValues(integration)

View File

@ -0,0 +1,270 @@
// Copyright 2022 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 rocketchat
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
commoncfg "github.com/prometheus/common/config"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
)
const maxTitleLenRunes = 1024
type Notifier struct {
conf *config.RocketchatConfig
tmpl *template.Template
logger log.Logger
client *http.Client
retrier *notify.Retrier
token string
tokenID string
postJSONFunc func(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error)
}
// PostMessage Payload for postmessage rest API
//
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
type Attachment struct {
Title string `json:"title,omitempty"`
TitleLink string `json:"title_link,omitempty"`
Text string `json:"text,omitempty"`
ImageURL string `json:"image_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
Color string `json:"color,omitempty"`
Fields []config.RocketchatAttachmentField `json:"fields,omitempty"`
Actions []config.RocketchatAttachmentAction `json:"actions,omitempty"`
}
// PostMessage Payload for postmessage rest API
//
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
type PostMessage struct {
Channel string `json:"channel,omitempty"`
Text string `json:"text,omitempty"`
ParseUrls bool `json:"parseUrls,omitempty"`
Alias string `json:"alias,omitempty"`
Emoji string `json:"emoji,omitempty"`
Avatar string `json:"avatar,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
Actions []config.RocketchatAttachmentAction `json:"actions,omitempty"`
}
type rocketchatRoundTripper struct {
wrapped http.RoundTripper
token string
tokenID string
}
func (t *rocketchatRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) {
req.Header.Set("X-Auth-Token", t.token)
req.Header.Set("X-User-Id", t.tokenID)
return t.wrapped.RoundTrip(req)
}
// New returns a new Rocketchat notification handler.
func New(c *config.RocketchatConfig, t *template.Template, l log.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
client, err := commoncfg.NewClientFromConfig(*c.HTTPConfig, "rocketchat", httpOpts...)
if err != nil {
return nil, err
}
token, err := getToken(c)
if err != nil {
return nil, err
}
tokenID, err := getTokenID(c)
if err != nil {
return nil, err
}
client.Transport = &rocketchatRoundTripper{wrapped: client.Transport, token: token, tokenID: tokenID}
return &Notifier{
conf: c,
tmpl: t,
logger: l,
client: client,
retrier: &notify.Retrier{},
postJSONFunc: notify.PostJSON,
token: token,
tokenID: tokenID,
}, nil
}
func getTokenID(c *config.RocketchatConfig) (string, error) {
if len(c.TokenIDFile) > 0 {
content, err := os.ReadFile(c.TokenIDFile)
if err != nil {
return "", fmt.Errorf("could not read %s: %w", c.TokenIDFile, err)
}
return strings.TrimSpace(string(content)), nil
}
return string(*c.TokenID), nil
}
func getToken(c *config.RocketchatConfig) (string, error) {
if len(c.TokenFile) > 0 {
content, err := os.ReadFile(c.TokenFile)
if err != nil {
return "", fmt.Errorf("could not read %s: %w", c.TokenFile, err)
}
return strings.TrimSpace(string(content)), nil
}
return string(*c.Token), nil
}
// Notify implements the Notifier interface.
func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
var err error
data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger)
tmplText := notify.TmplText(n.tmpl, data, &err)
if err != nil {
return false, err
}
title := tmplText(n.conf.Title)
if err != nil {
return false, err
}
title, truncated := notify.TruncateInRunes(title, maxTitleLenRunes)
if truncated {
key, err := notify.ExtractGroupKey(ctx)
if err != nil {
return false, err
}
level.Warn(n.logger).Log("msg", "Truncated title", "key", key, "max_runes", maxTitleLenRunes)
}
att := &Attachment{
Title: title,
TitleLink: tmplText(n.conf.TitleLink),
Text: tmplText(n.conf.Text),
ImageURL: tmplText(n.conf.ImageURL),
ThumbURL: tmplText(n.conf.ThumbURL),
Color: tmplText(n.conf.Color),
}
numFields := len(n.conf.Fields)
if numFields > 0 {
fields := make([]config.RocketchatAttachmentField, numFields)
for index, field := range n.conf.Fields {
// Check if short was defined for the field otherwise fallback to the global setting
var short bool
if field.Short != nil {
short = *field.Short
} else {
short = n.conf.ShortFields
}
// Rebuild the field by executing any templates and setting the new value for short
fields[index] = config.RocketchatAttachmentField{
Title: tmplText(field.Title),
Value: tmplText(field.Value),
Short: &short,
}
}
att.Fields = fields
}
numActions := len(n.conf.Actions)
if numActions > 0 {
actions := make([]config.RocketchatAttachmentAction, numActions)
for index, action := range n.conf.Actions {
actions[index] = config.RocketchatAttachmentAction{
Type: "button", // Only button type is supported
Text: tmplText(action.Text),
URL: tmplText(action.URL),
Msg: tmplText(action.Msg),
}
}
att.Actions = actions
}
body := &PostMessage{
Channel: n.conf.Channel,
Emoji: tmplText(n.conf.Emoji),
Avatar: tmplText(n.conf.IconURL),
Attachments: []Attachment{*att},
}
if err != nil {
return false, err
}
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(body); err != nil {
return false, err
}
url := n.conf.APIURL.JoinPath("api/v1/chat.postMessage").String()
resp, err := n.postJSONFunc(ctx, n.client, url, &buf)
if err != nil {
return true, notify.RedactURL(err)
}
defer notify.Drain(resp)
// Use a retrier to generate an error message for non-200 responses and
// classify them as retriable or not.
retry, err := n.retrier.Check(resp.StatusCode, resp.Body)
if err != nil {
err = fmt.Errorf("channel %q: %w", body.Channel, err)
return retry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err)
}
// Rocketchat web API might return errors with a 200 response code.
retry, err = checkResponseError(resp)
if err != nil {
err = fmt.Errorf("channel %q: %w", body.Channel, err)
return retry, notify.NewErrorWithReason(notify.ClientErrorReason, err)
}
return retry, nil
}
// checkResponseError parses out the error message from Rocketchat API response.
func checkResponseError(resp *http.Response) (bool, error) {
body, err := io.ReadAll(resp.Body)
if err != nil {
return true, fmt.Errorf("could not read response body: %w", err)
}
return checkJSONResponseError(body)
}
// checkJSONResponseError classifies JSON responses from Rocketchat.
func checkJSONResponseError(body []byte) (bool, error) {
// response is for parsing out errors from the JSON response.
type response struct {
Success bool `json:"success"`
Error string `json:"error"`
}
var data response
if err := json.Unmarshal(body, &data); err != nil {
return true, fmt.Errorf("could not unmarshal JSON response %q: %w", string(body), err)
}
if !data.Success {
return false, fmt.Errorf("error response from Rocketchat: %s", data.Error)
}
return false, nil
}

View File

@ -0,0 +1,66 @@
// 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 rocketchat
import (
"fmt"
"net/url"
"os"
"testing"
"github.com/go-kit/log"
commoncfg "github.com/prometheus/common/config"
"github.com/stretchr/testify/require"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/notify/test"
)
func TestRocketchatRetry(t *testing.T) {
secret := config.Secret("xxxxx")
notifier, err := New(
&config.RocketchatConfig{
HTTPConfig: &commoncfg.HTTPClientConfig{},
Token: &secret,
TokenID: &secret,
},
test.CreateTmpl(t),
log.NewNopLogger(),
)
require.NoError(t, err)
for statusCode, expected := range test.RetryTests(test.DefaultRetryCodes()) {
actual, _ := notifier.retrier.Check(statusCode, nil)
require.Equal(t, expected, actual, fmt.Sprintf("error on status %d", statusCode))
}
}
func TestGettingRocketchatTokenFromFile(t *testing.T) {
f, err := os.CreateTemp("", "rocketchat_test")
require.NoError(t, err, "creating temp file failed")
_, err = f.WriteString("secret")
require.NoError(t, err, "writing to temp file failed")
_, err = New(
&config.RocketchatConfig{
TokenFile: f.Name(),
TokenIDFile: f.Name(),
HTTPConfig: &commoncfg.HTTPClientConfig{},
APIURL: &config.URL{URL: &url.URL{Scheme: "http", Host: "example.com", Path: "/api/v1/"}},
},
test.CreateTmpl(t),
log.NewNopLogger(),
)
require.NoError(t, err)
}

View File

@ -209,3 +209,10 @@ Alerts Resolved:
{{- end -}} {{- end -}}
{{- $priority -}} {{- $priority -}}
{{- end -}} {{- end -}}
{{ define "rocketchat.default.title" }}{{ template "__subject" . }}{{ end }}
{{ define "rocketchat.default.alias" }}{{ template "__alertmanager" . }}{{ end }}
{{ define "rocketchat.default.titlelink" }}{{ template "__alertmanagerURL" . }}{{ end }}
{{ define "rocketchat.default.emoji" }}{{ end }}
{{ define "rocketchat.default.iconurl" }}{{ end }}
{{ define "rocketchat.default.text" }}{{ end }}