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