diff --git a/e2e/addon/addon_test.go b/e2e/addon/addon_test.go index 17144fc5a..d1497950f 100644 --- a/e2e/addon/addon_test.go +++ b/e2e/addon/addon_test.go @@ -22,10 +22,10 @@ import ( "strings" "time" - v1 "k8s.io/api/core/v1" - + "github.com/Netflix/go-expect" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" @@ -110,6 +110,30 @@ var _ = Describe("Addon Test", func() { g.Expect(apierrors.IsNotFound(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-test-addon", Namespace: "test-vela"}, &v1beta1.Application{}))).Should(BeTrue()) }, 60*time.Second).Should(Succeed()) }) + + It("Enable fluxcd-test-version whose version can't suit system requirements", func() { + output, err := e2e.InteractiveExec("vela addon enable fluxcd-test-version", func(c *expect.Console) { + _, err = c.SendLine("y") + Expect(err).NotTo(HaveOccurred()) + }) + Expect(output).To(ContainSubstring("enabled successfully")) + Expect(err).NotTo(HaveOccurred()) + }) + + It("Disable addon fluxcd-test-version", func() { + output, err := e2e.LongTimeExec("vela addon disable fluxcd-test-version", 600*time.Second) + Expect(err).NotTo(HaveOccurred()) + Expect(output).To(ContainSubstring("Successfully disable addon")) + }) + + It("Enable fluxcd-test-version whose version can't suit system requirements with 'n' input", func() { + output, err := e2e.InteractiveExec("vela addon enable fluxcd-test-version", func(c *expect.Console) { + _, err = c.SendLine("n") + Expect(err).NotTo(HaveOccurred()) + }) + Expect(output).To(ContainSubstring("you can try another version by command")) + Expect(err).NotTo(HaveOccurred()) + }) }) Context("Addon registry test", func() { diff --git a/e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-1.0.0.tgz b/e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-1.0.0.tgz new file mode 100644 index 000000000..132ead7c7 Binary files /dev/null and b/e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-1.0.0.tgz differ diff --git a/e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-2.0.0.tgz b/e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-2.0.0.tgz new file mode 100644 index 000000000..826693255 Binary files /dev/null and b/e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-2.0.0.tgz differ diff --git a/e2e/addon/mock/testrepo/helm-repo/index.yaml b/e2e/addon/mock/testrepo/helm-repo/index.yaml new file mode 100644 index 000000000..175e35556 --- /dev/null +++ b/e2e/addon/mock/testrepo/helm-repo/index.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +entries: + fluxcd-test-version: + - apiVersion: v2 + appVersion: 1.16.0 + description: A Helm chart for Kubernetes + name: fluxcd-test-version + type: application + urls: + - http://127.0.0.1:9098/helm/fluxcd-test-version-1.0.0.tgz + version: 1.0.0 + annotations: + system: "vela>=1.3.0; kubernetes>=1.10.0" + - apiVersion: v2 + appVersion: 1.16.0 + description: A Helm chart for Kubernetes + name: fluxcd-test-version + type: application + urls: + - http://127.0.0.1:9098/helm/fluxcd-test-version-2.0.0.tgz + version: 2.0.0 + annotations: + system: "vela>=1.5.0; kubernetes>=1.30.0" +generated: "2022-06-15T13:17:04.733573+08:00" \ No newline at end of file diff --git a/e2e/addon/mock/utils/utils.go b/e2e/addon/mock/utils/utils.go index 93a9f6c92..63fe363bd 100644 --- a/e2e/addon/mock/utils/utils.go +++ b/e2e/addon/mock/utils/utils.go @@ -39,7 +39,7 @@ var ( apiVersion: v1 data: registries: '{ "KubeVela":{ "name": "KubeVela", "oss": { "end_point": "http://REGISTRY_ADDR", - "bucket": "" } } }' + "bucket": "" } }, "Test-Helm":{ "name": "Test-Helm", "helm": { "name":"", "password":"", "url": "http://HELM_ADDR"} } }' kind: ConfigMap metadata: name: vela-addon-registry @@ -59,6 +59,7 @@ func ApplyMockServerConfig() error { cm := v1.ConfigMap{} registryCmStr := strings.ReplaceAll(velaRegistry, "REGISTRY_ADDR", fmt.Sprintf("127.0.0.1:%d", Port)) + registryCmStr = strings.ReplaceAll(registryCmStr, "HELM_ADDR", fmt.Sprintf("127.0.0.1:%d/helm", Port)) err = yaml.Unmarshal([]byte(registryCmStr), &cm) if err != nil { diff --git a/e2e/addon/mock/vela_addon_mock_server.go b/e2e/addon/mock/vela_addon_mock_server.go index 96a350708..b06f085c1 100644 --- a/e2e/addon/mock/vela_addon_mock_server.go +++ b/e2e/addon/mock/vela_addon_mock_server.go @@ -22,6 +22,7 @@ import ( "fmt" "html/template" "io/fs" + "io/ioutil" "log" "net/http" "path" @@ -46,6 +47,7 @@ func main() { log.Fatal("Apply mock server config to ConfigMap fail") } http.HandleFunc("/", ossHandler) + http.HandleFunc("/helm/", helmHandler) err = http.ListenAndServe(fmt.Sprintf(":%d", utils.Port), nil) if err != nil { log.Fatal("ListenAndServe: ", err) @@ -103,6 +105,29 @@ var ossHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request } } +var helmHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request) { + switch { + case strings.Contains(req.URL.Path, "index.yaml"): + file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/index.yaml") + if err != nil { + _, _ = rw.Write([]byte(err.Error())) + } + rw.Write(file) + case strings.Contains(req.URL.Path, "fluxcd-test-version-1.0.0.tgz"): + file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-1.0.0.tgz") + if err != nil { + _, _ = rw.Write([]byte(err.Error())) + } + rw.Write(file) + case strings.Contains(req.URL.Path, "fluxcd-test-version-2.0.0.tgz"): + file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-2.0.0.tgz") + if err != nil { + _, _ = rw.Write([]byte(err.Error())) + } + rw.Write(file) + } +} + func init() { _ = fs.WalkDir(testData, "testdata", func(path string, d fs.DirEntry, err error) error { path = strings.TrimPrefix(path, "testdata/") diff --git a/pkg/addon/addon.go b/pkg/addon/addon.go index 5c385d228..a6825d1fd 100644 --- a/pkg/addon/addon.go +++ b/pkg/addon/addon.go @@ -1138,7 +1138,8 @@ func (h *Installer) enableAddon(addon *InstallPackage) error { if !h.skipVersionValidate { err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc) if err != nil { - return VersionUnMatchError{addonName: addon.Name, err: err} + version := h.getAddonVersionMeetSystemRequirement(addon.Name) + return VersionUnMatchError{addonName: addon.Name, err: err, userSelectedAddonVersion: addon.Version, availableVersion: version} } } @@ -1380,6 +1381,27 @@ func (h *Installer) continueOrRestartWorkflow() error { return nil } +// getAddonVersionMeetSystemRequirement return the addon's latest version which meet the system requirements +func (h *Installer) getAddonVersionMeetSystemRequirement(addonName string) string { + if h.r != nil && IsVersionRegistry(*h.r) { + versionedRegistry := BuildVersionedRegistry(h.r.Name, h.r.Helm.URL, &common.HTTPOption{ + Username: h.r.Helm.Username, + Password: h.r.Helm.Password, + }) + versions, err := versionedRegistry.GetAddonAvailableVersion(addonName) + if err != nil { + return "" + } + for _, version := range versions { + req := LoadSystemRequirements(version.Annotations["system"]) + if checkAddonVersionMeetRequired(h.ctx, req, h.cli, h.dc) == nil { + return version.Version + } + } + } + return "" +} + func addOwner(child *unstructured.Unstructured, app *v1beta1.Application) { child.SetOwnerReferences(append(child.GetOwnerReferences(), *metav1.NewControllerRef(app, v1beta1.ApplicationKindVersionKind))) diff --git a/pkg/addon/addon_test.go b/pkg/addon/addon_test.go index 6b6888d82..698bd67b5 100644 --- a/pkg/addon/addon_test.go +++ b/pkg/addon/addon_test.go @@ -22,6 +22,7 @@ import ( "encoding/xml" "errors" "fmt" + "io/ioutil" "net/http" "net/http/httptest" "os" @@ -113,6 +114,29 @@ var ossHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request } } +var helmHandler http.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) { + switch { + case strings.Contains(request.URL.Path, "index.yaml"): + files, err := ioutil.ReadFile("./testdata/multiversion-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/multiversion-helm-repo/fluxcd-1.0.0.tgz") + if err != nil { + _, _ = writer.Write([]byte(err.Error())) + } + writer.Write(files) + case strings.Contains(request.URL.Path, "fluxcd-2.0.0.tgz"): + files, err := ioutil.ReadFile("./testdata/multiversion-helm-repo/fluxcd-2.0.0.tgz") + if err != nil { + _, _ = writer.Write([]byte(err.Error())) + } + writer.Write(files) + } +} + var ctx = context.Background() func testReaderFunc(t *testing.T, reader AsyncReader) { @@ -473,6 +497,22 @@ func TestGetAddonStatus4Observability(t *testing.T) { assert.Equal(t, addonStatus.AddonPhase, enabled) } +func TestGetAddonVersionMeetSystemRequirement(t *testing.T) { + server := httptest.NewServer(helmHandler) + defer server.Close() + i := &Installer{ + r: &Registry{ + Helm: &HelmSource{ + URL: server.URL, + }, + }, + } + version := i.getAddonVersionMeetSystemRequirement("fluxcd-no-requirements") + assert.Equal(t, version, "1.0.0") + version = i.getAddonVersionMeetSystemRequirement("not-exist") + assert.Equal(t, version, "") +} + var baseAddon = InstallPackage{ Meta: Meta{ Name: "test-render-cue-definition-addon", diff --git a/pkg/addon/error.go b/pkg/addon/error.go index 6fa53473b..06ecef735 100644 --- a/pkg/addon/error.go +++ b/pkg/addon/error.go @@ -55,8 +55,24 @@ func WrapErrRateLimit(err error) error { type VersionUnMatchError struct { err error addonName string + // userSelectedAddonVersion is the version of the addon which is selected to install by user + userSelectedAddonVersion string + // availableVersion is the latest available addon's version which suits system requirements + availableVersion string +} + +// GetAvailableVersion load addon's available version from the err +func (v VersionUnMatchError) GetAvailableVersion() (string, error) { + if v.availableVersion == "" { + return "", fmt.Errorf("%s don't exist available version meet system requirement", v.addonName) + } + return v.availableVersion, nil } func (v VersionUnMatchError) Error() string { - return fmt.Sprintf("addon %s system requirement miss match: %v", v.addonName, v.err) + if v.availableVersion != "" { + return fmt.Sprintf("fail to install %s version of %s, because %s.\nInstall %s(v%s) which is the latest version that suits current version requirements", v.userSelectedAddonVersion, v.addonName, v.err, v.addonName, v.availableVersion) + } + return fmt.Sprintf("fail to install %s version of %s, because %s", v.userSelectedAddonVersion, v.addonName, v.err) + } diff --git a/pkg/addon/error_test.go b/pkg/addon/error_test.go new file mode 100644 index 000000000..05709d372 --- /dev/null +++ b/pkg/addon/error_test.go @@ -0,0 +1,43 @@ +/* +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 ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestError(t *testing.T) { + err := &VersionUnMatchError{} + assert.False(t, strings.Contains(err.Error(), "which is the latest version that suits current version requirements")) + err = &VersionUnMatchError{availableVersion: "1.0.0"} + assert.Contains(t, err.Error(), "which is the latest version that suits current version requirements") +} + +func TestGetAvailableVersion(t *testing.T) { + unMatchErr := &VersionUnMatchError{availableVersion: "1.0.0"} + version, err := unMatchErr.GetAvailableVersion() + assert.Empty(t, err) + assert.Equal(t, version, "1.0.0") + + unMatchErr = &VersionUnMatchError{} + version, err = unMatchErr.GetAvailableVersion() + assert.NotEmpty(t, err) + assert.Equal(t, version, "") +} diff --git a/pkg/addon/testdata/multiversion-helm-repo/fluxcd-1.0.0.tgz b/pkg/addon/testdata/multiversion-helm-repo/fluxcd-1.0.0.tgz new file mode 100644 index 000000000..640c145be Binary files /dev/null and b/pkg/addon/testdata/multiversion-helm-repo/fluxcd-1.0.0.tgz differ diff --git a/pkg/addon/testdata/multiversion-helm-repo/fluxcd-2.0.0.tgz b/pkg/addon/testdata/multiversion-helm-repo/fluxcd-2.0.0.tgz new file mode 100644 index 000000000..4da6d1050 Binary files /dev/null and b/pkg/addon/testdata/multiversion-helm-repo/fluxcd-2.0.0.tgz differ diff --git a/pkg/addon/testdata/multiversion-helm-repo/index.yaml b/pkg/addon/testdata/multiversion-helm-repo/index.yaml new file mode 100644 index 000000000..9128a1393 --- /dev/null +++ b/pkg/addon/testdata/multiversion-helm-repo/index.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +entries: + fluxcd: + - apiVersion: v2 + appVersion: 1.16.0 + description: A Helm chart for Kubernetes + name: fluxcd + type: application + urls: + - http://127.0.0.1:18083/multi/fluxcd-1.0.0.tgz + version: 1.0.0 + annotations: + system: "vela>=1.3.0; kubernetes>=1.10.0" + - apiVersion: v2 + appVersion: 1.16.0 + description: A Helm chart for Kubernetes + name: fluxcd + type: application + urls: + - http://127.0.0.1:18083/multi/fluxcd-2.0.0.tgz + version: 2.0.0 + annotations: + system: "vela>=1.4.0; kubernetes>=1.20.0" + fluxcd-no-requirements: + - apiVersion: v2 + appVersion: 1.16.0 + description: A Helm chart for Kubernetes + name: fluxcd + type: application + urls: + - http://127.0.0.1:18083/multi/fluxcd-1.0.0.tgz + version: 1.0.0 + annotations: +generated: "2022-06-15T13:17:04.733573+08:00" \ No newline at end of file diff --git a/pkg/addon/versioned_registry.go b/pkg/addon/versioned_registry.go index 6cf3746ef..f22d7cd8c 100644 --- a/pkg/addon/versioned_registry.go +++ b/pkg/addon/versioned_registry.go @@ -20,18 +20,22 @@ import ( "bytes" "context" "fmt" - + "regexp" "sort" "github.com/Masterminds/semver/v3" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/repo" "github.com/oam-dev/kubevela/pkg/apiserver/utils/log" "github.com/oam-dev/kubevela/pkg/utils" "github.com/oam-dev/kubevela/pkg/utils/common" "github.com/oam-dev/kubevela/pkg/utils/helm" +) - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/repo" +const ( + // patternSystemRequirement is the regex pattern of loading system requirements of the addon + patternSystemRequirement = `vela(.*\d+\.\d+\.\d+);\s?kubernetes(.*\d+\.\d+\.\d+)` ) // VersionedRegistry is the interface of support version registry @@ -40,6 +44,7 @@ type VersionedRegistry interface { GetAddonUIData(ctx context.Context, addonName, version string) (*UIData, error) GetAddonInstallPackage(ctx context.Context, addonName, version string) (*InstallPackage, error) GetDetailedAddon(ctx context.Context, addonName, version string) (*WholeAddonPackage, error) + GetAddonAvailableVersion(addonName string) ([]*repo.ChartVersion, error) } // BuildVersionedRegistry is build versioned addon registry @@ -99,6 +104,11 @@ func (i *versionedRegistry) GetDetailedAddon(ctx context.Context, addonName, ver return wholePackage, nil } +// GetAddonAvailableVersion will return all available versions of the addon which is loaded from the registry, and the version are sorted from last to first +func (i versionedRegistry) GetAddonAvailableVersion(addonName string) ([]*repo.ChartVersion, error) { + return i.loadAddonVersions(addonName) +} + func (i *versionedRegistry) resolveAddonListFromIndex(repoName string, index *repo.IndexFile) []*UIData { var res []*UIData for addonName, versions := range index.Entries { @@ -157,11 +167,25 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string) } addonPkg.AvailableVersions = availableVersions addonPkg.RegistryName = i.name + addonPkg.Meta.SystemRequirements = LoadSystemRequirements(addonVersion.Annotations["system"]) return addonPkg, nil } return nil, fmt.Errorf("cannot fetch addon package") } +// loadAddonVersions Load all available versions of the addon +func (i versionedRegistry) loadAddonVersions(addonName string) ([]*repo.ChartVersion, error) { + versions, err := i.h.ListVersions(i.url, addonName, false, i.Opts) + if err != nil { + return nil, err + } + if len(versions) == 0 { + return nil, ErrNotExist + } + sort.Sort(sort.Reverse(versions)) + return versions, nil +} + func loadAddonPackage(addonName string, files []*loader.BufferedFile) (*WholeAddonPackage, error) { mr := MemoryReader{Name: addonName, Files: files} metas, err := mr.ListAddonMeta() @@ -212,3 +236,21 @@ func chooseVersion(specifiedVersion string, versions []*repo.ChartVersion) (*rep } return addonVersion, availableVersions } + +// LoadSystemRequirements load the system version requirements from the addon's meta file +func LoadSystemRequirements(requirements string) *SystemRequirements { + if len(requirements) == 0 { + return nil + } + regexReq := regexp.MustCompile(patternSystemRequirement) + matched := regexReq.FindStringSubmatch(requirements) + if len(matched) < 3 { + return nil + } + velaReq, k8sReq := matched[1], matched[2] + req := &SystemRequirements{ + VelaVersion: velaReq, + KubernetesVersion: k8sReq, + } + return req +} diff --git a/pkg/addon/versioned_registry_test.go b/pkg/addon/versioned_registry_test.go index 6ae1a2713..68a83c19b 100644 --- a/pkg/addon/versioned_registry_test.go +++ b/pkg/addon/versioned_registry_test.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "log" "net/http" + "net/http/httptest" "strings" "testing" "time" @@ -30,15 +31,17 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/repo" - "github.com/oam-dev/kubevela/pkg/utils/common" - "github.com/stretchr/testify/assert" + + "github.com/oam-dev/kubevela/pkg/utils/common" + "github.com/oam-dev/kubevela/pkg/utils/helm" ) func TestVersionRegistry(t *testing.T) { go func() { http.HandleFunc("/", versionedHandler) http.HandleFunc("/authReg", basicAuthVersionedHandler) + http.HandleFunc("/multi/", multiVersionHandler) err := http.ListenAndServe(fmt.Sprintf(":%d", 18083), nil) if err != nil { log.Fatal("Setup server error:", err) @@ -98,6 +101,38 @@ func TestVersionRegistry(t *testing.T) { assert.NotEmpty(t, addonWholePackage.RegistryName) testListUIData(t) + mr := BuildVersionedRegistry("multiversion-helm-repo", "http://127.0.0.1:18083/multi", nil) + addons, err = mr.ListAddon() + assert.NoError(t, err) + assert.Equal(t, len(addons), 2) + + addonUIData, err = mr.GetAddonUIData(context.Background(), "fluxcd", "2.0.0") + assert.NoError(t, err) + assert.NotEmpty(t, addonUIData.Definitions) + assert.NotEmpty(t, addonUIData.Icon) + assert.Equal(t, addonUIData.Version, "2.0.0") + + addonsInstallPackage, err = mr.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) + assert.NotEmpty(t, addonsInstallPackage.SystemRequirements.VelaVersion, "1.3.0") + assert.NotEmpty(t, addonsInstallPackage.SystemRequirements.KubernetesVersion, "1.10.0") + + addonWholePackage, err = mr.GetDetailedAddon(context.Background(), "fluxcd", "1.0.0") + assert.NoError(t, err) + assert.NotEmpty(t, addonWholePackage) + assert.NotEmpty(t, addonWholePackage.YAMLTemplates) + assert.NotEmpty(t, addonWholePackage.DefSchemas) + assert.NotEmpty(t, addonWholePackage.RegistryName) + assert.Equal(t, addonWholePackage.RegistryName, "multiversion-helm-repo") + + version, err := mr.GetAddonAvailableVersion("fluxcd") + assert.NoError(t, err) + assert.Equal(t, len(version), 2) + assert.Equal(t, addonWholePackage.SystemRequirements.VelaVersion, ">=1.3.0") + assert.Equal(t, addonWholePackage.SystemRequirements.KubernetesVersion, ">=1.10.0") } @@ -172,3 +207,66 @@ var basicAuthVersionedHandler http.HandlerFunc = func(writer http.ResponseWriter writer.Write(files) } } + +var multiVersionHandler http.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) { + switch { + case strings.Contains(request.URL.Path, "index.yaml"): + files, err := ioutil.ReadFile("./testdata/multiversion-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/multiversion-helm-repo/fluxcd-1.0.0.tgz") + if err != nil { + _, _ = writer.Write([]byte(err.Error())) + } + writer.Write(files) + case strings.Contains(request.URL.Path, "fluxcd-2.0.0.tgz"): + files, err := ioutil.ReadFile("./testdata/multiversion-helm-repo/fluxcd-2.0.0.tgz") + if err != nil { + _, _ = writer.Write([]byte(err.Error())) + } + writer.Write(files) + } +} + +func TestLoadSystemRequirements(t *testing.T) { + req := LoadSystemRequirements("vela>=1.3.0; kubernetes>=1.10.0") + assert.Equal(t, req.VelaVersion, ">=1.3.0") + assert.Equal(t, req.KubernetesVersion, ">=1.10.0") + + req = LoadSystemRequirements("") + assert.Empty(t, req) + + req = LoadSystemRequirements("&&&%%") + assert.Empty(t, req) + + req = LoadSystemRequirements("vela>=; kubernetes>=1.10.0") + assert.Empty(t, req) +} + +func TestLoadAddonVersions(t *testing.T) { + server := httptest.NewServer(multiVersionHandler) + defer server.Close() + mr := &versionedRegistry{ + name: "multiversion-helm-repo", + url: server.URL, + h: helm.NewHelperWithCache(), + Opts: nil, + } + versions, err := mr.loadAddonVersions("not-exist") + assert.Error(t, err) + assert.Equal(t, err, ErrNotExist) + assert.Equal(t, len(versions), 0) + + mr = &versionedRegistry{ + name: "multiversion-helm-repo", + url: server.URL, + h: helm.NewHelperWithCache(), + Opts: nil, + } + versions, err = mr.loadAddonVersions("not-exist") + assert.Error(t, err) + assert.Equal(t, len(versions), 0) +} diff --git a/references/cli/addon.go b/references/cli/addon.go index 9cd2c5607..f8ad3ed7f 100644 --- a/references/cli/addon.go +++ b/references/cli/addon.go @@ -26,25 +26,21 @@ import ( "time" "github.com/fatih/color" - "k8s.io/client-go/discovery" - - "helm.sh/helm/v3/pkg/strvals" - - "github.com/oam-dev/kubevela/pkg/apiserver/domain/service" - "github.com/oam-dev/kubevela/pkg/oam" - - "k8s.io/client-go/rest" - "github.com/gosuri/uitable" "github.com/pkg/errors" "github.com/spf13/cobra" + "helm.sh/helm/v3/pkg/strvals" types2 "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/discovery" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" common2 "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" pkgaddon "github.com/oam-dev/kubevela/pkg/addon" + "github.com/oam-dev/kubevela/pkg/apiserver/domain/service" + "github.com/oam-dev/kubevela/pkg/oam" addonutil "github.com/oam-dev/kubevela/pkg/utils/addon" "github.com/oam-dev/kubevela/pkg/utils/apply" "github.com/oam-dev/kubevela/pkg/utils/common" @@ -518,10 +514,21 @@ func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.Dis if errors.Is(err, pkgaddon.ErrNotExist) { continue } - if err != nil { - if errors.As(err, &pkgaddon.VersionUnMatchError{}) { - return fmt.Errorf("%w\nyou can try another version by command: \"vela addon enable %s --version \" ", err, name) + if unMatchErr := new(pkgaddon.VersionUnMatchError); errors.As(err, unMatchErr) { + // Get available version of the addon + availableVersion, err := unMatchErr.GetAvailableVersion() + if err != nil { + return err } + input := NewUserInput() + if input.AskBool(unMatchErr.Error(), &UserInputOptions{AssumeYes: false}) { + err = pkgaddon.EnableAddon(ctx, name, availableVersion, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil) + return err + } + // The user does not agree to use the version provided by us + return fmt.Errorf("you can try another version by command: \"vela addon enable %s --version \" ", name) + } + if err != nil { return err } if err = waitApplicationRunning(k8sClient, name); err != nil { diff --git a/test/e2e-apiserver-test/addon_test.go b/test/e2e-apiserver-test/addon_test.go index 2ef04beff..774bdb2d7 100644 --- a/test/e2e-apiserver-test/addon_test.go +++ b/test/e2e-apiserver-test/addon_test.go @@ -35,7 +35,7 @@ var _ = Describe("Test addon rest api", func() { defer resp.Body.Close() var addonRegistry apisv1.ListAddonRegistryResponse Expect(decodeResponseBody(resp, &addonRegistry)).Should(Succeed()) - Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(1)) + Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(2)) }) It("add addon registry", func() { @@ -55,7 +55,7 @@ var _ = Describe("Test addon rest api", func() { resp := get("/addon_registries") var addonRegistry apisv1.ListAddonRegistryResponse Expect(decodeResponseBody(resp, &addonRegistry)).Should(Succeed()) - Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(2)) + Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(3)) }) It("update an addon registry", func() { @@ -74,8 +74,8 @@ var _ = Describe("Test addon rest api", func() { resp := get("/addon_registries") var addonRegistry apisv1.ListAddonRegistryResponse Expect(decodeResponseBody(resp, &addonRegistry)).Should(Succeed()) - Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(2)) - Expect(addonRegistry.Registries[1].Git.URL).Should(BeEquivalentTo("github.com/another-path")) + Expect(len(addonRegistry.Registries)).Should(BeEquivalentTo(3)) + Expect(addonRegistry.Registries[2].Git.URL).Should(BeEquivalentTo("github.com/another-path")) }) It("delete an addon registry", func() {