diff --git a/config/notifiers.go b/config/notifiers.go index 29b5c383e4..27aa736786 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -898,6 +898,10 @@ type TelegramConfig struct { Message string `yaml:"message,omitempty" json:"message,omitempty"` DisableNotifications bool `yaml:"disable_notifications,omitempty" json:"disable_notifications,omitempty"` ParseMode string `yaml:"parse_mode,omitempty" json:"parse_mode,omitempty"` + + // Timeout is the maximum time allowed to invoke the telegram. Setting this to 0 + // does not impose a timeout. + Timeout time.Duration `yaml:"timeout" json:"timeout"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/docs/configuration.md b/docs/configuration.md index 72cce8c2df..e2e3086874 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1816,6 +1816,12 @@ attributes: # The HTTP client's configuration. [ http_config: | default = global.http_config ] + +# The maximum time to wait for a telegram request to complete, before failing the +# request and allowing it to be retried. The default value of 0s indicates that +# no timeout should be applied. +# NOTE: This will have no effect if set higher than the group_interval. +[ timeout: | default = 0s ] ``` ### `` diff --git a/notify/telegram/telegram.go b/notify/telegram/telegram.go index 07bbedbbe8..9e6296d0c0 100644 --- a/notify/telegram/telegram.go +++ b/notify/telegram/telegram.go @@ -50,6 +50,10 @@ func New(conf *config.TelegramConfig, t *template.Template, l *slog.Logger, http return nil, err } + if conf.Timeout > 0 { + httpclient.Timeout = conf.Timeout + } + client, err := createTelegramClient(conf.APIUrl.String(), conf.ParseMode, httpclient) if err != nil { return nil, err diff --git a/notify/telegram/telegram_test.go b/notify/telegram/telegram_test.go index 7192d493fd..622c0a29fd 100644 --- a/notify/telegram/telegram_test.go +++ b/notify/telegram/telegram_test.go @@ -213,3 +213,65 @@ func TestTelegramNotify(t *testing.T) { }) } } + +func TestTelegramTimeout(t *testing.T) { + token := "secret" + + tests := []struct { + name string + latency time.Duration + timeout time.Duration + wantErr bool + }{ + { + name: "success", + latency: 100 * time.Millisecond, + timeout: 120 * time.Millisecond, + wantErr: false, + }, + { + name: "timeout", + latency: 100 * time.Millisecond, + timeout: 80 * time.Millisecond, + wantErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/bot"+token+"/sendMessage", r.URL.Path) + _, err := io.ReadAll(r.Body) + require.NoError(t, err) + time.Sleep(tc.latency) + w.Write([]byte(`{"ok":true,"result":{"chat":{}}}`)) + })) + defer srv.Close() + u, _ := url.Parse(srv.URL) + + cfg := &config.TelegramConfig{ + Message: "test", + HTTPConfig: &commoncfg.HTTPClientConfig{}, + BotToken: commoncfg.Secret(token), + Timeout: tc.timeout, + APIUrl: &amcommoncfg.URL{URL: u}, + } + + notifier, err := New(cfg, test.CreateTmpl(t), promslog.NewNopLogger()) + require.NoError(t, err) + + ctx := context.Background() + ctx = notify.WithGroupKey(ctx, "1") + + alert := &types.Alert{ + Alert: model.Alert{ + StartsAt: time.Now(), + EndsAt: time.Now().Add(time.Hour), + }, + } + + _, err = notifier.Notify(ctx, alert) + require.Equal(t, tc.wantErr, err != nil) + }) + } +}