feat(3920): add msteamsv2 receiver (#4024)
* feat(3920): add msteamsv2 receiver Signed-off-by: Simon Schneider <github@simon-schneider.eu> * Don't use `fmt.Errorf` when there's no formatting required on `config/config.go` Signed-off-by: gotjosh <josue.abreu@gmail.com> * Don't use `fmt.Errorf` when there's no formatting required on `config/notifiers.go` Signed-off-by: gotjosh <josue.abreu@gmail.com> * Remove additional documentation steps Signed-off-by: gotjosh <josue.abreu@gmail.com> * add more info to the documentation Signed-off-by: gotjosh <josue.abreu@gmail.com> * Change documentation links to convey the message better Signed-off-by: gotjosh <josue.abreu@gmail.com> --------- Signed-off-by: Simon Schneider <github@simon-schneider.eu> Signed-off-by: gotjosh <josue.abreu@gmail.com> Co-authored-by: gotjosh <josue.abreu@gmail.com>
This commit is contained in:
parent
3d66826261
commit
d4009f5e93
|
@ -163,9 +163,9 @@ var Assets = func() http.FileSystem {
|
|||
"/templates/default.tmpl": &vfsgen۰CompressedFileInfo{
|
||||
name: "default.tmpl",
|
||||
modTime: time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC),
|
||||
uncompressedSize: 7283,
|
||||
uncompressedSize: 7656,
|
||||
|
||||
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x59\xcd\x6e\xeb\x36\x13\xdd\xfb\x29\x06\xba\xdf\x22\x5e\x44\xf7\x5b\x07\x08\x8a\x8b\xa2\x3f\x8b\xb4\x28\x1c\xa4\x9b\xa2\x10\x18\x69\xac\x30\xa1\x48\x85\x1c\xd9\x31\x1c\xbd\x7b\x41\x49\x96\x45\x49\xb6\x29\x5f\x77\x55\xef\x62\x7a\xe6\x9c\xe1\x39\xa3\x21\xe5\x6c\xb7\x90\xe0\x92\x4b\x84\x20\x8a\x98\x40\x4d\x19\x93\x2c\x45\x1d\x40\x59\x7e\xeb\x7c\xde\x6e\x01\x65\x02\x65\x39\x3b\x98\xf2\xb4\x78\xb0\x59\xdb\x2d\x84\x3f\x7d\x10\x6a\xc9\xc4\xd3\xe2\x01\xca\xf2\xeb\x97\xaf\x55\x9c\xf9\x41\x63\x8c\x7c\x85\xfa\xde\x06\x2d\x9a\x0f\xf0\x09\x85\x16\xef\x05\xea\x4d\x9d\xde\x10\xb9\x4c\xa6\x78\x7e\xc5\x98\x2c\xc3\x5f\x36\xfb\x91\x18\x15\x06\x3e\x81\xd4\x53\x9e\xa3\xae\x53\xf9\x12\xf0\xbd\xfd\x32\x58\x72\xcd\x65\x6a\x73\xee\x6c\x4e\xb5\x21\x13\xfe\x5c\xad\xc2\x27\x08\x94\x5d\xc6\xbf\xc1\x06\xfd\xa2\x55\x91\x3f\xb0\x67\x14\x26\x7c\x54\x9a\x30\xf9\x83\x71\x6d\xc2\x3f\x99\x28\xd0\x12\xbe\x2a\x2e\x21\x00\x8b\x0a\x35\x65\x4a\x70\x63\xb1\xc2\x1f\x55\x96\x29\x59\x27\xcf\x9b\xb5\x0e\xde\x1c\xca\xf2\x66\xbb\x85\x35\xa7\x17\x37\x38\x5c\x60\xa6\x56\xe8\xb2\xff\xce\x32\x34\x8d\xa2\x63\xec\x6d\xe1\xf3\xf6\xaf\x03\x36\x25\x68\x62\xcd\x73\xe2\x4a\x06\x47\x34\x26\xfc\xa0\xda\xd2\x48\x70\x43\x4d\xa8\x66\x32\x45\x08\xa1\x2c\xeb\xba\xee\x66\xfb\xc5\xa1\x4e\x56\x95\xdb\x4a\x48\x5b\xbe\xfd\x74\x0f\xed\x06\x9a\xc2\x6a\xf2\x6f\x52\x2a\x62\xb6\x26\x07\xb2\xb3\x7c\x1e\xee\xa3\x2a\x74\x8c\x77\xb5\x99\x28\x51\x33\x52\xba\xee\xc4\xd9\x88\x50\x47\x25\x88\x32\xa6\xdf\x12\xb5\x96\x03\x2d\x66\xbe\x62\x78\x56\x3d\x9b\x2e\x87\x2f\xb2\x97\x20\xb3\x71\x45\x8c\x60\xf1\x5b\x98\xe0\x92\x15\x82\x42\xe2\x24\xb0\x91\x82\x30\xcb\x05\x23\xf7\xe1\x0c\x0f\xf5\xa0\x8b\x53\x18\x3b\x1e\xb2\x31\x28\x77\x08\x79\xe2\x2d\x99\x10\xcf\x2c\x7e\x1b\xe0\x8d\x96\x6f\x41\xe1\x13\x4e\x05\x0a\x2e\xdf\xbc\x2b\x88\x9b\x0a\x78\x12\xf8\x25\xe4\x1a\x6d\xaf\x79\x46\x77\x0a\x3a\xaa\x58\x35\x83\x3d\x4b\xe6\xb1\x92\x98\xa9\x57\x1e\xf8\xc7\x17\x5a\xf8\x56\xec\xbf\xb9\xa5\x52\x54\x9f\x38\x9d\x1e\xec\x86\xe7\x76\x6b\x49\x41\x9b\x36\x65\x38\xd0\xa6\xb5\xe3\x10\x31\x16\x1c\x25\x9d\xdf\x90\x87\x10\xf7\xa7\xe2\x79\x9e\x0d\x71\xb9\x34\xc4\x64\x8c\x66\x04\x77\x30\xc1\xc3\xc3\xaa\xaa\xdc\xa4\x28\x39\xb6\xc0\x19\x1a\xc3\xd2\xf3\x9e\xef\x01\xd8\xd0\xa1\xe6\xc0\x3b\x30\xd0\x46\x4f\xb8\x59\xef\x7c\x75\x0e\xf0\x39\xfc\x1f\x6e\xed\xe0\xac\x16\xa1\x5e\xac\x46\xe7\x71\x45\xdc\x5b\x40\x45\x72\xdb\xd9\xd1\x08\xdf\x02\x8d\x12\x2b\x4c\x7a\x8c\xbb\x65\x7f\xce\x5d\xc6\x80\xf5\xd6\x47\x52\x53\xcd\xf1\xe9\xdd\xe4\xb8\xbe\xc6\xf8\x85\xd1\x54\xcf\x67\x57\xff\x8e\xf8\xd7\xbd\x28\x3f\x69\x31\xc0\x1b\xf5\xe7\x80\xeb\x3d\x7f\x48\x45\xf6\xb0\x3c\x38\x49\x87\xe1\x39\xd3\xb4\x99\x10\x4f\x2c\xf5\x8d\x66\x29\x4a\x8a\xfa\x47\x9c\xdb\x5f\x2b\x1e\x93\xd2\x2a\x37\xfb\xb6\x25\x46\x18\xb9\x8d\x76\xed\xa5\x69\xb3\x60\xa8\x2a\x4a\xe2\xb4\x89\x12\x6e\x72\xc1\x36\xd1\x81\xdb\xd4\xe9\xc1\x3d\x44\xce\x94\xe4\xa4\xac\x20\x11\x29\x25\x26\x1e\x89\xce\xd9\x55\x98\x17\xb5\x42\x7d\x81\xfb\xe3\x00\xea\xdf\xef\xa7\xcb\xb4\x93\x7f\x37\x5d\xae\x99\x86\x57\xfa\x63\x4a\xee\xef\x74\x53\xce\x94\xee\x6d\x4e\x76\x1e\xf6\xfd\x6b\xfa\xf4\x77\x84\x0e\xce\xd5\xde\x29\xf6\x76\x55\x24\x14\x98\x6a\x96\x8d\x49\xf9\x9f\x15\x25\xe1\x26\x56\x3a\xb9\xc0\x20\xea\x23\x5d\xd5\xb5\xd7\x84\x67\xfc\xb8\x3e\xba\xdf\xad\x63\x66\x08\x59\xd6\x1d\xa6\x59\xc6\xf4\xe6\xac\x3e\xed\x63\x9d\xdf\xf1\x03\xa4\xe6\xcd\xde\xc7\xa6\x2f\x30\xc9\xa8\xce\xcf\x6d\xdf\xed\x58\x4b\xed\xeb\xd9\x08\xf9\x14\xf3\x5e\xb9\x66\x17\x71\xce\x01\xea\xbd\x46\x5f\x35\x9f\x55\x37\xe4\x51\xad\x72\xcd\x95\xe6\xf6\xe5\xe7\xb6\xb9\x48\xff\x6f\xb7\x04\x77\xf7\x10\x04\xbb\xfb\xf5\xee\xa7\x55\x67\xb7\x36\x07\x00\xa0\xca\x33\xb8\xc2\x5d\x1e\x97\x09\x7e\xec\x7e\xdd\x85\x60\xf7\x55\xe0\x64\xf0\x25\xdc\xe0\x7b\x27\x31\x88\x35\x27\x1e\x33\x11\xcc\xdb\xc0\x16\xbe\x2d\xeb\x1e\x82\x5f\x79\xfa\xe2\x62\xa1\x30\x58\x01\x32\x99\xf4\x51\xd7\x4c\x4b\x2e\xd3\x60\x0e\x37\x12\x3b\x40\x35\xcc\xfc\x04\xd7\x6f\x98\xf0\x22\xf3\x67\xe3\x72\xa9\x2c\x95\x5d\xdd\x53\x9d\xa4\x79\x50\xeb\x1e\x87\x4c\x5a\x4f\xba\x7f\xd7\xff\xae\xe9\x42\x3b\x69\xae\x4f\x6d\x63\x0c\xb8\x27\xb9\x35\xd9\x31\x0f\xd7\x2e\xee\x9c\x97\x7b\x97\x73\xf0\xb4\x8b\x7d\x27\x4f\x39\xbb\x47\xea\x7f\xfb\x4f\x00\x00\x00\xff\xff\x30\xb3\x3d\xcd\x73\x1c\x00\x00"),
|
||||
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"),
|
||||
},
|
||||
"/templates/email.tmpl": &vfsgen۰CompressedFileInfo{
|
||||
name: "email.tmpl",
|
||||
|
|
|
@ -263,6 +263,9 @@ func resolveFilepaths(baseDir string, cfg *Config) {
|
|||
for _, cfg := range receiver.MSTeamsConfigs {
|
||||
cfg.HTTPConfig.SetDirectory(baseDir)
|
||||
}
|
||||
for _, cfg := range receiver.MSTeamsV2Configs {
|
||||
cfg.HTTPConfig.SetDirectory(baseDir)
|
||||
}
|
||||
for _, cfg := range receiver.JiraConfigs {
|
||||
cfg.HTTPConfig.SetDirectory(baseDir)
|
||||
}
|
||||
|
@ -282,7 +285,7 @@ func (mt *MuteTimeInterval) UnmarshalYAML(unmarshal func(interface{}) error) err
|
|||
return err
|
||||
}
|
||||
if mt.Name == "" {
|
||||
return fmt.Errorf("missing name in mute time interval")
|
||||
return errors.New("missing name in mute time interval")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -300,7 +303,7 @@ func (ti *TimeInterval) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return err
|
||||
}
|
||||
if ti.Name == "" {
|
||||
return fmt.Errorf("missing name in time interval")
|
||||
return errors.New("missing name in time interval")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -346,19 +349,19 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
|
||||
if c.Global.SlackAPIURL != nil && len(c.Global.SlackAPIURLFile) > 0 {
|
||||
return fmt.Errorf("at most one of slack_api_url & slack_api_url_file must be configured")
|
||||
return errors.New("at most one of slack_api_url & slack_api_url_file must be configured")
|
||||
}
|
||||
|
||||
if c.Global.OpsGenieAPIKey != "" && len(c.Global.OpsGenieAPIKeyFile) > 0 {
|
||||
return fmt.Errorf("at most one of opsgenie_api_key & opsgenie_api_key_file must be configured")
|
||||
return errors.New("at most one of opsgenie_api_key & opsgenie_api_key_file must be configured")
|
||||
}
|
||||
|
||||
if c.Global.VictorOpsAPIKey != "" && len(c.Global.VictorOpsAPIKeyFile) > 0 {
|
||||
return fmt.Errorf("at most one of victorops_api_key & victorops_api_key_file must be configured")
|
||||
return errors.New("at most one of victorops_api_key & victorops_api_key_file must be configured")
|
||||
}
|
||||
|
||||
if len(c.Global.SMTPAuthPassword) > 0 && len(c.Global.SMTPAuthPasswordFile) > 0 {
|
||||
return fmt.Errorf("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")
|
||||
}
|
||||
|
||||
names := map[string]struct{}{}
|
||||
|
@ -378,13 +381,13 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if ec.Smarthost.String() == "" {
|
||||
if c.Global.SMTPSmarthost.String() == "" {
|
||||
return fmt.Errorf("no global SMTP smarthost set")
|
||||
return errors.New("no global SMTP smarthost set")
|
||||
}
|
||||
ec.Smarthost = c.Global.SMTPSmarthost
|
||||
}
|
||||
if ec.From == "" {
|
||||
if c.Global.SMTPFrom == "" {
|
||||
return fmt.Errorf("no global SMTP from set")
|
||||
return errors.New("no global SMTP from set")
|
||||
}
|
||||
ec.From = c.Global.SMTPFrom
|
||||
}
|
||||
|
@ -415,7 +418,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if sc.APIURL == nil && len(sc.APIURLFile) == 0 {
|
||||
if c.Global.SlackAPIURL == nil && len(c.Global.SlackAPIURLFile) == 0 {
|
||||
return fmt.Errorf("no global Slack API URL set either inline or in a file")
|
||||
return errors.New("no global Slack API URL set either inline or in a file")
|
||||
}
|
||||
sc.APIURL = c.Global.SlackAPIURL
|
||||
sc.APIURLFile = c.Global.SlackAPIURLFile
|
||||
|
@ -432,7 +435,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if pdc.URL == nil {
|
||||
if c.Global.PagerdutyURL == nil {
|
||||
return fmt.Errorf("no global PagerDuty URL set")
|
||||
return errors.New("no global PagerDuty URL set")
|
||||
}
|
||||
pdc.URL = c.Global.PagerdutyURL
|
||||
}
|
||||
|
@ -443,7 +446,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if ogc.APIURL == nil {
|
||||
if c.Global.OpsGenieAPIURL == nil {
|
||||
return fmt.Errorf("no global OpsGenie URL set")
|
||||
return errors.New("no global OpsGenie URL set")
|
||||
}
|
||||
ogc.APIURL = c.Global.OpsGenieAPIURL
|
||||
}
|
||||
|
@ -452,7 +455,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if ogc.APIKey == "" && len(ogc.APIKeyFile) == 0 {
|
||||
if c.Global.OpsGenieAPIKey == "" && len(c.Global.OpsGenieAPIKeyFile) == 0 {
|
||||
return fmt.Errorf("no global OpsGenie API Key set either inline or in a file")
|
||||
return errors.New("no global OpsGenie API Key set either inline or in a file")
|
||||
}
|
||||
ogc.APIKey = c.Global.OpsGenieAPIKey
|
||||
ogc.APIKeyFile = c.Global.OpsGenieAPIKeyFile
|
||||
|
@ -465,21 +468,21 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
|
||||
if wcc.APIURL == nil {
|
||||
if c.Global.WeChatAPIURL == nil {
|
||||
return fmt.Errorf("no global Wechat URL set")
|
||||
return errors.New("no global Wechat URL set")
|
||||
}
|
||||
wcc.APIURL = c.Global.WeChatAPIURL
|
||||
}
|
||||
|
||||
if wcc.APISecret == "" {
|
||||
if c.Global.WeChatAPISecret == "" {
|
||||
return fmt.Errorf("no global Wechat ApiSecret set")
|
||||
return errors.New("no global Wechat ApiSecret set")
|
||||
}
|
||||
wcc.APISecret = c.Global.WeChatAPISecret
|
||||
}
|
||||
|
||||
if wcc.CorpID == "" {
|
||||
if c.Global.WeChatAPICorpID == "" {
|
||||
return fmt.Errorf("no global Wechat CorpID set")
|
||||
return errors.New("no global Wechat CorpID set")
|
||||
}
|
||||
wcc.CorpID = c.Global.WeChatAPICorpID
|
||||
}
|
||||
|
@ -494,7 +497,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if voc.APIURL == nil {
|
||||
if c.Global.VictorOpsAPIURL == nil {
|
||||
return fmt.Errorf("no global VictorOps URL set")
|
||||
return errors.New("no global VictorOps URL set")
|
||||
}
|
||||
voc.APIURL = c.Global.VictorOpsAPIURL
|
||||
}
|
||||
|
@ -503,7 +506,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if voc.APIKey == "" && len(voc.APIKeyFile) == 0 {
|
||||
if c.Global.VictorOpsAPIKey == "" && len(c.Global.VictorOpsAPIKeyFile) == 0 {
|
||||
return fmt.Errorf("no global VictorOps API Key set")
|
||||
return errors.New("no global VictorOps API Key set")
|
||||
}
|
||||
voc.APIKey = c.Global.VictorOpsAPIKey
|
||||
voc.APIKeyFile = c.Global.VictorOpsAPIKeyFile
|
||||
|
@ -528,7 +531,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
discord.HTTPConfig = c.Global.HTTPConfig
|
||||
}
|
||||
if discord.WebhookURL == nil && len(discord.WebhookURLFile) == 0 {
|
||||
return fmt.Errorf("no discord webhook URL or URLFile provided")
|
||||
return errors.New("no discord webhook URL or URLFile provided")
|
||||
}
|
||||
}
|
||||
for _, webex := range rcv.WebexConfigs {
|
||||
|
@ -537,7 +540,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if webex.APIURL == nil {
|
||||
if c.Global.WebexAPIURL == nil {
|
||||
return fmt.Errorf("no global Webex URL set")
|
||||
return errors.New("no global Webex URL set")
|
||||
}
|
||||
|
||||
webex.APIURL = c.Global.WebexAPIURL
|
||||
|
@ -548,7 +551,15 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
msteams.HTTPConfig = c.Global.HTTPConfig
|
||||
}
|
||||
if msteams.WebhookURL == nil && len(msteams.WebhookURLFile) == 0 {
|
||||
return fmt.Errorf("no msteams webhook URL or URLFile provided")
|
||||
return errors.New("no msteams webhook URL or URLFile provided")
|
||||
}
|
||||
}
|
||||
for _, msteamsv2 := range rcv.MSTeamsV2Configs {
|
||||
if msteamsv2.HTTPConfig == nil {
|
||||
msteamsv2.HTTPConfig = c.Global.HTTPConfig
|
||||
}
|
||||
if msteamsv2.WebhookURL == nil && len(msteamsv2.WebhookURLFile) == 0 {
|
||||
return errors.New("no msteamsv2 webhook URL or URLFile provided")
|
||||
}
|
||||
}
|
||||
for _, jira := range rcv.JiraConfigs {
|
||||
|
@ -557,7 +568,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
if jira.APIURL == nil {
|
||||
if c.Global.JiraAPIURL == nil {
|
||||
return fmt.Errorf("no global Jira Cloud URL set")
|
||||
return errors.New("no global Jira Cloud URL set")
|
||||
}
|
||||
jira.APIURL = c.Global.JiraAPIURL
|
||||
}
|
||||
|
@ -569,20 +580,20 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
// The root route must not have any matchers as it is the fallback node
|
||||
// for all alerts.
|
||||
if c.Route == nil {
|
||||
return fmt.Errorf("no routes provided")
|
||||
return errors.New("no routes provided")
|
||||
}
|
||||
if len(c.Route.Receiver) == 0 {
|
||||
return fmt.Errorf("root route must specify a default receiver")
|
||||
return errors.New("root route must specify a default receiver")
|
||||
}
|
||||
if len(c.Route.Match) > 0 || len(c.Route.MatchRE) > 0 || len(c.Route.Matchers) > 0 {
|
||||
return fmt.Errorf("root route must not have any matchers")
|
||||
return errors.New("root route must not have any matchers")
|
||||
}
|
||||
if len(c.Route.MuteTimeIntervals) > 0 {
|
||||
return fmt.Errorf("root route must not have any mute time intervals")
|
||||
return errors.New("root route must not have any mute time intervals")
|
||||
}
|
||||
|
||||
if len(c.Route.ActiveTimeIntervals) > 0 {
|
||||
return fmt.Errorf("root route must not have any active time intervals")
|
||||
return errors.New("root route must not have any active time intervals")
|
||||
}
|
||||
|
||||
// Validate that all receivers used in the routing tree are defined.
|
||||
|
@ -685,7 +696,7 @@ func parseURL(s string) (*URL, error) {
|
|||
return nil, fmt.Errorf("unsupported scheme %q for URL", u.Scheme)
|
||||
}
|
||||
if u.Host == "" {
|
||||
return nil, fmt.Errorf("missing host for URL")
|
||||
return nil, errors.New("missing host for URL")
|
||||
}
|
||||
return &URL{u}, nil
|
||||
}
|
||||
|
@ -848,7 +859,7 @@ func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
|
||||
if len(r.GroupBy) > 0 && r.GroupByAll {
|
||||
return fmt.Errorf("cannot have wildcard group_by (`...`) and other other labels at the same time")
|
||||
return errors.New("cannot have wildcard group_by (`...`) and other other labels at the same time")
|
||||
}
|
||||
|
||||
groupBy := map[model.LabelName]struct{}{}
|
||||
|
@ -861,10 +872,10 @@ func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
|
||||
if r.GroupInterval != nil && time.Duration(*r.GroupInterval) == time.Duration(0) {
|
||||
return fmt.Errorf("group_interval cannot be zero")
|
||||
return errors.New("group_interval cannot be zero")
|
||||
}
|
||||
if r.RepeatInterval != nil && time.Duration(*r.RepeatInterval) == time.Duration(0) {
|
||||
return fmt.Errorf("repeat_interval cannot be zero")
|
||||
return errors.New("repeat_interval cannot be zero")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -935,6 +946,7 @@ type Receiver struct {
|
|||
TelegramConfigs []*TelegramConfig `yaml:"telegram_configs,omitempty" json:"telegram_configs,omitempty"`
|
||||
WebexConfigs []*WebexConfig `yaml:"webex_configs,omitempty" json:"webex_configs,omitempty"`
|
||||
MSTeamsConfigs []*MSTeamsConfig `yaml:"msteams_configs,omitempty" json:"msteams_configs,omitempty"`
|
||||
MSTeamsV2Configs []*MSTeamsV2Config `yaml:"msteamsv2_configs,omitempty" json:"msteamsv2_configs,omitempty"`
|
||||
JiraConfigs []*JiraConfig `yaml:"jira_configs,omitempty" json:"jira_configs,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -945,7 +957,7 @@ func (c *Receiver) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return err
|
||||
}
|
||||
if c.Name == "" {
|
||||
return fmt.Errorf("missing name in receiver")
|
||||
return errors.New("missing name in receiver")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/textproto"
|
||||
"regexp"
|
||||
|
@ -174,6 +175,14 @@ var (
|
|||
Text: `{{ template "msteams.default.text" . }}`,
|
||||
}
|
||||
|
||||
DefaultMSTeamsV2Config = MSTeamsV2Config{
|
||||
NotifierConfig: NotifierConfig{
|
||||
VSendResolved: true,
|
||||
},
|
||||
Title: `{{ template "msteamsv2.default.title" . }}`,
|
||||
Text: `{{ template "msteamsv2.default.text" . }}`,
|
||||
}
|
||||
|
||||
DefaultJiraConfig = JiraConfig{
|
||||
NotifierConfig: NotifierConfig{
|
||||
VSendResolved: true,
|
||||
|
@ -212,11 +221,11 @@ func (c *WebexConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
|
||||
if c.RoomID == "" {
|
||||
return fmt.Errorf("missing room_id on webex_config")
|
||||
return errors.New("missing room_id on webex_config")
|
||||
}
|
||||
|
||||
if c.HTTPConfig == nil || c.HTTPConfig.Authorization == nil {
|
||||
return fmt.Errorf("missing webex_configs.http_config.authorization")
|
||||
return errors.New("missing webex_configs.http_config.authorization")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -243,11 +252,11 @@ func (c *DiscordConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
|
||||
if c.WebhookURL == nil && c.WebhookURLFile == "" {
|
||||
return fmt.Errorf("one of webhook_url or webhook_url_file must be configured")
|
||||
return errors.New("one of webhook_url or webhook_url_file must be configured")
|
||||
}
|
||||
|
||||
if c.WebhookURL != nil && len(c.WebhookURLFile) > 0 {
|
||||
return fmt.Errorf("at most one of webhook_url & webhook_url_file must be configured")
|
||||
return errors.New("at most one of webhook_url & webhook_url_file must be configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -282,7 +291,7 @@ func (c *EmailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return err
|
||||
}
|
||||
if c.To == "" {
|
||||
return fmt.Errorf("missing to address in email config")
|
||||
return errors.New("missing to address in email config")
|
||||
}
|
||||
// Header names are case-insensitive, check for collisions.
|
||||
normalizedHeaders := map[string]string{}
|
||||
|
@ -343,13 +352,13 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
|
|||
return err
|
||||
}
|
||||
if c.RoutingKey == "" && c.ServiceKey == "" && c.RoutingKeyFile == "" && c.ServiceKeyFile == "" {
|
||||
return fmt.Errorf("missing service or routing key in PagerDuty config")
|
||||
return errors.New("missing service or routing key in PagerDuty config")
|
||||
}
|
||||
if len(c.RoutingKey) > 0 && len(c.RoutingKeyFile) > 0 {
|
||||
return fmt.Errorf("at most one of routing_key & routing_key_file must be configured")
|
||||
return errors.New("at most one of routing_key & routing_key_file must be configured")
|
||||
}
|
||||
if len(c.ServiceKey) > 0 && len(c.ServiceKeyFile) > 0 {
|
||||
return fmt.Errorf("at most one of service_key & service_key_file must be configured")
|
||||
return errors.New("at most one of service_key & service_key_file must be configured")
|
||||
}
|
||||
if c.Details == nil {
|
||||
c.Details = make(map[string]string)
|
||||
|
@ -385,10 +394,10 @@ func (c *SlackAction) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return err
|
||||
}
|
||||
if c.Type == "" {
|
||||
return fmt.Errorf("missing type in Slack action configuration")
|
||||
return errors.New("missing type in Slack action configuration")
|
||||
}
|
||||
if c.Text == "" {
|
||||
return fmt.Errorf("missing text in Slack action configuration")
|
||||
return errors.New("missing text in Slack action configuration")
|
||||
}
|
||||
if c.URL != "" {
|
||||
// Clear all message action fields.
|
||||
|
@ -398,7 +407,7 @@ func (c *SlackAction) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
} else if c.Name != "" {
|
||||
c.URL = ""
|
||||
} else {
|
||||
return fmt.Errorf("missing name or url in Slack action configuration")
|
||||
return errors.New("missing name or url in Slack action configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -420,7 +429,7 @@ func (c *SlackConfirmationField) UnmarshalYAML(unmarshal func(interface{}) error
|
|||
return err
|
||||
}
|
||||
if c.Text == "" {
|
||||
return fmt.Errorf("missing text in Slack confirmation configuration")
|
||||
return errors.New("missing text in Slack confirmation configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -442,10 +451,10 @@ func (c *SlackField) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return err
|
||||
}
|
||||
if c.Title == "" {
|
||||
return fmt.Errorf("missing title in Slack field configuration")
|
||||
return errors.New("missing title in Slack field configuration")
|
||||
}
|
||||
if c.Value == "" {
|
||||
return fmt.Errorf("missing value in Slack field configuration")
|
||||
return errors.New("missing value in Slack field configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -491,7 +500,7 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
|
||||
if c.APIURL != nil && len(c.APIURLFile) > 0 {
|
||||
return fmt.Errorf("at most one of api_url & api_url_file must be configured")
|
||||
return errors.New("at most one of api_url & api_url_file must be configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -521,10 +530,10 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return err
|
||||
}
|
||||
if c.URL == nil && c.URLFile == "" {
|
||||
return fmt.Errorf("one of url or url_file must be configured")
|
||||
return errors.New("one of url or url_file must be configured")
|
||||
}
|
||||
if c.URL != nil && c.URLFile != "" {
|
||||
return fmt.Errorf("at most one of url & url_file must be configured")
|
||||
return errors.New("at most one of url & url_file must be configured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -604,7 +613,7 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
|
|||
}
|
||||
|
||||
if c.APIKey != "" && len(c.APIKeyFile) > 0 {
|
||||
return fmt.Errorf("at most one of api_key & api_key_file must be configured")
|
||||
return errors.New("at most one of api_key & api_key_file must be configured")
|
||||
}
|
||||
|
||||
for _, r := range c.Responders {
|
||||
|
@ -663,10 +672,10 @@ func (c *VictorOpsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
|
|||
return err
|
||||
}
|
||||
if c.RoutingKey == "" {
|
||||
return fmt.Errorf("missing Routing key in VictorOps config")
|
||||
return errors.New("missing Routing key in VictorOps config")
|
||||
}
|
||||
if c.APIKey != "" && len(c.APIKeyFile) > 0 {
|
||||
return fmt.Errorf("at most one of api_key & api_key_file must be configured")
|
||||
return errors.New("at most one of api_key & api_key_file must be configured")
|
||||
}
|
||||
|
||||
reservedFields := []string{"routing_key", "message_type", "state_message", "entity_display_name", "monitoring_tool", "entity_id", "entity_state"}
|
||||
|
@ -724,16 +733,16 @@ func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
|
|||
return err
|
||||
}
|
||||
if c.UserKey == "" && c.UserKeyFile == "" {
|
||||
return fmt.Errorf("one of user_key or user_key_file must be configured")
|
||||
return errors.New("one of user_key or user_key_file must be configured")
|
||||
}
|
||||
if c.UserKey != "" && c.UserKeyFile != "" {
|
||||
return fmt.Errorf("at most one of user_key & user_key_file must be configured")
|
||||
return errors.New("at most one of user_key & user_key_file must be configured")
|
||||
}
|
||||
if c.Token == "" && c.TokenFile == "" {
|
||||
return fmt.Errorf("one of token or token_file must be configured")
|
||||
return errors.New("one of token or token_file must be configured")
|
||||
}
|
||||
if c.Token != "" && c.TokenFile != "" {
|
||||
return fmt.Errorf("at most one of token & token_file must be configured")
|
||||
return errors.New("at most one of token & token_file must be configured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -761,7 +770,7 @@ func (c *SNSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return err
|
||||
}
|
||||
if (c.TargetARN == "") != (c.TopicARN == "") != (c.PhoneNumber == "") {
|
||||
return fmt.Errorf("must provide either a Target ARN, Topic ARN, or Phone Number for SNS config")
|
||||
return errors.New("must provide either a Target ARN, Topic ARN, or Phone Number for SNS config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -790,19 +799,19 @@ func (c *TelegramConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
|
|||
return err
|
||||
}
|
||||
if c.BotToken == "" && c.BotTokenFile == "" {
|
||||
return fmt.Errorf("missing bot_token or bot_token_file on telegram_config")
|
||||
return errors.New("missing bot_token or bot_token_file on telegram_config")
|
||||
}
|
||||
if c.BotToken != "" && c.BotTokenFile != "" {
|
||||
return fmt.Errorf("at most one of bot_token & bot_token_file must be configured")
|
||||
return errors.New("at most one of bot_token & bot_token_file must be configured")
|
||||
}
|
||||
if c.ChatID == 0 {
|
||||
return fmt.Errorf("missing chat_id on telegram_config")
|
||||
return errors.New("missing chat_id on telegram_config")
|
||||
}
|
||||
if c.ParseMode != "" &&
|
||||
c.ParseMode != "Markdown" &&
|
||||
c.ParseMode != "MarkdownV2" &&
|
||||
c.ParseMode != "HTML" {
|
||||
return fmt.Errorf("unknown parse_mode on telegram_config, must be Markdown, MarkdownV2, HTML or empty string")
|
||||
return errors.New("unknown parse_mode on telegram_config, must be Markdown, MarkdownV2, HTML or empty string")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -826,11 +835,39 @@ func (c *MSTeamsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
|
||||
if c.WebhookURL == nil && c.WebhookURLFile == "" {
|
||||
return fmt.Errorf("one of webhook_url or webhook_url_file must be configured")
|
||||
return errors.New("one of webhook_url or webhook_url_file must be configured")
|
||||
}
|
||||
|
||||
if c.WebhookURL != nil && len(c.WebhookURLFile) > 0 {
|
||||
return fmt.Errorf("at most one of webhook_url & webhook_url_file must be configured")
|
||||
return errors.New("at most one of webhook_url & webhook_url_file must be configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MSTeamsV2Config struct {
|
||||
NotifierConfig `yaml:",inline" json:",inline"`
|
||||
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
|
||||
WebhookURL *SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty"`
|
||||
WebhookURLFile string `yaml:"webhook_url_file,omitempty" json:"webhook_url_file,omitempty"`
|
||||
|
||||
Title string `yaml:"title,omitempty" json:"title,omitempty"`
|
||||
Text string `yaml:"text,omitempty" json:"text,omitempty"`
|
||||
}
|
||||
|
||||
func (c *MSTeamsV2Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
*c = DefaultMSTeamsV2Config
|
||||
type plain MSTeamsV2Config
|
||||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.WebhookURL == nil && c.WebhookURLFile == "" {
|
||||
return errors.New("one of webhook_url or webhook_url_file must be configured")
|
||||
}
|
||||
|
||||
if c.WebhookURL != nil && len(c.WebhookURLFile) > 0 {
|
||||
return errors.New("at most one of webhook_url & webhook_url_file must be configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -865,10 +902,10 @@ func (c *JiraConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
}
|
||||
|
||||
if c.Project == "" {
|
||||
return fmt.Errorf("missing project in jira_config")
|
||||
return errors.New("missing project in jira_config")
|
||||
}
|
||||
if c.IssueType == "" {
|
||||
return fmt.Errorf("missing issue_type in jira_config")
|
||||
return errors.New("missing issue_type in jira_config")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/prometheus/alertmanager/notify/email"
|
||||
"github.com/prometheus/alertmanager/notify/jira"
|
||||
"github.com/prometheus/alertmanager/notify/msteams"
|
||||
"github.com/prometheus/alertmanager/notify/msteamsv2"
|
||||
"github.com/prometheus/alertmanager/notify/opsgenie"
|
||||
"github.com/prometheus/alertmanager/notify/pagerduty"
|
||||
"github.com/prometheus/alertmanager/notify/pushover"
|
||||
|
@ -93,6 +94,9 @@ func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logg
|
|||
for i, c := range nc.MSTeamsConfigs {
|
||||
add("msteams", i, c, func(l log.Logger) (notify.Notifier, error) { return msteams.New(c, tmpl, l, httpOpts...) })
|
||||
}
|
||||
for i, c := range nc.MSTeamsV2Configs {
|
||||
add("msteamsv2", i, c, func(l log.Logger) (notify.Notifier, error) { return msteamsv2.New(c, tmpl, l, httpOpts...) })
|
||||
}
|
||||
for i, c := range nc.JiraConfigs {
|
||||
add("jira", i, c, func(l log.Logger) (notify.Notifier, error) { return jira.New(c, tmpl, l, httpOpts...) })
|
||||
}
|
||||
|
|
|
@ -700,6 +700,8 @@ email_configs:
|
|||
[ - <email_config>, ... ]
|
||||
msteams_configs:
|
||||
[ - <msteams_config>, ... ]
|
||||
msteamsv2_configs:
|
||||
[ - <msteamsv2_config>, ... ]
|
||||
jira_configs:
|
||||
[ - <jira_config>, ... ]
|
||||
opsgenie_configs:
|
||||
|
@ -927,6 +929,8 @@ tls_config:
|
|||
|
||||
Microsoft Teams notifications are sent via the [Incoming Webhooks](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/what-are-webhooks-and-connectors) API endpoint.
|
||||
|
||||
DEPRECATION NOTICE: Microsoft is deprecating the creation and usage of [Microsoft 365 connectors via Microsoft Teams](https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/). Consider migrating to using [Workflows](https://learn.microsoft.com/en-us/power-automate/teams/send-a-message-in-teams) with the msteamsv2 config.
|
||||
|
||||
```yaml
|
||||
# Whether to notify about resolved alerts.
|
||||
[ send_resolved: <boolean> | default = true ]
|
||||
|
@ -949,6 +953,29 @@ Microsoft Teams notifications are sent via the [Incoming Webhooks](https://learn
|
|||
[ http_config: <http_config> | default = global.http_config ]
|
||||
```
|
||||
|
||||
### `<msteamsv2_config>`
|
||||
|
||||
Microsoft Teams v2 notifications using the new message format with adaptive cards as required by [flows](https://learn.microsoft.com/en-us/power-automate/teams/overview). Please follow [the documentation](https://support.microsoft.com/en-gb/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) for more information on how to set up this integration.
|
||||
|
||||
```yaml
|
||||
# Whether to notify about resolved alerts.
|
||||
[ send_resolved: <boolean> | default = true ]
|
||||
|
||||
# The incoming webhook URL.
|
||||
# webhook_url and webhook_url_file are mutually exclusive.
|
||||
[ webhook_url: <secret> ]
|
||||
[ webhook_url_file: <filepath> ]
|
||||
|
||||
# Message title template.
|
||||
[ title: <tmpl_string> | default = '{{ template "msteamsv2.default.title" . }}' ]
|
||||
|
||||
# Message body template.
|
||||
[ text: <tmpl_string> | default = '{{ template "msteamsv2.default.text" . }}' ]
|
||||
|
||||
# The HTTP client's configuration.
|
||||
[ http_config: <http_config> | default = global.http_config ]
|
||||
```
|
||||
|
||||
### `<jira_config>`
|
||||
|
||||
JIRA notifications are sent via [JIRA Rest API v2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/)
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
// Copyright 2024 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 msteamsv2
|
||||
|
||||
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/common/model"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
const (
|
||||
colorRed = "Attention"
|
||||
colorGreen = "Good"
|
||||
colorGrey = "Warning"
|
||||
)
|
||||
|
||||
type Notifier struct {
|
||||
conf *config.MSTeamsV2Config
|
||||
tmpl *template.Template
|
||||
logger log.Logger
|
||||
client *http.Client
|
||||
retrier *notify.Retrier
|
||||
webhookURL *config.SecretURL
|
||||
postJSONFunc func(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error)
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/connectors/teams/?tabs=text1#adaptivecarditemschema
|
||||
type Content struct {
|
||||
Schema string `json:"$schema"`
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
Body []Body `json:"body"`
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
Weight string `json:"weigth,omitempty"`
|
||||
Size string `json:"size,omitempty"`
|
||||
Wrap bool `json:"wrap,omitempty"`
|
||||
Style string `json:"style,omitempty"`
|
||||
Color string `json:"color,omitempty"`
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
ContentType string `json:"contentType"`
|
||||
ContentURL *string `json:"contentUrl"` // Use a pointer to handle null values
|
||||
Content Content `json:"content"`
|
||||
}
|
||||
|
||||
type teamsMessage struct {
|
||||
Type string `json:"type"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
// New returns a new notifier that uses the Microsoft Teams Power Platform connector.
|
||||
func New(c *config.MSTeamsV2Config, t *template.Template, l log.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
|
||||
client, err := commoncfg.NewClientFromConfig(*c.HTTPConfig, "msteamsv2", httpOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n := &Notifier{
|
||||
conf: c,
|
||||
tmpl: t,
|
||||
logger: l,
|
||||
client: client,
|
||||
retrier: ¬ify.Retrier{},
|
||||
webhookURL: c.WebhookURL,
|
||||
postJSONFunc: notify.PostJSON,
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
level.Debug(n.logger).Log("incident", key)
|
||||
|
||||
data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger)
|
||||
tmpl := notify.TmplText(n.tmpl, data, &err)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
title := tmpl(n.conf.Title)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
text := tmpl(n.conf.Text)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
alerts := types.Alerts(as...)
|
||||
color := colorGrey
|
||||
switch alerts.Status() {
|
||||
case model.AlertFiring:
|
||||
color = colorRed
|
||||
case model.AlertResolved:
|
||||
color = colorGreen
|
||||
}
|
||||
|
||||
var url string
|
||||
if n.conf.WebhookURL != nil {
|
||||
url = n.conf.WebhookURL.String()
|
||||
} else {
|
||||
content, err := os.ReadFile(n.conf.WebhookURLFile)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("read webhook_url_file: %w", err)
|
||||
}
|
||||
url = strings.TrimSpace(string(content))
|
||||
}
|
||||
|
||||
// A message as referenced in https://learn.microsoft.com/en-us/connectors/teams/?tabs=text1%2Cdotnet#request-body-schema
|
||||
t := teamsMessage{
|
||||
Type: "message",
|
||||
Attachments: []Attachment{
|
||||
{
|
||||
ContentType: "application/vnd.microsoft.card.adaptive",
|
||||
ContentURL: nil,
|
||||
Content: Content{
|
||||
Schema: "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
Type: "AdaptiveCard",
|
||||
Version: "1.2",
|
||||
Body: []Body{
|
||||
{
|
||||
Type: "TextBlock",
|
||||
Text: title,
|
||||
Weight: "Bolder",
|
||||
Size: "Medium",
|
||||
Wrap: true,
|
||||
Style: "heading",
|
||||
Color: color,
|
||||
},
|
||||
{
|
||||
Type: "TextBlock",
|
||||
Text: text,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var payload bytes.Buffer
|
||||
if err = json.NewEncoder(&payload).Encode(t); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := n.postJSONFunc(ctx, n.client, url, &payload)
|
||||
if err != nil {
|
||||
return true, notify.RedactURL(err)
|
||||
}
|
||||
defer notify.Drain(resp)
|
||||
|
||||
// https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL#rate-limiting-for-connectors
|
||||
shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body)
|
||||
if err != nil {
|
||||
return shouldRetry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err)
|
||||
}
|
||||
return shouldRetry, err
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
// Copyright 2024 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 msteamsv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/notify/test"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
// This is a test URL that has been modified to not be valid.
|
||||
var testWebhookURL, _ = url.Parse("https://example.westeurope.logic.azure.com:443/workflows/xxx/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=xxx")
|
||||
|
||||
func TestMSTeamsV2Retry(t *testing.T) {
|
||||
notifier, err := New(
|
||||
&config.MSTeamsV2Config{
|
||||
WebhookURL: &config.SecretURL{URL: testWebhookURL},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
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("retry - error on status %d", statusCode))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifier_Notify_WithReason(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
statusCode int
|
||||
responseContent string
|
||||
expectedReason notify.Reason
|
||||
noError bool
|
||||
}{
|
||||
{
|
||||
name: "with a 2xx status code and response 1",
|
||||
statusCode: http.StatusOK,
|
||||
responseContent: "1",
|
||||
noError: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
notifier, err := New(
|
||||
&config.MSTeamsV2Config{
|
||||
WebhookURL: &config.SecretURL{URL: testWebhookURL},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
log.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
notifier.postJSONFunc = func(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) {
|
||||
resp := httptest.NewRecorder()
|
||||
resp.WriteString(tt.responseContent)
|
||||
resp.WriteHeader(tt.statusCode)
|
||||
return resp.Result(), nil
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
|
||||
alert1 := &types.Alert{
|
||||
Alert: model.Alert{
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
_, err = notifier.Notify(ctx, alert1)
|
||||
if tt.noError {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
var reasonError *notify.ErrorWithReason
|
||||
require.ErrorAs(t, err, &reasonError)
|
||||
require.Equal(t, tt.expectedReason, reasonError.Reason)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMSTeamsV2Templating(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
dec := json.NewDecoder(r.Body)
|
||||
out := make(map[string]interface{})
|
||||
err := dec.Decode(&out)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
u, _ := url.Parse(srv.URL)
|
||||
|
||||
for _, tc := range []struct {
|
||||
title string
|
||||
cfg *config.MSTeamsV2Config
|
||||
|
||||
retry bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
title: "full-blown message",
|
||||
cfg: &config.MSTeamsV2Config{
|
||||
Title: `{{ template "msteams.default.title" . }}`,
|
||||
Text: `{{ template "msteams.default.text" . }}`,
|
||||
},
|
||||
retry: false,
|
||||
},
|
||||
{
|
||||
title: "title with templating errors",
|
||||
cfg: &config.MSTeamsV2Config{
|
||||
Title: "{{ ",
|
||||
},
|
||||
errMsg: "template: :1: unclosed action",
|
||||
},
|
||||
{
|
||||
title: "message with templating errors",
|
||||
cfg: &config.MSTeamsV2Config{
|
||||
Title: `{{ template "msteams.default.title" . }}`,
|
||||
Text: "{{ ",
|
||||
},
|
||||
errMsg: "template: :1: unclosed action",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.title, func(t *testing.T) {
|
||||
tc.cfg.WebhookURL = &config.SecretURL{URL: u}
|
||||
tc.cfg.HTTPConfig = &commoncfg.HTTPClientConfig{}
|
||||
pd, err := New(tc.cfg, test.CreateTmpl(t), log.NewNopLogger())
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
|
||||
ok, err := pd.Notify(ctx, []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{
|
||||
"lbl1": "val1",
|
||||
},
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
},
|
||||
}...)
|
||||
if tc.errMsg == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.errMsg)
|
||||
}
|
||||
require.Equal(t, tc.retry, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMSTeamsV2RedactedURL(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
secret := "secret"
|
||||
notifier, err := New(
|
||||
&config.MSTeamsV2Config{
|
||||
WebhookURL: &config.SecretURL{URL: u},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
log.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, secret)
|
||||
}
|
||||
|
||||
func TestMSTeamsV2ReadingURLFromFile(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
f, err := os.CreateTemp("", "webhook_url")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(u.String() + "\n")
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
notifier, err := New(
|
||||
&config.MSTeamsV2Config{
|
||||
WebhookURLFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
log.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String())
|
||||
}
|
|
@ -365,6 +365,7 @@ func (m *Metrics) InitializeFor(receiver map[string][]Integration) {
|
|||
"discord",
|
||||
"webex",
|
||||
"msteams",
|
||||
"msteamsv2",
|
||||
"jira",
|
||||
} {
|
||||
m.numNotifications.WithLabelValues(integration)
|
||||
|
|
|
@ -159,6 +159,18 @@ Alerts Resolved:
|
|||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "msteamsv2.default.title" }}{{ template "__subject" . }}{{ end }}
|
||||
{{ define "msteamsv2.default.text" }}
|
||||
{{ if gt (len .Alerts.Firing) 0 }}
|
||||
# Alerts Firing:
|
||||
{{ template "__text_alert_list_markdown" .Alerts.Firing }}
|
||||
{{ end }}
|
||||
{{ if gt (len .Alerts.Resolved) 0 }}
|
||||
# Alerts Resolved:
|
||||
{{ template "__text_alert_list_markdown" .Alerts.Resolved }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "jira.default.summary" }}{{ template "__subject" . }}{{ end }}
|
||||
{{ define "jira.default.description" }}
|
||||
{{ if gt (len .Alerts.Firing) 0 }}
|
||||
|
|
Loading…
Reference in New Issue