github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/util/flags/flags_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package flags
    21  
    22  import (
    23  	. "github.com/onsi/ginkgo/v2"
    24  	. "github.com/onsi/gomega"
    25  
    26  	"github.com/spf13/cobra"
    27  	clientfake "k8s.io/client-go/rest/fake"
    28  	"k8s.io/kube-openapi/pkg/validation/spec"
    29  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    30  
    31  	"github.com/1aal/kubeblocks/pkg/cli/testing"
    32  )
    33  
    34  const singleFlags = `{
    35    "$schema": "http://json-schema.org/schema#",
    36    "type": "object",
    37    "properties": {
    38      "version": {
    39        "title": "Version",
    40        "description": "Cluster version.",
    41        "type": "string",
    42        "default": "ac-mysql-8.0.30"
    43      },
    44      "mode": {
    45        "title": "Mode",
    46        "description": "Cluster topology mode.",
    47        "type": "string",
    48        "default": "standalone",
    49        "enum": [
    50          "standalone",
    51          "raftGroup"
    52        ]
    53      },
    54      "replicas": {
    55        "title": "Replicas",
    56        "description": "The number of replicas, for standalone mode, the replicas is 1, for raftGroup mode, the default replicas is 3.",
    57        "type": "integer",
    58        "default": 1,
    59        "minimum": 1,
    60        "maximum": 5
    61      },
    62      "cpu": {
    63        "title": "CPU",
    64        "description": "CPU cores.",
    65        "type": [
    66          "number",
    67          "string"
    68        ],
    69        "default": 0.5,
    70        "minimum": 0.5,
    71        "maximum": 64,
    72        "multipleOf": 0.5
    73      },
    74      "memory": {
    75        "title": "Memory(Gi)",
    76        "description": "Memory, the unit is Gi.",
    77        "type": [
    78          "number",
    79          "string"
    80        ],
    81        "default": 0.5,
    82        "minimum": 0.5,
    83        "maximum": 1000
    84      },
    85      "storage": {
    86        "title": "Storage(Gi)",
    87        "description": "Storage size, the unit is Gi.",
    88        "type": [
    89          "number",
    90          "string"
    91        ],
    92        "default": 20,
    93        "minimum": 1,
    94        "maximum": 10000
    95      },
    96      "proxyEnabled": {
    97        "title": "Proxy",
    98        "description": "Enable proxy or not.",
    99        "type": "boolean",
   100        "default": false
   101      }
   102    }
   103  }
   104  `
   105  
   106  // objectFlags is the schema.json of risingwave-cluster
   107  const objectFlags = `
   108  {
   109      "$schema": "http://json-schema.org/schema#",
   110      "type": "object",
   111      "properties": {
   112          "risingwave": {
   113              "type": "object",
   114              "properties": {
   115                  "metaStore": {
   116                      "type": "object",
   117                      "properties": {
   118                          "etcd": {
   119                              "type": "object",
   120                              "properties": {
   121                                  "endpoints": {
   122                                      "title": "ETCD EndPoints",
   123                                      "description": "Specify ETCD cluster endpoints of the form host:port",
   124                                      "type": "string",
   125                                      "pattern": "^.+:\\d+$"
   126                                  }
   127                              }
   128                          }
   129                      }
   130                  },
   131                  "stateStore": {
   132                      "type": "object",
   133                      "properties": {
   134                          "s3": {
   135                              "type": "object",
   136                              "properties": {
   137                                  "authentication": {
   138                                      "type": "object",
   139                                      "properties": {
   140                                          "accessKey": {
   141                                              "$ref": "#/definitions/nonEmptyString",
   142                                              "description": "Specify the S3 access key."
   143                                          },
   144                                          "secretAccessKey": {
   145                                              "$ref": "#/definitions/nonEmptyString",
   146                                              "description": "Specify the S3 secret access key."
   147                                          }
   148                                      }
   149                                  },
   150                                  "bucket": {
   151                                      "$ref": "#/definitions/nonEmptyString",
   152                                      "description": "Specify the S3 bucket."
   153                                  },
   154                                  "endpoint": {
   155                                      "$ref": "#/definitions/nonEmptyString",
   156                                      "description": "Specify the S3 endpoint."
   157                                  },
   158                                  "region": {
   159                                      "$ref": "#/definitions/nonEmptyString",
   160                                      "description": "Specify the S3 region."
   161                                  }
   162                              }
   163                          }
   164                      }
   165                  }
   166              }
   167          }
   168      },
   169      "definitions": {
   170          "nonEmptyString": {
   171              "type": "string",
   172              "minLength": 1
   173          }
   174      }
   175  }
   176  `
   177  
   178  /*
   179  	objectArrayFlags yaml example:
   180  
   181  apiVersion: 1
   182  name: myapp
   183  servers:
   184    - name: server1
   185      address: 192.168.1.10
   186      port: 8080
   187    - name: server2
   188      address: 192.168.1.20
   189      port: 8081
   190  */
   191  const objectArrayFlags = `{
   192    "$schema": "http://json-schema.org/schema#",
   193    "type": "object",
   194    "properties": {
   195      "apiVersion": {
   196        "type": "integer"
   197      },
   198      "name": {
   199        "type": "string"
   200      },
   201      "servers": {
   202        "type": "array",
   203        "items": {
   204          "type": "object",
   205          "properties": {
   206            "name": {
   207              "type": "string"
   208            },
   209            "address": {
   210              "type": "string",
   211              "format": "ipv4"
   212            },
   213            "port": {
   214              "type": "integer",
   215              "minimum": 1,
   216              "maximum": 65535
   217            }
   218          },
   219          "required": ["name", "address", "port"]
   220        }
   221      }
   222    },
   223    "required": ["apiVersion", "name"]
   224  }
   225  `
   226  
   227  /*
   228  	simpleArrayFlags yaml example:
   229  
   230  name:
   231  - alal
   232  - jack
   233  */
   234  const simpleArrayFlags = `
   235  {
   236    "$schema": "http://json-schema.org/schema#",
   237    "type": "object",
   238    "properties": {
   239      "name": {
   240        "type": "array",
   241        "items": {
   242          "type": "string"
   243        }
   244      }
   245    }
   246  }
   247  `
   248  
   249  /*
   250  	arrayWithArray yaml example:
   251  
   252  data:
   253    - name: John
   254      age: 30
   255      hobbies:
   256    - Reading
   257    - Swimming
   258    - name: Alice
   259      age: 28
   260      hobbies:
   261    - Painting
   262    - Hiking
   263    - name: Bob
   264      age: 35
   265      hobbies:
   266    - Cycling
   267  */
   268  const arrayWithArray = `{
   269      "$schema": "https://json-schema.org/draft/2019-09/schema",
   270      "title": "Root Schema",
   271      "type": "object",
   272      "properties": {
   273          "data": {
   274              "title": "The data Schema",
   275              "type": "array",
   276              "items": {
   277                  "title": "A Schema",
   278                  "type": "object",
   279                  "properties": {
   280                      "name": {
   281                          "title": "The name Schema",
   282                          "type": "string"
   283                      },
   284                      "age": {
   285                          "title": "The age Schema",
   286                          "type": "number"
   287                      },
   288                      "hobbies": {
   289                          "title": "The hobbies Schema",
   290                          "type": "array",
   291                          "items": {
   292                              "title": "A Schema",
   293                              "type": "string"
   294                          }
   295                      }
   296                  }
   297              }
   298          }
   299      }
   300  }`
   301  
   302  var _ = Describe("flag", func() {
   303  	var cmd *cobra.Command
   304  	var schema *spec.Schema
   305  	testCast := []struct {
   306  		description   string
   307  		rawSchemaJSON string
   308  		flags         []string
   309  		success       bool
   310  	}{
   311  		{"test single flags",
   312  			singleFlags,
   313  			[]string{
   314  				"version", "mode", "replicas", "cpu", "memory", "storage", "proxy-enabled",
   315  			},
   316  			true,
   317  		}, {
   318  			"test complex flags",
   319  			objectFlags,
   320  			[]string{"risingwave.meta-store.etcd.endpoints",
   321  				"risingwave.state-store.s3.authentication.access-key",
   322  				"risingwave.state-store.s3.authentication.secret-access-key",
   323  				"risingwave.state-store.s3.bucket",
   324  				"risingwave.state-store.s3.endpoint",
   325  				"risingwave.state-store.s3.region",
   326  			},
   327  			true,
   328  		}, {
   329  			"test object array flags",
   330  			objectArrayFlags,
   331  			[]string{"api-version", "name", "servers.name", "servers.address", "servers.port"},
   332  			true,
   333  		}, {
   334  			"test simple array flags",
   335  			simpleArrayFlags,
   336  			[]string{"name"},
   337  			true,
   338  		}, {
   339  			"test array with array object",
   340  			arrayWithArray,
   341  			nil,
   342  			false,
   343  		},
   344  	}
   345  	It("test BuildFlagsBySchema", func() {
   346  		for i := range testCast {
   347  			cmd = &cobra.Command{}
   348  			schema = &spec.Schema{}
   349  			By(testCast[i].description, func() {
   350  				Expect(schema.UnmarshalJSON([]byte(testCast[i].rawSchemaJSON))).Should(Succeed())
   351  				if testCast[i].success {
   352  					Expect(BuildFlagsBySchema(cmd, schema)).Should(Succeed())
   353  					for _, flag := range testCast[i].flags {
   354  						Expect(cmd.Flags().Lookup(flag)).ShouldNot(BeNil())
   355  					}
   356  				} else {
   357  					Expect(BuildFlagsBySchema(cmd, schema)).Should(HaveOccurred())
   358  				}
   359  			})
   360  		}
   361  	})
   362  
   363  	Context("test autoCompleteClusterComponent ", func() {
   364  		var tf *cmdtesting.TestFactory
   365  		var flag string
   366  		BeforeEach(func() {
   367  			tf = cmdtesting.NewTestFactory()
   368  			fakeCluster := testing.FakeCluster("fake-cluster", "")
   369  			tf.FakeDynamicClient = testing.FakeDynamicClient(fakeCluster)
   370  			tf.Client = &clientfake.RESTClient{}
   371  			cmd = &cobra.Command{
   372  				Use:   "test",
   373  				Short: "test for autoComplete",
   374  			}
   375  			cmd.Flags().StringVar(&flag, "component", "", "test")
   376  		})
   377  
   378  		AfterEach(func() {
   379  			tf.Cleanup()
   380  		})
   381  
   382  		It("test build autoCompleteClusterComponent", func() {
   383  			Expect(autoCompleteClusterComponent(cmd, tf, "component")).Should(Succeed())
   384  		})
   385  	})
   386  })