kubevela/references/cli/addon_suite_test.go

544 lines
17 KiB
Go

/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cli
import (
"context"
"crypto/rand"
"crypto/tls"
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"time"
"github.com/fatih/color"
"github.com/gosuri/uitable"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/helm/pkg/tlsutil"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
pkgaddon "github.com/oam-dev/kubevela/pkg/addon"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
var _ = Describe("Output of listing addons tests", func() {
// Output of function listAddons to test
var actualTable *uitable.Table
// getRowsByName extracts every rows with its NAME matching name
getRowsByName := func(name string) []*uitable.Row {
matchedRows := []*uitable.Row{}
for _, row := range actualTable.Rows {
// Check column NAME(0) = name
if row.Cells[0].Data == name {
matchedRows = append(matchedRows, row)
}
}
return matchedRows
}
BeforeEach(func() {
// Prepare KubeVela registry
reg := &pkgaddon.Registry{
Name: "KubeVela",
Helm: &pkgaddon.HelmSource{
URL: "https://addons.kubevela.net",
},
}
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed())
})
AfterEach(func() {
// Delete KubeVela registry
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed())
})
JustBeforeEach(func() {
// Print addon list to table for later comparison
ret, err := listAddons(context.Background(), k8sClient, "")
Expect(err).Should(BeNil())
actualTable = ret
})
When("there is no addons installed", func() {
It("should not have any enabled addon", func() {
Expect(actualTable.Rows).ToNot(HaveLen(0))
for idx, row := range actualTable.Rows {
// Skip header
if idx == 0 {
continue
}
// Check column STATUS(4) = disabled
Expect(row.Cells[4].Data).To(Equal("-"))
}
})
})
When("there is locally installed addons", func() {
BeforeEach(func() {
// Install fluxcd locally
fluxcd := v1beta1.Application{}
err := yaml.Unmarshal([]byte(fluxcdYaml), &fluxcd)
Expect(err).Should(BeNil())
Expect(k8sClient.Create(context.Background(), &fluxcd)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
})
It("should print fluxcd addon as local", func() {
matchedRows := getRowsByName("fluxcd")
Expect(matchedRows).ToNot(HaveLen(0))
// Only use first row (local first), check column REGISTRY(1) = local
Expect(matchedRows[0].Cells[1].Data).To(Equal("local"))
Eventually(func() error {
matchedRows = getRowsByName("fluxcd")
// Check column STATUS(4) = enabled
if matchedRows[0].Cells[4].Data != "enabled" {
return fmt.Errorf("fluxcd is not enabled yet")
}
// Check column AVAILABLE-VERSIONS(3) = 1.1.0
if versionString := matchedRows[0].Cells[3].Data; versionString != fmt.Sprintf("[%s]", color.New(color.Bold, color.FgGreen).Sprintf("1.1.0")) {
return fmt.Errorf("fluxcd version string is incorrect: %s", versionString)
}
return nil
}, 30*time.Second, 1000*time.Millisecond).Should(BeNil())
})
It("should print fluxcd in the registry as disabled", func() {
matchedRows := getRowsByName("fluxcd")
// There should be a local one and a registry one
Expect(len(matchedRows)).To(Equal(2))
// The registry one should be disabled
Expect(matchedRows[1].Cells[1].Data).To(Equal("KubeVela"))
Expect(matchedRows[1].Cells[4].Data).To(Equal("-"))
})
})
})
var _ = Describe("Addon status or info", func() {
Context("when verbose is enabled", func() {
BeforeEach(func() {
verboseStatus = true
})
When("addon is not installed locally, also not in registry", func() {
It("should return an error, saying not found", func() {
addonName := "some-nonexistent-addon"
_, _, err := generateAddonInfo(k8sClient, addonName)
Expect(err).ShouldNot(BeNil())
})
})
When("addon is not installed locally, but in registry", func() {
// Prepare KubeVela registry
BeforeEach(func() {
reg := &pkgaddon.Registry{
Name: "KubeVela",
Helm: &pkgaddon.HelmSource{
URL: "https://addons.kubevela.net",
},
}
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed())
})
AfterEach(func() {
// Delete KubeVela registry
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed())
})
It("should display addon name and disabled status, registry name, available versions, dependencies, and parameters(optional)", func() {
addonName := "velaux"
res, _, err := generateAddonInfo(k8sClient, addonName)
Expect(err).Should(BeNil())
// Should include disabled status, like:
// velaux: disabled
Expect(res).To(ContainSubstring(
color.New(color.Bold).Sprintf("%s", addonName) + ": " + color.New(color.Faint).Sprintf("%s", statusDisabled),
))
// Should include registry name, like:
// ==> Registry Name
// KubeVela
Expect(res).To(ContainSubstring(
color.New(color.Bold).Sprintf("%s", "Registry Name") + "\n" +
"KubeVela",
))
// Should include available versions, like:
// ==> Available Versions
// [v2.6.3]
Expect(res).To(ContainSubstring(
color.New(color.Bold).Sprintf("%s", "vailable Versions") + "\n" +
"[",
))
// Should include dependencies, like:
// ==> Dependencies ✔
// []
Expect(res).To(ContainSubstring(
color.New(color.Bold).Sprintf("%s", "Dependencies ") + color.GreenString("✔") + "\n" +
"[]",
))
// Should include parameters, like:
// ==> Parameters
// -> serviceAccountName: Specify the serviceAccountName for apiserver
Expect(res).To(ContainSubstring(
color.New(color.Bold).Sprintf("%s", "Parameters") + "\n" +
color.New(color.FgCyan).Sprintf("-> "),
))
})
})
When("addon is installed locally, and also in registry", func() {
fluxcd := v1beta1.Application{}
err := yaml.Unmarshal([]byte(fluxcdRemoteYaml), &fluxcd)
Expect(err).Should(BeNil())
BeforeEach(func() {
// Prepare KubeVela registry
reg := &pkgaddon.Registry{
Name: "KubeVela",
Helm: &pkgaddon.HelmSource{
URL: "https://addons.kubevela.net",
},
}
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed())
})
AfterEach(func() {
// Delete fluxcd
Expect(k8sClient.Delete(context.Background(), &fluxcd)).To(Succeed())
// Delete KubeVela registry
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed())
})
JustBeforeEach(func() {
// Install fluxcd locally
Expect(k8sClient.Create(context.Background(), &fluxcd)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
})
It("should display addon name and enabled status, installed clusters, registry name, available versions, dependencies, and parameters(optional)", func() {
addonName := "fluxcd"
Eventually(func() error {
res, _, err := generateAddonInfo(k8sClient, addonName)
if err != nil {
return err
}
// Should include enabled status, like:
// fluxcd: enabled (1.1.0)
if !strings.Contains(res,
color.New(color.Bold).Sprintf("%s", addonName),
) {
return fmt.Errorf("addon name incorrect, %s", res)
}
// We cannot really get installed clusters in this test environment.
// Might change how this test is conducted in the future.
return nil
}, 30*time.Second, 1000*time.Millisecond).Should(BeNil())
})
})
When("addon is installed locally, but not in registry", func() {
fluxcd := v1beta1.Application{}
err := yaml.Unmarshal([]byte(fluxcdYaml), &fluxcd)
Expect(err).Should(BeNil())
BeforeEach(func() {
// Delete KubeVela registry
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).Should(SatisfyAny(Succeed(), util.NotFoundMatcher{}))
// Install fluxcd locally
Expect(k8sClient.Create(context.Background(), &fluxcd)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
})
AfterEach(func() {
// Delete fluxcd
Expect(k8sClient.Delete(context.Background(), &fluxcd)).To(Succeed())
})
It("should display addon name and enabled status, installed clusters, and registry name as local, nothing more", func() {
addonName := "fluxcd"
Eventually(func() error {
res, _, err := generateAddonInfo(k8sClient, addonName)
if err != nil {
return err
}
fmt.Println(addonName, res, err)
// Should include enabled status, like:
// fluxcd: enabled (1.1.0)
if !strings.Contains(res,
color.New(color.Bold).Sprintf("%s", addonName)+": ",
) {
return fmt.Errorf("addon name and enabled status incorrect:, %s", res)
}
// We cannot really get installed clusters in this test environment.
// Might change how this test is conducted in the future.
// Should include registry name, like:
// ==> Registry Name
// local
if !strings.Contains(res,
color.New(color.Bold).Sprintf("%s", "Registry Name")+"\n"+
"local",
) {
return fmt.Errorf("registry name incorrect, %s", res)
}
return nil
}, 30*time.Second, 1000*time.Millisecond).Should(BeNil())
})
})
})
Context("when verbose is disabled", func() {
When("addon is not installed locally, but in registry", func() {
// Prepare KubeVela registry
BeforeEach(func() {
reg := &pkgaddon.Registry{
Name: "KubeVela",
Helm: &pkgaddon.HelmSource{
URL: "https://addons.kubevela.net",
},
}
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed())
})
AfterEach(func() {
// Delete KubeVela registry
ds := pkgaddon.NewRegistryDataStore(k8sClient)
Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed())
})
It("should display addon name and disabled status, and registry name", func() {
addonName := "dex"
res, _, err := generateAddonInfo(k8sClient, addonName)
Expect(err).Should(BeNil())
// Should include disabled status, like:
// dex: disabled
Expect(res).To(ContainSubstring(
color.New(color.Bold).Sprintf("%s", addonName) + ": " + color.New(color.Faint).Sprintf("%s", statusDisabled),
))
// Should include registry name, like:
// ==> Registry Name
// KubeVela
Expect(res).To(ContainSubstring(
color.New(color.Bold).Sprintf("%s", "Registry Name") + "\n" +
"KubeVela",
))
})
It("should report addon not exist in any registry name", func() {
addonName := "not-exist"
_, _, err := generateAddonInfo(k8sClient, addonName)
Expect(err.Error()).Should(BeEquivalentTo("addon 'not-exist' not found in cluster or any registry"))
})
})
})
})
var _ = Describe("Addon push command", func() {
var c common.Args
var (
testTarballPath = "../../pkg/addon/testdata/charts/sample-1.0.1.tgz"
testServerCertPath = "../../pkg/addon/testdata/tls/server.crt"
testServerKeyPath = "../../pkg/addon/testdata/tls/server.key"
testServerCAPath = "../../pkg/addon/testdata/tls/server_ca.crt"
testClientCAPath = "../../pkg/addon/testdata/tls/client_ca.crt"
testClientCertPath = "../../pkg/addon/testdata/tls/client.crt"
testClientKeyPath = "../../pkg/addon/testdata/tls/client.key"
)
var (
statusCode int
body string
tmp string
)
var ts *httptest.Server
var err error
AfterEach(func() {
ts.Close()
_ = os.RemoveAll(tmp)
err = deleteAddonRegistry(context.TODO(), c, "helm-push-test")
Expect(err).To(Succeed())
})
Context("plain old HTTP Server", func() {
BeforeEach(func() {
c.SetClient(k8sClient)
c.SetConfig(cfg)
statusCode = 201
body = "{\"success\": true}"
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)
w.Write([]byte(body))
}))
// Create new Helm home w/ test repo
tmp, err = os.MkdirTemp("", "helm-push-test")
Expect(err).To(Succeed())
// Add our helm repo to addon registry
err = addAddonRegistry(context.TODO(), c, pkgaddon.Registry{
Name: "helm-push-test",
Helm: &pkgaddon.HelmSource{
URL: ts.URL,
},
})
Expect(err).To(Succeed())
_ = os.Setenv("HELM_REPO_USERNAME", "myuser")
_ = os.Setenv("HELM_REPO_PASSWORD", "mypass")
_ = os.Setenv("HELM_REPO_CONTEXT_PATH", "/x/y/z")
})
It("Not enough args", func() {
args := []string{}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).ShouldNot(Succeed(), "expecting error with missing args, instead got nil")
})
It("Bad chart path", func() {
args := []string{"/this/this/not/a/chart", "helm-push-test"}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).ShouldNot(Succeed(), "expecting error with bad chart path, instead got nil")
})
It("Bad repo name", func() {
args := []string{testTarballPath, "this-is-not-a-valid-repo"}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).ShouldNot(Succeed(), "expecting error with bad repo name, instead got nil")
})
It("Valid tar, repo name", func() {
args := []string{testTarballPath, "helm-push-test"}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).Should(Succeed())
})
It("Valid tar, repo URL", func() {
args := []string{testTarballPath, ts.URL}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).Should(Succeed())
})
It("Trigger 409, already exists", func() {
statusCode = 409
body = "{\"error\": \"package already exists\"}"
args := []string{testTarballPath, "helm-push-test"}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).ShouldNot(Succeed(), "expecting error with 409, instead got nil")
})
It("Unable to parse JSON response body", func() {
statusCode = 500
body = "duiasnhioasd"
args := []string{testTarballPath, "helm-push-test"}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).ShouldNot(Succeed(), "expecting error with bad response body, instead got nil")
})
})
Context("TLS Enabled Server", func() {
BeforeEach(func() {
c.SetClient(k8sClient)
c.SetConfig(cfg)
statusCode = 201
body = "{\"success\": true}"
ts = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)
_, _ = w.Write([]byte(body))
}))
serverCert, err := tls.LoadX509KeyPair(testServerCertPath, testServerKeyPath)
Expect(err).To(Succeed(), "failed to load certificate and key")
clientCaCertPool, err := tlsutil.CertPoolFromFile(testClientCAPath)
Expect(err).To(Succeed(), "load server CA file failed")
ts.TLS = &tls.Config{
ClientCAs: clientCaCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{serverCert},
Rand: rand.Reader,
}
ts.StartTLS()
// Create new Helm home w/ test repo
tmp, err = os.MkdirTemp("", "helm-push-test")
Expect(err).To(Succeed())
// Add our helm repo to addon registry
err = addAddonRegistry(context.TODO(), c, pkgaddon.Registry{
Name: "helm-push-test",
Helm: &pkgaddon.HelmSource{
URL: ts.URL,
},
})
Expect(err).To(Succeed())
_ = os.Setenv("HELM_REPO_USERNAME", "myuser")
_ = os.Setenv("HELM_REPO_PASSWORD", "mypass")
_ = os.Setenv("HELM_REPO_CONTEXT_PATH", "/x/y/z")
})
It("no cert provided", func() {
_ = os.Unsetenv("HELM_REPO_CA_FILE")
_ = os.Unsetenv("HELM_REPO_CERT_FILE")
_ = os.Unsetenv("HELM_REPO_KEY_FILE")
args := []string{testTarballPath, "helm-push-test"}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).ShouldNot(Succeed(), "expected non nil error but got nil when run cmd without certificate option")
})
It("with cert", func() {
_ = os.Setenv("HELM_REPO_CA_FILE", testServerCAPath)
_ = os.Setenv("HELM_REPO_CERT_FILE", testClientCertPath)
_ = os.Setenv("HELM_REPO_KEY_FILE", testClientKeyPath)
args := []string{testTarballPath, "helm-push-test"}
cmd := NewAddonPushCommand(c)
cmd.SetArgs(args)
err := cmd.RunE(cmd, args)
Expect(err).Should(Succeed())
})
})
})