Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

poc: send event with details #782

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ github.com/stretchr/testify,github.com/stretchr/testify/assert,MIT
github.com/stretchr/testify,github.com/stretchr/testify/mock,MIT
github.com/stretchr/testify,github.com/stretchr/testify/require,MIT
github.com/subosito/gotenv,github.com/subosito/gotenv,MIT
github.com/tidwall/gjson,github.com/tidwall/gjson,MIT
github.com/tidwall/match,github.com/tidwall/match,MIT
github.com/tidwall/pretty,github.com/tidwall/pretty,MIT
github.com/tinylib/msgp,github.com/tinylib/msgp/msgp,MIT
github.com/vishvananda/netlink,github.com/vishvananda/netlink,Apache-2.0
github.com/vishvananda/netlink,github.com/vishvananda/netlink/nl,Apache-2.0
Expand Down
5 changes: 5 additions & 0 deletions chart/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ data:
url: {{ .Values.controller.notifiers.http.url | quote }}
headers: {{ .Values.controller.notifiers.http.headers | toJson }}
headersFilepath: {{ .Values.controller.notifiers.http.headersFilepath | quote }}
hasDetails: {{ .Values.controller.notifiers.http.hasDetails }}
filteredReasons: {{ .Values.controller.notifiers.http.filteredReasons | toJson }}
authURL: {{ .Values.controller.notifiers.http.authURL | quote }}
authHeaders: {{ .Values.controller.notifiers.http.authHeaders | toJson }}
authTokenPath: {{ .Values.controller.notifiers.http.authTokenPath | quote }}
datadog:
enabled: {{ .Values.controller.notifiers.datadog.enabled }}
cloudProviders:
Expand Down
5 changes: 5 additions & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ controller:
url: ""
headersFilepath: ""
headers: []
hasDetails: false
filteredReasons: []
authURL: ""
authHeaders: []
authTokenPath: ""
cloudProviders: # cloud providers specific disruptions configuration
disableAll: false # disable all cloud providers disruption, it overrides per cloud provider configuration (you can't disable all + enable one)
pullInterval: 24h # pull interval used by the controller to update ip ranges files
Expand Down
30 changes: 30 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,36 @@ func New(logger *zap.SugaredLogger, osArgs []string) (config, error) {
return cfg, err
}

mainFS.BoolVar(&cfg.Controller.Notifiers.HTTP.HasDetails, "notifiers-http-has-details", false, "WARNING/ALPHA: Provide additional details in http body payload sent")

if err := viper.BindPFlag("controller.notifiers.http.hasDetails", mainFS.Lookup("notifiers-http-has-details")); err != nil {
return cfg, err
}

mainFS.StringSliceVar(&cfg.Controller.Notifiers.HTTP.FilteredReasons, "notifiers-http-filtered-reasons", []string{}, "WARNING/ALPHA: Only send http notification for provided reasons")

if err := viper.BindPFlag("controller.notifiers.http.filteredReasons", mainFS.Lookup("notifiers-http-filtered-reasons")); err != nil {
return cfg, err
}

mainFS.StringVar(&cfg.Controller.Notifiers.HTTP.AuthURL, "notifiers-http-auth-url", "", "WARNING/ALPHA: First perform an HTTP request to dynamically retrieve auth information before sending http notification")

if err := viper.BindPFlag("controller.notifiers.http.authURL", mainFS.Lookup("notifiers-http-auth-url")); err != nil {
return cfg, err
}

mainFS.StringSliceVar(&cfg.Controller.Notifiers.HTTP.AuthHeaders, "notifiers-http-auth-headers", []string{}, "WARNING/ALPHA: HTTP headers to provide to auth request")

if err := viper.BindPFlag("controller.notifiers.http.authHeaders", mainFS.Lookup("notifiers-http-auth-headers")); err != nil {
return cfg, err
}

mainFS.StringVar(&cfg.Controller.Notifiers.HTTP.AuthTokenPath, "notifiers-http-auth-token-path", "", "WARNING/ALPHA: Extract bearer token from provided JSON path (using GJSON)")

if err := viper.BindPFlag("controller.notifiers.http.authTokenPath", mainFS.Lookup("notifiers-http-auth-token-path")); err != nil {
return cfg, err
}

mainFS.StringToStringVar(&cfg.Injector.Annotations, "injector-annotations", map[string]string{}, "Annotations added to the generated injector pods")

