120 lines
3.6 KiB
Go
120 lines
3.6 KiB
Go
package external_host_plugins
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
|
"github.com/hashicorp/go-hclog"
|
|
)
|
|
|
|
// getOpts iterates the inbound Options and returns a struct
|
|
func getOpts(opt ...Option) (*options, error) {
|
|
opts := getDefaultOptions()
|
|
for _, o := range opt {
|
|
if o == nil {
|
|
continue
|
|
}
|
|
if err := o(opts); err != nil {
|
|
return nil, fmt.Errorf("error running option function: %w", err)
|
|
}
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
// Option - a type that wraps an interface for compile-time safety but can
|
|
// contain an option for this package or for wrappers implementing this
|
|
// interface.
|
|
type Option func(*options) error
|
|
|
|
type options struct {
|
|
withHostPluginsSources []pluginSourceInfo
|
|
withHostPluginExecutionDir string
|
|
withLogger hclog.Logger
|
|
}
|
|
|
|
func getDefaultOptions() *options {
|
|
return &options{}
|
|
}
|
|
|
|
// pluginSourceInfo contains information about plugin sources. Each entry in the
|
|
// final slice of withHostPluginsSources will either have the Fs/FsPrefix values
|
|
// populated or have the map populated. The former is for loading and executing
|
|
// a plugin via go-plugin from the filesystem; the latter is for executing it
|
|
// directly in-memory.
|
|
type pluginSourceInfo struct {
|
|
pluginMap map[string]func() (pb.HostPluginServiceClient, error)
|
|
pluginFs fs.FS
|
|
pluginFsPrefix string
|
|
}
|
|
|
|
// WithLogger allows passing a logger to the plugin library for debugging
|
|
func WithLogger(logger hclog.Logger) Option {
|
|
return func(o *options) error {
|
|
o.withLogger = logger
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithHostPluginsFilesystem provides an fs.FS containing plugins that can be
|
|
// executed to provide Host functionality. This can be specified multiple times;
|
|
// all FSes will be scanned. If there are conflicts, the last one wins (this
|
|
// property is shared with WithHostPluginsMap). The prefix will be stripped from
|
|
// each entry when determining the plugin type.
|
|
func WithHostPluginsFilesystem(prefix string, plugins fs.FS) Option {
|
|
return func(o *options) error {
|
|
if plugins == nil {
|
|
return errors.New("nil host plugin filesystem passed into option")
|
|
}
|
|
o.withHostPluginsSources = append(o.withHostPluginsSources,
|
|
pluginSourceInfo{
|
|
pluginFs: plugins,
|
|
pluginFsPrefix: prefix,
|
|
},
|
|
)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithHostPluginsMap provides a map containing functions that can be called to
|
|
// provide implementations of the server. This can be specified multiple times;
|
|
// all FSes will be scanned. If there are conflicts, the last one wins (this
|
|
// property is shared with WithHostPluginsFilesystem).
|
|
func WithHostPluginsMap(plugins map[string]func() (pb.HostPluginServiceClient, error)) Option {
|
|
return func(o *options) error {
|
|
if len(plugins) == 0 {
|
|
return errors.New("no entries in host plugins map passed into option")
|
|
}
|
|
o.withHostPluginsSources = append(o.withHostPluginsSources,
|
|
pluginSourceInfo{
|
|
pluginMap: plugins,
|
|
},
|
|
)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithHostPluginExecutionDir allows setting a specific directory for
|
|
// writing out and executing plugins; if not set, os.TempDir will be used
|
|
// to create a suitable directory.
|
|
func WithHostPluginExecutionDir(dir string) Option {
|
|
return func(o *options) error {
|
|
if dir == "" {
|
|
// We always call this with the option, so if it's not actually set,
|
|
// don't error
|
|
return nil
|
|
}
|
|
fi, err := os.Stat(dir)
|
|
if err != nil {
|
|
return fmt.Errorf("error while performing stat to validate path %q is a directory: %w", dir, err)
|
|
}
|
|
if !fi.IsDir() {
|
|
return fmt.Errorf("given plugin execution path %q is not a directory", dir)
|
|
}
|
|
o.withHostPluginExecutionDir = dir
|
|
return nil
|
|
}
|
|
}
|