package kube import ( "context" "fmt" "sort" "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 } for k, v := range got { sort.SliceStable(v, func(i, j int) bool { return v[i] < v[j] }) got[k] = v } 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{}