diff --git a/pkg/api/v1/model_test.go b/pkg/api/v1/model_test.go index b8ded810..c68254a3 100644 --- a/pkg/api/v1/model_test.go +++ b/pkg/api/v1/model_test.go @@ -3,9 +3,10 @@ package v1_test import ( "testing" + "github.com/stretchr/testify/assert" + v1 "github.com/kyverno/policy-reporter/pkg/api/v1" "github.com/kyverno/policy-reporter/pkg/database" - "github.com/stretchr/testify/assert" ) func TestMapping(t *testing.T) { diff --git a/pkg/api/v2/views_test.go b/pkg/api/v2/views_test.go index 0d4f3dad..d66cb980 100644 --- a/pkg/api/v2/views_test.go +++ b/pkg/api/v2/views_test.go @@ -3,10 +3,11 @@ package v2_test import ( "testing" + "github.com/stretchr/testify/assert" + v2 "github.com/kyverno/policy-reporter/pkg/api/v2" "github.com/kyverno/policy-reporter/pkg/config" "github.com/kyverno/policy-reporter/pkg/database" - "github.com/stretchr/testify/assert" ) func TestV2Views(t *testing.T) { @@ -330,7 +331,6 @@ func TestV2Views(t *testing.T) { Valid: true, Channels: []*config.Target[config.GCSOptions]{ { - Name: "Target 2", MinimumPriority: "warning", Config: &config.GCSOptions{ @@ -340,7 +340,6 @@ func TestV2Views(t *testing.T) { Valid: true, }, { - Name: "Target 2", MinimumPriority: "warning", Config: &config.GCSOptions{ @@ -354,5 +353,4 @@ func TestV2Views(t *testing.T) { assert.Equal(t, 2, len(targets)) }) - } diff --git a/pkg/cache/redis_test.go b/pkg/cache/redis_test.go index 11565fa2..bf91391c 100644 --- a/pkg/cache/redis_test.go +++ b/pkg/cache/redis_test.go @@ -7,6 +7,7 @@ import ( "time" goredis "github.com/go-redis/redis/v8" + "github.com/kyverno/policy-reporter/pkg/cache" "github.com/kyverno/policy-reporter/pkg/fixtures" ) diff --git a/pkg/config/resolver_test.go b/pkg/config/resolver_test.go index eeac1131..d706820a 100644 --- a/pkg/config/resolver_test.go +++ b/pkg/config/resolver_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" "k8s.io/client-go/rest" "github.com/kyverno/policy-reporter/pkg/config" @@ -218,6 +219,9 @@ var testConfig = &config.Config{ }, }, Targets: targets, + Logging: config.Logging{ + Development: true, + }, } func Test_ResolveTargets(t *testing.T) { @@ -326,7 +330,11 @@ func Test_ResolvePolicyStore(t *testing.T) { } func Test_ResolveAPIServer(t *testing.T) { - resolver := config.NewResolver(&config.Config{}, &rest.Config{}) + resolver := config.NewResolver(&config.Config{ + API: config.API{ + BasicAuth: config.BasicAuth{Username: "user", Password: "password"}, + }, + }, &rest.Config{}) server, _ := resolver.Server(context.Background(), nil) if server == nil { @@ -576,6 +584,14 @@ func Test_ResolveLogger(t *testing.T) { } } +func Test_Logger(t *testing.T) { + resolver := config.NewResolver(&config.Config{}, &rest.Config{}) + + logger, _ := resolver.Logger() + + assert.NotNil(t, logger) +} + func Test_ResolveEnableLeaderElection(t *testing.T) { t.Run("general disabled", func(t *testing.T) { resolver := config.NewResolver(&config.Config{ diff --git a/pkg/config/target_factory.go b/pkg/config/target_factory.go index 6222f0e0..ce78a822 100644 --- a/pkg/config/target_factory.go +++ b/pkg/config/target_factory.go @@ -22,6 +22,8 @@ import ( "github.com/kyverno/policy-reporter/pkg/target/http" "github.com/kyverno/policy-reporter/pkg/target/kinesis" "github.com/kyverno/policy-reporter/pkg/target/loki" + "github.com/kyverno/policy-reporter/pkg/target/provider/aws" + gs "github.com/kyverno/policy-reporter/pkg/target/provider/gcs" "github.com/kyverno/policy-reporter/pkg/target/s3" "github.com/kyverno/policy-reporter/pkg/target/securityhub" "github.com/kyverno/policy-reporter/pkg/target/slack" @@ -431,14 +433,14 @@ func (f *TargetFactory) createS3Client(config, parent *Target[S3Options]) target config.MapBaseParent(parent) - s3Client := helper.NewS3Client( + s3Client := aws.NewS3Client( config.Config.AccessKeyID, config.Config.SecretAccessKey, config.Config.Region, config.Config.Endpoint, config.Config.Bucket, config.Config.PathStyle, - helper.WithKMS(config.Config.BucketKeyEnabled, &config.Config.KmsKeyID, &config.Config.ServerSideEncryption), + aws.WithKMS(config.Config.BucketKeyEnabled, &config.Config.KmsKeyID, &config.Config.ServerSideEncryption), ) sugar.Infof("%s configured", config.Name) @@ -485,7 +487,7 @@ func (f *TargetFactory) createKinesisClient(config, parent *Target[KinesisOption config.MapBaseParent(parent) - kinesisClient := helper.NewKinesisClient( + kinesisClient := aws.NewKinesisClient( config.Config.AccessKeyID, config.Config.SecretAccessKey, config.Config.Region, @@ -533,7 +535,7 @@ func (f *TargetFactory) createSecurityHub(config, parent *Target[SecurityHubOpti setInt(&config.Config.DelayInSeconds, parent.Config.DelayInSeconds) - client := helper.NewHubClient( + client := aws.NewHubClient( config.Config.AccessKeyID, config.Config.SecretAccessKey, config.Config.Region, @@ -584,7 +586,7 @@ func (f *TargetFactory) createGCSClient(config, parent *Target[GCSOptions]) targ config.MapBaseParent(parent) - gcsClient := helper.NewGCSClient( + gcsClient := gs.NewClient( context.Background(), config.Config.Credentials, config.Config.Bucket, diff --git a/pkg/crd/api/policyreport/v1alpha2/clusterpolicyreport_types_test.go b/pkg/crd/api/policyreport/v1alpha2/clusterpolicyreport_types_test.go index 6fb0d76d..07e748dc 100644 --- a/pkg/crd/api/policyreport/v1alpha2/clusterpolicyreport_types_test.go +++ b/pkg/crd/api/policyreport/v1alpha2/clusterpolicyreport_types_test.go @@ -3,9 +3,10 @@ package v1alpha2_test import ( "testing" - "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" ) func TestClusterPolicyReport(t *testing.T) { diff --git a/pkg/crd/api/policyreport/v1alpha2/common_test.go b/pkg/crd/api/policyreport/v1alpha2/common_test.go index 7fd31f01..0737ead4 100644 --- a/pkg/crd/api/policyreport/v1alpha2/common_test.go +++ b/pkg/crd/api/policyreport/v1alpha2/common_test.go @@ -4,8 +4,9 @@ import ( "encoding/json" "testing" - "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" corev1 "k8s.io/api/core/v1" + + "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" ) func TestCommon(t *testing.T) { diff --git a/pkg/crd/api/policyreport/v1alpha2/policyreport_types_test.go b/pkg/crd/api/policyreport/v1alpha2/policyreport_types_test.go index ea3aa735..8a362924 100644 --- a/pkg/crd/api/policyreport/v1alpha2/policyreport_types_test.go +++ b/pkg/crd/api/policyreport/v1alpha2/policyreport_types_test.go @@ -3,9 +3,10 @@ package v1alpha2_test import ( "testing" - "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" ) func TestPolicyReport(t *testing.T) { diff --git a/pkg/helper/http_test.go b/pkg/helper/http_test.go new file mode 100644 index 00000000..169e3053 --- /dev/null +++ b/pkg/helper/http_test.go @@ -0,0 +1,43 @@ +package helper_test + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/kyverno/policy-reporter/pkg/helper" +) + +func TestSendJSONResponse(t *testing.T) { + t.Run("success response", func(t *testing.T) { + w := httptest.NewRecorder() + + helper.SendJSONResponse(w, []string{"default", "user"}, nil) + + assert.Equal(t, http.StatusOK, w.Code) + + resp := make([]string, 0, 2) + + json.NewDecoder(w.Body).Decode(&resp) + + assert.Equal(t, []string{"default", "user"}, resp) + }) + + t.Run("error response", func(t *testing.T) { + w := httptest.NewRecorder() + + helper.SendJSONResponse(w, nil, errors.New("error")) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + + resp := make(map[string]string, 0) + + json.NewDecoder(w.Body).Decode(&resp) + + assert.Equal(t, map[string]string{"message": "error"}, resp) + }) +} diff --git a/pkg/helper/utils.go b/pkg/helper/utils.go index 396c5116..77e56bdf 100644 --- a/pkg/helper/utils.go +++ b/pkg/helper/utils.go @@ -1,7 +1,6 @@ package helper import ( - "encoding/json" "strings" ) @@ -24,19 +23,6 @@ func ToList[T any, R comparable](mapping map[R]T) []T { return list } -func Merge[T comparable, R any](first, second map[T]R) map[T]R { - merged := make(map[T]R, len(first)+len(second)) - - for k, v := range first { - merged[k] = v - } - for k, v := range second { - merged[k] = v - } - - return merged -} - func Map[T any, R any](source []T, cb func(T) R) []R { list := make([]R, 0, len(source)) for _, i := range source { @@ -46,17 +32,6 @@ func Map[T any, R any](source []T, cb func(T) R) []R { return list } -func ConvertJSONToMap(s string) map[string]string { - m := make(map[string]string) - if s == "" { - return m - } - - _ = json.Unmarshal([]byte(s), &m) - - return m -} - func ConvertMap(m map[string]any) map[string]string { n := make(map[string]string, len(m)) for k, v := range m { @@ -75,3 +50,7 @@ func Defaults(s, f string) string { return f } + +func ToPointer[T any](s T) *T { + return &s +} diff --git a/pkg/helper/utils_test.go b/pkg/helper/utils_test.go new file mode 100644 index 00000000..d1146ba2 --- /dev/null +++ b/pkg/helper/utils_test.go @@ -0,0 +1,53 @@ +package helper_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/kyverno/policy-reporter/pkg/helper" +) + +func TestContains(t *testing.T) { + assert.True(t, helper.Contains("kyverno", []string{"test", "kyverno", "trivy"})) + assert.False(t, helper.Contains("kube-bench", []string{"test", "kyverno", "trivy"})) +} + +func TestToList(t *testing.T) { + result := helper.ToList(map[string]string{ + "first": "kyverno", + "second": "trivy", + }) + + assert.Equal(t, 2, len(result)) + assert.Contains(t, result, "kyverno") + assert.Contains(t, result, "trivy") +} + +func TestMap(t *testing.T) { + assert.Equal(t, []string{"kyverno", "trivy"}, helper.Map([]string{"source_kyverno", "source_trivy"}, func(value string) string { + return strings.TrimPrefix(value, "source_") + })) +} + +func TestConvertMap(t *testing.T) { + assert.Equal(t, map[string]string{"first": "kyverno", "second": "trivy"}, helper.ConvertMap(map[string]any{ + "first": "kyverno", + "second": "trivy", + "third": 3, + })) +} + +func TestDetauls(t *testing.T) { + assert.Equal(t, "fallback", helper.Defaults("", "fallback")) + assert.Equal(t, "value", helper.Defaults("value", "fallback")) +} + +func TestToPointer(t *testing.T) { + value := "test" + number := 5 + + assert.Equal(t, &value, helper.ToPointer(value)) + assert.Equal(t, &number, helper.ToPointer(number)) +} diff --git a/pkg/kubernetes/namespaces/client_test.go b/pkg/kubernetes/namespaces/client_test.go new file mode 100644 index 00000000..310c2db7 --- /dev/null +++ b/pkg/kubernetes/namespaces/client_test.go @@ -0,0 +1,100 @@ +package namespaces_test + +import ( + "context" + "errors" + "testing" + + gocache "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "github.com/kyverno/policy-reporter/pkg/kubernetes/namespaces" +) + +func newFakeClient() v1.NamespaceInterface { + return fake.NewSimpleClientset( + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Labels: map[string]string{ + "team": "team-a", + "name": "default", + }, + }, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "user", + Labels: map[string]string{ + "team": "team-a", + "name": "user", + }, + }, + }, + ).CoreV1().Namespaces() +} + +type nsErrorClient struct { + v1.NamespaceInterface +} + +func (s *nsErrorClient) List(ctx context.Context, opts metav1.ListOptions) (*corev1.NamespaceList, error) { + return nil, errors.New("error") +} + +func TestClient(t *testing.T) { + t.Run("read from api", func(t *testing.T) { + client := namespaces.NewClient(newFakeClient(), gocache.New(gocache.DefaultExpiration, gocache.DefaultExpiration)) + + list, err := client.List(context.Background(), map[string]string{"name": "default"}) + + assert.Nil(t, err) + assert.Equal(t, 1, len(list)) + }) + + t.Run("read from cache", func(t *testing.T) { + fake := newFakeClient() + cache := gocache.New(gocache.NoExpiration, gocache.NoExpiration) + + client := namespaces.NewClient(fake, cache) + + list, err := client.List(context.Background(), map[string]string{"team": "team-a"}) + + assert.Nil(t, err) + assert.Equal(t, 2, len(list)) + + fake.Create(context.Background(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "finance", + Labels: map[string]string{ + "team": "team-a", + "name": "finance", + }, + }, + }, metav1.CreateOptions{}) + + list, err = client.List(context.Background(), map[string]string{"team": "team-a"}) + + assert.Nil(t, err) + assert.Equal(t, 2, len(list)) + + cache.Flush() + + list, err = client.List(context.Background(), map[string]string{"team": "team-a"}) + + assert.Nil(t, err) + assert.Equal(t, 3, len(list)) + }) + t.Run("return error", func(t *testing.T) { + client := namespaces.NewClient(&nsErrorClient{NamespaceInterface: newFakeClient()}, gocache.New(gocache.DefaultExpiration, gocache.DefaultExpiration)) + + _, err := client.List(context.Background(), map[string]string{"team": "team-a"}) + + assert.NotNil(t, err) + assert.Equal(t, "error", err.Error()) + }) +} diff --git a/pkg/kubernetes/retry_test.go b/pkg/kubernetes/retry_test.go new file mode 100644 index 00000000..ce7fb936 --- /dev/null +++ b/pkg/kubernetes/retry_test.go @@ -0,0 +1,152 @@ +package kubernetes_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + kerr "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "github.com/kyverno/policy-reporter/pkg/kubernetes" +) + +func newFakeClient() v1.NamespaceInterface { + return fake.NewSimpleClientset( + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Labels: map[string]string{ + "team": "team-a", + "name": "default", + }, + }, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "user", + Labels: map[string]string{ + "team": "team-a", + "name": "user", + }, + }, + }, + ).CoreV1().Namespaces() +} + +type ns struct { + maxRetry int + try int + err bool + v1.NamespaceInterface +} + +func (s *ns) List(ctx context.Context, opts metav1.ListOptions) (*corev1.NamespaceList, error) { + if !s.err { + if s.try >= s.maxRetry { + return s.NamespaceInterface.List(ctx, opts) + } + + s.try++ + } + + return nil, errors.New("error") +} + +func TestRetry(t *testing.T) { + t.Run("direct success", func(t *testing.T) { + client := &ns{NamespaceInterface: newFakeClient()} + + list, err := kubernetes.Retry(func() (*corev1.NamespaceList, error) { + return client.List(context.Background(), metav1.ListOptions{}) + }) + + assert.Nil(t, err) + assert.Equal(t, 2, len(list.Items)) + }) + + t.Run("retry success", func(t *testing.T) { + client := &ns{maxRetry: 1, NamespaceInterface: newFakeClient()} + + list, err := kubernetes.Retry(func() (*corev1.NamespaceList, error) { + return client.List(context.Background(), metav1.ListOptions{}) + }) + + assert.Nil(t, err) + assert.Equal(t, 2, len(list.Items)) + }) + + t.Run("retry error", func(t *testing.T) { + client := &ns{NamespaceInterface: newFakeClient(), err: true} + + _, err := kubernetes.Retry(func() (*corev1.NamespaceList, error) { + return client.List(context.Background(), metav1.ListOptions{}) + }) + + assert.NotNil(t, err) + }) + + t.Run("retry timeout", func(t *testing.T) { + try := 0 + + _, err := kubernetes.Retry(func() (any, error) { + try++ + + return nil, &kerr.StatusError{ + ErrStatus: metav1.Status{Reason: metav1.StatusReasonTimeout}, + } + }) + + assert.Equal(t, 5, try) + assert.NotNil(t, err) + }) + + t.Run("retry server timeout", func(t *testing.T) { + try := 0 + + _, err := kubernetes.Retry(func() (any, error) { + try++ + + return nil, &kerr.StatusError{ + ErrStatus: metav1.Status{Reason: metav1.StatusReasonServerTimeout}, + } + }) + + assert.Equal(t, 5, try) + assert.NotNil(t, err) + }) + + t.Run("retry service unavailable", func(t *testing.T) { + try := 0 + + _, err := kubernetes.Retry(func() (any, error) { + try++ + + return nil, &kerr.StatusError{ + ErrStatus: metav1.Status{Reason: metav1.StatusReasonServiceUnavailable}, + } + }) + + assert.Equal(t, 5, try) + assert.NotNil(t, err) + }) + + t.Run("retry ignore other status", func(t *testing.T) { + try := 0 + + _, err := kubernetes.Retry(func() (any, error) { + try++ + + return nil, &kerr.StatusError{ + ErrStatus: metav1.Status{Reason: metav1.StatusReasonForbidden}, + } + }) + + assert.Equal(t, 1, try) + assert.NotNil(t, err) + }) +} diff --git a/pkg/leaderelection/client.go b/pkg/leaderelection/client.go index 6444a9cd..76b02d80 100644 --- a/pkg/leaderelection/client.go +++ b/pkg/leaderelection/client.go @@ -45,7 +45,13 @@ func (c *Client) RegisterOnNew(callback func(currentID string, lockID string)) * } func (c *Client) Run(ctx context.Context) error { - k8sleaderelection.RunOrDie(ctx, k8sleaderelection.LeaderElectionConfig{ + k8sleaderelection.RunOrDie(ctx, c.CreateConfig()) + + return errors.New("leaderelection stopped") +} + +func (c *Client) CreateConfig() k8sleaderelection.LeaderElectionConfig { + return k8sleaderelection.LeaderElectionConfig{ Lock: c.CreateLock(), ReleaseOnCancel: c.releaseOnCancel, LeaseDuration: c.leaseDuration, @@ -58,9 +64,7 @@ func (c *Client) Run(ctx context.Context) error { c.onNewLeader(identity, c.identity) }, }, - }) - - return errors.New("leaderelection stopped") + } } func (c *Client) CreateLock() *resourcelock.LeaseLock { diff --git a/pkg/leaderelection/client_test.go b/pkg/leaderelection/client_test.go index 76fd8b84..4228a79e 100644 --- a/pkg/leaderelection/client_test.go +++ b/pkg/leaderelection/client_test.go @@ -5,30 +5,42 @@ import ( "testing" "time" - "github.com/kyverno/policy-reporter/pkg/leaderelection" + "github.com/stretchr/testify/assert" "k8s.io/client-go/kubernetes/typed/coordination/v1/fake" + + "github.com/kyverno/policy-reporter/pkg/leaderelection" ) func TestClient(t *testing.T) { - client := leaderelection.New(&fake.FakeCoordinationV1{}, "policy-reporter", "namespace", "pod-123", time.Second, time.Second, time.Second, false) + client := leaderelection.New(&fake.FakeCoordinationV1{}, "policy-reporter", "namespace", "pod-123", time.Second, time.Second, time.Second, true) if client == nil { t.Fatal("failed to create leaderelection client") } - client.RegisterOnNew(func(currentID, lockID string) {}) + var isLeader bool + client.RegisterOnNew(func(currentID, lockID string) { + isLeader = currentID == lockID + }) + client.RegisterOnStart(func(c context.Context) {}) client.RegisterOnStop(func() {}) lock := client.CreateLock() - if lock.LeaseMeta.Name != "policy-reporter" { - t.Error("unexpected lease name") - } - if lock.LeaseMeta.Namespace != "namespace" { - t.Error("unexpected lease namespace") - } - if lock.LockConfig.Identity != "pod-123" { - t.Error("unexpected lease identity") - } + assert.Equal(t, "policy-reporter", lock.LeaseMeta.Name) + assert.Equal(t, "namespace", lock.LeaseMeta.Namespace) + assert.Equal(t, "pod-123", lock.LockConfig.Identity) + + assert.False(t, isLeader) + + config := client.CreateConfig() + + config.Callbacks.OnNewLeader("pod-123") + + assert.True(t, isLeader) + assert.Equal(t, time.Second, config.LeaseDuration) + assert.Equal(t, time.Second, config.RenewDeadline) + assert.Equal(t, time.Second, config.RetryPeriod) + assert.True(t, config.ReleaseOnCancel) } diff --git a/pkg/target/gcs/gcs.go b/pkg/target/gcs/gcs.go index add51ce1..95164adb 100644 --- a/pkg/target/gcs/gcs.go +++ b/pkg/target/gcs/gcs.go @@ -10,23 +10,23 @@ import ( "go.uber.org/zap" "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" - "github.com/kyverno/policy-reporter/pkg/helper" "github.com/kyverno/policy-reporter/pkg/target" "github.com/kyverno/policy-reporter/pkg/target/http" + "github.com/kyverno/policy-reporter/pkg/target/provider/gcs" ) // Options to configure the GCS target type Options struct { target.ClientOptions CustomFields map[string]string - Client helper.GCPClient + Client gcs.Client Prefix string } type client struct { target.BaseClient customFields map[string]string - client helper.GCPClient + client gcs.Client prefix string } diff --git a/pkg/target/http/logroundtripper.go b/pkg/target/http/logroundtripper.go index 762c669e..231315ca 100644 --- a/pkg/target/http/logroundtripper.go +++ b/pkg/target/http/logroundtripper.go @@ -16,14 +16,13 @@ type logRoundTripper struct { roundTripper http.RoundTripper } +var _ http.RoundTripper = (*logRoundTripper)(nil) + func (rt *logRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { logger := zap.L() if logger.Core().Enabled(zap.DebugLevel) { if info, err := httputil.DumpRequest(req, true); err == nil { logger.Debug(fmt.Sprintf("Sending request: %s", string(info))) - if err != nil { - return nil, err - } } } resp, err := rt.roundTripper.RoundTrip(req) diff --git a/pkg/target/http/logroundtripper_test.go b/pkg/target/http/logroundtripper_test.go new file mode 100644 index 00000000..ec6441c8 --- /dev/null +++ b/pkg/target/http/logroundtripper_test.go @@ -0,0 +1,32 @@ +package http_test + +import ( + net "net/http" + "net/http/httptest" + "testing" + + "github.com/kyverno/policy-reporter/pkg/target/http" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" +) + +type mock struct{} + +func (rt mock) RoundTrip(req *net.Request) (*net.Response, error) { + return httptest.NewRecorder().Result(), nil +} + +func TestDebug(t *testing.T) { + obs, logs := observer.New(zap.DebugLevel) + + zap.ReplaceGlobals(zap.New(obs)) + + r := http.NewLoggingRoundTripper(mock{}) + + _, err := r.RoundTrip(httptest.NewRequest("GET", "http://localhost:8080/healthz", nil)) + + assert.Nil(t, err) + + assert.Equal(t, 2, logs.FilterLevelExact(zap.DebugLevel).Len()) +} diff --git a/pkg/target/http/utils_test.go b/pkg/target/http/utils_test.go new file mode 100644 index 00000000..8b0d736e --- /dev/null +++ b/pkg/target/http/utils_test.go @@ -0,0 +1,94 @@ +package http_test + +import ( + "encoding/json" + "errors" + "net/http/httptest" + "testing" + + "github.com/kyverno/policy-reporter/pkg/fixtures" + "github.com/kyverno/policy-reporter/pkg/target/http" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.uber.org/zap/zaptest/observer" +) + +func TestResultMapping(t *testing.T) { + result := http.NewJSONResult(fixtures.CompleteTargetSendResult) + + assert.Equal(t, result.Message, fixtures.CompleteTargetSendResult.Message) + assert.Equal(t, result.Policy, fixtures.CompleteTargetSendResult.Policy) + assert.Equal(t, result.Rule, fixtures.CompleteTargetSendResult.Rule) + assert.Equal(t, result.Resource.Name, fixtures.CompleteTargetSendResult.Resources[0].Name) +} + +func TestCreateJSONRequest(t *testing.T) { + t.Run("success", func(t *testing.T) { + req, err := http.CreateJSONRequest("Test", "GET", "http://localhost:8080", []string{"test"}) + + assert.Nil(t, err) + + list := make([]string, 0) + + json.NewDecoder(req.Body).Decode(&list) + + assert.Equal(t, []string{"test"}, list) + assert.Equal(t, "GET", req.Method) + assert.Equal(t, "application/json; charset=utf-8", req.Header.Get("Content-Type")) + assert.Equal(t, "Policy-Reporter", req.Header.Get("User-Agent")) + }) + + t.Run("error", func(t *testing.T) { + _, err := http.CreateJSONRequest("Test", "GET", "\test", []string{"test"}) + + assert.NotNil(t, err) + }) +} + +func TestClient(t *testing.T) { + assert.NotNil(t, http.NewClient("", true)) +} + +func TestProcessHTTPResponse(t *testing.T) { + t.Run("success", func(t *testing.T) { + obs, logs := observer.New(zap.InfoLevel) + + zap.ReplaceGlobals(zap.New(obs)) + + w := httptest.NewRecorder() + w.Write([]byte(`["test"]`)) + + http.ProcessHTTPResponse("Test", w.Result(), nil) + + assert.Equal(t, 1, logs.Len()) + assert.Equal(t, 1, logs.FilterLevelExact(zap.InfoLevel).Len()) + }) + t.Run("error", func(t *testing.T) { + obs, logs := observer.New(zap.InfoLevel) + + zap.ReplaceGlobals(zap.New(obs)) + + w := httptest.NewRecorder() + w.Write([]byte(`["test"]`)) + + http.ProcessHTTPResponse("Test", w.Result(), errors.New("error")) + + assert.Equal(t, 1, logs.Len()) + assert.Equal(t, 1, logs.FilterMessage("Test: PUSH FAILED").Len()) + }) + t.Run("error status code", func(t *testing.T) { + obs, logs := observer.New(zap.InfoLevel) + + zap.ReplaceGlobals(zap.New(obs)) + + w := httptest.NewRecorder() + resp := w.Result() + resp.StatusCode = 404 + + http.ProcessHTTPResponse("Test", w.Result(), nil) + + assert.Equal(t, 1, logs.Len()) + assert.Equal(t, 1, logs.FilterMessage("Test: PUSH FAILED").Len()) + }) +} diff --git a/pkg/target/kinesis/kinesis.go b/pkg/target/kinesis/kinesis.go index c365a613..f002c406 100644 --- a/pkg/target/kinesis/kinesis.go +++ b/pkg/target/kinesis/kinesis.go @@ -10,22 +10,22 @@ import ( "go.uber.org/zap" "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" - "github.com/kyverno/policy-reporter/pkg/helper" "github.com/kyverno/policy-reporter/pkg/target" "github.com/kyverno/policy-reporter/pkg/target/http" + "github.com/kyverno/policy-reporter/pkg/target/provider/aws" ) // Options to configure the Kinesis target type Options struct { target.ClientOptions CustomFields map[string]string - Kinesis helper.AWSClient + Kinesis aws.Client } type client struct { target.BaseClient customFields map[string]string - kinesis helper.AWSClient + kinesis aws.Client } func (c *client) Send(result v1alpha2.PolicyReportResult) { diff --git a/pkg/helper/aws.go b/pkg/target/provider/aws/aws.go similarity index 97% rename from pkg/helper/aws.go rename to pkg/target/provider/aws/aws.go index a3cbec61..0735ff96 100644 --- a/pkg/helper/aws.go +++ b/pkg/target/provider/aws/aws.go @@ -1,4 +1,4 @@ -package helper +package aws import ( "bytes" @@ -21,7 +21,7 @@ import ( var enable = true -type AWSClient interface { +type Client interface { // Upload given Data the configured AWS storage Upload(body *bytes.Buffer, key string) error } @@ -62,7 +62,7 @@ func (s *s3Client) Upload(body *bytes.Buffer, key string) error { } // NewS3Client creates a new S3.client to send Results to S3 -func NewS3Client(accessKeyID, secretAccessKey, region, endpoint, bucket string, pathStyle bool, opts ...Options) AWSClient { +func NewS3Client(accessKeyID, secretAccessKey, region, endpoint, bucket string, pathStyle bool, opts ...Options) Client { config, err := createConfig(accessKeyID, secretAccessKey, region) if err != nil { zap.L().Error("error while creating config", zap.Error(err)) @@ -111,7 +111,7 @@ func (k *kinesisClient) Upload(body *bytes.Buffer, key string) error { } // NewKinesisClient creates a new S3.client to send Results to S3 -func NewKinesisClient(accessKeyID, secretAccessKey, region, endpoint, streamName string) AWSClient { +func NewKinesisClient(accessKeyID, secretAccessKey, region, endpoint, streamName string) Client { config, err := createConfig(accessKeyID, secretAccessKey, region) if err != nil { zap.L().Error("error while creating config", zap.Error(err)) diff --git a/pkg/target/provider/aws/aws_test.go b/pkg/target/provider/aws/aws_test.go new file mode 100644 index 00000000..9f7bdeca --- /dev/null +++ b/pkg/target/provider/aws/aws_test.go @@ -0,0 +1,28 @@ +package aws_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/kyverno/policy-reporter/pkg/helper" + "github.com/kyverno/policy-reporter/pkg/target/provider/aws" +) + +func TestS3Client(t *testing.T) { + client := aws.NewS3Client("access", "secret", "eu-central-1", "http://s3.aws.com", "policy-reporter", false, aws.WithKMS(true, helper.ToPointer("kms"), helper.ToPointer("encryption"))) + + assert.NotNil(t, client) +} + +func TestKinesisClient(t *testing.T) { + client := aws.NewKinesisClient("access", "secret", "eu-central-1", "http://kinesis.aws.com", "policy-reporter") + + assert.NotNil(t, client) +} + +func TestSecurityHubClient(t *testing.T) { + client := aws.NewHubClient("access", "secret", "eu-central-1", "http://securityhub.aws.com") + + assert.NotNil(t, client) +} diff --git a/pkg/helper/gcp.go b/pkg/target/provider/gcs/gcs.go similarity index 68% rename from pkg/helper/gcp.go rename to pkg/target/provider/gcs/gcs.go index 0b9740f2..95b763f8 100644 --- a/pkg/helper/gcp.go +++ b/pkg/target/provider/gcs/gcs.go @@ -1,4 +1,4 @@ -package helper +package gcs import ( "bytes" @@ -10,17 +10,17 @@ import ( "google.golang.org/api/option" ) -type GCPClient interface { +type Client interface { // Upload given Data the configured AWS storage Upload(body *bytes.Buffer, key string) error } -type gcsClient struct { +type client struct { bucket string client *storage.Client } -func (c *gcsClient) Upload(body *bytes.Buffer, key string) error { +func (c *client) Upload(body *bytes.Buffer, key string) error { object := c.client.Bucket(c.bucket).Object(key) writer := object.NewWriter(context.Background()) @@ -34,22 +34,22 @@ func (c *gcsClient) Upload(body *bytes.Buffer, key string) error { return writer.Close() } -// NewGCSClient creates a new GCS.client to send Results to GCS Bucket -func NewGCSClient(ctx context.Context, credentials, bucket string) GCPClient { +// NewClient creates a new GCS.client to send Results to GCS Bucket +func NewClient(ctx context.Context, credentials, bucket string) Client { cred, err := google.CredentialsFromJSON(ctx, []byte(credentials), storage.ScopeReadWrite) if err != nil { zap.L().Error("error while creating GCS credentials", zap.Error(err)) return nil } - client, err := storage.NewClient(ctx, option.WithCredentials(cred)) + baseClient, err := storage.NewClient(ctx, option.WithCredentials(cred)) if err != nil { zap.L().Error("error while creating GCS client", zap.Error(err)) return nil } - return &gcsClient{ + return &client{ bucket, - client, + baseClient, } } diff --git a/pkg/target/s3/s3.go b/pkg/target/s3/s3.go index 45333223..04c6412b 100644 --- a/pkg/target/s3/s3.go +++ b/pkg/target/s3/s3.go @@ -10,23 +10,23 @@ import ( "go.uber.org/zap" "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" - "github.com/kyverno/policy-reporter/pkg/helper" "github.com/kyverno/policy-reporter/pkg/target" "github.com/kyverno/policy-reporter/pkg/target/http" + "github.com/kyverno/policy-reporter/pkg/target/provider/aws" ) // Options to configure the S3 target type Options struct { target.ClientOptions CustomFields map[string]string - S3 helper.AWSClient + S3 aws.Client Prefix string } type client struct { target.BaseClient customFields map[string]string - s3 helper.AWSClient + s3 aws.Client prefix string } diff --git a/pkg/target/securityhub/securityhub_test.go b/pkg/target/securityhub/securityhub_test.go index 959735b3..26e1a12b 100644 --- a/pkg/target/securityhub/securityhub_test.go +++ b/pkg/target/securityhub/securityhub_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" hub "github.com/aws/aws-sdk-go-v2/service/securityhub" "github.com/aws/aws-sdk-go-v2/service/securityhub/types" + "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" "github.com/kyverno/policy-reporter/pkg/fixtures" "github.com/kyverno/policy-reporter/pkg/target/securityhub"