From 19049f3bb2bead9a7d8c449c3b65bfba66776492 Mon Sep 17 00:00:00 2001 From: Christoph Maser Date: Thu, 2 Nov 2023 16:30:15 +0100 Subject: [PATCH] feat: add rocketchat notifier Signed-off-by: Christoph Maser --- asset/assets_vfsdata.go | 8 +- config/config.go | 140 +++++---- config/config_test.go | 111 ++++++- config/notifiers.go | 83 ++++++ config/receiver/receiver.go | 4 + ...nf.rocketchat-both-token-and-tokenfile.yml | 20 ++ ...ocketchat-both-tokenid-and-tokenidfile.yml | 20 ++ .../conf.rocketchat-default-token-file.yml | 22 ++ .../conf.rocketchat-default-token.yml | 22 ++ config/testdata/conf.rocketchat-no-token.yml | 19 ++ docs/configuration.md | 63 ++++ notify/notify.go | 1 + notify/rocketchat/rocketchat.go | 272 ++++++++++++++++++ notify/rocketchat/rocketchat_test.go | 66 +++++ template/default.tmpl | 7 + template/email.tmpl | 34 +-- 16 files changed, 813 insertions(+), 79 deletions(-) create mode 100644 config/testdata/conf.rocketchat-both-token-and-tokenfile.yml create mode 100644 config/testdata/conf.rocketchat-both-tokenid-and-tokenidfile.yml create mode 100644 config/testdata/conf.rocketchat-default-token-file.yml create mode 100644 config/testdata/conf.rocketchat-default-token.yml create mode 100644 config/testdata/conf.rocketchat-no-token.yml create mode 100644 notify/rocketchat/rocketchat.go create mode 100644 notify/rocketchat/rocketchat_test.go diff --git a/asset/assets_vfsdata.go b/asset/assets_vfsdata.go index db503393a5..c7ce927ae1 100644 --- a/asset/assets_vfsdata.go +++ b/asset/assets_vfsdata.go @@ -163,16 +163,16 @@ 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: 7680, - 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\xcf\x6f\xeb\x36\x0c\xbe\xe7\xaf\x20\xfc\x76\x68\x0e\xf5\xdb\xb9\x40\x31\x3c\x0c\xfb\x71\xe8\x86\xa1\x45\x77\x19\x86\x40\xb5\x19\x57\xad\x2c\xb9\x12\x9d\x34\x48\xf3\xbf\x0f\xb2\x1d\x47\xb2\x9d\x44\x4e\xb3\xd3\xcb\xad\x91\xc9\x8f\xd4\xf7\xd1\xa4\xac\xae\xd7\x90\xe2\x9c\x4b\x84\x68\x36\x63\x02\x35\xe5\x4c\xb2\x0c\x75\x04\x9b\xcd\x37\xe7\xf7\x7a\x0d\x28\x53\xd8\x6c\x26\x7b\x5d\x1e\xef\xef\xac\xd7\x7a\x0d\xf1\x2f\xef\x84\x5a\x32\xf1\x78\x7f\x07\x9b\xcd\xd7\x2f\x5f\x2b\x3b\xf3\x93\xc6\x04\xf9\x02\xf5\xad\x35\xba\x6f\x7e\xc0\x07\x94\x5a\xbc\x95\xa8\x57\xb5\x7b\x13\xc8\x8f\x64\xca\xa7\x17\x4c\xc8\x46\xf8\xc7\x7a\x3f\x10\xa3\xd2\xc0\x07\x90\x7a\x2c\x0a\xd4\xb5\x2b\x9f\x03\xbe\xb5\x0f\xa3\x39\xd7\x5c\x66\xd6\xe7\xc6\xfa\x54\x1b\x32\xf1\xaf\xd5\x2a\x7c\x80\x40\xe9\x46\xfc\x17\xac\xd1\x6f\x5a\x95\xc5\x1d\x7b\x42\x61\xe2\x07\xa5\x09\xd3\xbf\x18\xd7\x26\xfe\x9b\x89\x12\x6d\xc0\x17\xc5\x25\x44\x60\x51\xa1\x0e\x99\x11\x5c\x59\xac\xf8\x67\x95\xe7\x4a\xd6\xce\xd3\x66\xcd\xc1\x9b\xc2\x66\x73\xb5\x5e\xc3\x92\xd3\xb3\x6f\x1c\xdf\x63\xae\x16\xe8\x47\xff\x93\xe5\x68\x1a\x46\x87\xa2\xb7\x89\x4f\xdb\xbf\xf6\xc8\x94\xa2\x49\x34\x2f\x88\x2b\x19\x1d\xe0\x98\xf0\x9d\x6a\x49\x67\x82\x1b\x6a\x4c\x35\x93\x19\x42\x0c\x9b\x4d\x9d\xd7\xcd\x64\xb7\xd8\xe7\xc9\xb2\x72\x5d\x11\x69\xd3\xb7\xbf\x6e\xa1\xdd\x40\x93\x58\x1d\xfc\x9b\x94\x8a\x98\xcd\xc9\x83\x74\x96\x4f\xc3\x7d\x50\xa5\x4e\xf0\xa6\x16\x13\x25\x6a\x46\x4a\xd7\x95\x38\x19\x20\xea\x20\x05\xb3\x9c\xe9\xd7\x54\x2d\x65\x8f\x8b\x49\x28\x19\x81\x59\x4f\xc6\xd3\x11\x8a\x1c\x44\xc8\x64\x98\x11\x23\x58\xf2\x1a\xa7\x38\x67\xa5\xa0\x98\x38\x09\x6c\xa8\x20\xcc\x0b\xc1\xc8\x7f\x39\xe3\x7d\x35\xe8\xe3\x94\xc6\xb6\x87\x7c\x08\xca\x6f\x42\x81\x78\x73\x26\xc4\x13\x4b\x5e\x7b\x78\x83\xe9\x5b\x50\xf8\x80\x63\x86\x82\xcb\xd7\xe0\x0c\x92\x26\x03\x9e\x46\x61\x0e\x85\x46\x5b\x6b\x81\xd6\x4e\x42\x07\x19\xab\x7a\x70\x60\xca\x3c\x51\x12\x73\xf5\xc2\xa3\x70\xfb\x52\x8b\xd0\x8c\xc3\x37\x37\x57\x8a\xea\x89\xe3\xd4\xa0\x6b\x5e\xd8\xad\xa5\x25\xad\x5a\x97\x7e\x43\x1b\x57\x8e\x7d\xc4\x44\x70\x94\x74\x7a\x41\xee\x43\xdc\x4d\xc5\xd3\x34\xeb\xe3\x72\x69\x88\xc9\x04\xcd\x00\x6e\xaf\x83\xc7\xfb\x59\x55\x85\xc9\x50\x72\x6c\x81\x73\x34\x86\x65\xa7\xbd\xdf\x3d\xb0\xbe\x42\xcd\xc0\xdb\xd3\xd0\x06\x27\xdc\xa4\x33\x5f\xbd\x01\x3e\x85\x1f\xe1\xda\x36\xce\x6a\x11\xea\xc5\xaa\x75\x1e\x66\xc4\x3f\x05\x54\x41\xae\x9d\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\x3d\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\x9c\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\xce\x8f\x3d\xa8\xff\xbf\x9e\xce\x53\x4e\xe1\xd5\x74\xbe\x62\xea\x1f\xe9\x0f\x31\xb9\x3b\xd3\x8d\x99\x29\xee\x69\x4e\x3a\x2f\xfb\xee\x33\x7d\xfc\x37\x82\x83\x73\x91\x77\x8c\xbc\x2e\x8b\x84\x02\x33\xcd\xf2\x21\x2a\xbf\x5b\x52\x52\x6e\x12\xa5\xd3\x33\x34\xa2\x2e\xd2\x85\x5d\x7b\x4c\x78\xc2\xf7\xcb\xab\xfb\x69\x1e\x73\x43\xc8\x72\xb7\x99\xe6\x39\xd3\xab\x93\xea\xb4\x8b\x75\x7a\xc5\xf7\x90\x9a\x2f\xfb\x10\x99\xbe\xc0\x28\xa1\x9c\xeb\xb6\x4f\x2b\xd6\x86\x0e\xd5\x6c\x20\xf8\x18\xf1\x5e\xb8\x66\x67\x51\xce\x03\xea\x7c\x46\x5f\x38\x9f\x54\x27\xe4\x41\xae\x0a\xcd\x95\xe6\xf6\xe3\xe7\xba\x39\x48\xff\xb0\x5d\x82\x9b\x5b\x88\xa2\xed\xf9\x7a\x7b\xb5\xea\xed\xd6\xfa\x00\x00\x54\x7e\x06\x17\xb8\xf5\xe3\x32\xc5\xf7\xed\xed\x2e\x44\xdb\x47\x91\xe7\xc1\xe7\x70\x85\x6f\x8e\x63\x94\x68\x4e\x3c\x61\x22\x9a\xb6\x86\x2d\x7c\x9b\xd6\x2d\x44\xbf\xf3\xec\xd9\xc7\x42\x61\xb0\x02\x64\x32\xed\xa2\x2e\x99\x96\x5c\x66\xd1\x14\xae\x24\x3a\x40\x35\xcc\xf4\x48\xac\x3f\x30\xe5\x65\x1e\x1e\x8d\xcb\xb9\xb2\xa1\xec\xea\x2e\xd4\xd1\x30\x77\x6a\xd9\x89\x21\xd3\x56\x13\xf7\xef\xfa\xdf\x35\x2e\xb4\xe7\xe6\xeb\xd4\x16\x46\x2f\xf6\x28\xb5\x46\x2b\x16\xa0\xda\xd9\x95\x0b\x52\xef\x7c\x0a\x1e\x57\xb1\xab\xe4\x31\x65\x77\x48\xdd\xa7\x6e\xab\xd3\x2a\x79\x45\xf2\x6f\x24\x4e\x9e\x53\x03\x60\x4c\x70\x66\x4e\xbf\xd3\xdd\x97\xde\xa7\x2f\xe2\x07\x80\x0f\xdf\xc4\x0f\x38\x1c\xbb\x8e\x1f\x4a\xbe\x77\x27\xff\x5f\x00\x00\x00\xff\xff\xab\xbc\x36\x42\x00\x1e\x00\x00"), }, "/templates/email.tmpl": &vfsgen۰CompressedFileInfo{ name: "email.tmpl", modTime: time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC), - uncompressedSize: 14057, + uncompressedSize: 14085, - compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x5a\x7b\x6f\xdb\x38\x12\xff\x5f\x9f\x62\x56\x8b\xc3\xb6\x80\x65\x3b\x69\x1b\x34\x7e\xe1\xdc\x44\x4e\x84\x73\xe4\xc0\x56\xda\x0b\x0e\x87\x05\x2d\x8d\x2d\xee\x4a\xa4\x8e\xa4\x63\x7b\x73\xf9\xee\x07\x52\xf2\x33\x4e\x9a\xdb\x5b\xd4\xde\x6b\x1a\x34\x91\xa8\x99\xe1\xbc\x38\xf3\xa3\x28\xeb\xfe\x1e\x22\x1c\x51\x86\x60\x63\x4a\x68\x52\x8e\x70\x44\x26\x89\x2a\xcb\xc9\xf0\x17\x0c\x95\x0d\x0f\x0f\xf7\xf7\xa0\x30\xcd\x12\xa2\x10\xec\x9f\x7f\x5e\x3e\x29\xe7\xcf\x90\x45\xf0\xf0\xf0\xb4\xa4\x58\xa5\x89\x16\x63\x35\x7e\x70\x1c\x6b\xa0\xe6\x09\x02\x61\x11\x5c\x06\x57\x5d\x88\x50\xd0\x3b\x8c\x60\x24\x78\x0a\xb1\x52\x99\xac\x55\x2a\x63\xaa\xe2\xc9\xb0\x1c\xf2\xb4\xa2\x25\x8d\x27\xac\xa2\x04\x61\x92\x84\x8a\x72\x46\x12\xc7\x4c\xe0\x2c\x94\x92\x96\x65\x05\x31\xc2\x95\x17\x40\x97\x86\xc8\x24\xc2\x9b\x2b\x2f\x78\x6b\x59\x67\x3c\x9b\x0b\x3a\x8e\x15\xbc\x09\xdf\xc2\x71\xf5\xe8\x3d\x5c\xe5\x12\x2d\xeb\x1a\x45\x4a\xa5\xa4\x9c\x01\x95\x10\xa3\xc0\xe1\x1c\xc6\x82\x30\x85\x51\x09\x46\x02\x11\xf8\x08\xc2\x98\x88\x31\x96\x40\x71\x20\x6c\x0e\x19\x0a\xc9\x19\xf0\xa1\x22\x94\x51\x36\x06\x02\x21\xcf\xe6\x16\x1f\x81\x8a\xa9\x04\xc9\x47\x6a\x4a\x44\x6e\x21\x91\x92\x87\x94\x28\x8c\x20\xe2\xe1\x24\x45\xa6\x88\xb6\x00\x46\x34\x41\x09\x6f\x54\x8c\x60\x0f\x0a\x0e\xfb\xad\x99\x24\x42\x92\x58\x94\x81\x7e\xb6\x78\x04\x53\xaa\x62\x3e\x51\x20\x50\x2a\x41\x8d\x17\x4a\x40\x59\x98\x4c\x22\xad\xc3\xe2\x71\x42\x53\x5a\xcc\xa0\xd9\x8d\xe1\xd2\x52\x1c\x26\x12\x4b\x46\xcf\x12\xa4\x3c\xa2\x23\xfd\x17\x8d\x59\xd9\x64\x98\x50\x19\x97\x20\xa2\x5a\xf4\x70\xa2\xb0\x04\x52\x0f\x1a\x3f\x96\xb4\x1d\x15\x2e\x40\x62\x92\x58\x21\xcf\x28\x4a\x30\xb6\xae\xb4\x33\x34\x5a\xf5\x4c\x3b\x54\x15\x2e\x92\x7a\x64\x1a\xf3\x74\xd3\x12\x2a\xad\xd1\x44\x30\x2a\x63\x34\x3c\x11\x07\xc9\xcd\x8c\x3a\xa7\xf4\x88\x26\x1f\xf1\x24\xe1\x53\x6d\x5a\xc8\x59\x44\xb5\x45\xb2\x96\x07\x99\x0c\xf9\x1d\x1a\x5b\xf2\xb8\x32\xae\x68\x98\xbb\xdb\x04\x20\x5b\x45\xb5\x78\x24\x63\x92\x24\x30\xc4\xc2\x61\x18\x01\x65\x40\xd6\xcc\x11\x7a\x7a\xa9\x08\x53\x94\x24\x90\x71\x61\xe6\xdb\x36\xb3\x6c\x59\xc1\xa5\x0b\x83\x5e\x27\xf8\xd2\xee\xbb\xe0\x0d\xe0\xba\xdf\xfb\xec\x9d\xbb\xe7\x60\xb7\x07\xe0\x0d\xec\x12\x7c\xf1\x82\xcb\xde\x4d\x00\x5f\xda\xfd\x7e\xdb\x0f\x6e\xa1\xd7\x81\xb6\x7f\x0b\x7f\xf3\xfc\xf3\x12\xb8\x7f\xbf\xee\xbb\x83\x01\xf4\xfa\x96\x77\x75\xdd\xf5\xdc\xf3\x12\x78\xfe\x59\xf7\xe6\xdc\xf3\x2f\xe0\xd3\x4d\x00\x7e\x2f\x80\xae\x77\xe5\x05\xee\x39\x04\x3d\xd0\x13\x16\xa2\x3c\x77\xa0\x85\x5d\xb9\xfd\xb3\xcb\xb6\x1f\xb4\x3f\x79\x5d\x2f\xb8\x2d\x59\x1d\x2f\xf0\xb5\xcc\x4e\xaf\x0f\x6d\xb8\x6e\xf7\x03\xef\xec\xa6\xdb\xee\xc3\xf5\x4d\xff\xba\x37\x70\xa1\xed\x9f\x83\xdf\xf3\x3d\xbf\xd3\xf7\xfc\x0b\xf7\xca\xf5\x83\x32\x78\x3e\xf8\x3d\x70\x3f\xbb\x7e\x00\x83\xcb\x76\xb7\xab\xa7\xb2\xda\x37\xc1\x65\xaf\xaf\xf5\x83\xb3\xde\xf5\x6d\xdf\xbb\xb8\x0c\xe0\xb2\xd7\x3d\x77\xfb\x03\xf8\xe4\x42\xd7\x6b\x7f\xea\xba\xf9\x54\xfe\x2d\x9c\x75\xdb\xde\x55\x09\xce\xdb\x57\xed\x0b\xd7\x70\xf5\x82\x4b\xb7\x6f\x69\xb2\x5c\x3b\xf8\x72\xe9\xea\x21\x3d\x5f\xdb\x87\xf6\x59\xe0\xf5\x7c\x6d\xc6\x59\xcf\x0f\xfa\xed\xb3\xa0\x04\x41\xaf\x1f\x2c\x59\xbf\x78\x03\xb7\x04\xed\xbe\x37\xd0\x0e\xe9\xf4\x7b\x57\x25\x4b\xbb\xb3\xd7\xd1\x24\x9e\xaf\xf9\x7c\x37\x97\xa2\x5d\x0d\x1b\x11\xe9\xf5\xcd\xfd\xcd\xc0\x5d\x0a\x84\x73\xb7\xdd\xf5\xfc\x8b\x81\x66\xd6\x26\x2e\x88\xcb\x96\xe3\xb4\xac\xc6\x0f\xe7\xbd\xb3\xe0\xf6\xda\x85\x96\xd5\xd0\x75\x0a\x66\x69\xc2\x64\xd3\xd6\x95\xa8\x56\xa9\x4c\xa7\xd3\xf2\xf4\x5d\x99\x8b\x71\xe5\xe8\xf4\xf4\xb4\x32\xcb\x6b\x99\xd4\x35\xac\x69\xa7\x44\x8c\x29\xab\x41\xb5\x0e\x23\xce\x94\x33\x22\x29\x4d\xe6\x35\xf8\xe9\x12\x93\x3b\x54\x34\x24\xe0\xe3\x04\x7f\x2a\xc1\x72\xa0\x04\x6d\x41\x49\x52\x02\x49\x98\x74\x24\x0a\x3a\xaa\xc3\x90\xcf\x1c\x49\x7f\xa3\x6c\x5c\x83\x21\x17\x11\x0a\x67\xc8\x67\x85\x50\x49\x7f\xc3\x1a\x1c\xbd\xcf\x66\x75\x5b\x2b\x89\x24\x6a\x59\x8d\x14\x15\x01\x46\x52\x6c\xda\x77\x14\xa7\x3a\x67\x6d\xbd\x56\x14\x32\xd5\xb4\xa7\x34\x52\x71\x33\xc2\x3b\x1a\xa2\x63\x6e\xec\x56\xa3\xa2\x79\x16\xac\xda\x3e\x07\xff\x35\xa1\x77\x4d\xfb\x2c\x67\x73\x82\x79\x86\x6b\x42\x14\xce\x54\x45\xdb\x5b\x37\x35\x50\xa2\x6a\xde\x04\x1d\xe7\xe3\x9a\x28\x45\x55\x82\xad\xe7\xda\x43\xa3\x92\xd3\x58\x0d\xe3\xb3\x96\xf5\xd7\x14\x23\x4a\x80\xb3\x64\x0e\x32\x14\x88\xcc\x2c\xdd\x37\x29\x99\xe5\x9a\xd6\xe0\xe4\x7d\x35\x9b\xbd\x85\x7b\x0b\x60\xc8\xa3\xb9\xb9\x00\xc8\x48\x14\x19\x0f\x55\xe1\x07\x9a\x6a\x83\x09\x53\x75\x0b\xe0\xc1\xb2\x00\xe2\xa3\x92\x15\x1f\x97\xac\xf8\x5d\xc9\x8a\xdf\x17\x2c\xc6\x7f\x53\xd4\x65\xa2\x06\x1f\xab\xdb\x8c\x00\x8b\x00\x1e\x57\xb3\x19\x54\xe1\x43\x36\xdb\x2d\x7b\x5d\x5e\x1e\x8f\xe3\xe3\x27\x68\x8f\x1f\xd3\x1e\x7d\x7c\x82\xf6\xdd\x0e\xda\x93\xdd\xb4\x65\x1d\x16\x42\x19\x8a\xaf\xb9\x03\xa0\x70\xe3\x51\xb5\xfa\x97\x27\x45\x21\x53\x2f\xf2\xeb\x82\xd8\x99\x0a\x92\x6d\x73\x1c\x55\x9f\x50\x96\xb2\x3b\xae\x2b\xef\xfd\x57\xf5\x79\xb0\x1a\x95\x22\x33\x1a\x95\x3c\xb7\xad\x86\x89\x3a\x55\x98\xca\x90\x67\xd8\xb4\x6d\x73\xa3\xe6\xfa\x7a\x01\x11\x64\x18\x63\x4a\xcc\xca\x74\x35\x18\xb8\x42\x29\xc9\x18\xf7\xb6\x36\xc1\x99\xe2\xf0\x57\xaa\x9c\xfc\x41\xca\xb9\x8a\x0d\x53\xde\x55\x28\x91\x18\xad\x88\xf4\xda\x32\xdc\x0e\x89\x7e\x99\x48\x55\x03\xc6\x19\xd6\x21\x2e\x92\x55\xfb\xaa\x0e\x09\x65\xe8\x2c\x87\xca\x27\x98\xd6\x61\x48\xc2\x5f\xc7\x82\x4f\x58\xe4\x84\x3c\xe1\xa2\x06\x3f\x8e\x4e\xf4\x4f\x7d\xdd\xcf\xba\x54\x58\x0d\x45\x86\x09\x42\x98\x10\x29\x9b\xb6\x76\xaa\x89\xe2\xfe\x5c\xf4\x52\xdd\xf3\xbb\xa6\xad\xef\x6c\x18\x8e\x0d\x71\xd3\x2e\x88\xed\x96\x05\xd0\x50\x62\x8f\x55\x58\x67\x75\x43\x45\x7b\x73\xe4\x1d\x0a\x2d\x24\x71\x48\x42\xc7\xac\x06\x8a\x67\x75\x1b\xee\xcc\x5d\xd3\x56\x3c\xd3\x35\x5a\x45\x2b\x45\x8b\x1c\x58\x16\x91\xa5\x8b\x4f\xaa\xd5\x65\x3e\xec\x4f\x77\x0d\x3e\xb3\x84\xcc\x6b\x30\x4c\x78\xf8\x6b\x1d\xd6\xbb\x41\xb5\xaa\xd9\x96\x3e\x06\x32\x51\xbc\x0e\x61\x82\x44\xe8\xa9\x54\xbc\x6d\xba\xb1\x1a\xa0\x11\xd1\xbb\x75\xc3\x91\xa9\x6f\x6c\xea\xd7\xad\xd8\xb6\x7b\x59\x5a\x75\x47\xaa\x2f\x4d\xd1\x41\x5c\x5f\xcb\x29\xa1\x6c\x6b\x95\x84\x98\x24\x05\x77\xd3\xae\xe6\xf7\x32\x23\xe1\xe2\xfe\x90\xd6\xfc\xc8\x08\xd0\x4c\x35\x38\xca\x66\x20\x79\x42\x23\xf8\x11\x4f\xf5\xcf\xe2\x91\x23\x48\x44\x27\xb2\x06\xef\xb4\x27\xd6\xab\xc0\x68\xb4\xe6\x98\x43\x28\x05\x8b\x7f\xf7\xf7\x40\x47\x30\x56\xf0\x26\x41\x06\xe5\x76\x82\x42\xc9\x72\x87\x0a\xca\xc6\x6f\xa1\xaa\xb7\xc0\xeb\xe4\x6b\x0b\x93\x68\x52\x30\xbf\x9d\x29\x11\x7a\x63\xf9\xcd\x42\xb6\x73\x41\x6e\x41\x92\x3a\x6c\x04\x6f\x03\x58\x7d\xa8\x56\xb7\x13\x17\x4c\x7f\x2b\xe4\x85\xc8\x14\x8a\x5d\x61\x35\xff\xab\xda\xb0\x1d\x59\xe2\x9e\x7c\x38\x3e\x3e\xdb\x5a\xdb\x50\x5c\xe7\x32\xd7\xb3\x22\x27\xdf\x0a\x88\x09\x49\x11\x07\xf8\x37\xe8\xa8\x3c\x3c\xe4\x6e\xde\x19\xab\xb7\x70\x04\x0f\x0f\x72\xf9\x4e\x03\x46\x5c\x68\x11\x82\xb0\x31\x42\xf9\x42\xf0\x49\xd6\x25\x43\x4c\x64\x79\xc0\x85\xc2\xe8\x9a\x50\x21\xb7\xe3\xba\x98\xd7\x27\x29\xc2\xc3\x43\x53\x5f\x7f\x26\xc9\x04\x1f\x13\xae\xde\x9e\x6c\x24\xc6\xb2\x76\xaf\xd3\x25\x12\x5f\x98\x41\x63\xce\xa3\xef\x3d\x7d\x4e\x3e\x7e\x3a\xad\x76\x5e\x9c\x3e\x39\xf9\xc1\xa7\x0f\xfc\x0f\xf9\xb3\x4d\xd7\xa8\x28\x71\x98\x75\x74\x0b\xb1\x2c\xb6\x1f\xf6\x41\xe1\xad\x55\xce\xbe\x33\xbd\x7a\x27\x06\xd9\x6a\xdf\x7f\x86\x8e\xfd\x48\xf7\x43\xca\x8c\x27\xf3\xc3\xe0\xa7\x43\x4d\x90\x2a\x54\x0b\x40\xf7\x6c\x92\x14\xc6\x11\x88\x05\x8e\x9a\xf6\xd6\x6b\x15\x53\x76\x52\xc2\xc8\x18\xc5\x4d\xbf\x9b\xbf\x5e\xb1\x97\xbb\x3b\xc5\x9c\x4c\xd0\x94\x88\xf9\xfe\xbc\x60\xca\x76\x84\x21\x17\xe6\x1d\xf4\x62\x53\xbb\x28\xc9\x9d\x4e\x67\x67\xa9\x7e\xf7\xfe\x23\x46\x64\x05\x09\x0b\x38\xb8\x39\xec\x2c\x77\x88\xd9\xac\xe8\x12\x1b\xdb\xe3\x63\xbd\x39\xde\x68\x2c\x43\x9e\x44\xbb\x5b\x49\x38\x11\x52\xcf\x9c\x71\x9a\x0f\x2c\x61\x38\x65\x46\x68\x81\xc6\xb7\x5a\xce\x87\xa5\x8d\xe6\x30\x62\xc4\x45\x5a\x83\x90\x64\x54\x91\x84\xfe\x86\x75\xbb\xf5\x99\xe2\x14\x28\x83\x67\x42\xb7\x78\x2d\x46\x76\x26\xf6\xa3\xc2\xbd\xab\x50\xff\x2e\xb0\xf9\xba\x8e\xbf\xf5\x3a\x96\x4a\x70\x36\xde\x9f\xc3\xff\xb1\x02\x2f\x45\x5a\x2c\x31\xcc\x3f\x21\x1f\x68\x54\x72\x25\xff\x80\x5c\xdc\x01\x43\x8a\x27\x05\xfa\xd9\xd4\xe4\x35\x3b\xbf\xf3\xec\xcc\x71\xf0\x32\x01\x1b\xc3\x3d\x06\xbf\x51\x19\x8a\xdd\x6e\x5a\xa5\xef\x4e\xdc\xbe\x06\xd3\xa1\x09\xeb\x40\x7d\xff\xf6\x3c\xbd\x26\x77\x75\x0f\xc6\x78\x7e\x70\x2c\xf3\xde\xb1\xf7\xfc\x58\xd3\xe8\x80\x92\xe4\xab\x4e\x5d\x14\xbb\x95\xf6\xff\x37\x29\xb3\x0e\x4c\xcb\x17\xc8\x50\x10\xc5\x35\x14\x35\x38\x74\x5f\x35\x71\x1b\x44\x3e\xc2\xa0\x13\x16\xa1\xd0\xa8\xae\x6e\xb7\x06\x7c\x22\x42\xd4\xe0\xeb\x20\xeb\xcd\xef\x6b\xb9\x2f\x44\x86\x7d\x94\x3c\xb9\xc3\xe8\x09\x6c\xf8\x0a\x28\x0f\xbe\x65\x1f\x66\x8b\x6c\xc4\x7b\x55\x2b\x16\x7f\x22\x6f\xfd\x37\x0b\xfc\x39\x54\xfd\xba\xee\xbe\xd7\x8d\xdc\xa2\x8a\xaf\x6d\xe5\x16\x43\x7b\xd8\xcc\x2d\xb5\x79\xcd\xd1\xd7\xed\xdc\xeb\x76\xee\x75\x3b\xf7\xba\x9d\x7b\xdd\xce\xbd\x6e\xe7\xfe\xc0\xa6\xdb\xa8\x98\x93\xc3\xd6\xb3\xe7\xbb\x9b\x22\x97\x2c\xab\x91\xb5\x2f\xa1\x46\x9c\x9b\xa3\xef\x7d\x05\x78\xfd\x43\xbf\x8d\x8f\xb8\x96\xa1\x3f\x3d\x3d\x7d\xe6\x43\xa8\xdd\x67\xa9\x87\x72\x6e\x7d\x38\x90\x67\xe3\xd3\x10\x3a\x66\xf9\xd9\x17\xec\x07\xfa\xbc\x10\xe9\xec\x3e\xac\x5b\x4f\x8b\x75\xb3\x8f\x1f\x61\xa3\xad\xcf\x3b\x5a\x1b\x25\xce\x9d\x29\x14\x8c\x24\xdf\xb8\xc2\x3d\x53\xc0\xbe\x62\x5a\x6b\x80\x4c\xc1\x70\xfe\xb2\xd3\xc4\xc7\xc5\xe6\xd1\xf7\x1d\xdb\xa5\xa4\x51\x89\xe8\x5d\x2b\xff\x6d\x6d\xd6\x95\x3f\xc9\xc7\xad\xb9\x89\xab\x82\xd7\xa8\x0c\x79\x34\x37\x9f\x92\xab\x34\x69\x59\xd6\xaa\xac\xfe\x27\x00\x00\xff\xff\xa9\x50\x22\x7a\xe9\x36\x00\x00"), + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x5a\xfd\x6e\xdb\x38\x12\xff\x5f\x4f\x31\xab\xc5\x61\x5b\xc0\xb2\x9d\xa4\x0d\x1a\x7f\xe1\x5c\x47\x4e\x84\x73\xe4\xc0\x56\xda\x2d\x0e\x87\x05\x2d\x8d\x2d\xee\x4a\xa4\x8e\xa4\x63\x7b\x73\x79\xf7\x03\x29\xd9\xb1\x1d\x27\xcd\xed\x1d\x6a\xef\x35\x0d\x9a\x48\xd4\xcc\x70\xbe\x38\xf3\xa3\x44\xeb\xee\x0e\x22\x1c\x53\x86\x60\x63\x4a\x68\x52\x8e\x70\x4c\xa6\x89\x2a\xcb\xe9\xe8\x57\x0c\x95\x0d\xf7\xf7\x77\x77\xa0\x30\xcd\x12\xa2\x10\xec\x5f\x7e\x59\x3d\x29\xe7\xcf\x90\x45\x70\x7f\xff\xb4\xa4\x58\xa5\x89\x16\x63\x35\x7e\x70\x1c\x6b\xa8\x16\x09\x02\x61\x11\x5c\x06\x57\x3d\x88\x50\xd0\x5b\x8c\x60\x2c\x78\x0a\xb1\x52\x99\xac\x55\x2a\x13\xaa\xe2\xe9\xa8\x1c\xf2\xb4\xa2\x25\x4d\xa6\xac\xa2\x04\x61\x92\x84\x8a\x72\x46\x12\xc7\x4c\xe0\x2c\x95\x92\x96\x65\x05\x31\xc2\x95\x17\x40\x8f\x86\xc8\x24\xc2\x9b\x2b\x2f\x78\x6b\x59\x1d\x9e\x2d\x04\x9d\xc4\x0a\xde\x84\x6f\xe1\xb8\x7a\xf4\x0e\xae\x72\x89\x96\x75\x8d\x22\xa5\x52\x52\xce\x80\x4a\x88\x51\xe0\x68\x01\x13\x41\x98\xc2\xa8\x04\x63\x81\x08\x7c\x0c\x61\x4c\xc4\x04\x4b\xa0\x38\x10\xb6\x80\x0c\x85\xe4\x0c\xf8\x48\x11\xca\x28\x9b\x00\x81\x90\x67\x0b\x8b\x8f\x41\xc5\x54\x82\xe4\x63\x35\x23\x22\xb7\x90\x48\xc9\x43\x4a\x14\x46\x10\xf1\x70\x9a\x22\x53\x44\x5b\x00\x63\x9a\xa0\x84\x37\x2a\x46\xb0\x87\x05\x87\xfd\xd6\x4c\x12\x21\x49\x2c\xca\x40\x3f\x5b\x3e\x82\x19\x55\x31\x9f\x2a\x10\x28\x95\xa0\xc6\x0b\x25\xa0\x2c\x4c\xa6\x91\xd6\x61\xf9\x38\xa1\x29\x2d\x66\xd0\xec\xc6\x70\x69\x29\x0e\x53\x89\x25\xa3\x67\x09\x52\x1e\xd1\xb1\xfe\x8b\xc6\xac\x6c\x3a\x4a\xa8\x8c\x4b\x10\x51\x2d\x7a\x34\x55\x58\x02\xa9\x07\x8d\x1f\x4b\xda\x8e\x0a\x17\x20\x31\x49\xac\x90\x67\x14\x25\x18\x5b\x1f\xb4\x33\x34\x5a\xf5\x4c\x3b\x54\x15\x2e\x92\x7a\x64\x16\xf3\x74\xd3\x12\x2a\xad\xf1\x54\x30\x2a\x63\x34\x3c\x11\x07\xc9\xcd\x8c\x3a\xa7\xf4\x88\x26\x1f\xf3\x24\xe1\x33\x6d\x5a\xc8\x59\x44\xb5\x45\xb2\x96\x07\x99\x8c\xf8\x2d\x1a\x5b\xf2\xb8\x32\xae\x68\x98\xbb\xdb\x04\x20\x7b\x88\x6a\xf1\x48\xc6\x24\x49\x60\x84\x85\xc3\x30\x02\xca\x80\xac\x99\x23\xf4\xf4\x52\x11\xa6\x28\x49\x20\xe3\xc2\xcc\xb7\x6d\x66\xd9\xb2\x82\x4b\x17\x86\xfd\x6e\xf0\xb9\x3d\x70\xc1\x1b\xc2\xf5\xa0\xff\xc9\x3b\x77\xcf\xc1\x6e\x0f\xc1\x1b\xda\x25\xf8\xec\x05\x97\xfd\x9b\x00\x3e\xb7\x07\x83\xb6\x1f\x7c\x81\x7e\x17\xda\xfe\x17\xf8\x9b\xe7\x9f\x97\xc0\xfd\xf9\x7a\xe0\x0e\x87\xd0\x1f\x58\xde\xd5\x75\xcf\x73\xcf\x4b\xe0\xf9\x9d\xde\xcd\xb9\xe7\x5f\xc0\xc7\x9b\x00\xfc\x7e\x00\x3d\xef\xca\x0b\xdc\x73\x08\xfa\xa0\x27\x2c\x44\x79\xee\x50\x0b\xbb\x72\x07\x9d\xcb\xb6\x1f\xb4\x3f\x7a\x3d\x2f\xf8\x52\xb2\xba\x5e\xe0\x6b\x99\xdd\xfe\x00\xda\x70\xdd\x1e\x04\x5e\xe7\xa6\xd7\x1e\xc0\xf5\xcd\xe0\xba\x3f\x74\xa1\xed\x9f\x83\xdf\xf7\x3d\xbf\x3b\xf0\xfc\x0b\xf7\xca\xf5\x83\x32\x78\x3e\xf8\x7d\x70\x3f\xb9\x7e\x00\xc3\xcb\x76\xaf\xa7\xa7\xb2\xda\x37\xc1\x65\x7f\xa0\xf5\x83\x4e\xff\xfa\xcb\xc0\xbb\xb8\x0c\xe0\xb2\xdf\x3b\x77\x07\x43\xf8\xe8\x42\xcf\x6b\x7f\xec\xb9\xf9\x54\xfe\x17\xe8\xf4\xda\xde\x55\x09\xce\xdb\x57\xed\x0b\xd7\x70\xf5\x83\x4b\x77\x60\x69\xb2\x5c\x3b\xf8\x7c\xe9\xea\x21\x3d\x5f\xdb\x87\x76\x27\xf0\xfa\xbe\x36\xa3\xd3\xf7\x83\x41\xbb\x13\x94\x20\xe8\x0f\x82\x15\xeb\x67\x6f\xe8\x96\xa0\x3d\xf0\x86\xda\x21\xdd\x41\xff\xaa\x64\x69\x77\xf6\xbb\x9a\xc4\xf3\x35\x9f\xef\xe6\x52\xb4\xab\x61\x23\x22\xfd\x81\xb9\xbf\x19\xba\x2b\x81\x70\xee\xb6\x7b\x9e\x7f\x31\xd4\xcc\xda\xc4\x25\x71\xd9\x72\x9c\x96\xd5\xf8\xe1\xbc\xdf\x09\xbe\x5c\xbb\xa0\x8b\x14\x5c\xdf\x7c\xec\x79\x1d\xb0\x9d\x4a\xe5\xf3\x49\xa7\x52\x39\x0f\xce\xe1\x67\x53\xa6\x8e\xca\x55\x08\x74\x09\xa2\x79\x05\xaa\x54\x5c\xdf\x06\x5b\x17\xac\x5a\xa5\x32\x9b\xcd\xca\xb3\x93\x32\x17\x93\x4a\x30\xa8\xcc\xb5\xac\x23\xcd\x5c\x5c\x3a\x6a\x8d\xb3\x1c\xa9\xc8\x6e\x59\x0d\x33\xe1\x3c\x4d\x98\x6c\xee\x10\x73\x74\x76\x76\x96\x73\xdb\x20\x75\xc5\x6c\xda\x29\x11\x13\xca\x6a\x50\xad\xc3\x98\x33\xe5\x8c\x49\x4a\x93\x45\x0d\x7e\xba\xc4\xe4\x16\x15\x0d\x09\xf8\x38\xc5\x9f\x4a\xb0\x1a\x28\x41\x5b\x50\x92\x94\x40\x12\x26\x1d\x89\x82\x8e\xeb\x30\xe2\x73\x47\xd2\xdf\x29\x9b\xd4\x60\xc4\x45\x84\xc2\x19\xf1\x79\x21\x54\xd2\xdf\xb1\x06\x47\xef\xb2\x79\xdd\x28\x89\x24\x6a\x59\x8d\x14\x15\x01\x46\x52\x6c\xda\xb7\x14\x67\x7a\x85\xd8\x7a\x65\x2a\x64\xaa\x69\xcf\x68\xa4\xe2\x66\x84\xb7\x34\x44\xc7\xdc\xd8\x4b\x1e\x6d\x98\x83\xff\x9c\xd2\xdb\xa6\xdd\xc9\xe9\x9d\x60\x91\xe1\x1a\xb7\xc2\xb9\xaa\x68\x43\xeb\xa6\xd4\x4a\x54\xcd\x9b\xa0\xeb\x7c\xd0\x32\x14\x55\x09\xb6\x9e\x6b\x3f\x8d\x4a\x4e\x63\x35\x8c\x97\x5a\xd6\x5f\x53\x8c\x28\x01\xce\x92\x05\xc8\x50\x20\x32\x53\x1a\xde\xa4\x64\x9e\xeb\x56\x83\xd3\x77\xd5\x6c\xfe\x16\xee\x2c\x80\x11\x8f\x16\xe6\x02\x20\x23\x51\x64\x7c\x52\x85\x1f\x68\xaa\x4d\x24\x4c\xd5\x2d\x80\x7b\xcb\x02\x88\x8f\x4a\x56\x7c\x5c\xb2\xe2\x93\x92\x15\xbf\x2b\x58\x8c\xc7\x66\xa8\xcb\x50\x0d\x3e\x54\xb7\x19\x01\x96\x21\x3b\xae\x66\x73\xa8\xc2\xfb\x6c\xbe\x5b\xf6\xba\xbc\x3c\x02\xc7\xc7\x4f\xd0\x1e\x3f\xa6\x3d\xfa\xf0\x04\xed\xc9\x0e\xda\xd3\xdd\xb4\x65\x1d\x0f\x42\x19\x8a\xaf\xb9\x03\xa0\x70\xe3\x51\xb5\xfa\x97\x27\x45\x21\x53\x2f\xf2\xeb\x92\xd8\x99\x09\x92\x6d\x73\x1c\x55\x9f\x50\x96\xb2\x5b\xae\x2b\xfb\xdd\x57\xf5\xb9\xb7\x1a\x95\x22\x33\x1a\x95\x3c\x9b\xad\x86\x89\x3a\x55\x98\xca\x90\x67\x68\xae\xd4\x22\xc3\x7c\x1d\x6a\xfc\x21\xc3\x18\x53\x62\x16\xa2\xab\x91\xc6\x15\x4a\x49\x26\xb8\xb7\xa5\x08\xce\x0c\x47\xbf\x51\xe5\xe4\x0f\x52\xce\x55\x6c\x98\xf2\x96\x45\x89\xc4\xe8\x81\x48\xaf\x28\xc3\xed\x90\xe8\xd7\xa9\x54\x35\x60\x9c\x61\x1d\xe2\x22\x53\xb5\xa3\xea\x90\x50\x86\xce\x6a\xa8\x7c\x8a\x69\x1d\x46\x24\xfc\x6d\x22\xf8\x94\x45\x4e\xc8\x13\x2e\x6a\xf0\xe3\xf8\x54\xff\xd4\xd7\x9d\xac\x2b\x83\xd5\x50\x64\x94\x20\x84\x09\x91\xb2\x69\x6b\x8f\x9a\x10\xee\xcf\x45\x2f\xd5\x3d\xbf\x6b\xda\xfa\xce\x86\xd1\xc4\x10\x37\xed\x82\xd8\x6e\x59\x00\x0d\x25\xf6\x58\x74\x75\x4a\x37\x54\xb4\x37\x47\xde\xa2\xd0\x42\x12\x87\x24\x74\xc2\x6a\xa0\x78\x56\xb7\xe1\xd6\xdc\x35\x6d\xc5\x33\xbb\xd5\xa8\xa8\xe8\x41\xd1\x22\x07\x56\x15\x64\xe5\xe2\xd3\x6a\x75\x95\x0f\xfb\xd3\x5d\x23\xdb\x2c\x21\x8b\x1a\x8c\x12\x1e\xfe\x56\x87\xf5\x56\x50\xad\x6a\xb6\x95\x8f\x81\x4c\x15\xaf\x43\x98\x20\x11\x7a\x2a\x15\x6f\x9b\x6e\xac\x06\x68\x44\xf4\x76\xdd\x70\x64\xea\x1b\x9b\xfa\x75\x2b\xb6\xed\x5e\xd5\x55\xdd\x8e\xea\x2b\x53\x74\x10\xd7\xd7\x72\x4a\x28\xdb\x5a\x25\x21\x26\x49\xc1\xdd\xb4\xab\xf9\xbd\xcc\x48\xb8\xbc\x3f\xa4\x35\x3f\x36\x02\x34\x53\x0d\x8e\xb2\x39\x48\x9e\xd0\x08\x7e\xc4\x33\xfd\xb3\x7c\xe4\x08\x12\xd1\xa9\xac\xc1\x89\xf6\xc4\x7a\x15\x18\x8f\xd7\x1c\x73\x08\xa5\x60\xf9\xef\xee\x0e\xe8\x18\x26\x0a\xde\x24\xc8\xa0\xdc\x4e\x50\x28\x59\xee\x52\x41\xd9\xe4\x2d\x54\xf5\xfe\x7a\x9d\x7c\x6d\x61\x12\x4d\x0a\xe6\xb7\x33\x23\x42\xef\x5a\xbf\x59\xc8\x76\x2e\xc8\x2d\x3c\x52\x87\x8d\xe0\x6d\xa0\xaa\xf7\xd5\xea\x76\xe2\x82\xe9\x6f\x85\xbc\x10\x99\x42\xb1\x2b\xac\xe6\x7f\x55\x1b\xb6\x23\x4b\xdc\xd3\xf7\xc7\xc7\x9d\xad\xb5\x0d\xc5\x75\x2e\x73\x3d\x2b\x72\xf2\xad\x80\x98\x90\x14\x71\x80\x7f\x81\x8e\xca\xfd\x7d\xee\xe6\x9d\xb1\x7a\x0b\x47\x70\x7f\x2f\x57\x2f\x4c\x60\xcc\x85\x16\x21\x08\x9b\x20\x94\x2f\x04\x9f\x66\x3d\x32\xc2\x44\x96\x87\x5c\x28\x8c\xae\x09\x15\x72\x3b\xae\xcb\x79\x7d\x92\x22\xdc\xdf\x37\xf5\xf5\x27\x92\x4c\xf1\x31\xe1\xc3\xab\x99\x8d\xc4\x58\xd5\xee\x75\xba\x44\xe2\x0b\x33\x68\xc2\x79\xf4\xbd\xa7\xcf\xe9\x87\x8f\x67\xd5\xee\x8b\xd3\x27\x27\x3f\xf8\xf4\x81\xff\x22\x7f\xb6\xe9\x1a\x15\x25\x0e\xb3\x8e\x6e\x21\x96\xe5\xde\xc3\x3e\x28\xbc\xf5\x90\xb3\x27\xa6\x57\xef\xc4\x20\x5b\xed\xfb\xcf\xd0\xb1\x1f\xe9\x7e\x48\x99\xf1\x64\x7e\x18\xfc\x74\xa8\x09\x52\x85\x6a\x01\xe8\x9e\x4d\x92\xc2\x38\x02\xb1\xc0\x71\xd3\xde\x7a\xa7\x62\xca\x4e\x4a\x18\x99\xa0\xb8\x19\xf4\xf2\x77\x2b\xf6\x6a\x77\xa7\x98\x93\x09\x9a\x12\xb1\xd8\x9f\x17\x4c\xd9\x8e\x30\xe4\xc2\xbc\xe0\x5e\x6e\x6a\x97\x25\xb9\xdb\xed\xee\x2c\xd5\x27\xef\x3e\x60\x44\x1e\x20\x61\x01\x07\x37\x87\x9d\xd5\x0e\x31\x9b\x17\x5d\x62\x63\x7b\x7c\xac\x37\xc7\x1b\x8d\x65\xc4\x93\x68\x77\x2b\x09\xa7\x42\xea\x99\x33\x4e\xf3\x81\x15\x0c\xa7\xcc\x08\x2d\xd0\xf8\x56\xcb\x79\xbf\xb2\xd1\xbc\x2c\x1c\x73\x91\xd6\x20\x24\x19\x55\x24\xa1\xbf\x63\xdd\x6e\x7d\xa2\x38\x03\xca\xe0\x99\xd0\x2d\xdf\x89\x91\x9d\x89\xfd\xa8\x70\xef\x2a\xd4\x7f\x08\x6c\xbe\xae\xe3\x6f\xbd\x8e\xa5\x12\x9c\x4d\xf6\xe7\xf0\xbf\x3f\x80\x97\x22\x2d\x56\x18\xe6\x1f\x90\x0f\x34\x2a\xb9\x92\xff\x83\x5c\xdc\x01\x43\x8a\x27\x05\xfa\xd9\xd4\xe4\x35\x3b\xbf\xf3\xec\xcc\x71\xf0\x2a\x01\x1b\xa3\xc3\x0a\xfe\x7a\xe6\xee\x84\xec\x6b\x08\x1d\x9a\xb0\x8e\xd1\xf7\x6a\xca\xd3\x2b\x71\x57\xcf\x60\x8c\xe7\xdf\xa2\x65\xde\x31\xf6\x9e\x15\x6b\x1a\x1d\x46\x6a\x7c\xd5\x9f\xcb\xea\xf6\xa0\xf8\xff\x43\xa2\xac\x83\xd0\xf2\x05\x32\x14\x44\x71\x0d\x3b\x0d\xe6\xdc\x57\xfd\xdb\x06\x8c\x8f\xf0\xe6\x94\x45\x28\x34\x82\xab\xdb\xad\x21\x9f\x8a\x10\x35\xd0\x3a\xb4\xda\xf2\xc7\x3a\xeb\x0b\x01\xe0\x00\x25\x4f\x6e\x31\x7a\x02\x02\xbe\xe2\xc6\x83\xef\xcc\x07\xd7\x09\x1b\xf1\xc1\x69\xf4\x27\x5e\xd1\xcf\xa1\xe5\xd7\x85\xf6\xbd\x6e\xd0\x96\x65\x7b\x6d\x8b\xb6\x1c\xda\xc3\x26\x6d\xa5\xcd\x6b\x8e\xbe\x6e\xd3\x5e\xb7\x69\xaf\xdb\xb4\xd7\x6d\xda\xeb\x36\xed\x75\x9b\xf6\x9f\xf7\xd6\x46\xc5\x7c\xf8\x6b\x3d\xfb\x79\x76\x53\xe4\x8a\xe5\x61\x64\xed\x20\xd3\x98\x73\xf3\xe5\x7a\x5f\xb1\x5d\x3f\xa7\xb7\x71\x06\x6b\x15\xf5\xb3\xb3\xb3\x67\xce\x31\xed\xfe\x14\x7a\x28\x9f\x9d\x0f\x27\x01\x37\x4e\x76\xd0\x09\xcb\x3f\x5d\xc1\x7e\x10\xce\x0b\x01\xcd\xee\x6f\x6d\xeb\x69\xb1\x6e\xf6\xf1\x23\x08\xb4\x75\x3a\xa3\xb5\x51\xdd\xdc\xb9\x42\xc1\x48\xf2\x8d\x8b\xdb\x33\xb5\xeb\x2b\xa6\xb5\x86\xc8\x14\x8c\x16\x2f\xfb\x18\xf8\xb8\xd8\x3c\x3a\x9e\xb1\x5d\x4a\x1a\x95\x88\xde\xb6\xf2\xdf\xd6\x66\x5d\xf9\x93\x9c\x4d\xcd\x4d\x7c\x28\x78\x8d\xca\x88\x47\x0b\x73\x0c\x5c\xa5\x49\xcb\xb2\x1e\xca\xea\xbf\x03\x00\x00\xff\xff\xa1\xf8\x6b\x5d\x05\x37\x00\x00"), }, } fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{ diff --git a/config/config.go b/config/config.go index 4d694eefcc..6c905f6279 100644 --- a/config/config.go +++ b/config/config.go @@ -266,6 +266,9 @@ func resolveFilepaths(baseDir string, cfg *Config) { for _, cfg := range receiver.JiraConfigs { cfg.HTTPConfig.SetDirectory(baseDir) } + for _, cfg := range receiver.RocketchatConfigs { + cfg.HTTPConfig.SetDirectory(baseDir) + } } } @@ -361,6 +364,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { return fmt.Errorf("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 fmt.Errorf("at most one of rocketchat_token & rocketchat_token_file must be configured") + } + + if c.Global.RocketchatTokenID != nil && len(c.Global.RocketchatTokenIDFile) > 0 { + return fmt.Errorf("at most one of rocketchat_token_id & rocketchat_token_id_file must be configured") + } + names := map[string]struct{}{} for _, rcv := range c.Receivers { @@ -562,6 +573,28 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { 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 fmt.Errorf("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 fmt.Errorf("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{}{} } @@ -654,17 +687,18 @@ func DefaultGlobalConfig() GlobalConfig { defaultSMTPTLSConfig := commoncfg.TLSConfig{} return GlobalConfig{ - ResolveTimeout: model.Duration(5 * time.Minute), - HTTPConfig: &defaultHTTPConfig, - SMTPHello: "localhost", - SMTPRequireTLS: true, - SMTPTLSConfig: &defaultSMTPTLSConfig, - PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"), - OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"), - WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"), - VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"), - TelegramAPIUrl: mustParseURL("https://api.telegram.org"), - WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"), + ResolveTimeout: model.Duration(5 * time.Minute), + HTTPConfig: &defaultHTTPConfig, + SMTPHello: "localhost", + SMTPRequireTLS: true, + SMTPTLSConfig: &defaultSMTPTLSConfig, + PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"), + OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"), + WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"), + VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"), + TelegramAPIUrl: mustParseURL("https://api.telegram.org"), + WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"), + RocketchatAPIURL: mustParseURL("https://open.rocket.chat/"), } } @@ -766,31 +800,36 @@ type GlobalConfig struct { HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` - JiraAPIURL *URL `yaml:"jira_api_url,omitempty" json:"jira_api_url,omitempty"` - SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"` - SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"` - SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,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"` - 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"` - SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,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"` - 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"` - PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_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"` - 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"` - 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"` - VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,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"` - TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"` - WebexAPIURL *URL `yaml:"webex_api_url,omitempty" json:"webex_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"` + SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"` + SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,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"` + 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"` + SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,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"` + 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"` + PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_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"` + 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"` + 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"` + VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,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"` + TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_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. @@ -922,20 +961,21 @@ type Receiver struct { // A unique identifier for this receiver. Name string `yaml:"name" json:"name"` - DiscordConfigs []*DiscordConfig `yaml:"discord_configs,omitempty" json:"discord_configs,omitempty"` - EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"` - PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"` - SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"` - WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"` - OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"` - WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"` - PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"` - VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"` - SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"` - 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"` - JiraConfigs []*JiraConfig `yaml:"jira_configs,omitempty" json:"jira_configs,omitempty"` + DiscordConfigs []*DiscordConfig `yaml:"discord_configs,omitempty" json:"discord_configs,omitempty"` + EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"` + PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"` + SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"` + WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"` + OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"` + WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"` + PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"` + VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"` + SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"` + 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"` + 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. diff --git a/config/config_test.go b/config/config_test.go index fa21bb498a..40f7d4598a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -872,14 +872,15 @@ func TestEmptyFieldsAndRegex(t *testing.T) { SMTPTLSConfig: &commoncfg.TLSConfig{ InsecureSkipVerify: false, }, - SlackAPIURL: (*SecretURL)(mustParseURL("http://slack.example.com/")), - SMTPRequireTLS: true, - PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"), - OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"), - WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"), - VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"), - TelegramAPIUrl: mustParseURL("https://api.telegram.org"), - WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"), + SlackAPIURL: (*SecretURL)(mustParseURL("http://slack.example.com/")), + SMTPRequireTLS: true, + PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"), + OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"), + WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"), + VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"), + TelegramAPIUrl: mustParseURL("https://api.telegram.org"), + WebexAPIURL: mustParseURL("https://webexapis.com/v1/messages"), + RocketchatAPIURL: mustParseURL("https://open.rocket.chat/"), }, 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) + } + + defaultToken := conf.Global.RocketchatTokenFile + overrideToken := "/override_file" + if defaultToken != conf.Receivers[0].RocketchatConfigs[0].TokenFile { + t.Fatalf("Invalid Rocketchat key_file: %s\nExpected: %s", conf.Receivers[0].RocketchatConfigs[0].TokenFile, defaultToken) + } + if overrideToken != conf.Receivers[1].RocketchatConfigs[0].TokenFile { + t.Errorf("Invalid Rocketchat key_file: %s\nExpected: %s", conf.Receivers[0].RocketchatConfigs[0].TokenFile, overrideToken) + } +} + +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) + } + + defaultTokenID := conf.Global.RocketchatTokenIDFile + overrideTokenID := "/override_file" + if defaultTokenID != conf.Receivers[0].RocketchatConfigs[0].TokenIDFile { + t.Fatalf("Invalid Rocketchat key_file: %s\nExpected: %s", conf.Receivers[0].RocketchatConfigs[0].TokenIDFile, defaultTokenID) + } + if overrideTokenID != conf.Receivers[1].RocketchatConfigs[0].TokenIDFile { + t.Errorf("Invalid Rocketchat key_file: %s\nExpected: %s", conf.Receivers[0].RocketchatConfigs[0].TokenIDFile, overrideTokenID) + } +} + +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) { for _, tc := range []struct { in string diff --git a/config/notifiers.go b/config/notifiers.go index 6984166170..45b87b0bcd 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -98,6 +98,18 @@ var ( CallbackID: `{{ template "slack.default.callbackid" . }}`, 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 = OpsGenieConfig{ @@ -870,6 +882,77 @@ func (c *JiraConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if c.IssueType == "" { return fmt.Errorf("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"` +} + +type RocketchatAttachmentActionType string + +type RocketchatMessageProcessingType string + +const ( + ProcessingTypeSendMessage RocketchatMessageProcessingType = "sendMessage" + ProcessingTypeRespondWithMessage RocketchatMessageProcessingType = "respondWithMessage" +) + +type RocketchatAttachmentAction struct { + Type RocketchatAttachmentActionType `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 RocketchatMessageProcessingType `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 fmt.Errorf("at most one of token & token_file must be configured") + } + if c.TokenID != nil && len(c.TokenIDFile) > 0 { + return fmt.Errorf("at most one of token_id & token_id_file must be configured") + } return nil } diff --git a/config/receiver/receiver.go b/config/receiver/receiver.go index 040d7534b9..a8572abe44 100644 --- a/config/receiver/receiver.go +++ b/config/receiver/receiver.go @@ -27,6 +27,7 @@ import ( "github.com/prometheus/alertmanager/notify/opsgenie" "github.com/prometheus/alertmanager/notify/pagerduty" "github.com/prometheus/alertmanager/notify/pushover" + "github.com/prometheus/alertmanager/notify/rocketchat" "github.com/prometheus/alertmanager/notify/slack" "github.com/prometheus/alertmanager/notify/sns" "github.com/prometheus/alertmanager/notify/telegram" @@ -96,6 +97,9 @@ func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logg for i, c := range nc.JiraConfigs { 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 { return nil, &errs diff --git a/config/testdata/conf.rocketchat-both-token-and-tokenfile.yml b/config/testdata/conf.rocketchat-both-token-and-tokenfile.yml new file mode 100644 index 0000000000..b51cebde47 --- /dev/null +++ b/config/testdata/conf.rocketchat-both-token-and-tokenfile.yml @@ -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' diff --git a/config/testdata/conf.rocketchat-both-tokenid-and-tokenidfile.yml b/config/testdata/conf.rocketchat-both-tokenid-and-tokenidfile.yml new file mode 100644 index 0000000000..702e0522c7 --- /dev/null +++ b/config/testdata/conf.rocketchat-both-tokenid-and-tokenidfile.yml @@ -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' diff --git a/config/testdata/conf.rocketchat-default-token-file.yml b/config/testdata/conf.rocketchat-default-token-file.yml new file mode 100644 index 0000000000..c298422938 --- /dev/null +++ b/config/testdata/conf.rocketchat-default-token-file.yml @@ -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 diff --git a/config/testdata/conf.rocketchat-default-token.yml b/config/testdata/conf.rocketchat-default-token.yml new file mode 100644 index 0000000000..01387472c8 --- /dev/null +++ b/config/testdata/conf.rocketchat-default-token.yml @@ -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 diff --git a/config/testdata/conf.rocketchat-no-token.yml b/config/testdata/conf.rocketchat-no-token.yml new file mode 100644 index 0000000000..ca44388828 --- /dev/null +++ b/config/testdata/conf.rocketchat-no-token.yml @@ -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' diff --git a/docs/configuration.md b/docs/configuration.md index 3269b3a59e..8a156260a6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -110,6 +110,11 @@ global: [ opsgenie_api_key: ] [ opsgenie_api_key_file: ] [ opsgenie_api_url: | default = "https://api.opsgenie.com/" ] + [ rocketchat_api_url: | default = "https://open.rocket.chat/" ] + [ rocketchat_token: ] + [ rocketchat_token_file: ] + [ rocketchat_token_id: ] + [ rocketchat_token_id_file: ] [ wechat_api_url: | default = "https://qyapi.weixin.qq.com/cgi-bin/" ] [ wechat_api_secret: ] [ wechat_api_corp_id: ] @@ -708,6 +713,8 @@ pagerduty_configs: [ - , ... ] pushover_configs: [ - , ... ] +rocket_configs: + [ - , ... ] slack_configs: [ - , ... ] sns_configs: @@ -1256,6 +1263,62 @@ token_file: [ http_config: | default = global.http_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: | default = true ] +[ api_url: | default = global.rocketchat_api_url ] +[ channel: | 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: +token_file: +token_id: +token_id_file: + + +[ color: ... ] +[ image_url ] +[ thumb_url ] +[ link_names ] +[ short_fields: | default = false ] +actions: + [ ... ] +``` + +#### `` + +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: ] +[ value: ] +[ short: | default = slack_config.short_fields ] +``` + +#### `` +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: | ignored, only "button" is supported ] +[ text: ] +[ url: ] +[ msg: ] + ### `` Slack notifications can be sent via [Incoming webhooks](https://api.slack.com/messaging/webhooks) or [Bot tokens](https://api.slack.com/authentication/token-types). diff --git a/notify/notify.go b/notify/notify.go index b5e1ae91b6..43df43a38b 100644 --- a/notify/notify.go +++ b/notify/notify.go @@ -366,6 +366,7 @@ func (m *Metrics) InitializeFor(receiver map[string][]Integration) { "webex", "msteams", "jira", + "rocketchat", } { m.numNotifications.WithLabelValues(integration) m.numNotificationRequestsTotal.WithLabelValues(integration) diff --git a/notify/rocketchat/rocketchat.go b/notify/rocketchat/rocketchat.go new file mode 100644 index 0000000000..085acf7783 --- /dev/null +++ b/notify/rocketchat/rocketchat.go @@ -0,0 +1,272 @@ +// 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 (rcrt rocketchatRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) { + req.Header.Set("X-Auth-Token", rcrt.token) + req.Header.Set("X-User-Id", rcrt.tokenID) + return rcrt.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: ¬ify.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 { + rocketchatAction := config.RocketchatAttachmentAction{ + Type: "button", // Only button type is supported + Text: tmplText(action.Text), + URL: tmplText(action.URL), + Msg: tmplText(action.Msg), + } + + actions[index] = rocketchatAction + } + 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 := fmt.Sprintf("%s/%s", n.conf.APIURL.String(), "api/v1/chat.postMessage") + 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("%w: channel %q", err, body.Channel) + 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("%w: channel %q", err, body.Channel) + 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("%w: could not read response body: ", 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("%w: could not unmarshal JSON response %q", err, string(body)) + } + if !data.Success { + return false, fmt.Errorf("error response from Rocketchat: %s", data.Error) + } + return false, nil +} diff --git a/notify/rocketchat/rocketchat_test.go b/notify/rocketchat/rocketchat_test.go new file mode 100644 index 0000000000..35033d3db7 --- /dev/null +++ b/notify/rocketchat/rocketchat_test.go @@ -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) +} diff --git a/template/default.tmpl b/template/default.tmpl index 299bdecbb6..8ebf1bd6aa 100644 --- a/template/default.tmpl +++ b/template/default.tmpl @@ -197,3 +197,10 @@ Alerts Resolved: {{- end -}} {{- $priority -}} {{- 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 }} diff --git a/template/email.tmpl b/template/email.tmpl index 4990c786b0..092d6d2873 100644 --- a/template/email.tmpl +++ b/template/email.tmpl @@ -27,11 +27,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - + - - + + {{ template "__subject" . }} - + @@ -120,11 +120,11 @@ h4 { {{ range .Alerts.Firing }} {{ end }} @@ -133,9 +133,9 @@ h4 { {{ if gt (len .Alerts.Firing) 0 }} {{ end }} @@ -148,11 +148,11 @@ h4 { {{ range .Alerts.Resolved }} {{ end }}
- Labels

- {{ range .Labels.SortedPairs }}{{ .Name }} = {{ .Value }}

{{ end }} - {{ if gt (len .Annotations) 0 }}Annotations

{{ end }} - {{ range .Annotations.SortedPairs }}{{ .Name }} = {{ .Value }}

{{ end }} - Source

+ Labels
+ {{ range .Labels.SortedPairs }}{{ .Name }} = {{ .Value }}
{{ end }} + {{ if gt (len .Annotations) 0 }}Annotations
{{ end }} + {{ range .Annotations.SortedPairs }}{{ .Name }} = {{ .Value }}
{{ end }} + Source
-

-
-

+
+
+
- Labels

- {{ range .Labels.SortedPairs }}{{ .Name }} = {{ .Value }}

{{ end }} - {{ if gt (len .Annotations) 0 }}Annotations

{{ end }} - {{ range .Annotations.SortedPairs }}{{ .Name }} = {{ .Value }}

{{ end }} - Source

+ Labels
+ {{ range .Labels.SortedPairs }}{{ .Name }} = {{ .Value }}
{{ end }} + {{ if gt (len .Annotations) 0 }}Annotations
{{ end }} + {{ range .Annotations.SortedPairs }}{{ .Name }} = {{ .Value }}
{{ end }} + Source