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 })