mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
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:
+15
-2
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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, "/", "-")
|
||||
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user