diff --git a/docs/services/alertmanager.md b/docs/services/alertmanager.md index 033a76a2..c8717417 100644 --- a/docs/services/alertmanager.md +++ b/docs/services/alertmanager.md @@ -11,6 +11,10 @@ The notification service is used to push events to [Alertmanager](https://github * `basicAuth` - optional, server auth * `bearerToken` - optional, server auth * `timeout` - optional, the timeout in seconds used when sending alerts, default is "3 seconds" +* `maxIdleConns` - optional, maximum number of idle (keep-alive) connections across all hosts. +* `maxIdleConnsPerHost` - optional, maximum number of idle (keep-alive) connections per host. +* `maxConnsPerHost` - optional, maximum total connections per host. +* `idleConnTimeout` - optional, maximum amount of time an idle (keep-alive) connection will remain open before closing. `basicAuth` or `bearerToken` is used for authentication, you can choose one. If the two are set at the same time, `basicAuth` takes precedence over `bearerToken`. diff --git a/docs/services/github.md b/docs/services/github.md index faca3db4..ea2fcea8 100644 --- a/docs/services/github.md +++ b/docs/services/github.md @@ -8,6 +8,10 @@ The GitHub notification service changes commit status using [GitHub Apps](https: - `installationID` - the app installation id - `privateKey` - the app private key - `enterpriseBaseURL` - optional URL, e.g. https://git.example.com/api/v3 +- `maxIdleConns` - optional, maximum number of idle (keep-alive) connections across all hosts. +- `maxIdleConnsPerHost` - optional, maximum number of idle (keep-alive) connections per host. +- `maxConnsPerHost` - optional, maximum total connections per host. +- `idleConnTimeout` - optional, maximum amount of time an idle (keep-alive) connection will remain open before closing. > ⚠️ _NOTE:_ Specifying `/api/v3` in the `enterpriseBaseURL` is required until [argoproj/notifications-engine#205](https://github.com/argoproj/notifications-engine/issues/205) is resolved. diff --git a/docs/services/grafana.md b/docs/services/grafana.md index 8f8f264c..310824dc 100644 --- a/docs/services/grafana.md +++ b/docs/services/grafana.md @@ -9,6 +9,10 @@ Available parameters : * `apiURL` - the server url, e.g. https://grafana.example.com * `apiKey` - the API key for the serviceaccount * `insecureSkipVerify` - optional bool, true or false +* `maxIdleConns` - optional, maximum number of idle (keep-alive) connections across all hosts. +* `maxIdleConnsPerHost` - optional, maximum number of idle (keep-alive) connections per host. +* `maxConnsPerHost` - optional, maximum total connections per host. +* `idleConnTimeout` - optional, maximum amount of time an idle (keep-alive) connection will remain open before closing. 1. Login to your Grafana instance as `admin` 2. On the left menu, go to Configuration / API Keys diff --git a/docs/services/mattermost.md b/docs/services/mattermost.md index d1f187e9..3d64c16e 100644 --- a/docs/services/mattermost.md +++ b/docs/services/mattermost.md @@ -5,6 +5,10 @@ * `apiURL` - the server url, e.g. https://mattermost.example.com * `token` - the bot token * `insecureSkipVerify` - optional bool, true or false +* `maxIdleConns` - optional, maximum number of idle (keep-alive) connections across all hosts. +* `maxIdleConnsPerHost` - optional, maximum number of idle (keep-alive) connections per host. +* `maxConnsPerHost` - optional, maximum total connections per host. +* `idleConnTimeout` - optional, maximum amount of time an idle (keep-alive) connection will remain open before closing, e.g. '90s'. ## Configuration diff --git a/docs/services/newrelic.md b/docs/services/newrelic.md index b0c7e340..57c4f28d 100644 --- a/docs/services/newrelic.md +++ b/docs/services/newrelic.md @@ -4,6 +4,10 @@ * `apiURL` - the api server url, e.g. https://api.newrelic.com * `apiKey` - a [NewRelic ApiKey](https://docs.newrelic.com/docs/apis/rest-api-v2/get-started/introduction-new-relic-rest-api-v2/#api_key) +* `maxIdleConns` - optional, maximum number of idle (keep-alive) connections across all hosts. +* `maxIdleConnsPerHost` - optional, maximum number of idle (keep-alive) connections per host. +* `maxConnsPerHost` - optional, maximum total connections per host. +* `idleConnTimeout` - optional, maximum amount of time an idle (keep-alive) connection will remain open before closing, e.g. '90s'. ## Configuration diff --git a/docs/services/slack.md b/docs/services/slack.md index e665d7a4..e44113b7 100644 --- a/docs/services/slack.md +++ b/docs/services/slack.md @@ -16,6 +16,11 @@ The Slack notification service configuration includes following settings: | `token` | **True** | `string` | The app's OAuth access token. | `xoxb-1234567890-1234567890123-5n38u5ed63fgzqlvuyxvxcx6` | | `username` | False | `string` | The app username. | `argocd` | | `disableUnfurl` | False | `bool` | Disable slack unfurling links in messages | `true` | +| `maxIdleConns` | False | `int` | Maximum number of idle (keep-alive) connections across all hosts. | — | +| `maxIdleConnsPerHost` | False | `int` | Maximum number of idle (keep-alive) connections per host. | — | +| `maxConnsPerHost` | False | `int` | Maximum total connections per host. | — | +| `idleConnTimeout` | False | `string` | Maximum amount of time an idle (keep-alive) connection will remain open before closing (e.g., `90s`). | — | + ## Configuration diff --git a/docs/services/webhook.md b/docs/services/webhook.md index 4b8ca38a..8b8b1de1 100644 --- a/docs/services/webhook.md +++ b/docs/services/webhook.md @@ -14,6 +14,10 @@ The Webhook notification service configuration includes following settings: - `retryWaitMin` - Optional, the minimum wait time between retries. Default value: 1s. - `retryWaitMax` - Optional, the maximum wait time between retries. Default value: 5s. - `retryMax` - Optional, the maximum number of retries. Default value: 3. +- `maxIdleConns` - optional, maximum number of idle (keep-alive) connections across all hosts. +- `maxIdleConnsPerHost` - optional, maximum number of idle (keep-alive) connections per host. +- `maxConnsPerHost` - optional, maximum total connections per host. +- `idleConnTimeout` - optional, maximum amount of time an idle (keep-alive) connection will remain open before closing, e.g. '90s'. ## Retry Behavior diff --git a/pkg/services/alertmanager.go b/pkg/services/alertmanager.go index 28967fde..f80c2a12 100644 --- a/pkg/services/alertmanager.go +++ b/pkg/services/alertmanager.go @@ -38,8 +38,9 @@ type AlertmanagerOptions struct { APIPath string `json:"apiPath"` BasicAuth *BasicAuth `json:"basicAuth"` BearerToken string `json:"bearerToken"` - InsecureSkipVerify bool `json:"insecureSkipVerify"` Timeout int `json:"timeout"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } // NewAlertmanagerService new service @@ -204,14 +205,13 @@ func (s alertmanagerService) Send(notification Notification, dest Destination) e return nil } -func (s alertmanagerService) sendOneTarget(ctx context.Context, target string, rawBody []byte) error { +func (s alertmanagerService) sendOneTarget(ctx context.Context, target string, rawBody []byte) (err error) { rawURL := fmt.Sprintf("%v://%v%v", s.opts.Scheme, target, s.opts.APIPath) - transport := httputil.NewTransport(rawURL, s.opts.InsecureSkipVerify) - client := &http.Client{ - Transport: httputil.NewLoggingRoundTripper(transport, s.entry), + client, err := httputil.NewServiceHTTPClient(s.opts.TransportOptions, s.opts.InsecureSkipVerify, rawURL, "alertmanager") + if err != nil { + return err } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, rawURL, bytes.NewReader(rawBody)) if err != nil { return err diff --git a/pkg/services/github.go b/pkg/services/github.go index 5971db5e..cdbef5df 100644 --- a/pkg/services/github.go +++ b/pkg/services/github.go @@ -14,7 +14,6 @@ import ( "github.com/bradleyfalzon/ghinstallation/v2" giturls "github.com/chainguard-dev/git-urls" "github.com/google/go-github/v69/github" - log "github.com/sirupsen/logrus" "github.com/spf13/cast" httputil "github.com/argoproj/notifications-engine/pkg/util/http" @@ -26,10 +25,12 @@ var ( ) type GitHubOptions struct { - AppID interface{} `json:"appID"` - InstallationID interface{} `json:"installationID"` - PrivateKey string `json:"privateKey"` - EnterpriseBaseURL string `json:"enterpriseBaseURL"` + AppID interface{} `json:"appID"` + InstallationID interface{} `json:"installationID"` + PrivateKey string `json:"privateKey"` + EnterpriseBaseURL string `json:"enterpriseBaseURL"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type GitHubNotification struct { @@ -395,26 +396,28 @@ func NewGitHubService(opts GitHubOptions) (*gitHubService, error) { return nil, err } - tr := httputil.NewLoggingRoundTripper( - httputil.NewTransport(url, false), log.WithField("service", "github")) - itr, err := ghinstallation.New(tr, appID, installationID, []byte(opts.PrivateKey)) + client, err := httputil.NewServiceHTTPClient(opts.TransportOptions, opts.InsecureSkipVerify, url, "github") + if err != nil { + return nil, err + } + itr, err := ghinstallation.New(client.Transport, appID, installationID, []byte(opts.PrivateKey)) if err != nil { return nil, err } - var client *github.Client + var ghclient *github.Client if opts.EnterpriseBaseURL == "" { - client = github.NewClient(&http.Client{Transport: itr}) + ghclient = github.NewClient(&http.Client{Transport: itr}) } else { itr.BaseURL = opts.EnterpriseBaseURL - client, err = github.NewClient(&http.Client{Transport: itr}).WithEnterpriseURLs(opts.EnterpriseBaseURL, "") + ghclient, err = github.NewClient(&http.Client{Transport: itr}).WithEnterpriseURLs(opts.EnterpriseBaseURL, "") if err != nil { return nil, err } } return &gitHubService{ - client: &githubClientAdapter{client: client}, + client: &githubClientAdapter{client: ghclient}, }, nil } diff --git a/pkg/services/googlechat.go b/pkg/services/googlechat.go index 9322a170..64f8485a 100644 --- a/pkg/services/googlechat.go +++ b/pkg/services/googlechat.go @@ -11,7 +11,6 @@ import ( "github.com/google/uuid" - log "github.com/sirupsen/logrus" "sigs.k8s.io/yaml" "google.golang.org/api/chat/v1" @@ -80,7 +79,9 @@ func (n *GoogleChatNotification) GetTemplater(name string, f texttemplate.FuncMa } type GoogleChatOptions struct { - WebhookUrls map[string]string `json:"webhooks"` + WebhookUrls map[string]string `json:"webhooks"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type googleChatService struct { @@ -101,14 +102,15 @@ type webhookError struct { Status string `json:"status"` } -func (s googleChatService) getClient(recipient string) (*googlechatClient, error) { +func (s googleChatService) getClient(recipient string) (googlechatclient *googlechatClient, err error) { webhookUrl, ok := s.opts.WebhookUrls[recipient] if !ok { return nil, fmt.Errorf("no Google chat webhook configured for recipient %s", recipient) } - transport := httputil.NewTransport(webhookUrl, false) - client := &http.Client{ - Transport: httputil.NewLoggingRoundTripper(transport, log.WithField("service", "googlechat")), + + client, err := httputil.NewServiceHTTPClient(s.opts.TransportOptions, s.opts.InsecureSkipVerify, webhookUrl, "googlechat") + if err != nil { + return nil, err } return &googlechatClient{httpClient: client, url: webhookUrl}, nil } diff --git a/pkg/services/grafana.go b/pkg/services/grafana.go index bfd37d14..460f6a23 100644 --- a/pkg/services/grafana.go +++ b/pkg/services/grafana.go @@ -20,6 +20,7 @@ type GrafanaOptions struct { ApiUrl string `json:"apiUrl"` ApiKey string `json:"apiKey"` InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type grafanaService struct { @@ -37,7 +38,7 @@ type GrafanaAnnotation struct { Text string `json:"text"` } -func (s *grafanaService) Send(notification Notification, dest Destination) error { +func (s *grafanaService) Send(notification Notification, dest Destination) (err error) { ga := GrafanaAnnotation{ Time: time.Now().Unix() * 1000, // unix ts in ms IsRegion: false, @@ -49,9 +50,9 @@ func (s *grafanaService) Send(notification Notification, dest Destination) error log.Warnf("Message is an empty string or not provided in the notifications template") } - client := &http.Client{ - Transport: httputil.NewLoggingRoundTripper( - httputil.NewTransport(s.opts.ApiUrl, s.opts.InsecureSkipVerify), log.WithField("service", "grafana")), + client, err := httputil.NewServiceHTTPClient(s.opts.TransportOptions, s.opts.InsecureSkipVerify, s.opts.ApiUrl, "grafana") + if err != nil { + return err } jsonValue, _ := json.Marshal(ga) diff --git a/pkg/services/grafana_test.go b/pkg/services/grafana_test.go index 10e96393..9f513c8a 100644 --- a/pkg/services/grafana_test.go +++ b/pkg/services/grafana_test.go @@ -46,8 +46,9 @@ func TestGrafana_UnSuccessfullySendsNotification(t *testing.T) { defer server.Close() service := NewGrafanaService(GrafanaOptions{ - ApiUrl: server.URL, - ApiKey: "something-secret-but-not-relevant-in-this-test", + ApiUrl: server.URL, + ApiKey: "something-secret-but-not-relevant-in-this-test", + InsecureSkipVerify: true, }) err := service.Send( diff --git a/pkg/services/mattermost.go b/pkg/services/mattermost.go index 8ad19c0e..d41469b8 100644 --- a/pkg/services/mattermost.go +++ b/pkg/services/mattermost.go @@ -8,8 +8,6 @@ import ( "net/http" texttemplate "text/template" - log "github.com/sirupsen/logrus" - httputil "github.com/argoproj/notifications-engine/pkg/util/http" ) @@ -40,6 +38,7 @@ type MattermostOptions struct { ApiURL string `json:"apiURL"` Token string `json:"token"` InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type mattermostService struct { @@ -50,10 +49,10 @@ func NewMattermostService(opts MattermostOptions) NotificationService { return &mattermostService{opts: opts} } -func (m *mattermostService) Send(notification Notification, dest Destination) error { - transport := httputil.NewTransport(m.opts.ApiURL, m.opts.InsecureSkipVerify) - client := &http.Client{ - Transport: httputil.NewLoggingRoundTripper(transport, log.WithField("service", "mattermost")), +func (m *mattermostService) Send(notification Notification, dest Destination) (err error) { + client, err := httputil.NewServiceHTTPClient(m.opts.TransportOptions, m.opts.InsecureSkipVerify, m.opts.ApiURL, "mattermost") + if err != nil { + return err } attachments := []interface{}{} diff --git a/pkg/services/newrelic.go b/pkg/services/newrelic.go index f0717be1..016414b2 100644 --- a/pkg/services/newrelic.go +++ b/pkg/services/newrelic.go @@ -15,8 +15,10 @@ import ( ) type NewrelicOptions struct { - ApiKey string `json:"apiKey"` - ApiURL string `json:"apiURL"` + ApiKey string `json:"apiKey"` + ApiURL string `json:"apiURL"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type NewrelicNotification struct { @@ -110,7 +112,7 @@ type newrelicDeploymentMarkerRequest struct { Deployment NewrelicNotification `json:"deployment"` } -func (s newrelicService) Send(notification Notification, dest Destination) error { +func (s newrelicService) Send(notification Notification, dest Destination) (err error) { if s.opts.ApiKey == "" { return ErrMissingApiKey } @@ -132,9 +134,9 @@ func (s newrelicService) Send(notification Notification, dest Destination) error }, } - client := &http.Client{ - Transport: httputil.NewLoggingRoundTripper( - httputil.NewTransport(s.opts.ApiURL, false), log.WithField("service", dest.Service)), + client, err := httputil.NewServiceHTTPClient(s.opts.TransportOptions, s.opts.InsecureSkipVerify, s.opts.ApiURL, "newrelic") + if err != nil { + return err } jsonValue, err := json.Marshal(deploymentMarker) diff --git a/pkg/services/opsgenie.go b/pkg/services/opsgenie.go index 5c238581..453277cd 100644 --- a/pkg/services/opsgenie.go +++ b/pkg/services/opsgenie.go @@ -4,19 +4,19 @@ import ( "bytes" "context" "fmt" - "net/http" texttemplate "text/template" "github.com/opsgenie/opsgenie-go-sdk-v2/alert" "github.com/opsgenie/opsgenie-go-sdk-v2/client" - log "github.com/sirupsen/logrus" httputil "github.com/argoproj/notifications-engine/pkg/util/http" ) type OpsgenieOptions struct { - ApiUrl string `json:"apiUrl"` - ApiKeys map[string]string `json:"apiKeys"` + ApiUrl string `json:"apiUrl"` + ApiKeys map[string]string `json:"apiKeys"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type OpsgenieNotification struct { @@ -244,18 +244,20 @@ func NewOpsgenieService(opts OpsgenieOptions) NotificationService { return &opsgenieService{opts: opts} } -func (s *opsgenieService) Send(notification Notification, dest Destination) error { +func (s *opsgenieService) Send(notification Notification, dest Destination) (err error) { apiKey, ok := s.opts.ApiKeys[dest.Recipient] if !ok { return fmt.Errorf("no API key configured for recipient %s", dest.Recipient) } + + opsclient, err := httputil.NewServiceHTTPClient(s.opts.TransportOptions, s.opts.InsecureSkipVerify, s.opts.ApiUrl, "opsgenie") + if err != nil { + return err + } alertClient, _ := alert.NewClient(&client.Config{ ApiKey: apiKey, OpsGenieAPIURL: client.ApiUrl(s.opts.ApiUrl), - HttpClient: &http.Client{ - Transport: httputil.NewLoggingRoundTripper( - httputil.NewTransport(s.opts.ApiUrl, false), log.WithField("service", "opsgenie")), - }, + HttpClient: opsclient, }) var description, alias, note, entity, user string @@ -308,7 +310,7 @@ func (s *opsgenieService) Send(notification Notification, dest Destination) erro } } - _, err := alertClient.Create(context.TODO(), &alert.CreateAlertRequest{ + _, err = alertClient.Create(context.TODO(), &alert.CreateAlertRequest{ Message: notification.Message, Description: description, Priority: priority, diff --git a/pkg/services/slack.go b/pkg/services/slack.go index ce0a2e23..ac92ede6 100644 --- a/pkg/services/slack.go +++ b/pkg/services/slack.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "net/http" "net/url" "regexp" texttemplate "text/template" @@ -101,9 +100,10 @@ type SlackOptions struct { Token string `json:"token"` SigningSecret string `json:"signingSecret"` Channels []string `json:"channels"` - InsecureSkipVerify bool `json:"insecureSkipVerify"` ApiURL string `json:"apiURL"` DisableUnfurl bool `json:"disableUnfurl"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type slackService struct { @@ -174,8 +174,12 @@ func (s *slackService) Send(notification Notification, dest Destination) error { if err != nil { return err } + client, err := newSlackClient(s.opts) + if err != nil { + return err + } return slackutil.NewThreadedClient( - newSlackClient(s.opts), + client, slackState, ).SendMessage( context.TODO(), @@ -192,16 +196,17 @@ func (s *slackService) GetSigningSecret() string { return s.opts.SigningSecret } -func newSlackClient(opts SlackOptions) *slack.Client { +func newSlackClient(opts SlackOptions) (slackclient *slack.Client, err error) { apiURL := slack.APIURL if opts.ApiURL != "" { apiURL = opts.ApiURL } - transport := httputil.NewTransport(apiURL, opts.InsecureSkipVerify) - client := &http.Client{ - Transport: httputil.NewLoggingRoundTripper(transport, log.WithField("service", "slack")), + + client, err := httputil.NewServiceHTTPClient(opts.TransportOptions, opts.InsecureSkipVerify, apiURL, "slack") + if err != nil { + return nil, err } - return slack.New(opts.Token, slack.OptionHTTPClient(client), slack.OptionAPIURL(apiURL)) + return slack.New(opts.Token, slack.OptionHTTPClient(client), slack.OptionAPIURL(apiURL)), nil } func isValidIconURL(iconURL string) bool { diff --git a/pkg/services/teams.go b/pkg/services/teams.go index 009a4b35..8f0ac03e 100644 --- a/pkg/services/teams.go +++ b/pkg/services/teams.go @@ -5,11 +5,8 @@ import ( "encoding/json" "fmt" "io" - "net/http" texttemplate "text/template" - log "github.com/sirupsen/logrus" - httputil "github.com/argoproj/notifications-engine/pkg/util/http" ) @@ -139,7 +136,9 @@ func (n *TeamsNotification) GetTemplater(name string, f texttemplate.FuncMap) (T } type TeamsOptions struct { - RecipientUrls map[string]string `json:"recipientUrls"` + RecipientUrls map[string]string `json:"recipientUrls"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type teamsService struct { @@ -150,14 +149,15 @@ func NewTeamsService(opts TeamsOptions) NotificationService { return &teamsService{opts: opts} } -func (s teamsService) Send(notification Notification, dest Destination) error { +func (s teamsService) Send(notification Notification, dest Destination) (err error) { webhookUrl, ok := s.opts.RecipientUrls[dest.Recipient] if !ok { return fmt.Errorf("no teams webhook configured for recipient %s", dest.Recipient) } - transport := httputil.NewTransport(webhookUrl, false) - client := &http.Client{ - Transport: httputil.NewLoggingRoundTripper(transport, log.WithField("service", "teams")), + + client, err := httputil.NewServiceHTTPClient(s.opts.TransportOptions, s.opts.InsecureSkipVerify, webhookUrl, "teams") + if err != nil { + return err } message, err := teamsNotificationToReader(notification) diff --git a/pkg/services/webex.go b/pkg/services/webex.go index ed6ed674..7878f5d9 100644 --- a/pkg/services/webex.go +++ b/pkg/services/webex.go @@ -9,14 +9,14 @@ import ( "regexp" "strings" - log "github.com/sirupsen/logrus" - httputil "github.com/argoproj/notifications-engine/pkg/util/http" ) type WebexOptions struct { - Token string `json:"token"` - ApiURL string `json:"apiURL"` + Token string `json:"token"` + ApiURL string `json:"apiURL"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } type webexService struct { @@ -40,12 +40,12 @@ func NewWebexService(opts WebexOptions) NotificationService { var validEmail = regexp.MustCompile(`^\S+@\S+\.\S+$`) -func (w webexService) Send(notification Notification, dest Destination) error { +func (w webexService) Send(notification Notification, dest Destination) (err error) { requestURL := fmt.Sprintf("%s/v1/messages", w.opts.ApiURL) - client := &http.Client{ - Transport: httputil.NewLoggingRoundTripper( - httputil.NewTransport(requestURL, false), log.WithField("service", dest.Service)), + client, err := httputil.NewServiceHTTPClient(w.opts.TransportOptions, w.opts.InsecureSkipVerify, requestURL, "webex") + if err != nil { + return err } message := webexMessage{ diff --git a/pkg/services/webhook.go b/pkg/services/webhook.go index a2a257b0..435c21f7 100644 --- a/pkg/services/webhook.go +++ b/pkg/services/webhook.go @@ -11,8 +11,6 @@ import ( "github.com/hashicorp/go-retryablehttp" - log "github.com/sirupsen/logrus" - httputil "github.com/argoproj/notifications-engine/pkg/util/http" "github.com/argoproj/notifications-engine/pkg/util/text" ) @@ -83,10 +81,11 @@ type WebhookOptions struct { URL string `json:"url"` Headers []Header `json:"headers"` BasicAuth *BasicAuth `json:"basicAuth"` - InsecureSkipVerify bool `json:"insecureSkipVerify"` RetryWaitMin time.Duration `json:"retryWaitMin"` RetryWaitMax time.Duration `json:"retryWaitMax"` RetryMax int `json:"retryMax"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + httputil.TransportOptions } func NewWebhookService(opts WebhookOptions) NotificationService { @@ -173,14 +172,13 @@ func (r *request) execute(service *webhookService) (*http.Response, error) { return nil, err } - transport := httputil.NewLoggingRoundTripper( - httputil.NewTransport(r.url, service.opts.InsecureSkipVerify), - log.WithField("service", r.destService)) + whclient, err := httputil.NewServiceHTTPClient(service.opts.TransportOptions, service.opts.InsecureSkipVerify, service.opts.URL, r.destService) + if err != nil { + return nil, err + } client := retryablehttp.NewClient() - client.HTTPClient = &http.Client{ - Transport: transport, - } + client.HTTPClient = whclient client.RetryWaitMin = service.opts.RetryWaitMin client.RetryWaitMax = service.opts.RetryWaitMax client.RetryMax = service.opts.RetryMax diff --git a/pkg/util/http/transport.go b/pkg/util/http/transport.go index cad9d1e2..27f55626 100644 --- a/pkg/util/http/transport.go +++ b/pkg/util/http/transport.go @@ -5,18 +5,42 @@ import ( "crypto/x509" "net/http" "net/url" + "time" + + log "github.com/sirupsen/logrus" ) +type TransportOptions struct { + MaxIdleConns int `json:"maxIdleConns"` + MaxIdleConnsPerHost int `json:"maxIdleConnsPerHost"` + MaxConnsPerHost int `json:"maxConnsPerHost"` + IdleConnTimeout string `json:"idleConnTimeout"` +} + var certResolver func(serverName string) ([]string, error) func SetCertResolver(resolver func(serverName string) ([]string, error)) { certResolver = resolver } -func NewTransport(rawURL string, insecureSkipVerify bool) *http.Transport { +func NewTransport(tp TransportOptions, rawURL string, insecureSkipVerify bool) *http.Transport { + // Parse IdleConnTimeout from string if provided + var idleConnTimeout time.Duration + if tp.IdleConnTimeout != "" { + dur, err := time.ParseDuration(tp.IdleConnTimeout) + if err == nil { + idleConnTimeout = dur + } + } + transport := &http.Transport{ - Proxy: http.ProxyFromEnvironment, + Proxy: http.ProxyFromEnvironment, + MaxIdleConns: tp.MaxIdleConns, + MaxIdleConnsPerHost: tp.MaxIdleConnsPerHost, + MaxConnsPerHost: tp.MaxConnsPerHost, + IdleConnTimeout: idleConnTimeout, } + if insecureSkipVerify { transport.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, @@ -35,9 +59,17 @@ func NewTransport(rawURL string, insecureSkipVerify bool) *http.Transport { } } } + return transport } +func NewServiceHTTPClient(tp TransportOptions, insecureSkipVerify bool, apiURL string, serviceName string) (*http.Client, error) { + transport := NewTransport(tp, apiURL, insecureSkipVerify) + return &http.Client{ + Transport: NewLoggingRoundTripper(transport, log.WithField("service", serviceName)), + }, nil +} + func getCertPoolFromPEMData(pemData []string) *x509.CertPool { certPool := x509.NewCertPool() for _, pem := range pemData {