github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/helper/pluginutils/loader/init.go (about)

     1  package loader
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"sort"
     9  
    10  	multierror "github.com/hashicorp/go-multierror"
    11  	plugin "github.com/hashicorp/go-plugin"
    12  	version "github.com/hashicorp/go-version"
    13  	"github.com/hashicorp/nomad/helper/pluginutils/hclspecutils"
    14  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
    15  	"github.com/hashicorp/nomad/nomad/structs/config"
    16  	"github.com/hashicorp/nomad/plugins/base"
    17  	"github.com/zclconf/go-cty/cty/msgpack"
    18  )
    19  
    20  // validateConfig returns whether or not the configuration is valid
    21  func validateConfig(config *PluginLoaderConfig) error {
    22  	var mErr multierror.Error
    23  	if config == nil {
    24  		return fmt.Errorf("nil config passed")
    25  	} else if config.Logger == nil {
    26  		multierror.Append(&mErr, fmt.Errorf("nil logger passed"))
    27  	}
    28  
    29  	// Validate that all plugins have a binary name
    30  	for _, c := range config.Configs {
    31  		if c.Name == "" {
    32  			multierror.Append(&mErr, fmt.Errorf("plugin config passed without binary name"))
    33  		}
    34  	}
    35  
    36  	// Validate internal plugins
    37  	for k, config := range config.InternalPlugins {
    38  		// Validate config
    39  		if config == nil {
    40  			multierror.Append(&mErr, fmt.Errorf("nil config passed for internal plugin %s", k))
    41  			continue
    42  		} else if config.Factory == nil {
    43  			multierror.Append(&mErr, fmt.Errorf("nil factory passed for internal plugin %s", k))
    44  			continue
    45  		}
    46  	}
    47  
    48  	return mErr.ErrorOrNil()
    49  }
    50  
    51  // init initializes the plugin loader by compiling both internal and external
    52  // plugins and selecting the highest versioned version of any given plugin.
    53  func (l *PluginLoader) init(config *PluginLoaderConfig) error {
    54  	// Create a mapping of name to config
    55  	configMap := configMap(config.Configs)
    56  
    57  	// Initialize the internal plugins
    58  	internal, err := l.initInternal(config.InternalPlugins, configMap)
    59  	if err != nil {
    60  		return fmt.Errorf("failed to fingerprint internal plugins: %v", err)
    61  	}
    62  
    63  	// Scan for eligibile binaries
    64  	plugins, err := l.scan()
    65  	if err != nil {
    66  		return fmt.Errorf("failed to scan plugin directory %q: %v", l.pluginDir, err)
    67  	}
    68  
    69  	// Fingerprint the passed plugins
    70  	external, err := l.fingerprintPlugins(plugins, configMap)
    71  	if err != nil {
    72  		return fmt.Errorf("failed to fingerprint plugins: %v", err)
    73  	}
    74  
    75  	// Merge external and internal plugins
    76  	l.plugins = l.mergePlugins(internal, external)
    77  
    78  	// Validate that the configs are valid for the plugins
    79  	if err := l.validatePluginConfigs(); err != nil {
    80  		return fmt.Errorf("parsing plugin configurations failed: %v", err)
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // initInternal initializes internal plugins.
    87  func (l *PluginLoader) initInternal(plugins map[PluginID]*InternalPluginConfig, configs map[string]*config.PluginConfig) (map[PluginID]*pluginInfo, error) {
    88  	var mErr multierror.Error
    89  	fingerprinted := make(map[PluginID]*pluginInfo, len(plugins))
    90  	for k, config := range plugins {
    91  		// Create an instance
    92  		raw := config.Factory(l.logger)
    93  		base, ok := raw.(base.BasePlugin)
    94  		if !ok {
    95  			multierror.Append(&mErr, fmt.Errorf("internal plugin %s doesn't meet base plugin interface", k))
    96  			continue
    97  		}
    98  
    99  		info := &pluginInfo{
   100  			factory: config.Factory,
   101  			config:  config.Config,
   102  		}
   103  
   104  		// Try to retrieve a user specified config
   105  		if userConfig, ok := configs[k.Name]; ok && userConfig.Config != nil {
   106  			info.config = userConfig.Config
   107  		}
   108  
   109  		// Fingerprint base info
   110  		i, err := base.PluginInfo()
   111  		if err != nil {
   112  			multierror.Append(&mErr, fmt.Errorf("PluginInfo info failed for internal plugin %s: %v", k, err))
   113  			continue
   114  		}
   115  		info.baseInfo = i
   116  
   117  		// Parse and set the plugin version
   118  		v, err := version.NewVersion(i.PluginVersion)
   119  		if err != nil {
   120  			multierror.Append(&mErr, fmt.Errorf("failed to parse version %q for internal plugin %s: %v", i.PluginVersion, k, err))
   121  			continue
   122  		}
   123  		info.version = v
   124  
   125  		// Detect the plugin API version to use
   126  		av, err := l.selectApiVersion(i)
   127  		if err != nil {
   128  			multierror.Append(&mErr, fmt.Errorf("failed to validate API versions %v for internal plugin %s: %v", i.PluginApiVersions, k, err))
   129  			continue
   130  		}
   131  		if av == "" {
   132  			l.logger.Warn("skipping plugin because supported API versions for plugin and Nomad do not overlap", "plugin", k)
   133  			continue
   134  		}
   135  		info.apiVersion = av
   136  
   137  		// Get the config schema
   138  		schema, err := base.ConfigSchema()
   139  		if err != nil {
   140  			multierror.Append(&mErr, fmt.Errorf("failed to retrieve config schema for internal plugin %s: %v", k, err))
   141  			continue
   142  		}
   143  		info.configSchema = schema
   144  
   145  		// Store the fingerprinted config
   146  		fingerprinted[k] = info
   147  	}
   148  
   149  	if err := mErr.ErrorOrNil(); err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	return fingerprinted, nil
   154  }
   155  
   156  // selectApiVersion takes in PluginInfo and returns the highest compatable
   157  // version or an error if the plugins response is malformed. If there is no
   158  // overlap, an empty string is returned.
   159  func (l *PluginLoader) selectApiVersion(i *base.PluginInfoResponse) (string, error) {
   160  	if i == nil {
   161  		return "", fmt.Errorf("nil plugin info given")
   162  	}
   163  	if len(i.PluginApiVersions) == 0 {
   164  		return "", fmt.Errorf("plugin provided no compatible API versions")
   165  	}
   166  
   167  	pluginVersions, err := convertVersions(i.PluginApiVersions)
   168  	if err != nil {
   169  		return "", fmt.Errorf("plugin provided invalid versions: %v", err)
   170  	}
   171  
   172  	// Lookup the supported versions. These will be sorted highest to lowest
   173  	supportedVersions, ok := l.supportedVersions[i.Type]
   174  	if !ok {
   175  		return "", fmt.Errorf("unsupported plugin type %q", i.Type)
   176  	}
   177  
   178  	for _, sv := range supportedVersions {
   179  		for _, pv := range pluginVersions {
   180  			if sv.Equal(pv) {
   181  				return pv.Original(), nil
   182  			}
   183  		}
   184  	}
   185  
   186  	return "", nil
   187  }
   188  
   189  // convertVersions takes a list of string versions and returns a sorted list of
   190  // versions from highest to lowest.
   191  func convertVersions(in []string) ([]*version.Version, error) {
   192  	converted := make([]*version.Version, len(in))
   193  	for i, v := range in {
   194  		vv, err := version.NewVersion(v)
   195  		if err != nil {
   196  			return nil, fmt.Errorf("failed to convert version %q : %v", v, err)
   197  		}
   198  
   199  		converted[i] = vv
   200  	}
   201  
   202  	sort.Slice(converted, func(i, j int) bool {
   203  		return converted[i].GreaterThan(converted[j])
   204  	})
   205  
   206  	return converted, nil
   207  }
   208  
   209  // scan scans the plugin directory and retrieves potentially eligible binaries
   210  func (l *PluginLoader) scan() ([]os.FileInfo, error) {
   211  	if l.pluginDir == "" {
   212  		return nil, nil
   213  	}
   214  
   215  	// Capture the list of binaries in the plugins folder
   216  	f, err := os.Open(l.pluginDir)
   217  	if err != nil {
   218  		// There are no plugins to scan
   219  		if os.IsNotExist(err) {
   220  			l.logger.Warn("skipping external plugins since plugin_dir doesn't exist")
   221  			return nil, nil
   222  		}
   223  
   224  		return nil, fmt.Errorf("failed to open plugin directory %q: %v", l.pluginDir, err)
   225  	}
   226  	files, err := f.Readdirnames(-1)
   227  	f.Close()
   228  	if err != nil {
   229  		return nil, fmt.Errorf("failed to read plugin directory %q: %v", l.pluginDir, err)
   230  	}
   231  
   232  	var plugins []os.FileInfo
   233  	for _, f := range files {
   234  		f = filepath.Join(l.pluginDir, f)
   235  		s, err := os.Stat(f)
   236  		if err != nil {
   237  			return nil, fmt.Errorf("failed to stat file %q: %v", f, err)
   238  		}
   239  		if s.IsDir() {
   240  			l.logger.Warn("skipping subdir in plugin folder", "subdir", f)
   241  			continue
   242  		}
   243  
   244  		if !executable(f, s) {
   245  			l.logger.Warn("skipping un-executable file in plugin folder", "file", f)
   246  			continue
   247  		}
   248  		plugins = append(plugins, s)
   249  	}
   250  
   251  	return plugins, nil
   252  }
   253  
   254  // fingerprintPlugins fingerprints all external plugin binaries
   255  func (l *PluginLoader) fingerprintPlugins(plugins []os.FileInfo, configs map[string]*config.PluginConfig) (map[PluginID]*pluginInfo, error) {
   256  	var mErr multierror.Error
   257  	fingerprinted := make(map[PluginID]*pluginInfo, len(plugins))
   258  	for _, p := range plugins {
   259  		name := cleanPluginExecutable(p.Name())
   260  		c := configs[name]
   261  		info, err := l.fingerprintPlugin(p, c)
   262  		if err != nil {
   263  			l.logger.Error("failed to fingerprint plugin", "plugin", name, "error", err)
   264  			multierror.Append(&mErr, err)
   265  			continue
   266  		}
   267  		if info == nil {
   268  			// Plugin was skipped for validation reasons
   269  			continue
   270  		}
   271  
   272  		id := PluginID{
   273  			Name:       info.baseInfo.Name,
   274  			PluginType: info.baseInfo.Type,
   275  		}
   276  
   277  		// Detect if we already have seen a version of this plugin
   278  		if prev, ok := fingerprinted[id]; ok {
   279  			oldVersion := prev.version
   280  			selectedVersion := info.version
   281  			skip := false
   282  
   283  			// Determine if we should keep the previous version or override
   284  			if prev.version.GreaterThan(info.version) {
   285  				oldVersion = info.version
   286  				selectedVersion = prev.version
   287  				skip = true
   288  			}
   289  			l.logger.Info("multiple versions of plugin detected",
   290  				"plugin", info.baseInfo.Name, "older_version", oldVersion, "selected_version", selectedVersion)
   291  
   292  			if skip {
   293  				continue
   294  			}
   295  		}
   296  
   297  		// Add the plugin
   298  		fingerprinted[id] = info
   299  	}
   300  
   301  	if err := mErr.ErrorOrNil(); err != nil {
   302  		return nil, err
   303  	}
   304  
   305  	return fingerprinted, nil
   306  }
   307  
   308  // fingerprintPlugin fingerprints the passed external plugin
   309  func (l *PluginLoader) fingerprintPlugin(pluginExe os.FileInfo, config *config.PluginConfig) (*pluginInfo, error) {
   310  	info := &pluginInfo{
   311  		exePath: filepath.Join(l.pluginDir, pluginExe.Name()),
   312  	}
   313  
   314  	// Build the command
   315  	cmd := exec.Command(info.exePath)
   316  	if config != nil {
   317  		cmd.Args = append(cmd.Args, config.Args...)
   318  		info.args = config.Args
   319  		info.config = config.Config
   320  	}
   321  
   322  	// Launch the plugin
   323  	client := plugin.NewClient(&plugin.ClientConfig{
   324  		HandshakeConfig: base.Handshake,
   325  		Plugins: map[string]plugin.Plugin{
   326  			base.PluginTypeBase: &base.PluginBase{},
   327  		},
   328  		Cmd:              cmd,
   329  		AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
   330  		Logger:           l.logger,
   331  	})
   332  	defer client.Kill()
   333  
   334  	// Connect via RPC
   335  	rpcClient, err := client.Client()
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	// Request the plugin
   341  	raw, err := rpcClient.Dispense(base.PluginTypeBase)
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  
   346  	// Cast the plugin to the base type
   347  	bplugin := raw.(base.BasePlugin)
   348  
   349  	// Retrieve base plugin information
   350  	i, err := bplugin.PluginInfo()
   351  	if err != nil {
   352  		return nil, fmt.Errorf("failed to get plugin info for plugin %q: %v", info.exePath, err)
   353  	}
   354  	info.baseInfo = i
   355  
   356  	// Parse and set the plugin version
   357  	v, err := version.NewVersion(i.PluginVersion)
   358  	if err != nil {
   359  		return nil, fmt.Errorf("failed to parse plugin %q (%v) version %q: %v",
   360  			i.Name, info.exePath, i.PluginVersion, err)
   361  	}
   362  	info.version = v
   363  
   364  	// Detect the plugin API version to use
   365  	av, err := l.selectApiVersion(i)
   366  	if err != nil {
   367  		return nil, fmt.Errorf("failed to validate API versions %v for plugin %s (%v): %v", i.PluginApiVersions, i.Name, info.exePath, err)
   368  	}
   369  	if av == "" {
   370  		l.logger.Warn("skipping plugin because supported API versions for plugin and Nomad do not overlap", "plugin", i.Name, "path", info.exePath)
   371  		return nil, nil
   372  	}
   373  	info.apiVersion = av
   374  
   375  	// Retrieve the schema
   376  	schema, err := bplugin.ConfigSchema()
   377  	if err != nil {
   378  		return nil, fmt.Errorf("failed to get plugin config schema for plugin %q: %v", info.exePath, err)
   379  	}
   380  	info.configSchema = schema
   381  
   382  	return info, nil
   383  }
   384  
   385  // mergePlugins merges internal and external plugins, preferring the highest
   386  // version.
   387  func (l *PluginLoader) mergePlugins(internal, external map[PluginID]*pluginInfo) map[PluginID]*pluginInfo {
   388  	finalized := make(map[PluginID]*pluginInfo, len(internal))
   389  
   390  	// Load the internal plugins
   391  	for k, v := range internal {
   392  		finalized[k] = v
   393  	}
   394  
   395  	for k, extPlugin := range external {
   396  		internal, ok := finalized[k]
   397  		if ok {
   398  			// We have overlapping plugins, determine if we should keep the
   399  			// internal version or override
   400  			if extPlugin.version.LessThan(internal.version) {
   401  				l.logger.Info("preferring internal version of plugin",
   402  					"plugin", extPlugin.baseInfo.Name, "internal_version", internal.version,
   403  					"external_version", extPlugin.version)
   404  				continue
   405  			}
   406  		}
   407  
   408  		// Add external plugin
   409  		finalized[k] = extPlugin
   410  	}
   411  
   412  	return finalized
   413  }
   414  
   415  // validatePluginConfigs is used to validate each plugins' configuration. If the
   416  // plugin has a config, it is parsed with the plugins config schema and
   417  // SetConfig is called to ensure the config is valid.
   418  func (l *PluginLoader) validatePluginConfigs() error {
   419  	var mErr multierror.Error
   420  	for id, info := range l.plugins {
   421  		if err := l.validatePluginConfig(id, info); err != nil {
   422  			wrapped := multierror.Prefix(err, fmt.Sprintf("plugin %s:", id))
   423  			multierror.Append(&mErr, wrapped)
   424  		}
   425  	}
   426  
   427  	return mErr.ErrorOrNil()
   428  }
   429  
   430  // validatePluginConfig is used to validate the plugin's configuration. If the
   431  // plugin has a config, it is parsed with the plugins config schema and
   432  // SetConfig is called to ensure the config is valid.
   433  func (l *PluginLoader) validatePluginConfig(id PluginID, info *pluginInfo) error {
   434  	var mErr multierror.Error
   435  
   436  	// Check if a config is allowed
   437  	if info.configSchema == nil {
   438  		if info.config != nil {
   439  			return fmt.Errorf("configuration not allowed but config passed")
   440  		}
   441  
   442  		// Nothing to do
   443  		return nil
   444  	}
   445  
   446  	// Convert the schema to hcl
   447  	spec, diag := hclspecutils.Convert(info.configSchema)
   448  	if diag.HasErrors() {
   449  		multierror.Append(&mErr, diag.Errs()...)
   450  		return multierror.Prefix(&mErr, "failed converting config schema:")
   451  	}
   452  
   453  	// If there is no config, initialize it to an empty map so we can still
   454  	// handle defaults
   455  	if info.config == nil {
   456  		info.config = map[string]interface{}{}
   457  	}
   458  
   459  	// Parse the config using the spec
   460  	val, diag, diagErrs := hclutils.ParseHclInterface(info.config, spec, nil)
   461  	if diag.HasErrors() {
   462  		multierror.Append(&mErr, diagErrs...)
   463  		return multierror.Prefix(&mErr, "failed to parse config: ")
   464  
   465  	}
   466  
   467  	// Marshal the value
   468  	cdata, err := msgpack.Marshal(val, val.Type())
   469  	if err != nil {
   470  		return fmt.Errorf("failed to msgpack encode config: %v", err)
   471  	}
   472  
   473  	// Store the marshalled config
   474  	info.msgpackConfig = cdata
   475  
   476  	// Dispense the plugin and set its config and ensure it is error free
   477  	instance, err := l.Dispense(id.Name, id.PluginType, nil, l.logger)
   478  	if err != nil {
   479  		return fmt.Errorf("failed to dispense plugin: %v", err)
   480  	}
   481  	defer instance.Kill()
   482  
   483  	b, ok := instance.Plugin().(base.BasePlugin)
   484  	if !ok {
   485  		return fmt.Errorf("dispensed plugin %s doesn't meet base plugin interface", id)
   486  	}
   487  
   488  	c := &base.Config{
   489  		PluginConfig: cdata,
   490  		AgentConfig:  nil,
   491  		ApiVersion:   info.apiVersion,
   492  	}
   493  
   494  	if err := b.SetConfig(c); err != nil {
   495  		return fmt.Errorf("setting config on plugin failed: %v", err)
   496  	}
   497  	return nil
   498  }