loading external plugins

Signed-off-by: Vicente Zepeda Mas <vzepedamas@suse.com>
(cherry picked from commit 3f7304e52ec57a0701f307c4d048c4269455d04a)
pull/4798/head
Vicente Zepeda Mas 7 years ago
parent bdd420a6b6
commit 7213904373

@ -25,7 +25,6 @@ import (
shellwords "github.com/mattn/go-shellwords" shellwords "github.com/mattn/go-shellwords"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/internal/test" "k8s.io/helm/internal/test"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"

@ -25,7 +25,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
) )
@ -41,8 +40,9 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
return return
} }
pluginsDirs := []string{settings.PluginDirs(), settings.SystemPluginsDir}
// debug("HELM_PLUGIN_DIRS=%s", settings.PluginDirs()) // debug("HELM_PLUGIN_DIRS=%s", settings.PluginDirs())
found, err := findPlugins(settings.PluginDirs()) found, err := findPlugins(pluginsDirs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
return return
@ -137,15 +137,17 @@ func manuallyProcessArgs(args []string) ([]string, []string) {
} }
// findPlugins returns a list of YAML files that describe plugins. // findPlugins returns a list of YAML files that describe plugins.
func findPlugins(plugdirs string) ([]*plugin.Plugin, error) { func findPlugins(plugdirs []string) ([]*plugin.Plugin, error) {
found := []*plugin.Plugin{} found := []*plugin.Plugin{}
// Let's get all UNIXy and allow path separators // Let's get all UNIXy and allow path separators
for _, p := range filepath.SplitList(plugdirs) { for _, pd := range plugdirs {
matches, err := plugin.LoadAll(p) for _, p := range filepath.SplitList(pd) {
if err != nil { matches, err := plugin.LoadAll(p)
return matches, err if err != nil {
return matches, err
}
found = append(found, matches...)
} }
found = append(found, matches...)
} }
return found, nil return found, nil
} }

