Paul Hinze 8dbe72a5a0
Use Component Options to implement ProviderOptions
* 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
2022-06-03 16:37:05 -05:00

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
}