if err := viper.BindPFlag("injector.annotations", mainFS.Lookup("injector-annotations")); err != nil {
Expand Down
4 changes: 4 additions & 0 deletions eventbroadcaster/notifiersink.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ type NotifierSink struct {
func RegisterNotifierSinks(mgr ctrl.Manager, broadcaster record.EventBroadcaster, notifiersConfig eventnotifier.NotifiersConfig, logger *zap.SugaredLogger) (err error) {
client := mgr.GetClient()

if notifiersConfig.Common.Client == nil {
notifiersConfig.Common.Client = client
}

notifiers, err := eventnotifier.GetNotifiers(notifiersConfig, logger)

for _, notifier := range notifiers {
Expand Down
2 changes: 1 addition & 1 deletion eventnotifier/eventnotifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package eventnotifier
import (
"github.com/DataDog/chaos-controller/api/v1beta1"
"github.com/DataDog/chaos-controller/eventnotifier/datadog"
http "github.com/DataDog/chaos-controller/eventnotifier/http"
"github.com/DataDog/chaos-controller/eventnotifier/http"
"github.com/DataDog/chaos-controller/eventnotifier/noop"
"github.com/DataDog/chaos-controller/eventnotifier/slack"
"github.com/DataDog/chaos-controller/eventnotifier/types"
Expand Down
84 changes: 84 additions & 0 deletions eventnotifier/http/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2023 Datadog, Inc.

package http

import (
"context"
"fmt"
"io"
"net/http"

"go.uber.org/zap"

"github.com/tidwall/gjson"
)

type BearerAuthTokenProvider interface {
AuthToken(ctx context.Context) (string, error)
}

// quickly detect if underlying type does not implement interface
var _ BearerAuthTokenProvider = bearerAuthTokenProvider{}

type bearerAuthTokenProvider struct {
Logger *zap.SugaredLogger
URL string
Client *http.Client
Headers map[string]string
TokenPath string
}

func NewBearerAuthTokenProvider(logger *zap.SugaredLogger, client *http.Client, url string, headers map[string]string, tokenPath string) BearerAuthTokenProvider {
return bearerAuthTokenProvider{
logger,
url,
client,
headers,
tokenPath,
}
}

func (b bearerAuthTokenProvider) AuthToken(ctx context.Context) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, b.URL, nil)
if err != nil {
return "", fmt.Errorf("unable to create http request for URL %s: %w", b.URL, err)
}

for headerKey, headerValue := range b.Headers {
req.Header.Add(headerKey, headerValue)
}

res, err := b.Client.Do(req)
if err != nil {
return "", fmt.Errorf("unable to do http request to get token: %w", err)
}

defer func() {
if err = res.Body.Close(); err != nil {
b.Logger.Warnw("an error occurred while closing body after reading auth token", "error", err)
}
}()

if res.StatusCode >= 300 || res.StatusCode < 200 {
return "", fmt.Errorf("received response contains unexpected status code %d when retrieving auth", res.StatusCode)
}

tokenBytes, err := io.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("error when reading token: %w", err)
}

if b.TokenPath == "" {
return string(tokenBytes), nil
}

value := gjson.Get(string(tokenBytes), b.TokenPath)
if value.Exists() {
return value.String(), nil
}

return "", fmt.Errorf("auth response body does not contains expected token path %s", b.TokenPath)
}
81 changes: 81 additions & 0 deletions eventnotifier/http/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2023 Datadog, Inc.
package http_test

import (
"bytes"
"errors"
"io"
"net/http"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/mock"

. "github.com/DataDog/chaos-controller/eventnotifier/http"
"github.com/DataDog/chaos-controller/mocks"

"go.uber.org/zap"
"go.uber.org/zap/zaptest"
)

var _ = Describe("Auth", func() {
var (
logger *zap.SugaredLogger
httpRoundTripperMock *mocks.RoundTripperMock
httpClient *http.Client
headers map[string]string
authTokenProvider BearerAuthTokenProvider
)

BeforeEach(func() {
logger = zaptest.NewLogger(GinkgoT()).Sugar()
httpRoundTripperMock = mocks.NewRoundTripperMock(GinkgoT())
headers = make(map[string]string)

httpClient = &http.Client{
Transport: httpRoundTripperMock,
}

authTokenProvider = nil
})

DescribeTable("returns expected errors",
func(ctx SpecContext, url, tokenPath string, httpResponse *http.Response, httpError error, expected any) {
if httpResponse != nil || httpError != nil {
httpRoundTripperMock.EXPECT().RoundTrip(mock.Anything).RunAndReturn(func(request *http.Request) (*http.Response, error) {
return httpResponse, httpError
}).Once()
}

authTokenProvider = NewBearerAuthTokenProvider(logger, httpClient, url, headers, tokenPath)

token, err := authTokenProvider.AuthToken(ctx)

Expect(err).To(MatchError(expected))
Expect(token).To(BeEmpty())
},
Entry("invalid url returns error", ":", "", nil, nil, `unable to create http request for URL :: parse ":": missing protocol scheme`),
Entry("server error returns error", "", "", nil, errors.New("server error"), `unable to do http request to get token: Get "": server error`),
Entry("3xx status code returns error", "", "", &http.Response{StatusCode: http.StatusContinue}, nil, "received response contains unexpected status code 100 when retrieving auth"),
Entry("3xx status code returns error", "", "", &http.Response{StatusCode: http.StatusMovedPermanently}, nil, "received response contains unexpected status code 301 when retrieving auth"),
Entry("4xx status code returns error", "", "", &http.Response{StatusCode: http.StatusNotFound}, nil, "received response contains unexpected status code 404 when retrieving auth"),
Entry("5xx status code returns error", "", "", &http.Response{StatusCode: http.StatusInternalServerError}, nil, "received response contains unexpected status code 500 when retrieving auth"),
Entry("2xx status no auth path returns error", "", "some.path", &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader([]byte(`{}`)))}, nil, "auth response body does not contains expected token path some.path"),
)

It("returns a token from a valid response body", func(ctx SpecContext) {
authTokenProvider = NewBearerAuthTokenProvider(logger, httpClient, "", headers, "some.path")

httpRoundTripperMock.EXPECT().RoundTrip(mock.Anything).RunAndReturn(func(request *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader([]byte(`{"some": {"path": "value"}}`)))}, nil
}).Once()

token, err := authTokenProvider.AuthToken(ctx)

Expect(err).ToNot(HaveOccurred())
Expect(token).To(Equal("value"))
})
})
93 changes: 93 additions & 0 deletions eventnotifier/http/bearer_auth_token_provider_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading