github.com/cilium/cilium@v1.16.2/pkg/command/output.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package command 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "os" 10 "regexp" 11 "strings" 12 13 "github.com/spf13/cobra" 14 "gopkg.in/yaml.v3" 15 "k8s.io/client-go/util/jsonpath" 16 ) 17 18 var ( 19 outputOpt string 20 re = regexp.MustCompile(`^jsonpath\=(.*)`) 21 ) 22 23 // OutputOption returns true if an output option was specified. 24 func OutputOption() bool { 25 return len(outputOpt) > 0 26 } 27 28 // OutputOptionString returns the output option as a string 29 func OutputOptionString() string { 30 if outputOpt == "yaml" { 31 return "YAML" 32 } 33 34 if outputOpt == "json" || re.MatchString(outputOpt) { 35 return "JSON" 36 } 37 38 return "unknown" 39 } 40 41 // AddOutputOption adds the -o|--output option to any cmd to export to json or yaml. 42 func AddOutputOption(cmd *cobra.Command) { 43 cmd.Flags().StringVarP(&outputOpt, "output", "o", "", "json| yaml| jsonpath='{}'") 44 } 45 46 // ForceJSON sets output mode to JSON (for unit tests) 47 func ForceJSON() { 48 outputOpt = "json" 49 } 50 51 // PrintOutput receives an interface and dump the data using the --output flag. 52 // ATM only json or jsonpath. In the future yaml 53 func PrintOutput(data interface{}) error { 54 return PrintOutputWithType(data, outputOpt) 55 } 56 57 // PrintOutputWithPatch merges data with patch and dump the data using the --output flag. 58 func PrintOutputWithPatch(data interface{}, patch interface{}) error { 59 mergedInterface, err := mergeInterfaces(data, patch) 60 if err != nil { 61 return fmt.Errorf("Unable to merge Interfaces: %w", err) 62 } 63 return PrintOutputWithType(mergedInterface, outputOpt) 64 } 65 66 func mergeInterfaces(data, patch interface{}) (interface{}, error) { 67 var i1, i2 interface{} 68 69 data1, err := json.Marshal(data) 70 if err != nil { 71 return nil, err 72 } 73 data2, err := json.Marshal(patch) 74 if err != nil { 75 return nil, err 76 } 77 err = json.Unmarshal(data1, &i1) 78 if err != nil { 79 return nil, err 80 } 81 82 err = json.Unmarshal(data2, &i2) 83 if err != nil { 84 return nil, err 85 } 86 return recursiveMerge(i1, i2), nil 87 } 88 89 func recursiveMerge(i1, i2 interface{}) interface{} { 90 switch i1 := i1.(type) { 91 case map[string]interface{}: 92 i2, ok := i2.(map[string]interface{}) 93 if !ok { 94 return i1 95 } 96 for k, v2 := range i2 { 97 if v1, ok := i1[k]; ok { 98 i1[k] = recursiveMerge(v1, v2) 99 } else { 100 i1[k] = v2 101 } 102 } 103 case nil: 104 i2, ok := i2.(map[string]interface{}) 105 if ok { 106 return i2 107 } 108 } 109 return i1 110 } 111 112 // PrintOutputWithType receives an interface and dump the data using the --output flag. 113 // ATM only json, yaml, or jsonpath. 114 func PrintOutputWithType(data interface{}, outputType string) error { 115 if outputType == "json" { 116 return dumpJSON(data, "") 117 } 118 119 if outputType == "yaml" { 120 return dumpYAML(data) 121 } 122 123 if re.MatchString(outputType) { 124 return dumpJSON(data, re.ReplaceAllString(outputType, "$1")) 125 } 126 127 return fmt.Errorf("couldn't find output printer") 128 } 129 130 // DumpJSONToString dumps the contents of data into a string. If jsonpath is 131 // non-empty, will attempt to do jsonpath filtering using said string. Returns a 132 // string containing the JSON in data, or an error if any JSON marshaling, 133 // parsing operations fail. 134 func DumpJSONToString(data interface{}, jsonPath string) (string, error) { 135 if len(jsonPath) == 0 { 136 result, err := json.MarshalIndent(data, "", " ") 137 if err != nil { 138 fmt.Fprintf(os.Stderr, "Couldn't marshal to json: '%s'\n", err) 139 return "", err 140 } 141 fmt.Println(string(result)) 142 return "", nil 143 } 144 145 parser := jsonpath.New("").AllowMissingKeys(true) 146 if err := parser.Parse(jsonPath); err != nil { 147 fmt.Fprintf(os.Stderr, "Couldn't parse jsonpath expression: '%s'\n", err) 148 return "", err 149 } 150 151 var sb strings.Builder 152 if err := parser.Execute(&sb, data); err != nil { 153 fmt.Fprintf(os.Stderr, "Couldn't parse jsonpath expression: '%s'\n", err) 154 return "", err 155 156 } 157 return sb.String(), nil 158 } 159 160 // dumpJSON dumps the data variable to the stdout as json. 161 // If something fails, it returns an error 162 // If jsonPath is passed, it runs the json query over data var. 163 func dumpJSON(data interface{}, jsonPath string) error { 164 jsonStr, err := DumpJSONToString(data, jsonPath) 165 if err != nil { 166 return err 167 } 168 fmt.Println(jsonStr) 169 return nil 170 } 171 172 // dumpYAML dumps the data variable to the stdout as yaml. 173 // If something fails, it returns an error 174 func dumpYAML(data interface{}) error { 175 result, err := yaml.Marshal(data) 176 if err != nil { 177 fmt.Fprintf(os.Stderr, "Couldn't marshal to yaml: '%s'\n", err) 178 return err 179 } 180 fmt.Println(string(result)) 181 return nil 182 }