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

     1  package loader
     2  
     3  import (
     4  	"fmt"
     5  	"os/exec"
     6  
     7  	log "github.com/hashicorp/go-hclog"
     8  	plugin "github.com/hashicorp/go-plugin"
     9  	version "github.com/hashicorp/go-version"
    10  	"github.com/hashicorp/nomad/nomad/structs/config"
    11  	"github.com/hashicorp/nomad/plugins"
    12  	"github.com/hashicorp/nomad/plugins/base"
    13  	"github.com/hashicorp/nomad/plugins/device"
    14  	"github.com/hashicorp/nomad/plugins/drivers"
    15  	"github.com/hashicorp/nomad/plugins/shared/hclspec"
    16  )
    17  
    18  // PluginCatalog is used to retrieve plugins, either external or internal
    19  type PluginCatalog interface {
    20  	// Dispense returns the plugin given its name and type. This will also
    21  	// configure the plugin
    22  	Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (PluginInstance, error)
    23  
    24  	// Reattach is used to reattach to a previously launched external plugin.
    25  	Reattach(name, pluginType string, config *plugin.ReattachConfig) (PluginInstance, error)
    26  
    27  	// Catalog returns the catalog of all plugins keyed by plugin type
    28  	Catalog() map[string][]*base.PluginInfoResponse
    29  }
    30  
    31  // InternalPluginConfig is used to configure launching an internal plugin.
    32  type InternalPluginConfig struct {
    33  	Config  map[string]interface{}
    34  	Factory plugins.PluginFactory
    35  }
    36  
    37  // PluginID is a tuple identifying a plugin
    38  type PluginID struct {
    39  	// Name is the name of the plugin
    40  	Name string
    41  
    42  	// PluginType is the plugin's type
    43  	PluginType string
    44  }
    45  
    46  // String returns a friendly representation of the plugin.
    47  func (id PluginID) String() string {
    48  	return fmt.Sprintf("%q (%v)", id.Name, id.PluginType)
    49  }
    50  
    51  func PluginInfoID(resp *base.PluginInfoResponse) PluginID {
    52  	return PluginID{
    53  		Name:       resp.Name,
    54  		PluginType: resp.Type,
    55  	}
    56  }
    57  
    58  // PluginLoaderConfig configures a plugin loader.
    59  type PluginLoaderConfig struct {
    60  	// Logger is the logger used by the plugin loader
    61  	Logger log.Logger
    62  
    63  	// PluginDir is the directory scanned for loading plugins
    64  	PluginDir string
    65  
    66  	// Configs is an optional set of configs for plugins
    67  	Configs []*config.PluginConfig
    68  
    69  	// InternalPlugins allows registering internal plugins.
    70  	InternalPlugins map[PluginID]*InternalPluginConfig
    71  
    72  	// SupportedVersions is a mapping of plugin type to the supported versions
    73  	SupportedVersions map[string][]string
    74  }
    75  
    76  // PluginLoader is used to retrieve plugins either externally or from internal
    77  // factories.
    78  type PluginLoader struct {
    79  	// logger is the plugin loaders logger
    80  	logger log.Logger
    81  
    82  	// supportedVersions is a mapping of plugin type to the supported versions
    83  	supportedVersions map[string][]*version.Version
    84  
    85  	// pluginDir is the directory containing plugin binaries
    86  	pluginDir string
    87  
    88  	// plugins maps a plugin to information required to launch it
    89  	plugins map[PluginID]*pluginInfo
    90  }
    91  
    92  // pluginInfo captures the necessary information to launch and configure a
    93  // plugin.
    94  type pluginInfo struct {
    95  	factory plugins.PluginFactory
    96  
    97  	exePath string
    98  	args    []string
    99  
   100  	baseInfo   *base.PluginInfoResponse
   101  	version    *version.Version
   102  	apiVersion string
   103  
   104  	configSchema  *hclspec.Spec
   105  	config        map[string]interface{}
   106  	msgpackConfig []byte
   107  }
   108  
   109  // NewPluginLoader returns an instance of a plugin loader or an error if the
   110  // plugins could not be loaded
   111  func NewPluginLoader(config *PluginLoaderConfig) (*PluginLoader, error) {
   112  	if err := validateConfig(config); err != nil {
   113  		return nil, fmt.Errorf("invalid plugin loader configuration passed: %v", err)
   114  	}
   115  
   116  	// Convert the versions
   117  	supportedVersions := make(map[string][]*version.Version, len(config.SupportedVersions))
   118  	for pType, versions := range config.SupportedVersions {
   119  		converted, err := convertVersions(versions)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		supportedVersions[pType] = converted
   124  	}
   125  
   126  	logger := config.Logger.Named("plugin_loader").With("plugin_dir", config.PluginDir)
   127  	l := &PluginLoader{
   128  		logger:            logger,
   129  		supportedVersions: supportedVersions,
   130  		pluginDir:         config.PluginDir,
   131  		plugins:           make(map[PluginID]*pluginInfo),
   132  	}
   133  
   134  	if err := l.init(config); err != nil {
   135  		return nil, fmt.Errorf("failed to initialize plugin loader: %v", err)
   136  	}
   137  
   138  	return l, nil
   139  }
   140  
   141  // Dispense returns a plugin instance, loading it either internally or by
   142  // launching an external plugin.
   143  func (l *PluginLoader) Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (PluginInstance, error) {
   144  	id := PluginID{
   145  		Name:       name,
   146  		PluginType: pluginType,
   147  	}
   148  	pinfo, ok := l.plugins[id]
   149  	if !ok {
   150  		return nil, fmt.Errorf("unknown plugin with name %q and type %q", name, pluginType)
   151  	}
   152  
   153  	// If the plugin is internal, launch via the factory
   154  	var instance PluginInstance
   155  	if pinfo.factory != nil {
   156  		instance = &internalPluginInstance{
   157  			instance:   pinfo.factory(logger),
   158  			apiVersion: pinfo.apiVersion,
   159  		}
   160  	} else {
   161  		var err error
   162  		instance, err = l.dispensePlugin(pinfo.baseInfo.Type, pinfo.apiVersion, pinfo.exePath, pinfo.args, nil, logger)
   163  		if err != nil {
   164  			return nil, fmt.Errorf("failed to launch plugin: %v", err)
   165  		}
   166  	}
   167  
   168  	// Cast to the base type and set the config
   169  	b, ok := instance.Plugin().(base.BasePlugin)
   170  	if !ok {
   171  		return nil, fmt.Errorf("plugin %s doesn't implement base plugin interface", id)
   172  	}
   173  
   174  	c := &base.Config{
   175  		PluginConfig: pinfo.msgpackConfig,
   176  		AgentConfig:  config,
   177  		ApiVersion:   pinfo.apiVersion,
   178  	}
   179  
   180  	if err := b.SetConfig(c); err != nil {
   181  		return nil, fmt.Errorf("setting config for plugin %s failed: %v", id, err)
   182  	}
   183  
   184  	return instance, nil
   185  }
   186  
   187  // Reattach reattaches to a previously launched external plugin.
   188  func (l *PluginLoader) Reattach(name, pluginType string, config *plugin.ReattachConfig) (PluginInstance, error) {
   189  	return l.dispensePlugin(pluginType, "", "", nil, config, l.logger)
   190  }
   191  
   192  // dispensePlugin is used to launch or reattach to an external plugin.
   193  func (l *PluginLoader) dispensePlugin(
   194  	pluginType, apiVersion, cmd string, args []string, reattach *plugin.ReattachConfig,
   195  	logger log.Logger) (PluginInstance, error) {
   196  
   197  	var pluginCmd *exec.Cmd
   198  	if cmd != "" && reattach != nil {
   199  		return nil, fmt.Errorf("both launch command and reattach config specified")
   200  	} else if cmd == "" && reattach == nil {
   201  		return nil, fmt.Errorf("one of launch command or reattach config must be specified")
   202  	} else if cmd != "" {
   203  		pluginCmd = exec.Command(cmd, args...)
   204  	}
   205  
   206  	client := plugin.NewClient(&plugin.ClientConfig{
   207  		HandshakeConfig:  base.Handshake,
   208  		Plugins:          getPluginMap(pluginType, logger),
   209  		Cmd:              pluginCmd,
   210  		AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
   211  		Logger:           logger,
   212  		Reattach:         reattach,
   213  	})
   214  
   215  	// Connect via RPC
   216  	rpcClient, err := client.Client()
   217  	if err != nil {
   218  		client.Kill()
   219  		return nil, err
   220  	}
   221  
   222  	// Request the plugin
   223  	raw, err := rpcClient.Dispense(pluginType)
   224  	if err != nil {
   225  		client.Kill()
   226  		return nil, err
   227  	}
   228  
   229  	instance := &externalPluginInstance{
   230  		client:   client,
   231  		instance: raw,
   232  	}
   233  
   234  	if apiVersion != "" {
   235  		instance.apiVersion = apiVersion
   236  	} else {
   237  		// We do not know the API version since we are reattaching, so discover
   238  		// it
   239  		bplugin := raw.(base.BasePlugin)
   240  
   241  		// Retrieve base plugin information
   242  		i, err := bplugin.PluginInfo()
   243  		if err != nil {
   244  			return nil, fmt.Errorf("failed to get plugin info for plugin: %v", err)
   245  		}
   246  
   247  		apiVersion, err := l.selectApiVersion(i)
   248  		if err != nil {
   249  			return nil, fmt.Errorf("failed to validate API versions %v for plugin %s: %v", i.PluginApiVersions, i.Name, err)
   250  		}
   251  		if apiVersion == "" {
   252  			return nil, fmt.Errorf("failed to reattach to plugin because supported API versions for the plugin and Nomad do not overlap")
   253  		}
   254  
   255  		instance.apiVersion = apiVersion
   256  	}
   257  
   258  	return instance, nil
   259  }
   260  
   261  // getPluginMap returns a plugin map based on the type of plugin being launched.
   262  func getPluginMap(pluginType string, logger log.Logger) map[string]plugin.Plugin {
   263  	pmap := map[string]plugin.Plugin{
   264  		base.PluginTypeBase: &base.PluginBase{},
   265  	}
   266  
   267  	switch pluginType {
   268  	case base.PluginTypeDevice:
   269  		pmap[base.PluginTypeDevice] = &device.PluginDevice{}
   270  	case base.PluginTypeDriver:
   271  		pmap[base.PluginTypeDriver] = drivers.NewDriverPlugin(nil, logger)
   272  	}
   273  
   274  	return pmap
   275  }
   276  
   277  // Catalog returns the catalog of all plugins
   278  func (l *PluginLoader) Catalog() map[string][]*base.PluginInfoResponse {
   279  	c := make(map[string][]*base.PluginInfoResponse, 3)
   280  	for id, info := range l.plugins {
   281  		c[id.PluginType] = append(c[id.PluginType], info.baseInfo)
   282  	}
   283  	return c
   284  }