Feat: helm repo as addon registry to support addon's multi-version (#3523)

* versioned registry impl
add more test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

* fix ci

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix ui

fix

fix

fix

modify addon registry
This commit is contained in:
wyike
2022-03-28 21:25:38 +08:00
committed by GitHub
parent 4f8e7506f9
commit 161d2646cb
25 changed files with 729 additions and 128 deletions
@@ -7,10 +7,8 @@ data:
registries: '{
"KubeVela":{
"name": "KubeVela",
"oss": {
"end_point": "https://addons.kubevela.net",
"bucket": "",
"path": ""
"helm": {
"url": "https://addons.kubevela.net",
}
}
}'
+33 -19
View File
@@ -286,6 +286,7 @@ func GetUIDataFromReader(r AsyncReader, meta *SourceMeta, opt ListOptions) (*UID
return nil, fmt.Errorf("fail to generate openAPIschema for addon %s : %w", meta.Name, err)
}
}
addon.AvailableVersions = []string{addon.Version}
return addon, nil
}
@@ -595,6 +596,7 @@ func formatAppFramework(addon *InstallPackage) *v1beta1.Application {
app.Labels = make(map[string]string)
}
app.Labels[oam.LabelAddonName] = addon.Name
app.Labels[oam.LabelAddonVersion] = addon.Version
return app
}
@@ -1051,26 +1053,37 @@ func (h *Installer) enableAddon(addon *InstallPackage) error {
return nil
}
func (h *Installer) loadInstallPackage(name string) (*InstallPackage, error) {
metas, err := h.getAddonMeta()
if err != nil {
return nil, errors.Wrap(err, "fail to get addon meta")
func (h *Installer) loadInstallPackage(name, version string) (*InstallPackage, error) {
var installPackage *InstallPackage
var err error
if !IsVersionRegistry(*h.r) {
metas, err := h.getAddonMeta()
if err != nil {
return nil, errors.Wrap(err, "fail to get addon meta")
}
meta, ok := metas[name]
if !ok {
return nil, ErrNotExist
}
var uiData *UIData
uiData, err = h.cache.GetUIData(*h.r, name, version)
if err != nil {
return nil, err
}
// enable this addon if it's invisible
installPackage, err = h.r.GetInstallPackage(&meta, uiData)
if err != nil {
return nil, errors.Wrap(err, "fail to find dependent addon in source repository")
}
} else {
versionedRegistry := BuildVersionedRegistry(h.r.Name, h.r.Helm.URL)
installPackage, err = versionedRegistry.GetAddonInstallPackage(context.Background(), name, version)
if err != nil {
return nil, err
}
}
meta, ok := metas[name]
if !ok {
return nil, ErrNotExist
}
var uiData *UIData
uiData, err = h.cache.GetUIData(*h.r, name)
if err != nil {
return nil, err
}
// enable this addon if it's invisible
installPackage, err := h.r.GetInstallPackage(&meta, uiData)
if err != nil {
return nil, errors.Wrap(err, "fail to find dependent addon in source repository")
}
return installPackage, nil
}
@@ -1098,7 +1111,8 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
if !apierrors.IsNotFound(err) {
return err
}
depAddon, err := h.loadInstallPackage(dep.Name)
// always install addon's latest version
depAddon, err := h.loadInstallPackage(dep.Name, "")
if err != nil {
return err
}
+2 -2
View File
@@ -552,7 +552,7 @@ func TestRenderApp4Observability(t *testing.T) {
},
},
args: map[string]interface{}{},
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":null}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application"}]}},"status":{}}`,
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability","addons.oam.dev/version":""}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":null}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application"}]}},"status":{}}`,
},
}
for _, tc := range testcases {
@@ -599,7 +599,7 @@ func TestRenderApp4ObservabilityWithK8sData(t *testing.T) {
},
},
args: map[string]interface{}{},
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":[{"name":"test-secret","placement":{"clusterSelector":{"name":"test-secret"}}}]}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"},{"name":"test-secret","type":"deploy2env","properties":{"env":"test-secret","parallel":true,"policy":"domain"}}]}},"status":{}}`,
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability","addons.oam.dev/version":""}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":[{"name":"test-secret","placement":{"clusterSelector":{"name":"test-secret"}}}]}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"},{"name":"test-secret","type":"deploy2env","properties":{"env":"test-secret","parallel":true,"policy":"domain"}}]}},"status":{}}`,
},
}
for _, tc := range testcases {
+104 -38
View File
@@ -43,6 +43,8 @@ type Cache struct {
registry map[string]Registry
versionedUIData map[string]map[string]*UIData
mutex *sync.RWMutex
ds RegistryDataStore
@@ -51,11 +53,12 @@ type Cache struct {
// NewCache will build a new cache instance
func NewCache(ds RegistryDataStore) *Cache {
return &Cache{
uiData: make(map[string][]*UIData),
registryMeta: make(map[string]map[string]SourceMeta),
registry: make(map[string]Registry),
mutex: new(sync.RWMutex),
ds: ds,
uiData: make(map[string][]*UIData),
registryMeta: make(map[string]map[string]SourceMeta),
registry: make(map[string]Registry),
versionedUIData: make(map[string]map[string]*UIData),
mutex: new(sync.RWMutex),
ds: ds,
}
}
@@ -80,21 +83,35 @@ func (u *Cache) ListAddonMeta(r Registry) (map[string]SourceMeta, error) {
}
// GetUIData get addon data for UI display from cache, if cache not found, it will find from source
func (u *Cache) GetUIData(r Registry, addonName string) (*UIData, error) {
addon := u.getCachedUIData(r.Name, addonName)
func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error) {
addon := u.getCachedUIData(r, addonName, version)
if addon != nil {
return addon, nil
}
var err error
registryMeta, err := u.ListAddonMeta(r)
if err != nil {
return nil, err
if !IsVersionRegistry(r) {
registryMeta, err := u.ListAddonMeta(r)
if err != nil {
return nil, err
}
meta, ok := registryMeta[addonName]
if !ok {
return nil, ErrNotExist
}
addon, err = r.GetUIData(&meta, UIMetaOptions)
if err != nil {
return nil, err
}
} else {
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
addon, err = versionedRegistry.GetAddonUIData(context.Background(), addonName, version)
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
return nil, err
}
}
meta, ok := registryMeta[addonName]
if !ok {
return nil, ErrNotExist
}
return r.GetUIData(&meta, UIMetaOptions)
return addon, nil
}
// ListUIData will always list UIData from cache first, if not exist, read from source.
@@ -104,24 +121,40 @@ func (u *Cache) ListUIData(r Registry) ([]*UIData, error) {
if listAddons != nil {
return listAddons, nil
}
addonMeta, err := u.ListAddonMeta(r)
if err != nil {
return nil, err
}
listAddons, err = r.ListUIData(addonMeta, UIMetaOptions)
if err != nil {
return nil, fmt.Errorf("fail to get addons from registry %s, %w", r.Name, err)
if !IsVersionRegistry(r) {
addonMeta, err := u.ListAddonMeta(r)
if err != nil {
return nil, err
}
listAddons, err = r.ListUIData(addonMeta, UIMetaOptions)
if err != nil {
return nil, fmt.Errorf("fail to get addons from registry %s, %w", r.Name, err)
}
} else {
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
listAddons, err = versionedRegistry.ListAddon()
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
return nil, err
}
}
u.putAddonUIData2Cache(r.Name, listAddons)
return listAddons, nil
}
func (u *Cache) getCachedUIData(registry, addonName string) *UIData {
addons := u.listCachedUIData(registry)
for _, a := range addons {
if a.Name == addonName {
return a
func (u *Cache) getCachedUIData(registry Registry, addonName, version string) *UIData {
if !IsVersionRegistry(registry) {
addons := u.listCachedUIData(registry.Name)
for _, a := range addons {
if a.Name == addonName {
return a
}
}
} else {
if len(version) == 0 {
version = "latest"
}
return u.versionedUIData[registry.Name][fmt.Sprintf("%s-%s", addonName, version)]
}
return nil
}
@@ -201,6 +234,20 @@ func (u *Cache) putRegistry2Cache(registry []Registry) {
}
}
func (u *Cache) putVersionedUIData2Cache(registryName, addonName, version string, uiData *UIData) {
if u == nil {
return
}
u.mutex.Lock()
defer u.mutex.Unlock()
if u.versionedUIData[registryName] == nil {
u.versionedUIData[registryName] = make(map[string]*UIData)
}
u.versionedUIData[registryName][fmt.Sprintf("%s-%s", addonName, version)] = uiData
}
func (u *Cache) discoverAndRefreshRegistry() {
registries, err := u.ds.ListRegistries(context.Background())
if err != nil {
@@ -210,17 +257,36 @@ func (u *Cache) discoverAndRefreshRegistry() {
u.putRegistry2Cache(registries)
for _, r := range registries {
registryMeta, err := r.ListAddonMeta()
if err != nil {
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
continue
if !IsVersionRegistry(r) {
registryMeta, err := r.ListAddonMeta()
if err != nil {
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
continue
}
u.putAddonMeta2Cache(r.Name, registryMeta)
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
continue
}
u.putAddonUIData2Cache(r.Name, uiData)
} else {
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
uiDatas, err := versionedRegistry.ListAddon()
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
continue
}
for _, addon := range uiDatas {
uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version)
if err != nil {
log.Logger.Errorf("fail to get addon from registry %s, addon %s version %s for cache updating, %v", addon.Name, r.Name, err)
continue
}
u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData)
// we also no version key, if use get addonUIData without version will return this vale as latest data.
u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData)
}
}
u.putAddonMeta2Cache(r.Name, registryMeta)
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
continue
}
u.putAddonUIData2Cache(r.Name, uiData)
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package addon
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPutVersionedUIData2cache(t *testing.T) {
uiData := UIData{Meta: Meta{Name: "fluxcd", Icon: "test.com/fluxcd.png", Version: "1.0.0"}}
u := NewCache(nil)
u.putVersionedUIData2Cache("helm-repo", "fluxcd", "1.0.0", &uiData)
assert.NotEmpty(t, u.versionedUIData)
assert.NotEmpty(t, u.versionedUIData["helm-repo"])
assert.NotEmpty(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"])
assert.Equal(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"].Name, "fluxcd")
}
+8 -8
View File
@@ -21,10 +21,7 @@ import (
"encoding/json"
"fmt"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"k8s.io/client-go/discovery"
"k8s.io/klog/v2"
v1 "k8s.io/api/core/v1"
@@ -34,8 +31,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
@@ -53,9 +52,9 @@ const (
)
// EnableAddon will enable addon with dependency check, source is where addon from.
func EnableAddon(ctx context.Context, name string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache) error {
func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache) error {
h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache)
pkg, err := h.loadInstallPackage(name)
pkg, err := h.loadInstallPackage(name, version)
if err != nil {
return err
}
@@ -172,11 +171,11 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
}
return Status{AddonPhase: enabled, AppStatus: &app.Status, Clusters: clusters}, nil
}
return Status{AddonPhase: enabled, AppStatus: &app.Status}, nil
return Status{AddonPhase: enabled, AppStatus: &app.Status, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion]}, nil
case commontypes.ApplicationDeleting:
return Status{AddonPhase: disabling, AppStatus: &app.Status}, nil
default:
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
return Status{AddonPhase: enabling, AppStatus: &app.Status, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion]}, nil
}
}
@@ -235,5 +234,6 @@ type Status struct {
AddonPhase string
AppStatus *commontypes.AppStatus
// the status of multiple clusters
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
InstalledVersion string
}
+71
View File
@@ -0,0 +1,71 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package addon
import (
"testing"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v3/pkg/chart/loader"
)
var files = []*loader.BufferedFile{
{
Name: "metadata.yaml",
Data: []byte(`name: test-helm-addon
version: 1.0.0
description: This is a addon for test when install addon from helm repo
icon: https://www.terraform.io/assets/images/logo-text-8c3ba8a6.svg
url: https://terraform.io/
tags: []
deployTo:
control_plane: true
runtime_cluster: false
dependencies: []
invisible: false`),
},
{
Name: "/resources/parameter.cue",
Data: []byte(`parameter: {
// test wrong parameter
example: *"default"
}`),
},
}
func TestMemoryReader(t *testing.T) {
m := MemoryReader{
Name: "fluxcd",
Files: files,
}
meta, err := m.ListAddonMeta()
assert.NoError(t, err)
assert.Equal(t, len(meta["fluxcd"].Items), 2)
metaFile, err := m.ReadFile("metadata.yaml")
assert.NoError(t, err)
assert.NotEmpty(t, metaFile)
paramterData, err := m.ReadFile("/resources/parameter.cue")
assert.NoError(t, err)
assert.NotEmpty(t, paramterData)
}
-1
View File
@@ -96,5 +96,4 @@ func TestGiteeReader(t *testing.T) {
_, err := r.ReadFile("example/metadata.yaml")
assert.NoError(t, err)
testReaderFunc(t, r)
}
-1
View File
@@ -108,7 +108,6 @@ func TestGitHubReader(t *testing.T) {
_, err := r.ReadFile("example/metadata.yaml")
assert.NoError(t, err)
testReaderFunc(t, r)
}
// Int is a helper routine that allocates a new int value
+60
View File
@@ -0,0 +1,60 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package addon
import (
"path/filepath"
"strings"
"helm.sh/helm/v3/pkg/chart/loader"
)
// MemoryReader is async reader for memory data
type MemoryReader struct {
Name string
Files []*loader.BufferedFile
fileData map[string]string
}
// ListAddonMeta list all metadata of helm repo registry
func (l *MemoryReader) ListAddonMeta() (map[string]SourceMeta, error) {
metas := SourceMeta{Name: l.Name}
for _, f := range l.Files {
metas.Items = append(metas.Items, OSSItem{tp: "file", name: f.Name})
if l.fileData == nil {
l.fileData = make(map[string]string)
}
l.fileData[f.Name] = string(f.Data)
}
return map[string]SourceMeta{l.Name: metas}, nil
}
// ReadFile ready file from memory
func (l *MemoryReader) ReadFile(path string) (string, error) {
if file, ok := l.fileData[path]; ok {
return file, nil
}
return l.fileData[strings.TrimPrefix(path, l.Name+"/")], nil
}
// RelativePath calculate the relative path of one file
func (l *MemoryReader) RelativePath(item Item) string {
if strings.HasPrefix(item.GetName(), l.Name) {
return item.GetName()
}
return filepath.Join(l.Name, item.GetName())
}
+1
View File
@@ -37,6 +37,7 @@ const registriesKey = "registries"
type Registry struct {
Name string `json:"name"`
Helm *HelmSource `json:"helm,omitempty"`
Git *GitAddonSource `json:"git,omitempty"`
OSS *OSSAddonSource `json:"oss,omitempty"`
Gitee *GiteeAddonSource `json:"gitee,omitempty"`
+5
View File
@@ -63,6 +63,11 @@ type GiteeAddonSource struct {
Token string `json:"token,omitempty"`
}
// HelmSource defines the information about the helm repo addon source
type HelmSource struct {
URL string `json:"url,omitempty" validate:"required"`
}
// Item is a partial interface for github.RepositoryContent
type Item interface {
// GetType return "dir" or "file"
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
entries:
fluxcd:
- created: "2022-03-25T21:04:51.244331+08:00"
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
name: fluxcd
urls:
- http://127.0.0.1:18083/fluxcd-1.0.0.tgz
version: 1.0.0
generated: "0001-01-01T00:00:00Z"
+14 -1
View File
@@ -37,6 +37,8 @@ type UIData struct {
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
Parameters string `json:"parameters"`
RegistryName string `json:"registryName"`
AvailableVersions []string `json:"availableVersions"`
}
// InstallPackage contains all necessary files that can be installed for an addon
@@ -47,7 +49,7 @@ type InstallPackage struct {
Definitions []ElementFile `json:"definitions"`
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
// DefSchemas are UI schemas read by VelaUX, it will only be installed in control plane clusters
DefSchemas []ElementFile `json:"def_schemas,omitempty"`
DefSchemas []ElementFile `json:"defSchemas,omitempty"`
Parameters string `json:"parameters"`
@@ -57,6 +59,17 @@ type InstallPackage struct {
AppTemplate *v1beta1.Application `json:"appTemplate"`
}
// WholeAddonPackage contains all infos of an addon
type WholeAddonPackage struct {
InstallPackage
APISchema *openapi3.Schema `json:"schema"`
// Detail is README.md in an addon
Detail string `json:"detail,omitempty"`
AvailableVersions []string `json:"availableVersions"`
}
// Meta defines the format for a single addon
type Meta struct {
Name string `json:"name" validate:"required"`
+27 -12
View File
@@ -153,18 +153,28 @@ func findLegacyAddonDefs(ctx context.Context, k8sClient client.Client, addonName
var defObjects []*unstructured.Unstructured
for i, registry := range registries {
if registry.Name == registryName {
installer := NewAddonInstaller(ctx, k8sClient, nil, nil, config, &registries[i], nil, nil)
metas, err := installer.getAddonMeta()
if err != nil {
return err
var uiData *UIData
if !IsVersionRegistry(registry) {
installer := NewAddonInstaller(ctx, k8sClient, nil, nil, config, &registries[i], nil, nil)
metas, err := installer.getAddonMeta()
if err != nil {
return err
}
meta := metas[addonName]
// only fetch definition files from registry.
uiData, err = registry.GetUIData(&meta, UnInstallOptions)
if err != nil {
return errors.Wrapf(err, "cannot fetch addon difinition files from registry")
}
} else {
versionedRegistry := BuildVersionedRegistry(registry.Name, registry.Helm.URL)
uiData, err = versionedRegistry.GetAddonUIData(ctx, addonName, "")
if err != nil {
return errors.Wrapf(err, "cannot fetch addon difinition files from registry")
}
}
meta := metas[addonName]
// only fetch definition files from registry.
data, err := registry.GetUIData(&meta, UnInstallOptions)
if err != nil {
return errors.Wrapf(err, "cannot fetch addon difinition files from registry")
}
for _, defYaml := range data.Definitions {
for _, defYaml := range uiData.Definitions {
def, err := renderObject(defYaml)
if err != nil {
// don't let one error defined definition block whole disable process
@@ -172,7 +182,7 @@ func findLegacyAddonDefs(ctx context.Context, k8sClient client.Client, addonName
}
defObjects = append(defObjects, def)
}
for _, cueDef := range data.CUEDefinitions {
for _, cueDef := range uiData.CUEDefinitions {
def := definition.Definition{Unstructured: unstructured.Unstructured{}}
err := def.FromCUEString(cueDef.Data, config)
if err != nil {
@@ -209,3 +219,8 @@ func usingAppsInfo(apps []v1beta1.Application) string {
res = strings.TrimSuffix(res, ",") + ".Please delete them before disabling the addon."
return res
}
// IsVersionRegistry check the repo source if support multi-version addon
func IsVersionRegistry(r Registry) bool {
return r.Helm != nil
}
+172
View File
@@ -0,0 +1,172 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package addon
import (
"bytes"
"context"
"fmt"
"sort"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/repo"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/helm"
)
// VersionedRegistry is the interface of support version registry
type VersionedRegistry interface {
ListAddon() ([]*UIData, error)
GetAddonUIData(ctx context.Context, addonName, version string) (*UIData, error)
GetAddonInstallPackage(ctx context.Context, addonName, version string) (*InstallPackage, error)
}
// BuildVersionedRegistry is build versioned addon registry
func BuildVersionedRegistry(name, repoURL string) VersionedRegistry {
return &versionedRegistry{
name: name,
url: repoURL,
h: helm.NewHelperWithCache(),
}
}
type versionedRegistry struct {
url string
name string
h *helm.Helper
}
func (i *versionedRegistry) ListAddon() ([]*UIData, error) {
chartIndex, err := i.h.GetIndexInfo(i.url, false)
if err != nil {
return nil, err
}
return i.resolveAddonListFromIndex(i.name, chartIndex), nil
}
func (i *versionedRegistry) GetAddonUIData(ctx context.Context, addonName, version string) (*UIData, error) {
wholePackage, err := i.loadAddon(ctx, addonName, version)
if err != nil {
return nil, err
}
return &UIData{
Meta: wholePackage.Meta,
APISchema: wholePackage.APISchema,
Parameters: wholePackage.Parameters,
Detail: wholePackage.Detail,
Definitions: wholePackage.Definitions,
AvailableVersions: wholePackage.AvailableVersions,
}, nil
}
func (i *versionedRegistry) GetAddonInstallPackage(ctx context.Context, addonName, version string) (*InstallPackage, error) {
wholePackage, err := i.loadAddon(context.Background(), addonName, version)
if err != nil {
return nil, err
}
return &wholePackage.InstallPackage, nil
}
func (i *versionedRegistry) resolveAddonListFromIndex(repoName string, index *repo.IndexFile) []*UIData {
var res []*UIData
for addonName, versions := range index.Entries {
if len(versions) == 0 {
continue
}
sort.Sort(sort.Reverse(versions))
latestVersion := versions[0]
var availableVersions []string
for _, version := range versions {
availableVersions = append(availableVersions, version.Version)
}
o := UIData{Meta: Meta{
Name: addonName,
Icon: latestVersion.Icon,
Tags: latestVersion.Keywords,
Description: latestVersion.Description,
Version: latestVersion.Version,
}, RegistryName: repoName, AvailableVersions: availableVersions}
res = append(res, &o)
}
return res
}
func (i versionedRegistry) loadAddon(ctx context.Context, name, version string) (*WholeAddonPackage, error) {
versions, err := i.h.ListVersions(i.url, name, false)
if err != nil {
return nil, err
}
if len(versions) == 0 {
return nil, ErrNotExist
}
var addonVersion *repo.ChartVersion
sort.Sort(sort.Reverse(versions))
if len(version) == 0 {
// if not specify version will always use latest version
addonVersion = versions[0]
}
var availableVersions []string
for i, v := range versions {
if v.Version == version {
addonVersion = versions[i]
availableVersions = append(availableVersions, v.Version)
}
}
if addonVersion == nil {
return nil, nil
}
for _, chartURL := range addonVersion.URLs {
archive, err := common.HTTPGet(ctx, chartURL)
if err != nil {
continue
}
bufferedFile, err := loader.LoadArchiveFiles(bytes.NewReader(archive))
if err != nil {
continue
}
addonPkg, err := loadAddonPackage(name, bufferedFile)
if err != nil {
return nil, err
}
addonPkg.AvailableVersions = availableVersions
return addonPkg, nil
}
return nil, fmt.Errorf("cannot fetch addon package")
}
func loadAddonPackage(addonName string, files []*loader.BufferedFile) (*WholeAddonPackage, error) {
mr := MemoryReader{Name: addonName, Files: files}
metas, err := mr.ListAddonMeta()
if err != nil {
return nil, err
}
meta := metas[addonName]
addonUIData, err := GetUIDataFromReader(&mr, &meta, UIMetaOptions)
if err != nil {
return nil, err
}
installPackage, err := GetInstallPackageFromReader(&mr, &meta, addonUIData)
if err != nil {
return nil, err
}
return &WholeAddonPackage{
InstallPackage: *installPackage,
Detail: addonUIData.Detail,
APISchema: addonUIData.APISchema,
}, nil
}
+76
View File
@@ -0,0 +1,76 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package addon
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestVersionRegistry(t *testing.T) {
go func() {
http.HandleFunc("/", versionedHandler)
err := http.ListenAndServe(fmt.Sprintf(":%d", 18083), nil)
if err != nil {
log.Fatal("Setup server error:", err)
}
}()
// wait server setup
time.Sleep(3 * time.Second)
r := BuildVersionedRegistry("helm-repo", "http://127.0.0.1:18083")
addons, err := r.ListAddon()
assert.NoError(t, err)
assert.Equal(t, len(addons), 1)
assert.Equal(t, addons[0].Name, "fluxcd")
assert.Equal(t, len(addons[0].AvailableVersions), 1)
addonUIData, err := r.GetAddonUIData(context.Background(), "fluxcd", "1.0.0")
assert.NoError(t, err)
assert.NotEmpty(t, addonUIData.Definitions)
assert.NotEmpty(t, addonUIData.Icon)
addonsInstallPackage, err := r.GetAddonInstallPackage(context.Background(), "fluxcd", "1.0.0")
assert.NoError(t, err)
assert.NotEmpty(t, addonsInstallPackage)
assert.NotEmpty(t, addonsInstallPackage.YAMLTemplates)
assert.NotEmpty(t, addonsInstallPackage.DefSchemas)
}
var versionedHandler http.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) {
switch {
case strings.Contains(request.URL.Path, "index.yaml"):
files, err := ioutil.ReadFile("./testdata/helm-repo/index.yaml")
if err != nil {
_, _ = writer.Write([]byte(err.Error()))
}
writer.Write(files)
case strings.Contains(request.URL.Path, "fluxcd-1.0.0.tgz"):
files, err := ioutil.ReadFile("./testdata/helm-repo/fluxcd-1.0.0.tgz")
if err != nil {
_, _ = writer.Write([]byte(err.Error()))
}
writer.Write(files)
}
}
+11 -4
View File
@@ -75,6 +75,7 @@ type NameAlias struct {
// CreateAddonRegistryRequest defines the format for addon registry create request
type CreateAddonRegistryRequest struct {
Name string `json:"name" validate:"checkname"`
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty" `
Oss *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
@@ -82,6 +83,7 @@ type CreateAddonRegistryRequest struct {
// UpdateAddonRegistryRequest defines the format for addon registry update request
type UpdateAddonRegistryRequest struct {
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty"`
Oss *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
@@ -90,6 +92,7 @@ type UpdateAddonRegistryRequest struct {
// AddonRegistry defines the format for a single addon registry
type AddonRegistry struct {
Name string `json:"name" validate:"required"`
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty"`
OSS *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
@@ -106,6 +109,8 @@ type EnableAddonRequest struct {
Args map[string]interface{} `json:"args,omitempty"`
// Clusters specify the clusters this addon should be installed, if not specified, it will follow the configure in addon metadata.yaml
Clusters []string `json:"clusters,omitempty"`
// Version specify the version of addon to enable
Version string `json:"version,omitempty"`
}
// ListAddonResponse defines the format for addon list response
@@ -141,9 +146,10 @@ type DetailAddonResponse struct {
UISchema utils.UISchema `json:"uiSchema"`
// More details about the addon, e.g. README
Detail string `json:"detail,omitempty"`
Definitions []*AddonDefinition `json:"definitions"`
RegistryName string `json:"registryName,omitempty"`
Detail string `json:"detail,omitempty"`
Definitions []*AddonDefinition `json:"definitions"`
RegistryName string `json:"registryName,omitempty"`
AvailableVersions []string `json:"availableVersions"`
}
// AddonDefinition is definition an addon can provide
@@ -161,7 +167,8 @@ type AddonStatusResponse struct {
EnablingProgress *EnablingProgress `json:"enabling_progress,omitempty"`
AppStatus common.AppStatus `json:"appStatus,omitempty"`
// the status of multiple clusters
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
InstalledVersion string `json:"installedVersion,omitempty"`
}
// EnablingProgress defines the progress of enabling an addon
+16 -14
View File
@@ -58,7 +58,7 @@ type AddonHandler interface {
ListAddonRegistries(ctx context.Context) ([]*apis.AddonRegistry, error)
ListAddons(ctx context.Context, registry, query string) ([]*apis.DetailAddonResponse, error)
StatusAddon(ctx context.Context, name string) (*apis.AddonStatusResponse, error)
GetAddon(ctx context.Context, name string, registry string) (*apis.DetailAddonResponse, error)
GetAddon(ctx context.Context, name string, registry string, version string) (*apis.DetailAddonResponse, error)
EnableAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error
DisableAddon(ctx context.Context, name string, force bool) error
ListEnabledAddon(ctx context.Context) ([]*apis.AddonBaseStatus, error)
@@ -82,12 +82,13 @@ func AddonImpl2AddonRes(impl *pkgaddon.UIData) (*apis.DetailAddonResponse, error
})
}
return &apis.DetailAddonResponse{
Meta: impl.Meta,
APISchema: impl.APISchema,
UISchema: impl.UISchema,
Detail: impl.Detail,
Definitions: defs,
RegistryName: impl.RegistryName,
Meta: impl.Meta,
APISchema: impl.APISchema,
UISchema: impl.UISchema,
Detail: impl.Detail,
Definitions: defs,
RegistryName: impl.RegistryName,
AvailableVersions: impl.AvailableVersions,
}, nil
}
@@ -134,7 +135,7 @@ type defaultAddonHandler struct {
}
// GetAddon will get addon information
func (u *defaultAddonHandler) GetAddon(ctx context.Context, name string, registry string) (*apis.DetailAddonResponse, error) {
func (u *defaultAddonHandler) GetAddon(ctx context.Context, name string, registry string, version string) (*apis.DetailAddonResponse, error) {
var addon *pkgaddon.UIData
var err error
if registry == "" {
@@ -143,7 +144,7 @@ func (u *defaultAddonHandler) GetAddon(ctx context.Context, name string, registr
return nil, err
}
for _, r := range registries {
addon, err = u.addonRegistryCache.GetUIData(r, name)
addon, err = u.addonRegistryCache.GetUIData(r, name, version)
if err != nil && !errors.Is(err, pkgaddon.ErrNotExist) {
return nil, err
}
@@ -156,7 +157,7 @@ func (u *defaultAddonHandler) GetAddon(ctx context.Context, name string, registr
if err != nil {
return nil, err
}
addon, err = u.addonRegistryCache.GetUIData(addonRegistry, name)
addon, err = u.addonRegistryCache.GetUIData(addonRegistry, name, version)
if err != nil && !errors.Is(err, pkgaddon.ErrNotExist) {
return nil, err
}
@@ -195,8 +196,9 @@ func (u *defaultAddonHandler) StatusAddon(ctx context.Context, name string) (*ap
Name: name,
Phase: apis.AddonPhase(status.AddonPhase),
},
AppStatus: *status.AppStatus,
Clusters: status.Clusters,
InstalledVersion: status.InstalledVersion,
AppStatus: *status.AppStatus,
Clusters: status.Clusters,
}
if res.Phase != apis.AddonPhaseEnabled {
@@ -360,7 +362,7 @@ func (u *defaultAddonHandler) EnableAddon(ctx context.Context, name string, args
return err
}
for _, r := range registries {
err = pkgaddon.EnableAddon(ctx, name, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache)
err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache)
if err == nil {
return nil
}
@@ -428,7 +430,7 @@ func (u *defaultAddonHandler) UpdateAddon(ctx context.Context, name string, args
}
for _, r := range registries {
err = pkgaddon.EnableAddon(ctx, name, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache)
err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache)
if err == nil {
return nil
}
+3 -1
View File
@@ -77,6 +77,8 @@ func (s *addonWebService) GetWebService() *restful.WebService {
Filter(s.rbacUsecase.CheckPerm("addon", "detail")).
Returns(200, "OK", apis.DetailAddonResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Param(ws.PathParameter("name", "addon name to query detail").DataType("string").Required(true)).
Param(ws.QueryParameter("version", "specify addon version to enable").DataType("string").Required(false)).
Param(ws.PathParameter("addonName", "addon name to query detail").DataType("string").Required(true)).
Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")).
Writes(apis.DetailAddonResponse{}))
@@ -154,7 +156,7 @@ func (s *addonWebService) listAddons(req *restful.Request, res *restful.Response
func (s *addonWebService) detailAddon(req *restful.Request, res *restful.Response) {
name := req.PathParameter("addonName")
addon, err := s.handler.GetAddon(req.Request.Context(), name, req.QueryParameter("registry"))
addon, err := s.handler.GetAddon(req.Request.Context(), name, req.QueryParameter("registry"), req.QueryParameter("version"))
if err != nil {
bcode.ReturnError(req, res, err)
return
+3
View File
@@ -69,6 +69,9 @@ const (
// LabelAddonName indicates the name of the corresponding Addon
LabelAddonName = "addons.oam.dev/name"
// LabelAddonVersion indicates the version of the corresponding installed Addon
LabelAddonVersion = "addons.oam.dev/version"
// LabelAddonRegistry indicates the name of addon-registry
LabelAddonRegistry = "addons.oam.dev/registry"
+5 -6
View File
@@ -179,14 +179,15 @@ func (h *Helper) UninstallRelease(releaseName, namespace string, config *rest.Co
// ListVersions list available versions from repo
func (h *Helper) ListVersions(repoURL string, chartName string, skipCache bool) (repo.ChartVersions, error) {
i, err := h.getIndexInfo(repoURL, skipCache)
i, err := h.GetIndexInfo(repoURL, skipCache)
if err != nil {
return nil, err
}
return i.Entries[chartName], nil
}
func (h *Helper) getIndexInfo(repoURL string, skipCache bool) (*repo.IndexFile, error) {
// GetIndexInfo get index.yaml form given repo url
func (h *Helper) GetIndexInfo(repoURL string, skipCache bool) (*repo.IndexFile, error) {
if h.cache != nil && !skipCache {
if i := h.cache.Get(fmt.Sprintf(repoPatten, repoURL)); i != nil {
return i.(*repo.IndexFile), nil
@@ -220,8 +221,6 @@ func (h *Helper) getIndexInfo(repoURL string, skipCache bool) (*repo.IndexFile,
if h.cache != nil {
h.cache.Put(fmt.Sprintf(repoPatten, repoURL), i, calculateCacheTimeFromIndex(len(i.Entries)))
}
fmt.Println(len(i.Entries))
return i, nil
}
@@ -286,7 +285,7 @@ func newActionConfig(config *rest.Config, namespace string, showDetail bool, log
// ListChartsFromRepo list available helm charts in a repo
func (h *Helper) ListChartsFromRepo(repoURL string, skipCache bool) ([]string, error) {
i, err := h.getIndexInfo(repoURL, skipCache)
i, err := h.GetIndexInfo(repoURL, skipCache)
if err != nil {
return nil, err
}
@@ -306,7 +305,7 @@ func (h *Helper) GetValuesFromChart(repoURL string, chartName string, version st
return v.(map[string]interface{}), nil
}
}
i, err := h.getIndexInfo(repoURL, skipCache)
i, err := h.GetIndexInfo(repoURL, skipCache)
if err != nil {
return nil, err
}
+14 -2
View File
@@ -38,6 +38,7 @@ const (
addonGitToken = "gitToken"
addonOssType = "OSS"
addonGitType = "git"
addonHelmType = "helm"
)
// NewAddonRegistryCommand return an addon registry command
@@ -173,7 +174,8 @@ func listAddonRegistry(ctx context.Context, c common.Args) error {
table.AddRow("Name", "Type", "URL")
for _, registry := range registries {
var repoType, repoURL string
if registry.OSS != nil {
switch {
case registry.OSS != nil:
repoType = "OSS"
u, err := url.Parse(registry.OSS.Endpoint)
if err != nil {
@@ -187,9 +189,16 @@ func listAddonRegistry(ctx context.Context, c common.Args) error {
}
repoURL = fmt.Sprintf("%s://%s.%s", u.Scheme, registry.OSS.Bucket, u.Host)
}
} else {
case registry.Git != nil:
repoType = "git"
repoURL = fmt.Sprintf("%s/tree/master/%s", registry.Git.URL, registry.Git.Path)
case registry.Gitee != nil:
repoType = "gitee"
repoURL = fmt.Sprintf("%s/tree/master/%s", registry.Gitee.URL, registry.Gitee.Path)
case registry.Helm != nil:
repoType = "helm"
repoURL = registry.Helm.URL
}
table.AddRow(registry.Name, repoType, repoURL)
}
@@ -314,6 +323,9 @@ func getRegistryFromArgs(cmd *cobra.Command, args []string) (*pkgaddon.Registry,
return nil, err
}
r.Git.Token = token
case addonHelmType:
r.Helm = &pkgaddon.HelmSource{}
r.Helm.URL = endpoint
default:
return nil, errors.New("not support addon registry type")
}
+54 -15
View File
@@ -24,6 +24,8 @@ import (
"strings"
"time"
"github.com/fatih/color"
"k8s.io/client-go/discovery"
"helm.sh/helm/v3/pkg/strvals"
@@ -69,6 +71,7 @@ const (
)
var forceDisable bool
var addonVersion string
// NewAddonCommand create `addon` command
func NewAddonCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
@@ -123,6 +126,8 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com
Example: `\
Enable addon by:
vela addon enable <addon-name>
Enable addon with specify version:
vela addon enable <addon-name> --version <addon-version>
Enable addon for specific clusters, (local means control plane):
vela addon enable <addon-name> --clusters={local,cluster1,cluster2}
`,
@@ -165,7 +170,7 @@ Enable addon for specific clusters, (local means control plane):
return fmt.Errorf("addon directory %s not found in local", addonOrDir)
}
err = enableAddon(ctx, k8sClient, dc, config, name, addonArgs)
err = enableAddon(ctx, k8sClient, dc, config, name, addonVersion, addonArgs)
if err != nil {
return err
}
@@ -175,6 +180,8 @@ Enable addon for specific clusters, (local means control plane):
return nil
},
}
cmd.Flags().StringVarP(&addonVersion, "version", "v", "", "specify the addon version to enable")
return cmd
}
@@ -207,6 +214,8 @@ func NewAddonUpgradeCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Co
Example: `\
Upgrade addon by:
vela addon upgrade <addon-name>
Upgrade addon with specify version:
vela addon upgrade <addon-name> --version <addon-version>
Upgrade addon for specific clusters, (local means control plane):
vela addon upgrade <addon-name> --clusters={local,cluster1,cluster2}
`,
@@ -256,7 +265,7 @@ Upgrade addon for specific clusters, (local means control plane):
if err != nil {
return errors.Wrapf(err, "cannot fetch addon related addon %s", addonOrDir)
}
err = enableAddon(ctx, k8sClient, dc, config, addonOrDir, addonArgs)
err = enableAddon(ctx, k8sClient, dc, config, addonOrDir, addonVersion, addonArgs)
if err != nil {
return err
}
@@ -267,6 +276,7 @@ Upgrade addon for specific clusters, (local means control plane):
return nil
},
}
cmd.Flags().StringVarP(&addonVersion, "version", "v", "", "specify the addon version to upgrade")
return cmd
}
@@ -349,7 +359,7 @@ func NewAddonStatusCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com
}
}
func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.DiscoveryClient, config *rest.Config, name string, args map[string]interface{}) error {
func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.DiscoveryClient, config *rest.Config, name string, version string, args map[string]interface{}) error {
var err error
registryDS := pkgaddon.NewRegistryDataStore(k8sClient)
registries, err := registryDS.ListRegistries(ctx)
@@ -358,7 +368,7 @@ func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.Dis
}
for _, registry := range registries {
err = pkgaddon.EnableAddon(ctx, name, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil)
err = pkgaddon.EnableAddon(ctx, name, version, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil)
if errors.Is(err, pkgaddon.ErrNotExist) {
continue
}
@@ -424,27 +434,40 @@ func listAddons(ctx context.Context, clt client.Client, registry string) error {
if registry != "" && r.Name != registry {
continue
}
meta, err := r.ListAddonMeta()
if err != nil {
continue
var addonList []*pkgaddon.UIData
var err error
if !pkgaddon.IsVersionRegistry(r) {
meta, err := r.ListAddonMeta()
if err != nil {
continue
}
addonList, err = r.ListUIData(meta, pkgaddon.CLIMetaOptions)
if err != nil {
continue
}
} else {
versionedRegistry := pkgaddon.BuildVersionedRegistry(r.Name, r.Helm.URL)
addonList, err = versionedRegistry.ListAddon()
if err != nil {
continue
}
}
addList, err := r.ListUIData(meta, pkgaddon.CLIMetaOptions)
if err != nil {
continue
}
addons = mergeAddons(addons, addList)
addons = mergeAddons(addons, addonList)
}
table := uitable.New()
table.AddRow("NAME", "REGISTRY", "DESCRIPTION", "STATUS")
table.AddRow("NAME", "REGISTRY", "DESCRIPTION", "AVAILABLE-VERSIONS", "STATUS")
for _, addon := range addons {
status, err := pkgaddon.GetAddonStatus(ctx, clt, addon.Name)
if err != nil {
return err
}
table.AddRow(addon.Name, addon.RegistryName, addon.Description, status.AddonPhase)
statusRow := status.AddonPhase
if len(status.InstalledVersion) != 0 {
statusRow += fmt.Sprintf(" (%s)", status.InstalledVersion)
}
table.AddRow(addon.Name, addon.RegistryName, addon.Description, genAvailableVersionInfo(addon.AvailableVersions, status), statusRow)
onlineAddon[addon.Name] = true
}
appList := v1alpha2.ApplicationList{}
@@ -492,6 +515,22 @@ func waitApplicationRunning(k8sClient client.Client, addonName string) error {
}
func genAvailableVersionInfo(versions []string, status pkgaddon.Status) string {
res := "["
for _, version := range versions {
if version == status.InstalledVersion {
col := color.New(color.Bold, color.FgGreen)
res += col.Sprintf("%s", version)
} else {
res += version
}
res += ", "
}
res = strings.TrimSuffix(res, ", ")
res += "]"
return res
}
// TransAddonName will turn addon's name from xxx/yyy to xxx-yyy
func TransAddonName(name string) string {
return strings.ReplaceAll(name, "/", "-")