mirror of https://github.com/fluxcd/flagger.git
Add alerting http proxy option
Signed-off-by: Léopold Jacquot <leopold.jacquot@infomaniak.com>
This commit is contained in:
parent
e6c740d917
commit
b82fd5e5a5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ slack:
|
|||
channel:
|
||||
# incoming webhook https://api.slack.com/incoming-webhooks
|
||||
url:
|
||||
proxy:
|
||||
|
||||
msteams:
|
||||
# MS Teams incoming webhook URL
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.")
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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:
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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)).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
// Slack holds the hook URL
|
||||
type Slack struct {
|
||||
URL string
|
||||
ProxyURL string
|
||||
Username string
|
||||
Channel string
|
||||
}
|
||||
|
|
@ -55,7 +56,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)
|
||||
|
|
@ -72,6 +73,7 @@ func NewSlack(hookURL string, username string, channel string) (*Slack, error) {
|
|||
return &Slack{
|
||||
Channel: channel,
|
||||
URL: hookURL,
|
||||
ProxyURL: proxyURL,
|
||||
Username: username,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -104,7 +106,7 @@ func (s *Slack) 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func TestSlack_Post(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
slack, err := NewSlack(ts.URL, "test", "test")
|
||||
slack, err := NewSlack(ts.URL, "", "test", "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = slack.Post("podinfo", "test", "test", fields, "error")
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ import (
|
|||
|
||||
// MS Teams holds the incoming webhook URL
|
||||
type MSTeams struct {
|
||||
URL string
|
||||
URL string
|
||||
ProxyURL string
|
||||
}
|
||||
|
||||
// MSTeamsPayload holds the message card data
|
||||
|
|
@ -48,14 +49,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
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +86,7 @@ func (s *MSTeams) Post(workload string, namespace string, message string, fields
|
|||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,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("podinfo", "test", "test", fields, "info")
|
||||
|
|
|
|||
Loading…
Reference in New Issue