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

170 lines
3.8 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)
}
}
func Factory(
cmd *exec.Cmd, // Plugin command to run
) PluginRegistration {
return func(log hclog.Logger) (p *Plugin, err error) {
// We have to copy the command because go-plugin will set some
// fields on it.
cmdCopy := *cmd
log = log.Named("factory")
nlog := log.ResetNamed("vagrant.plugin")
config := pluginclient.ClientConfig(nlog)
config.Cmd = &cmdCopy
config.Logger = nlog
// Log that we're going to launch this
log.Info("launching plugin",
"path", cmd.Path,
"args", cmd.Args)
// Connect to the plugin
client := plugin.NewClient(config)
// If we encounter any errors during setup, automatically
// kill the client
defer func() {
if err != nil {
client.Kill()
}
}()
rpcClient, err := client.Client()
if err != nil {
log.Error("error creating plugin client",
"error", err)
return
}
raw, err := rpcClient.Dispense("plugininfo")
if err != nil {
log.Error("error requesting plugin information interface",
"plugin", cmd.Path,
"error", err)
return
}
info, ok := raw.(component.PluginInfo)
if !ok {
log.Error("cannot load plugin info component",
"plugin", cmd.Path)
return nil, fmt.Errorf("failed to load plugin information interface")
}
p = &Plugin{
Builtin: false,
Client: rpcClient,
Location: cmd.Path,
Name: info.Name(),
Types: info.ComponentTypes(),
components: map[component.Type]*Instance{},
logger: nlog.Named(info.Name()),
src: client,
}
// Close the rpcClient when plugin is closed
p.Closer(func() error {
return rpcClient.Close()
})
return
}
}
// Request the mappers
// mappers, err := pluginclient.Mappers(client)
// if err != nil {
// log.Error("error requesting plugin mappers",
// "error", err)
// client.Kill()
// return nil, err
// }
// BuiltinFactory creates a factory for a built-in plugin type.
func BuiltinFactory(name string) PluginRegistration {
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)
}
func RubyFactory(
rubyClient plugin.ClientProtocol,
name string,
typ component.Type,
) PluginRegistration {
return func(log hclog.Logger) (*Plugin, error) {
return &Plugin{
Builtin: false,
Client: rubyClient,
Location: "ruby-runtime",
Name: name,
Types: []component.Type{typ},
components: map[component.Type]*Instance{},
logger: log.ResetNamed("vagrant.legacy." + strings.ToLower(typ.String())),
}, 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 PluginMetadata interface {
SetRequestMetadata(k, v string)
}
type hasGRPCBroker interface {
GRPCBroker() *plugin.GRPCBroker
}