mirror of https://github.com/helm/helm
Merge pull request #2225 from adamreese/feat/plugin-management
feat(helm): add plugin management commandspull/2229/head
commit
a19dee52c2
@ -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
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
Loading…
Reference in new issue