Commit ff42dadd authored by Sushil Kumar's avatar Sushil Kumar Committed by Adam Reese

Adds update option to plugin command (#2410)

* Adds update option to plugin command

Fixes issues/2385 - helm install silently updates the plugin, if it pre-existed

* Added tests for new methods for plugin update

* Updated docs

* Updated review comments :)

* Return error exit code when there is error
parent 8b6fff44
...@@ -41,6 +41,7 @@ func newPluginCmd(out io.Writer) *cobra.Command { ...@@ -41,6 +41,7 @@ func newPluginCmd(out io.Writer) *cobra.Command {
newPluginInstallCmd(out), newPluginInstallCmd(out),
newPluginListCmd(out), newPluginListCmd(out),
newPluginRemoveCmd(out), newPluginRemoveCmd(out),
newPluginUpdateCmd(out),
) )
return cmd return cmd
} }
......
/*
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"
"path/filepath"
"strings"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin"
"k8s.io/helm/pkg/plugin/installer"
"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 <plugin>...",
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
}
var errorPlugins []string
for _, name := range pcmd.names {
if found := findPlugin(plugins, name); found != nil {
if err := updatePlugin(found, pcmd.home); err != nil {
errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err))
} else {
fmt.Fprintf(pcmd.out, "Updated plugin: %s\n", name)
}
} else {
errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name))
}
}
if len(errorPlugins) > 0 {
return fmt.Errorf(strings.Join(errorPlugins, "\n"))
}
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)
}
...@@ -24,5 +24,6 @@ Manage client-side Helm plugins. ...@@ -24,5 +24,6 @@ Manage client-side Helm plugins.
* [helm plugin install](helm_plugin_install.md) - install one or more Helm plugins * [helm plugin install](helm_plugin_install.md) - install one or more Helm plugins
* [helm plugin list](helm_plugin_list.md) - list installed Helm plugins * [helm plugin list](helm_plugin_list.md) - list installed Helm plugins
* [helm plugin remove](helm_plugin_remove.md) - remove one or more Helm plugins * [helm plugin remove](helm_plugin_remove.md) - remove one or more Helm plugins
* [helm plugin update](helm_plugin_update.md) - update one or more Helm plugins
###### Auto generated by spf13/cobra on 16-Apr-2017 ###### Auto generated by spf13/cobra on 6-May-2017
## helm plugin update
update one or more Helm plugins
### Synopsis
update one or more Helm plugins
```
helm plugin update <plugin>...
```
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-namespace string namespace of tiller (default "kube-system")
```
### SEE ALSO
* [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins
###### Auto generated by spf13/cobra on 6-May-2017
.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" .TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" ""
.nh .nh
.ad l .ad l
...@@ -42,9 +42,9 @@ Manage client\-side Helm plugins. ...@@ -42,9 +42,9 @@ Manage client\-side Helm plugins.
.SH SEE ALSO .SH SEE ALSO
.PP .PP
\fBhelm(1)\fP, \fBhelm\-plugin\-install(1)\fP, \fBhelm\-plugin\-list(1)\fP, \fBhelm\-plugin\-remove(1)\fP \fBhelm(1)\fP, \fBhelm\-plugin\-install(1)\fP, \fBhelm\-plugin\-list(1)\fP, \fBhelm\-plugin\-remove(1)\fP, \fBhelm\-plugin\-update(1)\fP
.SH HISTORY .SH HISTORY
.PP .PP
16\-Apr\-2017 Auto generated by spf13/cobra 6\-May\-2017 Auto generated by spf13/cobra
.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" ""
.nh
.ad l
.SH NAME
.PP
helm\-plugin\-update \- update one or more Helm plugins
.SH SYNOPSIS
.PP
\fBhelm plugin update <plugin>\&...\fP
.SH DESCRIPTION
.PP
update one or more Helm plugins
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-debug\fP[=false]
enable verbose output
.PP
\fB\-\-home\fP="~/.helm"
location of your Helm config. Overrides $HELM\_HOME
.PP
\fB\-\-host\fP=""
address of tiller. Overrides $HELM\_HOST
.PP
\fB\-\-kube\-context\fP=""
name of the kubeconfig context to use
.PP
\fB\-\-tiller\-namespace\fP="kube\-system"
namespace of tiller
.SH SEE ALSO
.PP
\fBhelm\-plugin(1)\fP
.SH HISTORY
.PP
6\-May\-2017 Auto generated by spf13/cobra
...@@ -21,6 +21,8 @@ const ( ...@@ -21,6 +21,8 @@ const (
Install = "install" Install = "install"
// Delete is executed after the plugin is removed. // Delete is executed after the plugin is removed.
Delete = "delete" Delete = "delete"
// Update is executed after the plugin is updated.
Update = "update"
) )
// Hooks is a map of events to commands. // Hooks is a map of events to commands.
......
...@@ -36,6 +36,8 @@ type Installer interface { ...@@ -36,6 +36,8 @@ type Installer interface {
Install() error Install() error
// Path is the directory of the installed plugin. // Path is the directory of the installed plugin.
Path() string Path() string
// Update updates a plugin to $HELM_HOME.
Update() error
} }
// Install installs a plugin to $HELM_HOME. // Install installs a plugin to $HELM_HOME.
...@@ -47,6 +49,15 @@ func Install(i Installer) error { ...@@ -47,6 +49,15 @@ func Install(i Installer) error {
return i.Install() return i.Install()
} }
// Update updates a plugin in $HELM_HOME.
func Update(i Installer) error {
if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
return errors.New("plugin does not exist")
}
return i.Update()
}
// NewForSource determines the correct Installer for the given source. // NewForSource determines the correct Installer for the given source.
func NewForSource(source, version string, home helmpath.Home) (Installer, error) { func NewForSource(source, version string, home helmpath.Home) (Installer, error) {
// Check if source is a local directory // Check if source is a local directory
...@@ -56,6 +67,15 @@ func NewForSource(source, version string, home helmpath.Home) (Installer, error) ...@@ -56,6 +67,15 @@ func NewForSource(source, version string, home helmpath.Home) (Installer, error)
return NewVCSInstaller(source, version, home) 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. // isLocalReference checks if the source exists on the filesystem.
func isLocalReference(source string) bool { func isLocalReference(source string) bool {
_, err := os.Stat(source) _, err := os.Stat(source)
......
...@@ -47,3 +47,9 @@ func (i *LocalInstaller) Install() error { ...@@ -47,3 +47,9 @@ func (i *LocalInstaller) Install() error {
} }
return i.link(src) return i.link(src)
} }
// Update updates a local repository
func (i *LocalInstaller) Update() error {
debug("local repository is auto-updated")
return nil
}
...@@ -34,6 +34,18 @@ type VCSInstaller struct { ...@@ -34,6 +34,18 @@ type VCSInstaller struct {
base 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. // NewVCSInstaller creates a new VCSInstaller.
func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) { func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) {
key, err := cache.Key(source) key, err := cache.Key(source)
...@@ -77,6 +89,18 @@ func (i *VCSInstaller) Install() error { ...@@ -77,6 +89,18 @@ func (i *VCSInstaller) Install() error {
return i.link(i.Repo.LocalPath()) 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) { func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) {
if i.Version == "" { if i.Version == "" {
return "", nil return "", nil
......
...@@ -97,6 +97,13 @@ func TestVCSInstaller(t *testing.T) { ...@@ -97,6 +97,13 @@ func TestVCSInstaller(t *testing.T) {
} else if err.Error() != "plugin already exists" { } else if err.Error() != "plugin already exists" {
t.Errorf("expected error for plugin exists, got (%v)", err) t.Errorf("expected error for plugin exists, got (%v)", err)
} }
//Testing FindSource method, expect error because plugin code is not a cloned repository
if _, err := FindSource(i.Path(), home); err == nil {
t.Error("expected error for inability to find plugin source, got none")
} else if err.Error() != "cannot get information about plugin source" {
t.Errorf("expected error for inability to find plugin source, got (%v)", err)
}
} }
func TestVCSInstallerNonExistentVersion(t *testing.T) { func TestVCSInstallerNonExistentVersion(t *testing.T) {
...@@ -131,3 +138,66 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) { ...@@ -131,3 +138,66 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) {
t.Errorf("expected error for version does not exists, got (%v)", err) t.Errorf("expected error for version does not exists, got (%v)", err)
} }
} }
func TestVCSInstallerUpdate(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-home-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(hh)
home := helmpath.Home(hh)
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
}
source := "https://github.com/adamreese/helm-env"
i, err := NewForSource(source, "", home)
if err != nil {
t.Errorf("unexpected error: %s", err)
}
// ensure a VCSInstaller was returned
_, ok := i.(*VCSInstaller)
if !ok {
t.Error("expected a VCSInstaller")
}
if err := Update(i); err == nil {
t.Error("expected error for plugin does not exist, got none")
} else if err.Error() != "plugin does not exist" {
t.Errorf("expected error for plugin does not exist, got (%v)", err)
}
// Install plugin before update
if err := Install(i); err != nil {
t.Error(err)
}
// Test FindSource method for positive result
pluginInfo, err := FindSource(i.Path(), home)
if err != nil {
t.Error(err)
}
repoRemote := pluginInfo.(*VCSInstaller).Repo.Remote()
if repoRemote != source {
t.Errorf("invalid source found, expected %q got %q", source, repoRemote)
}
// Update plugin
if err := Update(i); err != nil {
t.Error(err)
}
// Test update failure
os.Remove(filepath.Join(i.Path(), "plugin.yaml"))
// Testing update for error
if err := Update(i); err == nil {
t.Error("expected error for plugin metadata missing, got none")
} else if err.Error() != "plugin metadata (plugin.yaml) missing" {
t.Errorf("expected error for plugin metadata missing, got (%v)", err)
}
}
...@@ -1044,6 +1044,28 @@ _helm_plugin_remove() ...@@ -1044,6 +1044,28 @@ _helm_plugin_remove()
noun_aliases=() noun_aliases=()
} }
_helm_plugin_update()
{
last_command="helm_plugin_update"
commands=()
flags=()
two_word_flags=()
local_nonpersistent_flags=()
flags_with_completion=()
flags_completion=()
flags+=("--debug")
flags+=("--home=")
flags+=("--host=")
flags+=("--kube-context=")
flags+=("--tiller-namespace=")
must_have_one_flag=()
must_have_one_noun=()
noun_aliases=()
}
_helm_plugin() _helm_plugin()
{ {
last_command="helm_plugin" last_command="helm_plugin"
...@@ -1051,6 +1073,7 @@ _helm_plugin() ...@@ -1051,6 +1073,7 @@ _helm_plugin()
commands+=("install") commands+=("install")
commands+=("list") commands+=("list")
commands+=("remove") commands+=("remove")
commands+=("update")
flags=() flags=()
two_word_flags=() two_word_flags=()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment