github.com/koderover/helm@v2.17.0+incompatible/pkg/plugin/plugin.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package plugin // import "k8s.io/helm/pkg/plugin"
    17  
    18  import (
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  
    25  	"github.com/ghodss/yaml"
    26  	yaml2 "gopkg.in/yaml.v2"
    27  
    28  	helm_env "k8s.io/helm/pkg/helm/environment"
    29  )
    30  
    31  const pluginFileName = "plugin.yaml"
    32  
    33  // Downloaders represents the plugins capability if it can retrieve
    34  // charts from special sources
    35  type Downloaders struct {
    36  	// Protocols are the list of schemes from the charts URL.
    37  	Protocols []string `json:"protocols" yaml:"protocols"`
    38  	// Command is the executable path with which the plugin performs
    39  	// the actual download for the corresponding Protocols
    40  	Command string `json:"command" yaml:"command"`
    41  }
    42  
    43  // Metadata describes a plugin.
    44  //
    45  // This is the plugin equivalent of a chart.Metadata.
    46  type Metadata struct {
    47  	// Name is the name of the plugin
    48  	Name string `json:"name" yaml:"name"`
    49  
    50  	// Version is a SemVer 2 version of the plugin.
    51  	Version string `json:"version" yaml:"version"`
    52  
    53  	// Usage is the single-line usage text shown in help
    54  	Usage string `json:"usage" yaml:"usage"`
    55  
    56  	// Description is a long description shown in places like `helm help`
    57  	Description string `json:"description" yaml:"description"`
    58  
    59  	// Command is the command, as a single string.
    60  	//
    61  	// The command will be passed through environment expansion, so env vars can
    62  	// be present in this command. Unless IgnoreFlags is set, this will
    63  	// also merge the flags passed from Helm.
    64  	//
    65  	// Note that command is not executed in a shell. To do so, we suggest
    66  	// pointing the command to a shell script.
    67  	Command string `json:"command" yaml:"command"`
    68  
    69  	// IgnoreFlags ignores any flags passed in from Helm
    70  	//
    71  	// For example, if the plugin is invoked as `helm --debug myplugin`, if this
    72  	// is false, `--debug` will be appended to `--command`. If this is true,
    73  	// the `--debug` flag will be discarded.
    74  	IgnoreFlags bool `json:"ignoreFlags" yaml:"ignoreFlags,omitempty"`
    75  
    76  	// UseTunnel indicates that this command needs a tunnel.
    77  	// Setting this will cause a number of side effects, such as the
    78  	// automatic setting of HELM_HOST.
    79  	UseTunnel bool `json:"useTunnel" yaml:"useTunnel,omitempty"`
    80  
    81  	// Hooks are commands that will run on events.
    82  	Hooks Hooks
    83  
    84  	// Downloaders field is used if the plugin supply downloader mechanism
    85  	// for special protocols.
    86  	Downloaders []Downloaders `json:"downloaders" yaml:"downloaders"`
    87  }
    88  
    89  // Plugin represents a plugin.
    90  type Plugin struct {
    91  	// Metadata is a parsed representation of a plugin.yaml
    92  	Metadata *Metadata
    93  	// Dir is the string path to the directory that holds the plugin.
    94  	Dir string
    95  }
    96  
    97  // PrepareCommand takes a Plugin.Command and prepares it for execution.
    98  //
    99  // It merges extraArgs into any arguments supplied in the plugin. It
   100  // returns the name of the command and an args array.
   101  //
   102  // The result is suitable to pass to exec.Command.
   103  func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string) {
   104  	parts := strings.Split(os.ExpandEnv(p.Metadata.Command), " ")
   105  	main := parts[0]
   106  	baseArgs := []string{}
   107  	if len(parts) > 1 {
   108  		baseArgs = parts[1:]
   109  	}
   110  	if !p.Metadata.IgnoreFlags {
   111  		baseArgs = append(baseArgs, extraArgs...)
   112  	}
   113  	return main, baseArgs
   114  }
   115  
   116  // LoadDir loads a plugin from the given directory.
   117  func LoadDir(dirname string) (*Plugin, error) {
   118  	data, err := ioutil.ReadFile(filepath.Join(dirname, pluginFileName))
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	plug := &Plugin{Dir: dirname}
   124  	if err := validateMeta(data); err != nil {
   125  		return nil, err
   126  	}
   127  	if err := yaml.Unmarshal(data, &plug.Metadata); err != nil {
   128  		return nil, err
   129  	}
   130  	return plug, nil
   131  }
   132  
   133  func validateMeta(data []byte) error {
   134  	// This is done ONLY for validation. We need to use ghodss/yaml for the actual parsing.
   135  	return yaml2.UnmarshalStrict(data, &Metadata{})
   136  }
   137  
   138  // LoadAll loads all plugins found beneath the base directory.
   139  //
   140  // This scans only one directory level.
   141  func LoadAll(basedir string) ([]*Plugin, error) {
   142  	plugins := []*Plugin{}
   143  	// We want basedir/*/plugin.yaml
   144  	scanpath := filepath.Join(basedir, "*", pluginFileName)
   145  	matches, err := filepath.Glob(scanpath)
   146  	if err != nil {
   147  		return plugins, err
   148  	}
   149  
   150  	if matches == nil {
   151  		return plugins, nil
   152  	}
   153  
   154  	loaded := map[string]bool{}
   155  	for _, yaml := range matches {
   156  		dir := filepath.Dir(yaml)
   157  		p, err := LoadDir(dir)
   158  		pname := p.Metadata.Name
   159  		if err != nil {
   160  			return plugins, err
   161  		}
   162  
   163  		if _, ok := loaded[pname]; ok {
   164  			fmt.Fprintf(os.Stderr, "A plugin named %q already exists. Skipping.", pname)
   165  			continue
   166  		}
   167  
   168  		plugins = append(plugins, p)
   169  		loaded[pname] = true
   170  	}
   171  	return plugins, nil
   172  }
   173  
   174  // FindPlugins returns a list of YAML files that describe plugins.
   175  func FindPlugins(plugdirs string) ([]*Plugin, error) {
   176  	found := []*Plugin{}
   177  	// Let's get all UNIXy and allow path separators
   178  	for _, p := range filepath.SplitList(plugdirs) {
   179  		matches, err := LoadAll(p)
   180  		if err != nil {
   181  			return matches, err
   182  		}
   183  		found = append(found, matches...)
   184  	}
   185  	return found, nil
   186  }
   187  
   188  // SetupPluginEnv prepares os.Env for plugins. It operates on os.Env because
   189  // the plugin subsystem itself needs access to the environment variables
   190  // created here.
   191  func SetupPluginEnv(settings helm_env.EnvSettings,
   192  	shortName, base string) {
   193  	for key, val := range map[string]string{
   194  		"HELM_PLUGIN_NAME": shortName,
   195  		"HELM_PLUGIN_DIR":  base,
   196  		"HELM_BIN":         os.Args[0],
   197  
   198  		// Set vars that may not have been set, and save client the
   199  		// trouble of re-parsing.
   200  		"HELM_PLUGIN": settings.PluginDirs(),
   201  		"HELM_HOME":   settings.Home.String(),
   202  
   203  		// Set vars that convey common information.
   204  		"HELM_PATH_REPOSITORY":       settings.Home.Repository(),
   205  		"HELM_PATH_REPOSITORY_FILE":  settings.Home.RepositoryFile(),
   206  		"HELM_PATH_CACHE":            settings.Home.Cache(),
   207  		"HELM_PATH_LOCAL_REPOSITORY": settings.Home.LocalRepository(),
   208  		"HELM_PATH_STARTER":          settings.Home.Starters(),
   209  
   210  		"TILLER_HOST":      settings.TillerHost,
   211  		"TILLER_NAMESPACE": settings.TillerNamespace,
   212  	} {
   213  		os.Setenv(key, val)
   214  	}
   215  
   216  	if settings.Debug {
   217  		os.Setenv("HELM_DEBUG", "1")
   218  	}
   219  }