chore: remove some duplication and add a first few tests

This commit is contained in:
2022-09-30 22:07:56 +02:00
parent e124a2ed6b
commit 762fa4f747
8 changed files with 542 additions and 70 deletions
+30
View File
@@ -0,0 +1,30 @@
package kube
import "strings"
type ImageCollector map[string]map[string]struct{}
func NewImageCollector() ImageCollector {
return make(map[string]map[string]struct{})
}
func (c *ImageCollector) Add(image string) {
parts := strings.Split(image[20:], ":")
if x, exists := (*c)[parts[0]]; exists {
x[parts[1]] = struct{}{}
} else {
(*c)[parts[0]] = map[string]struct{}{
parts[1]: {},
}
}
}
func (c *ImageCollector) Images() map[string][]string {
images := make(map[string][]string)
for i, x := range *c {
for v := range x {
images[i] = append(images[i], v)
}
}
return images
}
+1 -34
View File
@@ -19,12 +19,6 @@ type Client struct {
type Options func(*Client)
func WithInClusterProvider() func(c *Client) {
return func(c *Client) {
c.provider = &DefaultProvider{provider: &InClusterProvider{}}
}
}
func WithKubeConfigProvider(kubeconfig string) func(c *Client) {
return func(c *Client) {
c.provider = &DefaultProvider{provider: &PathConfigProvider{kubecfg: kubeconfig}}
@@ -32,7 +26,7 @@ func WithKubeConfigProvider(kubeconfig string) func(c *Client) {
}
func New(opts ...Options) *Client {
c := &Client{}
c := &Client{provider: &DefaultProvider{provider: &InClusterProvider{}}}
for _, opt := range opts {
opt(c)
}
@@ -119,30 +113,3 @@ func (k PathConfigProvider) Provide() (*rest.Config, error) {
}
var _ ConfigProvider = &PathConfigProvider{}
type ImageCollector map[string]map[string]struct{}
func NewImageCollector() ImageCollector {
return make(map[string]map[string]struct{})
}
func (c *ImageCollector) Add(image string) {
parts := strings.Split(image[20:], ":")
if x, exists := (*c)[parts[0]]; exists {
x[parts[1]] = struct{}{}
} else {
(*c)[parts[0]] = map[string]struct{}{
parts[1]: {},
}
}
}
func (c *ImageCollector) Images() map[string][]string {
images := make(map[string][]string)
for i, x := range *c {
for v := range x {
images[i] = append(images[i], v)
}
}
return images
}
+359
View File
@@ -0,0 +1,359 @@
package kube
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"gitlab.com/unboundsoftware/apex-mocks"
v1 "k8s.io/api/apps/v1"
batchapiv1 "k8s.io/api/batch/v1"
v12 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
)
func TestClient_GetImages(t *testing.T) {
type fields struct {
provider ClientProvider
}
type args struct {
namespaces []string
}
tests := []struct {
name string
fields fields
args args
want map[string][]string
wantLogged []string
wantErr assert.ErrorAssertionFunc
}{
{
name: "error getting client",
fields: fields{
provider: MockClientProvider(func() (APIClient, error) {
return nil, fmt.Errorf("error")
}),
},
args: args{},
want: nil,
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
return assert.EqualError(t, err, "error")
},
},
{
name: "error fetching deployments",
fields: fields{
provider: MockClientProvider(func() (APIClient, error) {
return &MockAPIClient{
Apps: func() appsv1.AppsV1Interface {
return &MockApps{
DeploymentsFn: func(namespace string) appsv1.DeploymentInterface {
assert.Equal(t, "default", namespace)
return &MockDeployments{
ListFn: func(ctx context.Context, opts metav1.ListOptions) (*v1.DeploymentList, error) {
return nil, fmt.Errorf("error")
},
}
},
}
},
}, nil
}),
},
args: args{
namespaces: []string{"default"},
},
want: nil,
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
return assert.EqualError(t, err, "error")
},
},
{
name: "error fetching cron jobs",
fields: fields{
provider: MockClientProvider(func() (APIClient, error) {
return &MockAPIClient{
Apps: func() appsv1.AppsV1Interface {
return &MockApps{
DeploymentsFn: func(namespace string) appsv1.DeploymentInterface {
return &MockDeployments{
ListFn: func(ctx context.Context, opts metav1.ListOptions) (*v1.DeploymentList, error) {
return &v1.DeploymentList{}, nil
},
}
},
}
},
Batch: func() batchv1.BatchV1Interface {
return &MockBatch{
CronJobsFn: func(namespace string) batchv1.CronJobInterface {
assert.Equal(t, "default", namespace)
return &MockCronJobs{
ListFn: func(ctx context.Context, opts metav1.ListOptions) (*batchapiv1.CronJobList, error) {
return nil, fmt.Errorf("error")
},
}
},
}
},
}, nil
}),
},
args: args{
namespaces: []string{"default"},
},
want: nil,
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
return assert.EqualError(t, err, "error")
},
},
{
name: "no deployments or cronjobs",
fields: fields{
provider: MockClientProvider(func() (APIClient, error) {
return &MockAPIClient{
Apps: func() appsv1.AppsV1Interface {
return &MockApps{
DeploymentsFn: func(namespace string) appsv1.DeploymentInterface {
return &MockDeployments{
ListFn: func(ctx context.Context, opts metav1.ListOptions) (*v1.DeploymentList, error) {
return &v1.DeploymentList{}, nil
},
}
},
}
},
Batch: func() batchv1.BatchV1Interface {
return &MockBatch{
CronJobsFn: func(namespace string) batchv1.CronJobInterface {
return &MockCronJobs{
ListFn: func(ctx context.Context, opts metav1.ListOptions) (*batchapiv1.CronJobList, error) {
return &batchapiv1.CronJobList{}, nil
},
}
},
}
},
}, nil
}),
},
args: args{
namespaces: []string{"default"},
},
want: map[string][]string{},
wantErr: assert.NoError,
},
{
name: "deployments and cronjobs in multiple namespaces",
fields: fields{
provider: MockClientProvider(func() (APIClient, error) {
return &MockAPIClient{
Apps: func() appsv1.AppsV1Interface {
return &MockApps{
DeploymentsFn: func(namespace string) appsv1.DeploymentInterface {
return &MockDeployments{
ListFn: func(ctx context.Context, opts metav1.ListOptions) (*v1.DeploymentList, error) {
if namespace == "default" {
return &v1.DeploymentList{
Items: []v1.Deployment{
{
ObjectMeta: metav1.ObjectMeta{
Name: "some-deployment",
},
Spec: v1.DeploymentSpec{
Template: v12.PodTemplateSpec{
Spec: v12.PodSpec{
Containers: []v12.Container{
{
Image: "registry.gitlab.com/unboundsoftware/dummy:abc123",
},
},
},
},
},
},
},
}, nil
}
return &v1.DeploymentList{
Items: []v1.Deployment{
{
ObjectMeta: metav1.ObjectMeta{
Name: "other-deployment",
},
Spec: v1.DeploymentSpec{
Template: v12.PodTemplateSpec{
Spec: v12.PodSpec{
Containers: []v12.Container{
{
Image: "registry.gitlab.com/unboundsoftware/dummy:def456",
},
},
},
},
},
},
},
}, nil
},
}
},
}
},
Batch: func() batchv1.BatchV1Interface {
return &MockBatch{
CronJobsFn: func(namespace string) batchv1.CronJobInterface {
return &MockCronJobs{
ListFn: func(ctx context.Context, opts metav1.ListOptions) (*batchapiv1.CronJobList, error) {
if namespace == "other" {
return &batchapiv1.CronJobList{
Items: []batchapiv1.CronJob{
{
ObjectMeta: metav1.ObjectMeta{
Name: "some-cronjob",
},
Spec: batchapiv1.CronJobSpec{
JobTemplate: batchapiv1.JobTemplateSpec{
Spec: batchapiv1.JobSpec{
Template: v12.PodTemplateSpec{
Spec: v12.PodSpec{
Containers: []v12.Container{
{
Image: "registry.gitlab.com/unboundsoftware/other:xxx111",
},
},
},
},
},
},
},
},
},
}, nil
}
return &batchapiv1.CronJobList{}, nil
},
}
},
}
},
}, nil
}),
},
args: args{
namespaces: []string{"default", "other"},
},
want: map[string][]string{
"unboundsoftware/dummy": {"abc123", "def456"},
"unboundsoftware/other": {"xxx111"},
},
wantLogged: []string{
"info: Found image 'unboundsoftware/dummy:abc123' in deployment default.some-deployment",
"info: Found image 'unboundsoftware/dummy:def456' in deployment other.other-deployment",
"info: Found image 'unboundsoftware/other:xxx111' in cronjob other.some-cronjob",
},
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Client{
provider: tt.fields.provider,
}
logger := apex.New()
ctx := context.Background()
got, err := c.GetImages(ctx, logger, tt.args.namespaces)
if !tt.wantErr(t, err, fmt.Sprintf("GetImages(%v, %v, %v)", ctx, logger, tt.args.namespaces)) {
return
}
assert.Equalf(t, tt.want, got, "GetImages(%v, %v, %v)", ctx, logger, tt.args.namespaces)
logger.Check(t, tt.wantLogged)
})
}
}
type MockClientProvider func() (APIClient, error)
func (m MockClientProvider) Provide() (APIClient, error) {
return m()
}
type MockAPIClient struct {
Apps func() appsv1.AppsV1Interface
Batch func() batchv1.BatchV1Interface
}
func (m *MockAPIClient) AppsV1() appsv1.AppsV1Interface {
if m.Apps == nil {
return nil
}
return m.Apps()
}
func (m *MockAPIClient) BatchV1() batchv1.BatchV1Interface {
if m.Batch == nil {
return nil
}
return m.Batch()
}
var _ APIClient = &MockAPIClient{}
type MockApps struct {
appsv1.AppsV1Interface
DeploymentsFn func(namespace string) appsv1.DeploymentInterface
}
func (a *MockApps) Deployments(namespace string) appsv1.DeploymentInterface {
if a.DeploymentsFn == nil {
return nil
}
return a.DeploymentsFn(namespace)
}
var _ appsv1.AppsV1Interface = &MockApps{}
type MockDeployments struct {
appsv1.DeploymentInterface
ListFn func(ctx context.Context, opts metav1.ListOptions) (*v1.DeploymentList, error)
}
func (d *MockDeployments) List(ctx context.Context, opts metav1.ListOptions) (*v1.DeploymentList, error) {
if d.ListFn == nil {
return nil, nil
}
return d.ListFn(ctx, opts)
}
var _ appsv1.DeploymentInterface = &MockDeployments{}
type MockBatch struct {
batchv1.BatchV1Interface
CronJobsFn func(namespace string) batchv1.CronJobInterface
}
func (b *MockBatch) CronJobs(namespace string) batchv1.CronJobInterface {
if b.CronJobsFn == nil {
return nil
}
return b.CronJobsFn(namespace)
}
var _ batchv1.BatchV1Interface = &MockBatch{}
type MockCronJobs struct {
batchv1.CronJobInterface
ListFn func(ctx context.Context, opts metav1.ListOptions) (*batchapiv1.CronJobList, error)
}
func (m *MockCronJobs) List(ctx context.Context, opts metav1.ListOptions) (*batchapiv1.CronJobList, error) {
if m.ListFn == nil {
return nil, nil
}
return m.ListFn(ctx, opts)
}
var _ batchv1.CronJobInterface = &MockCronJobs{}