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