github.com/oam-dev/kubevela@v1.9.11/references/cli/addon_suite_test.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 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 */ 16 17 package cli 18 19 import ( 20 "context" 21 "crypto/rand" 22 "crypto/tls" 23 "fmt" 24 "net/http" 25 "net/http/httptest" 26 "os" 27 "strings" 28 "time" 29 30 "github.com/fatih/color" 31 "github.com/gosuri/uitable" 32 . "github.com/onsi/ginkgo/v2" 33 . "github.com/onsi/gomega" 34 "k8s.io/helm/pkg/tlsutil" 35 "sigs.k8s.io/yaml" 36 37 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 38 pkgaddon "github.com/oam-dev/kubevela/pkg/addon" 39 "github.com/oam-dev/kubevela/pkg/oam/util" 40 "github.com/oam-dev/kubevela/pkg/utils/common" 41 ) 42 43 var _ = Describe("Output of listing addons tests", func() { 44 // Output of function listAddons to test 45 var actualTable *uitable.Table 46 47 // getRowsByName extracts every rows with its NAME matching name 48 getRowsByName := func(name string) []*uitable.Row { 49 matchedRows := []*uitable.Row{} 50 for _, row := range actualTable.Rows { 51 // Check column NAME(0) = name 52 if row.Cells[0].Data == name { 53 matchedRows = append(matchedRows, row) 54 } 55 } 56 return matchedRows 57 } 58 59 BeforeEach(func() { 60 // Prepare KubeVela registry 61 reg := &pkgaddon.Registry{ 62 Name: "KubeVela", 63 Helm: &pkgaddon.HelmSource{ 64 URL: "https://addons.kubevela.net", 65 }, 66 } 67 ds := pkgaddon.NewRegistryDataStore(k8sClient) 68 Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed()) 69 }) 70 71 AfterEach(func() { 72 // Delete KubeVela registry 73 ds := pkgaddon.NewRegistryDataStore(k8sClient) 74 Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed()) 75 }) 76 77 JustBeforeEach(func() { 78 // Print addon list to table for later comparison 79 ret, err := listAddons(context.Background(), k8sClient, "") 80 Expect(err).Should(BeNil()) 81 actualTable = ret 82 }) 83 84 When("there is no addons installed", func() { 85 It("should not have any enabled addon", func() { 86 Expect(actualTable.Rows).ToNot(HaveLen(0)) 87 for idx, row := range actualTable.Rows { 88 // Skip header 89 if idx == 0 { 90 continue 91 } 92 // Check column STATUS(4) = disabled 93 Expect(row.Cells[4].Data).To(Equal("-")) 94 } 95 }) 96 }) 97 98 When("there is locally installed addons", func() { 99 BeforeEach(func() { 100 // Install fluxcd locally 101 fluxcd := v1beta1.Application{} 102 err := yaml.Unmarshal([]byte(fluxcdYaml), &fluxcd) 103 Expect(err).Should(BeNil()) 104 Expect(k8sClient.Create(context.Background(), &fluxcd)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{})) 105 }) 106 107 It("should print fluxcd addon as local", func() { 108 matchedRows := getRowsByName("fluxcd") 109 Expect(matchedRows).ToNot(HaveLen(0)) 110 // Only use first row (local first), check column REGISTRY(1) = local 111 Expect(matchedRows[0].Cells[1].Data).To(Equal("local")) 112 Eventually(func() error { 113 matchedRows = getRowsByName("fluxcd") 114 // Check column STATUS(4) = enabled 115 if matchedRows[0].Cells[4].Data != "enabled" { 116 return fmt.Errorf("fluxcd is not enabled yet") 117 } 118 // Check column AVAILABLE-VERSIONS(3) = 1.1.0 119 if versionString := matchedRows[0].Cells[3].Data; versionString != fmt.Sprintf("[%s]", color.New(color.Bold, color.FgGreen).Sprintf("1.1.0")) { 120 return fmt.Errorf("fluxcd version string is incorrect: %s", versionString) 121 } 122 return nil 123 }, 30*time.Second, 1000*time.Millisecond).Should(BeNil()) 124 }) 125 126 It("should print fluxcd in the registry as disabled", func() { 127 matchedRows := getRowsByName("fluxcd") 128 // There should be a local one and a registry one 129 Expect(len(matchedRows)).To(Equal(2)) 130 // The registry one should be disabled 131 Expect(matchedRows[1].Cells[1].Data).To(Equal("KubeVela")) 132 Expect(matchedRows[1].Cells[4].Data).To(Equal("-")) 133 }) 134 }) 135 }) 136 137 var _ = Describe("Addon status or info", func() { 138 139 Context("when verbose is enabled", func() { 140 BeforeEach(func() { 141 verboseStatus = true 142 }) 143 144 When("addon is not installed locally, also not in registry", func() { 145 It("should return an error, saying not found", func() { 146 addonName := "some-nonexistent-addon" 147 _, _, err := generateAddonInfo(k8sClient, addonName) 148 Expect(err).ShouldNot(BeNil()) 149 }) 150 }) 151 152 When("addon is not installed locally, but in registry", func() { 153 // Prepare KubeVela registry 154 BeforeEach(func() { 155 reg := &pkgaddon.Registry{ 156 Name: "KubeVela", 157 Helm: &pkgaddon.HelmSource{ 158 URL: "https://addons.kubevela.net", 159 }, 160 } 161 ds := pkgaddon.NewRegistryDataStore(k8sClient) 162 Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed()) 163 }) 164 165 AfterEach(func() { 166 // Delete KubeVela registry 167 ds := pkgaddon.NewRegistryDataStore(k8sClient) 168 Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed()) 169 }) 170 171 It("should display addon name and disabled status, registry name, available versions, dependencies, and parameters(optional)", func() { 172 addonName := "velaux" 173 res, _, err := generateAddonInfo(k8sClient, addonName) 174 Expect(err).Should(BeNil()) 175 // Should include disabled status, like: 176 // velaux: disabled 177 Expect(res).To(ContainSubstring( 178 color.New(color.Bold).Sprintf("%s", addonName) + ": " + color.New(color.Faint).Sprintf("%s", statusDisabled), 179 )) 180 // Should include registry name, like: 181 // ==> Registry Name 182 // KubeVela 183 Expect(res).To(ContainSubstring( 184 color.New(color.Bold).Sprintf("%s", "Registry Name") + "\n" + 185 "KubeVela", 186 )) 187 // Should include available versions, like: 188 // ==> Available Versions 189 // [v2.6.3] 190 Expect(res).To(ContainSubstring( 191 color.New(color.Bold).Sprintf("%s", "vailable Versions") + "\n" + 192 "[", 193 )) 194 // Should include dependencies, like: 195 // ==> Dependencies ✔ 196 // [] 197 Expect(res).To(ContainSubstring( 198 color.New(color.Bold).Sprintf("%s", "Dependencies ") + color.GreenString("✔") + "\n" + 199 "[]", 200 )) 201 // Should include parameters, like: 202 // ==> Parameters 203 // -> serviceAccountName: Specify the serviceAccountName for apiserver 204 Expect(res).To(ContainSubstring( 205 color.New(color.Bold).Sprintf("%s", "Parameters") + "\n" + 206 color.New(color.FgCyan).Sprintf("-> "), 207 )) 208 }) 209 }) 210 211 When("addon is installed locally, and also in registry", func() { 212 fluxcd := v1beta1.Application{} 213 err := yaml.Unmarshal([]byte(fluxcdRemoteYaml), &fluxcd) 214 Expect(err).Should(BeNil()) 215 216 BeforeEach(func() { 217 // Prepare KubeVela registry 218 reg := &pkgaddon.Registry{ 219 Name: "KubeVela", 220 Helm: &pkgaddon.HelmSource{ 221 URL: "https://addons.kubevela.net", 222 }, 223 } 224 ds := pkgaddon.NewRegistryDataStore(k8sClient) 225 Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed()) 226 }) 227 228 AfterEach(func() { 229 // Delete fluxcd 230 Expect(k8sClient.Delete(context.Background(), &fluxcd)).To(Succeed()) 231 // Delete KubeVela registry 232 ds := pkgaddon.NewRegistryDataStore(k8sClient) 233 Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed()) 234 }) 235 236 JustBeforeEach(func() { 237 // Install fluxcd locally 238 Expect(k8sClient.Create(context.Background(), &fluxcd)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{})) 239 }) 240 241 It("should display addon name and enabled status, installed clusters, registry name, available versions, dependencies, and parameters(optional)", func() { 242 addonName := "fluxcd" 243 Eventually(func() error { 244 res, _, err := generateAddonInfo(k8sClient, addonName) 245 if err != nil { 246 return err 247 } 248 // Should include enabled status, like: 249 // fluxcd: enabled (1.1.0) 250 if !strings.Contains(res, 251 color.New(color.Bold).Sprintf("%s", addonName), 252 ) { 253 return fmt.Errorf("addon name incorrect, %s", res) 254 } 255 256 // We cannot really get installed clusters in this test environment. 257 // Might change how this test is conducted in the future. 258 return nil 259 }, 30*time.Second, 1000*time.Millisecond).Should(BeNil()) 260 }) 261 }) 262 263 When("addon is installed locally, but not in registry", func() { 264 fluxcd := v1beta1.Application{} 265 err := yaml.Unmarshal([]byte(fluxcdYaml), &fluxcd) 266 Expect(err).Should(BeNil()) 267 268 BeforeEach(func() { 269 // Delete KubeVela registry 270 ds := pkgaddon.NewRegistryDataStore(k8sClient) 271 Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).Should(SatisfyAny(Succeed(), util.NotFoundMatcher{})) 272 // Install fluxcd locally 273 Expect(k8sClient.Create(context.Background(), &fluxcd)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{})) 274 }) 275 276 AfterEach(func() { 277 // Delete fluxcd 278 Expect(k8sClient.Delete(context.Background(), &fluxcd)).To(Succeed()) 279 }) 280 281 It("should display addon name and enabled status, installed clusters, and registry name as local, nothing more", func() { 282 addonName := "fluxcd" 283 284 Eventually(func() error { 285 res, _, err := generateAddonInfo(k8sClient, addonName) 286 if err != nil { 287 return err 288 } 289 fmt.Println(addonName, res, err) 290 // Should include enabled status, like: 291 // fluxcd: enabled (1.1.0) 292 if !strings.Contains(res, 293 color.New(color.Bold).Sprintf("%s", addonName)+": ", 294 ) { 295 return fmt.Errorf("addon name and enabled status incorrect:, %s", res) 296 } 297 // We cannot really get installed clusters in this test environment. 298 // Might change how this test is conducted in the future. 299 300 // Should include registry name, like: 301 // ==> Registry Name 302 // local 303 if !strings.Contains(res, 304 color.New(color.Bold).Sprintf("%s", "Registry Name")+"\n"+ 305 "local", 306 ) { 307 return fmt.Errorf("registry name incorrect, %s", res) 308 } 309 return nil 310 }, 30*time.Second, 1000*time.Millisecond).Should(BeNil()) 311 }) 312 }) 313 }) 314 315 Context("when verbose is disabled", func() { 316 When("addon is not installed locally, but in registry", func() { 317 // Prepare KubeVela registry 318 BeforeEach(func() { 319 reg := &pkgaddon.Registry{ 320 Name: "KubeVela", 321 Helm: &pkgaddon.HelmSource{ 322 URL: "https://addons.kubevela.net", 323 }, 324 } 325 ds := pkgaddon.NewRegistryDataStore(k8sClient) 326 Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed()) 327 }) 328 329 AfterEach(func() { 330 // Delete KubeVela registry 331 ds := pkgaddon.NewRegistryDataStore(k8sClient) 332 Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed()) 333 }) 334 335 It("should display addon name and disabled status, and registry name", func() { 336 addonName := "dex" 337 res, _, err := generateAddonInfo(k8sClient, addonName) 338 Expect(err).Should(BeNil()) 339 // Should include disabled status, like: 340 // dex: disabled 341 Expect(res).To(ContainSubstring( 342 color.New(color.Bold).Sprintf("%s", addonName) + ": " + color.New(color.Faint).Sprintf("%s", statusDisabled), 343 )) 344 // Should include registry name, like: 345 // ==> Registry Name 346 // KubeVela 347 Expect(res).To(ContainSubstring( 348 color.New(color.Bold).Sprintf("%s", "Registry Name") + "\n" + 349 "KubeVela", 350 )) 351 }) 352 It("should report addon not exist in any registry name", func() { 353 addonName := "not-exist" 354 _, _, err := generateAddonInfo(k8sClient, addonName) 355 Expect(err.Error()).Should(BeEquivalentTo("addon 'not-exist' not found in cluster or any registry")) 356 }) 357 }) 358 }) 359 }) 360 361 var _ = Describe("Addon push command", func() { 362 var c common.Args 363 var ( 364 testTarballPath = "../../pkg/addon/testdata/charts/sample-1.0.1.tgz" 365 testServerCertPath = "../../pkg/addon/testdata/tls/server.crt" 366 testServerKeyPath = "../../pkg/addon/testdata/tls/server.key" 367 testServerCAPath = "../../pkg/addon/testdata/tls/server_ca.crt" 368 testClientCAPath = "../../pkg/addon/testdata/tls/client_ca.crt" 369 testClientCertPath = "../../pkg/addon/testdata/tls/client.crt" 370 testClientKeyPath = "../../pkg/addon/testdata/tls/client.key" 371 ) 372 var ( 373 statusCode int 374 body string 375 tmp string 376 ) 377 var ts *httptest.Server 378 var err error 379 380 AfterEach(func() { 381 ts.Close() 382 _ = os.RemoveAll(tmp) 383 err = deleteAddonRegistry(context.TODO(), c, "helm-push-test") 384 Expect(err).To(Succeed()) 385 }) 386 387 Context("plain old HTTP Server", func() { 388 BeforeEach(func() { 389 c.SetClient(k8sClient) 390 c.SetConfig(cfg) 391 392 statusCode = 201 393 body = "{\"success\": true}" 394 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 395 w.WriteHeader(statusCode) 396 w.Write([]byte(body)) 397 })) 398 399 // Create new Helm home w/ test repo 400 tmp, err = os.MkdirTemp("", "helm-push-test") 401 Expect(err).To(Succeed()) 402 403 // Add our helm repo to addon registry 404 err = addAddonRegistry(context.TODO(), c, pkgaddon.Registry{ 405 Name: "helm-push-test", 406 Helm: &pkgaddon.HelmSource{ 407 URL: ts.URL, 408 }, 409 }) 410 Expect(err).To(Succeed()) 411 412 _ = os.Setenv("HELM_REPO_USERNAME", "myuser") 413 _ = os.Setenv("HELM_REPO_PASSWORD", "mypass") 414 _ = os.Setenv("HELM_REPO_CONTEXT_PATH", "/x/y/z") 415 }) 416 417 It("Not enough args", func() { 418 args := []string{} 419 cmd := NewAddonPushCommand(c) 420 cmd.SetArgs(args) 421 err := cmd.RunE(cmd, args) 422 Expect(err).ShouldNot(Succeed(), "expecting error with missing args, instead got nil") 423 }) 424 425 It("Bad chart path", func() { 426 args := []string{"/this/this/not/a/chart", "helm-push-test"} 427 cmd := NewAddonPushCommand(c) 428 cmd.SetArgs(args) 429 err := cmd.RunE(cmd, args) 430 Expect(err).ShouldNot(Succeed(), "expecting error with bad chart path, instead got nil") 431 }) 432 433 It("Bad repo name", func() { 434 args := []string{testTarballPath, "this-is-not-a-valid-repo"} 435 cmd := NewAddonPushCommand(c) 436 cmd.SetArgs(args) 437 err := cmd.RunE(cmd, args) 438 Expect(err).ShouldNot(Succeed(), "expecting error with bad repo name, instead got nil") 439 }) 440 441 It("Valid tar, repo name", func() { 442 args := []string{testTarballPath, "helm-push-test"} 443 cmd := NewAddonPushCommand(c) 444 cmd.SetArgs(args) 445 err := cmd.RunE(cmd, args) 446 Expect(err).Should(Succeed()) 447 }) 448 449 It("Valid tar, repo URL", func() { 450 args := []string{testTarballPath, ts.URL} 451 cmd := NewAddonPushCommand(c) 452 cmd.SetArgs(args) 453 err := cmd.RunE(cmd, args) 454 Expect(err).Should(Succeed()) 455 }) 456 457 It("Trigger 409, already exists", func() { 458 statusCode = 409 459 body = "{\"error\": \"package already exists\"}" 460 args := []string{testTarballPath, "helm-push-test"} 461 cmd := NewAddonPushCommand(c) 462 cmd.SetArgs(args) 463 err := cmd.RunE(cmd, args) 464 Expect(err).ShouldNot(Succeed(), "expecting error with 409, instead got nil") 465 }) 466 467 It("Unable to parse JSON response body", func() { 468 statusCode = 500 469 body = "duiasnhioasd" 470 args := []string{testTarballPath, "helm-push-test"} 471 cmd := NewAddonPushCommand(c) 472 cmd.SetArgs(args) 473 err := cmd.RunE(cmd, args) 474 Expect(err).ShouldNot(Succeed(), "expecting error with bad response body, instead got nil") 475 }) 476 }) 477 478 Context("TLS Enabled Server", func() { 479 BeforeEach(func() { 480 c.SetClient(k8sClient) 481 c.SetConfig(cfg) 482 483 statusCode = 201 484 body = "{\"success\": true}" 485 ts = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 486 w.WriteHeader(statusCode) 487 _, _ = w.Write([]byte(body)) 488 })) 489 serverCert, err := tls.LoadX509KeyPair(testServerCertPath, testServerKeyPath) 490 Expect(err).To(Succeed(), "failed to load certificate and key") 491 492 clientCaCertPool, err := tlsutil.CertPoolFromFile(testClientCAPath) 493 Expect(err).To(Succeed(), "load server CA file failed") 494 495 ts.TLS = &tls.Config{ 496 ClientCAs: clientCaCertPool, 497 ClientAuth: tls.RequireAndVerifyClientCert, 498 Certificates: []tls.Certificate{serverCert}, 499 Rand: rand.Reader, 500 } 501 ts.StartTLS() 502 503 // Create new Helm home w/ test repo 504 tmp, err = os.MkdirTemp("", "helm-push-test") 505 Expect(err).To(Succeed()) 506 507 // Add our helm repo to addon registry 508 err = addAddonRegistry(context.TODO(), c, pkgaddon.Registry{ 509 Name: "helm-push-test", 510 Helm: &pkgaddon.HelmSource{ 511 URL: ts.URL, 512 }, 513 }) 514 Expect(err).To(Succeed()) 515 516 _ = os.Setenv("HELM_REPO_USERNAME", "myuser") 517 _ = os.Setenv("HELM_REPO_PASSWORD", "mypass") 518 _ = os.Setenv("HELM_REPO_CONTEXT_PATH", "/x/y/z") 519 }) 520 521 It("no cert provided", func() { 522 _ = os.Unsetenv("HELM_REPO_CA_FILE") 523 _ = os.Unsetenv("HELM_REPO_CERT_FILE") 524 _ = os.Unsetenv("HELM_REPO_KEY_FILE") 525 args := []string{testTarballPath, "helm-push-test"} 526 cmd := NewAddonPushCommand(c) 527 cmd.SetArgs(args) 528 err := cmd.RunE(cmd, args) 529 Expect(err).ShouldNot(Succeed(), "expected non nil error but got nil when run cmd without certificate option") 530 }) 531 532 It("with cert", func() { 533 _ = os.Setenv("HELM_REPO_CA_FILE", testServerCAPath) 534 _ = os.Setenv("HELM_REPO_CERT_FILE", testClientCertPath) 535 _ = os.Setenv("HELM_REPO_KEY_FILE", testClientKeyPath) 536 args := []string{testTarballPath, "helm-push-test"} 537 cmd := NewAddonPushCommand(c) 538 cmd.SetArgs(args) 539 err := cmd.RunE(cmd, args) 540 Expect(err).Should(Succeed()) 541 }) 542 }) 543 })