blob: 6e6d0d3acee7acdba6372326599e6a5be40d7fb6 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2 * Copyright 2019-present Ciena Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package commands
17
18import (
David K. Bainbridge1d946442021-03-19 16:45:52 +000019 "context"
20 "crypto/tls"
Zack Williamse940c7a2019-08-21 14:25:39 -070021 "encoding/json"
Scott Baker9173ed82020-05-19 08:30:12 -070022 "errors"
Zack Williamse940c7a2019-08-21 14:25:39 -070023 "fmt"
serkant.uluderyad3daa902020-05-21 22:49:25 -070024 "io/ioutil"
25 "log"
serkant.uluderyad3daa902020-05-21 22:49:25 -070026 "os"
David K. Bainbridge238e6252021-06-28 09:49:16 -070027 "os/user"
serkant.uluderyad3daa902020-05-21 22:49:25 -070028 "path/filepath"
29 "reflect"
30 "regexp"
31 "strconv"
32 "strings"
33 "time"
34
Scott Baker9173ed82020-05-19 08:30:12 -070035 "github.com/golang/protobuf/jsonpb"
36 "github.com/golang/protobuf/proto"
David K. Bainbridge1d946442021-03-19 16:45:52 +000037 configv1 "github.com/opencord/voltctl/internal/pkg/apis/config/v1"
38 configv2 "github.com/opencord/voltctl/internal/pkg/apis/config/v2"
David K. Bainbridge9189c632021-03-26 21:52:21 +000039 configv3 "github.com/opencord/voltctl/internal/pkg/apis/config/v3"
Scott Baker2b0ad652019-08-21 14:57:07 -070040 "github.com/opencord/voltctl/pkg/filter"
41 "github.com/opencord/voltctl/pkg/format"
42 "github.com/opencord/voltctl/pkg/order"
Zack Williamse940c7a2019-08-21 14:25:39 -070043 "google.golang.org/grpc"
David K. Bainbridge1d946442021-03-19 16:45:52 +000044 "google.golang.org/grpc/credentials"
45 yaml "gopkg.in/yaml.v2"
Zack Williamse940c7a2019-08-21 14:25:39 -070046)
47
48type OutputType uint8
49
50const (
51 OUTPUT_TABLE OutputType = iota
52 OUTPUT_JSON
53 OUTPUT_YAML
divyadesai19009132020-03-04 12:58:08 +000054
divyadesai19009132020-03-04 12:58:08 +000055 supportedKvStoreType = "etcd"
Zack Williamse940c7a2019-08-21 14:25:39 -070056)
57
Zack Williamse940c7a2019-08-21 14:25:39 -070058var (
59 ParamNames = map[string]map[string]string{
60 "v1": {
61 "ID": "voltha.ID",
62 },
63 "v2": {
64 "ID": "common.ID",
65 },
kesavand12cd8eb2020-01-20 22:25:22 -050066 "v3": {
Dinesh Belwalkarc9aa6d82020-03-04 15:22:17 -080067 "ID": "common.ID",
68 "port": "voltha.Port",
69 "ValueSpecifier": "common.ValueSpecifier",
kesavand12cd8eb2020-01-20 22:25:22 -050070 },
Zack Williamse940c7a2019-08-21 14:25:39 -070071 }
72
73 CharReplacer = strings.NewReplacer("\\t", "\t", "\\n", "\n")
74
David K. Bainbridge9189c632021-03-26 21:52:21 +000075 GlobalConfig = configv3.NewDefaultConfig()
Zack Williamse940c7a2019-08-21 14:25:39 -070076
David Bainbridgea6722342019-10-24 23:55:53 +000077 GlobalCommandOptions = make(map[string]map[string]string)
78
Zack Williamse940c7a2019-08-21 14:25:39 -070079 GlobalOptions struct {
divyadesai19009132020-03-04 12:58:08 +000080 Config string `short:"c" long:"config" env:"VOLTCONFIG" value-name:"FILE" default:"" description:"Location of client config file"`
David K. Bainbridge9189c632021-03-26 21:52:21 +000081 Stack string `short:"v" long:"stack" env:"STACK" value-name:"STACK" default:"" description:"Name of stack to use in multistack deployment"`
divyadesai19009132020-03-04 12:58:08 +000082 Server string `short:"s" long:"server" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of VOLTHA"`
83 Kafka string `short:"k" long:"kafka" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of Kafka"`
84 KvStore string `short:"e" long:"kvstore" env:"KVSTORE" value-name:"SERVER:PORT" description:"IP/Host and port of KV store (etcd)"`
85
David K. Bainbridge2b627612020-02-18 14:50:13 -080086 // nolint: staticcheck
serkant.uluderyad3daa902020-05-21 22:49:25 -070087 Debug bool `short:"d" long:"debug" description:"Enable debug mode"`
88 Timeout string `short:"t" long:"timeout" description:"API call timeout duration" value-name:"DURATION" default:""`
89 UseTLS bool `long:"tls" description:"Use TLS"`
90 CACert string `long:"tlscacert" value-name:"CA_CERT_FILE" description:"Trust certs signed only by this CA"`
91 Cert string `long:"tlscert" value-name:"CERT_FILE" description:"Path to TLS vertificate file"`
92 Key string `long:"tlskey" value-name:"KEY_FILE" description:"Path to TLS key file"`
93 Verify bool `long:"tlsverify" description:"Use TLS and verify the remote"`
94 K8sConfig string `short:"8" long:"k8sconfig" env:"KUBECONFIG" value-name:"FILE" default:"" description:"Location of Kubernetes config file"`
95 KvStoreTimeout string `long:"kvstoretimeout" env:"KVSTORE_TIMEOUT" value-name:"DURATION" default:"" description:"timeout for calls to KV store"`
96 CommandOptions string `short:"o" long:"command-options" env:"VOLTCTL_COMMAND_OPTIONS" value-name:"FILE" default:"" description:"Location of command options default configuration file"`
97 MaxCallRecvMsgSize string `short:"m" long:"maxcallrecvmsgsize" description:"Max GRPC Client request size limit in bytes (eg: 4MB)" value-name:"SIZE" default:"4M"`
Zack Williamse940c7a2019-08-21 14:25:39 -070098 }
David Bainbridgea6722342019-10-24 23:55:53 +000099
100 Debug = log.New(os.Stdout, "DEBUG: ", 0)
101 Info = log.New(os.Stdout, "INFO: ", 0)
102 Warn = log.New(os.Stderr, "WARN: ", 0)
103 Error = log.New(os.Stderr, "ERROR: ", 0)
Zack Williamse940c7a2019-08-21 14:25:39 -0700104)
105
106type OutputOptions struct {
David K. Bainbridge2b627612020-02-18 14:50:13 -0800107 Format string `long:"format" value-name:"FORMAT" default:"" description:"Format to use to output structured data"`
108 Quiet bool `short:"q" long:"quiet" description:"Output only the IDs of the objects"`
109 // nolint: staticcheck
Zack Williamse940c7a2019-08-21 14:25:39 -0700110 OutputAs string `short:"o" long:"outputas" default:"table" choice:"table" choice:"json" choice:"yaml" description:"Type of output to generate"`
111 NameLimit int `short:"l" long:"namelimit" default:"-1" value-name:"LIMIT" description:"Limit the depth (length) in the table column name"`
112}
113
Maninder045921e2020-09-29 16:46:02 +0530114type FlowIdOptions struct {
115 HexId bool `short:"x" long:"hex-id" description:"Output Ids in hex format"`
116}
117
Himani Chawla3c161c62021-05-13 16:36:51 +0530118type GroupListOptions struct {
119 Bucket bool `short:"b" long:"buckets" description:"Display Buckets"`
120}
Zack Williamse940c7a2019-08-21 14:25:39 -0700121type ListOutputOptions struct {
122 OutputOptions
123 Filter string `short:"f" long:"filter" default:"" value-name:"FILTER" description:"Only display results that match filter"`
124 OrderBy string `short:"r" long:"orderby" default:"" value-name:"ORDER" description:"Specify the sort order of the results"`
125}
126
127type OutputOptionsJson struct {
David K. Bainbridge2b627612020-02-18 14:50:13 -0800128 Format string `long:"format" value-name:"FORMAT" default:"" description:"Format to use to output structured data"`
129 Quiet bool `short:"q" long:"quiet" description:"Output only the IDs of the objects"`
130 // nolint: staticcheck
Zack Williamse940c7a2019-08-21 14:25:39 -0700131 OutputAs string `short:"o" long:"outputas" default:"json" choice:"table" choice:"json" choice:"yaml" description:"Type of output to generate"`
132 NameLimit int `short:"l" long:"namelimit" default:"-1" value-name:"LIMIT" description:"Limit the depth (length) in the table column name"`
133}
134
135type ListOutputOptionsJson struct {
136 OutputOptionsJson
137 Filter string `short:"f" long:"filter" default:"" value-name:"FILTER" description:"Only display results that match filter"`
138 OrderBy string `short:"r" long:"orderby" default:"" value-name:"ORDER" description:"Specify the sort order of the results"`
139}
140
141func toOutputType(in string) OutputType {
142 switch in {
143 case "table":
144 fallthrough
145 default:
146 return OUTPUT_TABLE
147 case "json":
148 return OUTPUT_JSON
149 case "yaml":
150 return OUTPUT_YAML
151 }
152}
153
154type CommandResult struct {
155 Format format.Format
156 Filter string
157 OrderBy string
158 OutputAs OutputType
159 NameLimit int
160 Data interface{}
161}
162
David Bainbridgea6722342019-10-24 23:55:53 +0000163func GetCommandOptionWithDefault(name, option, defaultValue string) string {
164 if cmd, ok := GlobalCommandOptions[name]; ok {
165 if val, ok := cmd[option]; ok {
166 return CharReplacer.Replace(val)
167 }
168 }
169 return defaultValue
170}
171
serkant.uluderyad3daa902020-05-21 22:49:25 -0700172var sizeParser = regexp.MustCompile(`^([0-9]+)([PETGMK]?)I?B?$`)
173
174func parseSize(size string) (uint64, error) {
175
176 parts := sizeParser.FindAllStringSubmatch(strings.ToUpper(size), -1)
177 if len(parts) == 0 {
178 return 0, fmt.Errorf("size: invalid size '%s'", size)
179 }
180 value, err := strconv.ParseUint(parts[0][1], 10, 64)
181 if err != nil {
182 return 0, fmt.Errorf("size: invalid size '%s'", size)
183 }
184 switch parts[0][2] {
185 case "E":
186 value = value * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
187 case "P":
188 value = value * 1024 * 1024 * 1024 * 1024 * 1024
189 case "T":
190 value = value * 1024 * 1024 * 1024 * 1024
191 case "G":
192 value = value * 1024 * 1024 * 1024
193 case "M":
194 value = value * 1024 * 1024
195 case "K":
196 value = value * 1024
197 default:
198 }
199 return value, nil
200}
201
Zack Williamse940c7a2019-08-21 14:25:39 -0700202func ProcessGlobalOptions() {
David K. Bainbridge9189c632021-03-26 21:52:21 +0000203 ReadConfig()
204
205 // If a stack is selected via command line set it
206 if GlobalOptions.Stack != "" {
207 if GlobalConfig.StackByName(GlobalOptions.Stack) == nil {
208 Error.Fatalf("stack specified, '%s', not found in configuration",
209 GlobalOptions.Stack)
Zack Williamse940c7a2019-08-21 14:25:39 -0700210 }
David K. Bainbridge9189c632021-03-26 21:52:21 +0000211 GlobalConfig.CurrentStack = GlobalOptions.Stack
Zack Williamse940c7a2019-08-21 14:25:39 -0700212 }
213
David K. Bainbridge9189c632021-03-26 21:52:21 +0000214 ApplyOptionOverrides(GlobalConfig.Current())
serkant.uluderyad3daa902020-05-21 22:49:25 -0700215
Zack Williamse940c7a2019-08-21 14:25:39 -0700216 // If a k8s cert/key were not specified, then attempt to read it from
217 // any $HOME/.kube/config if it exists
218 if len(GlobalOptions.K8sConfig) == 0 {
219 home, err := os.UserHomeDir()
220 if err != nil {
David Bainbridgea6722342019-10-24 23:55:53 +0000221 Warn.Printf("Unable to discover the user's home directory: %s", err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700222 home = "~"
223 }
224 GlobalOptions.K8sConfig = filepath.Join(home, ".kube", "config")
225 }
David Bainbridgea6722342019-10-24 23:55:53 +0000226
227 if len(GlobalOptions.CommandOptions) == 0 {
228 home, err := os.UserHomeDir()
229 if err != nil {
230 Warn.Printf("Unable to discover the user's home directory: %s", err)
231 home = "~"
232 }
233 GlobalOptions.CommandOptions = filepath.Join(home, ".volt", "command_options")
234 }
235
236 if info, err := os.Stat(GlobalOptions.CommandOptions); err == nil && !info.IsDir() {
237 optionsFile, err := ioutil.ReadFile(GlobalOptions.CommandOptions)
238 if err != nil {
239 Error.Fatalf("Unable to read command options configuration file '%s' : %s",
240 GlobalOptions.CommandOptions, err.Error())
241 }
242 if err = yaml.Unmarshal(optionsFile, &GlobalCommandOptions); err != nil {
243 Error.Fatalf("Unable to parse the command line options configuration file '%s': %s",
244 GlobalOptions.CommandOptions, err.Error())
245 }
246 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700247}
248
David K. Bainbridge9189c632021-03-26 21:52:21 +0000249func ApplyOptionOverrides(stack *configv3.StackConfigSpec) {
250
251 if stack == nil {
252 // nothing to do
253 return
254 }
255 // Override from command line
256 if GlobalOptions.Server != "" {
257 stack.Server = GlobalOptions.Server
258 }
259
260 if GlobalOptions.UseTLS {
261 stack.Tls.UseTls = true
262 }
263
264 if GlobalOptions.Verify {
265 stack.Tls.Verify = true
266 }
267
268 if GlobalOptions.Kafka != "" {
269 stack.Kafka = GlobalOptions.Kafka
270 }
271
272 if GlobalOptions.KvStore != "" {
273 stack.KvStore = GlobalOptions.KvStore
274 }
275
276 if GlobalOptions.KvStoreTimeout != "" {
277 timeout, err := time.ParseDuration(GlobalOptions.KvStoreTimeout)
278 if err != nil {
279 Error.Fatalf("Unable to parse specified KV strore timeout duration '%s': %s",
280 GlobalOptions.KvStoreTimeout, err.Error())
281 }
282 stack.KvStoreConfig.Timeout = timeout
283 }
284
285 if GlobalOptions.Timeout != "" {
286 timeout, err := time.ParseDuration(GlobalOptions.Timeout)
287 if err != nil {
288 Error.Fatalf("Unable to parse specified timeout duration '%s': %s",
289 GlobalOptions.Timeout, err.Error())
290 }
291 stack.Grpc.Timeout = timeout
292 }
293
294 if GlobalOptions.MaxCallRecvMsgSize != "" {
295 stack.Grpc.MaxCallRecvMsgSize = GlobalOptions.MaxCallRecvMsgSize
296 }
297}
298
David K. Bainbridge238e6252021-06-28 09:49:16 -0700299func homeDir() (string, error) {
300
301 // First attempt is using the current user information, if that
302 // fails we attempt to use the environment variables via a call
303 // to os.UserHomeDir()
304 cur, err := user.Current()
305 if err != nil {
David K. Bainbridge9189c632021-03-26 21:52:21 +0000306 home, err := os.UserHomeDir()
307 if err != nil {
David K. Bainbridge238e6252021-06-28 09:49:16 -0700308 return "", errors.New("unable to get current user or determine home directory via environment")
309 }
310 return home, nil
311 }
312 if len(cur.HomeDir) == 0 {
313 home, err := os.UserHomeDir()
314 if err != nil {
315 return "", errors.New("unable to get determine home directory via user information or environment")
316 }
317 return home, nil
318 }
319 return cur.HomeDir, nil
320}
321
322func ReadConfig() {
323 // assume that the config file was specified until it is
324 // determined it was not
325 if len(GlobalOptions.Config) == 0 {
326 home, err := homeDir()
327 if err != nil {
328 Error.Fatalf("Unable to create default configuration file path: %s", err.Error())
David K. Bainbridge9189c632021-03-26 21:52:21 +0000329 }
330 GlobalOptions.Config = filepath.Join(home, ".volt", "config")
331 }
332
David K. Bainbridge238e6252021-06-28 09:49:16 -0700333 // If the config file value is `~` or begins with `~/` then
334 // attempt to expand that to the user's home directory
335 if GlobalOptions.Config == "~" {
336 home, err := homeDir()
337 if err != nil {
338 Error.Fatalf("Unable to expand config file path '%s': %s",
339 GlobalOptions.Config, err)
340 }
341 GlobalOptions.Config = home
342 } else if strings.HasPrefix(GlobalOptions.Config, "~/") {
343 home, err := homeDir()
344 if err != nil {
345 Error.Fatalf("Unable to expand config file path '%s': %s",
346 GlobalOptions.Config, err)
347 }
348 GlobalOptions.Config = filepath.Join(home,
349 GlobalOptions.Config[2:])
350 }
351
David K. Bainbridge9189c632021-03-26 21:52:21 +0000352 if info, err := os.Stat(GlobalOptions.Config); err == nil && !info.IsDir() {
353 configFile, err := ioutil.ReadFile(GlobalOptions.Config)
354 if err != nil {
355 Error.Fatalf("Unable to read the configuration file '%s': %s",
356 GlobalOptions.Config, err.Error())
357 }
358 // First try the latest version of the config api then work
359 // backwards
360 if err = yaml.Unmarshal(configFile, &GlobalConfig); err != nil {
361 GlobalConfigV2 := configv2.NewDefaultConfig()
362 if err = yaml.Unmarshal(configFile, &GlobalConfigV2); err != nil {
363 GlobalConfigV1 := configv1.NewDefaultConfig()
364 if err = yaml.Unmarshal(configFile, &GlobalConfigV1); err != nil {
365 Error.Fatalf("Unable to parse the configuration file '%s': %s",
366 GlobalOptions.Config, err.Error())
367 }
368 GlobalConfig = configv3.FromConfigV1(GlobalConfigV1)
369 } else {
370 GlobalConfig = configv3.FromConfigV2(GlobalConfigV2)
371 }
372 }
373 }
David K. Bainbridge9189c632021-03-26 21:52:21 +0000374}
375
Zack Williamse940c7a2019-08-21 14:25:39 -0700376func NewConnection() (*grpc.ClientConn, error) {
377 ProcessGlobalOptions()
serkant.uluderyad3daa902020-05-21 22:49:25 -0700378
379 // convert grpc.msgSize into bytes
David K. Bainbridge9189c632021-03-26 21:52:21 +0000380 n, err := parseSize(GlobalConfig.Current().Grpc.MaxCallRecvMsgSize)
serkant.uluderyad3daa902020-05-21 22:49:25 -0700381 if err != nil {
David K. Bainbridge9189c632021-03-26 21:52:21 +0000382 Error.Fatalf("Cannot convert msgSize %s to bytes", GlobalConfig.Current().Grpc.MaxCallRecvMsgSize)
serkant.uluderyad3daa902020-05-21 22:49:25 -0700383 }
384
David K. Bainbridge1d946442021-03-19 16:45:52 +0000385 var opts []grpc.DialOption
386
387 opts = append(opts,
388 grpc.WithDisableRetry(),
David K. Bainbridge1d946442021-03-19 16:45:52 +0000389 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(n))))
390
David K. Bainbridge9189c632021-03-26 21:52:21 +0000391 if GlobalConfig.Current().Tls.UseTls {
David K. Bainbridge1d946442021-03-19 16:45:52 +0000392 creds := credentials.NewTLS(&tls.Config{
David K. Bainbridge9189c632021-03-26 21:52:21 +0000393 InsecureSkipVerify: !GlobalConfig.Current().Tls.Verify})
David K. Bainbridge1d946442021-03-19 16:45:52 +0000394 opts = append(opts, grpc.WithTransportCredentials(creds))
395 } else {
396 opts = append(opts, grpc.WithInsecure())
397 }
398 ctx, cancel := context.WithTimeout(context.TODO(),
David K. Bainbridge9189c632021-03-26 21:52:21 +0000399 GlobalConfig.Current().Grpc.ConnectTimeout)
David K. Bainbridge1d946442021-03-19 16:45:52 +0000400 defer cancel()
David K. Bainbridge9189c632021-03-26 21:52:21 +0000401 return grpc.DialContext(ctx, GlobalConfig.Current().Server, opts...)
Zack Williamse940c7a2019-08-21 14:25:39 -0700402}
403
Scott Baker9173ed82020-05-19 08:30:12 -0700404func ConvertJsonProtobufArray(data_in interface{}) (string, error) {
405 result := ""
406
407 slice := reflect.ValueOf(data_in)
408 if slice.Kind() != reflect.Slice {
409 return "", errors.New("Not a slice")
410 }
411
412 result = result + "["
413
414 marshaler := jsonpb.Marshaler{EmitDefaults: true}
415 for i := 0; i < slice.Len(); i++ {
416 item := slice.Index(i).Interface()
417 protoMessage, okay := item.(proto.Message)
418 if !okay {
419 return "", errors.New("Failed to convert item to a proto.Message")
420 }
421 asJson, err := marshaler.MarshalToString(protoMessage)
422 if err != nil {
423 return "", fmt.Errorf("Failed to marshal the json: %s", err)
424 }
425
426 result = result + asJson
427
428 if i < slice.Len()-1 {
429 result = result + ","
430 }
431 }
432
433 result = result + "]"
434
435 return result, nil
436}
437
Zack Williamse940c7a2019-08-21 14:25:39 -0700438func GenerateOutput(result *CommandResult) {
439 if result != nil && result.Data != nil {
440 data := result.Data
441 if result.Filter != "" {
442 f, err := filter.Parse(result.Filter)
443 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000444 Error.Fatalf("Unable to parse specified output filter '%s': %s", result.Filter, err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700445 }
446 data, err = f.Process(data)
447 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000448 Error.Fatalf("Unexpected error while filtering command results: %s", err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700449 }
450 }
451 if result.OrderBy != "" {
452 s, err := order.Parse(result.OrderBy)
453 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000454 Error.Fatalf("Unable to parse specified sort specification '%s': %s", result.OrderBy, err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700455 }
456 data, err = s.Process(data)
457 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000458 Error.Fatalf("Unexpected error while sorting command result: %s", err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700459 }
460 }
461 if result.OutputAs == OUTPUT_TABLE {
462 tableFormat := format.Format(result.Format)
David Bainbridge12f036f2019-10-15 22:09:04 +0000463 if err := tableFormat.Execute(os.Stdout, true, result.NameLimit, data); err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000464 Error.Fatalf("Unexpected error while attempting to format results as table : %s", err.Error())
David Bainbridge12f036f2019-10-15 22:09:04 +0000465 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700466 } else if result.OutputAs == OUTPUT_JSON {
Scott Baker9173ed82020-05-19 08:30:12 -0700467 // first try to convert it as an array of protobufs
468 asJson, err := ConvertJsonProtobufArray(data)
Zack Williamse940c7a2019-08-21 14:25:39 -0700469 if err != nil {
Scott Baker9173ed82020-05-19 08:30:12 -0700470 // if that fails, then just do a standard json conversion
471 asJsonB, err := json.Marshal(&data)
472 if err != nil {
473 Error.Fatalf("Unexpected error while processing command results to JSON: %s", err.Error())
474 }
475 asJson = string(asJsonB)
Zack Williamse940c7a2019-08-21 14:25:39 -0700476 }
477 fmt.Printf("%s", asJson)
478 } else if result.OutputAs == OUTPUT_YAML {
479 asYaml, err := yaml.Marshal(&data)
480 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000481 Error.Fatalf("Unexpected error while processing command results to YAML: %s", err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700482 }
483 fmt.Printf("%s", asYaml)
484 }
485 }
486}