From 4e48a1c07b7583f9b3549fbf27d06e2c4e24f753 Mon Sep 17 00:00:00 2001 From: Ali Afsharzadeh Date: Fri, 30 Jan 2026 19:03:36 +0330 Subject: [PATCH] Support global Telegram bot token (#4823) * Support global Telegram bot token --------- Signed-off-by: Ali Afsharzadeh Co-authored-by: Ben Kochie --- config/config.go | 13 ++++ config/config_test.go | 62 +++++++++++++++++++ config/notifiers.go | 3 - config/notifiers_test.go | 8 --- .../conf.telegram-both-bot-token-and-file.yml | 14 +++++ .../conf.telegram-default-bot-token-file.yml | 13 ++++ .../conf.telegram-default-bot-token.yml | 13 ++++ .../testdata/conf.telegram-no-bot-token.yml | 7 +++ ...valid-receiver-both-bot-token-and-file.yml | 11 ++++ 9 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 config/testdata/conf.telegram-both-bot-token-and-file.yml create mode 100644 config/testdata/conf.telegram-default-bot-token-file.yml create mode 100644 config/testdata/conf.telegram-default-bot-token.yml create mode 100644 config/testdata/conf.telegram-no-bot-token.yml create mode 100644 config/testdata/conf.telegram-valid-receiver-both-bot-token-and-file.yml diff --git a/config/config.go b/config/config.go index b56ffb2f9..d01076147 100644 --- a/config/config.go +++ b/config/config.go @@ -488,6 +488,10 @@ func (c *Config) UnmarshalYAML(unmarshal func(any) error) error { return errors.New("at most one of victorops_api_key & victorops_api_key_file must be configured") } + if c.Global.TelegramBotToken != "" && len(c.Global.TelegramBotTokenFile) > 0 { + return errors.New("at most one of telegram_bot_token & telegram_bot_token_file must be configured") + } + if len(c.Global.SMTPAuthPassword) > 0 && len(c.Global.SMTPAuthPasswordFile) > 0 { return errors.New("at most one of smtp_auth_password & smtp_auth_password_file must be configured") } @@ -652,6 +656,13 @@ func (c *Config) UnmarshalYAML(unmarshal func(any) error) error { for _, telegram := range rcv.TelegramConfigs { telegram.HTTPConfig = cmp.Or(telegram.HTTPConfig, c.Global.HTTPConfig) telegram.APIUrl = cmp.Or(telegram.APIUrl, c.Global.TelegramAPIUrl) + if telegram.BotToken == "" && len(telegram.BotTokenFile) == 0 { + if c.Global.TelegramBotToken == "" && len(c.Global.TelegramBotTokenFile) == 0 { + return errors.New("missing bot_token or bot_token_file on telegram_config") + } + telegram.BotToken = c.Global.TelegramBotToken + telegram.BotTokenFile = c.Global.TelegramBotTokenFile + } } for _, discord := range rcv.DiscordConfigs { discord.HTTPConfig = cmp.Or(discord.HTTPConfig, c.Global.HTTPConfig) @@ -940,6 +951,8 @@ type GlobalConfig struct { VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"` VictorOpsAPIKeyFile string `yaml:"victorops_api_key_file,omitempty" json:"victorops_api_key_file,omitempty"` TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"` + TelegramBotToken Secret `yaml:"telegram_bot_token,omitempty" json:"telegram_bot_token,omitempty"` + TelegramBotTokenFile string `yaml:"telegram_bot_token_file,omitempty" json:"telegram_bot_token_file,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"` diff --git a/config/config_test.go b/config/config_test.go index 1ab8b8e8b..3af84e9f4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1144,6 +1144,68 @@ func TestVictorOpsNoAPIKey(t *testing.T) { } } +func TestTelegramDefaultBotToken(t *testing.T) { + conf, err := LoadFile("testdata/conf.telegram-default-bot-token.yml") + if err != nil { + t.Fatalf("Error parsing %s: %s", "testdata/conf.telegram-default-bot-token.yml", err) + } + + defaultBotToken := conf.Global.TelegramBotToken + overrideBotToken := Secret("qwe456") + if defaultBotToken != conf.Receivers[0].TelegramConfigs[0].BotToken { + t.Fatalf("Invalid telegram bot token: %s\nExpected: %s", conf.Receivers[0].TelegramConfigs[0].BotToken, defaultBotToken) + } + if overrideBotToken != conf.Receivers[1].TelegramConfigs[0].BotToken { + t.Errorf("Invalid telegram bot token: %s\nExpected: %s", conf.Receivers[0].TelegramConfigs[0].BotToken, string(overrideBotToken)) + } +} + +func TestTelegramDefaultBotTokenFile(t *testing.T) { + conf, err := LoadFile("testdata/conf.telegram-default-bot-token-file.yml") + if err != nil { + t.Fatalf("Error parsing %s: %s", "testdata/conf.telegram-default-bot-token-file.yml", err) + } + + defaultBotToken := conf.Global.TelegramBotTokenFile + overrideBotToken := "/override_file" + if defaultBotToken != conf.Receivers[0].TelegramConfigs[0].BotTokenFile { + t.Fatalf("Invalid telegram bot token file: %s\nExpected: %s", conf.Receivers[0].TelegramConfigs[0].BotTokenFile, defaultBotToken) + } + if overrideBotToken != conf.Receivers[1].TelegramConfigs[0].BotTokenFile { + t.Errorf("Invalid telegram bot token file: %s\nExpected: %s", conf.Receivers[0].TelegramConfigs[0].BotTokenFile, overrideBotToken) + } +} + +func TestTelegramBothBotTokenAndFile(t *testing.T) { + _, err := LoadFile("testdata/conf.telegram-both-bot-token-and-file.yml") + if err == nil { + t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.telegram-both-bot-token-and-file.yml", err) + } + if err.Error() != "at most one of telegram_bot_token & telegram_bot_token_file must be configured" { + t.Errorf("Expected: %s\nGot: %s", "at most one of telegram_bot_token & telegram_bot_token_file must be configured", err.Error()) + } +} + +func TestTelegramValidReceiverBothBotTokenAndFile(t *testing.T) { + _, err := LoadFile("testdata/conf.telegram-valid-receiver-both-bot-token-and-file.yml") + if err == nil { + t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.telegram-valid-receiver-both-bot-token-and-file.yml", err) + } + if err.Error() != "at most one of telegram_bot_token & telegram_bot_token_file must be configured" { + t.Errorf("Expected: %s\nGot: %s", "at most one of telegram_bot_token & telegram_bot_token_file must be configured", err.Error()) + } +} + +func TestTelegramNoBotToken(t *testing.T) { + _, err := LoadFile("testdata/conf.telegram-no-bot-token.yml") + if err == nil { + t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.telegram-no-bot-token.yml", err) + } + if err.Error() != "missing bot_token or bot_token_file on telegram_config" { + t.Errorf("Expected: %s\nGot: %s", "missing bot_token or bot_token_file on telegram_config", err.Error()) + } +} + func TestOpsGenieDefaultAPIKey(t *testing.T) { conf, err := LoadFile("testdata/conf.opsgenie-default-apikey.yml") if err != nil { diff --git a/config/notifiers.go b/config/notifiers.go index 1124f3c6d..ed6b6e90d 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -938,9 +938,6 @@ func (c *TelegramConfig) UnmarshalYAML(unmarshal func(any) error) error { if err := unmarshal((*plain)(c)); err != nil { return err } - if c.BotToken == "" && c.BotTokenFile == "" { - return errors.New("missing bot_token or bot_token_file on telegram_config") - } if c.BotToken != "" && c.BotTokenFile != "" { return errors.New("at most one of bot_token & bot_token_file must be configured") } diff --git a/config/notifiers_test.go b/config/notifiers_test.go index fff286d28..1ad4c57cd 100644 --- a/config/notifiers_test.go +++ b/config/notifiers_test.go @@ -1099,14 +1099,6 @@ 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: ` diff --git a/config/testdata/conf.telegram-both-bot-token-and-file.yml b/config/testdata/conf.telegram-both-bot-token-and-file.yml new file mode 100644 index 000000000..8220797c6 --- /dev/null +++ b/config/testdata/conf.telegram-both-bot-token-and-file.yml @@ -0,0 +1,14 @@ +global: + telegram_bot_token: asd132 + telegram_bot_token_file: /global_file +route: + receiver: team-X-telegram + +receivers: + - name: team-X-telegram + telegram_configs: + - chat_id: 123 + - name: team-Y-telegram + telegram_configs: + - chat_id: 456 + bot_token: qwe456 diff --git a/config/testdata/conf.telegram-default-bot-token-file.yml b/config/testdata/conf.telegram-default-bot-token-file.yml new file mode 100644 index 000000000..f81a31a29 --- /dev/null +++ b/config/testdata/conf.telegram-default-bot-token-file.yml @@ -0,0 +1,13 @@ +global: + telegram_bot_token_file: /global_file +route: + receiver: team-X-telegram + +receivers: + - name: team-X-telegram + telegram_configs: + - chat_id: 123 + - name: team-Y-telegram + telegram_configs: + - chat_id: 456 + bot_token_file: /override_file diff --git a/config/testdata/conf.telegram-default-bot-token.yml b/config/testdata/conf.telegram-default-bot-token.yml new file mode 100644 index 000000000..de59b01d5 --- /dev/null +++ b/config/testdata/conf.telegram-default-bot-token.yml @@ -0,0 +1,13 @@ +global: + telegram_bot_token: asd132 +route: + receiver: team-X-telegram + +receivers: + - name: team-X-telegram + telegram_configs: + - chat_id: 123 + - name: team-Y-telegram + telegram_configs: + - chat_id: 456 + bot_token: qwe456 diff --git a/config/testdata/conf.telegram-no-bot-token.yml b/config/testdata/conf.telegram-no-bot-token.yml new file mode 100644 index 000000000..e13b8a3d2 --- /dev/null +++ b/config/testdata/conf.telegram-no-bot-token.yml @@ -0,0 +1,7 @@ +route: + receiver: team-X-telegram + +receivers: + - name: team-X-telegram + telegram_configs: + - chat_id: 123 diff --git a/config/testdata/conf.telegram-valid-receiver-both-bot-token-and-file.yml b/config/testdata/conf.telegram-valid-receiver-both-bot-token-and-file.yml new file mode 100644 index 000000000..c006513a5 --- /dev/null +++ b/config/testdata/conf.telegram-valid-receiver-both-bot-token-and-file.yml @@ -0,0 +1,11 @@ +global: + telegram_bot_token: asd132 + telegram_bot_token_file: /global_file +route: + receiver: team-X-telegram + +receivers: + - name: team-X-telegram + telegram_configs: + - chat_id: 123 + bot_token_file: /override_file