From 08516561f7bfb0094b12cfff37f0f05f4b35675c Mon Sep 17 00:00:00 2001 From: adisos Date: Thu, 2 Nov 2023 15:16:59 +0200 Subject: [PATCH] updates + fix lint issue Signed-off-by: adisos --- pkg/netpol/connlist/connlist.go | 25 ++- pkg/netpol/connlist/connlist_test.go | 131 +++++++++------ pkg/netpol/diff/diff.go | 18 +- pkg/netpol/eval/resources.go | 1 - pkg/netpol/internal/testutils/utils.go | 3 +- pkg/netpol/manifests/manifests.go | 2 +- pkg/netpol/manifests/manifests_test.go | 5 +- pkg/netpol/scan/scan.go | 224 ++++++++++++++----------- 8 files changed, 238 insertions(+), 171 deletions(-) diff --git a/pkg/netpol/connlist/connlist.go b/pkg/netpol/connlist/connlist.go index cfba4c02..a5f61375 100644 --- a/pkg/netpol/connlist/connlist.go +++ b/pkg/netpol/connlist/connlist.go @@ -51,10 +51,14 @@ func (ca *ConnlistAnalyzer) ConnlistFromResourceInfos(info []*resource.Info) ([] objs, fpErrs := scan.ResourceInfoListToK8sObjectsList(info, ca.logger) /* Examples for possible errors (non fatal) returned from this call (ResourceInfoListToK8sObjectsList): - (1) (Warning) malformed k8s resource manifest: "in file: tests\malformed_pod_example\pod.yaml, YAML document is malformed: unrecognized type: int32" - (2) (Warning) malformed k8s resource manifest: "in file: tests\malformed-pod-example-2\pod_list.json, YAML document is malformed: cannot restore slice from map" - (3) (Warning) no network policy resources found: (tests/malformed_pod_example): "no relevant Kubernetes network policy resources found" - (4) (Error) no workload resources found: (tests/malformed_pod_example/) : "no relevant Kubernetes workload resources found" + (1) (Warning) malformed k8s resource manifest: "in file: tests\malformed_pod_example\pod.yaml, + YAML document is malformed: unrecognized type: int32" + (2) (Warning) malformed k8s resource manifest: "in file: tests\malformed-pod-example-2\pod_list.json, + YAML document is malformed:cannot restore slice from map" + (3) (Warning) no network policy resources found: (tests/malformed_pod_example): + "no relevant Kubernetes network policy resources found" + (4) (Error) no workload resources found: (tests/malformed_pod_example/) : + "no relevant Kubernetes workload resources found" Examples for Log Infos that can be printed from this call: (1) (Info) in file: tests/bad_yamls/irrelevant_k8s_resources.yaml, skipping object with type: IngressClass @@ -92,10 +96,14 @@ func (ca *ConnlistAnalyzer) ConnlistFromDirPath(dirPath string) ([]Peer2PeerConn /* Examples for possible errors returned from this call (GetResourceInfos): (1) dir does not exist: "Error: the path "tests/bad_yamls/subdir5" does not exist" - (2) empty dir : "Error: error reading [tests/bad_yamls/subdir2/]: recognized file extensions are [.json .yaml .yml]" - (3) irrelevant JSON : "GetResourceInfos error: unable to decode "tests\\onlineboutique\\connlist_output.json": json: cannot unmarshal array into Go value of type unstructured.detector" - (4) bad JSON/YAML - missing kind : "Error: unable to decode "tests\\malformed-pod-example-4\\pods.json": Object 'Kind' is missing in '{ ... }" - (5) YAML doc with syntax error: "error parsing tests/bad_yamls/document_with_syntax_error.yaml: error converting YAML to JSON: yaml: line 19: found character that cannot start any token" + (2) empty dir : "Error: error reading [tests/bad_yamls/subdir2/]: recognized file + extensions are [.json .yaml .yml]" + (3) irrelevant JSON : "GetResourceInfos error: unable to decode "tests\\onlineboutique\\connlist_output.json": + json: cannot unmarshal array into Go value of type unstructured.detector" + (4) bad JSON/YAML - missing kind : "Error: unable to decode "tests\\malformed-pod-example-4\\pods.json": + Object 'Kind' is missing in '{ ... }" + (5) YAML doc with syntax error: "error parsing tests/bad_yamls/document_with_syntax_error.yaml: + error converting YAML to JSON: yaml: line 19: found character that cannot start any token" */ if len(rList) == 0 || ca.stopOnError { @@ -162,7 +170,6 @@ func NewConnlistAnalyzer(options ...ConnlistAnalyzerOption) *ConnlistAnalyzer { for _, o := range options { o(ca) } - //ca.scanner = scan.NewResourcesScanner(ca.logger, ca.stopOnError, ca.walkFn) return ca } diff --git a/pkg/netpol/connlist/connlist_test.go b/pkg/netpol/connlist/connlist_test.go index 753e4aec..55c0c748 100644 --- a/pkg/netpol/connlist/connlist_test.go +++ b/pkg/netpol/connlist/connlist_test.go @@ -32,10 +32,14 @@ interfaces to test: /* ConnlistFromResourceInfos: Examples for possible errors (non fatal) returned from this call (ResourceInfoListToK8sObjectsList): - (1) (Warning) malformed k8s resource manifest: "in file: tests\malformed_pod_example\pod.yaml, YAML document is malformed: unrecognized type: int32" - (2) (Warning) malformed k8s resource manifest: "in file: tests\malformed-pod-example-2\pod_list.json, YAML document is malformed: cannot restore slice from map" - (3) (Warning) no network policy resources found: (tests/malformed_pod_example): "no relevant Kubernetes network policy resources found" - (4) (Error) no workload resources found: (tests/malformed_pod_example/) : "no relevant Kubernetes workload resources found" + (1) (Warning) malformed k8s resource manifest: "in file: tests\malformed_pod_example\pod.yaml, + YAML document is malformed: unrecognized type: int32" + (2) (Warning) malformed k8s resource manifest: "in file: tests\malformed-pod-example-2\pod_list.json, + YAML document is malformed: cannot restore slice from map" + (3) (Warning) no network policy resources found: (tests/malformed_pod_example): + "no relevant Kubernetes network policy resources found" + (4) (Error) no workload resources found: (tests/malformed_pod_example/) : + "no relevant Kubernetes workload resources found" Examples for Log Infos that can be printed from this call: (1) (Info) in file: tests/bad_yamls/irrelevant_k8s_resources.yaml, skipping object with type: IngressClass @@ -51,10 +55,14 @@ interfaces to test: ConnlistFromDirPath: Examples for possible errors returned from this call (GetResourceInfos): (1) dir does not exist: "Error: the path "tests/bad_yamls/subdir5" does not exist" - (2) empty dir : "Error: error reading [tests/bad_yamls/subdir2/]: recognized file extensions are [.json .yaml .yml]" - (3) irrelevant JSON : "GetResourceInfos error: unable to decode "tests\\onlineboutique\\connlist_output.json": json: cannot unmarshal array into Go value of type unstructured.detector" - (4) bad JSON/YAML - missing kind : "Error: unable to decode "tests\\malformed-pod-example-4\\pods.json": Object 'Kind' is missing in '{ ... }" - (5) YAML doc with syntax error: "error parsing tests/bad_yamls/document_with_syntax_error.yaml: error converting YAML to JSON: yaml: line 19: found character that cannot start any token" + (2) empty dir : "Error: error reading [tests/bad_yamls/subdir2/]: recognized file + extensions are [.json .yaml .yml]" + (3) irrelevant JSON : "GetResourceInfos error: unable to decode "tests\\onlineboutique\\connlist_output.json": + json: cannot unmarshal array into Go value of type unstructured.detector" + (4) bad JSON/YAML - missing kind : "Error: unable to decode "tests\\malformed-pod-example-4\\pods.json": + Object 'Kind' is missing in '{ ... }" + (5) YAML doc with syntax error: "error parsing tests/bad_yamls/document_with_syntax_error.yaml: error + converting YAML to JSON: yaml: line 19: found character that cannot start any token" */ @@ -68,12 +76,12 @@ func TestConnListFromDir(t *testing.T) { t.Run(tt.testDirName, func(t *testing.T) { t.Parallel() for _, format := range tt.outputFormats { - testName, testInfo, dirPath, expectedOutputFileName, analyzer := prepareTest(tt.testDirName, tt.focusWorkload, format) - res, _, err := analyzer.ConnlistFromDirPath(dirPath) - require.Nil(t, err, testInfo) - output, err := analyzer.ConnectionsListToString(res) - require.Nil(t, err, testInfo) - testutils.CheckActualVsExpectedOutputMatch(t, testName, tt.testDirName, expectedOutputFileName, output, format) + pTest := prepareTest(tt.testDirName, tt.focusWorkload, format) + res, _, err := pTest.analyzer.ConnlistFromDirPath(pTest.dirPath) + require.Nil(t, err, pTest.testInfo) + output, err := pTest.analyzer.ConnectionsListToString(res) + require.Nil(t, err, pTest.testInfo) + testutils.CheckActualVsExpectedOutputMatch(t, pTest.testName, tt.testDirName, pTest.expectedOutputFileName, output, format) } }) } @@ -86,16 +94,17 @@ func TestConnListFromResourceInfos(t *testing.T) { t.Run(tt.testDirName, func(t *testing.T) { t.Parallel() for _, format := range tt.outputFormats { - testName, testInfo, dirPath, expectedOutputFileName, analyzer := prepareTest(tt.testDirName, tt.focusWorkload, format) - infos, _ := manifests.GetResourceInfosFromDirPath([]string{dirPath}, true, false) - //require.Empty(t, errs, testInfo) - TODO: add info about expected errors from each test here (these errors do not stop the analysis or affect the output) - //more suitable to test this in a separate package (manifests) where GetResourceInfosFromDirPath is implemented - res, _, err := analyzer.ConnlistFromResourceInfos(infos) - require.Nil(t, err, testInfo) - output, err := analyzer.ConnectionsListToString(res) - require.Nil(t, err, testInfo) + pTest := prepareTest(tt.testDirName, tt.focusWorkload, format) + infos, _ := manifests.GetResourceInfosFromDirPath([]string{pTest.dirPath}, true, false) + // require.Empty(t, errs, testInfo) - TODO: add info about expected errors + // from each test here (these errors do not stop the analysis or affect the output) + // more suitable to test this in a separate package (manifests) where GetResourceInfosFromDirPath is implemented + res, _, err := pTest.analyzer.ConnlistFromResourceInfos(infos) + require.Nil(t, err, pTest.testInfo) + output, err := pTest.analyzer.ConnectionsListToString(res) + require.Nil(t, err, pTest.testInfo) // TODO: send testInfo instead of format to CheckActualVsExpectedOutputMatch - testutils.CheckActualVsExpectedOutputMatch(t, testName, tt.testDirName, expectedOutputFileName, output, format) + testutils.CheckActualVsExpectedOutputMatch(t, pTest.testName, tt.testDirName, pTest.expectedOutputFileName, output, format) } }) } @@ -165,7 +174,12 @@ func TestConnlistAnalyzeFatalErrors(t *testing.T) { } } -func testFatalErr(t *testing.T, connsRes []Peer2PeerConnection, peersRes []Peer, err error, testName string, errStr string, analyzer *ConnlistAnalyzer) { +func testFatalErr(t *testing.T, + connsRes []Peer2PeerConnection, + peersRes []Peer, + err error, + testName, errStr string, + analyzer *ConnlistAnalyzer) { require.Empty(t, connsRes, testName) require.Empty(t, peersRes, testName) testutils.CheckErrorContainment(t, testName, errStr, err.Error(), true) @@ -173,16 +187,17 @@ func testFatalErr(t *testing.T, connsRes []Peer2PeerConnection, peersRes []Peer, testutils.CheckErrorContainment(t, testName, errStr, analyzer.errors[0].Error().Error(), true) } -func getAnalysisResFromAPI(apiName, dirName, focusWorkload string) (analyzer *ConnlistAnalyzer, connsRes []Peer2PeerConnection, peersRes []Peer, err error) { - _, _, dirPath, _, analyzer := prepareTest(dirName, focusWorkload, common.DefaultFormat) +func getAnalysisResFromAPI(apiName, dirName, focusWorkload string) ( + analyzer *ConnlistAnalyzer, connsRes []Peer2PeerConnection, peersRes []Peer, err error) { + pTest := prepareTest(dirName, focusWorkload, common.DefaultFormat) switch apiName { - case "ConnlistFromResourceInfos": - infos, _ := manifests.GetResourceInfosFromDirPath([]string{dirPath}, true, false) - connsRes, peersRes, err = analyzer.ConnlistFromResourceInfos(infos) - case "ConnlistFromDirPath": - connsRes, peersRes, err = analyzer.ConnlistFromDirPath(dirPath) + case ResourceInfosFunc: + infos, _ := manifests.GetResourceInfosFromDirPath([]string{pTest.dirPath}, true, false) + connsRes, peersRes, err = pTest.analyzer.ConnlistFromResourceInfos(infos) + case DirPathFunc: + connsRes, peersRes, err = pTest.analyzer.ConnlistFromDirPath(pTest.dirPath) } - return analyzer, connsRes, peersRes, err + return pTest.analyzer, connsRes, peersRes, err } // severe errors and warnings, common for both interfaces (ConnlistFromDirPath & ConnlistFromResourceInfos) @@ -193,13 +208,13 @@ func getAnalysisResFromAPI(apiName, dirName, focusWorkload string) (analyzer *Co func TestConnlistAnalyzeSevereErrorsAndWarnings(t *testing.T) { t.Parallel() cases := []struct { - name string - dirName string - //expectedErrNumWithoutStopOnErr int - //expectedErrNumWithStopOnErr int + name string + dirName string firstErrStrContains string emptyRes bool focusWorkload string + /*expectedErrNumWithoutStopOnErr int + expectedErrNumWithStopOnErr int*/ }{ { @@ -216,7 +231,8 @@ func TestConnlistAnalyzeSevereErrorsAndWarnings(t *testing.T) { }, /* $ ./bin/k8snetpolicy list --dirpath tests/malformed_pod_example/ - 2023/11/02 08:56:16 : err : in file: tests\malformed_pod_example\pod.yaml YAML document is malformed: error for resource with kind: Pod , name: nginx , : unrecognized type: int32 + 2023/11/02 08:56:16 : err : in file: tests\malformed_pod_example\pod.yaml YAML document is malformed: + error for resource with kind: Pod , name: nginx , : unrecognized type: int32 2023/11/02 08:56:16 : no relevant Kubernetes workload resources found 2023/11/02 08:56:16 no relevant Kubernetes network policy resources found @@ -458,7 +474,7 @@ func appendFocusWorkloadOptIfRequired(focusWorkload string) []ConnlistAnalyzerOp return analyzerOptions } -func testNameByTestType(dirName, focusWorkload, format string) (testName string, expectedOutputFileName string) { +func testNameByTestType(dirName, focusWorkload, format string) (testName, expectedOutputFileName string) { switch { case focusWorkload == "": return dirName, connlistExpectedOutputFileNamePrefix + format @@ -471,12 +487,21 @@ func testNameByTestType(dirName, focusWorkload, format string) (testName string, return "", "" } -func prepareTest(dirName, focusWorkload, format string) (testName, testInfo, dirPath, expectedOutputFileName string, analyzer *ConnlistAnalyzer) { - testName, expectedOutputFileName = testNameByTestType(dirName, focusWorkload, format) - testInfo = testutils.GetDebugMsgWithTestNameAndFormat(testName, format) - analyzer = NewConnlistAnalyzer(WithOutputFormat(format), WithFocusWorkload(focusWorkload)) - dirPath = getDirPathFromDirName(dirName) - return testName, testInfo, dirPath, expectedOutputFileName, analyzer +type preparedTest struct { + testName string + testInfo string + dirPath string + expectedOutputFileName string + analyzer *ConnlistAnalyzer +} + +func prepareTest(dirName, focusWorkload, format string) preparedTest { + res := preparedTest{} + res.testName, res.expectedOutputFileName = testNameByTestType(dirName, focusWorkload, format) + res.testInfo = testutils.GetDebugMsgWithTestNameAndFormat(res.testName, format) + res.analyzer = NewConnlistAnalyzer(WithOutputFormat(format), WithFocusWorkload(focusWorkload)) + res.dirPath = getDirPathFromDirName(dirName) + return res } // fatal errors for interface ConnectionsListToString @@ -502,30 +527,30 @@ func TestConnlistOutputFatalErrors(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, _, dirPath, _, analyzer := prepareTest(tt.dirName, "", tt.format) - connsRes, peersRes, err := analyzer.ConnlistFromDirPath(dirPath) + preparedTest := prepareTest(tt.dirName, "", tt.format) + connsRes, peersRes, err := preparedTest.analyzer.ConnlistFromDirPath(preparedTest.dirPath) require.Nil(t, err, tt.name) // "unable to decode ... connlist_output.json" - require.Equal(t, len(analyzer.errors), 1, "expecting error since builder not able to parse connlist_output.json") + require.Equal(t, len(preparedTest.analyzer.errors), 1, "expecting error since builder not able to parse connlist_output.json") require.NotEmpty(t, connsRes, "expecting non-empty analysis res") require.NotEmpty(t, peersRes, "expecting non-empty analysis res") - output, err := analyzer.ConnectionsListToString(connsRes) + output, err := preparedTest.analyzer.ConnectionsListToString(connsRes) require.Empty(t, output, tt.name) testutils.CheckErrorContainment(t, tt.name, tt.errorStrContains, err.Error(), true) // re-run the test with new analyzer (to clear the analyzer.errors array ) - _, _, dirPath, _, analyzer = prepareTest(tt.dirName, "", tt.format) - infos, _ := manifests.GetResourceInfosFromDirPath([]string{dirPath}, true, false) - connsRes2, peersRes2, err2 := analyzer.ConnlistFromResourceInfos(infos) + preparedTest = prepareTest(tt.dirName, "", tt.format) + infos, _ := manifests.GetResourceInfosFromDirPath([]string{preparedTest.dirPath}, true, false) + connsRes2, peersRes2, err2 := preparedTest.analyzer.ConnlistFromResourceInfos(infos) require.Nil(t, err2, tt.name) - require.Empty(t, analyzer.errors, "expecting no errors from ConnlistFromResourceInfos") + require.Empty(t, preparedTest.analyzer.errors, "expecting no errors from ConnlistFromResourceInfos") require.NotEmpty(t, connsRes2, "expecting non-empty analysis res") require.NotEmpty(t, peersRes2, "expecting non-empty analysis res") - output, err2 = analyzer.ConnectionsListToString(connsRes) + output, err2 = preparedTest.analyzer.ConnectionsListToString(connsRes) require.Empty(t, output, tt.name) testutils.CheckErrorContainment(t, tt.name, tt.errorStrContains, err2.Error(), true) }) diff --git a/pkg/netpol/diff/diff.go b/pkg/netpol/diff/diff.go index aac81a3b..9d550994 100644 --- a/pkg/netpol/diff/diff.go +++ b/pkg/netpol/diff/diff.go @@ -9,13 +9,14 @@ import ( "errors" "path/filepath" + "k8s.io/cli-runtime/pkg/resource" + "github.com/np-guard/netpol-analyzer/pkg/netpol/common" "github.com/np-guard/netpol-analyzer/pkg/netpol/connlist" "github.com/np-guard/netpol-analyzer/pkg/netpol/eval" "github.com/np-guard/netpol-analyzer/pkg/netpol/logger" "github.com/np-guard/netpol-analyzer/pkg/netpol/manifests" "github.com/np-guard/netpol-analyzer/pkg/netpol/scan" - "k8s.io/cli-runtime/pkg/resource" utilerrors "k8s.io/apimachinery/pkg/util/errors" ) @@ -119,7 +120,8 @@ func (da *DiffAnalyzer) determineConnlistAnalyzerOptionsForDiffAnalysis() []conn // returns results from calling connlist analyzer ConnlistFromDirPath for the given dir path // and appends the connlist analyzers' errors to diffError -/*func (da *DiffAnalyzer) getConnsAndWorkloadsFromDir(caAnalyzer *connlist.ConnlistAnalyzer, dirPath string) ([]connlist.Peer2PeerConnection, +/*func (da *DiffAnalyzer) getConnsAndWorkloadsFromDir( + caAnalyzer *connlist.ConnlistAnalyzer, dirPath string) ([]connlist.Peer2PeerConnection, []connlist.Peer, error) { conns, workloads, err := caAnalyzer.ConnlistFromDirPath(dirPath) if err != nil { @@ -130,8 +132,12 @@ func (da *DiffAnalyzer) determineConnlistAnalyzerOptionsForDiffAnalysis() []conn return conns, workloads, nil }*/ -func (da *DiffAnalyzer) getConnsAndWorkloadsFromResourceInfos(caAnalyzer *connlist.ConnlistAnalyzer, infos []*resource.Info) ([]connlist.Peer2PeerConnection, - []connlist.Peer, error) { +func (da *DiffAnalyzer) getConnsAndWorkloadsFromResourceInfos( + caAnalyzer *connlist.ConnlistAnalyzer, + infos []*resource.Info) ( + []connlist.Peer2PeerConnection, + []connlist.Peer, + error) { conns, workloads, err := caAnalyzer.ConnlistFromResourceInfos(infos) if err != nil { // append all fatal/severe errors and warnings returned by caAnalyzer then return because of the fatal err @@ -201,11 +207,11 @@ func (da *DiffAnalyzer) ConnDiffFromDirPaths(dirPath1, dirPath2 string) (Connect // split err if it's an aggregated error to a list of separate errors for _, err := range errs1 { - da.logger.Warnf("GetResourceInfos error: %s", err) // print to log the error from builder + da.logger.Warnf(err.Error()) // print to log the error from builder da.errors = append(da.errors, scan.FailedReadingFile(dirPath1, err)) // add the error from builder to accumulated errors } for _, err := range errs2 { - da.logger.Warnf("GetResourceInfos error: %s", err) // print to log the error from builder + da.logger.Warnf(err.Error()) // print to log the error from builder da.errors = append(da.errors, scan.FailedReadingFile(dirPath1, err)) // add the error from builder to accumulated errors } } diff --git a/pkg/netpol/eval/resources.go b/pkg/netpol/eval/resources.go index d3474f57..f4a2d7b9 100644 --- a/pkg/netpol/eval/resources.go +++ b/pkg/netpol/eval/resources.go @@ -79,7 +79,6 @@ func NewPolicyEngineWithObjects(objects []scan.K8sObject) (*PolicyEngine, error) case scan.Service, scan.Route, scan.Ingress: continue default: - //err = fmt.Errorf("unsupported kind: %s", obj.Kind) fmt.Printf("ignoring resource kind %s", obj.Kind) } if err != nil { diff --git a/pkg/netpol/internal/testutils/utils.go b/pkg/netpol/internal/testutils/utils.go index f57b562b..8be881d3 100644 --- a/pkg/netpol/internal/testutils/utils.go +++ b/pkg/netpol/internal/testutils/utils.go @@ -47,7 +47,8 @@ func CheckActualVsExpectedOutputMatch(t *testing.T, testName, dirName, expectedO err := common.WriteToFile(actualOutput, actualOutputFile) require.Nil(t, err, GetDebugMsgWithTestNameAndFormat(testName, format)) } - require.Equal(t, cleanStr(string(expectedOutput)), cleanStr(actualOutput), "output mismatch for %s, actual output file %q vs expected output file: %q", + require.Equal(t, cleanStr(string(expectedOutput)), cleanStr(actualOutput), + "output mismatch for %s, actual output file %q vs expected output file: %q", GetDebugMsgWithTestNameAndFormat(testName, format), actualOutputFile, expectedOutputFile) } diff --git a/pkg/netpol/manifests/manifests.go b/pkg/netpol/manifests/manifests.go index 991ee9f1..10aad5ad 100644 --- a/pkg/netpol/manifests/manifests.go +++ b/pkg/netpol/manifests/manifests.go @@ -6,7 +6,7 @@ import ( ) // GetResourceInfosFromDirPath returns a list of resource.Info objects from input paths to scan -func GetResourceInfosFromDirPath(paths []string, recursive bool, stopOnErr bool) ([]*resource.Info, []error) { +func GetResourceInfosFromDirPath(paths []string, recursive, stopOnErr bool) ([]*resource.Info, []error) { fileOption := resource.FilenameOptions{Filenames: paths, Recursive: recursive} builder := getResourceBuilder(stopOnErr, fileOption) resourceResult := builder.Do() diff --git a/pkg/netpol/manifests/manifests_test.go b/pkg/netpol/manifests/manifests_test.go index 6609c45d..2eab6fdc 100644 --- a/pkg/netpol/manifests/manifests_test.go +++ b/pkg/netpol/manifests/manifests_test.go @@ -5,10 +5,11 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "github.com/np-guard/netpol-analyzer/pkg/netpol/internal/testutils" "github.com/np-guard/netpol-analyzer/pkg/netpol/logger" "github.com/np-guard/netpol-analyzer/pkg/netpol/scan" - "github.com/stretchr/testify/require" ) func TestBasic(t *testing.T) { @@ -18,7 +19,7 @@ func TestBasic(t *testing.T) { // TODO: move the code below to scan pkg oList, _ := scan.ResourceInfoListToK8sObjectsList(rList, logger.NewDefaultLogger()) - //require.Nil(t, err, "err ResourceInfoToK8sObjects") + // require.Nil(t, err, "err ResourceInfoToK8sObjects") require.Equal(t, len(oList), len(rList), "expecting same length fot input and output lists") fmt.Println("done") } diff --git a/pkg/netpol/scan/scan.go b/pkg/netpol/scan/scan.go index f760277b..87b05923 100644 --- a/pkg/netpol/scan/scan.go +++ b/pkg/netpol/scan/scan.go @@ -92,17 +92,117 @@ type K8sObject struct { Daemonset *appsv1.DaemonSet } +func (k *K8sObject) getEmptyInitializedFieldObjByKind(kind string) interface{} { + switch kind { + case Deployment: + k.Deployment = &appsv1.Deployment{} + return k.Deployment + case Daemonset: + k.Daemonset = &appsv1.DaemonSet{} + return k.Daemonset + case ReplicaSet: + k.Replicaset = &appsv1.ReplicaSet{} + return k.Replicaset + case Statefulset: + k.Statefulset = &appsv1.StatefulSet{} + return k.Statefulset + case ReplicationController: + k.ReplicationController = &v1.ReplicationController{} + return k.ReplicationController + case Job: + k.Job = &batchv1.Job{} + return k.Job + case CronJob: + k.CronJob = &batchv1.CronJob{} + return k.CronJob + case Route: + k.Route = &ocroutev1.Route{} + return k.Route + case Ingress: + k.Ingress = &netv1.Ingress{} + return k.Ingress + case Service: + k.Service = &v1.Service{} + return k.Service + case Pod: + k.Pod = &v1.Pod{} + return k.Pod + case Networkpolicy: + k.Networkpolicy = &netv1.NetworkPolicy{} + return k.Networkpolicy + case Namespace: + k.Namespace = &v1.Namespace{} + return k.Namespace + } + return nil +} + +//gocyclo:ignore +func (k *K8sObject) initDefaultNamespace() { + switch k.Kind { + case Deployment: + if k.Deployment.Namespace == "" { + k.Deployment.Namespace = metav1.NamespaceDefault + } + case Daemonset: + if k.Daemonset.Namespace == "" { + k.Daemonset.Namespace = metav1.NamespaceDefault + } + case ReplicaSet: + if k.Replicaset.Namespace == "" { + k.Replicaset.Namespace = metav1.NamespaceDefault + } + case Statefulset: + if k.Statefulset.Namespace == "" { + k.Statefulset.Namespace = metav1.NamespaceDefault + } + case ReplicationController: + if k.ReplicationController.Namespace == "" { + k.ReplicationController.Namespace = metav1.NamespaceDefault + } + case Job: + if k.Job.Namespace == "" { + k.Job.Namespace = metav1.NamespaceDefault + } + case CronJob: + if k.CronJob.Namespace == "" { + k.CronJob.Namespace = metav1.NamespaceDefault + } + case Route: + if k.Route.Namespace == "" { + k.Route.Namespace = metav1.NamespaceDefault + } + case Ingress: + if k.Ingress.Namespace == "" { + k.Ingress.Namespace = metav1.NamespaceDefault + } + case Service: + if k.Service.Namespace == "" { + k.Service.Namespace = metav1.NamespaceDefault + } + case Pod: + if k.Pod.Namespace == "" { + k.Pod.Namespace = metav1.NamespaceDefault + } + checkAndUpdatePodStatusIPsFields(k.Pod) + case Networkpolicy: + if k.Networkpolicy.Namespace == "" { + k.Networkpolicy.Namespace = metav1.NamespaceDefault + } + } +} + // ResourceInfoListToK8sObjectsList returns a list of K8sObject and list of FileProcessingError objects from analyzing // an input list of resource.Info objects. Irrelevant resources are skipped. // Possible errs/warnings as FileProcessingError: // malformedYamlDoc , noK8sWorkloadResourcesFound, noK8sNetworkPolicyResourcesFound -func ResourceInfoListToK8sObjectsList(infosList []*resource.Info, logger logger.Logger) ([]K8sObject, []FileProcessingError) { +func ResourceInfoListToK8sObjectsList(infosList []*resource.Info, l logger.Logger) ([]K8sObject, []FileProcessingError) { res := make([]K8sObject, 0) fpErrList := []FileProcessingError{} var hasWorkloads, hasNetpols bool for _, info := range infosList { // fpErr can be malformedYamlDoc - k8sObj, fpErr, isWorkload, isNetpol := resourceInfoToK8sObject(info, logger) + k8sObj, fpErr, isWorkload, isNetpol := resourceInfoToK8sObject(info, l) if fpErr != nil { fpErrList = append(fpErrList, *fpErr) // no need to stop if stopOnErr was set, since malformedYamlDoc is a warning @@ -118,110 +218,31 @@ func ResourceInfoListToK8sObjectsList(infosList []*resource.Info, logger logger. } } if !hasWorkloads { - /*fpErrList = append(fpErrList, *MissingResources(true, false)) - LogError(logger, MissingResources(true, false))*/ - fpErrList = appendAndLogNewError(fpErrList, missingResources(true, false), logger) + fpErrList = appendAndLogNewError(fpErrList, missingResources(true, false), l) } if !hasNetpols { - fpErrList = appendAndLogNewError(fpErrList, missingResources(false, true), logger) - /*fpErrList = append(fpErrList, *MissingResources(false, true)) - LogError(logger, MissingResources(false, true))*/ + fpErrList = appendAndLogNewError(fpErrList, missingResources(false, true), l) } return res, fpErrList } // resourceInfoToK8sObject converts an input resource.Info object to a K8sObject -func resourceInfoToK8sObject(info *resource.Info, logger logger.Logger) (*K8sObject, *FileProcessingError, bool, bool) { +func resourceInfoToK8sObject(info *resource.Info, l logger.Logger) (*K8sObject, *FileProcessingError, bool, bool) { resObject := K8sObject{} - if unstructured, ok := info.Object.(*unstructured.Unstructured); ok { - resObject.Kind = unstructured.GetKind() + if unstructuredObj, ok := info.Object.(*unstructured.Unstructured); ok { + resObject.Kind = unstructuredObj.GetKind() var err error - switch resObject.Kind { - case Deployment: - resObject.Deployment = &appsv1.Deployment{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Deployment) - if err == nil && resObject.Deployment.Namespace == "" { - resObject.Deployment.Namespace = "default" - } - case Daemonset: - resObject.Daemonset = &appsv1.DaemonSet{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Daemonset) - if err == nil && resObject.Daemonset.Namespace == "" { - resObject.Daemonset.Namespace = "default" - } - case ReplicaSet: - resObject.Replicaset = &appsv1.ReplicaSet{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Replicaset) - if err == nil && resObject.Replicaset.Namespace == "" { - resObject.Replicaset.Namespace = "default" - } - case Statefulset: - resObject.Statefulset = &appsv1.StatefulSet{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Statefulset) - if err == nil && resObject.Statefulset.Namespace == "" { - resObject.Statefulset.Namespace = "default" - } - case ReplicationController: - resObject.ReplicationController = &v1.ReplicationController{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.ReplicationController) - if err == nil && resObject.ReplicationController.Namespace == "" { - resObject.ReplicationController.Namespace = "default" - } - case Job: - resObject.Job = &batchv1.Job{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Job) - if err == nil && resObject.Job.Namespace == "" { - resObject.Job.Namespace = "default" - } - case CronJob: - resObject.CronJob = &batchv1.CronJob{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.CronJob) - if err == nil && resObject.CronJob.Namespace == "" { - resObject.CronJob.Namespace = "default" - } - case Route: - resObject.Route = &ocroutev1.Route{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Route) - if err == nil && resObject.Route.Namespace == "" { - resObject.Route.Namespace = "default" - } - case Ingress: - resObject.Ingress = &netv1.Ingress{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Ingress) - if err == nil && resObject.Ingress.Namespace == "" { - resObject.Ingress.Namespace = "default" - } - case Service: - resObject.Service = &v1.Service{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Service) - if err == nil && resObject.Service.Namespace == "" { - resObject.Service.Namespace = "default" - } - case Pod: - resObject.Pod = &v1.Pod{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Pod) - if err == nil && resObject.Pod.Namespace == "" { - resObject.Pod.Namespace = "default" - } - checkAndUpdatePodStatusIPsFields(resObject.Pod) - case Networkpolicy: - resObject.Networkpolicy = &netv1.NetworkPolicy{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Networkpolicy) - if err == nil && resObject.Networkpolicy.Namespace == "" { - resObject.Networkpolicy.Namespace = "default" - } - case Namespace: - resObject.Namespace = &v1.Namespace{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, resObject.Namespace) - default: - logger.Infof("in file: %s, skipping object with type: %s", info.Source, unstructured.GetKind()) + objField := resObject.getEmptyInitializedFieldObjByKind(resObject.Kind) + if objField == nil { + l.Infof("in file: %s, skipping object with type: %s", info.Source, resObject.Kind) return nil, nil, false, false } + err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, objField) if err != nil { - kind := unstructured.GetKind() - name := unstructured.GetName() - namespace := unstructured.GetNamespace() + kind := unstructuredObj.GetKind() + name := unstructuredObj.GetName() + namespace := unstructuredObj.GetNamespace() // malformed k8s resource resourceStr := getResourceInfoStr(kind, name, namespace) errStr := "error for resource" @@ -229,9 +250,15 @@ func resourceInfoToK8sObject(info *resource.Info, logger logger.Logger) (*K8sObj errStr += " with " + resourceStr } fpErr := malformedYamlDoc(info.Source, 0, -1, fmt.Errorf("%s: %s", errStr, err)) - logError(logger, fpErr) + logError(l, fpErr) return nil, fpErr, false, false } + resObject.initDefaultNamespace() + } else { + // failed conversion to unstructured + fpErr := malformedYamlDoc(info.Source, 0, -1, fmt.Errorf("failed conversion from resource.Info to unstructured.Unstructured")) + logError(l, fpErr) + return nil, fpErr, false, false } isNetpol := resObject.Kind == Networkpolicy @@ -242,14 +269,15 @@ func resourceInfoToK8sObject(info *resource.Info, logger logger.Logger) (*K8sObj // error for resource with kind: , name: ,namespace: , func getResourceInfoStr(kind, name, namespace string) string { res := "" + sep := " , " if kind != "" { - res += "kind: " + kind + " , " + res += "kind: " + kind + sep } if name != "" { - res += "name: " + name + " , " + res += "name: " + name + sep } if namespace != "" { - res += "namespace: " + namespace + " , " + res += "namespace: " + namespace + sep } return res } @@ -286,7 +314,7 @@ func (sc *ResourcesScanner) YAMLDocumentsToObjectsList(documents []YAMLDocumentI return res, errs } -func missingResources(isWorkload bool, isNetpol bool) *FileProcessingError { +func missingResources(isWorkload, isNetpol bool) *FileProcessingError { if isWorkload { return noK8sWorkloadResourcesFound() }