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 000000000..053676bfb Binary files /dev/null and b/pkg/addon/example-1.0.1.tgz differ diff --git a/pkg/addon/helper.go b/pkg/addon/helper.go index 0493cef48..f80cef0bb 100644 --- a/pkg/addon/helper.go +++ b/pkg/addon/helper.go @@ -37,6 +37,7 @@ import ( "github.com/oam-dev/kubevela/pkg/multicluster" "github.com/oam-dev/kubevela/pkg/oam" "github.com/oam-dev/kubevela/pkg/utils/apply" + "github.com/oam-dev/kubevela/pkg/utils/common" ) const ( @@ -127,15 +128,23 @@ func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli cli return nil } -// GetAddonStatus is genrall func for cli and apiServer get addon status +// GetAddonStatus is general func for cli and apiServer get addon status func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status, error) { + var addonStatus Status + app, err := FetchAddonRelatedApp(ctx, cli, name) if err != nil { if apierrors.IsNotFound(err) { - return Status{AddonPhase: disabled, AppStatus: nil}, nil + addonStatus.AddonPhase = disabled + return addonStatus, nil } - return Status{}, err + return addonStatus, err } + labels := app.GetLabels() + addonStatus.AppStatus = &app.Status + addonStatus.InstalledVersion = labels[oam.LabelAddonVersion] + addonStatus.InstalledRegistry = labels[oam.LabelAddonRegistry] + var clusters = make(map[string]map[string]interface{}) for _, r := range app.Status.AppliedResources { if r.Cluster == "" { @@ -144,13 +153,33 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status // TODO(wonderflow): we should collect all the necessary information as observability, currently we only collect cluster name clusters[r.Cluster] = make(map[string]interface{}) } + addonStatus.Clusters = clusters if app.Status.Workflow != nil && app.Status.Workflow.Suspend { - return Status{AddonPhase: suspend, AppStatus: &app.Status, Clusters: clusters, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion]}, nil + addonStatus.AddonPhase = suspend + return addonStatus, nil } + + // Get addon parameters + var sec v1.Secret + err = cli.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: Convert2SecName(name)}, &sec) + if err != nil { + // Not found error can be ignored. Others can't. + if !apierrors.IsNotFound(err) { + return addonStatus, err + } + } else { + // Although normally `else` is not preferred, we must use `else` here. + args, err := FetchArgsFromSecret(&sec) + if err != nil { + return addonStatus, err + } + addonStatus.Parameters = args + } + switch app.Status.Phase { case commontypes.ApplicationRunning: - + addonStatus.AddonPhase = enabled if name == ObservabilityAddon { // TODO(wonderflow): this is a hack Implementation and need be fixed in a unified way var ( @@ -159,7 +188,9 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status ) if err = cli.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: Convert2SecName(name)}, &sec); err != nil { klog.ErrorS(err, "failed to get observability secret") - return Status{AddonPhase: enabling, AppStatus: &app.Status, Clusters: clusters}, nil + addonStatus.AddonPhase = enabling + addonStatus.InstalledVersion = "" + return addonStatus, nil } if v, ok := sec.Data[ObservabilityAddonDomainArg]; ok { @@ -168,7 +199,9 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status observability, err := GetObservabilityAccessibilityInfo(ctx, cli, domain) if err != nil { klog.ErrorS(err, "failed to get observability accessibility info") - return Status{AddonPhase: enabling, AppStatus: &app.Status, Clusters: clusters}, nil + addonStatus.AddonPhase = enabling + addonStatus.InstalledVersion = "" + return addonStatus, nil } for _, o := range observability { @@ -183,13 +216,16 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status "serviceExternalIP": o.ServiceExternalIP, } } - return Status{AddonPhase: enabled, AppStatus: &app.Status, Clusters: clusters}, nil + + return addonStatus, nil } - return Status{AddonPhase: enabled, AppStatus: &app.Status, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion], Clusters: clusters}, nil + return addonStatus, nil case commontypes.ApplicationDeleting: - return Status{AddonPhase: disabling, AppStatus: &app.Status, Clusters: clusters}, nil + addonStatus.AddonPhase = disabling + return addonStatus, nil default: - return Status{AddonPhase: enabling, AppStatus: &app.Status, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion], Clusters: clusters}, nil + addonStatus.AddonPhase = enabling + return addonStatus, nil } } @@ -243,6 +279,99 @@ func GetObservabilityAccessibilityInfo(ctx context.Context, k8sClient client.Cli return domains, nil } +// FindWholeAddonPackagesFromRegistry find addons' WholeInstallPackage from registries, empty registryName indicates matching all +func FindWholeAddonPackagesFromRegistry(ctx context.Context, k8sClient client.Client, addonNames []string, registryNames []string) ([]*WholeAddonPackage, error) { + var addons []*WholeAddonPackage + var registries []Registry + + if len(addonNames) == 0 { + return nil, fmt.Errorf("no addon name specified") + } + + registryDataStore := NewRegistryDataStore(k8sClient) + + // Find matched registries + if len(registryNames) == 0 { + // Empty registryNames will match all registries + regs, err := registryDataStore.ListRegistries(ctx) + if err != nil { + return nil, err + } + registries = regs + } else { + // Only match specified registries + for _, registryName := range registryNames { + r, err := registryDataStore.GetRegistry(ctx, registryName) + if err != nil { + continue + } + registries = append(registries, r) + } + } + + if len(registries) == 0 { + return nil, ErrRegistryNotExist + } + + // Found addons, for deduplication purposes + foundAddons := make(map[string]bool) + merge := func(addon *WholeAddonPackage) { + if _, ok := foundAddons[addon.Name]; !ok { + foundAddons[addon.Name] = true + } + addons = append(addons, addon) + } + + // Find matched addons in registries + for _, r := range registries { + if IsVersionRegistry(r) { + vr := BuildVersionedRegistry(r.Name, r.Helm.URL, &common.HTTPOption{Username: r.Helm.Username, Password: r.Helm.Password}) + for _, addonName := range addonNames { + wholePackage, err := vr.GetDetailedAddon(ctx, addonName, "") + if err != nil { + continue + } + merge(wholePackage) + } + } else { + meta, err := r.ListAddonMeta() + if err != nil { + continue + } + + for _, addonName := range addonNames { + sourceMeta, ok := meta[addonName] + if !ok { + continue + } + uiData, err := r.GetUIData(&sourceMeta, CLIMetaOptions) + if err != nil { + continue + } + installPackage, err := r.GetInstallPackage(&sourceMeta, uiData) + if err != nil { + continue + } + // Combine UIData and InstallPackage into WholeAddonPackage + wholePackage := &WholeAddonPackage{ + InstallPackage: *installPackage, + APISchema: uiData.APISchema, + Detail: uiData.Detail, + AvailableVersions: uiData.AvailableVersions, + RegistryName: uiData.RegistryName, + } + merge(wholePackage) + } + } + } + + if len(addons) == 0 { + return nil, ErrNotExist + } + + return addons, nil +} + // Status contain addon phase and related app status type Status struct { AddonPhase string @@ -250,4 +379,7 @@ type Status struct { // the status of multiple clusters Clusters map[string]map[string]interface{} `json:"clusters,omitempty"` InstalledVersion string + Parameters map[string]interface{} + // Where the addon is from. Can be empty if not installed. + InstalledRegistry string } diff --git a/pkg/addon/helper_test.go b/pkg/addon/helper_test.go new file mode 100644 index 000000000..de305dbac --- /dev/null +++ b/pkg/addon/helper_test.go @@ -0,0 +1,197 @@ +/* +Copyright 2022 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 addon + +import ( + "context" + "errors" + "net/http/httptest" + "strings" + + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("test FindWholeAddonPackagesFromRegistry", func() { + Describe("when no registry is added, no matter what you do, it will just return error", func() { + Context("when empty addonNames and registryNames is supplied", func() { + It("should return error", func() { + _, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{}, []string{}) + Expect(err).To(HaveOccurred()) + }) + It("should return error", func() { + _, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, nil, nil) + Expect(err).To(HaveOccurred()) + }) + }) + Context("when non-empty addonNames and registryNames is supplied", func() { + It("should return error saying ErrRegistryNotExist", func() { + _, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"fluxcd"}, []string{"some-registry"}) + Expect(errors.Is(err, ErrRegistryNotExist)).To(BeTrue()) + }) + }) + }) + + Describe("one versioned registry is added", func() { + BeforeEach(func() { + // Prepare KubeVela registry + reg := &Registry{ + Name: "KubeVela", + Helm: &HelmSource{ + URL: "https://addons.kubevela.net", + }, + } + ds := NewRegistryDataStore(k8sClient) + Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed()) + }) + + AfterEach(func() { + // Clean up KubeVela registry + ds := NewRegistryDataStore(k8sClient) + Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed()) + }) + + Context("when empty addonNames and registryNames is supplied", func() { + It("should return error, empty addonNames are not allowed", func() { + _, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{}, []string{"KubeVela"}) + Expect(err).To(HaveOccurred()) + }) + It("should return error, empty addonNames are not allowed", func() { + _, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, nil, []string{"KubeVela"}) + Expect(err).To(HaveOccurred()) + }) + }) + + Context("one existing addon name provided", func() { + It("should return one valid result, matching all registries", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"velaux"}, nil) + Expect(err).To(Succeed()) + Expect(res).To(HaveLen(1)) + Expect(res[0].Name).To(Equal("velaux")) + Expect(res[0].InstallPackage).ToNot(BeNil()) + Expect(res[0].APISchema).ToNot(BeNil()) + }) + It("should return one valid result, matching one registry", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"velaux"}, []string{"KubeVela"}) + Expect(err).To(Succeed()) + Expect(res).To(HaveLen(1)) + Expect(res[0].Name).To(Equal("velaux")) + Expect(res[0].InstallPackage).ToNot(BeNil()) + Expect(res[0].APISchema).ToNot(BeNil()) + }) + }) + + Context("one non-existent addon name provided", func() { + It("should return error as ErrNotExist", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"non-existent-addon"}, nil) + Expect(errors.Is(err, ErrNotExist)).To(BeTrue()) + Expect(res).To(BeNil()) + }) + }) + + Context("two existing addon names provided", func() { + It("should return two valid result", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"velaux", "traefik"}, nil) + Expect(err).To(Succeed()) + Expect(res).To(HaveLen(2)) + Expect(res[0].Name).To(Equal("velaux")) + Expect(res[0].InstallPackage).ToNot(BeNil()) + Expect(res[0].APISchema).ToNot(BeNil()) + Expect(res[1].Name).To(Equal("traefik")) + Expect(res[1].InstallPackage).ToNot(BeNil()) + Expect(res[1].APISchema).ToNot(BeNil()) + }) + }) + + Context("one existing addon name and one non-existent addon name provided", func() { + It("should return only one valid result", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"velaux", "non-existent-addon"}, nil) + Expect(err).To(Succeed()) + Expect(res).To(HaveLen(1)) + Expect(res[0].Name).To(Equal("velaux")) + Expect(res[0].InstallPackage).ToNot(BeNil()) + Expect(res[0].APISchema).ToNot(BeNil()) + }) + }) + }) + + Describe("one non-versioned registry is added", func() { + var server *httptest.Server + BeforeEach(func() { + // Prepare local non-versioned registry + server = httptest.NewServer(ossHandler) + cm := v1.ConfigMap{} + cmYaml := strings.ReplaceAll(registryCmYaml, "TEST_SERVER_URL", server.URL) + cmYaml = strings.ReplaceAll(cmYaml, "KubeVela", "testreg") + Expect(yaml.Unmarshal([]byte(cmYaml), &cm)).Should(BeNil()) + Expect(k8sClient.Update(ctx, &cm)).Should(BeNil()) + }) + + AfterEach(func() { + server.Close() + }) + + Context("when empty addonNames and registryNames is supplied", func() { + It("should return error, empty addonNames are not allowed", func() { + _, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{}, []string{}) + Expect(err).To(HaveOccurred()) + }) + It("should return error, empty addonNames are not allowed", func() { + _, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, nil, []string{"testreg"}) + Expect(err).To(HaveOccurred()) + }) + }) + + Context("one existing addon name provided", func() { + It("should return one valid result, matching all registries", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"example"}, nil) + Expect(err).To(Succeed()) + Expect(res).To(HaveLen(1)) + Expect(res[0].Name).To(Equal("example")) + Expect(res[0].InstallPackage).ToNot(BeNil()) + }) + It("should return one valid result, matching one registry", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"example"}, []string{"testreg"}) + Expect(err).To(Succeed()) + Expect(res).To(HaveLen(1)) + Expect(res[0].Name).To(Equal("example")) + Expect(res[0].InstallPackage).ToNot(BeNil()) + }) + }) + + Context("one non-existent addon name provided", func() { + It("should return error as ErrNotExist", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"non-existent-addon"}, nil) + Expect(errors.Is(err, ErrNotExist)).To(BeTrue()) + Expect(res).To(BeNil()) + }) + }) + + Context("one existing addon name and one non-existent addon name provided", func() { + It("should return only one valid result", func() { + res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"example", "non-existent-addon"}, nil) + Expect(err).To(Succeed()) + Expect(res).To(HaveLen(1)) + Expect(res[0].Name).To(Equal("example")) + Expect(res[0].InstallPackage).ToNot(BeNil()) + }) + }) + }) +}) diff --git a/pkg/addon/testdata/example/Chart.yaml b/pkg/addon/testdata/example/Chart.yaml new file mode 100644 index 000000000..fa44643f0 --- /dev/null +++ b/pkg/addon/testdata/example/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +appVersion: 1.0.1 +description: Extended workload to do continuous and progressive delivery +home: https://fluxcd.io +icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png +keywords: +- extended_workload +- gitops +- only_example +name: example +type: library +version: 1.0.1 diff --git a/pkg/addon/type.go b/pkg/addon/type.go index f8346115f..d921ed8e3 100644 --- a/pkg/addon/type.go +++ b/pkg/addon/type.go @@ -68,6 +68,7 @@ type WholeAddonPackage struct { // Detail is README.md in an addon Detail string `json:"detail,omitempty"` AvailableVersions []string `json:"availableVersions"` + RegistryName string `json:"registryName"` } // Meta defines the format for a single addon diff --git a/pkg/addon/utils_test.go b/pkg/addon/utils_test.go index d591e17ea..91a409582 100644 --- a/pkg/addon/utils_test.go +++ b/pkg/addon/utils_test.go @@ -30,6 +30,8 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" velatypes "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/oam" @@ -120,7 +122,12 @@ var _ = Describe("Test definition check", func() { cmYaml := strings.ReplaceAll(registryCmYaml, "TEST_SERVER_URL", url) cm := v1.ConfigMap{} Expect(yaml.Unmarshal([]byte(cmYaml), &cm)).Should(BeNil()) - Expect(k8sClient.Create(ctx, &cm)).Should(BeNil()) + err := k8sClient.Create(ctx, &cm) + if apierrors.IsAlreadyExists(err) { + Expect(k8sClient.Update(ctx, &cm)).To(Succeed()) + } else { + Expect(err).To(Succeed()) + } disableTestAddonApp := v1beta1.Application{} Expect(yaml.Unmarshal([]byte(addonDisableTestAppYaml), &disableTestAddonApp)).Should(BeNil()) diff --git a/pkg/addon/versioned_registry.go b/pkg/addon/versioned_registry.go index e24f110b4..13f03a11c 100644 --- a/pkg/addon/versioned_registry.go +++ b/pkg/addon/versioned_registry.go @@ -36,6 +36,7 @@ type VersionedRegistry interface { ListAddon() ([]*UIData, error) GetAddonUIData(ctx context.Context, addonName, version string) (*UIData, error) GetAddonInstallPackage(ctx context.Context, addonName, version string) (*InstallPackage, error) + GetDetailedAddon(ctx context.Context, addonName, version string) (*WholeAddonPackage, error) } // BuildVersionedRegistry is build versioned addon registry @@ -87,6 +88,14 @@ func (i *versionedRegistry) GetAddonInstallPackage(ctx context.Context, addonNam return &wholePackage.InstallPackage, nil } +func (i *versionedRegistry) GetDetailedAddon(ctx context.Context, addonName, version string) (*WholeAddonPackage, error) { + wholePackage, err := i.loadAddon(ctx, addonName, version) + if err != nil { + return nil, err + } + return wholePackage, nil +} + func (i *versionedRegistry) resolveAddonListFromIndex(repoName string, index *repo.IndexFile) []*UIData { var res []*UIData for addonName, versions := range index.Entries { @@ -155,6 +164,7 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string) return nil, err } addonPkg.AvailableVersions = availableVersions + addonPkg.RegistryName = i.name return addonPkg, nil } return nil, fmt.Errorf("cannot fetch addon package") diff --git a/pkg/addon/versioned_registry_test.go b/pkg/addon/versioned_registry_test.go index 495a1760a..43680e5c1 100644 --- a/pkg/addon/versioned_registry_test.go +++ b/pkg/addon/versioned_registry_test.go @@ -62,6 +62,13 @@ func TestVersionRegistry(t *testing.T) { assert.NotEmpty(t, addonsInstallPackage.YAMLTemplates) assert.NotEmpty(t, addonsInstallPackage.DefSchemas) + addonWholePackage, err := r.GetDetailedAddon(context.Background(), "fluxcd", "1.0.0") + assert.NoError(t, err) + assert.NotEmpty(t, addonWholePackage) + assert.NotEmpty(t, addonWholePackage.YAMLTemplates) + assert.NotEmpty(t, addonWholePackage.DefSchemas) + assert.NotEmpty(t, addonWholePackage.RegistryName) + ar := BuildVersionedRegistry("auth-helm-repo", "http://127.0.0.1:18083/authReg", &common.HTTPOption{Username: "hello", Password: "hello"}) addons, err = ar.ListAddon() assert.NoError(t, err) @@ -80,6 +87,13 @@ func TestVersionRegistry(t *testing.T) { assert.NotEmpty(t, addonsInstallPackage.YAMLTemplates) assert.NotEmpty(t, addonsInstallPackage.DefSchemas) + addonWholePackage, err = ar.GetDetailedAddon(context.Background(), "fluxcd", "1.0.0") + assert.NoError(t, err) + assert.NotEmpty(t, addonWholePackage) + assert.NotEmpty(t, addonWholePackage.YAMLTemplates) + assert.NotEmpty(t, addonWholePackage.DefSchemas) + assert.NotEmpty(t, addonWholePackage.RegistryName) + testListUIData(t) } diff --git a/references/cli/addon.go b/references/cli/addon.go index 9686a374f..146498097 100644 --- a/references/cli/addon.go +++ b/references/cli/addon.go @@ -73,6 +73,8 @@ var addonVersion string var addonClusters string +var verboseSatatus bool + // NewAddonCommand create `addon` command func NewAddonCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command { cmd := &cobra.Command{ @@ -330,7 +332,7 @@ func NewAddonDisableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Co // NewAddonStatusCommand create addon status command func NewAddonStatusCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "status", Short: "get an addon's status.", Long: "get an addon's status from cluster.", @@ -347,6 +349,8 @@ func NewAddonStatusCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com return nil }, } + cmd.Flags().BoolVarP(&verboseSatatus, "verbose", "v", false, "show addon descriptions and parameters in addition to status") + return cmd } func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.DiscoveryClient, config *rest.Config, name string, version string, args map[string]interface{}) error { @@ -396,12 +400,13 @@ func statusAddon(name string, ioStreams cmdutil.IOStreams, cmd *cobra.Command, c if err != nil { return err } - status, err := pkgaddon.GetAddonStatus(context.Background(), k8sClient, name) + + statusString, status, err := generateAddonInfo(k8sClient, name) if err != nil { return err } - fmt.Print(generateAddonInfo(name, status)) + fmt.Print(statusString) if status.AddonPhase != statusEnabled && status.AddonPhase != statusDisabled { fmt.Printf("diagnose addon info from application %s", pkgaddon.Convert2AppName(name)) @@ -413,34 +418,215 @@ func statusAddon(name string, ioStreams cmdutil.IOStreams, cmd *cobra.Command, c return nil } -func generateAddonInfo(name string, status pkgaddon.Status) string { +// generateAddonInfo will get addon status, description, version, dependencies (and whether they are installed), +// and parameters (and their current values). +// The first return value is the formatted string for printing. +// The second return value is just for diagnostic purposes, as it is needed in statusAddon to print diagnostic info. +func generateAddonInfo(c client.Client, name string) (string, pkgaddon.Status, error) { var res string var phase string + var installed bool + var addonPackage *pkgaddon.WholeAddonPackage + + // Get addon install package + if verboseSatatus { + // We need the metadata to get descriptions about parameters + addonPackages, err := pkgaddon.FindWholeAddonPackagesFromRegistry(context.Background(), c, []string{name}, nil) + // Not found error can be ignored, because the user can define their own addon. Others can't. + if err != nil && !errors.Is(err, pkgaddon.ErrNotExist) && !errors.Is(err, pkgaddon.ErrRegistryNotExist) { + return "", pkgaddon.Status{}, err + } + if len(addonPackages) != 0 { + addonPackage = addonPackages[0] + } + } + + // Check current addon status + status, err := pkgaddon.GetAddonStatus(context.Background(), c, name) + if err != nil { + return res, status, err + } switch status.AddonPhase { case statusEnabled: + installed = true c := color.New(color.FgGreen) phase = c.Sprintf("%s", status.AddonPhase) case statusSuspend: + installed = true c := color.New(color.FgRed) phase = c.Sprintf("%s", status.AddonPhase) + case statusDisabled: + c := color.New(color.Faint) + phase = c.Sprintf("%s", status.AddonPhase) + // If the addon is + // 1. disabled, + // 2. does not exist in the registry, + // 3. verbose is on (when off, it is not possible to know whether the addon is in registry or not), + // means the addon does not exist at all. + // So, no need to go further, we return error message saying that we can't find it. + if addonPackage == nil && verboseSatatus { + return res, pkgaddon.Status{}, fmt.Errorf("addon %s is not found in registries nor locally installed", name) + } default: - phase = status.AddonPhase - } - res += fmt.Sprintf("addon %s status is %s \n", name, phase) - if len(status.InstalledVersion) != 0 { - res += fmt.Sprintf("installedVersion: %s \n", status.InstalledVersion) + c := color.New(color.Faint) + phase = c.Sprintf("%s", status.AddonPhase) } + // Addon name + res += color.New(color.Bold).Sprintf("%s", name) + res += fmt.Sprintf(": %s ", phase) + if installed { + res += fmt.Sprintf("(%s)", status.InstalledVersion) + } + res += "\n" + + // Description + // Skip this if addon is installed from local sources. + // Description is fetched from the Internet, which is not useful for local sources. + if status.InstalledRegistry != pkgaddon.LocalAddonRegistryName && addonPackage != nil { + res += fmt.Sprintln(addonPackage.Description) + } + + // Installed Clusters if len(status.Clusters) != 0 { + res += color.New(color.FgHiBlue).Sprint("==> ") + 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