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"
"github.com/spf13/cobra"
"k8s.io/helm/internal/test"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"

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

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

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

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

@ -23,7 +23,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin"
"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 {
installer.Debug = settings.Debug
debug("loading installed plugins from %s", settings.PluginDirs())
plugins, err := findPlugins(settings.PluginDirs())
plugins, err := findPlugins([]string{settings.PluginDirs()})
if err != nil {
return err
}

@ -21,6 +21,10 @@ Helm plugins have the following features:
- They can be written in any programming language.
- They integrate with Helm, and will show up in `helm help` and other places.
## Plugins Backends
### Native
Helm plugins live in `$(helm home)/plugins`.
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
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
$ 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`
## 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
directory, and then a `plugin.yaml` file.
@ -73,7 +100,7 @@ ignoreFlags: false
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` 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`,
but will not handle `helm myplugin --help`.
## Downloader Plugins
By default, Helm is able to pull Charts using HTTP/S. As of Helm 2.4.0, plugins
## Downloader Native 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.
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
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
When Helm executes a plugin, it passes the outer environment to the plugin, and

@ -30,7 +30,8 @@ import (
// collectPlugins scans for getter plugins.
// This will load plugins according to the environment.
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 {
return nil, err
}

@ -28,7 +28,6 @@ import (
"github.com/spf13/pflag"
"k8s.io/client-go/util/homedir"
"k8s.io/helm/pkg/helm/helmpath"
)
@ -47,6 +46,8 @@ type EnvSettings struct {
KubeContext string
// Debug indicates whether or not Helm is running in Debug mode.
Debug bool
// SystemPluginsDir system dir plugin
SystemPluginsDir string
}
// 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.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.SystemPluginsDir, "system-plugins-dir", "/usr/share", "path system plugins configfiles")
fs.BoolVar(&s.Debug, "debug", false, "enable verbose output")
}

@ -18,12 +18,12 @@ package environment
import (
"os"
"reflect"
"strings"
"testing"
"k8s.io/helm/pkg/helm/helmpath"
"github.com/spf13/pflag"
"k8s.io/helm/pkg/helm/helmpath"
)
func TestEnvSettings(t *testing.T) {
@ -90,7 +90,7 @@ func TestEnvSettings(t *testing.T) {
if settings.Home != helmpath.Home(tt.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())
}
if settings.Debug != tt.debug {

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

Loading…
Cancel
Save