Implement http(s) proxy for alerts

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan 2020-10-17 14:21:20 +03:00
parent 2f14e4b74b
commit 1f5d108d1d
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF
14 changed files with 67 additions and 29 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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"))

View File

@ -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:

View File

@ -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)
}

View File

@ -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())

View File

@ -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)
}

View File

@ -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())

View File

@ -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)
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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())

View File

@ -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",