1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

refactor: cli jp command (#5552)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2022-12-03 19:18:01 +01:00 committed by GitHub
parent 13866c4504
commit a7b4089613
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -6,12 +6,11 @@ import (
"io"
"os"
"path/filepath"
"sort"
"strings"
gojmespath "github.com/jmespath/go-jmespath"
jmespath "github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
"sigs.k8s.io/yaml"
)
@ -31,87 +30,29 @@ func Command() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
if listFunctions {
printFunctionList()
return nil
}
// The following function has been adapted from
// https://github.com/jmespath/jp/blob/54882e03bd277fc4475a677fab1d35eaa478b839/jp.go
var expression string
if exprFile != "" {
byteExpr, err := os.ReadFile(filepath.Clean(exprFile))
if err != nil {
return fmt.Errorf("error opening expression file: %w", err)
}
expression = string(byteExpr)
} else {
if len(args) == 0 {
return fmt.Errorf("must provide at least one argument")
}
expression = args[0]
}
if ast {
parser := gojmespath.NewParser()
parsed, err := parser.Parse(expression)
expression, err := loadExpression(exprFile, args)
if err != nil {
if syntaxError, ok := err.(gojmespath.SyntaxError); ok {
return fmt.Errorf("%w\n%s",
syntaxError,
syntaxError.HighlightLocation())
}
return err
}
fmt.Printf("%s", parsed)
return nil
}
var input interface{}
if filename != "" {
f, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return fmt.Errorf("error opening input file: %w", err)
}
if err := yaml.Unmarshal(f, &input); err != nil {
return fmt.Errorf("error parsing input json: %w", err)
}
} else {
f, err := io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("error opening input file: %w", err)
}
if err := yaml.Unmarshal(f, &input); err != nil {
return fmt.Errorf("error parsing input json: %w", err)
}
}
jp, err := jmespath.New(expression)
if err != nil {
return fmt.Errorf("failed to compile JMESPath: %s, error: %v", expression, err)
}
result, err := jp.Search(input)
if err != nil {
if syntaxError, ok := err.(gojmespath.SyntaxError); ok {
return fmt.Errorf("%s\n%s",
syntaxError,
syntaxError.HighlightLocation())
}
return fmt.Errorf("error evaluating JMESPath expression: %w", err)
}
converted, isString := result.(string)
if unquoted && isString {
fmt.Println(converted)
} else {
var toJSON []byte
if compact {
toJSON, err = json.Marshal(result)
if ast {
return printAst(expression)
} else {
toJSON, err = json.MarshalIndent(result, "", " ")
input, err := loadInput(filename)
if err != nil {
return err
}
result, err := evaluate(expression, input)
if err != nil {
return err
}
return printResult(result, unquoted, compact)
}
if err != nil {
return fmt.Errorf("error marshalling result to JSON: %w", err)
}
fmt.Println(string(toJSON))
}
return nil
},
}
cmd.Flags().BoolVarP(&compact, "compact", "c", false, "Produce compact JSON output that omits nonessential whitespace")
cmd.Flags().BoolVarP(&compact, "compact", "c", false, "Produce compact JSON output that omits non essential whitespace")
cmd.Flags().BoolVarP(&listFunctions, "list-functions", "l", false, "Output a list of custom JMESPath functions in Kyverno")
cmd.Flags().BoolVarP(&unquoted, "unquoted", "u", false, "If the final result is a string, it will be printed without quotes")
cmd.Flags().BoolVar(&ast, "ast", false, "Only print the AST of the parsed expression. Do not rely on this output, only useful for debugging purposes")
@ -120,11 +61,107 @@ func Command() *cobra.Command {
return cmd
}
func printFunctionList() {
functions := []string{}
for _, function := range jmespath.GetFunctions() {
functions = append(functions, function.String())
func loadExpression(file string, args []string) (string, error) {
if file != "" {
data, err := os.ReadFile(filepath.Clean(file))
if err != nil {
return "", fmt.Errorf("error opening expression file: %w", err)
}
return string(data), nil
} else {
if len(args) == 0 {
return "", fmt.Errorf("must provide at least one argument")
}
return args[0], nil
}
sort.Strings(functions)
fmt.Println(strings.Join(functions, "\n"))
}
func loadInput(file string) (interface{}, error) {
var data []byte
if file != "" {
f, err := os.ReadFile(filepath.Clean(file))
if err != nil {
return nil, fmt.Errorf("error opening input file: %w", err)
}
data = f
} else {
f, err := io.ReadAll(os.Stdin)
if err != nil {
return nil, fmt.Errorf("error opening input file: %w", err)
}
data = f
}
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(expression string, input interface{}) (interface{}, error) {
jp, err := jmespath.New(expression)
if err != nil {
return nil, fmt.Errorf("failed to compile JMESPath: %s, error: %v", expression, err)
}
result, err := jp.Search(input)
if err != nil {
if syntaxError, ok := err.(gojmespath.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(result interface{}, unquoted bool, compact bool) error {
converted, isString := result.(string)
if unquoted && isString {
fmt.Println(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.Println(string(toJSON))
}
return nil
}
func printFunctionList() {
functions := jmespath.GetFunctions()
slices.SortFunc(functions, func(a, b *jmespath.FunctionEntry) bool {
return a.String() < b.String()
})
for _, function := range functions {
function := *function
note := function.Note
function.Note = ""
fmt.Println("Name:", function.Entry.Name)
fmt.Println(" Signature:", function.String())
if note != "" {
fmt.Println(" Note: ", note)
}
fmt.Println()
}
}
// The following function has been adapted from
// https://github.com/jmespath/jp/blob/54882e03bd277fc4475a677fab1d35eaa478b839/jp.go
func printAst(expression string) error {
parser := gojmespath.NewParser()
parsed, err := parser.Parse(expression)
if err != nil {
if syntaxError, ok := err.(gojmespath.SyntaxError); ok {
return fmt.Errorf("%w\n%s", syntaxError, syntaxError.HighlightLocation())
}
return err
}
fmt.Print(parsed)
return nil
}