From dcb5378ff92c2cfaafbd0f11f29a5073ee77469e Mon Sep 17 00:00:00 2001 From: Jianbo Sun Date: Fri, 16 Dec 2022 18:07:27 +0800 Subject: [PATCH] Feat: support NOTES.cue in addon for additional info printer (#5195) Signed-off-by: Jianbo Sun Signed-off-by: Jianbo Sun --- pkg/addon/addon.go | 121 +++++++++++++++++--- pkg/addon/addon_suite_test.go | 43 ++++++- pkg/addon/addon_test.go | 3 +- pkg/addon/helper.go | 31 ++--- pkg/addon/render.go | 16 ++- pkg/addon/testdata/test-notes/NOTES.cue | 13 +++ pkg/addon/testdata/test-notes/metadata.yaml | 15 +++ pkg/addon/type.go | 1 + pkg/apiserver/domain/service/addon.go | 6 +- references/cli/addon.go | 75 +++++++----- 10 files changed, 246 insertions(+), 78 deletions(-) create mode 100644 pkg/addon/testdata/test-notes/NOTES.cue create mode 100644 pkg/addon/testdata/test-notes/metadata.yaml diff --git a/pkg/addon/addon.go b/pkg/addon/addon.go index fa6ea3d8c..2a0001387 100644 --- a/pkg/addon/addon.go +++ b/pkg/addon/addon.go @@ -35,6 +35,7 @@ import ( "github.com/google/go-github/v32/github" "github.com/imdario/mergo" prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1" + "github.com/kubevela/workflow/pkg/cue/model/value" "github.com/pkg/errors" "github.com/xanzy/go-gitlab" "golang.org/x/oauth2" @@ -88,6 +89,12 @@ const ( // AppTemplateCueFileName is the addon application template.cue file name AppTemplateCueFileName string = "template.cue" + // NotesCUEFileName is the addon notes print to end users when installed + NotesCUEFileName string = "NOTES.cue" + + // KeyWordNotes is the keyword in NOTES.cue which will render the notes message out. + KeyWordNotes string = "notes" + // GlobalParameterFileName is the addon global parameter.cue file name GlobalParameterFileName string = "parameter.cue" @@ -111,6 +118,9 @@ const ( // DefaultGiteeURL is the addon repository of gitee api DefaultGiteeURL string = "https://gitee.com/api/v5/" + + // InstallerRuntimeOption inject install runtime info into addon options + InstallerRuntimeOption string = "installerRuntimeOption" ) // ParameterFileName is the addon resources/parameter.cue file name @@ -153,10 +163,17 @@ type Pattern struct { // Patterns is the file pattern that the addon should be in var Patterns = []Pattern{ + // config-templates pattern {IsDir: true, Value: ConfigTemplateDirName}, + // single file reader pattern {Value: ReadmeFileName}, {Value: MetadataFileName}, {Value: TemplateFileName}, - {Value: ParameterFileName}, {IsDir: true, Value: ResourcesDirName}, {IsDir: true, Value: DefinitionsDirName}, - {IsDir: true, Value: DefSchemaName}, {IsDir: true, Value: ViewDirName}, {Value: AppTemplateCueFileName}, {Value: GlobalParameterFileName}, {Value: LegacyReadmeFileName}} + // parameter in resource directory + {Value: ParameterFileName}, + // directory files + {IsDir: true, Value: ResourcesDirName}, {IsDir: true, Value: DefinitionsDirName}, {IsDir: true, Value: DefSchemaName}, {IsDir: true, Value: ViewDirName}, + // CUE app template, parameter and notes + {Value: AppTemplateCueFileName}, {Value: GlobalParameterFileName}, {Value: NotesCUEFileName}, + {Value: LegacyReadmeFileName}} // GetPatternFromItem will check if the file path has a valid pattern, return empty string if it's invalid. // AsyncReader is needed to calculate relative path @@ -282,6 +299,7 @@ func GetInstallPackageFromReader(r AsyncReader, meta *SourceMeta, uiData *UIData DefSchemaName: readDefSchemaFile, ViewDirName: readViewFile, AppTemplateCueFileName: readAppCueTemplate, + NotesCUEFileName: readNotesFile, } ptItems := ClassifyItemByPattern(meta, r) @@ -342,6 +360,16 @@ func readParamFile(a *UIData, reader AsyncReader, readPath string) error { return nil } +// readNotesFile read single NOTES.cue file +func readNotesFile(a *InstallPackage, reader AsyncReader, readPath string) error { + data, err := reader.ReadFile(readPath) + if err != nil { + return err + } + a.Notes = ElementFile{Data: data, Name: filepath.Base(readPath)} + return nil +} + // readGlobalParamFile read global parameter file. func readGlobalParamFile(a *UIData, reader AsyncReader, readPath string) error { b, err := reader.ReadFile(readPath) @@ -842,11 +870,16 @@ type Installer struct { dryRun bool dryRunBuff *bytes.Buffer + installerRuntime map[string]interface{} + registries []Registry } // NewAddonInstaller will create an installer for addon 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, registries []Registry, opts ...InstallOption) Installer { + if args == nil { + args = map[string]interface{}{} + } i := Installer{ ctx: ctx, config: config, @@ -859,13 +892,22 @@ func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient * dryRunBuff: &bytes.Buffer{}, registries: registries, } + ir := args[InstallerRuntimeOption] + if irr, ok := ir.(map[string]interface{}); ok { + i.installerRuntime = irr + } else { + i.installerRuntime = map[string]interface{}{} + } + // clean injected data from runtime option + delete(args, InstallerRuntimeOption) + for _, opt := range opts { opt(&i) } return i } -func (h *Installer) enableAddon(addon *InstallPackage) error { +func (h *Installer) enableAddon(addon *InstallPackage) (string, error) { var err error h.addon = addon @@ -873,22 +915,28 @@ func (h *Installer) enableAddon(addon *InstallPackage) error { err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc) if err != nil { version := h.getAddonVersionMeetSystemRequirement(addon.Name) - return VersionUnMatchError{addonName: addon.Name, err: err, userSelectedAddonVersion: addon.Version, availableVersion: version} + return "", VersionUnMatchError{addonName: addon.Name, err: err, userSelectedAddonVersion: addon.Version, availableVersion: version} } } if err = h.installDependency(addon); err != nil { - return err + return "", err } if err = h.dispatchAddonResource(addon); err != nil { - return err + return "", err } // we shouldn't put continue func into dispatchAddonResource, because the re-apply app maybe already update app and // the suspend will set with false automatically if err := h.continueOrRestartWorkflow(); err != nil { - return err + return "", err } - return nil + additionalInfo, err := h.renderNotes(addon) + if err != nil { + klog.Warningf("fail to render notes for addon %s: %v\n", addon.Name, err) + // notes don't affect the installation, so just print warn logs instead of abort with errors + return "", nil + } + return additionalInfo, nil } func (h *Installer) loadInstallPackage(name, version string) (*InstallPackage, error) { @@ -960,9 +1008,13 @@ func (h *Installer) installDependency(addon *InstallPackage) error { // try to install the dependent addon from the same registry with the current addon depAddon, err = h.loadInstallPackage(dep.Name, dep.Version) if err == nil { - if err = depHandler.enableAddon(depAddon); err != nil { + additionalInfo, err := depHandler.enableAddon(depAddon) + if err != nil { return errors.Wrap(err, "fail to dispatch dependent addon resource") } + if len(additionalInfo) > 0 { + klog.Infof("addon %s installed with additional info: %s\n", addon.Name, additionalInfo) + } return nil } if !errors.Is(err, ErrNotExist) { @@ -983,9 +1035,13 @@ func (h *Installer) installDependency(addon *InstallPackage) error { return err } if err == nil { - if err = depHandler.enableAddon(depAddon); err != nil { + additionalInfo, err := depHandler.enableAddon(depAddon) + if err != nil { return errors.Wrap(err, "fail to dispatch dependent addon resource") } + if len(additionalInfo) > 0 { + klog.Infof("addon %s installed with additional info: %s\n", addon.Name, additionalInfo) + } return nil } return fmt.Errorf("dependency addon: %s with version: %s cannot be found from all registries", dep.Name, dep.Version) @@ -1016,14 +1072,16 @@ func (h *Installer) checkDependency(addon *InstallPackage) ([]string, error) { } return needEnable, nil } -func (h *Installer) createOrUpdate(app *v1beta1.Application) error { + +// createOrUpdate will return true if updated +func (h *Installer) createOrUpdate(app *v1beta1.Application) (bool, error) { var getapp v1beta1.Application err := h.cli.Get(h.ctx, client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, &getapp) if apierrors.IsNotFound(err) { - return h.cli.Create(h.ctx, app) + return false, h.cli.Create(h.ctx, app) } if err != nil { - return err + return false, err } getapp.Spec = app.Spec getapp.Labels = app.Labels @@ -1031,10 +1089,10 @@ func (h *Installer) createOrUpdate(app *v1beta1.Application) error { err = h.cli.Update(h.ctx, &getapp) if err != nil { klog.Errorf("fail to create application: %v", err) - return errors.Wrap(err, "fail to create application") + return false, errors.Wrap(err, "fail to create application") } getapp.DeepCopyInto(app) - return nil + return true, nil } func (h *Installer) dispatchAddonResource(addon *InstallPackage) error { @@ -1096,10 +1154,13 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error { h.dryRunBuff.Write(result) h.dryRunBuff.WriteString("\n") } else { - err = h.createOrUpdate(app) + updated, err := h.createOrUpdate(app) if err != nil { return err } + if updated { + h.installerRuntime["upgrade"] = true + } } auxiliaryOutputs = append(auxiliaryOutputs, defs...) @@ -1146,6 +1207,34 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error { return nil } +func (h *Installer) renderNotes(addon *InstallPackage) (string, error) { + r := addonCueTemplateRender{ + addon: addon, + inputArgs: h.args, + contextInfo: map[string]interface{}{ + "installer": h.installerRuntime, + }, + } + contextFile, err := r.formatContext() + if err != nil { + return "", err + } + notesFile := addon.Notes.Data + "\n" + contextFile + val, err := value.NewValue(notesFile, nil, "") + if err != nil { + return "", errors.Wrap(err, "build values for NOTES.cue") + } + notes, err := val.LookupValue(KeyWordNotes) + if err != nil { + return "", errors.Wrap(err, "look up notes in NOTES.cue") + } + notesStr, err := notes.CueValue().String() + if err != nil { + return "", errors.Wrap(err, "convert notes to string") + } + return notesStr, nil +} + // this func will handle such two case // 1. if last apply failed an workflow have suspend, this func will continue the workflow // 2. restart the workflow, if the new cluster have been added in KubeVela diff --git a/pkg/addon/addon_suite_test.go b/pkg/addon/addon_suite_test.go index 33fe8e1ee..10c9dc623 100644 --- a/pkg/addon/addon_suite_test.go +++ b/pkg/addon/addon_suite_test.go @@ -382,7 +382,7 @@ var _ = Describe("test enable addon in local dir", func() { It("test enable addon by local dir", func() { ctx := context.Background() - err := EnableAddonByLocalDir(ctx, "example", "./testdata/example", k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, map[string]interface{}{"example": "test"}) + _, err := EnableAddonByLocalDir(ctx, "example", "./testdata/example", k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, map[string]interface{}{"example": "test"}) Expect(err).Should(BeNil()) app := v1beta1.Application{} Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: "vela-system", Name: "addon-example"}, &app)).Should(BeNil()) @@ -420,7 +420,7 @@ var _ = Describe("test dry-run addon from local dir", func() { h := NewAddonInstaller(ctx, k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, &Registry{Name: LocalAddonRegistryName}, map[string]interface{}{"example": "test-dry-run"}, nil, nil, DryRunAddon) - err = h.enableAddon(pkg) + _, err = h.enableAddon(pkg) Expect(err).Should(BeNil()) decoder := yaml3.NewDecoder(h.dryRunBuff) @@ -450,7 +450,7 @@ var _ = Describe("test enable addon which applies the views independently", func It("test enable addon which applies the views independently", func() { ctx := context.Background() - err := EnableAddonByLocalDir(ctx, "test-view", "./testdata/test-view", k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, map[string]interface{}{"example": "test"}) + _, err := EnableAddonByLocalDir(ctx, "test-view", "./testdata/test-view", k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, map[string]interface{}{"example": "test"}) Expect(err).Should(BeNil()) app := v1beta1.Application{} Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: "vela-system", Name: "addon-test-view"}, &app)).Should(BeNil()) @@ -459,6 +459,43 @@ var _ = Describe("test enable addon which applies the views independently", func }) }) +var _ = Describe("test enable addon with notes", func() { + BeforeEach(func() { + app := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-test-notes"}} + Expect(k8sClient.Delete(ctx, &app)).Should(SatisfyAny(BeNil(), util.NotFoundMatcher{})) + sec := v1.Secret{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-secret-test-notes"}} + Expect(k8sClient.Delete(ctx, &sec)).Should(SatisfyAny(BeNil(), util.NotFoundMatcher{})) + }) + + It("test 'enable' addon which render notes output", func() { + ctx := context.Background() + addonInputArgs := map[string]interface{}{"example": "test"} + // inject runtime info + addonInputArgs[InstallerRuntimeOption] = map[string]interface{}{ + "upgrade": false, + } + notes, err := EnableAddonByLocalDir(ctx, "test-notes", "./testdata/test-notes", k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, addonInputArgs) + Expect(err).Should(BeNil()) + app := v1beta1.Application{} + Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: "vela-system", Name: "addon-test-notes"}, &app)).Should(BeNil()) + Expect(notes).Should(ContainSubstring(`Thank you for your first installation! +Please refer to URL.`)) + }) + + It("test 'upgrade' addon which render notes output", func() { + ctx := context.Background() + addonInputArgs := map[string]interface{}{"example": "test"} + // inject runtime info + addonInputArgs[InstallerRuntimeOption] = map[string]interface{}{ + "upgrade": true, + } + notes, err := EnableAddonByLocalDir(ctx, "test-notes-upgrade", "./testdata/test-notes", k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, addonInputArgs) + Expect(err).Should(BeNil()) + Expect(notes).Should(ContainSubstring(`Thank you for your upgrade! +Please refer to URL.`)) + }) +}) + var _ = Describe("test override defs of addon", func() { It("test compDef exist", func() { ctx := context.Background() diff --git a/pkg/addon/addon_test.go b/pkg/addon/addon_test.go index 2450174ba..e0bc7a54a 100644 --- a/pkg/addon/addon_test.go +++ b/pkg/addon/addon_test.go @@ -1057,7 +1057,7 @@ func TestCheckEnableAddonErrorWhenMissMatch(t *testing.T) { version2.VelaVersion = "v1.3.0" i := InstallPackage{Meta: Meta{SystemRequirements: &SystemRequirements{VelaVersion: ">=1.4.0"}}} installer := &Installer{} - err := installer.enableAddon(&i) + _, err := installer.enableAddon(&i) assert.Equal(t, errors.As(err, &VersionUnMatchError{}), true) } @@ -1082,7 +1082,6 @@ func TestPackageAddon(t *testing.T) { archiver, err = PackageAddon(invalidAddonMetadata) assert.NotNil(t, err) assert.Equal(t, "", archiver) - } func TestGenerateAnnotation(t *testing.T) { diff --git a/pkg/addon/helper.go b/pkg/addon/helper.go index caf6b8426..b6a906f21 100644 --- a/pkg/addon/helper.go +++ b/pkg/addon/helper.go @@ -52,17 +52,13 @@ const ( ) // EnableAddon will enable addon with dependency check, source is where addon from. -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, registries []Registry, opts ...InstallOption) 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, registries []Registry, opts ...InstallOption) (string, error) { h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache, registries, opts...) pkg, err := h.loadInstallPackage(name, version) if err != nil { - return err + return "", err } - err = h.enableAddon(pkg) - if err != nil { - return err - } - return nil + return h.enableAddon(pkg) } // DisableAddon will disable addon from cluster. @@ -91,39 +87,34 @@ func DisableAddon(ctx context.Context, cli client.Client, name string, config *r } // EnableAddonByLocalDir enable an addon from local dir -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{}, opts ...InstallOption) 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{}, opts ...InstallOption) (string, error) { absDir, err := filepath.Abs(dir) if err != nil { - return err + return "", err } r := localReader{dir: absDir, name: name} metas, err := r.ListAddonMeta() if err != nil { - return err + return "", err } meta := metas[r.name] UIData, err := GetUIDataFromReader(r, &meta, UIMetaOptions) if err != nil { - return err + return "", err } pkg, err := GetInstallPackageFromReader(r, &meta, UIData) if err != nil { - return err + return "", err } h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil, nil, opts...) needEnableAddonNames, err := h.checkDependency(pkg) if err != nil { - return err + return "", err } if len(needEnableAddonNames) > 0 { - return fmt.Errorf("you must first enable dependencies: %v", needEnableAddonNames) + return "", fmt.Errorf("you must first enable dependencies: %v", needEnableAddonNames) } - - err = h.enableAddon(pkg) - if err != nil { - return err - } - return nil + return h.enableAddon(pkg) } // GetAddonStatus is general func for cli and apiServer get addon status diff --git a/pkg/addon/render.go b/pkg/addon/render.go index 8250dcf59..d1f575292 100644 --- a/pkg/addon/render.go +++ b/pkg/addon/render.go @@ -60,8 +60,9 @@ const ( ) type addonCueTemplateRender struct { - addon *InstallPackage - inputArgs map[string]interface{} + addon *InstallPackage + inputArgs map[string]interface{} + contextInfo map[string]interface{} } func (a addonCueTemplateRender) formatContext() (string, error) { @@ -69,6 +70,10 @@ func (a addonCueTemplateRender) formatContext() (string, error) { if args == nil { args = map[string]interface{}{} } + contextInfo := a.contextInfo + if contextInfo == nil { + contextInfo = map[string]interface{}{} + } bt, err := json.Marshal(args) if err != nil { return "", err @@ -80,12 +85,13 @@ func (a addonCueTemplateRender) formatContext() (string, error) { // in case the user defined data has packages contextFile.WriteString(a.addon.Parameters + "\n") - // addon metadata context - metadataJSON, err := json.Marshal(a.addon.Meta) + // add metadata of addon into context + contextInfo["metadata"] = a.addon.Meta + contextJSON, err := json.Marshal(contextInfo) if err != nil { return "", err } - contextFile.WriteString(fmt.Sprintf("context: metadata: %s\n", string(metadataJSON))) + contextFile.WriteString(fmt.Sprintf("context: %s\n", string(contextJSON))) // parameter definition contextFile.WriteString(paramFile + "\n") diff --git a/pkg/addon/testdata/test-notes/NOTES.cue b/pkg/addon/testdata/test-notes/NOTES.cue new file mode 100644 index 000000000..f143908d8 --- /dev/null +++ b/pkg/addon/testdata/test-notes/NOTES.cue @@ -0,0 +1,13 @@ +info: string + +if !context.installer.upgrade { + info: "first installation!" +} +if context.installer.upgrade { + info: "upgrade!" +} + +notes: "Thank you for your " + """ +\(info) +Please refer to URL. +""" diff --git a/pkg/addon/testdata/test-notes/metadata.yaml b/pkg/addon/testdata/test-notes/metadata.yaml new file mode 100644 index 000000000..53fe51f4c --- /dev/null +++ b/pkg/addon/testdata/test-notes/metadata.yaml @@ -0,0 +1,15 @@ +name: test-notes +version: 1.0.0 +description: test +icon: https://www.terraform.io/assets/images/logo-text-8c3ba8a6.svg +url: https://terraform.io/ + +tags: [] + +deployTo: + controlPlane: true + runtimeCluster: false + +dependencies: [] + +invisible: false \ No newline at end of file diff --git a/pkg/addon/type.go b/pkg/addon/type.go index 1633cd43c..f65df049b 100644 --- a/pkg/addon/type.go +++ b/pkg/addon/type.go @@ -66,6 +66,7 @@ type InstallPackage struct { YAMLTemplates []ElementFile `json:"YAMLTemplates,omitempty"` AppTemplate *v1beta1.Application `json:"appTemplate"` AppCueTemplate ElementFile `json:"appCueTemplate,omitempty"` + Notes ElementFile `json:"notes,omitempty"` } // WholeAddonPackage contains all infos of an addon diff --git a/pkg/apiserver/domain/service/addon.go b/pkg/apiserver/domain/service/addon.go index 4e1f4f589..b91c0dd66 100644 --- a/pkg/apiserver/domain/service/addon.go +++ b/pkg/apiserver/domain/service/addon.go @@ -415,7 +415,8 @@ func (u *addonServiceImpl) EnableAddon(ctx context.Context, name string, args ap if len(args.RegistryName) != 0 && args.RegistryName != r.Name { continue } - err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries)) + // TODO: response the additional info to velaux users + _, err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries)) if err == nil { return nil } @@ -486,7 +487,8 @@ func (u *addonServiceImpl) UpdateAddon(ctx context.Context, name string, args ap } for i, r := range registries { - err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries)) + // TODO: response the additional info to velaux users + _, err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries)) if err == nil { return nil } diff --git a/references/cli/addon.go b/references/cli/addon.go index 112c10a23..b003242d3 100644 --- a/references/cli/addon.go +++ b/references/cli/addon.go @@ -150,7 +150,7 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com vela addon enable / `, RunE: func(cmd *cobra.Command, args []string) error { - + var additionalInfo string if len(args) < 1 { return fmt.Errorf("must specify addon name") } @@ -176,6 +176,11 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com } addonOrDir := args[0] var name = addonOrDir + // inject runtime info + addonArgs[pkgaddon.InstallerRuntimeOption] = map[string]interface{}{ + "upgrade": false, + } + if file, err := os.Stat(addonOrDir); err == nil { if !file.IsDir() { return fmt.Errorf("%s is not addon dir", addonOrDir) @@ -192,7 +197,7 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com return err } } - err = enableAddonByLocal(ctx, name, addonOrDir, k8sClient, dc, config, addonArgs) + additionalInfo, err = enableAddonByLocal(ctx, name, addonOrDir, k8sClient, dc, config, addonArgs) if err != nil { return err } @@ -205,7 +210,7 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com return err } } - err = enableAddon(ctx, k8sClient, dc, config, name, addonVersion, addonArgs) + additionalInfo, err = enableAddon(ctx, k8sClient, dc, config, name, addonVersion, addonArgs) if err != nil { return err } @@ -214,7 +219,7 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com return nil } fmt.Printf("Addon %s enabled successfully.\n", name) - AdditionalEndpointPrinter(ctx, c, k8sClient, name, false) + AdditionalEndpointPrinter(ctx, c, k8sClient, name, additionalInfo, false) return nil }, } @@ -229,7 +234,7 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com } // AdditionalEndpointPrinter will print endpoints -func AdditionalEndpointPrinter(ctx context.Context, c common.Args, k8sClient client.Client, name string, isUpgrade bool) { +func AdditionalEndpointPrinter(ctx context.Context, c common.Args, k8sClient client.Client, name, info string, isUpgrade bool) { err := printAppEndpoints(ctx, addonutil.Addon2AppName(name), types.DefaultKubeVelaNS, Filter{}, c, true) if err != nil { fmt.Println("Get application endpoints error:", err) @@ -247,6 +252,9 @@ func AdditionalEndpointPrinter(ctx context.Context, c common.Args, k8sClient cli fmt.Println() fmt.Println(`Please refer to https://kubevela.io/docs/reference/addons/velaux for more VelaUX addon installation and visiting method.`) } + if len(info) > 0 { + fmt.Println(info) + } } // NewAddonUpgradeCommand create addon upgrade command @@ -295,7 +303,13 @@ non-empty new arg addonInputArgs[types.ClustersArg] = clusterArgs } addonOrDir := args[0] - var name string + + // inject runtime info + addonInputArgs[pkgaddon.InstallerRuntimeOption] = map[string]interface{}{ + "upgrade": true, + } + + var name, additionalInfo string if file, err := os.Stat(addonOrDir); err == nil { if !file.IsDir() { return fmt.Errorf("%s is not addon dir", addonOrDir) @@ -315,7 +329,7 @@ non-empty new arg if err != nil { return err } - err = enableAddonByLocal(ctx, name, addonOrDir, k8sClient, dc, config, addonArgs) + additionalInfo, err = enableAddonByLocal(ctx, name, addonOrDir, k8sClient, dc, config, addonArgs) if err != nil { return err } @@ -332,14 +346,14 @@ non-empty new arg if err != nil { return err } - err = enableAddon(ctx, k8sClient, dc, config, addonOrDir, addonVersion, addonArgs) + additionalInfo, err = enableAddon(ctx, k8sClient, dc, config, addonOrDir, addonVersion, addonArgs) if err != nil { return err } } - fmt.Printf("Addon %s enabled successfully.", name) - AdditionalEndpointPrinter(ctx, c, k8sClient, name, true) + fmt.Printf("Addon %s enabled successfully.\n", name) + AdditionalEndpointPrinter(ctx, c, k8sClient, name, additionalInfo, true) return nil }, } @@ -547,16 +561,17 @@ $ HELM_REPO_USERNAME=name HELM_REPO_PASSWORD=pswd vela addon push mongo-1.0.0.tg return cmd } -func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.DiscoveryClient, config *rest.Config, name string, version string, args map[string]interface{}) error { +func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.DiscoveryClient, config *rest.Config, name string, version string, args map[string]interface{}) (string, error) { var err error + var additionalInfo string registryDS := pkgaddon.NewRegistryDataStore(k8sClient) registries, err := registryDS.ListRegistries(ctx) if err != nil { - return err + return "", err } registryName, addonName, err := splitSpecifyRegistry(name) if err != nil { - return err + return "", err } if len(registryName) != 0 { foundRegistry := false @@ -566,7 +581,7 @@ func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.Dis } } if !foundRegistry { - return fmt.Errorf("specified registry %s not exist", registryName) + return "", fmt.Errorf("specified registry %s not exist", registryName) } } for i, registry := range registries { @@ -574,7 +589,7 @@ func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.Dis if len(registryName) != 0 && registryName != registry.Name { continue } - err = pkgaddon.EnableAddon(ctx, addonName, version, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil, pkgaddon.FilterDependencyRegistries(i, registries), opts...) + additionalInfo, err = pkgaddon.EnableAddon(ctx, addonName, version, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil, pkgaddon.FilterDependencyRegistries(i, registries), opts...) if errors.Is(err, pkgaddon.ErrNotExist) || errors.Is(err, pkgaddon.ErrFetch) { continue } @@ -582,28 +597,27 @@ func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.Dis // Get available version of the addon availableVersion, err := unMatchErr.GetAvailableVersion() if err != nil { - return err + return "", err } input := NewUserInput() if input.AskBool(unMatchErr.Error(), &UserInputOptions{AssumeYes: false}) { - err = pkgaddon.EnableAddon(ctx, addonName, availableVersion, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil, pkgaddon.FilterDependencyRegistries(i, registries)) - return err + return pkgaddon.EnableAddon(ctx, addonName, availableVersion, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil, pkgaddon.FilterDependencyRegistries(i, registries)) } // 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 \" ", addonName) + return "", fmt.Errorf("you can try another version by command: \"vela addon enable %s --version \" ", addonName) } if err != nil { - return err + return "", err } if err = waitApplicationRunning(k8sClient, addonName); err != nil { - return err + return "", err } - return nil + return additionalInfo, nil } if len(registryName) != 0 { - return fmt.Errorf("addon: %s not found in registry %s", addonName, registryName) + return "", fmt.Errorf("addon: %s not found in registry %s", addonName, registryName) } - return fmt.Errorf("addon: %s not found in all candidate registries", addonName) + return "", fmt.Errorf("addon: %s not found in all candidate registries", addonName) } func addonOptions() []pkgaddon.InstallOption { @@ -621,15 +635,16 @@ func addonOptions() []pkgaddon.InstallOption { } // enableAddonByLocal enable addon in local dir and return the addon name -func enableAddonByLocal(ctx context.Context, name string, dir string, k8sClient client.Client, dc *discovery.DiscoveryClient, config *rest.Config, args map[string]interface{}) error { +func enableAddonByLocal(ctx context.Context, name string, dir string, k8sClient client.Client, dc *discovery.DiscoveryClient, config *rest.Config, args map[string]interface{}) (string, error) { opts := addonOptions() - if err := pkgaddon.EnableAddonByLocalDir(ctx, name, dir, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, args, opts...); err != nil { - return err + info, err := pkgaddon.EnableAddonByLocalDir(ctx, name, dir, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, args, opts...) + if err != nil { + return "", err } - if err := waitApplicationRunning(k8sClient, name); err != nil { - return err + if err = waitApplicationRunning(k8sClient, name); err != nil { + return "", err } - return nil + return info, nil } func disableAddon(client client.Client, name string, config *rest.Config, force bool) error {