From 1a89ff7ef322a934918c8f27aa25e4925777169a Mon Sep 17 00:00:00 2001 From: Luc DUZAN Date: Mon, 16 Dec 2024 15:50:58 +0100 Subject: [PATCH] Add template command --- client/console_client.go | 4 +- client/gateway_client.go | 4 +- cmd/root.go | 1 + cmd/template.go | 75 ++++++++++++++++++++++++++++++ schema/console-default-schema.json | 2 +- schema/gateway_schema_test.go | 74 ++++++++++++++++++++++++++++- schema/kind.go | 11 +++++ schema/schema.go | 10 ++++ 8 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 cmd/template.go diff --git a/client/console_client.go b/client/console_client.go index 27dd471..d7c89b7 100644 --- a/client/console_client.go +++ b/client/console_client.go @@ -133,7 +133,9 @@ func Make(apiParameter ApiParameter) (*Client, error) { err := result.initKindFromApi() if err != nil { - fmt.Fprintf(os.Stderr, "Cannot access the Conduktor API: %s\nUsing offline defaults.\n", err) + if apiParameter.Debug { + fmt.Fprintf(os.Stderr, "Cannot access the Conduktor API: %s\nUsing offline defaults.\n", err) + } result.kinds = schema.ConsoleDefaultKind() } diff --git a/client/gateway_client.go b/client/gateway_client.go index 50331da..8499afb 100644 --- a/client/gateway_client.go +++ b/client/gateway_client.go @@ -52,7 +52,9 @@ func MakeGateway(apiParameter GatewayApiParameter) (*GatewayClient, error) { err := result.initKindFromApi() if err != nil { - fmt.Fprintf(os.Stderr, "Cannot access the Gateway Conduktor API: %s\nUsing offline defaults.\n", err) + if apiParameter.Debug { + fmt.Fprintf(os.Stderr, "Cannot access the Gateway Conduktor API: %s\nUsing offline defaults.\n", err) + } result.kinds = schema.GatewayDefaultKind() } diff --git a/cmd/root.go b/cmd/root.go index f619ae2..7fdfc31 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -80,6 +80,7 @@ func init() { debug = rootCmd.PersistentFlags().BoolP("verbose", "v", false, "show more information for debugging") var permissive = rootCmd.PersistentFlags().Bool("permissive", false, "permissive mode, allow undefined environment variables") initGet(kinds) + initTemplate(kinds) initDelete(kinds, !*permissive) initApply(kinds, !*permissive) initConsoleMkKind() diff --git a/cmd/template.go b/cmd/template.go new file mode 100644 index 0000000..423a39e --- /dev/null +++ b/cmd/template.go @@ -0,0 +1,75 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + + "github.com/conduktor/ctl/schema" + "github.com/spf13/cobra" +) + +var templateCmd = &cobra.Command{ + Use: "template", + Short: "Get a yaml example for a given kind", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + // Root command does nothing + cmd.Help() + os.Exit(1) + }, +} + +func initTemplate(kinds schema.KindCatalog) { + rootCmd.AddCommand(templateCmd) + var file *string + file = templateCmd.PersistentFlags().StringP("output", "o", "", "Write example to file") + + // Add all kinds to the 'template' command + for name, kind := range kinds { + kindCmd := &cobra.Command{ + Use: name, + Short: "Get a yaml example for resource of kind " + name, + Args: cobra.NoArgs, + Long: `If name not provided it will list all resource`, + Aliases: buildAlias(name), + Run: func(cmd *cobra.Command, args []string) { + example := kind.GetLatestKindVersion().GetApplyExample() + if example == "" { + fmt.Fprintf(os.Stderr, "No template for kind %s\n", name) + os.Exit(1) + } else { + if file == nil || *file == "" { + fmt.Println("---") + fmt.Println(kind.GetLatestKindVersion().GetApplyExample()) + } else { + _, err := os.Stat(*file) + if err == nil { + fmt.Fprintf(os.Stderr, "File %s already exists. You can use conduktor template %s >> %s to append to existing file\n", *file, name, *file) + os.Exit(2) + } + f, err := os.Create(*file) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating file %s: %s\n", *file, err) + os.Exit(3) + } + defer f.Close() + w := bufio.NewWriter(f) + _, err = w.WriteString("---\n") + _, err = w.WriteString(kind.GetLatestKindVersion().GetApplyExample()) + if err != nil { + fmt.Fprintf(os.Stderr, "Error writting to file %s: %s\n", *file, err) + os.Exit(4) + } + err = w.Flush() + if err != nil { + fmt.Fprintf(os.Stderr, "Error writting to file %s: %s\n", *file, err) + os.Exit(5) + } + } + } + }, + } + templateCmd.AddCommand(kindCmd) + } +} diff --git a/schema/console-default-schema.json b/schema/console-default-schema.json index fc29f73..b63c9d3 100644 --- a/schema/console-default-schema.json +++ b/schema/console-default-schema.json @@ -1 +1 @@ -{"Alert":{"Versions":{"2":{"ListPath":"/public/monitoring/v2/cluster/{cluster}/alert","Name":"Alert","ParentPathParam":["cluster"],"ListQueryParamter":{"alertType":{"FlagName":"alert-type","Required":false,"Type":"string"},"connect":{"FlagName":"connect","Required":false,"Type":"string"},"connector":{"FlagName":"connector","Required":false,"Type":"string"},"consumerGroup":{"FlagName":"consumer-group","Required":false,"Type":"string"},"topic":{"FlagName":"topic","Required":false,"Type":"string"}},"Order":15}}},"Application":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/application","Name":"Application","ParentPathParam":[],"ListQueryParamter":{},"Order":6}}},"ApplicationGroup":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/application-group","Name":"ApplicationGroup","ParentPathParam":[],"ListQueryParamter":{},"Order":9}}},"ApplicationInstance":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/application-instance","Name":"ApplicationInstance","ParentPathParam":[],"ListQueryParamter":{"application":{"FlagName":"application","Required":false,"Type":"string"}},"Order":7}}},"ApplicationInstancePermission":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/application-instance-permission","Name":"ApplicationInstancePermission","ParentPathParam":[],"ListQueryParamter":{"filterByApplication":{"FlagName":"application","Required":false,"Type":"string"},"filterByApplicationInstance":{"FlagName":"application-instance","Required":false,"Type":"string"},"filterByGrantedTo":{"FlagName":"granted-to","Required":false,"Type":"string"}},"Order":8}}},"Connector":{"Versions":{"2":{"ListPath":"/public/kafka/v2/cluster/{cluster}/connect/{connectCluster}/connector","Name":"Connector","ParentPathParam":["cluster","connectCluster"],"ListQueryParamter":{},"Order":13}}},"Group":{"Versions":{"2":{"ListPath":"/public/iam/v2/group","Name":"Group","ParentPathParam":[],"ListQueryParamter":{},"Order":1}}},"IndexedTopic":{"Versions":{"1":{"ListPath":"/public/sql/v1/cluster/{cluster}/indexed_topic","Name":"IndexedTopic","ParentPathParam":["cluster"],"ListQueryParamter":{},"Order":14}}},"KafkaCluster":{"Versions":{"2":{"ListPath":"/public/console/v2/kafka-cluster","Name":"KafkaCluster","ParentPathParam":[],"ListQueryParamter":{},"Order":2}}},"KafkaConnectCluster":{"Versions":{"2":{"ListPath":"/public/console/v2/cluster/{cluster}/kafka-connect","Name":"KafkaConnectCluster","ParentPathParam":["cluster"],"ListQueryParamter":{},"Order":3}}},"KsqlDBCluster":{"Versions":{"2":{"ListPath":"/public/console/v2/cluster/{cluster}/ksqldb","Name":"KsqlDBCluster","ParentPathParam":["cluster"],"ListQueryParamter":{},"Order":4}}},"ServiceAccount":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/cluster/{cluster}/service-account","Name":"ServiceAccount","ParentPathParam":["cluster"],"ListQueryParamter":{},"Order":10}}},"Subject":{"Versions":{"2":{"ListPath":"/public/kafka/v2/cluster/{cluster}/subject","Name":"Subject","ParentPathParam":["cluster"],"ListQueryParamter":{},"Order":12}}},"Topic":{"Versions":{"2":{"ListPath":"/public/kafka/v2/cluster/{cluster}/topic","Name":"Topic","ParentPathParam":["cluster"],"ListQueryParamter":{},"Order":11}}},"TopicPolicy":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/topic-policy","Name":"TopicPolicy","ParentPathParam":[],"ListQueryParamter":{"app-instance":{"FlagName":"application-instance","Required":false,"Type":"string"}},"Order":5}}},"User":{"Versions":{"2":{"ListPath":"/public/iam/v2/user","Name":"User","ParentPathParam":[],"ListQueryParamter":{},"Order":0}}}} \ No newline at end of file +{"Alert":{"Versions":{"2":{"ListPath":"/public/monitoring/v2/cluster/{cluster}/alert","Name":"Alert","ParentPathParam":["cluster"],"ListQueryParamter":{"alertType":{"FlagName":"alert-type","Required":false,"Type":"string"},"connect":{"FlagName":"connect","Required":false,"Type":"string"},"connector":{"FlagName":"connector","Required":false,"Type":"string"},"consumerGroup":{"FlagName":"consumer-group","Required":false,"Type":"string"},"topic":{"FlagName":"topic","Required":false,"Type":"string"}},"ApplyExample":"null\n","Order":15}}},"Application":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/application","Name":"Application","ParentPathParam":[],"ListQueryParamter":{},"ApplyExample":"null\n","Order":6}}},"ApplicationGroup":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/application-group","Name":"ApplicationGroup","ParentPathParam":[],"ListQueryParamter":{},"ApplyExample":"null\n","Order":9}}},"ApplicationInstance":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/application-instance","Name":"ApplicationInstance","ParentPathParam":[],"ListQueryParamter":{"application":{"FlagName":"application","Required":false,"Type":"string"}},"ApplyExample":"null\n","Order":7}}},"ApplicationInstancePermission":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/application-instance-permission","Name":"ApplicationInstancePermission","ParentPathParam":[],"ListQueryParamter":{"filterByApplication":{"FlagName":"application","Required":false,"Type":"string"},"filterByApplicationInstance":{"FlagName":"application-instance","Required":false,"Type":"string"},"filterByGrantedTo":{"FlagName":"granted-to","Required":false,"Type":"string"}},"ApplyExample":"null\n","Order":8}}},"Connector":{"Versions":{"2":{"ListPath":"/public/kafka/v2/cluster/{cluster}/connect/{connectCluster}/connector","Name":"Connector","ParentPathParam":["cluster","connectCluster"],"ListQueryParamter":{},"ApplyExample":"null\n","Order":13}}},"Group":{"Versions":{"2":{"ListPath":"/public/iam/v2/group","Name":"Group","ParentPathParam":[],"ListQueryParamter":{},"ApplyExample":"null\n","Order":1}}},"IndexedTopic":{"Versions":{"1":{"ListPath":"/public/sql/v1/cluster/{cluster}/indexed_topic","Name":"IndexedTopic","ParentPathParam":["cluster"],"ListQueryParamter":{},"ApplyExample":"null\n","Order":14}}},"KafkaCluster":{"Versions":{"2":{"ListPath":"/public/console/v2/kafka-cluster","Name":"KafkaCluster","ParentPathParam":[],"ListQueryParamter":{},"ApplyExample":"null\n","Order":2}}},"KafkaConnectCluster":{"Versions":{"2":{"ListPath":"/public/console/v2/cluster/{cluster}/kafka-connect","Name":"KafkaConnectCluster","ParentPathParam":["cluster"],"ListQueryParamter":{},"ApplyExample":"null\n","Order":3}}},"KsqlDBCluster":{"Versions":{"2":{"ListPath":"/public/console/v2/cluster/{cluster}/ksqldb","Name":"KsqlDBCluster","ParentPathParam":["cluster"],"ListQueryParamter":{},"ApplyExample":"null\n","Order":4}}},"ServiceAccount":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/cluster/{cluster}/service-account","Name":"ServiceAccount","ParentPathParam":["cluster"],"ListQueryParamter":{},"ApplyExample":"null\n","Order":10}}},"Subject":{"Versions":{"2":{"ListPath":"/public/kafka/v2/cluster/{cluster}/subject","Name":"Subject","ParentPathParam":["cluster"],"ListQueryParamter":{},"ApplyExample":"null\n","Order":12}}},"Topic":{"Versions":{"2":{"ListPath":"/public/kafka/v2/cluster/{cluster}/topic","Name":"Topic","ParentPathParam":["cluster"],"ListQueryParamter":{},"ApplyExample":"null\n","Order":11}}},"TopicPolicy":{"Versions":{"1":{"ListPath":"/public/self-serve/v1/topic-policy","Name":"TopicPolicy","ParentPathParam":[],"ListQueryParamter":{"app-instance":{"FlagName":"application-instance","Required":false,"Type":"string"}},"ApplyExample":"null\n","Order":5}}},"User":{"Versions":{"2":{"ListPath":"/public/iam/v2/user","Name":"User","ParentPathParam":[],"ListQueryParamter":{},"ApplyExample":"null\n","Order":0}}}} \ No newline at end of file diff --git a/schema/gateway_schema_test.go b/schema/gateway_schema_test.go index 89b8c01..7c1aff4 100644 --- a/schema/gateway_schema_test.go +++ b/schema/gateway_schema_test.go @@ -34,7 +34,17 @@ func TestGetKindWithYamlFromGateway(t *testing.T) { ParentPathParam: []string{}, ListQueryParameter: map[string]QueryParameterOption{}, GetAvailable: true, - Order: 7, + ApplyExample: `kind: VirtualCluster +apiVersion: gateway/v2 +metadata: + name: vcluster1 +spec: + aclEnabled: false + superUsers: + - username1 + - username2 +`, + Order: 7, }, }, }, @@ -63,6 +73,14 @@ func TestGetKindWithYamlFromGateway(t *testing.T) { }, GetAvailable: false, Order: 8, + ApplyExample: `kind: AliasTopic +apiVersion: gateway/v2 +metadata: + name: name1 + vCluster: vCluster1 +spec: + physicalName: physicalName1 +`, }, }, }, @@ -91,6 +109,20 @@ func TestGetKindWithYamlFromGateway(t *testing.T) { }, GetAvailable: false, Order: 9, + ApplyExample: `kind: ConcentrationRule +apiVersion: gateway/v2 +metadata: + name: concentrationRule1 + vCluster: vCluster1 +spec: + pattern: topic.* + physicalTopics: + delete: topic + compact: compact_topic + deleteCompact: compact_delete_topic + autoManaged: false + offsetCorrectness: false +`, }, }, }, @@ -100,6 +132,22 @@ func TestGetKindWithYamlFromGateway(t *testing.T) { Name: "GatewayGroup", ListPath: "/gateway/v2/group", ParentPathParam: []string{}, + ApplyExample: `kind: GatewayGroup +apiVersion: gateway/v2 +metadata: + name: group1 +spec: + members: + - vCluster: vCluster1 + name: serviceAccount1 + - vCluster: vCluster2 + name: serviceAccount2 + - vCluster: vCluster3 + name: serviceAccount3 + externalGroups: + - GROUP_READER + - GROUP_WRITER +`, ListQueryParameter: map[string]QueryParameterOption{ "showDefaults": { FlagName: "show-defaults", @@ -142,6 +190,16 @@ func TestGetKindWithYamlFromGateway(t *testing.T) { }, GetAvailable: false, Order: 10, + ApplyExample: `kind: GatewayServiceAccount +apiVersion: gateway/v2 +metadata: + name: user1 + vCluster: vcluster1 +spec: + type: EXTERNAL + externalNames: + - externalName +`, }, }, }, @@ -178,6 +236,20 @@ func TestGetKindWithYamlFromGateway(t *testing.T) { Type: "string", }, }, + ApplyExample: `kind: Interceptor +apiVersion: gateway/v2 +metadata: + name: yellow_cars_filter + scope: + vCluster: vCluster1 +spec: + comment: Filter yellow cars + pluginClass: io.conduktor.gateway.interceptor.VirtualSqlTopicPlugin + priority: 1 + config: + virtualTopic: yellow_cars + statement: SELECT '$.type' as type, '$.price' as price FROM cars WHERE '$.color' = 'yellow' +`, GetAvailable: false, Order: 12, }, diff --git a/schema/kind.go b/schema/kind.go index 5a3b301..a7c6c10 100644 --- a/schema/kind.go +++ b/schema/kind.go @@ -19,6 +19,7 @@ type KindVersion interface { GetParentPathParam() []string GetOrder() int GetListQueryParamter() map[string]QueryParameterOption + GetApplyExample() string } // two logics: uniformize flag name and kebab case @@ -38,6 +39,7 @@ type ConsoleKindVersion struct { Name string ParentPathParam []string ListQueryParamter map[string]QueryParameterOption + ApplyExample string Order int `json:1000` //same value DefaultPriority } @@ -45,6 +47,10 @@ func (c *ConsoleKindVersion) GetListPath() string { return c.ListPath } +func (c *ConsoleKindVersion) GetApplyExample() string { + return c.ApplyExample +} + func (c *ConsoleKindVersion) GetName() string { return c.Name } @@ -72,6 +78,7 @@ type GatewayKindVersion struct { ParentPathParam []string ListQueryParameter map[string]QueryParameterOption GetAvailable bool + ApplyExample string Order int `json:1000` //same value DefaultPriority } @@ -87,6 +94,10 @@ func (g *GatewayKindVersion) GetParentPathParam() []string { return g.ParentPathParam } +func (g *GatewayKindVersion) GetApplyExample() string { + return g.ApplyExample +} + func (g *GatewayKindVersion) GetOrder() int { return g.Order } diff --git a/schema/schema.go b/schema/schema.go index 3c1b157..e335ed7 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -10,6 +10,7 @@ import ( "github.com/conduktor/ctl/utils" "github.com/pb33f/libopenapi" v3high "github.com/pb33f/libopenapi/datamodel/high/v3" + "gopkg.in/yaml.v3" ) type Schema struct { @@ -83,6 +84,7 @@ func buildConsoleKindVersion(s *Schema, path, kind string, order int, put *v3hig newKind.ParentPathParam = append(newKind.ParentPathParam, putParameter.Name) } + } for _, getParameter := range get.Parameters { if getParameter.In == "query" { @@ -98,6 +100,13 @@ func buildConsoleKindVersion(s *Schema, path, kind string, order int, put *v3hig } } } + schemaJson, ok := put.RequestBody.Content.Get("application/json") + if ok && schemaJson.Example != nil { + data, err := yaml.Marshal(schemaJson.Example) + if err == nil { + newKind.ApplyExample = string(data) + } + } if strict { err := checkThatPathParamAreInSpec(newKind, put.RequestBody) if err != nil { @@ -130,6 +139,7 @@ func buildGatewayKindVersion(s *Schema, path, kind string, order int, put *v3hig ListPath: consoleKind.ListPath, ParentPathParam: consoleKind.ParentPathParam, ListQueryParameter: consoleKind.ListQueryParamter, + ApplyExample: consoleKind.ApplyExample, GetAvailable: getAvailable, Order: consoleKind.Order, }, nil