mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 20:17:04 +00:00
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:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -96,5 +96,4 @@ func TestGiteeReader(t *testing.T) {
|
||||
_, err := r.ReadFile("example/metadata.yaml")
|
||||
assert.NoError(t, err)
|
||||
|
||||
testReaderFunc(t, r)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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"`
|
||||
|
||||
@@ -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"
|
||||
|
||||
BIN
Binary file not shown.
+15
@@ -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
@@ -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
@@ -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, ®istries[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, ®istries[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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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, "/", "-")
|
||||
|
||||
Reference in New Issue
Block a user