Implement http(s) proxy for alerts
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
parent
2f14e4b74b
commit
1f5d108d1d
|
|
@ -19,13 +19,37 @@ package notifier
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
)
|
||||
|
||||
func postMessage(address string, payload interface{}) error {
|
||||
func postMessage(address string, proxy string, payload interface{}) error {
|
||||
httpClient := retryablehttp.NewClient()
|
||||
|
||||
if proxy != "" {
|
||||
proxyURL, err := url.Parse(proxy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse proxy URL '%s', error: %w", proxy, err)
|
||||
}
|
||||
httpClient.HTTPClient.Transport = &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 15 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
|
||||
}
|
||||
}
|
||||
|
||||
httpClient.HTTPClient.Timeout = 15 * time.Second
|
||||
httpClient.RetryWaitMin = 2 * time.Second
|
||||
httpClient.RetryWaitMax = 30 * time.Second
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ func Test_postMessage(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
err := postMessage(ts.URL, map[string]string{"status": "success"})
|
||||
err := postMessage(ts.URL, "", map[string]string{"status": "success"})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,13 @@ import (
|
|||
// Discord holds the hook URL
|
||||
type Discord struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
Username string
|
||||
Channel string
|
||||
}
|
||||
|
||||
// NewDiscord validates the URL and returns a Discord object
|
||||
func NewDiscord(hookURL string, username string, channel string) (*Discord, error) {
|
||||
func NewDiscord(hookURL string, proxyURL string, username string, channel string) (*Discord, error) {
|
||||
webhook, err := url.ParseRequestURI(hookURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Discord hook URL %s", hookURL)
|
||||
|
|
@ -58,6 +59,7 @@ func NewDiscord(hookURL string, username string, channel string) (*Discord, erro
|
|||
return &Discord{
|
||||
Channel: channel,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
Username: username,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -97,7 +99,7 @@ func (s *Discord) Post(event recorder.Event) error {
|
|||
|
||||
payload.Attachments = []SlackAttachment{a}
|
||||
|
||||
err := postMessage(s.URL, payload)
|
||||
err := postMessage(s.URL, s.ProxyURL, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("postMessage failed: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func TestDiscord_Post(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
discord, err := NewDiscord(ts.URL, "test", "test")
|
||||
discord, err := NewDiscord(ts.URL, "", "test", "test")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, strings.HasSuffix(discord.URL, "/slack"))
|
||||
|
||||
|
|
|
|||
|
|
@ -24,14 +24,16 @@ import (
|
|||
|
||||
type Factory struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
Username string
|
||||
Channel string
|
||||
Token string
|
||||
}
|
||||
|
||||
func NewFactory(url string, username string, channel string, token string) *Factory {
|
||||
func NewFactory(url string, proxy string, username string, channel string, token string) *Factory {
|
||||
return &Factory{
|
||||
URL: url,
|
||||
ProxyURL: proxy,
|
||||
Channel: channel,
|
||||
Username: username,
|
||||
Token: token,
|
||||
|
|
@ -47,15 +49,15 @@ func (f Factory) Notifier(provider string) (Interface, error) {
|
|||
var err error
|
||||
switch provider {
|
||||
case v1beta1.GenericProvider:
|
||||
n, err = NewForwarder(f.URL)
|
||||
n, err = NewForwarder(f.URL, f.ProxyURL)
|
||||
case v1beta1.SlackProvider:
|
||||
n, err = NewSlack(f.URL, f.Username, f.Channel)
|
||||
n, err = NewSlack(f.URL, f.ProxyURL, f.Username, f.Channel)
|
||||
case v1beta1.DiscordProvider:
|
||||
n, err = NewDiscord(f.URL, f.Username, f.Channel)
|
||||
n, err = NewDiscord(f.URL, f.ProxyURL, f.Username, f.Channel)
|
||||
case v1beta1.RocketProvider:
|
||||
n, err = NewRocket(f.URL, f.Username, f.Channel)
|
||||
n, err = NewRocket(f.URL, f.ProxyURL, f.Username, f.Channel)
|
||||
case v1beta1.MSTeamsProvider:
|
||||
n, err = NewMSTeams(f.URL)
|
||||
n, err = NewMSTeams(f.URL, f.ProxyURL)
|
||||
case v1beta1.GitHubProvider:
|
||||
n, err = NewGitHub(f.URL, f.Token)
|
||||
case v1beta1.GitLabProvider:
|
||||
|
|
|
|||
|
|
@ -8,19 +8,23 @@ import (
|
|||
)
|
||||
|
||||
type Forwarder struct {
|
||||
URL string
|
||||
URL string
|
||||
ProxyURL string
|
||||
}
|
||||
|
||||
func NewForwarder(hookURL string) (*Forwarder, error) {
|
||||
func NewForwarder(hookURL string, proxyURL string) (*Forwarder, error) {
|
||||
if _, err := url.ParseRequestURI(hookURL); err != nil {
|
||||
return nil, fmt.Errorf("invalid Discord hook URL %s", hookURL)
|
||||
}
|
||||
|
||||
return &Forwarder{URL: hookURL}, nil
|
||||
return &Forwarder{
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *Forwarder) Post(event recorder.Event) error {
|
||||
err := postMessage(f.URL, event)
|
||||
err := postMessage(f.URL, f.ProxyURL, event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("postMessage failed: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func TestForwarder_Post(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
forwarder, err := NewForwarder(ts.URL)
|
||||
forwarder, err := NewForwarder(ts.URL, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = forwarder.Post(testEvent())
|
||||
|
|
|
|||
|
|
@ -27,12 +27,13 @@ import (
|
|||
// Rocket holds the hook URL
|
||||
type Rocket struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
Username string
|
||||
Channel string
|
||||
}
|
||||
|
||||
// NewRocket validates the Rocket URL and returns a Rocket object
|
||||
func NewRocket(hookURL string, username string, channel string) (*Rocket, error) {
|
||||
func NewRocket(hookURL string, proxyURL string, username string, channel string) (*Rocket, error) {
|
||||
_, err := url.ParseRequestURI(hookURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Rocket hook URL %s", hookURL)
|
||||
|
|
@ -49,6 +50,7 @@ func NewRocket(hookURL string, username string, channel string) (*Rocket, error)
|
|||
return &Rocket{
|
||||
Channel: channel,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
Username: username,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -85,7 +87,7 @@ func (s *Rocket) Post(event recorder.Event) error {
|
|||
|
||||
payload.Attachments = []SlackAttachment{a}
|
||||
|
||||
err := postMessage(s.URL, payload)
|
||||
err := postMessage(s.URL, s.ProxyURL, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("postMessage failed: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func TestRocket_Post(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
rocket, err := NewRocket(ts.URL, "test", "test")
|
||||
rocket, err := NewRocket(ts.URL, "", "test", "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = rocket.Post(testEvent())
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
// Slack holds the hook URL
|
||||
type Slack struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
Username string
|
||||
Channel string
|
||||
}
|
||||
|
|
@ -57,7 +58,7 @@ type SlackField struct {
|
|||
}
|
||||
|
||||
// NewSlack validates the Slack URL and returns a Slack object
|
||||
func NewSlack(hookURL string, username string, channel string) (*Slack, error) {
|
||||
func NewSlack(hookURL string, proxyURL string, username string, channel string) (*Slack, error) {
|
||||
_, err := url.ParseRequestURI(hookURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Slack hook URL %s", hookURL)
|
||||
|
|
@ -71,6 +72,7 @@ func NewSlack(hookURL string, username string, channel string) (*Slack, error) {
|
|||
Channel: channel,
|
||||
Username: username,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +111,7 @@ func (s *Slack) Post(event recorder.Event) error {
|
|||
|
||||
payload.Attachments = []SlackAttachment{a}
|
||||
|
||||
err := postMessage(s.URL, payload)
|
||||
err := postMessage(s.URL, s.ProxyURL, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("postMessage failed: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func TestSlack_Post(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
slack, err := NewSlack(ts.URL, "", "test")
|
||||
slack, err := NewSlack(ts.URL, "", "", "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = slack.Post(testEvent())
|
||||
|
|
@ -48,7 +48,7 @@ func TestSlack_Post(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSlack_PostUpdate(t *testing.T) {
|
||||
slack, err := NewSlack("http://localhost", "", "test")
|
||||
slack, err := NewSlack("http://localhost", "", "", "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
event := testEvent()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ import (
|
|||
|
||||
// MS Teams holds the incoming webhook URL
|
||||
type MSTeams struct {
|
||||
URL string
|
||||
URL string
|
||||
ProxyURL string
|
||||
}
|
||||
|
||||
// MSTeamsPayload holds the message card data
|
||||
|
|
@ -50,14 +51,15 @@ type MSTeamsField struct {
|
|||
}
|
||||
|
||||
// NewMSTeams validates the MS Teams URL and returns a MSTeams object
|
||||
func NewMSTeams(hookURL string) (*MSTeams, error) {
|
||||
func NewMSTeams(hookURL string, proxyURL string) (*MSTeams, error) {
|
||||
_, err := url.ParseRequestURI(hookURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid MS Teams webhook URL %s", hookURL)
|
||||
}
|
||||
|
||||
return &MSTeams{
|
||||
URL: hookURL,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +97,7 @@ func (s *MSTeams) Post(event recorder.Event) error {
|
|||
payload.ThemeColor = "FF0000"
|
||||
}
|
||||
|
||||
err := postMessage(s.URL, payload)
|
||||
err := postMessage(s.URL, s.ProxyURL, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("postMessage failed: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func TestTeams_Post(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
teams, err := NewMSTeams(ts.URL)
|
||||
teams, err := NewMSTeams(ts.URL, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = teams.Post(testEvent())
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
factory := notifier.NewFactory(webhook, provider.Spec.Username, provider.Spec.Channel, token)
|
||||
factory := notifier.NewFactory(webhook, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token)
|
||||
sender, err := factory.Notifier(provider.Spec.Type)
|
||||
if err != nil {
|
||||
s.logger.Error(err, "failed to initialise provider",
|
||||
|
|
|
|||
Loading…
Reference in New Issue