diff --git a/cmd/helm/plugin.go b/cmd/helm/plugin.go index 37d9205b7..47e1b361c 100644 --- a/cmd/helm/plugin.go +++ b/cmd/helm/plugin.go @@ -41,6 +41,7 @@ func newPluginCmd(out io.Writer) *cobra.Command { newPluginInstallCmd(out), newPluginListCmd(out), newPluginRemoveCmd(out), + newPluginUpdateCmd(out), ) return cmd } diff --git a/cmd/helm/plugin_update.go b/cmd/helm/plugin_update.go new file mode 100644 index 000000000..f5615c6a8 --- /dev/null +++ b/cmd/helm/plugin_update.go @@ -0,0 +1,109 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +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 main + +import ( + "errors" + "fmt" + "io" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin" + "k8s.io/helm/pkg/plugin/installer" + + "path/filepath" + + "github.com/spf13/cobra" +) + +type pluginUpdateCmd struct { + names []string + home helmpath.Home + out io.Writer +} + +func newPluginUpdateCmd(out io.Writer) *cobra.Command { + pcmd := &pluginUpdateCmd{out: out} + cmd := &cobra.Command{ + Use: "update ...", + Short: "update one or more Helm plugins", + PreRunE: func(cmd *cobra.Command, args []string) error { + return pcmd.complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return pcmd.run() + }, + } + return cmd +} + +func (pcmd *pluginUpdateCmd) complete(args []string) error { + if len(args) == 0 { + return errors.New("please provide plugin name to update") + } + pcmd.names = args + pcmd.home = settings.Home + return nil +} + +func (pcmd *pluginUpdateCmd) run() error { + installer.Debug = settings.Debug + plugdirs := pluginDirs(pcmd.home) + debug("loading installed plugins from %s", plugdirs) + plugins, err := findPlugins(plugdirs) + if err != nil { + return err + } + for _, name := range pcmd.names { + if found := findPlugin(plugins, name); found != nil { + if err := updatePlugin(found, pcmd.home); err != nil { + fmt.Fprintf(pcmd.out, "Failed to update plugin %s, got error (%v)\n", name, err) + } else { + fmt.Fprintf(pcmd.out, "Updated plugin: %s\n", name) + } + } else { + fmt.Fprintf(pcmd.out, "Plugin: %s not found\n", name) + } + } + return nil +} + +func updatePlugin(p *plugin.Plugin, home helmpath.Home) error { + exactLocation, err := filepath.EvalSymlinks(p.Dir) + if err != nil { + return err + } + absExactLocation, err := filepath.Abs(exactLocation) + if err != nil { + return err + } + + i, err := installer.FindSource(absExactLocation, home) + if err != nil { + return err + } + if err := installer.Update(i); err != nil { + return err + } + + debug("loading plugin from %s", i.Path()) + updatedPlugin, err := plugin.LoadDir(i.Path()) + if err != nil { + return err + } + + return runHook(updatedPlugin, plugin.Update, home) +} diff --git a/pkg/plugin/hooks.go b/pkg/plugin/hooks.go index 1f435f9f8..eb078bd10 100644 --- a/pkg/plugin/hooks.go +++ b/pkg/plugin/hooks.go @@ -21,6 +21,8 @@ const ( Install = "install" // Delete is executed after the plugin is removed. Delete = "delete" + // Update is executed after the plugin is removed. + Update = "update" ) // Hooks is a map of events to commands. diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go index 8da6e24e5..ed264da76 100644 --- a/pkg/plugin/installer/installer.go +++ b/pkg/plugin/installer/installer.go @@ -36,6 +36,8 @@ type Installer interface { Install() error // Path is the directory of the installed plugin. Path() string + // Update updates a plugin to $HELM_HOME. + Update() error } // Install installs a plugin to $HELM_HOME. @@ -47,6 +49,15 @@ func Install(i Installer) error { return i.Install() } +// Update updates a plugin to $HELM_HOME. +func Update(i Installer) error { + if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) { + return errors.New("plugin does not exists") + } + + return i.Update() +} + // NewForSource determines the correct Installer for the given source. func NewForSource(source, version string, home helmpath.Home) (Installer, error) { // Check if source is a local directory @@ -56,6 +67,15 @@ func NewForSource(source, version string, home helmpath.Home) (Installer, error) return NewVCSInstaller(source, version, home) } +// FindSource determines the correct Installer for the given source. +func FindSource(location string, home helmpath.Home) (Installer, error) { + installer, err := existingVCSRepo(location, home) + if err != nil && err.Error() == "Cannot detect VCS" { + return installer, errors.New("cannot get information about plugin source") + } + return installer, err +} + // isLocalReference checks if the source exists on the filesystem. func isLocalReference(source string) bool { _, err := os.Stat(source) diff --git a/pkg/plugin/installer/local_installer.go b/pkg/plugin/installer/local_installer.go index 7ab588d60..18011f8de 100644 --- a/pkg/plugin/installer/local_installer.go +++ b/pkg/plugin/installer/local_installer.go @@ -47,3 +47,9 @@ func (i *LocalInstaller) Install() error { } return i.link(src) } + +// Update updates a local repository +func (i *LocalInstaller) Update() error { + debug("local repository is auto-updated") + return nil +} diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go index f9f181662..5285092a4 100644 --- a/pkg/plugin/installer/vcs_installer.go +++ b/pkg/plugin/installer/vcs_installer.go @@ -34,6 +34,18 @@ type VCSInstaller struct { base } +func existingVCSRepo(location string, home helmpath.Home) (Installer, error) { + repo, err := vcs.NewRepo("", location) + if err != nil { + return nil, err + } + i := &VCSInstaller{ + Repo: repo, + base: newBase(repo.Remote(), home), + } + return i, err +} + // NewVCSInstaller creates a new VCSInstaller. func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) { key, err := cache.Key(source) @@ -77,6 +89,18 @@ func (i *VCSInstaller) Install() error { return i.link(i.Repo.LocalPath()) } +// Update updates a remote repository +func (i *VCSInstaller) Update() error { + debug("updating %s", i.Repo.Remote()) + if err := i.Repo.Update(); err != nil { + return err + } + if !isPlugin(i.Repo.LocalPath()) { + return ErrMissingMetadata + } + return nil +} + func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) { if i.Version == "" { return "", nil