Skip to content

Commit

Permalink
Add alerting http proxy option
Browse files Browse the repository at this point in the history
Signed-off-by: Léopold Jacquot <[email protected]>
  • Loading branch information
L3o-pold committed Apr 8, 2021
1 parent 4b084cf commit f639ff9
Show file tree
Hide file tree
Showing 22 changed files with 109 additions and 26 deletions.
3 changes: 3 additions & 0 deletions artifacts/flagger/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,9 @@ spec:
address:
description: Hook URL address of this provider
type: string
proxy:
description: Http/s proxy of this provider
type: string
secretRef:
description: Kubernetes secret reference containing the provider address
type: object
Expand Down
2 changes: 2 additions & 0 deletions charts/flagger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,11 @@ Parameter | Description | Default
`configTracking.enabled` | If `true`, flagger will track changes in Secrets and ConfigMaps referenced in the target deployment | `true`
`eventWebhook` | If set, Flagger will publish events to the given webhook | None
`slack.url` | Slack incoming webhook | None
`slack.proxyUrl` | Slack proxy url | None
`slack.channel` | Slack channel | None
`slack.user` | Slack username | `flagger`
`msteams.url` | Microsoft Teams incoming webhook | None
`msteams.proxyUrl` | Microsoft Teams proxy url | None
`podMonitor.enabled` | If `true`, create a PodMonitor for [monitoring the metrics](https://docs.flagger.app/usage/monitoring#metrics) | `false`
`podMonitor.namespace` | Namespace where the PodMonitor is created | the same namespace
`podMonitor.interval` | Interval at which metrics should be scraped | `15s`
Expand Down
3 changes: 3 additions & 0 deletions charts/flagger/crds/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,9 @@ spec:
address:
description: Hook URL address of this provider
type: string
proxy:
description: Http/s proxy of this provider
type: string
secretRef:
description: Kubernetes secret reference containing the provider address
type: object
Expand Down
6 changes: 6 additions & 0 deletions charts/flagger/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ spec:
{{- if .Values.slack.url }}
- -slack-url={{ .Values.slack.url }}
{{- end }}
{{- if .Values.slack.proxyUrl }}
- -slack-proxy-url={{ .Values.slack.proxyUrl }}
{{- end }}
{{- if .Values.slack.user }}
- -slack-user={{ .Values.slack.user }}
{{- end }}
Expand All @@ -99,6 +102,9 @@ spec:
{{- if .Values.msteams.url }}
- -msteams-url={{ .Values.msteams.url }}
{{- end }}
{{- if .Values.msteams.proxyUrl }}
- -msteams-proxy-url={{ .Values.msteams.proxyUrl }}
{{- end }}
{{- if .Values.leaderElection.enabled }}
- -enable-leader-election=true
- -leader-election-namespace={{ .Release.Namespace }}
Expand Down
11 changes: 11 additions & 0 deletions charts/flagger/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ slack:
channel:
# incoming webhook https://api.slack.com/incoming-webhooks
url:
proxy:

msteams:
# MS Teams incoming webhook URL
Expand All @@ -72,11 +73,21 @@ podMonitor:
# secretKeyRef:
# name: slack
# key: url
#- name: SLACK_PROXY_URL
# valueFrom:
# secretKeyRef:
# name: slack
# key: proxy-url
#- name: MSTEAMS_URL
# valueFrom:
# secretKeyRef:
# name: msteams
# key: url
#- name: MSTEAMS_PROXY_URL
# valueFrom:
# secretKeyRef:
# name: msteams
# key: proxy-url
#- name: EVENT_WEBHOOK_URL
# valueFrom:
# secretKeyRef:
Expand Down
8 changes: 7 additions & 1 deletion cmd/flagger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ var (
logLevel string
port string
msteamsURL string
msteamsProxyURL string
includeLabelPrefix string
slackURL string
slackProxyURL string
slackUser string
slackChannel string
eventWebhook string
Expand Down Expand Up @@ -93,10 +95,12 @@ func init() {
flag.StringVar(&logLevel, "log-level", "debug", "Log level can be: debug, info, warning, error.")
flag.StringVar(&port, "port", "8080", "Port to listen on.")
flag.StringVar(&slackURL, "slack-url", "", "Slack hook URL.")
flag.StringVar(&slackProxyURL, "slack-proxy-url", "", "Slack proxy URL.")
flag.StringVar(&slackUser, "slack-user", "flagger", "Slack user name.")
flag.StringVar(&slackChannel, "slack-channel", "", "Slack channel.")
flag.StringVar(&eventWebhook, "event-webhook", "", "Webhook for publishing flagger events")
flag.StringVar(&msteamsURL, "msteams-url", "", "MS Teams incoming webhook URL.")
flag.StringVar(&msteamsProxyURL, "msteams-proxy-url", "", "MS Teams proxy URL.")
flag.StringVar(&includeLabelPrefix, "include-label-prefix", "", "List of prefixes of labels that are copied when creating primary deployments or daemonsets. Use * to include all.")
flag.IntVar(&threadiness, "threadiness", 2, "Worker concurrency.")
flag.BoolVar(&zapReplaceGlobals, "zap-replace-globals", false, "Whether to change the logging level of the global zap logger.")
Expand Down Expand Up @@ -349,11 +353,13 @@ func startLeaderElection(ctx context.Context, run func(), ns string, kubeClient
func initNotifier(logger *zap.SugaredLogger) (client notifier.Interface) {
provider := "slack"
notifierURL := fromEnv("SLACK_URL", slackURL)
notifierProxyURL := fromEnv("SLACK_PROXY_URL", slackProxyURL)
if msteamsURL != "" || os.Getenv("MSTEAMS_URL") != "" {
provider = "msteams"
notifierURL = fromEnv("MSTEAMS_URL", msteamsURL)
notifierProxyURL = fromEnv("MSTEAMS_PROXY_URL", msteamsProxyURL)
}
notifierFactory := notifier.NewFactory(notifierURL, slackUser, slackChannel)
notifierFactory := notifier.NewFactory(notifierURL, notifierProxyURL, slackUser, slackChannel)

var err error
client, err = notifierFactory.Notifier(provider)
Expand Down
6 changes: 5 additions & 1 deletion docs/gitbook/usage/alerting.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Once the webhook has been generated. Flagger can be configured to send Slack not
```bash
helm upgrade -i flagger flagger/flagger \
--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
--set slack.proxy-url=my-http-proxy.com \ # optional http/s proxy
--set slack.channel=general \
--set slack.user=flagger
```
Expand All @@ -41,7 +42,8 @@ Flagger can be configured to send notifications to Microsoft Teams:

```bash
helm upgrade -i flagger flagger/flagger \
--set msteams.url=https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK
--set msteams.url=https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK \
--set msteams.proxy-url=my-http-proxy.com # optional http/s proxy
```

Similar to Slack, Flagger alerts on canary analysis events:
Expand Down Expand Up @@ -71,6 +73,8 @@ spec:
username: flagger
# webhook address (ignored if secretRef is specified)
address: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
# optional http/s proxy
proxy: http://my-http-proxy.com
# secret containing the webhook address (optional)
secretRef:
name: on-call-url
Expand Down
3 changes: 3 additions & 0 deletions kustomize/base/flagger/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,9 @@ spec:
address:
description: Hook URL address of this provider
type: string
proxy:
description: Http/s proxy of this provider
type: string
secretRef:
description: Kubernetes secret reference containing the provider address
type: object
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/flagger/v1beta1/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ type AlertProviderSpec struct {
// +optional
Address string `json:"address,omitempty"`

// HTTP/S address of the proxy
// +optional
Proxy string `json:"proxy,omitempty"`

// Secret reference containing the provider webhook URL
// +optional
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
Expand Down
6 changes: 5 additions & 1 deletion pkg/controller/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,13 @@ func (c *Controller) alert(canary *flaggerv1.Canary, message string, metadata bo
if provider.Spec.Channel != "" {
channel = provider.Spec.Channel
}
proxy := ""
if provider.Spec.Proxy != "" {
proxy = provider.Spec.Proxy
}

// create notifier based on provider type
f := notifier.NewFactory(url, username, channel)
f := notifier.NewFactory(url, proxy, username, channel)
n, err := f.Notifier(provider.Spec.Type)
if err != nil {
c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Expand Down
5 changes: 4 additions & 1 deletion pkg/controller/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ func callWebhook(webhook string, payload interface{}, timeout string) error {
ctx, cancel := context.WithTimeout(req.Context(), t)
defer cancel()

r, err := http.DefaultClient.Do(req.WithContext(ctx))
httpClient := http.DefaultClient
httpClient.Transport = http.DefaultTransport

r, err := httpClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
Expand Down
28 changes: 26 additions & 2 deletions pkg/notifier/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,35 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"runtime"
"time"
)

func postMessage(address string, payload interface{}) error {
func postMessage(address string, proxy string, payload interface{}) error {
httpClient := http.DefaultClient

if proxy != "" {
proxyURL, err := url.Parse(proxy)
if err != nil {
return fmt.Errorf("unable to parse proxy URL '%s', error: %w", proxy, err)
}
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,
}
}

data, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("marshalling notification payload failed: %w", err)
Expand All @@ -43,7 +67,7 @@ func postMessage(address string, payload interface{}) error {
ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second)
defer cancel()

res, err := http.DefaultClient.Do(req.WithContext(ctx))
res, err := httpClient.Do(req.WithContext(ctx))
if err != nil {
return fmt.Errorf("sending notification failed: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/notifier/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ 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)
}
6 changes: 4 additions & 2 deletions pkg/notifier/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,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)
Expand All @@ -56,6 +57,7 @@ func NewDiscord(hookURL string, username string, channel string) (*Discord, erro
return &Discord{
Channel: channel,
URL: hookURL,
ProxyURL: proxyURL,
Username: username,
}, nil
}
Expand Down Expand Up @@ -88,7 +90,7 @@ func (s *Discord) Post(workload string, namespace string, message string, fields

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)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/notifier/discord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,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"))

Expand Down
12 changes: 7 additions & 5 deletions pkg/notifier/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import (

type Factory struct {
URL string
ProxyURL string
Username string
Channel string
}

func NewFactory(url string, username string, channel string) *Factory {
func NewFactory(url string, proxy string, username string, channel string) *Factory {
return &Factory{
URL: url,
ProxyURL: proxy,
Channel: channel,
Username: username,
}
Expand All @@ -43,13 +45,13 @@ func (f Factory) Notifier(provider string) (Interface, error) {
var err error
switch provider {
case "slack":
n, err = NewSlack(f.URL, f.Username, f.Channel)
n, err = NewSlack(f.URL, f.ProxyURL, f.Username, f.Channel)
case "discord":
n, err = NewDiscord(f.URL, f.Username, f.Channel)
n, err = NewDiscord(f.URL, f.ProxyURL, f.Username, f.Channel)
case "rocket":
n, err = NewRocket(f.URL, f.Username, f.Channel)
n, err = NewRocket(f.URL, f.ProxyURL, f.Username, f.Channel)
case "msteams":
n, err = NewMSTeams(f.URL)
n, err = NewMSTeams(f.URL, f.ProxyURL)
default:
err = fmt.Errorf("provider %s not supported", provider)
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/notifier/rocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,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)
Expand All @@ -47,6 +48,7 @@ func NewRocket(hookURL string, username string, channel string) (*Rocket, error)
return &Rocket{
Channel: channel,
URL: hookURL,
ProxyURL: proxyUrl,
Username: username,
}, nil
}
Expand Down Expand Up @@ -79,7 +81,7 @@ func (s *Rocket) Post(workload string, namespace string, message string, fields

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)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/notifier/rocket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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("podinfo", "test", "test", fields, "error")
Expand Down
Loading

0 comments on commit f639ff9

Please sign in to comment.