Skip to content

Commit

Permalink
feat(metrics-operator): add basicauth to prometheus provider (#2154)
Browse files Browse the repository at this point in the history
Signed-off-by: realanna <anna.reale@dynatrace.com>
Signed-off-by: RealAnna <89971034+RealAnna@users.noreply.github.com>
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: Florian Bacher <florian.bacher@dynatrace.com>
Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com>
Co-authored-by: Florian Bacher <florian.bacher@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Sep 28, 2023
1 parent d7fce2a commit bab605e
Show file tree
Hide file tree
Showing 14 changed files with 518 additions and 61 deletions.
14 changes: 13 additions & 1 deletion metrics-operator/api/v1alpha3/keptnmetricsprovider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,19 @@ func (p *KeptnMetricsProvider) HasSecretDefined() bool {
if p.Spec.SecretKeyRef == (corev1.SecretKeySelector{}) {
return false
}
if strings.TrimSpace(p.Spec.SecretKeyRef.Name) == "" || strings.TrimSpace(p.Spec.SecretKeyRef.Key) == "" {
//if the secret name exists the secret is defined
if strings.TrimSpace(p.Spec.SecretKeyRef.Name) == "" {
return false
}
return true
}

func (p *KeptnMetricsProvider) HasSecretKeyDefined() bool {
if p.Spec.SecretKeyRef == (corev1.SecretKeySelector{}) {
return false
}
//if the secret name exists the secret is defined
if strings.TrimSpace(p.Spec.SecretKeyRef.Key) == "" {
return false
}
return true
Expand Down
13 changes: 1 addition & 12 deletions metrics-operator/controllers/common/providers/datadog/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"strings"

metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3"
corev1 "k8s.io/api/core/v1"
Expand All @@ -16,18 +15,8 @@ const apiKey, appKey = "DD_CLIENT_API_KEY", "DD_CLIENT_APP_KEY"

var ErrSecretKeyRefNotDefined = errors.New("the SecretKeyRef property with the DataDog API Key is missing")

func hasDDSecretDefined(spec metricsapi.KeptnMetricsProviderSpec) bool {
if spec.SecretKeyRef == (corev1.SecretKeySelector{}) {
return false
}
if strings.TrimSpace(spec.SecretKeyRef.Name) == "" {
return false
}
return true
}

func getDDSecret(ctx context.Context, provider metricsapi.KeptnMetricsProvider, k8sClient client.Client) (string, string, error) {
if !hasDDSecretDefined(provider.Spec) {
if !provider.HasSecretDefined() {
return "", "", ErrSecretKeyRefNotDefined
}
ddCredsSecret := &corev1.Secret{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type Error struct {
}

func getDTSecret(ctx context.Context, provider metricsapi.KeptnMetricsProvider, k8sClient client.Client) (string, error) {
if !provider.HasSecretDefined() {
if !provider.HasSecretDefined() || !provider.HasSecretKeyDefined() {
return "", ErrSecretKeyRefNotDefined
}
dtCredsSecret := &corev1.Secret{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ import (
)

func TestGetSecret_NoKeyDefined(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(dtpayload))
require.Nil(t, err)
}))
defer svr.Close()

fakeClient := fake.NewClient()

p := metricsapi.KeptnMetricsProvider{
Spec: metricsapi.KeptnMetricsProviderSpec{
TargetServer: svr.URL,
TargetServer: "svr.URL",
},
}
r, e := getDTSecret(context.TODO(), p, fakeClient)
Expand All @@ -31,16 +27,11 @@ func TestGetSecret_NoKeyDefined(t *testing.T) {
}

func TestGetSecret_NoSecret(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(dtpayload))
require.Nil(t, err)
}))
defer svr.Close()
fakeClient := fake.NewClient()

p := metricsapi.KeptnMetricsProvider{
Spec: metricsapi.KeptnMetricsProviderSpec{
TargetServer: svr.URL,
TargetServer: "svr.URL",
},
}
r, e := getDTSecret(context.TODO(), p, fakeClient)
Expand Down
64 changes: 64 additions & 0 deletions metrics-operator/controllers/common/providers/prometheus/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package prometheus

import (
"context"
"errors"
"net/http"

metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3"
promapi "github.com/prometheus/client_golang/api"
"github.com/prometheus/common/config"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const secretKeyUserName = "user"
const secretKeyPassword = "password"

var ErrSecretKeyRefNotDefined = errors.New("the SecretKeyRef property with the Prometheus API Key is missing")
var ErrInvalidSecretFormat = errors.New("secret key does not contain user and password")

type SecretData struct {
User string `json:"user"`
Password config.Secret `json:"password"`
}

//go:generate moq -pkg fake -skip-ensure -out ./fake/roundtripper_mock.go . IRoundTripper
type IRoundTripper interface {
GetRoundTripper(context.Context, metricsapi.KeptnMetricsProvider, client.Client) (http.RoundTripper, error)
}

type RoundTripperRetriever struct {
}

func (r RoundTripperRetriever) GetRoundTripper(ctx context.Context, provider metricsapi.KeptnMetricsProvider, k8sClient client.Client) (http.RoundTripper, error) {
secret, err := getPrometheusSecret(ctx, provider, k8sClient)
if err != nil {
if errors.Is(err, ErrSecretKeyRefNotDefined) {
return promapi.DefaultRoundTripper, nil
}
return nil, err
}
return config.NewBasicAuthRoundTripper(secret.User, secret.Password, "", promapi.DefaultRoundTripper), nil
}

func getPrometheusSecret(ctx context.Context, provider metricsapi.KeptnMetricsProvider, k8sClient client.Client) (*SecretData, error) {
if !provider.HasSecretDefined() {
return nil, ErrSecretKeyRefNotDefined
}
secret := &corev1.Secret{}
if err := k8sClient.Get(ctx, types.NamespacedName{Name: provider.Spec.SecretKeyRef.Name, Namespace: provider.Namespace}, secret); err != nil {
return nil, err
}

var secretData SecretData
user, ok := secret.Data[secretKeyUserName]
pw, yes := secret.Data[secretKeyPassword]
if !ok || !yes {
return nil, ErrInvalidSecretFormat
}
secretData.User = string(user)
secretData.Password = config.Secret(pw)
return &secretData, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package prometheus

import (
"context"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"

metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3"
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/fake"
promapi "github.com/prometheus/client_golang/api"
"github.com/prometheus/common/config"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const prometheusPayload = "test"

func TestGetSecret_NoKeyDefined(t *testing.T) {

fakeClient := fake.NewClient()

p := metricsapi.KeptnMetricsProvider{
Spec: metricsapi.KeptnMetricsProviderSpec{
TargetServer: "svr.URL",
},
}
r1, e := getPrometheusSecret(context.TODO(), p, fakeClient)
require.NotNil(t, e)
require.ErrorIs(t, ErrSecretKeyRefNotDefined, e)
require.Empty(t, r1)

}

func TestGetSecret_NoSecretDefined(t *testing.T) {

secretName := "testSecret"

fakeClient := fake.NewClient()

b := true
p := metricsapi.KeptnMetricsProvider{
Spec: metricsapi.KeptnMetricsProviderSpec{
SecretKeyRef: v1.SecretKeySelector{
Key: "apiKey",
LocalObjectReference: v1.LocalObjectReference{
Name: secretName,
},
Optional: &b,
},
TargetServer: "svr",
},
}
r1, e := getPrometheusSecret(context.TODO(), p, fakeClient)
require.NotNil(t, e)
t.Log(e.Error())
require.True(t, k8serrors.IsNotFound(e))
require.Empty(t, r1)

}

func TestGetSecret_HappyPath(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(prometheusPayload))
require.Nil(t, err)
}))
defer svr.Close()

secretName := "mySecret"
apiToken := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: "default",
},
Data: map[string][]byte{
"user": []byte("myuser"),
"password": []byte("mytoken"),
},
}
fakeClient := fake.NewClient(apiToken)

p := metricsapi.KeptnMetricsProvider{
ObjectMeta: metav1.ObjectMeta{Namespace: "default"},
Spec: metricsapi.KeptnMetricsProviderSpec{
SecretKeyRef: v1.SecretKeySelector{
Key: "login",
LocalObjectReference: v1.LocalObjectReference{
Name: secretName,
},
},
TargetServer: svr.URL,
},
}
r1, e := getPrometheusSecret(context.TODO(), p, fakeClient)
require.Nil(t, e)
require.Equal(t, "myuser", r1.User)
require.Equal(t, config.Secret("mytoken"), r1.Password)

}

