feat(helm): add --plugins flag to 'helm init' (#5109)

Allow specifying a set of plugins in a yaml file that will be installed during
the `helm init` process.

Closes #5079.

Signed-off-by: Sven van Heugten <svenvanheugten@home.nl>
pull/5284/head
Sven van Heugten 6 years ago committed by Matthew Fisher
parent 5eb48f4471
commit 480a83206f

@ -19,14 +19,19 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"github.com/Masterminds/semver"
"github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin"
"k8s.io/helm/pkg/plugin/installer"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )
@ -42,10 +47,20 @@ const (
type initOptions struct { type initOptions struct {
skipRefresh bool // --skip-refresh skipRefresh bool // --skip-refresh
stableRepositoryURL string // --stable-repo-url stableRepositoryURL string // --stable-repo-url
pluginsFilename string // --plugins
home helmpath.Home home helmpath.Home
} }
type pluginsFileEntry struct {
URL string `json:"url"`
Version string `json:"version,omitempty"`
}
type pluginsFile struct {
Plugins []*pluginsFileEntry `json:"plugins"`
}
func newInitCmd(out io.Writer) *cobra.Command { func newInitCmd(out io.Writer) *cobra.Command {
o := &initOptions{} o := &initOptions{}
@ -63,6 +78,7 @@ func newInitCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&o.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") f.BoolVar(&o.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache")
f.StringVar(&o.stableRepositoryURL, "stable-repo-url", defaultStableRepositoryURL, "URL for stable repository") f.StringVar(&o.stableRepositoryURL, "stable-repo-url", defaultStableRepositoryURL, "URL for stable repository")
f.StringVar(&o.pluginsFilename, "plugins", "", "a YAML file specifying plugins to install")
return cmd return cmd
} }
@ -78,6 +94,11 @@ func (o *initOptions) run(out io.Writer) error {
if err := ensureRepoFileFormat(o.home.RepositoryFile(), out); err != nil { if err := ensureRepoFileFormat(o.home.RepositoryFile(), out); err != nil {
return err return err
} }
if o.pluginsFilename != "" {
if err := ensurePluginsInstalled(o.pluginsFilename, out); err != nil {
return err
}
}
fmt.Fprintf(out, "$HELM_HOME has been configured at %s.\n", settings.Home) fmt.Fprintf(out, "$HELM_HOME has been configured at %s.\n", settings.Home)
fmt.Fprintln(out, "Happy Helming!") fmt.Fprintln(out, "Happy Helming!")
return nil return nil
@ -163,3 +184,71 @@ func ensureRepoFileFormat(file string, out io.Writer) error {
} }
return nil return nil
} }
func ensurePluginsInstalled(pluginsFilename string, out io.Writer) error {
bytes, err := ioutil.ReadFile(pluginsFilename)
if err != nil {
return err
}
pf := new(pluginsFile)
if err := yaml.Unmarshal(bytes, &pf); err != nil {
return errors.Wrapf(err, "failed to parse %s", pluginsFilename)
}
for _, requiredPlugin := range pf.Plugins {
if err := ensurePluginInstalled(requiredPlugin, pluginsFilename, out); err != nil {
return errors.Wrapf(err, "failed to install plugin from %s", requiredPlugin.URL)
}
}
return nil
}
func ensurePluginInstalled(requiredPlugin *pluginsFileEntry, pluginsFilename string, out io.Writer) error {
i, err := installer.NewForSource(requiredPlugin.URL, requiredPlugin.Version, settings.Home)
if err != nil {
return err
}
if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
if err := installer.Install(i); err != nil {
return err
}
p, err := plugin.LoadDir(i.Path())
if err != nil {
return err
}
if err := runHook(p, plugin.Install); err != nil {
return err
}
fmt.Fprintf(out, "Installed plugin: %s\n", p.Metadata.Name)
} else if requiredPlugin.Version != "" {
p, err := plugin.LoadDir(i.Path())
if err != nil {
return err
}
if p.Metadata.Version != "" {
pluginVersion, err := semver.NewVersion(p.Metadata.Version)
if err != nil {
return err
}
constraint, err := semver.NewConstraint(requiredPlugin.Version)
if err != nil {
return err
}
if !constraint.Check(pluginVersion) {
fmt.Fprintf(out, "WARNING: Installed plugin '%s' is at version %s, while %s specifies %s\n",
p.Metadata.Name, p.Metadata.Version, pluginsFilename, requiredPlugin.Version)
}
}
}
return nil
}

@ -24,6 +24,8 @@ import (
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
) )
const testPluginsFile = "testdata/plugins.yaml"
func TestEnsureHome(t *testing.T) { func TestEnsureHome(t *testing.T) {
hh := helmpath.Home(testTempDir(t)) hh := helmpath.Home(testTempDir(t))
@ -41,6 +43,9 @@ func TestEnsureHome(t *testing.T) {
if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil { if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil {
t.Error(err) t.Error(err)
} }
if err := ensurePluginsInstalled(testPluginsFile, b); err != nil {
t.Error(err)
}
expectedDirs := []string{hh.String(), hh.Repository(), hh.Cache()} expectedDirs := []string{hh.String(), hh.Repository(), hh.Cache()}
for _, dir := range expectedDirs { for _, dir := range expectedDirs {
@ -56,4 +61,12 @@ func TestEnsureHome(t *testing.T) {
} else if fi.IsDir() { } else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi) t.Errorf("%s should not be a directory", fi)
} }
if plugins, err := findPlugins(settings.PluginDirs()); err != nil {
t.Error(err)
} else if len(plugins) != 1 {
t.Errorf("Expected 1 plugin, got %d", len(plugins))
} else if plugins[0].Metadata.Name != "testplugin" {
t.Errorf("Expected %s to be installed", "testplugin")
}
} }

@ -0,0 +1,3 @@
plugins:
- name: testplugin
url: testdata/testplugin

@ -0,0 +1,4 @@
name: testplugin
usage: "echo test"
description: "This echos test"
command: "echo test"

@ -37,10 +37,20 @@ Plugins are installed using the `$ helm plugin install <path|url>` command. You
$ helm plugin install https://github.com/technosophos/helm-template $ helm plugin install https://github.com/technosophos/helm-template
``` ```
If you have a plugin tar distribution, simply untar the plugin into the If you have a plugin tar distribution, simply untar the plugin into the `$(helm home)/plugins` directory. You can also install tarball plugins directly from url by issuing `helm plugin install http://domain/path/to/plugin.tar.gz`
`$(helm home)/plugins` directory.
You can also install tarball plugins directly from url by issuing `helm plugin install http://domain/path/to/plugin.tar.gz` Alternatively, a set of plugins can be installed during the `helm init` process by using the `--plugins <file.yaml>` flag, where `file.yaml` looks like this:
```
plugins:
- name: helm-template
url: https://github.com/technosophos/helm-template
- name: helm-diff
url: https://github.com/databus23/helm-diff
version: 2.11.0+3
```
The `name` field only exists to allow you to easily identify plugins, and does not serve a functional purpose. If a plugin specified in the file is already installed, it maintains its current version.
## Building Plugins ## Building Plugins

Loading…
Cancel
Save