Merge pull request #2225 from adamreese/feat/plugin-management

feat(helm): add plugin management commands
pull/2229/head
Adam Reese 8 years ago committed by GitHub
commit a19dee52c2

@ -136,12 +136,16 @@ func newRootCmd(out io.Writer) *cobra.Command {
addFlagsTLS(newStatusCmd(nil, out)),
addFlagsTLS(newUpgradeCmd(nil, out)),
addFlagsTLS(newReleaseTestCmd(nil, out)),
addFlagsTLS(newResetCmd(nil, out)),
addFlagsTLS(newVersionCmd(nil, out)),
newCompletionCmd(out),
newHomeCmd(out),
newInitCmd(out),
addFlagsTLS(newResetCmd(nil, out)),
addFlagsTLS(newVersionCmd(nil, out)),
addFlagsTLS(newReleaseTestCmd(nil, out)),
newResetCmd(nil, out),
newVersionCmd(nil, out),
newReleaseTestCmd(nil, out),
newPluginCmd(out),
// Hidden documentation generator command: 'helm docs'
newDocsCmd(out),

@ -31,6 +31,13 @@ import (
const pluginEnvVar = "HELM_PLUGIN"
func pluginDirs(home helmpath.Home) string {
if dirs := os.Getenv(pluginEnvVar); dirs != "" {
return dirs
}
return home.Plugins()
}
// loadPlugins loads plugins into the command list.
//
// This follows a different pattern than the other commands because it has
@ -43,11 +50,7 @@ func loadPlugins(baseCmd *cobra.Command, home helmpath.Home, out io.Writer) {
return
}
plugdirs := os.Getenv(pluginEnvVar)
if plugdirs == "" {
plugdirs = home.Plugins()
}
plugdirs := pluginDirs(home)
found, err := findPlugins(plugdirs)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)

@ -0,0 +1,72 @@
/*
Copyright 2016 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 (
"fmt"
"io"
"os"
"os/exec"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin"
"github.com/spf13/cobra"
)
const pluginHelp = `
Manage client-side Helm plugins.
`
func newPluginCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "plugin",
Short: "add, list, or remove Helm plugins",
Long: pluginHelp,
}
cmd.AddCommand(
newPluginInstallCmd(out),
newPluginListCmd(out),
newPluginRemoveCmd(out),
)
return cmd
}
// runHook will execute a plugin hook.
func runHook(p *plugin.Plugin, event string, home helmpath.Home) error {
hook := p.Metadata.Hooks.Get(event)
if hook == "" {
return nil
}
prog := exec.Command("sh", "-c", hook)
// TODO make this work on windows
// I think its ... ¯\_(ツ)_/¯
// prog := exec.Command("cmd", "/C", p.Metadata.Hooks.Install())
debug("running %s hook: %s", event, prog)
setupEnv(p.Metadata.Name, p.Dir, home.Plugins(), home)
prog.Stdout, prog.Stderr = os.Stdout, os.Stderr
if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
os.Stderr.Write(eerr.Stderr)
return fmt.Errorf("plugin %s hook for %q exited with error", event, p.Metadata.Name)
}
return err
}
return nil
}

@ -0,0 +1,84 @@
/*
Copyright 2016 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 (
"fmt"
"io"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin"
"k8s.io/helm/pkg/plugin/installer"
"github.com/spf13/cobra"
)
type pluginInstallCmd struct {
source string
version string
home helmpath.Home
out io.Writer
}
func newPluginInstallCmd(out io.Writer) *cobra.Command {
pcmd := &pluginInstallCmd{out: out}
cmd := &cobra.Command{
Use: "install [options] <path|url>...",
Short: "install 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()
},
}
cmd.Flags().StringVar(&pcmd.version, "version", "", "specify a version constraint. If this is not specified, the latest version is installed")
return cmd
}
func (pcmd *pluginInstallCmd) complete(args []string) error {
if err := checkArgsLength(len(args), "plugin"); err != nil {
return err
}
pcmd.source = args[0]
pcmd.home = helmpath.Home(homePath())
return nil
}
func (pcmd *pluginInstallCmd) run() error {
installer.Debug = flagDebug
i, err := installer.NewForSource(pcmd.source, pcmd.version, pcmd.home)
if err != nil {
return err
}
if err := installer.Install(i); err != nil {
return err
}
debug("loading plugin from %s", i.Path())
p, err := plugin.LoadDir(i.Path())
if err != nil {
return err
}
if err := runHook(p, plugin.Install, pcmd.home); err != nil {
return err
}
fmt.Fprintf(pcmd.out, "Installed plugin: %s\n", p.Metadata.Name)
return nil
}

@ -0,0 +1,63 @@
/*
Copyright 2016 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 (
"fmt"
"io"
"k8s.io/helm/pkg/helm/helmpath"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
)
type pluginListCmd struct {
home helmpath.Home
out io.Writer
}
func newPluginListCmd(out io.Writer) *cobra.Command {
pcmd := &pluginListCmd{out: out}
cmd := &cobra.Command{
Use: "list",
Short: "list installed Helm plugins",
RunE: func(cmd *cobra.Command, args []string) error {
pcmd.home = helmpath.Home(homePath())
return pcmd.run()
},
}
return cmd
}
func (pcmd *pluginListCmd) run() error {
plugdirs := pluginDirs(pcmd.home)
debug("pluginDirs: %s", plugdirs)
plugins, err := findPlugins(plugdirs)
if err != nil {
return err
}
table := uitable.New()
table.AddRow("NAME", "VERSION", "DESCRIPTION")
for _, p := range plugins {
table.AddRow(p.Metadata.Name, p.Metadata.Version, p.Metadata.Description)
}
fmt.Fprintln(pcmd.out, table)
return nil
}

@ -0,0 +1,92 @@
/*
Copyright 2016 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 (
"fmt"
"io"
"os"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin"
"github.com/spf13/cobra"
)
type pluginRemoveCmd struct {
names []string
home helmpath.Home
out io.Writer
}
func newPluginRemoveCmd(out io.Writer) *cobra.Command {
pcmd := &pluginRemoveCmd{out: out}
cmd := &cobra.Command{
Use: "remove <plugin>...",
Short: "remove 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 *pluginRemoveCmd) complete(args []string) error {
if err := checkArgsLength(len(args), "plugin"); err != nil {
return err
}
pcmd.names = args
pcmd.home = helmpath.Home(homePath())
return nil
}
func (pcmd *pluginRemoveCmd) run() error {
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 := removePlugin(found, pcmd.home); err != nil {
return err
}
fmt.Fprintf(pcmd.out, "Removed plugin: %s\n", name)
}
}
return nil
}
func removePlugin(p *plugin.Plugin, home helmpath.Home) error {
if err := os.Remove(p.Dir); err != nil {
return err
}
return runHook(p, plugin.Delete, home)
}
func findPlugin(plugins []*plugin.Plugin, name string) *plugin.Plugin {
for _, p := range plugins {
if p.Metadata.Name == name {
return p
}
}
return nil
}

@ -17,6 +17,7 @@ limitations under the License.
package main
import (
"fmt"
"io"
"text/template"
"time"
@ -72,3 +73,10 @@ func tpl(t string, vals map[string]interface{}, out io.Writer) error {
}
return tt.Execute(out, vals)
}
func debug(format string, args ...interface{}) {
if flagDebug {
format = fmt.Sprintf("[debug] %s\n", format)
fmt.Printf(format, args...)
}
}

8
glide.lock generated

@ -1,5 +1,5 @@
hash: df0fa621e6a6f80dbfeb815d9d8aa308c50346a9821e401b19b6f10782da3774
updated: 2017-04-03T17:00:07.670429885-06:00
hash: 6a39d319e98b1b4305c48e9b718604b723184f27a1366efcedc42d95bcbeb0c8
updated: 2017-04-06T10:04:41.822904395-07:00
imports:
- name: cloud.google.com/go
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
@ -185,6 +185,8 @@ imports:
version: 3f0ab6d4ab4bed1c61caf056b63a6e62190c7801
- name: github.com/Masterminds/sprig
version: 23597e5f6ad0e4d590e71314bfd0251a4a3cf849
- name: github.com/Masterminds/vcs
version: 795e20f901c3d561de52811fb3488a2cb2c8588b
- name: github.com/mattn/go-runewidth
version: d6bea18f789704b5f83375793155289da36a3c7f
- name: github.com/mitchellh/go-wordwrap
@ -300,7 +302,7 @@ imports:
- name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4
- name: k8s.io/kubernetes
version: ea8f6637b639246faa14a8d5c6f864100fcb77a9
version: 114f8911f9597be669a747ab72787e0bd74c9359
subpackages:
- cmd/kubeadm/app/apis/kubeadm
- cmd/kubeadm/app/apis/kubeadm/install

@ -8,6 +8,8 @@ import:
version: f62e98d28ab7ad31d707ba837a966378465c7b57
- package: github.com/spf13/pflag
version: 5ccb023bc27df288a957c5e994cd44fd19619465
- package: github.com/Masterminds/vcs
version: ~1.11.0
- package: github.com/Masterminds/sprig
version: ^2.10
- package: github.com/ghodss/yaml

@ -0,0 +1,74 @@
/*
Copyright 2016 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 cache provides a key generator for vcs urls.
package cache // import "k8s.io/helm/pkg/plugin/cache"
import (
"net/url"
"regexp"
"strings"
)
// Thanks glide!
// scpSyntaxRe matches the SCP-like addresses used to access repos over SSH.
var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
// Key generates a cache key based on a url or scp string. The key is file
// system safe.
func Key(repo string) (string, error) {
var u *url.URL
var err error
var strip bool
if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil {
// Match SCP-like syntax and convert it to a URL.
// Eg, "git@github.com:user/repo" becomes
// "ssh://git@github.com/user/repo".
u = &url.URL{
Scheme: "ssh",
User: url.User(m[1]),
Host: m[2],
Path: "/" + m[3],
}
strip = true
} else {
u, err = url.Parse(repo)
if err != nil {
return "", err
}
}
if strip {
u.Scheme = ""
}
var key string
if u.Scheme != "" {
key = u.Scheme + "-"
}
if u.User != nil && u.User.Username() != "" {
key = key + u.User.Username() + "-"
}
key = key + u.Host
if u.Path != "" {
key = key + strings.Replace(u.Path, "/", "-", -1)
}
key = strings.Replace(key, ":", "-", -1)
return key, nil
}

@ -0,0 +1,33 @@
/*
Copyright 2016 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 plugin // import "k8s.io/helm/pkg/plugin"
// Types of hooks
const (
// Install is executed after the plugin is added.
Install = "install"
// Delete is executed after the plugin is removed.
Delete = "delete"
)
// Hooks is a map of events to commands.
type Hooks map[string]string
// Get returns a hook for an event.
func (hooks Hooks) Get(event string) string {
h, _ := hooks[event]
return h
}

@ -0,0 +1,48 @@
/*
Copyright 2016 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 installer // import "k8s.io/helm/pkg/plugin/installer"
import (
"os"
"path/filepath"
"k8s.io/helm/pkg/helm/helmpath"
)
type base struct {
// Source is the reference to a plugin
Source string
// HelmHome is the $HELM_HOME directory
HelmHome helmpath.Home
}
func newBase(source string, home helmpath.Home) base {
return base{source, home}
}
// link creates a symlink from the plugin source to $HELM_HOME.
func (b *base) link(from string) error {
debug("symlinking %s to %s", from, b.Path())
return os.Symlink(from, b.Path())
}
// Path is where the plugin will be symlinked to.
func (b *base) Path() string {
if b.Source == "" {
return ""
}
return filepath.Join(b.HelmHome.Plugins(), filepath.Base(b.Source))
}

@ -0,0 +1,17 @@
/*
Copyright 2016 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 installer provides an interface for installing Helm plugins.
package installer // import "k8s.io/helm/pkg/plugin/installer"

@ -0,0 +1,72 @@
/*
Copyright 2016 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 installer
import (
"errors"
"fmt"
"os"
"path/filepath"
"k8s.io/helm/pkg/helm/helmpath"
)
// ErrMissingMetadata indicates that plugin.yaml is missing.
var ErrMissingMetadata = errors.New("plugin metadata (plugin.yaml) missing")
// Debug enables verbose output.
var Debug bool
// Installer provides an interface for installing helm client plugins.
type Installer interface {
// Install adds a plugin to $HELM_HOME.
Install() error
// Path is the directory of the installed plugin.
Path() string
}
// Install installs a plugin to $HELM_HOME.
func Install(i Installer) error {
return i.Install()
}
// 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
if isLocalReference(source) {
return NewLocalInstaller(source, home)
}
return NewVCSInstaller(source, version, home)
}
// isLocalReference checks if the source exists on the filesystem.
func isLocalReference(source string) bool {
_, err := os.Stat(source)
return err == nil
}
// isPlugin checks if the directory contains a plugin.yaml file.
func isPlugin(dirname string) bool {
_, err := os.Stat(filepath.Join(dirname, "plugin.yaml"))
return err == nil
}
func debug(format string, args ...interface{}) {
if Debug {
format = fmt.Sprintf("[debug] %s\n", format)
fmt.Printf(format, args...)
}
}

@ -0,0 +1,49 @@
/*
Copyright 2016 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 installer // import "k8s.io/helm/pkg/plugin/installer"
import (
"path/filepath"
"k8s.io/helm/pkg/helm/helmpath"
)
// LocalInstaller installs plugins from the filesystem.
type LocalInstaller struct {
base
}
// NewLocalInstaller creates a new LocalInstaller.
func NewLocalInstaller(source string, home helmpath.Home) (*LocalInstaller, error) {
i := &LocalInstaller{
base: newBase(source, home),
}
return i, nil
}
// Install creates a symlink to the plugin directory in $HELM_HOME.
//
// Implements Installer.
func (i *LocalInstaller) Install() error {
if !isPlugin(i.Source) {
return ErrMissingMetadata
}
src, err := filepath.Abs(i.Source)
if err != nil {
return err
}
return i.link(src)
}

@ -0,0 +1,64 @@
/*
Copyright 2016 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 installer // import "k8s.io/helm/pkg/plugin/installer"
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"k8s.io/helm/pkg/helm/helmpath"
)
var _ Installer = new(LocalInstaller)
func TestLocalInstaller(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-home-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(hh)
home := helmpath.Home(hh)
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
}
// Make a temp dir
tdir, err := ioutil.TempDir("", "helm-installer-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tdir)
if err := ioutil.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil {
t.Fatal(err)
}
source := "../testdata/plugdir/echo"
i, err := NewForSource(source, "", home)
if err != nil {
t.Errorf("unexpected error: %s", err)
}
if err := Install(i); err != nil {
t.Error(err)
}
if i.Path() != home.Path("plugins", "echo") {
t.Errorf("expected path '$HELM_HOME/plugins/helm-env', got %q", i.Path())
}
}

@ -0,0 +1,145 @@
/*
Copyright 2016 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 installer // import "k8s.io/helm/pkg/plugin/installer"
import (
"os"
"sort"
"github.com/Masterminds/semver"
"github.com/Masterminds/vcs"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin/cache"
)
// VCSInstaller installs plugins from remote a repository.
type VCSInstaller struct {
Repo vcs.Repo
Version string
base
}
// NewVCSInstaller creates a new VCSInstaller.
func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) {
key, err := cache.Key(source)
if err != nil {
return nil, err
}
cachedpath := home.Path("cache", "plugins", key)
repo, err := vcs.NewRepo(source, cachedpath)
if err != nil {
return nil, err
}
i := &VCSInstaller{
Repo: repo,
Version: version,
base: newBase(source, home),
}
return i, err
}
// Install clones a remote repository and creates a symlink to the plugin directory in HELM_HOME.
//
// Implements Installer.
func (i *VCSInstaller) Install() error {
if err := i.sync(i.Repo); err != nil {
return err
}
ref, err := i.solveVersion(i.Repo)
if err != nil {
return err
}
if err := i.setVersion(i.Repo, ref); err != nil {
return err
}
if !isPlugin(i.Repo.LocalPath()) {
return ErrMissingMetadata
}
return i.link(i.Repo.LocalPath())
}
func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) {
if i.Version == "" {
return "", nil
}
if repo.IsReference(i.Version) {
return i.Version, nil
}
// Create the constraint first to make sure it's valid before
// working on the repo.
constraint, err := semver.NewConstraint(i.Version)
if err != nil {
return "", err
}
// Get the tags and branches (in that order)
refs, err := repo.Tags()
if err != nil {
return "", err
}
debug("found refs: %s", refs)
// Convert and filter the list to semver.Version instances
semvers := getSemVers(refs)
// Sort semver list
sort.Sort(sort.Reverse(semver.Collection(semvers)))
for _, v := range semvers {
if constraint.Check(v) {
// If the constrint passes get the original reference
ver := v.Original()
debug("setting to %s", ver)
return ver, nil
}
}
return "", nil
}
// setVersion attempts to checkout the version
func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error {
debug("setting version to %q", i.Version)
return repo.UpdateVersion(ref)
}
// sync will clone or update a remote repo.
func (i *VCSInstaller) sync(repo vcs.Repo) error {
if _, err := os.Stat(repo.LocalPath()); os.IsNotExist(err) {
debug("cloning %s to %s", repo.Remote(), repo.LocalPath())
return repo.Get()
}
debug("updating %s", repo.Remote())
return repo.Update()
}
// Filter a list of versions to only included semantic versions. The response
// is a mapping of the original version to the semantic version.
func getSemVers(refs []string) []*semver.Version {
var sv []*semver.Version
for _, r := range refs {
v, err := semver.NewVersion(r)
if err == nil {
sv = append(sv, v)
}
}
return sv
}

@ -0,0 +1,90 @@
/*
Copyright 2016 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 installer // import "k8s.io/helm/pkg/plugin/installer"
import (
"io/ioutil"
"os"
"testing"
"k8s.io/helm/pkg/helm/helmpath"
"github.com/Masterminds/vcs"
)
var _ Installer = new(VCSInstaller)
type testRepo struct {
local, remote, current string
tags, branches []string
err error
vcs.Repo
}
func (r *testRepo) LocalPath() string { return r.local }
func (r *testRepo) Remote() string { return r.remote }
func (r *testRepo) Update() error { return r.err }
func (r *testRepo) Get() error { return r.err }
func (r *testRepo) IsReference(string) bool { return false }
func (r *testRepo) Tags() ([]string, error) { return r.tags, r.err }
func (r *testRepo) Branches() ([]string, error) { return r.branches, r.err }
func (r *testRepo) UpdateVersion(version string) error {
r.current = version
return r.err
}
func TestVCSInstaller(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-home-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(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"
repo := &testRepo{
local: "../testdata/plugdir/echo",
tags: []string{"0.1.0", "0.1.1"},
}
i, err := NewForSource(source, "~0.1.0", home)
if err != nil {
t.Errorf("unexpected error: %s", err)
}
// ensure a VCSInstaller was returned
vcsInstaller, ok := i.(*VCSInstaller)
if !ok {
t.Error("expected a VCSInstaller")
}
// set the testRepo in the VCSInstaller
vcsInstaller.Repo = repo
if err := Install(i); err != nil {
t.Error(err)
}
if repo.current != "0.1.1" {
t.Errorf("expected version '0.1.1', got %q", repo.current)
}
if i.Path() != home.Path("plugins", "helm-env") {
t.Errorf("expected path '$HELM_HOME/plugins/helm-env', got %q", i.Path())
}
}

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package plugin
package plugin // import "k8s.io/helm/pkg/plugin"
import (
"io/ioutil"
@ -21,6 +21,7 @@ import (
"path/filepath"
"strings"
"github.com/Masterminds/vcs"
"github.com/ghodss/yaml"
)
@ -64,6 +65,9 @@ type Metadata struct {
// Setting this will cause a number of side effects, such as the
// automatic setting of HELM_HOST.
UseTunnel bool `json:"useTunnel"`
// Hooks are commands that will run on events.
Hooks Hooks
}
// Plugin represents a plugin.
@ -72,6 +76,17 @@ type Plugin struct {
Metadata *Metadata
// Dir is the string path to the directory that holds the plugin.
Dir string
// Remote is the remote repo location.
Remote string
}
func detectSource(dirname string) (string, error) {
if repo, err := vcs.NewRepo("", dirname); err == nil {
if repo.CheckLocal() {
return repo.Remote(), nil
}
}
return os.Readlink(dirname)
}
// PrepareCommand takes a Plugin.Command and prepares it for execution.
@ -101,6 +116,10 @@ func LoadDir(dirname string) (*Plugin, error) {
}
plug := &Plugin{Dir: dirname}
if src, err := detectSource(dirname); err == nil {
plug.Remote = src
}
if err := yaml.Unmarshal(data, &plug.Metadata); err != nil {
return nil, err
}

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package plugin
package plugin // import "k8s.io/helm/pkg/plugin"
import (
"reflect"
@ -82,6 +82,9 @@ func TestLoadDir(t *testing.T) {
Command: "$HELM_PLUGIN_SELF/hello.sh",
UseTunnel: true,
IgnoreFlags: true,
Hooks: map[string]string{
Install: "echo installing...",
},
}
if !reflect.DeepEqual(expect, plug.Metadata) {

@ -4,3 +4,5 @@ usage: "echo something"
description: |-
This is a testing fixture.
command: "echo Hello"
hooks:
install: "echo Installing"

@ -6,3 +6,6 @@ description: |-
command: "$HELM_PLUGIN_SELF/hello.sh"
useTunnel: true
ignoreFlags: true
install: "echo installing..."
hooks:
install: "echo installing..."

Loading…
Cancel
Save