diff --git a/pkg/jobs/collector_test.go b/pkg/jobs/collector_test.go index c2a1619..942c082 100644 --- a/pkg/jobs/collector_test.go +++ b/pkg/jobs/collector_test.go @@ -1,11 +1,14 @@ package jobs import ( + "context" "reflect" "testing" trivy_checks "github.com/aquasecurity/trivy-checks" "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" ) func TestLoadCheckFilesByID(t *testing.T) { @@ -177,8 +180,8 @@ func TestFilterCommands(t *testing.T) { } } -func TestFilterCommandsByPlatform(t *testing.T) { - commandsK8s := []any{ +var ( + commandsK8s = []any{ map[string]interface{}{ "id": "CMD-0001", "title": "kubelet.conf file permissions", @@ -204,7 +207,7 @@ func TestFilterCommandsByPlatform(t *testing.T) { "platforms": []interface{}{"k8s"}, }, } - commandsRKE2 := []any{ + commandsRKE2 = []any{ map[string]interface{}{ "id": "CMD-0001", "title": "kubelet.conf file permissions", @@ -222,11 +225,13 @@ func TestFilterCommandsByPlatform(t *testing.T) { "platforms": []interface{}{"k8s", "rke2"}, }, } - commandsMap := map[string][]any{ + commandsMap = map[string][]any{ "k8s": commandsK8s, "rke2": commandsRKE2, } +) +func TestFilterCommandsByPlatform(t *testing.T) { tests := []struct { name string platform string @@ -273,3 +278,39 @@ func TestFilterCommandsByPlatform(t *testing.T) { }) } } + +func TestJobCollector_ApplyAndCollect(t *testing.T) { + nss := []string{"default", "kube-system"} + + tests := []struct { + name string + nodeName string + err error + want string + }{ + { + "success", + "node1", + nil, + "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + nsResource := k8s.NewMockNamespaceableResourceInterface(nss, test.err) + mockCluster := k8s.NewMockCluster(k8s.NewMockClusterDynamicClient(nsResource)) + mockCollector := NewCollector(mockCluster, + WithEmbeddedCommandFileSystem(trivy_checks.EmbeddedK8sCommandsFileSystem), + WithEmbeddedNodeConfigFilesystem(trivy_checks.EmbeddedConfigCommandsFileSystem)) + // gotCmd, gotCfg := getEmbeddedCommands(trivy_checks.EmbeddedK8sCommandsFileSystem, trivy_checks.EmbeddedConfigCommandsFileSystem, AddChecksByCheckId) + + got, err := mockCollector.ApplyAndCollect(context.TODO(), test.nodeName) + if err != test.err { + t.Errorf("expected error %v, got %v", test.err, err) + } + assert.Equal(t, got, test.want) + }) + } + +} diff --git a/pkg/k8s/mocks.go b/pkg/k8s/mocks.go new file mode 100644 index 0000000..f26c788 --- /dev/null +++ b/pkg/k8s/mocks.go @@ -0,0 +1,138 @@ +package k8s + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + + "github.com/aquasecurity/trivy-kubernetes/pkg/bom" + "github.com/aquasecurity/trivy-kubernetes/pkg/k8s/docker" +) + +type MockClusterDynamicClient struct { + resource dynamic.NamespaceableResourceInterface +} + +func NewMockClusterDynamicClient(resource dynamic.NamespaceableResourceInterface) *MockClusterDynamicClient { + return &MockClusterDynamicClient{ + resource: resource, + } +} +func (m MockClusterDynamicClient) Resource(schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { + return m.resource + +} + +type MockNamespaceableResourceInterface struct { + err error + namespaces []string +} + +func NewMockNamespaceableResourceInterface(namespaces []string, err error) *MockNamespaceableResourceInterface { + return &MockNamespaceableResourceInterface{ + err: err, + namespaces: namespaces, + } +} + +func (m MockNamespaceableResourceInterface) Namespace(s string) dynamic.ResourceInterface { + return nil +} + +func (m MockNamespaceableResourceInterface) Create(ctx context.Context, obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{}, nil +} + +func (m MockNamespaceableResourceInterface) Update(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{}, nil +} + +func (m MockNamespaceableResourceInterface) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{}, nil +} + +func (m MockNamespaceableResourceInterface) Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error { + return nil +} + +func (m MockNamespaceableResourceInterface) DeleteCollection(ctx context.Context, options metav1.DeleteOptions, listOptions metav1.ListOptions) error { + return nil +} + +func (m MockNamespaceableResourceInterface) Get(ctx context.Context, name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{}, nil +} + +func (m MockNamespaceableResourceInterface) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + return nil, nil +} + +func (m MockNamespaceableResourceInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{}, nil +} + +func (m MockNamespaceableResourceInterface) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{}, nil +} + +func (m MockNamespaceableResourceInterface) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{}, nil +} + +func (m MockNamespaceableResourceInterface) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { + if m.err != nil { + return nil, m.err + } + result := &unstructured.UnstructuredList{} + for _, namespace := range m.namespaces { + result.Items = append(result.Items, unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": namespace, + }, + }, + }) + } + return result, nil +} + +type MockCluster struct { + dynamicClient dynamic.Interface +} + +func NewMockCluster(dynamicClient dynamic.Interface) *MockCluster { + return &MockCluster{ + dynamicClient: dynamicClient, + } +} + +// GetDynamicClient returns dynamic.Interface +func (m *MockCluster) GetDynamicClient() dynamic.Interface { + return m.dynamicClient +} + +// Stub methods to satisfy the Cluster interface +func (m *MockCluster) GetCurrentContext() string { return "" } +func (m *MockCluster) GetCurrentNamespace() string { return "" } +func (m *MockCluster) GetK8sClientSet() kubernetes.Interface { + return fake.NewClientset() +} +func (m *MockCluster) GetGVRs(bool, []string) ([]schema.GroupVersionResource, error) { return nil, nil } +func (m *MockCluster) GetGVR(string) (schema.GroupVersionResource, error) { + return schema.GroupVersionResource{}, nil +} +func (m *MockCluster) CreateClusterBom(ctx context.Context) (*bom.Result, error) { return nil, nil } +func (m *MockCluster) GetClusterVersion() string { return "" } +func (m *MockCluster) AuthByResource(resource unstructured.Unstructured) (map[string]docker.Auth, error) { + return nil, nil +} +func (m *MockCluster) Platform() Platform { + return Platform{Name: "k8s", Version: "1.23.0"} +} diff --git a/pkg/trivyk8s/trivyk8s_test.go b/pkg/trivyk8s/trivyk8s_test.go index 5681904..1aacdf6 100644 --- a/pkg/trivyk8s/trivyk8s_test.go +++ b/pkg/trivyk8s/trivyk8s_test.go @@ -1,132 +1,17 @@ package trivyk8s import ( - "context" "fmt" "testing" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - - "github.com/stretchr/testify/assert" "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" - "github.com/aquasecurity/trivy-kubernetes/pkg/bom" "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" - "github.com/aquasecurity/trivy-kubernetes/pkg/k8s/docker" ) -type MockClusterDynamicClient struct { - resource dynamic.NamespaceableResourceInterface -} - -func (m MockClusterDynamicClient) Resource(schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { - return m.resource - -} - -type MockNamespaceableResourceInterface struct { - err error - namespaces []string -} - -func (m MockNamespaceableResourceInterface) Namespace(s string) dynamic.ResourceInterface { - return nil -} - -func (m MockNamespaceableResourceInterface) Create(ctx context.Context, obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) { - return &unstructured.Unstructured{}, nil -} - -func (m MockNamespaceableResourceInterface) Update(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) { - return &unstructured.Unstructured{}, nil -} - -func (m MockNamespaceableResourceInterface) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error) { - return &unstructured.Unstructured{}, nil -} - -func (m MockNamespaceableResourceInterface) Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error { - return nil -} - -func (m MockNamespaceableResourceInterface) DeleteCollection(ctx context.Context, options metav1.DeleteOptions, listOptions metav1.ListOptions) error { - return nil -} - -func (m MockNamespaceableResourceInterface) Get(ctx context.Context, name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { - return &unstructured.Unstructured{}, nil -} - -func (m MockNamespaceableResourceInterface) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - return nil, nil -} - -func (m MockNamespaceableResourceInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) { - return &unstructured.Unstructured{}, nil -} - -func (m MockNamespaceableResourceInterface) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) { - return &unstructured.Unstructured{}, nil -} - -func (m MockNamespaceableResourceInterface) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error) { - return &unstructured.Unstructured{}, nil -} - -func (m MockNamespaceableResourceInterface) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { - if m.err != nil { - return nil, m.err - } - result := &unstructured.UnstructuredList{} - for _, namespace := range m.namespaces { - result.Items = append(result.Items, unstructured.Unstructured{ - Object: map[string]interface{}{ - "metadata": map[string]interface{}{ - "name": namespace, - }, - }, - }) - } - return result, nil -} - -type MockCluster struct { - dynamicClient dynamic.Interface -} - -func newMockCluster(dynamicClient dynamic.Interface) *MockCluster { - return &MockCluster{ - dynamicClient: dynamicClient, - } -} - -// GetDynamicClient returns dynamic.Interface -func (m *MockCluster) GetDynamicClient() dynamic.Interface { - return m.dynamicClient -} - -// Stub methods to satisfy the Cluster interface -func (m *MockCluster) GetCurrentContext() string { return "" } -func (m *MockCluster) GetCurrentNamespace() string { return "" } -func (m *MockCluster) GetK8sClientSet() *kubernetes.Clientset { return nil } -func (m *MockCluster) GetGVRs(bool, []string) ([]schema.GroupVersionResource, error) { return nil, nil } -func (m *MockCluster) GetGVR(string) (schema.GroupVersionResource, error) { - return schema.GroupVersionResource{}, nil -} -func (m *MockCluster) CreateClusterBom(ctx context.Context) (*bom.Result, error) { return nil, nil } -func (m *MockCluster) GetClusterVersion() string { return "" } -func (m *MockCluster) AuthByResource(resource unstructured.Unstructured) (map[string]docker.Auth, error) { - return nil, nil -} -func (m *MockCluster) Platform() k8s.Platform { return k8s.Platform{} } - func TestGetNamespaces(t *testing.T) { tests := []struct { name string @@ -192,15 +77,12 @@ func TestGetNamespaces(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + nsResource := k8s.NewMockNamespaceableResourceInterface(tt.mockNamespaces, tt.mockError) + client := &client{ includeNamespaces: tt.includeNamespaces, excludeNamespaces: tt.excludeNamespaces, - cluster: newMockCluster(MockClusterDynamicClient{ - resource: MockNamespaceableResourceInterface{ - err: tt.mockError, - namespaces: tt.mockNamespaces, - }, - }), + cluster: k8s.NewMockCluster(k8s.NewMockClusterDynamicClient(nsResource)), } // Run the test