@ -19,10 +19,9 @@ import (
"fmt" "fmt"
"io" "io"
"k8s.io/helm/pkg/helm/helmpath"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath"
) )
type pluginListOptions struct { type pluginListOptions struct {
@ -44,7 +43,8 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
func (o *pluginListOptions) run(out io.Writer) error { func (o *pluginListOptions) run(out io.Writer) error {
debug("pluginDirs: %s", settings.PluginDirs()) debug("pluginDirs: %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs()) directories := []string{settings.PluginDirs(), settings.SystemPluginsDir}
plugins, err := findPlugins(directories)
if err != nil { if err != nil {
return err return err
} }

@ -23,7 +23,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
) )
@ -59,7 +58,7 @@ func (o *pluginRemoveOptions) complete(args []string) error {
func (o *pluginRemoveOptions) run(out io.Writer) error { func (o *pluginRemoveOptions) run(out io.Writer) error {
debug("loading installed plugins from %s", settings.PluginDirs()) debug("loading installed plugins from %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs()) plugins, err := findPlugins([]string{settings.PluginDirs()})
if err != nil { if err != nil {
return err return err
} }

@ -23,10 +23,9 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
"github.com/spf13/cobra"
) )
func TestManuallyProcessArgs(t *testing.T) { func TestManuallyProcessArgs(t *testing.T) {
@ -65,6 +64,7 @@ func TestLoadPlugins(t *testing.T) {
defer resetEnv()() defer resetEnv()()
settings.Home = "testdata/helmhome" settings.Home = "testdata/helmhome"
settings.SystemPluginsDir = "test"
os.Setenv("HELM_HOME", settings.Home.String()) os.Setenv("HELM_HOME", settings.Home.String())
hh := settings.Home hh := settings.Home

@ -23,7 +23,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
"k8s.io/helm/pkg/plugin/installer" "k8s.io/helm/pkg/plugin/installer"
@ -61,7 +60,7 @@ func (o *pluginUpdateOptions) complete(args []string) error {
func (o *pluginUpdateOptions) run(out io.Writer) error { func (o *pluginUpdateOptions) run(out io.Writer) error {
installer.Debug = settings.Debug installer.Debug = settings.Debug
debug("loading installed plugins from %s", settings.PluginDirs()) debug("loading installed plugins from %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs()) plugins, err := findPlugins([]string{settings.PluginDirs()})
if err != nil { if err != nil {
return err return err
} }

@ -21,6 +21,10 @@ Helm plugins have the following features:
- They can be written in any programming language. - They can be written in any programming language.
- They integrate with Helm, and will show up in `helm help` and other places. - They integrate with Helm, and will show up in `helm help` and other places.
## Plugins Backends
### Native
Helm plugins live in `$(helm home)/plugins`. Helm plugins live in `$(helm home)/plugins`.
The Helm plugin model is partially modeled on Git's plugin model. To that end, The Helm plugin model is partially modeled on Git's plugin model. To that end,
@ -29,9 +33,27 @@ plugins being the _plumbing_. This is a shorthand way of suggesting that
Helm provides the user experience and top level processing logic, while the Helm provides the user experience and top level processing logic, while the
plugins do the "detail work" of performing a desired action. plugins do the "detail work" of performing a desired action.
## Installing a Plugin ### External
Helm external plugins can live anywhere in your `PATH`.
These plugins become necessary because of systems that are closed to manual
configuration and the only way to get software installed is by an administrator
managing the plugins at system level via a linux distribution package manager.
These plugins are going to be available globally. All users may use the plugin,
not limited to a user like Native Plugins.
The only condition for these plugins is that they provide the yaml description
in the folder `/usr/share`; and the full path should look like this
`/usr/share/<helm-external-plugin>/plugin.yaml`.
Plugins are installed using the `$ helm plugin install <path|url>` command. You can pass in a path to a plugin on your local file system or a url of a remote VCS repo. The `helm plugin install` command clones or copies the plugin at the path/url given into `$ (helm home)/plugins` ## Installing a Native Plugin
Native Plugins are installed using the `$ helm plugin install <path|url>` command.
You can pass in a path to a plugin on your local file system or a url of a remote
VCS repo. The `helm plugin install` command clones or copies the plugin at the
path/url given into `$ (helm home)/plugins`
```console ```console
$ helm plugin install https://github.com/technosophos/helm-template $ helm plugin install https://github.com/technosophos/helm-template
@ -42,7 +64,12 @@ If you have a plugin tar distribution, simply untar the plugin into the
You can also install tarball plugins directly from url by issuing `helm plugin install http://domain/path/to/plugin.tar.gz` You can also install tarball plugins directly from url by issuing `helm plugin install http://domain/path/to/plugin.tar.gz`
## Building Plugins ## Installing an External Plugin
Each external plugin has its own installation process, you should check with the plugin's
site/help to get the correct information.
## Building Native Plugins
In many ways, a plugin is similar to a chart. Each plugin has a top-level In many ways, a plugin is similar to a chart. Each plugin has a top-level
directory, and then a `plugin.yaml` file. directory, and then a `plugin.yaml` file.
@ -73,7 +100,7 @@ ignoreFlags: false
command: "$HELM_PLUGIN_DIR/keybase.sh" command: "$HELM_PLUGIN_DIR/keybase.sh"
``` ```
The `name` is the name of the plugin. When Helm executes it plugin, this is the The `name` is the name of the plugin. When Helm executes a plugin, this is the
name it will use (e.g. `helm NAME` will invoke this plugin). name it will use (e.g. `helm NAME` will invoke this plugin).
_`name` should match the directory name._ In our example above, that means the _`name` should match the directory name._ In our example above, that means the
@ -111,8 +138,8 @@ There are some strategies for working with plugin commands:
Helm will use `usage` and `description` for `helm help` and `helm help myplugin`, Helm will use `usage` and `description` for `helm help` and `helm help myplugin`,
but will not handle `helm myplugin --help`. but will not handle `helm myplugin --help`.
## Downloader Plugins ## Downloader Native Plugins
By default, Helm is able to pull Charts using HTTP/S. As of Helm 2.4.0, plugins By default, Helm is able to fetch Charts using HTTP/S. As of Helm 2.4.0, plugins
can have a special capability to download Charts from arbitrary sources. can have a special capability to download Charts from arbitrary sources.
Plugins shall declare this special capability in the `plugin.yaml` file (top level): Plugins shall declare this special capability in the `plugin.yaml` file (top level):
@ -137,6 +164,66 @@ The defined command will be invoked with the following scheme:
repo definition, stored in `$HELM_HOME/repository/repositories.yaml`. Downloader repo definition, stored in `$HELM_HOME/repository/repositories.yaml`. Downloader
plugin is expected to dump the raw content to stdout and report errors on stderr. plugin is expected to dump the raw content to stdout and report errors on stderr.
## External Plugins Breakout
External plugins have to comply with two conditions:
- Be sure it is accessible by being in the `PATH`.
- Have the `plugin.yaml` installed in the `/usr/share/<external-plugin>` folder.
### Example
```console
/usr/
|- bin/
|- helm-mirror
|- share/
|- helm-mirror
|- plugin.yaml
```
### plugin.yaml for External Plugins
```
name: "mirror"
version: "0.1.0"
usage: "Mirrors Helm charts to a local folder"
description: "Mirrors Helm charts to a local folder"
ignoreFlags: false
useTunnel: false
command: "/usr/bin/helm-mirror"
```
The `name` is the name of the plugin. When Helm executes a plugin, this is the
name it will use (e.g. `helm NAME` will invoke this plugin).
Restrictions on `name`:
- `name` cannot duplicate one of the existing `helm` top-level commands.
- `name` must be restricted to the characters ASCII a-z, A-Z, 0-9, `_` and `-`.
`version` is the SemVer 2 version of the plugin.
`usage` and `description` are both used to generate the help text of a command.
The `ignoreFlags` switch tells Helm to _not_ pass flags to the plugin. So if a
plugin is called with `helm myplugin --foo` and `ignoreFlags: true`, then `--foo`
is silently discarded.
The `useTunnel` switch indicates that the plugin needs a tunnel to Tiller. This
should be set to `true` _anytime a plugin talks to Tiller_. It will cause Helm
to open a tunnel, and then set `$TILLER_HOST` to the right local address for that
tunnel. But don't worry: if Helm detects that a tunnel is not necessary because
Tiller is running locally, it will not create the tunnel.
Finally, and most importantly, `command` is the command that this plugin will
execute when it is called. Environment variables are interpolated before the plugin
is executed. The pattern above illustrates the preferred way to indicate where
the plugin program lives.
> NOTE: For External Plugins the `plugin.yaml` definition **MUST NOT** contain any `Hooks`.
## Environment Variables ## Environment Variables
When Helm executes a plugin, it passes the outer environment to the plugin, and When Helm executes a plugin, it passes the outer environment to the plugin, and

@ -30,7 +30,8 @@ import (
// collectPlugins scans for getter plugins. // collectPlugins scans for getter plugins.
// This will load plugins according to the environment. // This will load plugins according to the environment.
func collectPlugins(settings environment.EnvSettings) (Providers, error) { func collectPlugins(settings environment.EnvSettings) (Providers, error) {
plugins, err := plugin.FindPlugins(settings.PluginDirs()) pluginsDirs := []string{settings.PluginDirs(), settings.SystemPluginsDir}
plugins, err := plugin.FindPlugins(pluginsDirs)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -28,7 +28,6 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/client-go/util/homedir" "k8s.io/client-go/util/homedir"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
) )
@ -47,6 +46,8 @@ type EnvSettings struct {
KubeContext string KubeContext string
// Debug indicates whether or not Helm is running in Debug mode. // Debug indicates whether or not Helm is running in Debug mode.
Debug bool Debug bool
// SystemPluginsDir system dir plugin
SystemPluginsDir string
} }
// AddFlags binds flags to the given flagset. // AddFlags binds flags to the given flagset.
@ -55,6 +56,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringVarP(&s.Namespace, "namespace", "n", "", "namespace scope for this request") fs.StringVarP(&s.Namespace, "namespace", "n", "", "namespace scope for this request")
fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file")
fs.StringVar(&s.KubeContext, "kube-context", "", "name of the kubeconfig context to use") fs.StringVar(&s.KubeContext, "kube-context", "", "name of the kubeconfig context to use")
fs.StringVar(&s.SystemPluginsDir, "system-plugins-dir", "/usr/share", "path system plugins configfiles")
fs.BoolVar(&s.Debug, "debug", false, "enable verbose output") fs.BoolVar(&s.Debug, "debug", false, "enable verbose output")
} }

@ -18,12 +18,12 @@ package environment
import ( import (
"os" "os"
"reflect"
"strings" "strings"
"testing" "testing"
"k8s.io/helm/pkg/helm/helmpath"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/helm/pkg/helm/helmpath"
) )
func TestEnvSettings(t *testing.T) { func TestEnvSettings(t *testing.T) {
@ -90,7 +90,7 @@ func TestEnvSettings(t *testing.T) {
if settings.Home != helmpath.Home(tt.home) { if settings.Home != helmpath.Home(tt.home) {
t.Errorf("expected home %q, got %q", tt.home, settings.Home) t.Errorf("expected home %q, got %q", tt.home, settings.Home)
} }
if settings.PluginDirs() != tt.plugins { if !reflect.DeepEqual(settings.PluginDirs(), tt.plugins) {
t.Errorf("expected plugins %q, got %q", tt.plugins, settings.PluginDirs()) t.Errorf("expected plugins %q, got %q", tt.plugins, settings.PluginDirs())
} }
if settings.Debug != tt.debug { if settings.Debug != tt.debug {

@ -21,9 +21,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
helm_env "k8s.io/helm/pkg/helm/environment"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
helm_env "k8s.io/helm/pkg/helm/environment"
) )
const pluginFileName = "plugin.yaml" const pluginFileName = "plugin.yaml"
@ -148,15 +147,17 @@ func LoadAll(basedir string) ([]*Plugin, error) {
} }
// FindPlugins returns a list of YAML files that describe plugins. // FindPlugins returns a list of YAML files that describe plugins.
func FindPlugins(plugdirs string) ([]*Plugin, error) { func FindPlugins(plugdirs []string) ([]*Plugin, error) {
found := []*Plugin{} found := []*Plugin{}
// Let's get all UNIXy and allow path separators // Let's get all UNIXy and allow path separators
for _, p := range filepath.SplitList(plugdirs) { for _, pd := range plugdirs {
matches, err := LoadAll(p) for _, p := range filepath.SplitList(pd) {
if err != nil { matches, err := LoadAll(p)
return matches, err if err != nil {
return matches, err
}
found = append(found, matches...)
} }
found = append(found, matches...)
} }
return found, nil return found, nil
} }

Loading…
Cancel
Save