From fc6da9c9e6e9351fa6b8f5710ae9586416238e5a Mon Sep 17 00:00:00 2001 From: Jim Bugwadia Date: Tue, 18 Aug 2020 21:03:00 -0700 Subject: [PATCH] improve CLI validation reports --- go.sum | 6 ++ pkg/kyverno/apply/command.go | 151 ++++++++++++++++++++--------------- pkg/kyverno/common/common.go | 2 +- 3 files changed, 92 insertions(+), 67 deletions(-) diff --git a/go.sum b/go.sum index ded104a23d..eaf422e247 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,7 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko github.com/Shopify/sarama v1.24.1/go.mod h1:fGP8eQ6PugKEI0iUETYYtnP6d1pH/bdDMTel1X5ajsU= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/ahmetb/gen-crd-api-reference-docs v0.2.0 h1:YI/cAcRdNAHArfhGKcmCY5qMa32k/UyCZagLgabC5JY= @@ -184,6 +185,7 @@ github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -663,6 +665,7 @@ github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qo github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190805055040-f9202b1cfdeb/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= @@ -671,6 +674,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -818,6 +822,7 @@ github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4do github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20190704165056-9c2d0518ed81/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -834,6 +839,7 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= github.com/santhosh-tekuri/jsonschema/v2 v2.1.0/go.mod h1:yzJzKUGV4RbWqWIBBP4wSOBqavX5saE02yirLS0OTyg= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/secure-io/sio-go v0.3.0 h1:QKGb6rGJeiExac9wSWxnWPYo8O8OFN7lxXQvHshX6vo= github.com/secure-io/sio-go v0.3.0/go.mod h1:D3KmXgKETffyYxBdFRN+Hpd2WzhzqS0EQwT3XWsAcBU= github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do= github.com/segmentio/analytics-go v3.0.1+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= diff --git a/pkg/kyverno/apply/command.go b/pkg/kyverno/apply/command.go index cfadd54f8a..0ba5d74ac8 100644 --- a/pkg/kyverno/apply/command.go +++ b/pkg/kyverno/apply/command.go @@ -37,17 +37,26 @@ import ( log "sigs.k8s.io/controller-runtime/pkg/log" ) + +type resultCounts struct { + pass int + fail int + warn int + error int + skip int +} + func Command() *cobra.Command { var cmd *cobra.Command var resourcePaths []string var cluster bool - var mutatelogPath string + var mutateLogPath string kubernetesConfig := genericclioptions.NewConfigFlags(true) cmd = &cobra.Command{ Use: "apply", - Short: "Applies policies on resources", + Short: "applies policies on resources", Example: fmt.Sprintf("To apply on a resource:\nkyverno apply /path/to/policy.yaml /path/to/folderOfPolicies --resource=/path/to/resource1 --resource=/path/to/resource2\n\nTo apply on a cluster\nkyverno apply /path/to/policy.yaml /path/to/folderOfPolicies --cluster"), RunE: func(cmd *cobra.Command, policyPaths []string) (err error) { defer func() { @@ -60,12 +69,12 @@ func Command() *cobra.Command { }() if len(resourcePaths) == 0 && !cluster { - return sanitizedError.NewWithError("resource file or cluster required", err) + return sanitizedError.NewWithError(fmt.Sprintf("resource file(s) or cluster required"), err) } var mutatelogPathIsDir bool - if mutatelogPath != "" { - spath := strings.Split(mutatelogPath, "/") + if mutateLogPath != "" { + spath := strings.Split(mutateLogPath, "/") sfileName := strings.Split(spath[len(spath)-1], ".") if sfileName[len(sfileName)-1] == "yml" || sfileName[len(sfileName)-1] == "yaml" { mutatelogPathIsDir = false @@ -73,7 +82,7 @@ func Command() *cobra.Command { mutatelogPathIsDir = true } - err = createFileOrFolder(mutatelogPath, mutatelogPathIsDir) + err = createFileOrFolder(mutateLogPath, mutatelogPathIsDir) if err != nil { if !sanitizedError.IsErrorSanitized(err) { return sanitizedError.NewWithError("failed to create file/folder.", err) @@ -90,18 +99,6 @@ func Command() *cobra.Command { return err } - for _, policy := range policies { - err := policy2.Validate(utils.MarshalPolicy(*policy), nil, true, openAPIController) - if err != nil { - fmt.Printf("Policy %v is not valid: %v\n", policy.Name, err) - os.Exit(1) - } - - if common.PolicyHasVariables(*policy) { - return sanitizedError.NewWithError(fmt.Sprintf("invalid policy %s. 'apply' does not support policies with variables", policy.Name), err) - } - } - var dClient *client.Client if cluster { restConfig, err := kubernetesConfig.ToRESTConfig() @@ -119,31 +116,61 @@ func Command() *cobra.Command { return sanitizedError.NewWithError("failed to load resources", err) } - newPolicies, err := mutatePolices(policies) - if err != nil { - return sanitizedError.NewWithError("failed to mutate policy", err) + mutatedPolicies, err := mutatePolices(policies) + msgPolicies := "1 policy" + if len(mutatedPolicies) > 1 { + msgPolicies = fmt.Sprintf("%d policies", len(policies)) } - for i, policy := range newPolicies { - for j, resource := range resources { - if !(j == 0 && i == 0) { - fmt.Printf("\n\n==========================================================================================\n") - } + msgResources := "1 resource" + if len(resources) > 1 { + msgResources = fmt.Sprintf("%d resources", len(resources)) + } - err = applyPolicyOnResource(policy, resource, mutatelogPath, mutatelogPathIsDir) + fmt.Printf("\napplying %s to %s \n", msgPolicies, msgResources) + + if len(mutatedPolicies) == 0 || len(resources) == 0 { + return + } + + rc := &resultCounts{} + for _, policy := range mutatedPolicies { + + err := policy2.Validate(utils.MarshalPolicy(*policy), nil, true, openAPIController) + if err != nil { + rc.skip += len(resources) + fmt.Printf("\nskipping policy %v as it is not valid: %v\n", policy.Name, err) + continue + } + + if common.PolicyHasVariables(*policy) { + rc.skip += len(resources) + fmt.Printf("\nskipping policy %s as policies with variables are not supported\n", policy.Name) + continue + } + + for _, resource := range resources { + applyPolicyOnResource(policy, resource, rc) if err != nil { return sanitizedError.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err) } } } + fmt.Printf("\npass: %d, fail: %d, warn: %d, error: %d, skip: %d \n", + rc.pass, rc.fail, rc.warn, rc.error, rc.skip) + + if rc.fail > 0 || rc.error > 0 { + os.Exit(1) + } + return nil }, } cmd.Flags().StringArrayVarP(&resourcePaths, "resource", "r", []string{}, "Path to resource files") cmd.Flags().BoolVarP(&cluster, "cluster", "c", false, "Checks if policies should be applied to cluster in the current context") - cmd.Flags().StringVarP(&mutatelogPath, "output", "o", "", "Prints the mutated resources in provided file/directory") + cmd.Flags().StringVarP(&mutateLogPath, "output", "o", "", "Prints the mutated resources in provided file/directory") return cmd } @@ -273,55 +300,45 @@ func getResource(path string) ([]*unstructured.Unstructured, error) { } // applyPolicyOnResource - function to apply policy on resource -func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured, mutatelogPath string, mutatelogPathIsDir bool) error { - fmt.Printf("\n\nApplying Policy %s on Resource %s/%s/%s\n", policy.Name, resource.GetNamespace(), resource.GetKind(), resource.GetName()) +func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured, rc *resultCounts) { + responseError := false + + resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName()) + log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath) mutateResponse := engine.Mutate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) if !mutateResponse.IsSuccessful() { - fmt.Printf("\n\nMutation:") - fmt.Printf("\nFailed to apply mutation") + fmt.Printf("Failed to apply mutate policy %s -> resource %s", policy.Name, resPath) for i, r := range mutateResponse.PolicyResponse.Rules { fmt.Printf("\n%d. %s", i+1, r.Message) } - fmt.Printf("\n\n") + responseError = true } else { if len(mutateResponse.PolicyResponse.Rules) > 0 { yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object) if err != nil { - return err + rc.error++ } - if mutatelogPath == "" { - fmt.Printf("\n\nMutation:\nMutation has been applied succesfully") - fmt.Printf("\n\n" + string(yamlEncodedResource)) - fmt.Printf("\n\n") - } else { - err := printMutatedOutput(mutatelogPath, mutatelogPathIsDir, string(yamlEncodedResource), resource.GetName()+"-mutated") - if err != nil { - return sanitizedError.NewWithError("failed to print mutated result", err) - } - fmt.Printf("\n\nMutation:\nMutation has been applied succesfully. Check the files.") + mutatedResource := string(yamlEncodedResource) + if len(strings.TrimSpace(mutatedResource)) > 0 { + fmt.Printf("mutate policy %s applied to %s:", policy.Name, resPath) + fmt.Printf("\n" + mutatedResource) + fmt.Printf("\n") } - - } else { - fmt.Printf("\n\nMutation:\nMutation skipped. Resource not matches the policy") } } validateResponse := engine.Validate(engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource}) if !validateResponse.IsSuccessful() { - fmt.Printf("\n\nValidation:") - fmt.Printf("\nResource is invalid") + fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath) for i, r := range validateResponse.PolicyResponse.Rules { - fmt.Printf("\n%d. %s", i+1, r.Message) - } - fmt.Printf("\n\n") - } else { - if len(validateResponse.PolicyResponse.Rules) > 0 { - fmt.Printf("\n\nValidation:") - fmt.Printf("\nResource is valid") - fmt.Printf("\n\n") + if !r.Success { + fmt.Printf("%d. %s: %s \n", i+1, r.Name, r.Message) + } } + + responseError = true } var policyHasGenerate bool @@ -334,20 +351,22 @@ func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst if policyHasGenerate { generateResponse := engine.Generate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) if len(generateResponse.PolicyResponse.Rules) > 0 { - fmt.Printf("\n\nGenerate:") - fmt.Printf("\nResource is valid") - fmt.Printf("\n\n") + log.Log.V(3).Info("generate resource is valid", "policy", policy.Name, "resource", resPath) } else { - fmt.Printf("\n\nGenerate:") - fmt.Printf("\nResource is invalid") + fmt.Printf("generate policy %s resource %s is invalid \n", policy.Name, resPath) for i, r := range generateResponse.PolicyResponse.Rules { - fmt.Printf("\n%d. %s", i+1, r.Message) + fmt.Printf("%d. %s \b", i+1, r.Message) } - fmt.Printf("\n\n") + + responseError = true } } - return nil + if responseError == true { + rc.fail++ + } else { + rc.pass++ + } } // mutatePolicies - function to apply mutation on policies @@ -394,7 +413,7 @@ func printMutatedOutput(mutatelogPath string, mutatelogPathIsDir bool, yaml stri return nil } -// createFileOrFolder - creating file or folder accoring to path provided +// createFileOrFolder - creating file or folder according to path provided func createFileOrFolder(mutatelogPath string, mutatelogPathIsDir bool) error { mutatelogPath = filepath.Clean(mutatelogPath) _, err := os.Stat(mutatelogPath) diff --git a/pkg/kyverno/common/common.go b/pkg/kyverno/common/common.go index 498ff4dd3e..8589c6e242 100644 --- a/pkg/kyverno/common/common.go +++ b/pkg/kyverno/common/common.go @@ -61,7 +61,7 @@ func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, error error) { } if errString != "" { - fmt.Println("falied to extract policies") + fmt.Println("failed to extract policies: %s", errString) os.Exit(2) }