From ba8eec9e678551652d62b68593e452797061761d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Thu, 5 Oct 2023 12:36:01 +0200 Subject: [PATCH 1/4] feat: add jp commands (#51) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add scan command Signed-off-by: Charles-Edouard Brétéché * feat: add jp commands Signed-off-by: Charles-Edouard Brétéché * codegen Signed-off-by: Charles-Edouard Brétéché --------- Signed-off-by: Charles-Edouard Brétéché --- docs/user/commands/kyverno-json.md | 1 + docs/user/commands/kyverno-json_jp.md | 25 +++ .../user/commands/kyverno-json_jp_function.md | 22 +++ docs/user/commands/kyverno-json_jp_parse.md | 23 +++ docs/user/commands/kyverno-json_jp_query.md | 26 +++ pkg/commands/jp/command.go | 27 +++ pkg/commands/jp/command_test.go | 66 +++++++ pkg/commands/jp/doc.go | 22 +++ pkg/commands/jp/function/command.go | 70 +++++++ pkg/commands/jp/function/command_test.go | 60 ++++++ pkg/commands/jp/function/doc.go | 18 ++ pkg/commands/jp/parse/command.go | 98 ++++++++++ pkg/commands/jp/parse/command_test.go | 58 ++++++ pkg/commands/jp/parse/doc.go | 30 +++ pkg/commands/jp/query/command.go | 178 ++++++++++++++++++ pkg/commands/jp/query/command_test.go | 58 ++++++ pkg/commands/jp/query/doc.go | 30 +++ pkg/commands/root.go | 4 +- pkg/commands/root_test.go | 2 +- pkg/commands/scan/command.go | 2 +- pkg/commands/scan/command_test.go | 2 +- pkg/engine/template/functions.go | 13 ++ pkg/engine/template/template.go | 9 +- 23 files changed, 832 insertions(+), 12 deletions(-) create mode 100644 docs/user/commands/kyverno-json_jp.md create mode 100644 docs/user/commands/kyverno-json_jp_function.md create mode 100644 docs/user/commands/kyverno-json_jp_parse.md create mode 100644 docs/user/commands/kyverno-json_jp_query.md create mode 100644 pkg/commands/jp/command.go create mode 100644 pkg/commands/jp/command_test.go create mode 100644 pkg/commands/jp/doc.go create mode 100644 pkg/commands/jp/function/command.go create mode 100644 pkg/commands/jp/function/command_test.go create mode 100644 pkg/commands/jp/function/doc.go create mode 100644 pkg/commands/jp/parse/command.go create mode 100644 pkg/commands/jp/parse/command_test.go create mode 100644 pkg/commands/jp/parse/doc.go create mode 100644 pkg/commands/jp/query/command.go create mode 100644 pkg/commands/jp/query/command_test.go create mode 100644 pkg/commands/jp/query/doc.go create mode 100644 pkg/engine/template/functions.go diff --git a/docs/user/commands/kyverno-json.md b/docs/user/commands/kyverno-json.md index 59769e26..a84a45e8 100644 --- a/docs/user/commands/kyverno-json.md +++ b/docs/user/commands/kyverno-json.md @@ -20,5 +20,6 @@ kyverno-json [flags] * [kyverno-json completion](kyverno-json_completion.md) - Generate the autocompletion script for the specified shell * [kyverno-json docs](kyverno-json_docs.md) - Generates reference documentation. +* [kyverno-json jp](kyverno-json_jp.md) - Provides a command-line interface to JMESPath, enhanced with custom functions. * [kyverno-json scan](kyverno-json_scan.md) - scan diff --git a/docs/user/commands/kyverno-json_jp.md b/docs/user/commands/kyverno-json_jp.md new file mode 100644 index 00000000..776cf4a9 --- /dev/null +++ b/docs/user/commands/kyverno-json_jp.md @@ -0,0 +1,25 @@ +## kyverno-json jp + +Provides a command-line interface to JMESPath, enhanced with custom functions. + +### Synopsis + +Provides a command-line interface to JMESPath, enhanced with custom functions. + +``` +kyverno-json jp [flags] +``` + +### Options + +``` + -h, --help help for jp +``` + +### SEE ALSO + +* [kyverno-json](kyverno-json.md) - kyverno-json +* [kyverno-json jp function](kyverno-json_jp_function.md) - Provides function informations. +* [kyverno-json jp parse](kyverno-json_jp_parse.md) - Parses jmespath expression and shows corresponding AST. +* [kyverno-json jp query](kyverno-json_jp_query.md) - Provides a command-line interface to JMESPath, enhanced with Kyverno specific custom functions. + diff --git a/docs/user/commands/kyverno-json_jp_function.md b/docs/user/commands/kyverno-json_jp_function.md new file mode 100644 index 00000000..11992238 --- /dev/null +++ b/docs/user/commands/kyverno-json_jp_function.md @@ -0,0 +1,22 @@ +## kyverno-json jp function + +Provides function informations. + +### Synopsis + +Provides function informations. + +``` +kyverno-json jp function [function_name]... [flags] +``` + +### Options + +``` + -h, --help help for function +``` + +### SEE ALSO + +* [kyverno-json jp](kyverno-json_jp.md) - Provides a command-line interface to JMESPath, enhanced with custom functions. + diff --git a/docs/user/commands/kyverno-json_jp_parse.md b/docs/user/commands/kyverno-json_jp_parse.md new file mode 100644 index 00000000..f879ebd3 --- /dev/null +++ b/docs/user/commands/kyverno-json_jp_parse.md @@ -0,0 +1,23 @@ +## kyverno-json jp parse + +Parses jmespath expression and shows corresponding AST. + +### Synopsis + +Parses jmespath expression and shows corresponding AST. + +``` +kyverno-json jp parse [-f file|expression]... [flags] +``` + +### Options + +``` + -f, --file strings Read input from a JSON or YAML file instead of stdin + -h, --help help for parse +``` + +### SEE ALSO + +* [kyverno-json jp](kyverno-json_jp.md) - Provides a command-line interface to JMESPath, enhanced with custom functions. + diff --git a/docs/user/commands/kyverno-json_jp_query.md b/docs/user/commands/kyverno-json_jp_query.md new file mode 100644 index 00000000..8a352f06 --- /dev/null +++ b/docs/user/commands/kyverno-json_jp_query.md @@ -0,0 +1,26 @@ +## kyverno-json jp query + +Provides a command-line interface to JMESPath, enhanced with Kyverno specific custom functions. + +### Synopsis + +Provides a command-line interface to JMESPath, enhanced with Kyverno specific custom functions. + +``` +kyverno-json jp query [-i input] [-q query|query]... [flags] +``` + +### Options + +``` + -c, --compact Produce compact JSON output that omits non essential whitespace + -h, --help help for query + -i, --input string Read input from a JSON or YAML file instead of stdin + -q, --query strings Read JMESPath expression from the specified file + -u, --unquoted If the final result is a string, it will be printed without quotes +``` + +### SEE ALSO + +* [kyverno-json jp](kyverno-json_jp.md) - Provides a command-line interface to JMESPath, enhanced with custom functions. + diff --git a/pkg/commands/jp/command.go b/pkg/commands/jp/command.go new file mode 100644 index 00000000..bcaae6e9 --- /dev/null +++ b/pkg/commands/jp/command.go @@ -0,0 +1,27 @@ +package jp + +import ( + "github.com/kyverno/kyverno-json/pkg/commands/jp/function" + "github.com/kyverno/kyverno-json/pkg/commands/jp/parse" + "github.com/kyverno/kyverno-json/pkg/commands/jp/query" + "github.com/spf13/cobra" +) + +func Command() *cobra.Command { + cmd := &cobra.Command{ + Use: "jp", + Short: "Provides a command-line interface to JMESPath, enhanced with custom functions.", + Long: "Provides a command-line interface to JMESPath, enhanced with custom functions.", + Args: cobra.NoArgs, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, _ []string) error { + return cmd.Help() + }, + } + cmd.AddCommand( + function.Command(), + parse.Command(), + query.Command(), + ) + return cmd +} diff --git a/pkg/commands/jp/command_test.go b/pkg/commands/jp/command_test.go new file mode 100644 index 00000000..dce9dfc0 --- /dev/null +++ b/pkg/commands/jp/command_test.go @@ -0,0 +1,66 @@ +package jp + +import ( + "bytes" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommand(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + err := cmd.Execute() + assert.NoError(t, err) +} + +func TestCommandWithArgs(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + cmd.SetArgs([]string{"foo"}) + err := cmd.Execute() + assert.Error(t, err) +} + +func TestCommandWithInvalidArg(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetErr(b) + cmd.SetArgs([]string{"foo"}) + err := cmd.Execute() + assert.Error(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + expected := `Error: unknown command "foo" for "jp"` + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestCommandWithInvalidFlag(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetErr(b) + cmd.SetArgs([]string{"--xxx"}) + err := cmd.Execute() + assert.Error(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + expected := `Error: unknown flag: --xxx` + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestCommandHelp(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"--help"}) + err := cmd.Execute() + assert.NoError(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(string(out), cmd.Long)) +} diff --git a/pkg/commands/jp/doc.go b/pkg/commands/jp/doc.go new file mode 100644 index 00000000..5d3e4d0d --- /dev/null +++ b/pkg/commands/jp/doc.go @@ -0,0 +1,22 @@ +package jp + +// var websiteUrl = `https://kyverno.io/docs/kyverno-cli/#jp` + +// var description = []string{ +// `Provides a command-line interface to JMESPath, enhanced with Kyverno specific custom functions.`, +// } + +// var examples = [][]string{ +// { +// "# List functions", +// "kyverno jp function", +// }, +// { +// "# Evaluate query", +// "kyverno jp query -i object.yaml 'request.object.metadata.name | truncate(@, `9`)'", +// }, +// { +// "# Parse expression", +// "kyverno jp parse 'request.object.metadata.name | truncate(@, `9`)'", +// }, +// } diff --git a/pkg/commands/jp/function/command.go b/pkg/commands/jp/function/command.go new file mode 100644 index 00000000..606f7dfc --- /dev/null +++ b/pkg/commands/jp/function/command.go @@ -0,0 +1,70 @@ +package function + +import ( + "cmp" + "fmt" + "io" + "slices" + "strings" + + jpfunctions "github.com/jmespath-community/go-jmespath/pkg/functions" + "github.com/kyverno/kyverno-json/pkg/engine/template" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/sets" +) + +func Command() *cobra.Command { + return &cobra.Command{ + Use: "function [function_name]...", + Short: "Provides function informations.", + Long: "Provides function informations.", + SilenceUsage: true, + Run: func(cmd *cobra.Command, args []string) { + printFunctions(cmd.OutOrStdout(), args...) + }, + } +} + +func functionString(f jpfunctions.FunctionEntry) string { + if f.Name == "" { + return "" + } + var args []string + for _, a := range f.Arguments { + var aTypes []string + for _, t := range a.Types { + aTypes = append(aTypes, string(t)) + } + args = append(args, strings.Join(aTypes, "|")) + } + // var returnArgs []string + // for _, ra := range f.ReturnType { + // returnArgs = append(returnArgs, string(ra)) + // } + // output := fmt.Sprintf("%s(%s) %s", f.Name, strings.Join(args, ", "), strings.Join(returnArgs, ",")) + output := fmt.Sprintf("%s(%s)", f.Name, strings.Join(args, ", ")) + // if f.Note != "" { + // output += fmt.Sprintf(" (%s)", f.Note) + // } + return output +} + +func printFunctions(out io.Writer, names ...string) { + funcs := template.GetFunctions() + slices.SortFunc(funcs, func(a, b jpfunctions.FunctionEntry) int { + return cmp.Compare(functionString(a), functionString(b)) + }) + namesSet := sets.New(names...) + for _, function := range funcs { + if len(namesSet) == 0 || namesSet.Has(function.Name) { + // note := function.Note + // function.Note = "" + fmt.Fprintln(out, "Name:", function.Name) + fmt.Fprintln(out, " Signature:", functionString(function)) + // if note != "" { + // fmt.Fprintln(out, " Note: ", note) + // } + fmt.Fprintln(out) + } + } +} diff --git a/pkg/commands/jp/function/command_test.go b/pkg/commands/jp/function/command_test.go new file mode 100644 index 00000000..2ad46cb7 --- /dev/null +++ b/pkg/commands/jp/function/command_test.go @@ -0,0 +1,60 @@ +package function + +import ( + "bytes" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommand(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + err := cmd.Execute() + assert.NoError(t, err) +} + +func TestCommandWithOneArg(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + cmd.SetArgs([]string{"truncate"}) + err := cmd.Execute() + assert.NoError(t, err) +} + +func TestCommandWithArgs(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + cmd.SetArgs([]string{"truncate", "to_upper"}) + err := cmd.Execute() + assert.NoError(t, err) +} + +func TestCommandWithInvalidFlag(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetErr(b) + cmd.SetArgs([]string{"--xxx"}) + err := cmd.Execute() + assert.Error(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + expected := `Error: unknown flag: --xxx` + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestCommandHelp(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"--help"}) + err := cmd.Execute() + assert.NoError(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(string(out), cmd.Long)) +} diff --git a/pkg/commands/jp/function/doc.go b/pkg/commands/jp/function/doc.go new file mode 100644 index 00000000..92586cc2 --- /dev/null +++ b/pkg/commands/jp/function/doc.go @@ -0,0 +1,18 @@ +package function + +// var websiteUrl = `https://kyverno.io/docs/kyverno-cli/#jp` + +// var description = []string{ +// `Provides function informations.`, +// } + +// var examples = [][]string{ +// { +// "# List functions", +// "kyverno jp function", +// }, +// { +// "# Get function infos", +// "kyverno jp function truncate", +// }, +// } diff --git a/pkg/commands/jp/parse/command.go b/pkg/commands/jp/parse/command.go new file mode 100644 index 00000000..d2b2983a --- /dev/null +++ b/pkg/commands/jp/parse/command.go @@ -0,0 +1,98 @@ +package parse + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/jmespath-community/go-jmespath/pkg/parsing" + "github.com/spf13/cobra" +) + +func Command() *cobra.Command { + var files []string + cmd := &cobra.Command{ + Use: "parse [-f file|expression]...", + Short: "Parses jmespath expression and shows corresponding AST.", + Long: "Parses jmespath expression and shows corresponding AST.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + expressions, err := loadExpressions(cmd, args, files) + if err != nil { + return err + } + for _, expression := range expressions { + if err := printAst(cmd.OutOrStdout(), expression); err != nil { + return err + } + } + return nil + }, + } + cmd.Flags().StringSliceVarP(&files, "file", "f", nil, "Read input from a JSON or YAML file instead of stdin") + return cmd +} + +func readFile(reader io.Reader) (string, error) { + data, err := io.ReadAll(reader) + if err != nil { + return "", err + } + return string(data), nil +} + +func loadFile(cmd *cobra.Command, file string) (string, error) { + reader, err := os.Open(filepath.Clean(file)) + if err != nil { + return "", fmt.Errorf("failed open file %s: %v", file, err) + } + defer func() { + if err := reader.Close(); err != nil { + fmt.Fprintf(cmd.OutOrStdout(), "Error closing file: %s\n", err) + } + }() + content, err := readFile(reader) + if err != nil { + return "", fmt.Errorf("failed read file %s: %v", file, err) + } + return content, nil +} + +func loadExpressions(cmd *cobra.Command, args []string, files []string) ([]string, error) { + var expressions []string + expressions = append(expressions, args...) + for _, file := range files { + expression, err := loadFile(cmd, file) + if err != nil { + return nil, err + } + expressions = append(expressions, expression) + } + if len(expressions) == 0 { + fmt.Fprintln(cmd.OutOrStdout(), "Reading from terminal input.") + fmt.Fprintln(cmd.OutOrStdout(), "Enter a jmespath expression and hit Ctrl+D.") + data, err := readFile(cmd.InOrStdin()) + if err != nil { + return nil, fmt.Errorf("failed to read file STDIN: %v", err) + } + expressions = append(expressions, data) + } + return expressions, nil +} + +// The following function has been adapted from +// https://github.com/jmespath/jp/blob/54882e03bd277fc4475a677fab1d35eaa478b839/jp.go +func printAst(out io.Writer, expression string) error { + parser := parsing.NewParser() + parsed, err := parser.Parse(expression) + if err != nil { + if syntaxError, ok := err.(parsing.SyntaxError); ok { + return fmt.Errorf("%w\n%s", syntaxError, syntaxError.HighlightLocation()) + } + return err + } + fmt.Fprintln(out, "#", expression) + fmt.Fprintln(out, parsed) + return nil +} diff --git a/pkg/commands/jp/parse/command_test.go b/pkg/commands/jp/parse/command_test.go new file mode 100644 index 00000000..c355ebb3 --- /dev/null +++ b/pkg/commands/jp/parse/command_test.go @@ -0,0 +1,58 @@ +package parse + +import ( + "bytes" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommand(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + cmd.SetArgs([]string{"request.object.metadata.name | truncate(@, `9`)"}) + err := cmd.Execute() + assert.NoError(t, err) +} + +func TestCommandWithInvalidArg(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetErr(b) + err := cmd.Execute() + assert.Error(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + expected := "Error: SyntaxError: Incomplete expression\n\n^" + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestCommandWithInvalidFlag(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetErr(b) + cmd.SetArgs([]string{"--xxx"}) + err := cmd.Execute() + assert.Error(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + expected := `Error: unknown flag: --xxx` + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestCommandHelp(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"--help"}) + err := cmd.Execute() + assert.NoError(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(string(out), cmd.Long)) +} diff --git a/pkg/commands/jp/parse/doc.go b/pkg/commands/jp/parse/doc.go new file mode 100644 index 00000000..110e3e3e --- /dev/null +++ b/pkg/commands/jp/parse/doc.go @@ -0,0 +1,30 @@ +package parse + +// var websiteUrl = `https://kyverno.io/docs/kyverno-cli/#jp` + +// var description = []string{ +// `Parses jmespath expression and shows corresponding AST.`, +// } + +// var examples = [][]string{ +// { +// "# Parse expression", +// "kyverno jp parse 'request.object.metadata.name | truncate(@, `9`)'", +// }, +// { +// "# Parse expression from a file", +// "kyverno jp parse -f my-file", +// }, +// { +// "# Parse expression from stdin", +// "kyverno jp parse", +// }, +// { +// "# Parse multiple expressionxs", +// "kyverno jp parse -f my-file1 -f my-file-2 'request.object.metadata.name | truncate(@, `9`)'", +// }, +// { +// "# Cat into", +// "cat my-file | kyverno jp parse", +// }, +// } diff --git a/pkg/commands/jp/query/command.go b/pkg/commands/jp/query/command.go new file mode 100644 index 00000000..d8f77857 --- /dev/null +++ b/pkg/commands/jp/query/command.go @@ -0,0 +1,178 @@ +package query + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/jmespath-community/go-jmespath/pkg/parsing" + "github.com/kyverno/kyverno-json/pkg/engine/template" + "github.com/spf13/cobra" + "sigs.k8s.io/yaml" +) + +func Command() *cobra.Command { + var compact, unquoted bool + var input string + var queries []string + cmd := &cobra.Command{ + Use: "query [-i input] [-q query|query]...", + Short: "Provides a command-line interface to JMESPath, enhanced with Kyverno specific custom functions.", + Long: "Provides a command-line interface to JMESPath, enhanced with Kyverno specific custom functions.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + queries, err := loadQueries(cmd, args, queries) + if err != nil { + return err + } + input, err := loadInput(cmd, input) + if err != nil { + return err + } + if len(queries) == 0 && input == nil { + return errors.New("at least one query or input object is required") + } + if len(queries) == 0 { + query, err := readQuery(cmd) + if err != nil { + return err + } + queries = append(queries, query) + } + if input == nil { + i, err := readInput(cmd) + if err != nil { + return err + } + input = i + } + for _, query := range queries { + result, err := evaluate(input, query) + if err != nil { + return err + } + if err := printResult(cmd, query, result, unquoted, compact); err != nil { + return err + } + } + return nil + }, + } + cmd.Flags().BoolVarP(&compact, "compact", "c", false, "Produce compact JSON output that omits non essential whitespace") + cmd.Flags().BoolVarP(&unquoted, "unquoted", "u", false, "If the final result is a string, it will be printed without quotes") + cmd.Flags().StringSliceVarP(&queries, "query", "q", nil, "Read JMESPath expression from the specified file") + cmd.Flags().StringVarP(&input, "input", "i", "", "Read input from a JSON or YAML file instead of stdin") + return cmd +} + +func readFile(reader io.Reader) ([]byte, error) { + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + return data, nil +} + +func loadFile(cmd *cobra.Command, file string) ([]byte, error) { + reader, err := os.Open(filepath.Clean(file)) + if err != nil { + return nil, fmt.Errorf("failed open file %s: %v", file, err) + } + defer func() { + if err := reader.Close(); err != nil { + fmt.Fprintf(cmd.OutOrStdout(), "Error closing file: %s\n", err) + } + }() + content, err := readFile(reader) + if err != nil { + return nil, fmt.Errorf("failed to read file %s: %v", file, err) + } + return content, nil +} + +func readQuery(cmd *cobra.Command) (string, error) { + fmt.Fprintln(cmd.OutOrStdout(), "Reading from terminal input.") + fmt.Fprintln(cmd.OutOrStdout(), "Enter a jmespath expression and hit Ctrl+D.") + data, err := readFile(cmd.InOrStdin()) + if err != nil { + return "", err + } + return string(data), nil +} + +func loadQueries(cmd *cobra.Command, args []string, files []string) ([]string, error) { + var queries []string + queries = append(queries, args...) + for _, file := range files { + query, err := loadFile(cmd, file) + if err != nil { + return nil, err + } + queries = append(queries, string(query)) + } + return queries, nil +} + +func readInput(cmd *cobra.Command) (interface{}, error) { + fmt.Fprintln(cmd.OutOrStdout(), "Reading from terminal input.") + fmt.Fprintln(cmd.OutOrStdout(), "Enter input object and hit Ctrl+D.") + data, err := readFile(cmd.InOrStdin()) + if err != nil { + return nil, err + } + var input interface{} + if err := yaml.Unmarshal(data, &input); err != nil { + return nil, fmt.Errorf("error parsing input json: %w", err) + } + return input, nil +} + +func loadInput(cmd *cobra.Command, file string) (interface{}, error) { + if file == "" { + return nil, nil + } + data, err := loadFile(cmd, file) + if err != nil { + return nil, err + } + var input interface{} + if err := yaml.Unmarshal(data, &input); err != nil { + return nil, fmt.Errorf("error parsing input json: %w", err) + } + return input, nil +} + +func evaluate(input interface{}, query string) (interface{}, error) { + result, err := template.Execute(query, input, nil) + if err != nil { + if syntaxError, ok := err.(parsing.SyntaxError); ok { + return nil, fmt.Errorf("%s\n%s", syntaxError, syntaxError.HighlightLocation()) + } + return nil, fmt.Errorf("error evaluating JMESPath expression: %w", err) + } + return result, nil +} + +func printResult(cmd *cobra.Command, query string, result interface{}, unquoted bool, compact bool) error { + converted, isString := result.(string) + fmt.Fprintln(cmd.OutOrStdout(), "#", query) + if unquoted && isString { + fmt.Fprintln(cmd.OutOrStdout(), converted) + } else { + var toJSON []byte + var err error + if compact { + toJSON, err = json.Marshal(result) + } else { + toJSON, err = json.MarshalIndent(result, "", " ") + } + if err != nil { + return fmt.Errorf("error marshalling result to JSON: %w", err) + } + fmt.Fprintln(cmd.OutOrStdout(), string(toJSON)) + } + return nil +} diff --git a/pkg/commands/jp/query/command_test.go b/pkg/commands/jp/query/command_test.go new file mode 100644 index 00000000..a4cbd1d6 --- /dev/null +++ b/pkg/commands/jp/query/command_test.go @@ -0,0 +1,58 @@ +package query + +import ( + "bytes" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommand(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + cmd.SetArgs([]string{"-i", "object.yaml", "-q", "query-file"}) + err := cmd.Execute() + assert.Error(t, err) +} + +func TestCommandWithInvalidArg(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetErr(b) + err := cmd.Execute() + assert.Error(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + expected := `Error: at least one query or input object is required` + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestCommandWithInvalidFlag(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetErr(b) + cmd.SetArgs([]string{"--xxx"}) + err := cmd.Execute() + assert.Error(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + expected := `Error: unknown flag: --xxx` + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) +} + +func TestCommandHelp(t *testing.T) { + cmd := Command() + assert.NotNil(t, cmd) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"--help"}) + err := cmd.Execute() + assert.NoError(t, err) + out, err := io.ReadAll(b) + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(string(out), cmd.Long)) +} diff --git a/pkg/commands/jp/query/doc.go b/pkg/commands/jp/query/doc.go new file mode 100644 index 00000000..2d72d6d5 --- /dev/null +++ b/pkg/commands/jp/query/doc.go @@ -0,0 +1,30 @@ +package query + +// var websiteUrl = `https://kyverno.io/docs/kyverno-cli/#jp` + +// var description = []string{ +// `Provides a command-line interface to JMESPath, enhanced with Kyverno specific custom functions.`, +// } + +// var examples = [][]string{ +// { +// "# Evaluate query", +// "kyverno jp query -i object.yaml 'request.object.metadata.name | truncate(@, `9`)'", +// }, +// { +// "# Evaluate query", +// "kyverno jp query -i object.yaml -q query-file", +// }, +// { +// "# Evaluate multiple queries", +// "kyverno jp query -i object.yaml -q query-file-1 -q query-file-2 'request.object.metadata.name | truncate(@, `9`)'", +// }, +// { +// "# Cat query into", +// "cat query-file | kyverno jp query -i object.yaml", +// }, +// { +// "# Cat object into", +// "cat object.yaml | kyverno jp query -q query-file", +// }, +// } diff --git a/pkg/commands/root.go b/pkg/commands/root.go index d2cd6074..b48f0e70 100644 --- a/pkg/commands/root.go +++ b/pkg/commands/root.go @@ -2,6 +2,7 @@ package commands import ( "github.com/kyverno/kyverno-json/pkg/commands/docs" + "github.com/kyverno/kyverno-json/pkg/commands/jp" "github.com/kyverno/kyverno-json/pkg/commands/scan" "github.com/spf13/cobra" ) @@ -18,7 +19,8 @@ func RootCommand() *cobra.Command { } cmd.AddCommand( docs.Command(cmd), - scan.Command(cmd), + jp.Command(), + scan.Command(), ) return cmd } diff --git a/pkg/commands/root_test.go b/pkg/commands/root_test.go index 1fb09f69..1593147d 100644 --- a/pkg/commands/root_test.go +++ b/pkg/commands/root_test.go @@ -12,7 +12,7 @@ import ( func TestRootCommand(t *testing.T) { cmd := RootCommand() assert.NotNil(t, cmd) - assert.Len(t, cmd.Commands(), 2) + assert.Len(t, cmd.Commands(), 3) err := cmd.Execute() assert.NoError(t, err) } diff --git a/pkg/commands/scan/command.go b/pkg/commands/scan/command.go index 1b3ba872..b124e44f 100644 --- a/pkg/commands/scan/command.go +++ b/pkg/commands/scan/command.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" ) -func Command(root *cobra.Command) *cobra.Command { +func Command() *cobra.Command { var command options cmd := &cobra.Command{ Use: "scan", diff --git a/pkg/commands/scan/command_test.go b/pkg/commands/scan/command_test.go index d79de047..a0d2980b 100644 --- a/pkg/commands/scan/command_test.go +++ b/pkg/commands/scan/command_test.go @@ -70,7 +70,7 @@ func Test_Execute(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cmd := Command(nil) + cmd := Command() assert.NotNil(t, cmd) var args []string args = append(args, "--payload", tt.payload) diff --git a/pkg/engine/template/functions.go b/pkg/engine/template/functions.go new file mode 100644 index 00000000..5b34261b --- /dev/null +++ b/pkg/engine/template/functions.go @@ -0,0 +1,13 @@ +package template + +import ( + jpfunctions "github.com/jmespath-community/go-jmespath/pkg/functions" + "github.com/kyverno/kyverno-json/pkg/engine/template/functions" +) + +func GetFunctions() []jpfunctions.FunctionEntry { + var funcs []jpfunctions.FunctionEntry + funcs = append(funcs, jpfunctions.GetDefaultFunctions()...) + funcs = append(funcs, functions.GetFunctions()...) + return funcs +} diff --git a/pkg/engine/template/template.go b/pkg/engine/template/template.go index ab82255e..ae6b927d 100644 --- a/pkg/engine/template/template.go +++ b/pkg/engine/template/template.go @@ -6,21 +6,14 @@ import ( "strings" "github.com/jmespath-community/go-jmespath/pkg/binding" - jpfunctions "github.com/jmespath-community/go-jmespath/pkg/functions" "github.com/jmespath-community/go-jmespath/pkg/interpreter" "github.com/jmespath-community/go-jmespath/pkg/parsing" - "github.com/kyverno/kyverno-json/pkg/engine/template/functions" ) var ( variable = regexp.MustCompile(`{{(.*?)}}`) parser = parsing.NewParser() - caller = interpreter.NewFunctionCaller(func() []jpfunctions.FunctionEntry { - var funcs []jpfunctions.FunctionEntry - funcs = append(funcs, jpfunctions.GetDefaultFunctions()...) - funcs = append(funcs, functions.GetFunctions()...) - return funcs - }()...) + caller = interpreter.NewFunctionCaller(GetFunctions()...) ) func String(in string, value interface{}, bindings binding.Bindings) string { From 7974827ec909a906ee3bb16fd237ea47c54232bc Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Thu, 5 Oct 2023 17:46:26 +0530 Subject: [PATCH 2/4] fix: update dead link in readme (#59) Signed-off-by: Vishal Choudhary --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5fe0cc79..85c8fc6b 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Both [Kyverno policies](https://kyverno.io/docs/kyverno-policies/) and policies [Kyverno policies](https://kyverno.io/docs/kyverno-policies/) use [Kubernetes](https://kubernetes.io) specific constructs for that matter that didn't map well with arbitrary payloads. -This tool uses [assertion trees](#assertion-trees-replace-pattern-syntax) to implement `match` and `exclude` statements: +This tool uses [assertion trees](#assertion-trees-replace-pattern-matching) to implement `match` and `exclude` statements: ```yaml apiVersion: json.kyverno.io/v1alpha1 @@ -104,7 +104,7 @@ spec: Team: ($expectedTeam) ``` -Note that all context entries are lazily evaluated, a context entry will only be evaluated once. They can be used in all [assertion trees](#assertion-trees-replace-pattern-syntax), including `match` and `exclude` statements. +Note that all context entries are lazily evaluated, a context entry will only be evaluated once. They can be used in all [assertion trees](#assertion-trees-replace-pattern-matching), including `match` and `exclude` statements. ### No preconditions, pattern operators, anchors or wildcards @@ -113,7 +113,7 @@ Policies used by this tool don't support `preconditions`, pattern operators, anc Most of the time `preconditions` can be replaced by the more flexible `match` and `exclude` statements. Pattern operators, anchors and wildcards can be replaced with an improved pattern matching system. -The new pattern matching system is called *assertion trees*, this is detailed [below](#assertion-trees-replace-pattern-syntax). +The new pattern matching system is called *assertion trees*, this is detailed [below](#assertion-trees-replace-pattern-matching). ### Assertion trees replace pattern matching From 29add3c6add17b4d53758cf873a277573ff606df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Thu, 5 Oct 2023 14:19:31 +0200 Subject: [PATCH 3/4] chore: add more command docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- go.mod | 2 +- pkg/command/command.go | 72 ++++++++++++++++++++++++ pkg/command/example.go | 6 ++ pkg/command/option.go | 24 ++++++++ pkg/commands/docs/command.go | 32 +++++++++-- pkg/commands/docs/doc.go | 12 ---- pkg/commands/jp/command.go | 17 ++++-- pkg/commands/jp/command_test.go | 10 ++-- pkg/commands/jp/doc.go | 22 -------- pkg/commands/jp/function/command.go | 14 ++++- pkg/commands/jp/function/command_test.go | 10 ++-- pkg/commands/jp/function/doc.go | 18 ------ pkg/commands/root.go | 2 +- 13 files changed, 166 insertions(+), 75 deletions(-) create mode 100644 pkg/command/command.go create mode 100644 pkg/command/example.go create mode 100644 pkg/command/option.go delete mode 100644 pkg/commands/docs/doc.go delete mode 100644 pkg/commands/jp/doc.go delete mode 100644 pkg/commands/jp/function/doc.go diff --git a/go.mod b/go.mod index 516da380..2a8e674b 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.28.2 sigs.k8s.io/kubectl-validate v0.0.0-20230927155409-3b3ca3ad91d0 + sigs.k8s.io/yaml v1.3.0 ) require ( @@ -301,5 +302,4 @@ require ( sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/release-utils v0.7.4 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/pkg/command/command.go b/pkg/command/command.go new file mode 100644 index 00000000..277f08b5 --- /dev/null +++ b/pkg/command/command.go @@ -0,0 +1,72 @@ +package command + +import ( + "strings" + + "github.com/spf13/cobra" +) + +type Command struct { + parent *cobra.Command + experimental bool + description []string + websiteUrl string + examples []Example +} + +func new(parent *cobra.Command, experimental bool, options ...option) Command { + cmd := Command{ + parent: parent, + experimental: experimental, + } + for _, opt := range options { + if opt != nil { + opt(&cmd) + } + } + return cmd +} + +func New(parent *cobra.Command, options ...option) Command { + return new(parent, false, options...) +} + +func NewExperimental(parent *cobra.Command, options ...option) Command { + return new(parent, true, options...) +} + +func Description(c Command, short bool) string { + if len(c.description) == 0 { + return "" + } + var lines []string + lines = append(lines, c.description[0]) + if !short { + lines = append(lines, "") + lines = append(lines, c.description[1:]...) + if c.experimental { + lines = append(lines, "", "NOTE: This is an experimental command.") + } + if c.websiteUrl != "" { + lines = append(lines, "", "For more information visit "+c.websiteUrl) + } + } + return strings.Join(lines, "\n") +} + +func Examples(c Command) string { + if len(c.examples) == 0 { + return "" + } + var useLine string + if c.parent != nil { + useLine = c.parent.UseLine() + " " + } + var lines []string + for _, example := range c.examples { + lines = append(lines, " # "+example.title) + lines = append(lines, " "+useLine+example.command) + lines = append(lines, "") + } + return strings.Join(lines, "\n") +} diff --git a/pkg/command/example.go b/pkg/command/example.go new file mode 100644 index 00000000..83a44043 --- /dev/null +++ b/pkg/command/example.go @@ -0,0 +1,6 @@ +package command + +type Example struct { + title string + command string +} diff --git a/pkg/command/option.go b/pkg/command/option.go new file mode 100644 index 00000000..8813a449 --- /dev/null +++ b/pkg/command/option.go @@ -0,0 +1,24 @@ +package command + +type option = func(*Command) + +func WithDescription(description ...string) option { + return func(d *Command) { + d.description = description + } +} + +func WithWebsiteUrl(websiteUrl string) option { + return func(d *Command) { + d.websiteUrl = websiteUrl + } +} + +func WithExample(title, command string) option { + return func(d *Command) { + d.examples = append(d.examples, Example{ + title: title, + command: command, + }) + } +} diff --git a/pkg/commands/docs/command.go b/pkg/commands/docs/command.go index abb2b33a..9d11ba21 100644 --- a/pkg/commands/docs/command.go +++ b/pkg/commands/docs/command.go @@ -3,19 +3,43 @@ package docs import ( "log" - "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/command" + "github.com/kyverno/kyverno-json/pkg/command" "github.com/spf13/cobra" ) -func Command(root *cobra.Command) *cobra.Command { +func Command(parent *cobra.Command) *cobra.Command { var options options + doc := command.New( + parent, + command.WithDescription( + "Generates reference documentation.", + "The docs command generates CLI reference documentation.", + "It can be used to generate simple markdown files or markdown to be used for the website.", + ), + command.WithExample( + "Generate simple markdown documentation", + "docs -o . --autogenTag=false", + ), + command.WithExample( + "Generate website documentation", + "docs -o . --website", + ), + ) cmd := &cobra.Command{ Use: "docs", - Short: command.FormatDescription(true, websiteUrl, false, description...), - Long: command.FormatDescription(false, websiteUrl, false, description...), + Short: command.Description(doc, true), + Long: command.Description(doc, false), + Example: command.Examples(doc), Args: cobra.NoArgs, SilenceUsage: true, RunE: func(cmd *cobra.Command, _ []string) error { + root := cmd + for { + if !root.HasParent() { + break + } + root = root.Parent() + } if err := options.validate(root); err != nil { return err } diff --git a/pkg/commands/docs/doc.go b/pkg/commands/docs/doc.go deleted file mode 100644 index 53545e0c..00000000 --- a/pkg/commands/docs/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -package docs - -// TODO -var websiteUrl = `` - -var description = []string{ - `Generates reference documentation.`, - ``, - `The docs command generates CLI reference documentation.`, - ``, - `It can be used to generate simple markdown files or markdown to be used for the website.`, -} diff --git a/pkg/commands/jp/command.go b/pkg/commands/jp/command.go index bcaae6e9..60cc52b5 100644 --- a/pkg/commands/jp/command.go +++ b/pkg/commands/jp/command.go @@ -1,17 +1,26 @@ package jp import ( + "github.com/kyverno/kyverno-json/pkg/command" "github.com/kyverno/kyverno-json/pkg/commands/jp/function" "github.com/kyverno/kyverno-json/pkg/commands/jp/parse" "github.com/kyverno/kyverno-json/pkg/commands/jp/query" "github.com/spf13/cobra" ) -func Command() *cobra.Command { +func Command(parent *cobra.Command) *cobra.Command { + doc := command.New( + parent, + command.WithDescription("Provides a command-line interface to JMESPath, enhanced with custom functions."), + command.WithExample("List functions", "jp function"), + command.WithExample("Evaluate query", "jp query -i object.yaml 'request.object.metadata.name | truncate(@, `9`)'"), + command.WithExample("Parse expression", "jp parse 'request.object.metadata.name | truncate(@, `9`)'"), + ) cmd := &cobra.Command{ Use: "jp", - Short: "Provides a command-line interface to JMESPath, enhanced with custom functions.", - Long: "Provides a command-line interface to JMESPath, enhanced with custom functions.", + Short: command.Description(doc, true), + Long: command.Description(doc, false), + Example: command.Examples(doc), Args: cobra.NoArgs, SilenceUsage: true, RunE: func(cmd *cobra.Command, _ []string) error { @@ -19,7 +28,7 @@ func Command() *cobra.Command { }, } cmd.AddCommand( - function.Command(), + function.Command(cmd), parse.Command(), query.Command(), ) diff --git a/pkg/commands/jp/command_test.go b/pkg/commands/jp/command_test.go index dce9dfc0..0f3e1b56 100644 --- a/pkg/commands/jp/command_test.go +++ b/pkg/commands/jp/command_test.go @@ -10,14 +10,14 @@ import ( ) func TestCommand(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) err := cmd.Execute() assert.NoError(t, err) } func TestCommandWithArgs(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) cmd.SetArgs([]string{"foo"}) err := cmd.Execute() @@ -25,7 +25,7 @@ func TestCommandWithArgs(t *testing.T) { } func TestCommandWithInvalidArg(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) b := bytes.NewBufferString("") cmd.SetErr(b) @@ -39,7 +39,7 @@ func TestCommandWithInvalidArg(t *testing.T) { } func TestCommandWithInvalidFlag(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) b := bytes.NewBufferString("") cmd.SetErr(b) @@ -53,7 +53,7 @@ func TestCommandWithInvalidFlag(t *testing.T) { } func TestCommandHelp(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) b := bytes.NewBufferString("") cmd.SetOut(b) diff --git a/pkg/commands/jp/doc.go b/pkg/commands/jp/doc.go deleted file mode 100644 index 5d3e4d0d..00000000 --- a/pkg/commands/jp/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -package jp - -// var websiteUrl = `https://kyverno.io/docs/kyverno-cli/#jp` - -// var description = []string{ -// `Provides a command-line interface to JMESPath, enhanced with Kyverno specific custom functions.`, -// } - -// var examples = [][]string{ -// { -// "# List functions", -// "kyverno jp function", -// }, -// { -// "# Evaluate query", -// "kyverno jp query -i object.yaml 'request.object.metadata.name | truncate(@, `9`)'", -// }, -// { -// "# Parse expression", -// "kyverno jp parse 'request.object.metadata.name | truncate(@, `9`)'", -// }, -// } diff --git a/pkg/commands/jp/function/command.go b/pkg/commands/jp/function/command.go index 606f7dfc..51aa0dac 100644 --- a/pkg/commands/jp/function/command.go +++ b/pkg/commands/jp/function/command.go @@ -8,16 +8,24 @@ import ( "strings" jpfunctions "github.com/jmespath-community/go-jmespath/pkg/functions" + "github.com/kyverno/kyverno-json/pkg/command" "github.com/kyverno/kyverno-json/pkg/engine/template" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/util/sets" ) -func Command() *cobra.Command { +func Command(parent *cobra.Command) *cobra.Command { + doc := command.New( + parent, + command.WithDescription("Provides function informations."), + command.WithExample("List functions", "function"), + command.WithExample("Get function infos", "function truncate"), + ) return &cobra.Command{ Use: "function [function_name]...", - Short: "Provides function informations.", - Long: "Provides function informations.", + Short: command.Description(doc, true), + Long: command.Description(doc, false), + Example: command.Examples(doc), SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { printFunctions(cmd.OutOrStdout(), args...) diff --git a/pkg/commands/jp/function/command_test.go b/pkg/commands/jp/function/command_test.go index 2ad46cb7..9c506081 100644 --- a/pkg/commands/jp/function/command_test.go +++ b/pkg/commands/jp/function/command_test.go @@ -10,14 +10,14 @@ import ( ) func TestCommand(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) err := cmd.Execute() assert.NoError(t, err) } func TestCommandWithOneArg(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) cmd.SetArgs([]string{"truncate"}) err := cmd.Execute() @@ -25,7 +25,7 @@ func TestCommandWithOneArg(t *testing.T) { } func TestCommandWithArgs(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) cmd.SetArgs([]string{"truncate", "to_upper"}) err := cmd.Execute() @@ -33,7 +33,7 @@ func TestCommandWithArgs(t *testing.T) { } func TestCommandWithInvalidFlag(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) b := bytes.NewBufferString("") cmd.SetErr(b) @@ -47,7 +47,7 @@ func TestCommandWithInvalidFlag(t *testing.T) { } func TestCommandHelp(t *testing.T) { - cmd := Command() + cmd := Command(nil) assert.NotNil(t, cmd) b := bytes.NewBufferString("") cmd.SetOut(b) diff --git a/pkg/commands/jp/function/doc.go b/pkg/commands/jp/function/doc.go deleted file mode 100644 index 92586cc2..00000000 --- a/pkg/commands/jp/function/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -package function - -// var websiteUrl = `https://kyverno.io/docs/kyverno-cli/#jp` - -// var description = []string{ -// `Provides function informations.`, -// } - -// var examples = [][]string{ -// { -// "# List functions", -// "kyverno jp function", -// }, -// { -// "# Get function infos", -// "kyverno jp function truncate", -// }, -// } diff --git a/pkg/commands/root.go b/pkg/commands/root.go index b48f0e70..acbb93af 100644 --- a/pkg/commands/root.go +++ b/pkg/commands/root.go @@ -19,7 +19,7 @@ func RootCommand() *cobra.Command { } cmd.AddCommand( docs.Command(cmd), - jp.Command(), + jp.Command(cmd), scan.Command(), ) return cmd From 5a1cf079bf57177a854734ade5823876699f4cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Thu, 5 Oct 2023 14:22:51 +0200 Subject: [PATCH 4/4] codegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- docs/user/commands/kyverno-json_docs.md | 18 ++++++++++++++---- docs/user/commands/kyverno-json_jp.md | 15 +++++++++++++++ docs/user/commands/kyverno-json_jp_function.md | 12 ++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/user/commands/kyverno-json_docs.md b/docs/user/commands/kyverno-json_docs.md index a4f76388..00002354 100644 --- a/docs/user/commands/kyverno-json_docs.md +++ b/docs/user/commands/kyverno-json_docs.md @@ -5,15 +5,25 @@ Generates reference documentation. ### Synopsis Generates reference documentation. - - The docs command generates CLI reference documentation. - - It can be used to generate simple markdown files or markdown to be used for the website. + +The docs command generates CLI reference documentation. +It can be used to generate simple markdown files or markdown to be used for the website. ``` kyverno-json docs [flags] ``` +### Examples + +``` + # Generate simple markdown documentation + kyverno-json docs -o . --autogenTag=false + + # Generate website documentation + kyverno-json docs -o . --website + +``` + ### Options ``` diff --git a/docs/user/commands/kyverno-json_jp.md b/docs/user/commands/kyverno-json_jp.md index 776cf4a9..be5ec22c 100644 --- a/docs/user/commands/kyverno-json_jp.md +++ b/docs/user/commands/kyverno-json_jp.md @@ -6,10 +6,25 @@ Provides a command-line interface to JMESPath, enhanced with custom functions. Provides a command-line interface to JMESPath, enhanced with custom functions. + ``` kyverno-json jp [flags] ``` +### Examples + +``` + # List functions + kyverno-json jp function + + # Evaluate query + kyverno-json jp query -i object.yaml 'request.object.metadata.name | truncate(@, `9`)' + + # Parse expression + kyverno-json jp parse 'request.object.metadata.name | truncate(@, `9`)' + +``` + ### Options ``` diff --git a/docs/user/commands/kyverno-json_jp_function.md b/docs/user/commands/kyverno-json_jp_function.md index 11992238..e577f5a8 100644 --- a/docs/user/commands/kyverno-json_jp_function.md +++ b/docs/user/commands/kyverno-json_jp_function.md @@ -6,10 +6,22 @@ Provides function informations. Provides function informations. + ``` kyverno-json jp function [function_name]... [flags] ``` +### Examples + +``` + # List functions + jp function + + # Get function infos + jp function truncate + +``` + ### Options ```