From 9911cbe9b956095ea29394fb1f7da95d39d0625f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Derosiaux?= Date: Wed, 28 Aug 2024 12:16:46 +0100 Subject: [PATCH] Support -o json and -o name on get (#60) * Support -o json * Cleanup * Err * Fix * Add -o name * Review: use enum * Fix * Add tests * Fix tests --- cmd/get.go | 74 +++++++++++++++++++++++++++++++++++++++++++--- go.mod | 3 +- go.sum | 3 ++ test_final_exec.sh | 60 ++++++++++++++++++++----------------- 4 files changed, 108 insertions(+), 32 deletions(-) diff --git a/cmd/get.go b/cmd/get.go index f7d5a16..bce25df 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "os" "strconv" @@ -10,8 +11,27 @@ import ( "github.com/conduktor/ctl/schema" "github.com/conduktor/ctl/utils" "github.com/spf13/cobra" + "github.com/thediveo/enumflag/v2" ) +type OutputFormat enumflag.Flag + +const ( + JSON OutputFormat = iota + YAML + NAME +) + +var OutputFormatIds = map[OutputFormat][]string{ + JSON: {"json"}, + YAML: {"yaml"}, + NAME: {"name"}, +} + +func (o OutputFormat) String() string { + return OutputFormatIds[o][0] +} + var getCmd = &cobra.Command{ Use: "get", Short: "Get resource of a given kind", @@ -52,8 +72,47 @@ func buildAlias(name string) []string { return []string{strings.ToLower(name), removeTrailingSIfAny(strings.ToLower(name)), removeTrailingSIfAny(name)} } +func printResource(result interface{}, format OutputFormat) error { + switch format { + case JSON: + jsonOutput, err := json.MarshalIndent(result, "", " ") + if err != nil { + return fmt.Errorf("error marshalling JSON: %s\n%s", err, result) + } + fmt.Println(string(jsonOutput)) + case NAME: + // show Kind/Name + switch res := result.(type) { + case []resource.Resource: + for _, r := range res { + fmt.Println(r.Kind + "/" + r.Name) + } + case resource.Resource: + fmt.Println(res.Kind + "/" + res.Name) + default: + return fmt.Errorf("unexpected resource type") + } + case YAML: + switch res := result.(type) { + case []resource.Resource: + for _, r := range res { + fmt.Println("---") // '---' indicates the start of a new document in YAML + r.PrintPreservingOriginalFieldOrder() + } + case resource.Resource: + res.PrintPreservingOriginalFieldOrder() + default: + return fmt.Errorf("unexpected resource type") + } + default: + return fmt.Errorf("invalid output format %s", format.String()) + } + return nil +} + func initGet(kinds schema.KindCatalog) { rootCmd.AddCommand(getCmd) + var format OutputFormat = YAML for name, kind := range kinds { gatewayKind, isGatewayKind := kind.GetLatestKindVersion().(*schema.GatewayKindVersion) @@ -80,6 +139,7 @@ func initGet(kinds schema.KindCatalog) { parentValue[i] = *v } var err error + if len(args) == 0 { var result []resource.Resource if isGatewayKind { @@ -87,10 +147,11 @@ func initGet(kinds schema.KindCatalog) { } else { result, err = consoleApiClient().Get(&kind, parentValue, queryParams) } - for _, r := range result { - r.PrintPreservingOriginalFieldOrder() - fmt.Println("---") + if err != nil { + fmt.Fprintf(os.Stderr, "Error fetching resources: %s\n", err) + return } + err = printResource(result, format) } else if len(args) == 1 { var result resource.Resource if isGatewayKind { @@ -98,7 +159,11 @@ func initGet(kinds schema.KindCatalog) { } else { result, err = consoleApiClient().Describe(&kind, parentValue, args[0]) } - result.PrintPreservingOriginalFieldOrder() + if err != nil { + fmt.Fprintf(os.Stderr, "Error describing resource: %s\n", err) + return + } + err = printResource(result, format) } if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) @@ -127,6 +192,7 @@ func initGet(kinds schema.KindCatalog) { kindCmd.MarkFlagRequired(flag.FlagName) } } + kindCmd.Flags().VarP(enumflag.New(&format, "output", OutputFormatIds, enumflag.EnumCaseInsensitive), "output", "o", "Output format. One of: json|yaml|name (default is yaml)") getCmd.AddCommand(kindCmd) } } diff --git a/go.mod b/go.mod index 3278a8d..cc4259f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/conduktor/ctl go 1.22.0 require ( + github.com/Jeffail/gabs/v2 v2.7.0 github.com/davecgh/go-spew v1.1.1 github.com/ghodss/yaml v1.0.0 github.com/go-resty/resty/v2 v2.11.0 @@ -11,7 +12,6 @@ require ( github.com/spf13/cobra v1.8.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a gopkg.in/yaml.v3 v3.0.1 - github.com/Jeffail/gabs/v2 v2.7.0 ) require ( @@ -21,6 +21,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/thediveo/enumflag/v2 v2.0.5 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect golang.org/x/net v0.23.0 // indirect diff --git a/go.sum b/go.sum index d837efd..4ccc622 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= github.com/pb33f/libopenapi v0.15.14 h1:A0fn45jbthDyFGXfu5bYIZVsWyPI6hJYm3wG143MT8o= github.com/pb33f/libopenapi v0.15.14/go.mod h1:PEXNwvtT4KNdjrwudp5OYnD1ryqK6uJ68aMNyWvoMuc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -83,6 +84,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/thediveo/enumflag/v2 v2.0.5 h1:VJjvlAqUb6m6mxOrB/0tfBJI0Kvi9wJ8ulh38xK87i8= +github.com/thediveo/enumflag/v2 v2.0.5/go.mod h1:0NcG67nYgwwFsAvoQCmezG0J0KaIxZ0f7skg9eLq1DA= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= diff --git a/test_final_exec.sh b/test_final_exec.sh index e965259..ab71b91 100755 --- a/test_final_exec.sh +++ b/test_final_exec.sh @@ -8,40 +8,46 @@ function cleanup { docker compose -f "$SCRIPTDIR/docker/docker-compose.yml" down } +run() { + docker compose -f docker/docker-compose.yml run --rm "$@" +} + trap cleanup EXIT main() { cd "$SCRIPTDIR" docker compose -f docker/docker-compose.yml build docker compose -f docker/docker-compose.yml up -d mock mockGateway - sleep 1 - docker compose -f docker/docker-compose.yml run --rm conduktor apply -f /test_resource.yml - docker compose -f docker/docker-compose.yml run --rm conduktor apply -f / - docker compose -f docker/docker-compose.yml run --rm conduktor delete -f /test_resource.yml - docker compose -f docker/docker-compose.yml run --rm conduktor apply -f / - docker compose -f docker/docker-compose.yml run --rm conduktor get Topic yolo --cluster=my-cluster - docker compose -f docker/docker-compose.yml run --rm conduktor delete Topic yolo -v --cluster=my-cluster - docker compose -f docker/docker-compose.yml run --rm -e CDK_USER=admin -e CDK_PASSWORD=secret conduktor login - docker compose -f docker/docker-compose.yml run --rm -e CDK_USER=admin -e CDK_PASSWORD=secret -e CDK_API_KEY="" conduktor get KafkaCluster my_kafka_cluster - docker compose -f docker/docker-compose.yml run --rm conduktor token list admin - docker compose -f docker/docker-compose.yml run --rm conduktor token list application-instance -i=my_app_instance - docker compose -f docker/docker-compose.yml run --rm conduktor token create admin a_admin_token - docker compose -f docker/docker-compose.yml run --rm conduktor token create application-instance -i=my_app_instance a_admin_token - docker compose -f docker/docker-compose.yml run --rm conduktor token delete 0-0-0-0-0 + sleep 2 + run conduktor apply -f /test_resource.yml + run conduktor apply -f / + run conduktor delete -f /test_resource.yml + run conduktor apply -f / + run conduktor get Topic yolo --cluster=my-cluster + run conduktor get Topic yolo --cluster=my-cluster -o yaml + run conduktor get Topic yolo --cluster=my-cluster -o name + run conduktor delete Topic yolo -v --cluster=my-cluster + run -e CDK_USER=admin -e CDK_PASSWORD=secret conduktor login + run -e CDK_USER=admin -e CDK_PASSWORD=secret -e CDK_API_KEY="" conduktor get KafkaCluster my_kafka_cluster + run conduktor token list admin + run conduktor token list application-instance -i=my_app_instance + run conduktor token create admin a_admin_token + run conduktor token create application-instance -i=my_app_instance a_admin_token + run conduktor token delete 0-0-0-0-0 # Gateway - docker compose -f docker/docker-compose.yml run --rm conduktor apply -f /test_resource_gw.yml - docker compose -f docker/docker-compose.yml run --rm conduktor delete VirtualCluster vcluster1 - docker compose -f docker/docker-compose.yml run --rm conduktor get VirtualCluster - docker compose -f docker/docker-compose.yml run --rm conduktor get VirtualCluster vcluster1 - docker compose -f docker/docker-compose.yml run --rm conduktor get GatewayGroup - docker compose -f docker/docker-compose.yml run --rm conduktor get GatewayGroup g1 - docker compose -f docker/docker-compose.yml run --rm conduktor get AliasTopic --show-defaults --name=yo --vcluster=mycluster1 - docker compose -f docker/docker-compose.yml run --rm conduktor get ConcentrationRule --show-defaults --name=yo --vcluster=mycluster1 - docker compose -f docker/docker-compose.yml run --rm conduktor get GatewayServiceAccount --show-defaults --name=yo --vcluster=mycluster1 - docker compose -f docker/docker-compose.yml run --rm conduktor get interceptor --group=g1 --name=yo --username=me --vcluster=mycluster1 - docker compose -f docker/docker-compose.yml run --rm conduktor delete aliastopic aliastopicname --vcluster=v1 - docker compose -f docker/docker-compose.yml run --rm conduktor delete concentrationrule cr1 --vcluster=v1 - docker compose -f docker/docker-compose.yml run --rm conduktor delete gatewayserviceaccount s1 --vcluster=v1 + run conduktor apply -f /test_resource_gw.yml + run conduktor delete VirtualCluster vcluster1 + run conduktor get VirtualCluster + run conduktor get VirtualCluster vcluster1 + run conduktor get GatewayGroup + run conduktor get GatewayGroup g1 + run conduktor get AliasTopic --show-defaults --name=yo --vcluster=mycluster1 + run conduktor get ConcentrationRule --show-defaults --name=yo --vcluster=mycluster1 + run conduktor get GatewayServiceAccount --show-defaults --name=yo --vcluster=mycluster1 + run conduktor get interceptor --group=g1 --name=yo --username=me --vcluster=mycluster1 + run conduktor delete aliastopic aliastopicname --vcluster=v1 + run conduktor delete concentrationrule cr1 --vcluster=v1 + run conduktor delete gatewayserviceaccount s1 --vcluster=v1 } main "$@"