From eb5f416e4e264513e3fa7358a9795b378b29048d Mon Sep 17 00:00:00 2001 From: Charlie Chiang Date: Thu, 19 May 2022 16:21:36 +0800 Subject: [PATCH] Feat: add more detailed output for `vela addon status` (#3876) * Feat: show available versions and dependencies in `addon status` Also prettify output Signed-off-by: Charlie Chiang * Feat: show addon dependencies in `addon status` Signed-off-by: Charlie Chiang * Style: adjust `addon status` output Signed-off-by: Charlie Chiang * Style: remove spaces in `addon status` output Signed-off-by: Charlie Chiang * Fix: truncate string if available versions too long if a local addon has been installed, the AVAILABLE-VERSIONS column of the registry one will not truncate. this commit fixes that. Signed-off-by: Charlie Chiang * Perf: avoid unnecessary network requests to improve performance Signed-off-by: Charlie Chiang * Feat: do not display remote info for local addons Limit Description length when using `addon list` Use simpler method to get addon info Signed-off-by: Charlie Chiang * Test: add tests for cli/addon limitStringLength, generateParameterString Signed-off-by: Charlie Chiang * Fix: fix output being empty if the searched addon does not exist anywhere Signed-off-by: Charlie Chiang * Test: add tests for generateAddonInfo Signed-off-by: Charlie Chiang * Test: add tests for generateAddonInfo and FindWholeAddonPackagesFromRegistry Signed-off-by: Charlie Chiang * Style: format code Signed-off-by: Charlie Chiang * Test: correct unnecessarily focused tests Signed-off-by: Charlie Chiang * Style: remove unused code blocks Signed-off-by: Charlie Chiang * Refactor: remove unnecessary output to stdout Signed-off-by: Charlie Chiang * Test: remove added registry after tests has been completed Signed-off-by: Charlie Chiang * Test: new RegistryDataStore every test, to avoid panics Signed-off-by: Charlie Chiang * Test: fix failing tests due to incorrect registry name Signed-off-by: Charlie Chiang * Refactor: move logic to get addon package out of where it is supposed to get addon status Signed-off-by: Charlie Chiang * Test: add test cases for secret-getting in TestGetAddonStatus Signed-off-by: Charlie Chiang * Feat: return error if an addon is not installed, nor does it exist in the registry Signed-off-by: Charlie Chiang * Refactor: rename GetAddonWholePackage to GetDetailedAddon as per wonderflow Signed-off-by: Charlie Chiang * Feat: require --verbose option to show details in `vela addon status` Signed-off-by: Charlie Chiang * Feat: require --verbose option to show details in `vela addon status` Signed-off-by: Charlie Chiang * chore: run make reviewable Signed-off-by: Charlie Chiang * Test: fix configmap being already existed Signed-off-by: Charlie Chiang * Fix: fix output show addon does not exist when verbose is off and an addon is not installed, but it does exist in the registry Signed-off-by: Charlie Chiang * Style: fix golangci-lint issues Signed-off-by: Charlie Chiang --- pkg/addon/addon_test.go | 17 +- pkg/addon/error.go | 3 + pkg/addon/example-1.0.1.tgz | Bin 0 -> 2986 bytes pkg/addon/helper.go | 154 +++++++++++++++-- pkg/addon/helper_test.go | 197 ++++++++++++++++++++++ pkg/addon/testdata/example/Chart.yaml | 12 ++ pkg/addon/type.go | 1 + pkg/addon/utils_test.go | 9 +- pkg/addon/versioned_registry.go | 10 ++ pkg/addon/versioned_registry_test.go | 14 ++ references/cli/addon.go | 229 ++++++++++++++++++++++++-- references/cli/addon_suite_test.go | 227 ++++++++++++++++++++++++- references/cli/addon_test.go | 159 +++++++++++++++--- references/cli/cli_suite_test.go | 6 + references/cli/uninstall_test.go | 20 +++ 15 files changed, 1004 insertions(+), 54 deletions(-) create mode 100644 pkg/addon/example-1.0.1.tgz create mode 100644 pkg/addon/helper_test.go create mode 100644 pkg/addon/testdata/example/Chart.yaml diff --git a/pkg/addon/addon_test.go b/pkg/addon/addon_test.go index e2135ce76..45b44a60f 100644 --- a/pkg/addon/addon_test.go +++ b/pkg/addon/addon_test.go @@ -322,6 +322,18 @@ func TestGetAddonStatus(t *testing.T) { app := &v1beta1.Application{} app.Status.Phase = common.ApplicationDeleting *o = *app + case "addon-secret-enabled": + o := obj.(*corev1.Secret) + secret := &corev1.Secret{} + secret.Data = map[string][]byte{ + "some-key": []byte("some-value"), + } + *o = *secret + case "addon-secret-disabling", "addon-secret-enabling": + o := obj.(*corev1.Secret) + secret := &corev1.Secret{} + secret.Data = map[string][]byte{} + *o = *secret default: o := obj.(*v1beta1.Application) app := &v1beta1.Application{} @@ -336,8 +348,9 @@ func TestGetAddonStatus(t *testing.T) { } cases := []struct { - name string - expectStatus string + name string + expectStatus string + expectedParameters map[string]interface{} }{ { name: "disabled", expectStatus: "disabled", diff --git a/pkg/addon/error.go b/pkg/addon/error.go index 3a4731f61..6fa53473b 100644 --- a/pkg/addon/error.go +++ b/pkg/addon/error.go @@ -37,6 +37,9 @@ var ( // ErrNotExist means addon not exists ErrNotExist = NewAddonError("addon not exist") + + // ErrRegistryNotExist means registry not exists + ErrRegistryNotExist = NewAddonError("registry does not exist") ) // WrapErrRateLimit return ErrRateLimit if is the situation, or return error directly diff --git a/pkg/addon/example-1.0.1.tgz b/pkg/addon/example-1.0.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..053676bfb0e387bf3c0958f452797bc386c3b1df GIT binary patch literal 2986 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 g) ") + color.New(color.Bold).Sprintln("Installed Clusters") var ic []string for c := range status.Clusters { ic = append(ic, c) } sort.Strings(ic) - res += fmt.Sprintf("installedClusters: %s \n", ic) + res += fmt.Sprintln(ic) } - return res + + // Registry name + registryName := status.InstalledRegistry + // Disabled addons will have empty InstalledRegistry, so if the addon exists in the registry, we use the registry name. + if registryName == "" && addonPackage != nil { + registryName = addonPackage.RegistryName + } + if registryName != "" { + res += color.New(color.FgHiBlue).Sprint("==> ") + color.New(color.Bold).Sprintln("Registry Name") + res += fmt.Sprintln(registryName) + } + + // If the addon is installed from local sources, or does not exist at all, stop here! + // The following information is fetched from the Internet, which is not useful for local sources. + if registryName == pkgaddon.LocalAddonRegistryName || registryName == "" || addonPackage == nil { + return res, status, nil + } + + // Available Versions + res += color.New(color.FgHiBlue).Sprint("==> ") + color.New(color.Bold).Sprintln("Available Versions") + res += genAvailableVersionInfo(addonPackage.AvailableVersions, status.InstalledVersion, 8) + res += "\n" + + // Dependencies + dependenciesString, allInstalled := generateDependencyString(c, addonPackage.Dependencies) + res += color.New(color.FgHiBlue).Sprint("==> ") + color.New(color.Bold).Sprint("Dependencies ") + if allInstalled { + res += color.GreenString("✔") + } else { + res += color.RedString("✘") + } + res += "\n" + res += dependenciesString + res += "\n" + + // Parameters + parameterString := generateParameterString(status, addonPackage) + if len(parameterString) != 0 { + res += color.New(color.FgHiBlue).Sprint("==> ") + color.New(color.Bold).Sprintln("Parameters") + res += parameterString + } + + return res, status, nil +} + +func generateParameterString(status pkgaddon.Status, addonPackage *pkgaddon.WholeAddonPackage) string { + ret := "" + + if addonPackage.APISchema == nil { + return ret + } + + // Required parameters + required := make(map[string]bool) + for _, k := range addonPackage.APISchema.Required { + required[k] = true + } + + for propKey, propValue := range addonPackage.APISchema.Properties { + desc := propValue.Value.Description + defaultValue := propValue.Value.Default + if defaultValue == nil { + defaultValue = "" + } + required := required[propKey] + currentValue := status.Parameters[propKey] + if currentValue == nil { + currentValue = "" + } + + // Header: addon: description + ret += color.New(color.FgCyan).Sprintf("-> ") + ret += color.New(color.Bold).Sprint(propKey) + ": " + ret += fmt.Sprintf("%s\n", desc) + // Current value + if currentValue != "" { + ret += "\tcurrent: " + color.New(color.FgGreen).Sprintf("%#v\n", currentValue) + } + // Default value + if defaultValue != "" { + ret += "\tdefault: " + fmt.Sprintf("%#v\n", defaultValue) + } + // Required or not + if required { + ret += "\trequired: " + ret += color.GreenString("✔\n") + } + } + + return ret +} + +func generateDependencyString(c client.Client, dependencies []*pkgaddon.Dependency) (string, bool) { + if len(dependencies) == 0 { + return "[]", true + } + + ret := "[" + allDependenciesInstalled := true + + for idx, d := range dependencies { + name := d.Name + + // Checks if the dependency is enabled, and mark it + status, err := pkgaddon.GetAddonStatus(context.Background(), c, name) + if err != nil { + continue + } + + var enabledString string + switch status.AddonPhase { + case statusEnabled: + enabledString = color.GreenString("✔") + case statusSuspend: + enabledString = color.RedString("✔") + default: + enabledString = color.RedString("✘") + allDependenciesInstalled = false + } + ret += fmt.Sprintf("%s %s", name, enabledString) + + if idx != len(dependencies)-1 { + ret += ", " + } + } + + ret += "]" + + return ret, allDependenciesInstalled } func listAddons(ctx context.Context, clt client.Client, registry string) (*uitable.Table, error) { @@ -490,14 +676,14 @@ func listAddons(ctx context.Context, clt client.Client, registry string) (*uitab labels := app.GetLabels() addonName := labels[oam.LabelAddonName] addonVersion := labels[oam.LabelAddonVersion] - table.AddRow(addonName, app.GetLabels()[oam.LabelAddonRegistry], "", genAvailableVersionInfo([]string{addonVersion}, addonVersion), statusEnabled) + table.AddRow(addonName, app.GetLabels()[oam.LabelAddonRegistry], "", genAvailableVersionInfo([]string{addonVersion}, addonVersion, 3), statusEnabled) locallyInstalledAddons[addonName] = true } for _, addon := range addons { // if the addon with same name has already installed locally, display the registry one as not installed if locallyInstalledAddons[addon.Name] { - table.AddRow(addon.Name, addon.RegistryName, addon.Description, addon.AvailableVersions, "disabled") + table.AddRow(addon.Name, addon.RegistryName, limitStringLength(addon.Description, 60), genAvailableVersionInfo(addon.AvailableVersions, "", 3), "disabled") continue } status, err := pkgaddon.GetAddonStatus(ctx, clt, addon.Name) @@ -508,7 +694,7 @@ func listAddons(ctx context.Context, clt client.Client, registry string) (*uitab if len(status.InstalledVersion) != 0 { statusRow += fmt.Sprintf(" (%s)", status.InstalledVersion) } - table.AddRow(addon.Name, addon.RegistryName, addon.Description, genAvailableVersionInfo(addon.AvailableVersions, status.InstalledVersion), statusRow) + table.AddRow(addon.Name, addon.RegistryName, limitStringLength(addon.Description, 60), genAvailableVersionInfo(addon.AvailableVersions, status.InstalledVersion, 3), statusRow) } return table, nil @@ -547,7 +733,7 @@ func waitApplicationRunning(k8sClient client.Client, addonName string) error { // generate the available version // this func put the installed version as the first version and keep the origin order // print ... if available version too much -func genAvailableVersionInfo(versions []string, installedVersion string) string { +func genAvailableVersionInfo(versions []string, installedVersion string, limit int) string { var v []string // put installed-version as the first version and keep the origin order @@ -564,7 +750,7 @@ func genAvailableVersionInfo(versions []string, installedVersion string) string res := "[" var count int for _, version := range v { - if count == 3 { + if count == limit { // just show newest 3 versions res += "..." break @@ -583,6 +769,17 @@ func genAvailableVersionInfo(versions []string, installedVersion string) string return res } +// limitStringLength limits the length of the string, and add ... if it is too long +func limitStringLength(str string, length int) string { + if length <= 0 { + return str + } + if len(str) > length { + return str[:length] + "..." + } + return str +} + // TransAddonName will turn addon's name from xxx/yyy to xxx-yyy func TransAddonName(name string) string { return strings.ReplaceAll(name, "/", "-") diff --git a/references/cli/addon_suite_test.go b/references/cli/addon_suite_test.go index 0b5ae252d..d18b8d22a 100644 --- a/references/cli/addon_suite_test.go +++ b/references/cli/addon_suite_test.go @@ -19,6 +19,7 @@ package cli import ( "context" "fmt" + "strings" "time" "sigs.k8s.io/yaml" @@ -64,6 +65,12 @@ var _ = Describe("Output of listing addons tests", func() { 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, "") @@ -110,7 +117,7 @@ var _ = Describe("Output of listing addons tests", func() { return fmt.Errorf("fluxcd version string is incorrect: %s", versionString) } return nil - }, 30*time.Second, 300*time.Millisecond).Should(BeNil()) + }, 30*time.Second, 1000*time.Millisecond).Should(BeNil()) }) It("should print fluxcd in the registry as disabled", func() { @@ -123,3 +130,221 @@ var _ = Describe("Output of listing addons tests", func() { }) }) }) + +var _ = Describe("Addon status or info", func() { + + Context("when verbose is enabled", func() { + BeforeEach(func() { + verboseSatatus = 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")).To(Succeed()) + // 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 + } + // 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", + )) + }) + }) + }) +}) diff --git a/references/cli/addon_test.go b/references/cli/addon_test.go index d06a559ea..9dbdca5ca 100644 --- a/references/cli/addon_test.go +++ b/references/cli/addon_test.go @@ -25,6 +25,8 @@ import ( pkgaddon "github.com/oam-dev/kubevela/pkg/addon" + "github.com/getkin/kin-openapi/openapi3" + "github.com/oam-dev/kubevela/pkg/utils/common" "github.com/oam-dev/kubevela/pkg/utils/util" @@ -207,28 +209,6 @@ func TestTransCluster(t *testing.T) { } } -func TestGenerateStatusIn(t *testing.T) { - testcases := []struct { - c pkgaddon.Status - res []string - }{ - { - c: pkgaddon.Status{InstalledVersion: "1.2.1", Clusters: map[string]map[string]interface{}{"cluster1": nil, "cluster2": nil}, AddonPhase: statusEnabled}, - res: []string{"installedVersion: 1.2.1", "installedClusters: [cluster1 cluster2]", fmt.Sprintf("status is %s", color.New(color.FgGreen).Sprintf(statusEnabled))}, - }, - { - c: pkgaddon.Status{InstalledVersion: "1.2.3", AddonPhase: statusSuspend}, - res: []string{"installedVersion: 1.2.3", fmt.Sprintf("status is %s", color.New(color.FgRed).Sprintf(statusSuspend))}, - }, - } - for _, testcase := range testcases { - res := generateAddonInfo("test", testcase.c) - for _, re := range testcase.res { - assert.Equal(t, strings.Contains(res, re), true) - } - } -} - func TestGenerateAvailableVersions(t *testing.T) { type testcase struct { inVersion string @@ -261,7 +241,65 @@ func TestGenerateAvailableVersions(t *testing.T) { }, } for _, s := range testcases { - re := genAvailableVersionInfo(s.c.versions, s.c.inVersion) + re := genAvailableVersionInfo(s.c.versions, s.c.inVersion, 3) + assert.Equal(t, re, s.res) + } +} + +func TestLimitStringLength(t *testing.T) { + type testcase struct { + testString string + lengthLimit int + } + + testcases := []struct { + c testcase + res string + }{ + // len = limit + { + c: testcase{ + testString: "4444", + lengthLimit: 4, + }, + res: "4444", + }, + // len > limit + { + c: testcase{ + testString: "3333", + lengthLimit: 3, + }, + res: "333...", + }, + // len < limit + { + c: testcase{ + testString: "22", + lengthLimit: 3, + }, + res: "22", + }, + // limit = 0 + { + c: testcase{ + testString: "000", + lengthLimit: 0, + }, + res: "000", + }, + // limit < 0 + { + c: testcase{ + testString: "000", + lengthLimit: -1, + }, + res: "000", + }, + } + + for _, s := range testcases { + re := limitStringLength(s.c.testString, s.c.lengthLimit) assert.Equal(t, re, s.res) } } @@ -302,3 +340,78 @@ func TestPackageValidAddon(t *testing.T) { err := cmd.Execute() assert.NilError(t, err) } + +func TestGenerateParameterString(t *testing.T) { + testcase := []struct { + status pkgaddon.Status + addonPackage *pkgaddon.WholeAddonPackage + outputs []string + }{ + { + status: pkgaddon.Status{}, + addonPackage: &pkgaddon.WholeAddonPackage{ + APISchema: nil, + }, + outputs: []string{""}, + }, + { + status: pkgaddon.Status{ + Parameters: map[string]interface{}{ + "database": "kubevela", + "dbType": "kubeapi", + }, + }, + addonPackage: &pkgaddon.WholeAddonPackage{ + APISchema: &openapi3.Schema{ + Required: []string{"dbType", "serviceAccountName", "serviceType", "dex"}, + Properties: openapi3.Schemas{ + "database": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Description: "Specify the database name, for the kubeapi db type, it represents namespace.", + Default: nil, + }, + }, + "dbURL": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Description: "Specify the MongoDB URL. it only enabled where DB type is MongoDB.", + Default: nil, + }, + }, + "dbType": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Description: "Specify the database type, current support KubeAPI(default) and MongoDB.", + Default: "kubeapi", + }, + }, + }, + }, + }, + outputs: []string{ + // dbType + color.New(color.FgCyan).Sprintf("-> ") + + color.New(color.Bold).Sprint("dbType") + ": " + + "Specify the database type, current support KubeAPI(default) and MongoDB.\n" + + "\tcurrent: " + color.New(color.FgGreen).Sprint("\"kubeapi\"\n") + + "\tdefault: " + "\"kubeapi\"\n" + + "\trequired: " + color.GreenString("✔\n"), + // dbURL + color.New(color.FgCyan).Sprintf("-> ") + + color.New(color.Bold).Sprint("dbURL") + ": " + + "Specify the MongoDB URL. it only enabled where DB type is MongoDB.", + // database + color.New(color.FgCyan).Sprintf("-> ") + + color.New(color.Bold).Sprint("database") + ": " + + "Specify the database name, for the kubeapi db type, it represents namespace.\n" + + "\tcurrent: " + color.New(color.FgGreen).Sprint("\"kubevela\""), + }, + }, + } + + for _, s := range testcase { + res := generateParameterString(s.status, s.addonPackage) + for _, o := range s.outputs { + assert.Check(t, strings.Contains(res, o)) + } + + } +} diff --git a/references/cli/cli_suite_test.go b/references/cli/cli_suite_test.go index 29a42d216..cd5b1b29f 100644 --- a/references/cli/cli_suite_test.go +++ b/references/cli/cli_suite_test.go @@ -26,6 +26,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/discovery" "k8s.io/client-go/rest" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" @@ -43,6 +44,7 @@ func TestCli(t *testing.T) { var cfg *rest.Config var k8sClient client.Client var testEnv *envtest.Environment +var dc *discovery.DiscoveryClient var _ = BeforeSuite(func(done Done) { rand.Seed(time.Now().UnixNano()) @@ -67,6 +69,10 @@ var _ = BeforeSuite(func(done Done) { Expect(err).Should(BeNil()) Expect(k8sClient).ToNot(BeNil()) + dc, err = discovery.NewDiscoveryClientForConfig(cfg) + Expect(err).ToNot(HaveOccurred()) + Expect(dc).ShouldNot(BeNil()) + By("new namespace") err = k8sClient.Create(context.TODO(), &corev1.Namespace{ ObjectMeta: v1.ObjectMeta{Name: types.DefaultKubeVelaNS}, diff --git a/references/cli/uninstall_test.go b/references/cli/uninstall_test.go index ecfaa64d3..4628c772e 100644 --- a/references/cli/uninstall_test.go +++ b/references/cli/uninstall_test.go @@ -83,6 +83,26 @@ spec: name: flux-system ` +var fluxcdRemoteYaml = ` +apiVersion: core.oam.dev/v1beta1 +kind: Application +metadata: + name: addon-fluxcd + namespace: vela-system + labels: + addons.oam.dev/name: fluxcd + addons.oam.dev/registry: KubeVela + addons.oam.dev/version: 1.1.0 +spec: + components: + - name: ns-flux-system + properties: + apiVersion: v1 + kind: Namespace + metadata: + name: flux-system +` + var rolloutYaml = ` apiVersion: core.oam.dev/v1beta1 kind: Application