mirror of https://github.com/helm/helm
Signed-off-by: Matthew Fisher <matt.fisher@microsoft.com>pull/5514/head
parent
0b1caa14a7
commit
bdfe016b09
@ -1,90 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/cmd/helm/require"
|
||||
"helm.sh/helm/pkg/helmpath"
|
||||
"helm.sh/helm/pkg/plugin"
|
||||
"helm.sh/helm/pkg/plugin/installer"
|
||||
)
|
||||
|
||||
type pluginInstallOptions struct {
|
||||
source string
|
||||
version string
|
||||
home helmpath.Home
|
||||
}
|
||||
|
||||
const pluginInstallDesc = `
|
||||
This command allows you to install a plugin from a url to a VCS repo or a local path.
|
||||
|
||||
Example usage:
|
||||
$ helm plugin install https://github.com/technosophos/helm-template
|
||||
`
|
||||
|
||||
func newPluginInstallCmd(out io.Writer) *cobra.Command {
|
||||
o := &pluginInstallOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "install [options] <path|url>...",
|
||||
Short: "install one or more Helm plugins",
|
||||
Long: pluginInstallDesc,
|
||||
Args: require.ExactArgs(1),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return o.complete(args)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return o.run(out)
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&o.version, "version", "", "specify a version constraint. If this is not specified, the latest version is installed")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *pluginInstallOptions) complete(args []string) error {
|
||||
o.source = args[0]
|
||||
o.home = settings.Home
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *pluginInstallOptions) run(out io.Writer) error {
|
||||
installer.Debug = settings.Debug
|
||||
|
||||
i, err := installer.NewForSource(o.source, o.version, o.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); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Installed plugin: %s\n", p.Metadata.Name)
|
||||
return nil
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/pkg/helmpath"
|
||||
"helm.sh/helm/pkg/plugin"
|
||||
)
|
||||
|
||||
type pluginRemoveOptions struct {
|
||||
names []string
|
||||
home helmpath.Home
|
||||
}
|
||||
|
||||
func newPluginRemoveCmd(out io.Writer) *cobra.Command {
|
||||
o := &pluginRemoveOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "remove <plugin>...",
|
||||
Short: "remove one or more Helm plugins",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return o.complete(args)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return o.run(out)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *pluginRemoveOptions) complete(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("please provide plugin name to remove")
|
||||
}
|
||||
o.names = args
|
||||
o.home = settings.Home
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *pluginRemoveOptions) run(out io.Writer) error {
|
||||
debug("loading installed plugins from %s", settings.PluginDirs())
|
||||
plugins, err := findPlugins(settings.PluginDirs())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errorPlugins []string
|
||||
for _, name := range o.names {
|
||||
if found := findPlugin(plugins, name); found != nil {
|
||||
if err := removePlugin(found); err != nil {
|
||||
errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to remove plugin %s, got error (%v)", name, err))
|
||||
} else {
|
||||
fmt.Fprintf(out, "Removed plugin: %s\n", name)
|
||||
}
|
||||
} else {
|
||||
errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name))
|
||||
}
|
||||
}
|
||||
if len(errorPlugins) > 0 {
|
||||
return errors.Errorf(strings.Join(errorPlugins, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removePlugin(p *plugin.Plugin) error {
|
||||
if err := os.RemoveAll(p.Dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return runHook(p, plugin.Delete)
|
||||
}
|
||||
|
||||
func findPlugin(plugins []*plugin.Plugin, name string) *plugin.Plugin {
|
||||
for _, p := range plugins {
|
||||
if p.Metadata.Name == name {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/pkg/helmpath"
|
||||
"helm.sh/helm/pkg/plugin"
|
||||
"helm.sh/helm/pkg/plugin/installer"
|
||||
)
|
||||
|
||||
type pluginUpdateOptions struct {
|
||||
names []string
|
||||
home helmpath.Home
|
||||
}
|
||||
|
||||
func newPluginUpdateCmd(out io.Writer) *cobra.Command {
|
||||
o := &pluginUpdateOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <plugin>...",
|
||||
Short: "update one or more Helm plugins",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return o.complete(args)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return o.run(out)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *pluginUpdateOptions) complete(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("please provide plugin name to update")
|
||||
}
|
||||
o.names = args
|
||||
o.home = settings.Home
|
||||
return nil
|
||||
}
|
||||
|
||||
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())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errorPlugins []string
|
||||
|
||||
for _, name := range o.names {
|
||||
if found := findPlugin(plugins, name); found != nil {
|
||||
if err := updatePlugin(found, o.home); err != nil {
|
||||
errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err))
|
||||
} else {
|
||||
fmt.Fprintf(out, "Updated plugin: %s\n", name)
|
||||
}
|
||||
} else {
|
||||
errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name))
|
||||
}
|
||||
}
|
||||
if len(errorPlugins) > 0 {
|
||||
return errors.Errorf(strings.Join(errorPlugins, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePlugin(p *plugin.Plugin, home helmpath.Home) error {
|
||||
exactLocation, err := filepath.EvalSymlinks(p.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
absExactLocation, err := filepath.Abs(exactLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i, err := installer.FindSource(absExactLocation, home)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := installer.Update(i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
debug("loading plugin from %s", i.Path())
|
||||
updatedPlugin, err := plugin.LoadDir(i.Path())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runHook(updatedPlugin, plugin.Update)
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 osutil // import "helm.sh/helm/pkg/osutil"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsExecutable determines whether or not a particular file is executable. If there is an error, it
|
||||
// will be of type *PathError.
|
||||
func IsExecutable(fullPath string) (bool, error) {
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
fileExt := strings.ToLower(filepath.Ext(fullPath))
|
||||
|
||||
switch fileExt {
|
||||
case ".bat", ".cmd", ".com", ".exe", ".ps1":
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/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
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/helm/pkg/plugin/installer"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"helm.sh/helm/pkg/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))
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/helm/pkg/plugin/installer"
|
@ -1,210 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/helm/pkg/plugin/installer"
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/pkg/cli"
|
||||
"helm.sh/helm/pkg/getter"
|
||||
"helm.sh/helm/pkg/helmpath"
|
||||
"helm.sh/helm/pkg/plugin/cache"
|
||||
)
|
||||
|
||||
// HTTPInstaller installs plugins from an archive served by a web server.
|
||||
type HTTPInstaller struct {
|
||||
CacheDir string
|
||||
PluginName string
|
||||
base
|
||||
extractor Extractor
|
||||
getter getter.Getter
|
||||
}
|
||||
|
||||
// TarGzExtractor extracts gzip compressed tar archives
|
||||
type TarGzExtractor struct{}
|
||||
|
||||
// Extractor provides an interface for extracting archives
|
||||
type Extractor interface {
|
||||
Extract(buffer *bytes.Buffer, targetDir string) error
|
||||
}
|
||||
|
||||
// Extractors contains a map of suffixes and matching implementations of extractor to return
|
||||
var Extractors = map[string]Extractor{
|
||||
".tar.gz": &TarGzExtractor{},
|
||||
".tgz": &TarGzExtractor{},
|
||||
}
|
||||
|
||||
// NewExtractor creates a new extractor matching the source file name
|
||||
func NewExtractor(source string) (Extractor, error) {
|
||||
for suffix, extractor := range Extractors {
|
||||
if strings.HasSuffix(source, suffix) {
|
||||
return extractor, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Errorf("no extractor implemented yet for %s", source)
|
||||
}
|
||||
|
||||
// NewHTTPInstaller creates a new HttpInstaller.
|
||||
func NewHTTPInstaller(source string, home helmpath.Home) (*HTTPInstaller, error) {
|
||||
|
||||
key, err := cache.Key(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extractor, err := NewExtractor(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
getConstructor, err := getter.ByScheme("http", cli.EnvSettings{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
get, err := getConstructor.New(source, "", "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i := &HTTPInstaller{
|
||||
CacheDir: home.Path("cache", "plugins", key),
|
||||
PluginName: stripPluginName(filepath.Base(source)),
|
||||
base: newBase(source, home),
|
||||
extractor: extractor,
|
||||
getter: get,
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// helper that relies on some sort of convention for plugin name (plugin-name-<version>)
|
||||
func stripPluginName(name string) string {
|
||||
var strippedName string
|
||||
for suffix := range Extractors {
|
||||
if strings.HasSuffix(name, suffix) {
|
||||
strippedName = strings.TrimSuffix(name, suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
re := regexp.MustCompile(`(.*)-[0-9]+\..*`)
|
||||
return re.ReplaceAllString(strippedName, `$1`)
|
||||
}
|
||||
|
||||
// Install downloads and extracts the tarball into the cache directory and creates a symlink to the plugin directory in $HELM_HOME.
|
||||
//
|
||||
// Implements Installer.
|
||||
func (i *HTTPInstaller) Install() error {
|
||||
|
||||
pluginData, err := i.getter.Get(i.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = i.extractor.Extract(pluginData, i.CacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isPlugin(i.CacheDir) {
|
||||
return ErrMissingMetadata
|
||||
}
|
||||
|
||||
src, err := filepath.Abs(i.CacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.link(src)
|
||||
}
|
||||
|
||||
// Update updates a local repository
|
||||
// Not implemented for now since tarball most likely will be packaged by version
|
||||
func (i *HTTPInstaller) Update() error {
|
||||
return errors.Errorf("method Update() not implemented for HttpInstaller")
|
||||
}
|
||||
|
||||
// Override link because we want to use HttpInstaller.Path() not base.Path()
|
||||
func (i *HTTPInstaller) link(from string) error {
|
||||
debug("symlinking %s to %s", from, i.Path())
|
||||
return os.Symlink(from, i.Path())
|
||||
}
|
||||
|
||||
// Path is overridden because we want to join on the plugin name not the file name
|
||||
func (i HTTPInstaller) Path() string {
|
||||
if i.base.Source == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(i.base.HelmHome.Plugins(), i.PluginName)
|
||||
}
|
||||
|
||||
// Extract extracts compressed archives
|
||||
//
|
||||
// Implements Extractor.
|
||||
func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error {
|
||||
uncompressedStream, err := gzip.NewReader(buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tarReader := tar.NewReader(uncompressedStream)
|
||||
|
||||
os.MkdirAll(targetDir, 0755)
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := filepath.Join(targetDir, header.Name)
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.Mkdir(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
outFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
outFile.Close()
|
||||
return err
|
||||
}
|
||||
outFile.Close()
|
||||
default:
|
||||
return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/helm/pkg/plugin/installer"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/pkg/helmpath"
|
||||
)
|
||||
|
||||
var _ Installer = new(HTTPInstaller)
|
||||
|
||||
// Fake http client
|
||||
type TestHTTPGetter struct {
|
||||
MockResponse *bytes.Buffer
|
||||
MockError error
|
||||
}
|
||||
|
||||
func (t *TestHTTPGetter) Get(href string) (*bytes.Buffer, error) { return t.MockResponse, t.MockError }
|
||||
|
||||
// Fake plugin tarball data
|
||||
var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA="
|
||||
|
||||
func TestStripName(t *testing.T) {
|
||||
if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" {
|
||||
t.Errorf("name does not match expected value")
|
||||
}
|
||||
if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" {
|
||||
t.Errorf("name does not match expected value")
|
||||
}
|
||||
if stripPluginName("fake-plugin.tgz") != "fake-plugin" {
|
||||
t.Errorf("name does not match expected value")
|
||||
}
|
||||
if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" {
|
||||
t.Errorf("name does not match expected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPInstaller(t *testing.T) {
|
||||
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
|
||||
hh, err := ioutil.TempDir("", "helm-home-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(hh)
|
||||
|
||||
home := helmpath.Home(hh)
|
||||
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
|
||||
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
|
||||
}
|
||||
|
||||
i, err := NewForSource(source, "0.0.1", home)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// ensure a HTTPInstaller was returned
|
||||
httpInstaller, ok := i.(*HTTPInstaller)
|
||||
if !ok {
|
||||
t.Error("expected a HTTPInstaller")
|
||||
}
|
||||
|
||||
// inject fake http client responding with minimal plugin tarball
|
||||
mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not decode fake tgz plugin: %s", err)
|
||||
}
|
||||
|
||||
httpInstaller.getter = &TestHTTPGetter{
|
||||
MockResponse: bytes.NewBuffer(mockTgz),
|
||||
}
|
||||
|
||||
// install the plugin
|
||||
if err := Install(i); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if i.Path() != home.Path("plugins", "fake-plugin") {
|
||||
t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path())
|
||||
}
|
||||
|
||||
// Install again to test plugin exists error
|
||||
if err := Install(i); err == nil {
|
||||
t.Error("expected error for plugin exists, got none")
|
||||
} else if err.Error() != "plugin already exists" {
|
||||
t.Errorf("expected error for plugin exists, got (%v)", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHTTPInstallerNonExistentVersion(t *testing.T) {
|
||||
source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz"
|
||||
hh, err := ioutil.TempDir("", "helm-home-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(hh)
|
||||
|
||||
home := helmpath.Home(hh)
|
||||
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
|
||||
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
|
||||
}
|
||||
|
||||
i, err := NewForSource(source, "0.0.2", home)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// ensure a HTTPInstaller was returned
|
||||
httpInstaller, ok := i.(*HTTPInstaller)
|
||||
if !ok {
|
||||
t.Error("expected a HTTPInstaller")
|
||||
}
|
||||
|
||||
// inject fake http client responding with error
|
||||
httpInstaller.getter = &TestHTTPGetter{
|
||||
MockError: errors.Errorf("failed to download plugin for some reason"),
|
||||
}
|
||||
|
||||
// attempt to install the plugin
|
||||
if err := Install(i); err == nil {
|
||||
t.Error("expected error from http client")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHTTPInstallerUpdate(t *testing.T) {
|
||||
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
|
||||
hh, err := ioutil.TempDir("", "helm-home-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(hh)
|
||||
|
||||
home := helmpath.Home(hh)
|
||||
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
|
||||
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
|
||||
}
|
||||
|
||||
i, err := NewForSource(source, "0.0.1", home)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// ensure a HTTPInstaller was returned
|
||||
httpInstaller, ok := i.(*HTTPInstaller)
|
||||
if !ok {
|
||||
t.Error("expected a HTTPInstaller")
|
||||
}
|
||||
|
||||
// inject fake http client responding with minimal plugin tarball
|
||||
mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not decode fake tgz plugin: %s", err)
|
||||
}
|
||||
|
||||
httpInstaller.getter = &TestHTTPGetter{
|
||||
MockResponse: bytes.NewBuffer(mockTgz),
|
||||
}
|
||||
|
||||
// install the plugin before updating
|
||||
if err := Install(i); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if i.Path() != home.Path("plugins", "fake-plugin") {
|
||||
t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path())
|
||||
}
|
||||
|
||||
// Update plugin, should fail because it is not implemented
|
||||
if err := Update(i); err == nil {
|
||||
t.Error("update method not implemented for http installer")
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/pkg/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
|
||||
// Update updates a plugin to $HELM_HOME.
|
||||
Update() error
|
||||
}
|
||||
|
||||
// Install installs a plugin to $HELM_HOME.
|
||||
func Install(i Installer) error {
|
||||
if _, pathErr := os.Stat(path.Dir(i.Path())); os.IsNotExist(pathErr) {
|
||||
return errors.New(`plugin home "$HELM_HOME/plugins" does not exist`)
|
||||
}
|
||||
|
||||
if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) {
|
||||
return errors.New("plugin already exists")
|
||||
}
|
||||
|
||||
return i.Install()
|
||||
}
|
||||
|
||||
// Update updates a plugin in $HELM_HOME.
|
||||
func Update(i Installer) error {
|
||||
if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
|
||||
return errors.New("plugin does not exist")
|
||||
}
|
||||
|
||||
return i.Update()
|
||||
}
|
||||
|
||||
// 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)
|
||||
} else if isRemoteHTTPArchive(source) {
|
||||
return NewHTTPInstaller(source, home)
|
||||
}
|
||||
return NewVCSInstaller(source, version, home)
|
||||
}
|
||||
|
||||
// FindSource determines the correct Installer for the given source.
|
||||
func FindSource(location string, home helmpath.Home) (Installer, error) {
|
||||
installer, err := existingVCSRepo(location, home)
|
||||
if err != nil && err.Error() == "Cannot detect VCS" {
|
||||
return installer, errors.New("cannot get information about plugin source")
|
||||
}
|
||||
return installer, err
|
||||
}
|
||||
|
||||
// isLocalReference checks if the source exists on the filesystem.
|
||||
func isLocalReference(source string) bool {
|
||||
_, err := os.Stat(source)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// isRemoteHTTPArchive checks if the source is a http/https url and is an archive
|
||||
func isRemoteHTTPArchive(source string) bool {
|
||||
if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") {
|
||||
for suffix := range Extractors {
|
||||
if strings.HasSuffix(source, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/helm/pkg/plugin/installer"
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/pkg/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) {
|
||||
src, err := filepath.Abs(source)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get absolute path to plugin")
|
||||
}
|
||||
i := &LocalInstaller{
|
||||
base: newBase(src, 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
|
||||
}
|
||||
return i.link(i.Source)
|
||||
}
|
||||
|
||||
// Update updates a local repository
|
||||
func (i *LocalInstaller) Update() error {
|
||||
debug("local repository is auto-updated")
|
||||
return nil
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/helm/pkg/plugin/installer"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/pkg/helmpath"
|
||||
)
|
||||
|
||||
var _ Installer = new(LocalInstaller)
|
||||
|
||||
func TestLocalInstaller(t *testing.T) {
|
||||
hh, err := ioutil.TempDir("", "helm-home-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(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.RemoveAll(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())
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/helm/pkg/plugin/installer"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/Masterminds/vcs"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/pkg/helmpath"
|
||||
"helm.sh/helm/pkg/plugin/cache"
|
||||
)
|
||||
|
||||
// VCSInstaller installs plugins from remote a repository.
|
||||
type VCSInstaller struct {
|
||||
Repo vcs.Repo
|
||||
Version string
|
||||
base
|
||||
}
|
||||
|
||||
func existingVCSRepo(location string, home helmpath.Home) (Installer, error) {
|
||||
repo, err := vcs.NewRepo("", location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &VCSInstaller{
|
||||
Repo: repo,
|
||||
base: newBase(repo.Remote(), home),
|
||||
}
|
||||
return i, err
|
||||
}
|
||||
|
||||
// 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 ref != "" {
|
||||
if err := i.setVersion(i.Repo, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !isPlugin(i.Repo.LocalPath()) {
|
||||
return ErrMissingMetadata
|
||||
}
|
||||
|
||||
return i.link(i.Repo.LocalPath())
|
||||
}
|
||||
|
||||
// Update updates a remote repository
|
||||
func (i *VCSInstaller) Update() error {
|
||||
debug("updating %s", i.Repo.Remote())
|
||||
if i.Repo.IsDirty() {
|
||||
return errors.New("plugin repo was modified")
|
||||
}
|
||||
if err := i.Repo.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !isPlugin(i.Repo.LocalPath()) {
|
||||
return ErrMissingMetadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
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 "", errors.Errorf("requested version %q does not exist for plugin %q", i.Version, i.Repo.Remote())
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if v, err := semver.NewVersion(r); err == nil {
|
||||
sv = append(sv, v)
|
||||
}
|
||||
}
|
||||
return sv
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
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 "helm.sh/helm/pkg/plugin/installer"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/Masterminds/vcs"
|
||||
|
||||
"helm.sh/helm/pkg/helmpath"
|
||||
)
|
||||
|
||||
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.RemoveAll(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"
|
||||
testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo")
|
||||
repo := &testRepo{
|
||||
local: testRepoPath,
|
||||
tags: []string{"0.1.0", "0.1.1"},
|
||||
}
|
||||
|
||||
i, err := NewForSource(source, "~0.1.0", home)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// ensure a VCSInstaller was returned
|
||||
vcsInstaller, ok := i.(*VCSInstaller)
|
||||
if !ok {
|
||||
t.Fatal("expected a VCSInstaller")
|
||||
}
|
||||
|
||||
// set the testRepo in the VCSInstaller
|
||||
vcsInstaller.Repo = repo
|
||||
|
||||
if err := Install(i); err != nil {
|
||||
t.Fatal(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())
|
||||
}
|
||||
|
||||
// Install again to test plugin exists error
|
||||
if err := Install(i); err == nil {
|
||||
t.Error("expected error for plugin exists, got none")
|
||||
} else if err.Error() != "plugin already exists" {
|
||||
t.Errorf("expected error for plugin exists, got (%v)", err)
|
||||
}
|
||||
|
||||
//Testing FindSource method, expect error because plugin code is not a cloned repository
|
||||
if _, err := FindSource(i.Path(), home); err == nil {
|
||||
t.Error("expected error for inability to find plugin source, got none")
|
||||
} else if err.Error() != "cannot get information about plugin source" {
|
||||
t.Errorf("expected error for inability to find plugin source, got (%v)", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVCSInstallerNonExistentVersion(t *testing.T) {
|
||||
hh, err := ioutil.TempDir("", "helm-home-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(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"
|
||||
version := "0.2.0"
|
||||
|
||||
i, err := NewForSource(source, version, home)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// ensure a VCSInstaller was returned
|
||||
_, ok := i.(*VCSInstaller)
|
||||
if !ok {
|
||||
t.Fatal("expected a VCSInstaller")
|
||||
}
|
||||
|
||||
if err := Install(i); err == nil {
|
||||
t.Error("expected error for version does not exists, got none")
|
||||
} else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) {
|
||||
t.Errorf("expected error for version does not exists, got (%v)", err)
|
||||
}
|
||||
}
|
||||
func TestVCSInstallerUpdate(t *testing.T) {
|
||||
|
||||
hh, err := ioutil.TempDir("", "helm-home-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(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"
|
||||
|
||||
i, err := NewForSource(source, "", home)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// ensure a VCSInstaller was returned
|
||||
_, ok := i.(*VCSInstaller)
|
||||
if !ok {
|
||||
t.Fatal("expected a VCSInstaller")
|
||||
}
|
||||
|
||||
if err := Update(i); err == nil {
|
||||
t.Fatal("expected error for plugin does not exist, got none")
|
||||
} else if err.Error() != "plugin does not exist" {
|
||||
t.Fatalf("expected error for plugin does not exist, got (%v)", err)
|
||||
}
|
||||
|
||||
// Install plugin before update
|
||||
if err := Install(i); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test FindSource method for positive result
|
||||
pluginInfo, err := FindSource(i.Path(), home)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
repoRemote := pluginInfo.(*VCSInstaller).Repo.Remote()
|
||||
if repoRemote != source {
|
||||
t.Fatalf("invalid source found, expected %q got %q", source, repoRemote)
|
||||
}
|
||||
|
||||
// Update plugin
|
||||
if err := Update(i); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test update failure
|
||||
os.Remove(filepath.Join(i.Path(), "plugin.yaml"))
|
||||
// Testing update for error
|
||||
if err := Update(i); err == nil {
|
||||
t.Error("expected error for plugin modified, got none")
|
||||
} else if err.Error() != "plugin repo was modified" {
|
||||
t.Errorf("expected error for plugin modified, got (%v)", err)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in new issue