diff --git a/cmd/nettop/main.go b/cmd/nettop/main.go index c688188..0363d1e 100644 --- a/cmd/nettop/main.go +++ b/cmd/nettop/main.go @@ -1,43 +1,24 @@ package main import ( - "errors" + "fmt" "os" - "syscall" - - "go.uber.org/zap" "github.com/np-guard/cluster-topology-analyzer/pkg/common" "github.com/np-guard/cluster-topology-analyzer/pkg/controller" ) -func runAnalysis() int { - logger := common.SetupLogger() - defer func() { - err := logger.Sync() - // If stderr is a TTY we might not be able to sync. - // See https://github.com/uber-go/zap/issues/991#issuecomment-962098428 for - // why we ignore ENOTTY. On OSX, we must ignore EBADF. - if err != nil && !errors.Is(err, syscall.ENOTTY) && !errors.Is(err, syscall.EBADF) { - panic(err) - } - }() - +func main() { var inArgs common.InArgs err := common.ParseInArgs(&inArgs) if err != nil { - zap.S().Debug("error parsing arguments, exiting...") - return 1 + fmt.Fprintf(os.Stderr, "error parsing arguments: %v. exiting...\n", err) + os.Exit(1) } err = controller.Start(inArgs) if err != nil { - zap.S().Debug("error running topology analysis exiting...") - return 1 + fmt.Fprintf(os.Stderr, "error running topology analysis: %v. exiting...", err) + os.Exit(1) } - return 0 -} - -func main() { - os.Exit(runAnalysis()) } diff --git a/go.mod b/go.mod index 89f8fe9..7311877 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/np-guard/cluster-topology-analyzer go 1.17 require ( - go.uber.org/zap v1.23.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.23.8 k8s.io/apimachinery v0.23.8 @@ -18,8 +17,6 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 0bbfa98..974d93b 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,6 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -205,8 +203,6 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -217,15 +213,12 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -238,14 +231,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/pkg/analyzer/resources.go b/pkg/analyzer/resources.go index b254dbf..d868acb 100644 --- a/pkg/analyzer/resources.go +++ b/pkg/analyzer/resources.go @@ -12,21 +12,8 @@ import ( const yamlParseBufferSize = 200 -// ParsePod parses replicationController -func ParsePod(r io.Reader) *v1.Pod { - if r == nil { - return nil - } - rc := v1.Pod{} - err := yaml.NewYAMLOrJSONDecoder(r, yamlParseBufferSize).Decode(&rc) - if err != nil { - return nil - } - return &rc -} - -// ParseDeployment parses deployment -func ParseDeployment(r io.Reader) *appsv1.Deployment { +// parseDeployment parses deployment +func parseDeployment(r io.Reader) *appsv1.Deployment { if r == nil { return nil } @@ -38,8 +25,8 @@ func ParseDeployment(r io.Reader) *appsv1.Deployment { return &rc } -// ParseReplicaSet parses replicaset -func ParseReplicaSet(r io.Reader) *appsv1.ReplicaSet { +// parseReplicaSet parses replicaset +func parseReplicaSet(r io.Reader) *appsv1.ReplicaSet { if r == nil { return nil } @@ -51,8 +38,8 @@ func ParseReplicaSet(r io.Reader) *appsv1.ReplicaSet { return &rc } -// ParseReplicationController parses replicationController -func ParseReplicationController(r io.Reader) *v1.ReplicationController { +// parseReplicationController parses replicationController +func parseReplicationController(r io.Reader) *v1.ReplicationController { if r == nil { return nil } @@ -65,8 +52,8 @@ func ParseReplicationController(r io.Reader) *v1.ReplicationController { return &rc } -// ParseDaemonSet parses replicationController -func ParseDaemonSet(r io.Reader) *appsv1.DaemonSet { +// parseDaemonSet parses a DaemonSet resource +func parseDaemonSet(r io.Reader) *appsv1.DaemonSet { if r == nil { return nil } @@ -79,8 +66,8 @@ func ParseDaemonSet(r io.Reader) *appsv1.DaemonSet { return &rc } -// ParseStatefulSet parses replicationController -func ParseStatefulSet(r io.Reader) *appsv1.StatefulSet { +// parseStatefulSet parses a StatefulSet resource +func parseStatefulSet(r io.Reader) *appsv1.StatefulSet { if r == nil { return nil } @@ -93,8 +80,8 @@ func ParseStatefulSet(r io.Reader) *appsv1.StatefulSet { return &rc } -// ParseJob parses replicationController -func ParseJob(r io.Reader) *batchv1.Job { +// parseJob parses a Job resource +func parseJob(r io.Reader) *batchv1.Job { if r == nil { return nil } @@ -107,8 +94,8 @@ func ParseJob(r io.Reader) *batchv1.Job { return &rc } -// ParseService parses replicationController -func ParseService(r io.Reader) *v1.Service { +// parseService parses a Service resource +func parseService(r io.Reader) *v1.Service { if r == nil { return nil } @@ -120,21 +107,8 @@ func ParseService(r io.Reader) *v1.Service { return &rc } -// ParseServiceAccount parses replicationController -func ParseServiceAccount(r io.Reader) *v1.ServiceAccount { - if r == nil { - return nil - } - rc := v1.ServiceAccount{} - err := yaml.NewYAMLOrJSONDecoder(r, yamlParseBufferSize).Decode(&rc) - if err != nil { - return nil - } - return &rc -} - -// ParseConfigMap parses ConfigMap -func ParseConfigMap(r io.Reader) *v1.ConfigMap { +// parseConfigMap parses a ConfigMap resource +func parseConfigMap(r io.Reader) *v1.ConfigMap { if r == nil { return nil } diff --git a/pkg/analyzer/scan.go b/pkg/analyzer/scan.go index 029a329..3a87dde 100644 --- a/pkg/analyzer/scan.go +++ b/pkg/analyzer/scan.go @@ -6,7 +6,6 @@ import ( "net/url" "strconv" - "go.uber.org/zap" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -14,46 +13,44 @@ import ( ) // Create a common.Resource object from a k8s Workload object -func ScanK8sDeployObject(kind string, objDataBuf []byte) (common.Resource, error) { +func ScanK8sWorkloadObject(kind string, objDataBuf []byte) (common.Resource, error) { var podSpecV1 v1.PodTemplateSpec var resourceCtx common.Resource var metaObj metaV1.Object resourceCtx.Resource.Kind = kind - switch kind { - case "Pod": - zap.S().Info("evaluating pod") + switch kind { // TODO: handle Pod case "ReplicaSet": - obj := ParseReplicaSet(bytes.NewReader(objDataBuf)) + obj := parseReplicaSet(bytes.NewReader(objDataBuf)) resourceCtx.Resource.Labels = obj.GetLabels() resourceCtx.Resource.Selectors = matchLabelSelectorToStrLabels(obj.Spec.Selector.MatchLabels) podSpecV1 = obj.Spec.Template metaObj = obj case "ReplicationController": - obj := ParseReplicationController(bytes.NewReader(objDataBuf)) + obj := parseReplicationController(bytes.NewReader(objDataBuf)) resourceCtx.Resource.Labels = obj.Spec.Template.Labels resourceCtx.Resource.Selectors = matchLabelSelectorToStrLabels(obj.Spec.Selector) podSpecV1 = *obj.Spec.Template metaObj = obj case "Deployment": - obj := ParseDeployment(bytes.NewReader(objDataBuf)) + obj := parseDeployment(bytes.NewReader(objDataBuf)) resourceCtx.Resource.Labels = obj.Spec.Template.Labels resourceCtx.Resource.Selectors = matchLabelSelectorToStrLabels(obj.Spec.Selector.MatchLabels) podSpecV1 = obj.Spec.Template metaObj = obj case "DaemonSet": - obj := ParseDaemonSet(bytes.NewReader(objDataBuf)) + obj := parseDaemonSet(bytes.NewReader(objDataBuf)) resourceCtx.Resource.Labels = obj.Spec.Template.Labels resourceCtx.Resource.Selectors = matchLabelSelectorToStrLabels(obj.Spec.Selector.MatchLabels) podSpecV1 = obj.Spec.Template metaObj = obj case "StatefulSet": - obj := ParseStatefulSet(bytes.NewReader(objDataBuf)) + obj := parseStatefulSet(bytes.NewReader(objDataBuf)) resourceCtx.Resource.Labels = obj.Spec.Template.Labels resourceCtx.Resource.Selectors = matchLabelSelectorToStrLabels(obj.Spec.Selector.MatchLabels) podSpecV1 = obj.Spec.Template metaObj = obj case "Job": - obj := ParseJob(bytes.NewReader(objDataBuf)) + obj := parseJob(bytes.NewReader(objDataBuf)) resourceCtx.Resource.Labels = obj.Spec.Template.Labels resourceCtx.Resource.Selectors = matchLabelSelectorToStrLabels(obj.Spec.Selector.MatchLabels) podSpecV1 = obj.Spec.Template @@ -75,7 +72,10 @@ func matchLabelSelectorToStrLabels(labels map[string]string) []string { } func ScanK8sConfigmapObject(kind string, objDataBuf []byte) (common.CfgMap, error) { - obj := ParseConfigMap(bytes.NewReader(objDataBuf)) + obj := parseConfigMap(bytes.NewReader(objDataBuf)) + if obj == nil { + return common.CfgMap{}, fmt.Errorf("unable to parse configmap") + } fullName := obj.ObjectMeta.Namespace + "/" + obj.ObjectMeta.Name data := map[string]string{} @@ -95,7 +95,10 @@ func ScanK8sServiceObject(kind string, objDataBuf []byte) (common.Service, error } var serviceCtx common.Service - svcObj := ParseService(bytes.NewReader(objDataBuf)) + svcObj := parseService(bytes.NewReader(objDataBuf)) + if svcObj == nil { + return common.Service{}, fmt.Errorf("failed to parse Service resource") + } serviceCtx.Resource.Name = svcObj.GetName() serviceCtx.Resource.Namespace = svcObj.Namespace serviceCtx.Resource.Kind = kind @@ -129,8 +132,7 @@ func parseDeployResource(podSpec *v1.PodTemplateSpec, obj metaV1.Object, resourc } for _, e := range container.Env { if e.Value != "" { - isPotentialAddress := IsNetworkAddressValue(e.Value) - if isPotentialAddress { + if IsNetworkAddressValue(e.Value) { resourceCtx.Resource.Envs = append(resourceCtx.Resource.Envs, e.Value) } } else if e.ValueFrom != nil && e.ValueFrom.ConfigMapKeyRef != nil { diff --git a/pkg/common/params.go b/pkg/common/params.go index 9219718..821206d 100644 --- a/pkg/common/params.go +++ b/pkg/common/params.go @@ -1,10 +1,8 @@ package common import ( - "errors" "flag" - - "go.uber.org/zap" + "fmt" ) func ParseInArgs(args *InArgs) error { @@ -20,9 +18,8 @@ func ParseInArgs(args *InArgs) error { *args.GitBranch == "" || *args.CommitID == "" || *args.GitURL == "" { - zap.S().Debugf("missing parameters: [%s %s %s %s]", *args.DirPath, *args.GitURL, *args.GitBranch, *args.CommitID) flag.PrintDefaults() - return errors.New("missing params") + return fmt.Errorf("missing parameters: [%s %s %s %s]", *args.DirPath, *args.GitURL, *args.GitBranch, *args.CommitID) } return nil diff --git a/pkg/common/utils.go b/pkg/common/utils.go deleted file mode 100644 index 843451e..0000000 --- a/pkg/common/utils.go +++ /dev/null @@ -1,17 +0,0 @@ -package common - -import ( - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -// SetupLogger is used to create a new human friendly -// logger using Uber's zap package. This will change -// the encode level to include color for debugging. -func SetupLogger() (logger *zap.Logger) { - config := zap.NewDevelopmentConfig() - config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - logger, _ = config.Build() - zap.ReplaceGlobals(logger) - return -} diff --git a/pkg/controller/connections.go b/pkg/controller/connections.go index 98fb204..2a36deb 100644 --- a/pkg/controller/connections.go +++ b/pkg/controller/connections.go @@ -4,16 +4,12 @@ import ( "fmt" "strings" - "go.uber.org/zap" - "github.com/np-guard/cluster-topology-analyzer/pkg/common" ) -var debug = false - // This function is at the core of the topology analysis // For each resource, it finds other resources that may use it and compiles a list of connections holding these dependencies -func discoverConnections(resources []common.Resource, links []common.Service) ([]common.Connections, error) { +func discoverConnections(resources []common.Resource, links []common.Service) []common.Connections { connections := []common.Connections{} for destResIdx := range resources { destRes := &resources[destResIdx] @@ -23,7 +19,7 @@ func discoverConnections(resources []common.Resource, links []common.Service) ([ srcRes := findSource(resources, svc) if len(srcRes) > 0 { for _, r := range srcRes { - zap.S().Debugf("source: %s target: %s link: %s", svc.Resource.Name, r.Resource.Name, svc.Resource.Name) + activeLogger.Debugf("source: %s target: %s link: %s", svc.Resource.Name, r.Resource.Name, svc.Resource.Name) connections = append(connections, common.Connections{Source: r, Target: destRes, Link: svc}) } } else { @@ -31,7 +27,7 @@ func discoverConnections(resources []common.Resource, links []common.Service) ([ } } } - return connections, nil + return connections } // areSelectorsContained returns true if selectors2 is contained in selectors1 @@ -65,9 +61,7 @@ func findServices(resource *common.Resource, links []common.Service) []common.Se } } - if debug { - zap.S().Debugf("matched service: %v", matchedSvc) - } + activeLogger.Debugf("services matched to %v: %v", resource.Resource.Name, matchedSvc) return matchedSvc } diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 89bd10f..7121e64 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -16,10 +16,10 @@ import ( // TestOutput calls controller.Start() with an example repo dir tests/onlineboutique/ , // checking for the json output to match expected output at tests/expected_output.json func TestConnectionsOutput(t *testing.T) { - currentDir, _ := os.Getwd() - dirPath := filepath.Join(currentDir, "..", "..", "tests", "onlineboutique", "kubernetes-manifests.yaml") - outFile := filepath.Join(currentDir, "..", "..", "tests", "onlineboutique", "output.json") - expectedOutput := filepath.Join(currentDir, "..", "..", "tests", "onlineboutique", "expected_output.json") + testsDir := getTestsDir() + dirPath := filepath.Join(testsDir, "onlineboutique", "kubernetes-manifests.yaml") + outFile := filepath.Join(testsDir, "onlineboutique", "output.json") + expectedOutput := filepath.Join(testsDir, "onlineboutique", "expected_output.json") args := getTestArgs(dirPath, outFile, false) err := Start(args) @@ -40,10 +40,10 @@ func TestConnectionsOutput(t *testing.T) { } func TestDirScan(t *testing.T) { - currentDir, _ := os.Getwd() - dirPath := filepath.Join(currentDir, "..", "..", "tests", "onlineboutique") - outFile := filepath.Join(currentDir, "..", "..", "tests", "onlineboutique", "output.json") - expectedOutput := filepath.Join(currentDir, "..", "..", "tests", "onlineboutique", "expected_dirscan_output.json") + testsDir := getTestsDir() + dirPath := filepath.Join(testsDir, "onlineboutique") + outFile := filepath.Join(dirPath, "output.json") + expectedOutput := filepath.Join(dirPath, "expected_dirscan_output.json") args := getTestArgs(dirPath, outFile, false) err := Start(args) @@ -70,8 +70,7 @@ type TestDetails struct { } func TestNetpolsJsonOutput(t *testing.T) { - currentDir, _ := os.Getwd() - testsDir := filepath.Join(currentDir, "..", "..", "tests") + testsDir := getTestsDir() tests := map[string]TestDetails{} // map from test name to test details tests["onlineboutique"] = TestDetails{dirPath: filepath.Join(testsDir, "onlineboutique", "kubernetes-manifests.yaml"), outFile: filepath.Join(testsDir, "onlineboutique", "output.json"), @@ -103,16 +102,21 @@ func TestNetpolsJsonOutput(t *testing.T) { } } -func TestNetpolsInterface(t *testing.T) { - currentDir, _ := os.Getwd() - testsDir := filepath.Join(currentDir, "..", "..", "tests") +func TestPoliciesSynthesizerAPI(t *testing.T) { + testsDir := getTestsDir() dirPath := filepath.Join(testsDir, "onlineboutique", "kubernetes-manifests.yaml") outFile := filepath.Join(testsDir, "onlineboutique", "output.json") expectedOutput := filepath.Join(testsDir, "onlineboutique", "expected_netpol_interface_output.json") - netpols, err := PoliciesFromFolderPath(dirPath) + logger := NewDefaultLogger() + synthesizer := NewPoliciesSynthesizer(WithLogger(logger)) + netpols, err := synthesizer.PoliciesFromFolderPath(dirPath) if err != nil { - t.Fatalf("expected err to be nil, but got %v", err) + t.Fatalf("expected no fatal errors, but got %v", err) + } + fileScanningErrors := synthesizer.Errors() + if len(fileScanningErrors) > 0 { + t.Fatalf("expected no file-scanning errors, but got %v", fileScanningErrors) } if len(netpols) == 0 { t.Fatalf("expected policies to be non-empty, but got empty") @@ -139,6 +143,80 @@ func TestNetpolsInterface(t *testing.T) { os.Remove(outFile) } +func TestPoliciesSynthesizerAPIFatalError(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "badPath") + + logger := NewDefaultLogger() + synthesizer := NewPoliciesSynthesizer(WithLogger(logger)) + netpols, err := synthesizer.PoliciesFromFolderPath(dirPath) + if err == nil { + t.Fatal("expected a fatal error, but got none") + } + fileScanningErrors := synthesizer.Errors() + if len(fileScanningErrors) != 1 { + t.Fatalf("expected 1 file-scanning error, but got %d", len(fileScanningErrors)) + } + if len(netpols) != 0 { + t.Fatalf("expected no policies, but got %d policies", len(netpols)) + } +} + +func TestPoliciesSynthesizerAPIFailFast(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "bad_yamls") + + synthesizer := NewPoliciesSynthesizer(WithStopOnError()) + netpols, err := synthesizer.PoliciesFromFolderPath(dirPath) + if err != nil { + t.Fatalf("expected no fatal errors, but got %v", err) + } + fileScanningErrors := synthesizer.Errors() + if len(fileScanningErrors) != 1 { + t.Fatalf("expected 1 file-scanning error, but got %d", len(fileScanningErrors)) + } + if len(netpols) != 0 { + t.Fatalf("expected no policies, but got %d policies", len(netpols)) + } +} + +func TestExtractConnectionsNoK8sResources(t *testing.T) { + testsDir := getTestsDir() + dirPath := filepath.Join(testsDir, "bad_yamls", "irrelevant_k8s_resources.yaml") + args := getTestArgs(dirPath, "", false) + conns, errs := extractConnections(args, false) + if len(errs) != 1 { + t.Fatalf("expected one error but got %d", len(errs)) + } + if len(conns) > 0 { + t.Fatalf("expected no conns but got %d", len(conns)) + } +} + +func TestExtractConnectionsNoK8sResourcesFailFast(t *testing.T) { + testsDir := getTestsDir() + dirPath := filepath.Join(testsDir, "bad_yamls") + args := getTestArgs(dirPath, "", true) + conns, errs := extractConnections(args, true) + if len(errs) != 1 { + t.Fatalf("expected one error but got %d", len(errs)) + } + if len(conns) > 0 { + t.Fatalf("expected no conns but got %d", len(conns)) + } +} + +func TestExtractConnectionsBadConfigMapRefs(t *testing.T) { + testsDir := getTestsDir() + dirPath := filepath.Join(testsDir, "bad_yamls", "bad_configmap_refs.yaml") + args := getTestArgs(dirPath, "", false) + conns, errs := extractConnections(args, false) + if len(errs) != 3 { + t.Fatalf("expected 3 errors but got %d", len(errs)) + } + if len(conns) > 0 { + t.Fatalf("expected no conns but got %d", len(conns)) + } +} + func readLines(path string) ([]string, error) { file, err := os.Open(path) if err != nil { @@ -154,6 +232,11 @@ func readLines(path string) ([]string, error) { return lines, scanner.Err() } +func getTestsDir() string { + currentDir, _ := os.Getwd() + return filepath.Join(currentDir, "..", "..", "tests") +} + func getTestArgs(dirPath, outFile string, netpols bool) common.InArgs { args := common.InArgs{} emptyStr := "" diff --git a/pkg/controller/engine.go b/pkg/controller/engine.go index 71bc527..fae740f 100644 --- a/pkg/controller/engine.go +++ b/pkg/controller/engine.go @@ -6,12 +6,8 @@ import ( "fmt" "os" - networking "k8s.io/api/networking/v1" - "github.com/np-guard/cluster-topology-analyzer/pkg/analyzer" "github.com/np-guard/cluster-topology-analyzer/pkg/common" - - "go.uber.org/zap" ) // Start : This is the entry point for the topology analysis engine. @@ -20,14 +16,15 @@ import ( // (or NetworkPolicies to allow only this connectivity) func Start(args common.InArgs) error { // 1. Discover all connections between resources - connections, err := extractConnections(args) - if err != nil { - return err + connections, fileScanErrors := extractConnections(args, false) + if len(fileScanErrors) > 0 { + return fmt.Errorf("errors in processing input files: %v", fileScanErrors) } // 2. Write the output to a file or to stdout const indent = " " var buf []byte + var err error if args.SynthNetpols != nil && *args.SynthNetpols { buf, err = json.MarshalIndent(synthNetpolList(connections), "", indent) } else { @@ -39,14 +36,14 @@ func Start(args common.InArgs) error { if *args.OutputFile != "" { fp, err := os.Create(*args.OutputFile) if err != nil { - msg := fmt.Sprintf("error creating file: %s: %v", *args.OutputFile, err) - zap.S().Errorf(msg) + msg := fmt.Sprintf("error creating file: %s", *args.OutputFile) + activeLogger.Errorf(err, msg) return errors.New(msg) } _, err = fp.Write(buf) if err != nil { - msg := fmt.Sprintf("error writing to file: %s: %v", *args.OutputFile, err) - zap.S().Errorf(msg) + msg := fmt.Sprintf("error writing to file: %s", *args.OutputFile) + activeLogger.Errorf(err, msg) return errors.New(msg) } fp.Close() @@ -56,47 +53,37 @@ func Start(args common.InArgs) error { return nil } -func PoliciesFromFolderPath(fullTargetPath string) ([]*networking.NetworkPolicy, error) { - emptyStr := "" - args := common.InArgs{} - args.DirPath = &fullTargetPath - args.CommitID = &emptyStr - args.GitBranch = &emptyStr - args.GitURL = &emptyStr - - connections, err := extractConnections(args) - if err != nil { - return []*networking.NetworkPolicy{}, err - } - return synthNetpols(connections), nil -} - -func extractConnections(args common.InArgs) ([]common.Connections, error) { +func extractConnections(args common.InArgs, stopOn1stErr bool) ([]common.Connections, []FileProcessingError) { // 1. Get all relevant resources from the repo and parse them - dObjs := getK8sDeploymentResources(args.DirPath) + dObjs, fileErrors := getK8sDeploymentResources(*args.DirPath, stopOn1stErr) + if stopProcessing(stopOn1stErr, fileErrors) { + return nil, fileErrors + } if len(dObjs) == 0 { - msg := "no deployment objects discovered in the repository" - zap.S().Errorf(msg) - return []common.Connections{}, errors.New(msg) + fileErrors = appendAndLogNewError(fileErrors, noK8sResourcesFound()) + return []common.Connections{}, fileErrors + } + + resources, links, parseErrors := parseResources(dObjs, args) + fileErrors = append(fileErrors, parseErrors...) + if stopProcessing(stopOn1stErr, fileErrors) { + return nil, fileErrors } - resources, links := parseResources(dObjs, args) // 2. Discover all connections between resources - return discoverConnections(resources, links) + return discoverConnections(resources, links), fileErrors } -func parseResources(objs []parsedK8sObjects, args common.InArgs) ([]common.Resource, []common.Service) { +func parseResources(objs []parsedK8sObjects, args common.InArgs) ([]common.Resource, []common.Service, []FileProcessingError) { resources := []common.Resource{} links := []common.Service{} configmaps := map[string]common.CfgMap{} // map from a configmap's full-name to its data + parseErrors := []FileProcessingError{} for _, o := range objs { - r, l, c := parseResource(o) - if len(r) != 0 { - resources = append(resources, r...) - } - if len(l) != 0 { - links = append(links, l...) - } + r, l, c, e := parseResource(o) + resources = append(resources, r...) + links = append(links, l...) + parseErrors = append(parseErrors, e...) for _, cfgObj := range c { configmaps[cfgObj.FullName] = cfgObj } @@ -112,8 +99,12 @@ func parseResources(objs []parsedK8sObjects, args common.InArgs) ([]common.Resou configmapFullName := res.Resource.Namespace + "/" + cfgMapRef if cfgMap, ok := configmaps[configmapFullName]; ok { for _, v := range cfgMap.Data { - res.Resource.Envs = append(res.Resource.Envs, v) + if analyzer.IsNetworkAddressValue(v) { + res.Resource.Envs = append(res.Resource.Envs, v) + } } + } else { + parseErrors = appendAndLogNewError(parseErrors, configMapNotFound(configmapFullName, res.Resource.Name)) } } for _, cfgMapKeyRef := range res.Resource.ConfigMapKeyRefs { @@ -123,7 +114,11 @@ func parseResources(objs []parsedK8sObjects, args common.InArgs) ([]common.Resou if analyzer.IsNetworkAddressValue(val) { res.Resource.Envs = append(res.Resource.Envs, val) } + } else { + parseErrors = appendAndLogNewError(parseErrors, configMapKeyNotFound(cfgMapKeyRef.Name, cfgMapKeyRef.Key, res.Resource.Name)) } + } else { + parseErrors = appendAndLogNewError(parseErrors, configMapNotFound(configmapFullName, res.Resource.Name)) } } } @@ -133,35 +128,36 @@ func parseResources(objs []parsedK8sObjects, args common.InArgs) ([]common.Resou links[idx].GitBranch = *args.GitBranch links[idx].GitURL = *args.GitURL } - return resources, links + return resources, links, parseErrors } -func parseResource(obj parsedK8sObjects) ([]common.Resource, []common.Service, []common.CfgMap) { +func parseResource(obj parsedK8sObjects) ([]common.Resource, []common.Service, []common.CfgMap, []FileProcessingError) { links := []common.Service{} deployments := []common.Resource{} configMaps := []common.CfgMap{} + parseErrors := []FileProcessingError{} for _, p := range obj.DeployObjects { switch p.GroupKind { - case "Service": + case service: res, err := analyzer.ScanK8sServiceObject(p.GroupKind, p.RuntimeObject) if err != nil { - zap.S().Errorf("error scanning service object: %v", err) + parseErrors = appendAndLogNewError(parseErrors, failedScanningResource(p.GroupKind, obj.ManifestFilepath, err)) continue } res.Resource.FilePath = obj.ManifestFilepath links = append(links, res) - case "ConfigMap": + case configmap: res, err := analyzer.ScanK8sConfigmapObject(p.GroupKind, p.RuntimeObject) if err != nil { - zap.S().Errorf("error scanning Configmap object: %v", err) + parseErrors = appendAndLogNewError(parseErrors, failedScanningResource(p.GroupKind, obj.ManifestFilepath, err)) continue } configMaps = append(configMaps, res) default: - res, err := analyzer.ScanK8sDeployObject(p.GroupKind, p.RuntimeObject) + res, err := analyzer.ScanK8sWorkloadObject(p.GroupKind, p.RuntimeObject) if err != nil { - zap.S().Debugf("Skipping object with type: %s", p.GroupKind) + parseErrors = appendAndLogNewError(parseErrors, failedScanningResource(p.GroupKind, obj.ManifestFilepath, err)) continue } res.Resource.FilePath = obj.ManifestFilepath @@ -169,5 +165,5 @@ func parseResource(obj parsedK8sObjects) ([]common.Resource, []common.Service, [ } } - return deployments, links, configMaps + return deployments, links, configMaps, parseErrors } diff --git a/pkg/controller/error_types.go b/pkg/controller/error_types.go new file mode 100644 index 0000000..a1eae5c --- /dev/null +++ b/pkg/controller/error_types.go @@ -0,0 +1,109 @@ +package controller + +import ( + "errors" + "fmt" +) + +type FileProcessingError struct { + err error + filePath string + lineNum int // the line number in filePath where the error originates from (1-based, 0 means unknown) + docID int // the number of the YAML document where the error originates from (0-based, -1 means unknown) + fatal bool // a fatal error is not recoverable. Outputs should not be used + severe bool // a severe error is recoverable. However, outputs should be used with care +} + +// constructs a FileProcessingError object and prints an error/warning to the log +func newFileProcessingError(origErr error, msg, filePath string, lineNum, docID int, fatal, severe bool) *FileProcessingError { + err := errors.New(msg) + if origErr != nil { + err = fmt.Errorf("%s: %w", msg, origErr) + } + fpe := FileProcessingError{err, filePath, lineNum, docID, fatal, severe} + + return &fpe +} + +func (e *FileProcessingError) Error() error { + return e.err +} + +func (e *FileProcessingError) File() string { + return e.filePath +} + +func (e *FileProcessingError) LineNo() int { + return e.lineNum +} + +func (e *FileProcessingError) DocumentID() (int, error) { + if e.docID < 0 { + return -1, errors.New("no document ID is available for this error") + } + return e.docID, nil +} + +func (e *FileProcessingError) Location() string { + if e.filePath == "" { + return "" + } + + suffix := "" + if e.lineNum > 0 { + suffix = fmt.Sprintf(", line: %d", e.LineNo()) + } + if did, err := e.DocumentID(); err == nil { + suffix += fmt.Sprintf(", document: %d%s", did, suffix) + } + return fmt.Sprintf("in file: %s%s", e.File(), suffix) +} + +func (e *FileProcessingError) IsFatal() bool { + return e.fatal +} + +func (e *FileProcessingError) IsSevere() bool { + return e.severe +} + +// -------- Constructors for specific error types ---------------- + +func noYamlsFound() *FileProcessingError { + return newFileProcessingError(nil, "no yaml files found", "", 0, -1, false, false) +} + +func noK8sResourcesFound() *FileProcessingError { + return newFileProcessingError(nil, "no relevant Kubernetes resources found", "", 0, -1, false, false) +} + +func configMapNotFound(cfgMapName, resourceName string) *FileProcessingError { + msg := fmt.Sprintf("configmap %s not found (referenced by %s)", cfgMapName, resourceName) + return newFileProcessingError(nil, msg, "", 0, -1, false, false) +} + +func configMapKeyNotFound(cfgMapName, cfgMapKey, resourceName string) *FileProcessingError { + msg := fmt.Sprintf("configmap %s does not have key %s (referenced by %s)", cfgMapName, cfgMapKey, resourceName) + return newFileProcessingError(nil, msg, "", 0, -1, false, false) +} + +func failedScanningResource(resourceType, filePath string, err error) *FileProcessingError { + msg := fmt.Sprintf("error scanning %s resource", resourceType) + return newFileProcessingError(err, msg, filePath, 0, -1, false, false) +} + +func notK8sResource(filePath string, docID int, err error) *FileProcessingError { + return newFileProcessingError(err, "Yaml document is not a K8s resource", filePath, 0, docID, false, false) +} + +func malformedYamlDoc(filePath string, lineNum, docID int, err error) *FileProcessingError { + return newFileProcessingError(err, "YAML document is malformed", filePath, lineNum, docID, false, true) +} + +func failedReadingFile(filePath string, err error) *FileProcessingError { + return newFileProcessingError(err, "error reading file", filePath, 0, -1, false, true) +} + +func failedAccessingDir(dirPath string, err error, isSubDir bool) *FileProcessingError { + return newFileProcessingError(err, "error accessing directory", dirPath, 0, -1, !isSubDir, true) +} diff --git a/pkg/controller/logger.go b/pkg/controller/logger.go new file mode 100644 index 0000000..525996b --- /dev/null +++ b/pkg/controller/logger.go @@ -0,0 +1,52 @@ +package controller + +import ( + "errors" + "fmt" + "log" +) + +type Logger interface { + Debugf(format string, o ...interface{}) + Infof(format string, o ...interface{}) + Warnf(format string, o ...interface{}) + Errorf(err error, format string, o ...interface{}) +} + +type DefaultLogger struct { + l *log.Logger +} + +func NewDefaultLogger() *DefaultLogger { + return &DefaultLogger{ + l: log.Default(), + } +} + +func (df *DefaultLogger) Debugf(format string, o ...interface{}) { + df.l.Printf(format, o...) +} +func (df *DefaultLogger) Infof(format string, o ...interface{}) { + df.l.Printf(format, o...) +} +func (df *DefaultLogger) Warnf(format string, o ...interface{}) { + df.l.Printf(format, o...) +} +func (df *DefaultLogger) Errorf(err error, format string, o ...interface{}) { + df.l.Printf("%s: %v", fmt.Sprintf(format, o...), err) +} + +var activeLogger Logger = NewDefaultLogger() + +func logError(fpe *FileProcessingError) { + logMsg := fpe.Error().Error() + location := fpe.Location() + if location != "" { + logMsg = fmt.Sprintf("%s %s", location, logMsg) + } + if fpe.IsSevere() || fpe.IsFatal() { + activeLogger.Errorf(errors.New(logMsg), "") + } else { + activeLogger.Warnf(logMsg) + } +} diff --git a/pkg/controller/policies_synthesizer.go b/pkg/controller/policies_synthesizer.go new file mode 100644 index 0000000..b95e7ad --- /dev/null +++ b/pkg/controller/policies_synthesizer.go @@ -0,0 +1,71 @@ +package controller + +import ( + networking "k8s.io/api/networking/v1" + + "github.com/np-guard/cluster-topology-analyzer/pkg/common" +) + +type PoliciesSynthesizer struct { + logger Logger + stopOnError bool + + errors []FileProcessingError +} + +type PoliciesSynthesizerOption func(*PoliciesSynthesizer) + +func WithLogger(logger Logger) PoliciesSynthesizerOption { + return func(p *PoliciesSynthesizer) { + p.logger = logger + } +} + +func WithStopOnError() PoliciesSynthesizerOption { + return func(p *PoliciesSynthesizer) { + p.stopOnError = true + } +} + +func NewPoliciesSynthesizer(options ...PoliciesSynthesizerOption) *PoliciesSynthesizer { + // object with default behavior options + ps := &PoliciesSynthesizer{ + logger: NewDefaultLogger(), + stopOnError: false, + errors: []FileProcessingError{}, + } + for _, o := range options { + o(ps) + } + return ps +} + +func (ps *PoliciesSynthesizer) Errors() []FileProcessingError { + return ps.errors +} + +func (ps *PoliciesSynthesizer) PoliciesFromFolderPath(dirPath string) ([]*networking.NetworkPolicy, error) { + activeLogger = ps.logger + + emptyStr := "" + args := common.InArgs{} + args.DirPath = &dirPath + args.CommitID = &emptyStr + args.GitBranch = &emptyStr + args.GitURL = &emptyStr + + connections, errs := extractConnections(args, ps.stopOnError) + policies := []*networking.NetworkPolicy{} + if !stopProcessing(ps.stopOnError, errs) { + policies = synthNetpols(connections) + } + + ps.errors = errs + for idx := range errs { + if errs[idx].IsFatal() { + return nil, errs[idx].Error() + } + } + + return policies, nil +} diff --git a/pkg/controller/utils.go b/pkg/controller/utils.go index 9f9a451..e4a4d8b 100644 --- a/pkg/controller/utils.go +++ b/pkg/controller/utils.go @@ -9,7 +9,6 @@ import ( "regexp" "strings" - "go.uber.org/zap" "gopkg.in/yaml.v3" "k8s.io/client-go/kubernetes/scheme" ) @@ -27,6 +26,13 @@ const ( configmap string = "ConfigMap" ) +var ( + acceptedK8sTypesRegex = fmt.Sprintf("(%s|%s|%s|%s|%s|%s|%s|%s|%s|%s)", + pod, replicaSet, replicationController, deployment, daemonset, statefulset, job, cronJob, service, configmap) + acceptedK8sTypes = regexp.MustCompile(acceptedK8sTypesRegex) + yamlSuffix = regexp.MustCompile(".ya?ml$") +) + type parsedK8sObjects struct { ManifestFilepath string DeployObjects []deployObject @@ -38,94 +44,123 @@ type deployObject struct { } // return a list of yaml files under a given directory (recursively) -func searchDeploymentManifests(repoDir *string) []string { +func searchDeploymentManifests(repoDir string, stopOn1stErr bool) ([]string, []FileProcessingError) { yamls := []string{} - err := filepath.WalkDir(*repoDir, func(path string, f os.DirEntry, err error) error { + errors := []FileProcessingError{} + err := filepath.WalkDir(repoDir, func(path string, f os.DirEntry, err error) error { if err != nil { - return err - } - if f != nil && !f.IsDir() { - r, err := regexp.MatchString(`^.*\.y(a)?ml$`, f.Name()) - if err == nil && r { - yamls = append(yamls, path) + errors = appendAndLogNewError(errors, failedAccessingDir(path, err, path != repoDir)) + if stopProcessing(stopOn1stErr, errors) { + return err } + return filepath.SkipDir + } + if f != nil && !f.IsDir() && yamlSuffix.MatchString(f.Name()) { + yamls = append(yamls, path) } return nil }) if err != nil { - zap.S().Errorf("Error in searching for manifests: %v", err) + activeLogger.Errorf(err, "Error walking directory") } - return yamls + return yamls, errors } -func getK8sDeploymentResources(repoDir *string) []parsedK8sObjects { - manifestFiles := searchDeploymentManifests(repoDir) +func getK8sDeploymentResources(repoDir string, stopOn1stErr bool) ([]parsedK8sObjects, []FileProcessingError) { + manifestFiles, fileScanErrors := searchDeploymentManifests(repoDir, stopOn1stErr) + if stopProcessing(stopOn1stErr, fileScanErrors) { + return nil, fileScanErrors + } if len(manifestFiles) == 0 { - zap.S().Info("no deployment manifest found") - return nil + fileScanErrors = appendAndLogNewError(fileScanErrors, noYamlsFound()) + return nil, fileScanErrors } + parsedObjs := []parsedK8sObjects{} for _, mfp := range manifestFiles { - filebuf, err := os.ReadFile(mfp) - if err != nil { - continue + deployObjects, err := parseK8sYaml(mfp, stopOn1stErr) + fileScanErrors = append(fileScanErrors, err...) + if stopProcessing(stopOn1stErr, fileScanErrors) { + return nil, fileScanErrors } - p := parsedK8sObjects{} - p.ManifestFilepath = mfp - if pathSplit := strings.Split(mfp, *repoDir); len(pathSplit) > 1 { - p.ManifestFilepath = pathSplit[1] + if len(deployObjects) > 0 { + manifestFilepath := mfp + if pathSplit := strings.Split(mfp, repoDir); len(pathSplit) > 1 { + manifestFilepath = pathSplit[1] + } + parsedObjs = append(parsedObjs, parsedK8sObjects{DeployObjects: deployObjects, ManifestFilepath: manifestFilepath}) } - p.DeployObjects = parseK8sYaml(filebuf) - parsedObjs = append(parsedObjs, p) } - return parsedObjs + return parsedObjs, fileScanErrors } -func splitByYamlDocuments(data []byte) []string { - decoder := yaml.NewDecoder(bytes.NewBuffer(data)) +func splitByYamlDocuments(mfp string) ([]string, []FileProcessingError) { + fileBuf, err := os.ReadFile(mfp) + if err != nil { + return []string{}, appendAndLogNewError(nil, failedReadingFile(mfp, err)) + } + + decoder := yaml.NewDecoder(bytes.NewBuffer(fileBuf)) documents := []string{} + documentID := 0 for { - var doc map[interface{}]interface{} + var doc yaml.Node if err := decoder.Decode(&doc); err != nil { - if err == io.EOF { - break + if err != io.EOF { + return documents, appendAndLogNewError(nil, malformedYamlDoc(mfp, 0, documentID, err)) } - zap.S().Warn(err) // document decode failed + break } - if len(doc) > 0 { - out, err := yaml.Marshal(doc) + if len(doc.Content) > 0 && doc.Content[0].Kind == yaml.MappingNode { + out, err := yaml.Marshal(doc.Content[0]) if err != nil { - zap.S().Warn(err) // document marshal failed + return documents, appendAndLogNewError(nil, malformedYamlDoc(mfp, doc.Line, documentID, err)) } documents = append(documents, string(out)) } + documentID += 1 } - return documents + return documents, nil } -func parseK8sYaml(fileR []byte) []deployObject { +func parseK8sYaml(mfp string, stopOn1stErr bool) ([]deployObject, []FileProcessingError) { dObjs := []deployObject{} - acceptedK8sTypes := regexp.MustCompile(fmt.Sprintf("(%s|%s|%s|%s|%s|%s|%s|%s|%s|%s)", - pod, replicaSet, replicationController, deployment, daemonset, statefulset, job, cronJob, service, configmap)) - sepYamlFiles := splitByYamlDocuments(fileR) - for _, f := range sepYamlFiles { - if f == "\n" || f == "" { - continue // ignore empty yaml documents - } + sepYamlFiles, fileProcessingErrors := splitByYamlDocuments(mfp) + if stopProcessing(stopOn1stErr, fileProcessingErrors) { + return nil, fileProcessingErrors + } + + for docID, doc := range sepYamlFiles { decode := scheme.Codecs.UniversalDeserializer().Decode - _, groupVersionKind, err := decode([]byte(f), nil, nil) + _, groupVersionKind, err := decode([]byte(doc), nil, nil) if err != nil { - zap.S().Warn(err) // not a k8s resource + fileProcessingErrors = appendAndLogNewError(fileProcessingErrors, notK8sResource(mfp, docID, err)) continue } if !acceptedK8sTypes.MatchString(groupVersionKind.Kind) { - zap.S().Infof("Skipping object with type: %s", groupVersionKind.Kind) + activeLogger.Infof("Skipping object with type: %s", groupVersionKind.Kind) } else { d := deployObject{} d.GroupKind = groupVersionKind.Kind - d.RuntimeObject = []byte(f) + d.RuntimeObject = []byte(doc) dObjs = append(dObjs, d) } } - return dObjs + return dObjs, fileProcessingErrors +} + +func stopProcessing(stopOn1stErr bool, errs []FileProcessingError) bool { + for idx := range errs { + if errs[idx].IsFatal() || stopOn1stErr && errs[idx].IsSevere() { + return true + } + } + + return false +} + +func appendAndLogNewError(errs []FileProcessingError, newErr *FileProcessingError) []FileProcessingError { + logError(newErr) + errs = append(errs, *newErr) + return errs } diff --git a/pkg/controller/utils_test.go b/pkg/controller/utils_test.go new file mode 100644 index 0000000..e1d9d12 --- /dev/null +++ b/pkg/controller/utils_test.go @@ -0,0 +1,95 @@ +package controller + +import ( + "path/filepath" + "testing" +) + +func TestGetK8sDeploymentResourcesBadYamlDocument(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "bad_yamls", "document_with_syntax_error.yaml") + objs, errs := getK8sDeploymentResources(dirPath, false) + if len(errs) != 1 { + t.Fatalf("expected one error but got %d", len(errs)) + } + if docID, err := errs[0].DocumentID(); docID != 6 || err != nil { + t.Fatalf("expected bad Document ID to be 6 but got %d", errs[0].docID) + } + if len(objs) != 1 { + t.Fatalf("expected 1 parsed file but got %d", len(objs)) + } + if len(objs[0].DeployObjects) != 6 { + t.Fatalf("expected 6 parsed objects but got %d", len(objs[0].DeployObjects)) + } +} + +func TestGetK8sDeploymentResourcesBadYamlDocumentFailFast(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "bad_yamls", "document_with_syntax_error.yaml") + objs, errs := getK8sDeploymentResources(dirPath, true) + if len(errs) != 1 { + t.Fatalf("expected one error but got %d", len(errs)) + } + if docID, err := errs[0].DocumentID(); docID != 6 || err != nil { + t.Fatalf("expected bad Document ID to be 6 but got %d", errs[0].docID) + } + if len(objs) != 0 { + t.Fatalf("expected no parsed file but got %d", len(objs)) + } +} + +func TestGetK8sDeploymentResourcesNoK8sResource(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "bad_yamls", "not_a_k8s_resource.yaml") + objs, errs := getK8sDeploymentResources(dirPath, false) + if len(errs) != 1 { + t.Fatalf("expected one error but got %d", len(errs)) + } + if len(objs) != 1 { + t.Fatalf("expected 1 parsed file but got %d", len(objs)) + } + if len(objs[0].DeployObjects) != 1 { + t.Fatalf("expected 1 parsed object but got %d", len(objs[0].DeployObjects)) + } +} + +func TestGetK8sDeploymentResourcesNoYAMLs(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "bad_yamls", "subdir2") + objs, errs := getK8sDeploymentResources(dirPath, false) + if len(errs) != 1 { + t.Fatalf("expected one error but got %d", len(errs)) + } + if len(objs) != 0 { + t.Fatalf("expected no parsed files but got %d", len(objs)) + } +} + +func TestGetK8sDeploymentResourcesBadDir(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "bad_yamls", "subdir3") // doesn't exist + objs, errs := getK8sDeploymentResources(dirPath, false) + if len(errs) != 1 { + t.Fatalf("expected 1 errors but got %d", len(errs)) + } + if len(objs) != 0 { + t.Fatalf("expected no parsed files but got %d", len(objs)) + } +} + +func TestGetK8sDeploymentResourcesBadDirFailFast(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "bad_yamls", "subdir3") // doesn't exist + objs, errs := getK8sDeploymentResources(dirPath, true) + if len(errs) != 1 { + t.Fatalf("expected 1 error but got %d", len(errs)) + } + if len(objs) != 0 { + t.Fatalf("expected no parsed files but got %d", len(objs)) + } +} + +func TestSearchDeploymentManifests(t *testing.T) { + dirPath := filepath.Join(getTestsDir(), "bad_yamls") + yamlFiles, errs := searchDeploymentManifests(dirPath, false) + if len(yamlFiles) != 5 { + t.Fatalf("expected 5 yamls but got %d", len(yamlFiles)) + } + if len(errs) > 0 { + t.Fatalf("expected no errors but got %d", len(errs)) + } +} diff --git a/tests/bad_yamls/bad_configmap_refs.yaml b/tests/bad_yamls/bad_configmap_refs.yaml new file mode 100644 index 0000000..b7960d9 --- /dev/null +++ b/tests/bad_yamls/bad_configmap_refs.yaml @@ -0,0 +1,143 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/frontend:v0.2.3 + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "recommendationservice:8080" + - name: CHECKOUT_SERVICE_ADDR + value: "checkoutservice:5050" + - name: AD_SERVICE_ADDR + value: "adservice:9555" + # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem + # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp + # - name: ENV_PLATFORM + # value: "aws" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + envFrom: + - configMapRef: + name: shipping-service-confi + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: checkoutservice +spec: + selector: + matchLabels: + app: checkoutservice + template: + metadata: + labels: + app: checkoutservice + spec: + serviceAccountName: default + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/checkoutservice:v0.2.3 + ports: + - containerPort: 5050 + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:5050"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:5050"] + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: SHIPPING_SERVICE_ADDR + valueFrom: + configMapKeyRef: + name: shipping-service-config + key: SHIPPING_SERVICE_ADDR + - name: SHIPPING_SERVICE_ADDR2 + valueFrom: + configMapKeyRef: + name: shipping-service-confi + key: SHIPPING_SERVICE_ADDR + - name: PAYMENT_SERVICE_ADDR + value: "paymentservice:50051" + - name: EMAIL_SERVICE_ADDR + value: "emailservice:5000" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + # - name: DISABLE_STATS + # value: "1" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: shipping-service-config +data: + SHIPPING_SERVICE_ADD: shippingservice:50051 diff --git a/tests/bad_yamls/document_with_syntax_error.yaml b/tests/bad_yamls/document_with_syntax_error.yaml new file mode 100644 index 0000000..e5c0f70 --- /dev/null +++ b/tests/bad_yamls/document_with_syntax_error.yaml @@ -0,0 +1,735 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------- +# WARNING: This file is autogenerated. Do not manually edit. +# ---------------------------------------------------------- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: emailservice +spec: + selector: + matchLabels: + app: emailservice + template: + metadata: + labels: + app: emailservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/emailservice:v0.2.3 + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + # - name: DISABLE_TRACING + # value: "1" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + livenessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: emailservice +spec: + type: ClusterIP + selector: + app: emailservice + ports: + - name: grpc + port: 5000 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: checkoutservice +spec: + selector: + matchLabels: + app: checkoutservice + template: + metadata: + labels: + app: checkoutservice + spec: + serviceAccountName: default + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/checkoutservice:v0.2.3 + ports: + - containerPort: 5050 + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:5050"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:5050"] + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: SHIPPING_SERVICE_ADDR + valueFrom: + configMapKeyRef: + name: shipping-service-config + key: SHIPPING_SERVICE_ADDR + - name: PAYMENT_SERVICE_ADDR + value: "paymentservice:50051" + - name: EMAIL_SERVICE_ADDR + value: "emailservice:5000" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + # - name: DISABLE_STATS + # value: "1" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: checkoutservice +spec: + type: ClusterIP + selector: + app: checkoutservice + ports: + - name: grpc + port: 5050 + targetPort: 5050 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: recommendationservice +spec: + selector: + matchLabels: + app: recommendationservice + template: + metadata: + labels: + app: recommendationservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/recommendationservice:v0.2.3 + ports: + - containerPort: 8080 + readinessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + livenessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: DISABLE_DEBUGGER + # value: "1" + resources: + requests: + cpu: 100m + memory: 220Mi + limits: + cpu: 200m + memory: 450Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: recommendationservice +spec: + type: ClusterIP + selector: + app: recommendationservice + ports: + - name: grpc + port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + containers: + - name: server + image: "{{ printf "%s:%s" ._zzz.image.main.repository ._zzz.image.main.tag }}" + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "recommendationservice:8080" + - name: CHECKOUT_SERVICE_ADDR + value: "checkoutservice:5050" + - name: AD_SERVICE_ADDR + value: "adservice:9555" + # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem + # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp + # - name: ENV_PLATFORM + # value: "aws" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + envFrom: + - configMapRef: + name: shipping-service-config + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend # testing --- separator inside yaml comment +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-external +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paymentservice +spec: + selector: + matchLabels: + app: paymentservice + template: + metadata: + labels: + app: paymentservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/paymentservice:v0.2.3 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: paymentservice +spec: + type: ClusterIP + selector: + app: paymentservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productcatalogservice +spec: + selector: + matchLabels: + app: productcatalogservice + template: + metadata: + labels: + app: productcatalogservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/productcatalogservice:v0.2.3 + ports: + - containerPort: 3550 + env: + - name: PORT + value: "3550" + # - name: DISABLE_STATS + # value: "1" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:3550"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:3550"] + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: productcatalogservice +spec: + type: ClusterIP + selector: + app: productcatalogservice + ports: + - name: grpc + port: 3550 + targetPort: 3550 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cartservice +spec: + selector: + matchLabels: + app: cartservice + template: + metadata: + labels: + app: cartservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/cartservice:v0.2.3 + ports: + - containerPort: 7070 + env: + - name: REDIS_ADDR + value: "redis-cart:6379" + resources: + requests: + cpu: 200m + memory: 64Mi + limits: + cpu: 300m + memory: 128Mi + readinessProbe: + initialDelaySeconds: 15 + exec: + command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 10 + exec: + command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] +--- +apiVersion: v1 +kind: Service +metadata: + name: cartservice +spec: + type: ClusterIP + selector: + app: cartservice + ports: + - name: grpc + port: 7070 + targetPort: 7070 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loadgenerator +spec: + selector: + matchLabels: + app: loadgenerator + replicas: 1 + template: + metadata: + labels: + app: loadgenerator + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + restartPolicy: Always + containers: + - name: main + image: gcr.io/google-samples/microservices-demo/loadgenerator:v0.2.3 + env: + - name: FRONTEND_ADDR + value: "frontend:80" + - name: USERS + value: "10" + resources: + requests: + cpu: 300m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: currencyservice +spec: + selector: + matchLabels: + app: currencyservice + template: + metadata: + labels: + app: currencyservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/currencyservice:v0.2.3 + ports: + - name: grpc + containerPort: 7000 + env: + - name: PORT + value: "7000" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: DISABLE_DEBUGGER + # value: "1" + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:7000"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:7000"] + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: currencyservice +spec: + type: ClusterIP + selector: + app: currencyservice + ports: + - name: grpc + port: 7000 + targetPort: 7000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shippingservice +spec: + selector: + matchLabels: + app: shippingservice + template: + metadata: + labels: + app: shippingservice + spec: + serviceAccountName: default + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/shippingservice:v0.2.3 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + # - name: DISABLE_STATS + # value: "1" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + readinessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: shippingservice +spec: + type: ClusterIP + selector: + app: shippingservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-cart +spec: + selector: + matchLabels: + app: redis-cart + template: + metadata: + labels: + app: redis-cart + spec: + containers: + - name: redis + image: redis:alpine + ports: + - containerPort: 6379 + readinessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + livenessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + resources: + limits: + memory: 256Mi + cpu: 125m + requests: + cpu: 70m + memory: 200Mi + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cart +spec: + type: ClusterIP + selector: + app: redis-cart + ports: + - name: redis + port: 6379 + targetPort: 6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: adservice +spec: + selector: + matchLabels: + app: adservice + template: + metadata: + labels: + app: adservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: gcr.io/google-samples/microservices-demo/adservice:v0.2.3 + ports: + - containerPort: 9555 + env: + - name: PORT + value: "9555" + # - name: DISABLE_STATS + # value: "1" + # - name: DISABLE_TRACING + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + resources: + requests: + cpu: 200m + memory: 180Mi + limits: + cpu: 300m + memory: 300Mi + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + exec: + command: ["/bin/grpc_health_probe", "-addr=:9555"] + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + exec: + command: ["/bin/grpc_health_probe", "-addr=:9555"] +--- +apiVersion: v1 +kind: Service +metadata: + name: adservice +spec: + type: ClusterIP + selector: + app: adservice + ports: + - name: grpc + port: 9555 + targetPort: 9555 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: shipping-service-config +data: + SHIPPING_SERVICE_ADDR: shippingservice:50051 diff --git a/tests/bad_yamls/irrelevant_k8s_resources.yaml b/tests/bad_yamls/irrelevant_k8s_resources.yaml new file mode 100644 index 0000000..e61b699 --- /dev/null +++ b/tests/bad_yamls/irrelevant_k8s_resources.yaml @@ -0,0 +1,42 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + creationTimestamp: "2022-01-27T18:20:25Z" + generation: 3 + name: testcase26-ingress-policy #-ingress + namespace: default + resourceVersion: "37135" + uid: 85e70fbc-0b4b-462c-9bc1-9a1e7e00db58 +spec: + ingressClassName: nginx + rules: + - host: demo.localdev.me + http: + paths: + - backend: + service: + name: details + port: + number: 9080 + path: /details + pathType: Prefix +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: ingress-based-on-named-ports + namespace: kube-system +spec: + podSelector: + matchLabels: + app: helm + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + tier: frontend + ports: + - port: tiller + protocol: TCP diff --git a/tests/bad_yamls/not_a_k8s_resource.yaml b/tests/bad_yamls/not_a_k8s_resource.yaml new file mode 100644 index 0000000..fcbde1f --- /dev/null +++ b/tests/bad_yamls/not_a_k8s_resource.yaml @@ -0,0 +1,22 @@ +namespaceList: ../../example_podlist/bookinfo_ns_list.json +podList: ../../example_podlist/bookinfo_deployments.yaml + +networkConfigList: + - name: istio-test-methods-4 + networkPolicyList: + - bookinfo-policy-test-methods-4.yaml +--- +# empty document +--- +apiVersion: v1 +kind: Service +metadata: + name: recommendationservice +spec: + type: ClusterIP + selector: + app: recommendationservice + ports: + - name: grpc + port: 8080 + targetPort: 8080 diff --git a/tests/bad_yamls/subdir/empty.yaml b/tests/bad_yamls/subdir/empty.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/bad_yamls/subdir2/empty.xml b/tests/bad_yamls/subdir2/empty.xml new file mode 100644 index 0000000..e69de29