Support loading Telegram bot token from file
Signed-off-by: Andrey Mishakin <stieroglif@gmail.com>
This commit is contained in:
parent
f59460bfd4
commit
6c9c58015e
|
@ -739,6 +739,7 @@ type TelegramConfig struct {
|
||||||
|
|
||||||
APIUrl *URL `yaml:"api_url" json:"api_url,omitempty"`
|
APIUrl *URL `yaml:"api_url" json:"api_url,omitempty"`
|
||||||
BotToken Secret `yaml:"bot_token,omitempty" json:"token,omitempty"`
|
BotToken Secret `yaml:"bot_token,omitempty" json:"token,omitempty"`
|
||||||
|
BotTokenFile string `yaml:"bot_token_file,omitempty" json:"token_file,omitempty"`
|
||||||
ChatID int64 `yaml:"chat_id,omitempty" json:"chat,omitempty"`
|
ChatID int64 `yaml:"chat_id,omitempty" json:"chat,omitempty"`
|
||||||
Message string `yaml:"message,omitempty" json:"message,omitempty"`
|
Message string `yaml:"message,omitempty" json:"message,omitempty"`
|
||||||
DisableNotifications bool `yaml:"disable_notifications,omitempty" json:"disable_notifications,omitempty"`
|
DisableNotifications bool `yaml:"disable_notifications,omitempty" json:"disable_notifications,omitempty"`
|
||||||
|
@ -752,8 +753,11 @@ func (c *TelegramConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
|
||||||
if err := unmarshal((*plain)(c)); err != nil {
|
if err := unmarshal((*plain)(c)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.BotToken == "" {
|
if c.BotToken == "" && c.BotTokenFile == "" {
|
||||||
return fmt.Errorf("missing bot_token on telegram_config")
|
return fmt.Errorf("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")
|
||||||
}
|
}
|
||||||
if c.ChatID == 0 {
|
if c.ChatID == 0 {
|
||||||
return fmt.Errorf("missing chat_id on telegram_config")
|
return fmt.Errorf("missing chat_id on telegram_config")
|
||||||
|
|
|
@ -958,6 +958,70 @@ http_config:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTelegramConfiguration(t *testing.T) {
|
||||||
|
tc := []struct {
|
||||||
|
name string
|
||||||
|
in string
|
||||||
|
expected error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with both bot_token & bot_token_file - it fails",
|
||||||
|
in: `
|
||||||
|
bot_token: xyz
|
||||||
|
bot_token_file: /file
|
||||||
|
`,
|
||||||
|
expected: errors.New("at most one of bot_token & bot_token_file must be configured"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with no bot_token & bot_token_file - it fails",
|
||||||
|
in: `
|
||||||
|
bot_token: ''
|
||||||
|
bot_token_file: ''
|
||||||
|
`,
|
||||||
|
expected: errors.New("missing bot_token or bot_token_file on telegram_config"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with bot_token and chat_id set - it succeeds",
|
||||||
|
in: `
|
||||||
|
bot_token: xyz
|
||||||
|
chat_id: 123
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with bot_token_file and chat_id set - it succeeds",
|
||||||
|
in: `
|
||||||
|
bot_token_file: /file
|
||||||
|
chat_id: 123
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with no chat_id set - it fails",
|
||||||
|
in: `
|
||||||
|
bot_token: xyz
|
||||||
|
`,
|
||||||
|
expected: errors.New("missing chat_id on telegram_config"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with unknown parse_mode - it fails",
|
||||||
|
in: `
|
||||||
|
bot_token: xyz
|
||||||
|
chat_id: 123
|
||||||
|
parse_mode: invalid
|
||||||
|
`,
|
||||||
|
expected: errors.New("unknown parse_mode on telegram_config, must be Markdown, MarkdownV2, HTML or empty string"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tc {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var cfg TelegramConfig
|
||||||
|
err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)
|
||||||
|
|
||||||
|
require.Equal(t, tt.expected, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newBoolPointer(b bool) *bool {
|
func newBoolPointer(b bool) *bool {
|
||||||
return &b
|
return &b
|
||||||
}
|
}
|
||||||
|
|
|
@ -1050,9 +1050,12 @@ attributes:
|
||||||
# If not specified, default API URL will be used.
|
# If not specified, default API URL will be used.
|
||||||
[ api_url: <string> | default = global.telegram_api_url ]
|
[ api_url: <string> | default = global.telegram_api_url ]
|
||||||
|
|
||||||
# Telegram bot token.
|
# Telegram bot token. It is mutually exclusive with `bot_token_file`.
|
||||||
[ bot_token: <secret> ]
|
[ bot_token: <secret> ]
|
||||||
|
|
||||||
|
# Read the Telegram bot token from a file. It is mutually exclusive with `bot_token`.
|
||||||
|
[ bot_token_file: <filepath> ]
|
||||||
|
|
||||||
# ID of the chat where to send the messages.
|
# ID of the chat where to send the messages.
|
||||||
[ chat_id: <int> ]
|
[ chat_id: <int> ]
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/go-kit/log/level"
|
"github.com/go-kit/log/level"
|
||||||
|
@ -48,7 +50,7 @@ func New(conf *config.TelegramConfig, t *template.Template, l log.Logger, httpOp
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := createTelegramClient(conf.BotToken, conf.APIUrl.String(), conf.ParseMode, httpclient)
|
client, err := createTelegramClient(conf.APIUrl.String(), conf.ParseMode, httpclient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -83,6 +85,11 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err
|
||||||
level.Warn(n.logger).Log("msg", "Truncated message", "alert", key, "max_runes", maxMessageLenRunes)
|
level.Warn(n.logger).Log("msg", "Truncated message", "alert", key, "max_runes", maxMessageLenRunes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n.client.Token, err = n.getBotToken()
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
message, err := n.client.Send(telebot.ChatID(n.conf.ChatID), messageText, &telebot.SendOptions{
|
message, err := n.client.Send(telebot.ChatID(n.conf.ChatID), messageText, &telebot.SendOptions{
|
||||||
DisableNotification: n.conf.DisableNotifications,
|
DisableNotification: n.conf.DisableNotifications,
|
||||||
DisableWebPagePreview: true,
|
DisableWebPagePreview: true,
|
||||||
|
@ -95,10 +102,8 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTelegramClient(token config.Secret, apiURL, parseMode string, httpClient *http.Client) (*telebot.Bot, error) {
|
func createTelegramClient(apiURL, parseMode string, httpClient *http.Client) (*telebot.Bot, error) {
|
||||||
secret := string(token)
|
|
||||||
bot, err := telebot.NewBot(telebot.Settings{
|
bot, err := telebot.NewBot(telebot.Settings{
|
||||||
Token: secret,
|
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
ParseMode: parseMode,
|
ParseMode: parseMode,
|
||||||
Client: httpClient,
|
Client: httpClient,
|
||||||
|
@ -110,3 +115,14 @@ func createTelegramClient(token config.Secret, apiURL, parseMode string, httpCli
|
||||||
|
|
||||||
return bot, nil
|
return bot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) getBotToken() (string, error) {
|
||||||
|
if len(n.conf.BotTokenFile) > 0 {
|
||||||
|
content, err := os.ReadFile(n.conf.BotTokenFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not read %s: %w", n.conf.BotTokenFile, err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(content)), nil
|
||||||
|
}
|
||||||
|
return string(n.conf.BotToken), nil
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -84,6 +85,13 @@ func TestTelegramRetry(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTelegramNotify(t *testing.T) {
|
func TestTelegramNotify(t *testing.T) {
|
||||||
|
token := "secret"
|
||||||
|
|
||||||
|
fileWithToken, err := os.CreateTemp("", "telegram-bot-token")
|
||||||
|
require.NoError(t, err, "creating temp file failed")
|
||||||
|
_, err = fileWithToken.WriteString(token)
|
||||||
|
require.NoError(t, err, "writing to temp file failed")
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
cfg config.TelegramConfig
|
cfg config.TelegramConfig
|
||||||
|
@ -94,6 +102,7 @@ func TestTelegramNotify(t *testing.T) {
|
||||||
cfg: config.TelegramConfig{
|
cfg: config.TelegramConfig{
|
||||||
Message: "<code>x < y</code>",
|
Message: "<code>x < y</code>",
|
||||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||||
|
BotToken: config.Secret(token),
|
||||||
},
|
},
|
||||||
expText: "<code>x < y</code>",
|
expText: "<code>x < y</code>",
|
||||||
},
|
},
|
||||||
|
@ -103,13 +112,24 @@ func TestTelegramNotify(t *testing.T) {
|
||||||
ParseMode: "HTML",
|
ParseMode: "HTML",
|
||||||
Message: "<code>x < y</code>",
|
Message: "<code>x < y</code>",
|
||||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||||
|
BotToken: config.Secret(token),
|
||||||
},
|
},
|
||||||
expText: "<code>x < y</code>",
|
expText: "<code>x < y</code>",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Bot token from file",
|
||||||
|
cfg: config.TelegramConfig{
|
||||||
|
Message: "test",
|
||||||
|
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||||
|
BotTokenFile: fileWithToken.Name(),
|
||||||
|
},
|
||||||
|
expText: "test",
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var out []byte
|
var out []byte
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, "/bot"+token+"/sendMessage", r.URL.Path)
|
||||||
var err error
|
var err error
|
||||||
out, err = io.ReadAll(r.Body)
|
out, err = io.ReadAll(r.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
Loading…
Reference in New Issue