diff --git a/apis/types/types.go b/apis/types/types.go index 0a51935dc..2ff2b881f 100644 --- a/apis/types/types.go +++ b/apis/types/types.go @@ -31,6 +31,8 @@ const ( DefaultAppNamespace = "default" // AutoDetectWorkloadDefinition defines the default workload type for ComponentDefinition which doesn't specify a workload AutoDetectWorkloadDefinition = "autodetects.core.oam.dev" + // KubeVelaControllerDeployment defines the KubeVela controller's deployment name + KubeVelaControllerDeployment = "kubevela-vela-core" ) // DefaultKubeVelaNS defines the default KubeVela namespace in Kubernetes diff --git a/pkg/addon/addon.go b/pkg/addon/addon.go index 5129052e3..39e8ae2b3 100644 --- a/pkg/addon/addon.go +++ b/pkg/addon/addon.go @@ -32,8 +32,10 @@ import ( "cuelang.org/go/cue" cueyaml "cuelang.org/go/encoding/yaml" "github.com/google/go-github/v32/github" + "github.com/hashicorp/go-version" "github.com/pkg/errors" "golang.org/x/oauth2" + appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -41,6 +43,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" k8syaml "k8s.io/apimachinery/pkg/runtime/serializer/yaml" types2 "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/discovery" "k8s.io/client-go/rest" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" @@ -60,6 +63,7 @@ import ( "github.com/oam-dev/kubevela/pkg/utils" "github.com/oam-dev/kubevela/pkg/utils/apply" "github.com/oam-dev/kubevela/pkg/utils/common" + version2 "github.com/oam-dev/kubevela/version" ) const ( @@ -865,10 +869,11 @@ type Installer struct { registryMeta map[string]SourceMeta args map[string]interface{} cache *Cache + dc *discovery.DiscoveryClient } // NewAddonInstaller will create an installer for addon -func NewAddonInstaller(ctx context.Context, cli client.Client, apply apply.Applicator, config *rest.Config, r *Registry, args map[string]interface{}, cache *Cache) Installer { +func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r *Registry, args map[string]interface{}, cache *Cache) Installer { return Installer{ ctx: ctx, config: config, @@ -877,12 +882,18 @@ func NewAddonInstaller(ctx context.Context, cli client.Client, apply apply.Appli r: r, args: args, cache: cache, + dc: discoveryClient, } } func (h *Installer) enableAddon(addon *InstallPackage) error { var err error h.addon = addon + err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc) + if err != nil { + return ErrVersionMismatch + } + if err = h.installDependency(addon); err != nil { return err } @@ -1104,3 +1115,99 @@ func FetchAddonRelatedApp(ctx context.Context, cli client.Client, addonName stri } return app, nil } + +// checkAddonVersionMeetRequired will check the version of cli/ux and kubevela-core-controller whether meet the addon requirement, if not will return an error +// please notice that this func is for check production environment which vela cli/ux or vela core is officalVersion +// if version is for test or debug eg: latest/commit-id/branch-name this func will return nil error +func checkAddonVersionMeetRequired(ctx context.Context, require *SystemRequirements, k8sClient client.Client, dc *discovery.DiscoveryClient) error { + if require == nil { + return nil + } + + // if not semver version, bypass check cli/ux. eg: {branch name/git commit id/UNKNOWN} + if version2.IsOfficialKubeVelaVersion(version2.VelaVersion) { + res, err := checkSemVer(version2.VelaVersion, require.VelaVersion) + if err != nil { + return err + } + if !res { + return fmt.Errorf("vela cli/ux version: %s cannot meet requirement", version2.VelaVersion) + } + } + + // check vela core controller version + imageVersion, err := fetchVelaCoreImageTag(ctx, k8sClient) + if err != nil { + return err + } + + // if not semver version, bypass check vela-core. + if version2.IsOfficialKubeVelaVersion(imageVersion) { + res, err := checkSemVer(imageVersion, require.VelaVersion) + if err != nil { + return err + } + if !res { + return fmt.Errorf("the vela core controller: %s cannot meet requirement ", imageVersion) + } + } + + // discovery client is nil so bypass check kubernetes version + if dc == nil { + return nil + } + + k8sVersion, err := dc.ServerVersion() + if err != nil { + return err + } + // if not semver version, bypass check kubernetes version. + if version2.IsOfficialKubeVelaVersion(k8sVersion.GitVersion) { + res, err := checkSemVer(k8sVersion.GitVersion, require.KubernetesVersion) + if err != nil { + return err + } + + if !res { + return fmt.Errorf("the kubernetes version %s cannot meet requirement", k8sVersion.GitVersion) + } + } + + return nil +} + +func checkSemVer(actual string, require string) (bool, error) { + if len(require) == 0 { + return true, nil + } + smeVer := strings.TrimPrefix(actual, "v") + l := strings.ReplaceAll(require, "v", " ") + constraint, err := version.NewConstraint(l) + if err != nil { + return false, err + } + v, err := version.NewVersion(smeVer) + if err != nil { + return false, err + } + return constraint.Check(v), nil +} + +func fetchVelaCoreImageTag(ctx context.Context, k8sClient client.Client) (string, error) { + deploy := &appsv1.Deployment{} + if err := k8sClient.Get(ctx, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: types.KubeVelaControllerDeployment}, deploy); err != nil { + return "", err + } + var tag string + for _, c := range deploy.Spec.Template.Spec.Containers { + if c.Name == types.DefaultKubeVelaReleaseName { + l := strings.Split(c.Image, ":") + if len(l) == 1 { + // if tag is empty mean use latest image + return "latest", nil + } + tag = l[1] + } + } + return tag, nil +} diff --git a/pkg/addon/addon_suite_test.go b/pkg/addon/addon_suite_test.go index 97bcdbdff..396ccedd3 100644 --- a/pkg/addon/addon_suite_test.go +++ b/pkg/addon/addon_suite_test.go @@ -21,6 +21,8 @@ import ( "fmt" "time" + appsv1 "k8s.io/api/apps/v1" + types2 "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -30,6 +32,8 @@ import ( "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/oam/util" ) var _ = Describe("Addon test", func() { @@ -176,6 +180,47 @@ var _ = Describe("Addon test", func() { }) }) +var _ = Describe("Addon func test", func() { + var deploy appsv1.Deployment + + AfterEach(func() { + Expect(k8sClient.Delete(ctx, &deploy)) + }) + + It("fetchVelaCoreImageTag func test", func() { + deploy = appsv1.Deployment{} + tag, err := fetchVelaCoreImageTag(ctx, k8sClient) + Expect(err).Should(util.NotFoundMatcher{}) + Expect(tag).Should(BeEquivalentTo("")) + + Expect(yaml.Unmarshal([]byte(deployYaml), &deploy)).Should(BeNil()) + deploy.SetNamespace(types.DefaultKubeVelaNS) + Expect(k8sClient.Create(ctx, &deploy)).Should(BeNil()) + + Eventually(func() error { + tag, err := fetchVelaCoreImageTag(ctx, k8sClient) + if err != nil { + return err + } + if tag != "v1.2.3" { + return fmt.Errorf("tag missmatch want %s actual %s", "v1.2.3", tag) + } + return err + }, 30*time.Second, 300*time.Millisecond).Should(BeNil()) + }) + + It("checkAddonVersionMeetRequired func test", func() { + deploy = appsv1.Deployment{} + Expect(checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=v1.2.1"}, k8sClient, dc)).Should(util.NotFoundMatcher{}) + Expect(yaml.Unmarshal([]byte(deployYaml), &deploy)).Should(BeNil()) + deploy.SetNamespace(types.DefaultKubeVelaNS) + Expect(k8sClient.Create(ctx, &deploy)).Should(BeNil()) + + Expect(checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=v1.2.1"}, k8sClient, dc)).Should(BeNil()) + Expect(checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=v1.2.4"}, k8sClient, dc)).ShouldNot(BeNil()) + }) +}) + const ( appYaml = `apiVersion: core.oam.dev/v1beta1 kind: Application @@ -201,4 +246,56 @@ spec: image: crccheck/hello-world port: 8000 ` + deployYaml = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: kubevela-vela-core + namespace: vela-system +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/instance: kubevela + app.kubernetes.io/name: vela-core + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + prometheus.io/path: /metrics + prometheus.io/port: "8080" + prometheus.io/scrape: "true" + labels: + app.kubernetes.io/instance: kubevela + app.kubernetes.io/name: vela-core + spec: + containers: + - args: + image: oamdev/vela-core:v1.2.3 + imagePullPolicy: Always + name: kubevela + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + - containerPort: 9440 + name: healthz + protocol: TCP + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 50m + memory: 20Mi + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30` ) diff --git a/pkg/addon/addon_test.go b/pkg/addon/addon_test.go index 1090bea35..1356446f1 100644 --- a/pkg/addon/addon_test.go +++ b/pkg/addon/addon_test.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "encoding/xml" + "fmt" "net/http" "net/http/httptest" "os" @@ -27,6 +28,8 @@ import ( "strings" "testing" + version2 "github.com/oam-dev/kubevela/version" + "github.com/crossplane/crossplane-runtime/pkg/test" "github.com/google/go-github/v32/github" v1alpha12 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1" @@ -651,3 +654,96 @@ func TestGitLabReaderNotPanic(t *testing.T) { _, err := NewAsyncReader("https://gitlab.com/test/catalog", "", "addons", "", gitType) assert.EqualError(t, err, "git type repository only support github for now") } + +func TestCheckSemVer(t *testing.T) { + testCases := []struct { + actual string + require string + nilError bool + res bool + }{ + { + actual: "v1.2.1", + require: "<=v1.2.1", + res: true, + }, + { + actual: "v1.2.1", + require: ">v1.2.1", + res: false, + }, + { + actual: "v1.2.1", + require: "<=v1.2.3", + res: true, + }, + { + actual: "v1.2", + require: "<=v1.2.3", + res: true, + }, + { + actual: "v1.2.1", + require: ">v1.2.3", + res: false, + }, + { + actual: "v1.2.1", + require: "=v1.2.1", + res: true, + }, + { + actual: "1.2.1", + require: "=v1.2.1", + res: true, + }, + { + actual: "1.2.1", + require: "", + res: true, + }, + { + actual: "v1.2.2", + require: "<=v1.2.3, >=v1.2.1", + res: true, + }, + { + actual: "v1.2.0", + require: "v1.2.0, <=v1.2.3", + res: true, + }, + { + actual: "1.2.2", + require: "v1.2.2", + res: true, + }, + { + actual: "1.2.02", + require: "v1.2.2", + res: true, + }, + } + for _, testCase := range testCases { + result, err := checkSemVer(testCase.actual, testCase.require) + assert.NoError(t, err) + assert.Equal(t, result, testCase.res) + } +} + +func TestCheckAddonVersionMeetRequired(t *testing.T) { + k8sClient := &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + return nil + }), + } + ctx := context.Background() + assert.NoError(t, checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=1.2.4"}, k8sClient, nil)) + + version2.VelaVersion = "v1.2.3" + if err := checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=1.2.4"}, k8sClient, nil); err == nil { + assert.Error(t, fmt.Errorf("should meet error")) + } + + version2.VelaVersion = "v1.2.4" + assert.NoError(t, checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=1.2.4"}, k8sClient, nil)) +} diff --git a/pkg/addon/error.go b/pkg/addon/error.go index 865f523e0..f72426326 100644 --- a/pkg/addon/error.go +++ b/pkg/addon/error.go @@ -35,6 +35,9 @@ var ( // ErrNotExist means addon not exists ErrNotExist = NewAddonError("addon not exist") + + // ErrVersionMismatch means addon version requirement mismatch + ErrVersionMismatch = NewAddonError("addon version requirements mismatch") ) // WrapErrRateLimit return ErrRateLimit if is the situation, or return error directly diff --git a/pkg/addon/helper.go b/pkg/addon/helper.go index 30132de0a..65f00673e 100644 --- a/pkg/addon/helper.go +++ b/pkg/addon/helper.go @@ -21,6 +21,8 @@ import ( "encoding/json" "fmt" + "k8s.io/client-go/discovery" + "k8s.io/klog/v2" v1 "k8s.io/api/core/v1" @@ -49,8 +51,8 @@ const ( ) // EnableAddon will enable addon with dependency check, source is where addon from. -func EnableAddon(ctx context.Context, name string, cli client.Client, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache) error { - h := NewAddonInstaller(ctx, cli, apply, config, &r, args, cache) +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 { + h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache) pkg, err := h.loadInstallPackage(name) if err != nil { return err @@ -76,7 +78,7 @@ func DisableAddon(ctx context.Context, cli client.Client, name string) error { } // EnableAddonByLocalDir enable an addon from local dir -func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli client.Client, applicator apply.Applicator, config *rest.Config, args map[string]interface{}) error { +func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli client.Client, dc *discovery.DiscoveryClient, applicator apply.Applicator, config *rest.Config, args map[string]interface{}) error { r := localReader{dir: dir, name: name} metas, err := r.ListAddonMeta() if err != nil { @@ -91,7 +93,7 @@ func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli cli if err != nil { return err } - h := NewAddonInstaller(ctx, cli, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil) + h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil) needEnableAddonNames, err := h.checkDependency(pkg) if err != nil { return err diff --git a/pkg/addon/suite_test.go b/pkg/addon/suite_test.go index fb89edf9c..5ad2fa307 100644 --- a/pkg/addon/suite_test.go +++ b/pkg/addon/suite_test.go @@ -21,6 +21,8 @@ import ( "testing" "time" + "k8s.io/client-go/discovery" + v12 "k8s.io/api/core/v1" crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -49,6 +51,7 @@ var testEnv *envtest.Environment var dm discoverymapper.DiscoveryMapper var pd *packages.PackageDiscover var testns string +var dc *discovery.DiscoveryClient func TestAddon(t *testing.T) { RegisterFailHandler(Fail) @@ -79,6 +82,11 @@ var _ = BeforeSuite(func(done Done) { k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) Expect(err).ToNot(HaveOccurred()) Expect(k8sClient).ToNot(BeNil()) + + dc, err = discovery.NewDiscoveryClientForConfig(cfg) + Expect(err).ToNot(HaveOccurred()) + Expect(dc).ShouldNot(BeNil()) + dm, err = discoverymapper.New(cfg) Expect(err).ToNot(HaveOccurred()) Expect(dm).ToNot(BeNil()) diff --git a/pkg/addon/type.go b/pkg/addon/type.go index 944c06686..7fbc93efe 100644 --- a/pkg/addon/type.go +++ b/pkg/addon/type.go @@ -54,16 +54,17 @@ type InstallPackage struct { // Meta defines the format for a single addon type Meta struct { - Name string `json:"name" validate:"required"` - Version string `json:"version"` - Description string `json:"description"` - Icon string `json:"icon"` - URL string `json:"url,omitempty"` - Tags []string `json:"tags,omitempty"` - DeployTo *DeployTo `json:"deployTo,omitempty"` - Dependencies []*Dependency `json:"dependencies,omitempty"` - NeedNamespace []string `json:"needNamespace,omitempty"` - Invisible bool `json:"invisible"` + Name string `json:"name" validate:"required"` + Version string `json:"version"` + Description string `json:"description"` + Icon string `json:"icon"` + URL string `json:"url,omitempty"` + Tags []string `json:"tags,omitempty"` + DeployTo *DeployTo `json:"deployTo,omitempty"` + Dependencies []*Dependency `json:"dependencies,omitempty"` + NeedNamespace []string `json:"needNamespace,omitempty"` + Invisible bool `json:"invisible"` + SystemRequirements *SystemRequirements `json:"system,omitempty"` } // DeployTo defines where the addon to deploy to @@ -84,3 +85,9 @@ type ElementFile struct { Data string Name string } + +// SystemRequirements is this addon need version +type SystemRequirements struct { + VelaVersion string `json:"vela,omitempty"` + KubernetesVersion string `json:"kubernetes,omitempty"` +} diff --git a/pkg/apiserver/clients/kubeclient.go b/pkg/apiserver/clients/kubeclient.go index 0f880b7b3..e43e0dc17 100644 --- a/pkg/apiserver/clients/kubeclient.go +++ b/pkg/apiserver/clients/kubeclient.go @@ -17,6 +17,7 @@ limitations under the License. package clients import ( + "k8s.io/client-go/discovery" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -84,3 +85,16 @@ func GetPackageDiscover() (*packages.PackageDiscover, error) { } return pd, nil } + +// GetDiscoveryClient return a discovery client +func GetDiscoveryClient() (*discovery.DiscoveryClient, error) { + conf, err := GetKubeConfig() + if err != nil { + return nil, err + } + dc, err := discovery.NewDiscoveryClientForConfig(conf) + if err != nil { + return nil, err + } + return dc, nil +} diff --git a/pkg/apiserver/rest/usecase/addon.go b/pkg/apiserver/rest/usecase/addon.go index 07984b435..03c212c29 100644 --- a/pkg/apiserver/rest/usecase/addon.go +++ b/pkg/apiserver/rest/usecase/addon.go @@ -26,6 +26,8 @@ import ( "sync" "time" + "k8s.io/client-go/discovery" + k8stypes "k8s.io/apimachinery/pkg/types" v1 "k8s.io/api/core/v1" @@ -101,6 +103,10 @@ func NewAddonUsecase(cacheTime time.Duration) AddonHandler { if err != nil { panic(err) } + dc, err := clients.GetDiscoveryClient() + if err != nil { + panic(err) + } ds := pkgaddon.NewRegistryDataStore(kubecli) cache := pkgaddon.NewCache(ds) @@ -114,6 +120,7 @@ func NewAddonUsecase(cacheTime time.Duration) AddonHandler { config: config, apply: apply.NewAPIApplicator(kubecli), mutex: new(sync.RWMutex), + discoveryClient: dc, } } @@ -123,6 +130,7 @@ type defaultAddonHandler struct { kubeClient client.Client config *rest.Config apply apply.Applicator + discoveryClient *discovery.DiscoveryClient mutex *sync.RWMutex } @@ -352,15 +360,23 @@ func (u *defaultAddonHandler) EnableAddon(ctx context.Context, name string, args return err } for _, r := range registries { - err = pkgaddon.EnableAddon(ctx, name, u.kubeClient, u.apply, u.config, r, args.Args, u.addonRegistryCache) + err = pkgaddon.EnableAddon(ctx, name, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache) if err == nil { return nil } - if err != nil && errors.As(err, &pkgaddon.ErrNotExist) { + // if reach this line error must is not nil + if errors.Is(err, pkgaddon.ErrNotExist) { // one registry return addon not exist error, should not break other registry func continue } + + // wrap this error with special bcode + if errors.Is(err, pkgaddon.ErrVersionMismatch) { + return bcode.ErrAddonSystemVersionMismatch + } + // except `addon not found`, other errors should return directly + return err } return bcode.ErrAddonNotExist } @@ -412,13 +428,21 @@ func (u *defaultAddonHandler) UpdateAddon(ctx context.Context, name string, args } for _, r := range registries { - err = pkgaddon.EnableAddon(ctx, name, u.kubeClient, u.apply, u.config, r, args.Args, u.addonRegistryCache) + err = pkgaddon.EnableAddon(ctx, name, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache) if err == nil { return nil } - if err != nil && !errors.Is(err, pkgaddon.ErrNotExist) { - return bcode.WrapGithubRateLimitErr(err) + + if errors.Is(err, pkgaddon.ErrNotExist) { + continue } + + // wrap this error with special bcode + if errors.Is(err, pkgaddon.ErrVersionMismatch) { + return bcode.ErrAddonSystemVersionMismatch + } + // except `addon not found`, other errors should return directly + return err } return bcode.ErrAddonNotExist } diff --git a/pkg/apiserver/rest/utils/bcode/005_addon.go b/pkg/apiserver/rest/utils/bcode/005_addon.go index c23b39550..e890015b3 100644 --- a/pkg/apiserver/rest/utils/bcode/005_addon.go +++ b/pkg/apiserver/rest/utils/bcode/005_addon.go @@ -61,6 +61,9 @@ var ( // ErrAddonDependencyNotSatisfy means addon's dependencies is not enabled ErrAddonDependencyNotSatisfy = NewBcode(500, 50017, "addon's dependencies is not enabled") + + // ErrAddonSystemVersionMismatch means addon's version required mismatch + ErrAddonSystemVersionMismatch = NewBcode(400, 50018, "addon's system version requirement mismatch") ) // isGithubRateLimit check if error is github rate limit diff --git a/pkg/utils/common/args.go b/pkg/utils/common/args.go index 02d0e7b0e..c370d148e 100644 --- a/pkg/utils/common/args.go +++ b/pkg/utils/common/args.go @@ -19,6 +19,8 @@ package common import ( "fmt" + "k8s.io/client-go/discovery" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/util/flowcontrol" @@ -36,6 +38,7 @@ type Args struct { client client.Client dm discoverymapper.DiscoveryMapper pd *packages.PackageDiscover + dc *discovery.DiscoveryClient } // SetConfig insert kubeconfig into Args @@ -122,3 +125,20 @@ func (a *Args) GetPackageDiscover() (*packages.PackageDiscover, error) { a.pd = pd return pd, nil } + +// GetDiscoveryClient return a discovery client from cli args +func (a *Args) GetDiscoveryClient() (*discovery.DiscoveryClient, error) { + if a.dc != nil { + return a.dc, nil + } + cfg, err := a.GetConfig() + if err != nil { + return nil, err + } + + dc, err := discovery.NewDiscoveryClientForConfig(cfg) + if err != nil { + return nil, err + } + return dc, nil +} diff --git a/references/cli/addon.go b/references/cli/addon.go index 25a51035b..ff8c7dc2e 100644 --- a/references/cli/addon.go +++ b/references/cli/addon.go @@ -24,6 +24,8 @@ import ( "strings" "time" + "k8s.io/client-go/discovery" + "helm.sh/helm/v3/pkg/strvals" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" @@ -134,6 +136,10 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com if err != nil { return err } + dc, err := c.GetDiscoveryClient() + if err != nil { + return err + } addonOrDir := args[0] var name = addonOrDir if file, err := os.Stat(addonOrDir); err == nil { @@ -143,7 +149,7 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com ioStream.Infof("enable addon by local dir: %s \n", addonOrDir) // args[0] is a local path install with local dir, use base dir name as addonName name = filepath.Base(addonOrDir) - err = enableAddonByLocal(ctx, name, addonOrDir, k8sClient, config, addonArgs) + err = enableAddonByLocal(ctx, name, addonOrDir, k8sClient, dc, config, addonArgs) if err != nil { return err } @@ -152,7 +158,7 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com return fmt.Errorf("addon directory %s not found in local", addonOrDir) } - err = enableAddon(ctx, k8sClient, config, name, addonArgs) + err = enableAddon(ctx, k8sClient, dc, config, name, addonArgs) if err != nil { return err } @@ -204,6 +210,10 @@ func NewAddonUpgradeCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Co if err != nil { return err } + dc, err := c.GetDiscoveryClient() + if err != nil { + return err + } addonArgs, err := parseToMap(args[1:]) if err != nil { return err @@ -221,7 +231,7 @@ func NewAddonUpgradeCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Co if err != nil { return errors.Wrapf(err, "cannot fetch addon related addon %s", name) } - err = enableAddonByLocal(ctx, name, addonOrDir, k8sClient, config, addonArgs) + err = enableAddonByLocal(ctx, name, addonOrDir, k8sClient, dc, config, addonArgs) if err != nil { return err } @@ -234,7 +244,7 @@ func NewAddonUpgradeCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Co if err != nil { return errors.Wrapf(err, "cannot fetch addon related addon %s", addonOrDir) } - err = enableAddon(ctx, k8sClient, config, addonOrDir, addonArgs) + err = enableAddon(ctx, k8sClient, dc, config, addonOrDir, addonArgs) if err != nil { return err } @@ -305,7 +315,7 @@ func NewAddonStatusCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com } } -func enableAddon(ctx context.Context, k8sClient client.Client, 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, args map[string]interface{}) error { var err error registryDS := pkgaddon.NewRegistryDataStore(k8sClient) registries, err := registryDS.ListRegistries(ctx) @@ -314,7 +324,7 @@ func enableAddon(ctx context.Context, k8sClient client.Client, config *rest.Conf } for _, registry := range registries { - err = pkgaddon.EnableAddon(ctx, name, k8sClient, apply.NewAPIApplicator(k8sClient), config, registry, args, nil) + err = pkgaddon.EnableAddon(ctx, name, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil) if errors.Is(err, pkgaddon.ErrNotExist) { continue } @@ -330,8 +340,8 @@ func enableAddon(ctx context.Context, k8sClient client.Client, config *rest.Conf } // enableAddonByLocal enable addon in local dir and return the addon name -func enableAddonByLocal(ctx context.Context, name string, dir string, k8sClient client.Client, config *rest.Config, args map[string]interface{}) error { - if err := pkgaddon.EnableAddonByLocalDir(ctx, name, dir, k8sClient, apply.NewAPIApplicator(k8sClient), config, args); err != nil { +func enableAddonByLocal(ctx context.Context, name string, dir string, k8sClient client.Client, dc *discovery.DiscoveryClient, config *rest.Config, args map[string]interface{}) error { + if err := pkgaddon.EnableAddonByLocalDir(ctx, name, dir, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, args); err != nil { return err } if err := waitApplicationRunning(k8sClient, name); err != nil {