diff --git a/pkg/commands/scan/command.go b/pkg/commands/scan/command.go index 0b4b922c..10ae111a 100644 --- a/pkg/commands/scan/command.go +++ b/pkg/commands/scan/command.go @@ -18,5 +18,6 @@ func Command() *cobra.Command { cmd.Flags().StringSliceVar(&command.preprocessors, "pre-process", nil, "JMESPath expression used to pre process payload") cmd.Flags().StringSliceVar(&command.policies, "policy", nil, "Path to kyverno-json policies") cmd.Flags().StringSliceVar(&command.selectors, "labels", nil, "Labels selectors for policies") + cmd.Flags().StringVar(&command.output, "output", "text", "Output format (text or json)") return cmd } diff --git a/pkg/commands/scan/options.go b/pkg/commands/scan/options.go index 14b7177b..d8579b12 100644 --- a/pkg/commands/scan/options.go +++ b/pkg/commands/scan/options.go @@ -21,11 +21,12 @@ type options struct { preprocessors []string policies []string selectors []string + output string } func (c *options) run(cmd *cobra.Command, _ []string) error { - out := cmd.OutOrStdout() - fmt.Fprintln(out, "Loading policies ...") + out := newOutput(cmd.OutOrStdout(), c.output) + out.println("Loading policies ...") policies, err := policy.Load(c.policies...) if err != nil { return err @@ -47,7 +48,7 @@ func (c *options) run(cmd *cobra.Command, _ []string) error { } policies = filteredPolicies } - fmt.Fprintln(out, "Loading payload ...") + out.println("Loading payload ...") payload, err := payload.Load(c.payload) if err != nil { return err @@ -55,7 +56,7 @@ func (c *options) run(cmd *cobra.Command, _ []string) error { if payload == nil { return errors.New("payload is `null`") } - fmt.Fprintln(out, "Pre processing ...") + out.println("Pre processing ...") for _, preprocessor := range c.preprocessors { result, err := template.Execute(context.Background(), preprocessor, payload, nil) if err != nil { @@ -72,7 +73,7 @@ func (c *options) run(cmd *cobra.Command, _ []string) error { } else { resources = append(resources, payload) } - fmt.Fprintln(out, "Running", "(", "evaluating", len(resources), pluralize.Pluralize(len(resources), "resource", "resources"), "against", len(policies), pluralize.Pluralize(len(policies), "policy", "policies"), ")", "...") + out.println("Running", "(", "evaluating", len(resources), pluralize.Pluralize(len(resources), "resource", "resources"), "against", len(policies), pluralize.Pluralize(len(policies), "policy", "policies"), ")", "...") e := jsonengine.New() responses := e.Run(context.Background(), jsonengine.Request{ Resources: resources, @@ -80,14 +81,14 @@ func (c *options) run(cmd *cobra.Command, _ []string) error { }) for _, response := range responses { if response.Result == jsonengine.StatusFail { - fmt.Fprintln(out, "-", response.PolicyName, "/", response.RuleName, "/", response.Identifier, "FAILED:", response.Message) + out.println("-", response.PolicyName, "/", response.RuleName, "/", response.Identifier, "FAILED:", response.Message) } else if response.Result == jsonengine.StatusError { - fmt.Fprintln(out, "-", response.PolicyName, "/", response.RuleName, "/", response.Identifier, "ERROR:", response.Message) + out.println("-", response.PolicyName, "/", response.RuleName, "/", response.Identifier, "ERROR:", response.Message) } else { // TODO: handle skip, warn - fmt.Fprintln(out, "-", response.PolicyName, "/", response.RuleName, "/", response.Identifier, "PASSED") + out.println("-", response.PolicyName, "/", response.RuleName, "/", response.Identifier, "PASSED") } } - fmt.Fprintln(out, "Done") + out.println("Done") return nil } diff --git a/pkg/commands/scan/output.go b/pkg/commands/scan/output.go new file mode 100644 index 00000000..80ab8d54 --- /dev/null +++ b/pkg/commands/scan/output.go @@ -0,0 +1,48 @@ +package scan + +import ( + "encoding/json" + "fmt" + "io" + + jsonengine "github.com/kyverno/kyverno-json/pkg/json-engine" +) + +type output interface { + println(args ...any) + responses(responses ...jsonengine.RuleResponse) +} + +type textOutput struct { + out io.Writer +} + +func (t *textOutput) println(args ...any) { + fmt.Fprintln(t.out, args...) +} + +func (t *textOutput) responses(responses ...jsonengine.RuleResponse) { +} + +type jsonOutput struct { + out io.Writer +} + +func (t *jsonOutput) println(args ...any) { +} + +func (t *jsonOutput) responses(responses ...jsonengine.RuleResponse) { + payload, err := json.MarshalIndent(&jsonengine.Response{Results: responses}, "", " ") + if err != nil { + fmt.Fprintln(t.out, err) + } else { + fmt.Fprintln(t.out, string(payload)) + } +} + +func newOutput(out io.Writer, format string) output { + if format == "json" { + return &jsonOutput{out: out} + } + return &textOutput{out: out} +} diff --git a/website/docs/cli/commands/kyverno-json_scan.md b/website/docs/cli/commands/kyverno-json_scan.md index f3daddf6..1aef9f72 100644 --- a/website/docs/cli/commands/kyverno-json_scan.md +++ b/website/docs/cli/commands/kyverno-json_scan.md @@ -15,6 +15,7 @@ kyverno-json scan [flags] ``` -h, --help help for scan --labels strings Labels selectors for policies + --output string Output format (text or json) (default "text") --payload string Path to payload (json or yaml file) --policy strings Path to kyverno-json policies --pre-process strings JMESPath expression used to pre process payload