2022-04-25 12:24:12 -05:00

180 lines
4.5 KiB
Go

package plugin
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"github.com/hashicorp/go-argmapper"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vagrant-plugin-sdk/component"
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/pluginclient"
)
// exePath contains the value of os.Executable. We cache the value because
// we use it a lot and subsequent calls perform syscalls.
var exePath string
func init() {
var err error
exePath, err = os.Executable()
if err != nil {
panic(err)
}
}
// Factory returns the factory function for a plugin that is already
// represented by an *exec.Cmd. This returns an *Instance and NOT the component
// interface value directly. This instance lets you more carefully manage the
// lifecycle of the plugin as well as get additional information about the
// plugin.
func Factory(cmd *exec.Cmd, typ component.Type) interface{} {
return func(log hclog.Logger) (interface{}, error) {
// We have to copy the command because go-plugin will set some
// fields on it.
cmdCopy := *cmd
config := pluginclient.ClientConfig(log)
config.Cmd = &cmdCopy
config.Logger = log
// Log that we're going to launch this
log.Info("launching plugin",
"type", typ,
"path", cmd.Path,
"args", cmd.Args)
// Connect to the plugin
client := plugin.NewClient(config)
rpcClient, err := client.Client()
if err != nil {
log.Error("error creating plugin client",
"error", err)
client.Kill()
return nil, err
}
// Request the plugin. We don't request the mapper type because
// we handle that below.
var raw interface{}
if typ != component.MapperType {
raw, err = rpcClient.Dispense(strings.ToLower(typ.String()))
if err != nil {
log.Error("error requesting plugin",
"type", typ,
"error", err)
client.Kill()
return nil, err
}
}
b, ok := raw.(hasGRPCBroker)
if !ok {
log.Error("cannot extract grpc broker from plugin client")
client.Kill()
return nil, fmt.Errorf("unable to extract broker from plugin client")
}
// Request the mappers
mappers, err := pluginclient.Mappers(client)
if err != nil {
log.Error("error requesting plugin mappers",
"error", err)
client.Kill()
return nil, err
}
log.Debug("plugin successfully launched and connected")
return &Instance{
Component: raw,
Broker: b.GRPCBroker(),
Mappers: mappers,
Close: func() { client.Kill() },
}, nil
}
}
// BuiltinFactory creates a factory for a built-in plugin type.
func BuiltinFactory(name string, typ component.Type) interface{} {
cmd := exec.Command(exePath, "plugin-run", name)
// For non-windows systems, we attach stdout/stderr as extra fds
// so that we can get direct access to the TTY if possible for output.
if runtime.GOOS != "windows" {
cmd.ExtraFiles = []*os.File{os.Stdout, os.Stderr}
}
return Factory(cmd, typ)
}
type PluginMetadata interface {
SetRequestMetadata(k, v string)
}
func BuiltinRubyFactory(rubyClient plugin.ClientProtocol, name string, typ component.Type) interface{} {
return func(log hclog.Logger) (interface{}, error) {
raw, err := rubyClient.Dispense(strings.ToLower(typ.String()))
if err != nil {
log.Error("error requesting the ruby plugin",
"type", typ,
"name", name,
"error", err)
return nil, err
}
setter, ok := raw.(PluginMetadata)
if !ok {
return nil, fmt.Errorf("ruby runtime plugin does not support name setting (%s - %s)",
typ.String(), name)
}
setter.SetRequestMetadata("plugin_name", name)
log.Trace("set plugin name on legacy ruby plugin",
"type", typ.String(),
"name", name,
"plugin", hclog.Fmt("%T", raw))
b, ok := raw.(hasGRPCBroker)
if !ok {
log.Error("cannot extract grpc broker from plugin client",
"type", typ,
"name", name)
return nil, fmt.Errorf("unable to extract broker from builtin ruby plugin client")
}
return &Instance{
Component: raw,
Broker: b.GRPCBroker(),
Mappers: nil,
Close: func() {},
}, nil
}
}
// Instance is the result generated by the factory. This lets us pack
// a bit more information into plugin-launched components.
type Instance struct {
// Component is the dispensed component
Component interface{}
// Mappers is the list of mappers that this plugin is providing.
Mappers []*argmapper.Func
// The GRPCBroker attached to this plugin
Broker *plugin.GRPCBroker
// Closer is a function that should be called to clean up resources
// associated with this plugin.
Close func()
}
type hasGRPCBroker interface {
GRPCBroker() *plugin.GRPCBroker
}