func Test_GetRoundtripper(t *testing.T) {
goodsecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Data: map[string][]byte{
"user": []byte("myuser"),
"password": []byte("mytoken"),
},
}
tests := []struct {
name string
provider metricsapi.KeptnMetricsProvider
k8sClient client.Client
want http.RoundTripper
wantErr bool
errorStr string
}{
{
name: "TestSuccess",
provider: metricsapi.KeptnMetricsProvider{
ObjectMeta: metav1.ObjectMeta{Namespace: "default"},
Spec: metricsapi.KeptnMetricsProviderSpec{
Type: "",
TargetServer: "",
SecretKeyRef: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "test",
},
Key: "",
Optional: nil,
},
},
},
k8sClient: fake.NewClient(goodsecret),
want: config.NewBasicAuthRoundTripper("myuser", "mytoken", "", promapi.DefaultRoundTripper),
wantErr: false,
},
{
name: "TestSecretNotDefined",
provider: metricsapi.KeptnMetricsProvider{},
k8sClient: fake.NewClient(),
want: promapi.DefaultRoundTripper,
wantErr: false,
},
{
name: "TestErrorFromGetPrometheusSecretNotExists",
provider: metricsapi.KeptnMetricsProvider{
ObjectMeta: metav1.ObjectMeta{Namespace: "default"},
Spec: metricsapi.KeptnMetricsProviderSpec{
Type: "",
TargetServer: "",
SecretKeyRef: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "test",
},
Key: "",
Optional: nil,
},
},
},
k8sClient: fake.NewClient(),
want: nil,
wantErr: true,
errorStr: "not found",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := RoundTripperRetriever{}.GetRoundTripper(context.TODO(), tt.provider, tt.k8sClient)
t.Log(err)
if (err != nil) != tt.wantErr {
t.Errorf("getRoundtripper() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.errorStr != "" && !strings.Contains(err.Error(), tt.errorStr) {
t.Errorf("getRoundtripper() error = %s, wantErr %s", err.Error(), tt.errorStr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getRoundtripper() got = %v, want %v", got, tt.want)
}
})
}
}
Loading

0 comments on commit bab605e

Please sign in to comment.