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