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 <charlie_c_0129@outlook.com>

* Feat: show addon dependencies in `addon status`

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Style: adjust `addon status` output

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Style: remove spaces in `addon status` output

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* 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 <charlie_c_0129@outlook.com>

* Perf: avoid unnecessary network requests to improve performance

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* 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 <charlie_c_0129@outlook.com>

* Test: add tests for cli/addon limitStringLength, generateParameterString

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Fix: fix output being empty if the searched addon does not exist anywhere

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: add tests for generateAddonInfo

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: add tests for generateAddonInfo and FindWholeAddonPackagesFromRegistry

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Style: format code

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: correct unnecessarily focused tests

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Style: remove unused code blocks

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Refactor: remove unnecessary output to stdout

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: remove added registry after tests has been completed

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: new RegistryDataStore every test, to avoid panics

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: fix failing tests due to incorrect registry name

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Refactor: move logic to get addon package out of where it is supposed to get addon status

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: add test cases for secret-getting in TestGetAddonStatus

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Feat: return error if an addon is not installed, nor does it exist in the registry

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Refactor: rename GetAddonWholePackage to GetDetailedAddon as per wonderflow

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Feat: require --verbose option to show details in `vela addon status`

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Feat: require --verbose option to show details in `vela addon status`

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* chore: run make reviewable

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: fix configmap being already existed

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* 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 <charlie_c_0129@outlook.com>

* Style: fix golangci-lint issues

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>
This commit is contained in:
Charlie Chiang
2022-05-19 16:21:36 +08:00
committed by GitHub
parent 1cc5c54d32
commit eb5f416e4e
15 changed files with 1004 additions and 54 deletions
+15 -2
View File
@@ -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",
+3
View File
@@ -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
Binary file not shown.
+143 -11
View File
@@ -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
}
+197
View File
@@ -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())
})
})
})
})
+12
View File
@@ -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
+1
View File
@@ -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
+8 -1
View File
@@ -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())
+10
View File
@@ -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")
+14
View File
@@ -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)
}
+213 -16
View File
@@ -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, "/", "-")
+226 -1
View File
@@ -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",
))
})
})
})
})
+136 -23
View File
@@ -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))
}
}
}
+6
View File
@@ -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},
+20
View File
@@ -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