You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/sdk/plugins/plugin.go

153 lines
3.7 KiB

// Copyright IBM Corp. 2020, 2025
// SPDX-License-Identifier: MPL-2.0
package external_plugins
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
)
const (
hostServicePluginSetName = "host-plugin"
storageServicePluginSetName = "storage-plugin"
)
// HandshakeConfig is a shared config that can be used regardless of plugin, to
// avoid having to know type-specific things about each plugin
var HandshakeConfig = plugin.HandshakeConfig{
MagicCookieKey: "HASHICORP_BOUNDARY_PLUGIN",
MagicCookieValue: "boundary-plugin",
}
// ServeHostPlugin is a generic function to start serving a host plugin service as a
// plugin
func ServePlugin(svc any, opt ...Option) error {
opts, err := getOpts(opt...)
if err != nil {
return err
}
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, syscall.SIGHUP)
go func() {
for {
<-signalCh
}
}()
plugins := make(map[string]plugin.Plugin)
if hostSvc, ok := svc.(pb.HostPluginServiceServer); ok {
hostServiceServer, err := NewHostPluginServiceServer(hostSvc)
if err != nil {
return err
}
plugins[hostServicePluginSetName] = hostServiceServer
}
if storageSvc, ok := svc.(pb.StoragePluginServiceServer); ok {
storageServiceServer, err := NewStoragePluginServiceServer(storageSvc)
if err != nil {
return err
}
plugins[storageServicePluginSetName] = storageServiceServer
}
if len(plugins) == 0 {
return errors.New("no valid plugin server provided")
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: HandshakeConfig,
VersionedPlugins: map[int]plugin.PluginSet{
1: plugins,
},
Logger: opts.withLogger,
GRPCServer: plugin.DefaultGRPCServer,
})
return nil
}
func NewPluginClient(pluginPath, setName string, opt ...Option) (*plugin.Client, error) {
opts, err := getOpts(opt...)
if err != nil {
return nil, err
}
var set plugin.PluginSet
switch setName {
case hostServicePluginSetName:
set = plugin.PluginSet{hostServicePluginSetName: &hostPlugin{}}
case storageServicePluginSetName:
set = plugin.PluginSet{storageServicePluginSetName: &storagePlugin{}}
}
return plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
VersionedPlugins: map[int]plugin.PluginSet{
1: set,
},
Cmd: exec.Command(pluginPath),
AllowedProtocols: []plugin.Protocol{
plugin.ProtocolGRPC,
},
Logger: opts.withLogger,
AutoMTLS: true,
}), nil
}
type hostPlugin struct {
plugin.Plugin
impl pb.HostPluginServiceServer
}
func NewHostPluginServiceServer(impl pb.HostPluginServiceServer) (*hostPlugin, error) {
if impl == nil {
return nil, fmt.Errorf("empty underlying host plugin passed in")
}
return &hostPlugin{
impl: impl,
}, nil
}
func (h *hostPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
pb.RegisterHostPluginServiceServer(s, h.impl)
return nil
}
func (h *hostPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (any, error) {
return pb.NewHostPluginServiceClient(c), nil
}
type storagePlugin struct {
plugin.Plugin
impl pb.StoragePluginServiceServer
}
func NewStoragePluginServiceServer(impl pb.StoragePluginServiceServer) (*storagePlugin, error) {
if impl == nil {
return nil, fmt.Errorf("empty underlying storage plugin passed in")
}
return &storagePlugin{
impl: impl,
}, nil
}
func (p *storagePlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
pb.RegisterStoragePluginServiceServer(s, p.impl)
return nil
}
func (p *storagePlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (any, error) {
return pb.NewStoragePluginServiceClient(c), nil
}