* Populates ComponentOptions into plugin structs * Maps options for legacy Provider Plugins into PluginOptions * Demos use of PluginOptions in a stub provider * Honors plugin priority and defaultable settings
196 lines
4.8 KiB
Go
196 lines
4.8 KiB
Go
package plugin
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/go-argmapper"
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-plugin"
|
|
|
|
sdk "github.com/hashicorp/vagrant-plugin-sdk"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/core"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cacher"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cleanup"
|
|
"github.com/hashicorp/vagrant/builtin/myplugin"
|
|
"github.com/hashicorp/vagrant/builtin/otherplugin"
|
|
)
|
|
|
|
// Setting this value to `true` will run builtin plugins
|
|
// in the existing process. This mode of plugin execution
|
|
// is not a "supported" mode of the go-plugin library and
|
|
// currently should only be used during testing in development
|
|
// for determining impact of a large number of builtin
|
|
// plugins
|
|
const IN_PROCESS_PLUGINS = false
|
|
|
|
var (
|
|
// Builtins is the map of all available builtin plugins and their
|
|
// options for launching them.
|
|
Builtins = map[string][]sdk.Option{
|
|
"myplugin": myplugin.CommandOptions,
|
|
"otherplugin": otherplugin.CommandOptions,
|
|
}
|
|
)
|
|
|
|
type Plugin struct {
|
|
Builtin bool // Flags if this plugin is a builtin plugin
|
|
Cache cacher.Cache // Cache for plugins to utilize in mappers
|
|
Client plugin.ClientProtocol // Client connection to plugin
|
|
Location string // Location of the plugin (generally path to binary)
|
|
Mappers []*argmapper.Func // Plugin specific mappers
|
|
Name string // Name of the plugin
|
|
Types []component.Type // Component types supported by this plugin
|
|
Options map[component.Type]interface{} // Options for supported components
|
|
|
|
cleaner cleanup.Cleanup // Cleanup tasks to perform on closing
|
|
logger hclog.Logger
|
|
m sync.Mutex
|
|
manager *Manager // Plugin manager this plugin belongs to
|
|
src *plugin.Client // Client for the plugin
|
|
}
|
|
|
|
// Interface for plugins with mapper support
|
|
type HasMappers interface {
|
|
AppendMappers(...*argmapper.Func)
|
|
}
|
|
|
|
// Interface for plugins which allow broker access
|
|
type HasGRPCBroker interface {
|
|
GRPCBroker() *plugin.GRPCBroker
|
|
}
|
|
|
|
// Interface for plugins that allow setting request metadata
|
|
type HasPluginMetadata interface {
|
|
SetRequestMetadata(k, v string)
|
|
}
|
|
|
|
// Interface for plugins that support having a parent
|
|
type HasParent interface {
|
|
GetParentComponent() interface{}
|
|
Parent() (string, error)
|
|
SetParentComponent(interface{})
|
|
}
|
|
|
|
// Check if plugin implements specific component type
|
|
func (p *Plugin) HasType(
|
|
t component.Type,
|
|
) bool {
|
|
for _, pt := range p.Types {
|
|
if pt == t {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Add a callback to execute when plugin is closed
|
|
func (p *Plugin) Closer(c func() error) {
|
|
p.cleaner.Do(c)
|
|
}
|
|
|
|
// Calls all registered close callbacks
|
|
func (p *Plugin) Close() (err error) {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
|
|
return p.cleaner.Close()
|
|
}
|
|
|
|
// Get specific component type from plugin
|
|
func (p *Plugin) InstanceOf(
|
|
c component.Type,
|
|
cfns []PluginConfigurator,
|
|
) (i *Instance, err error) {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
|
|
p.logger.Trace("loading component from plugin",
|
|
"name", p.Name,
|
|
"type", c.String())
|
|
|
|
if !p.HasType(c) {
|
|
p.logger.Error("unsupported component type requested",
|
|
"name", p.Name,
|
|
"type", c.String(),
|
|
"valid", p.types())
|
|
|
|
return nil, fmt.Errorf("plugin does not support %s component type", c.String())
|
|
}
|
|
|
|
// Build the instance
|
|
raw, err := p.Client.Dispense(strings.ToLower(c.String()))
|
|
if err != nil {
|
|
p.logger.Error("failed to dispense component from plugin",
|
|
"name", p.Name,
|
|
"type", c.String())
|
|
|
|
return
|
|
}
|
|
|
|
// Extract the GRPC broker if possible
|
|
b, ok := raw.(HasGRPCBroker)
|
|
if !ok {
|
|
p.logger.Error("cannot extract grpc broker from plugin client",
|
|
"component", c.String(),
|
|
"name", p.Name)
|
|
|
|
return nil, fmt.Errorf("unable to extract broker from plugin client")
|
|
}
|
|
|
|
// Include any mappers provided by the plugin
|
|
if cm, ok := raw.(HasMappers); ok {
|
|
cm.AppendMappers(p.Mappers...)
|
|
}
|
|
|
|
// Set the plugin name if possible
|
|
if named, ok := raw.(core.Named); ok {
|
|
named.SetPluginName(p.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Create our instance
|
|
i = &Instance{
|
|
Component: raw,
|
|
Close: func() error {
|
|
if cl, ok := raw.(io.Closer); ok {
|
|
return cl.Close()
|
|
}
|
|
return nil
|
|
},
|
|
Broker: b.GRPCBroker(),
|
|
Mappers: p.Mappers,
|
|
Name: p.Name,
|
|
Type: c,
|
|
Options: p.Options[c],
|
|
}
|
|
|
|
// Be sure the instance is close when the plugin is closed
|
|
p.Closer(func() error {
|
|
return i.Close()
|
|
})
|
|
|
|
// Apply configurators to the instance
|
|
for _, fn := range cfns {
|
|
if err = fn(i, p.logger); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Helper that returns supported types as strings
|
|
func (p *Plugin) types() []string {
|
|
result := []string{}
|
|
for _, t := range p.Types {
|
|
result = append(result, t.String())
|
|
}
|
|
return result
|
|
}
|