posh/pkg/plugin/manager.go
2023-01-10 14:44:59 +01:00

103 lines
2.9 KiB
Go

package plugin
import (
"context"
"fmt"
"os/exec"
"path"
"path/filepath"
"plugin"
"strings"
"github.com/foomo/posh/pkg/log"
"github.com/pkg/errors"
)
type Manager struct {
l log.Logger
plugins map[string]*plugin.Plugin
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewManager(l log.Logger) (*Manager, error) {
inst := &Manager{
l: l.Named("plugin"),
plugins: map[string]*plugin.Plugin{},
}
return inst, nil
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (m *Manager) BuildAndLoadPlugin(ctx context.Context, filename, provider string) (Plugin, error) {
if err := m.Tidy(ctx, filename); err != nil {
return nil, err
}
if err := m.Build(ctx, filename); err != nil {
return nil, err
}
return m.LoadPlugin(filename, provider)
}
func (m *Manager) Tidy(ctx context.Context, filename string) error {
m.l.Debug("tidying:", filename)
cmd := exec.CommandContext(ctx, "go", "mod", "tidy")
cmd.Dir = filepath.Dir(filename)
if output, err := cmd.CombinedOutput(); err != nil {
return errors.Wrap(err, string(output))
}
return nil
}
func (m *Manager) Build(ctx context.Context, filename string) error {
m.l.Debug("building:", filename)
base := path.Base(filename)
cmd := exec.CommandContext(ctx, "go", "build",
"-buildmode=plugin",
"-a",
"-o", strings.ReplaceAll(base, ".go", ".so"),
base,
)
cmd.Dir = filepath.Dir(filename)
if output, err := cmd.CombinedOutput(); err != nil {
return errors.Wrap(err, string(output))
}
return nil
}
func (m *Manager) LoadPlugin(filename, provider string) (Plugin, error) {
m.l.Debug("loading plugin:", filename, provider)
filename = strings.ReplaceAll(filename, ".go", ".so")
if plg, err := m.Load(filename); err != nil {
return nil, err
} else if sym, err := plg.Lookup(provider); err != nil {
return nil, errors.Wrapf(err, "failed to lookup provider (%s)", provider)
} else if fn, ok := sym.(func(l log.Logger) (Plugin, error)); !ok {
return nil, fmt.Errorf("invalid provider type (%T) ", sym)
} else if inst, err := fn(m.l.Named("")); err != nil {
return nil, errors.Wrap(err, "failed to create plugin instance")
} else if inst == nil {
return nil, errors.New("plugin can not be nil")
} else {
return inst, nil
}
}
func (m *Manager) Load(filename string) (*plugin.Plugin, error) {
if value, ok := m.plugins[filename]; ok {
return value, nil
}
m.l.Debug("loading:", filename)
if plg, err := plugin.Open(filename); err != nil {
return nil, errors.Wrapf(err, "failed to load plugin (%s)", filename)
} else {
m.plugins[filename] = plg
return plg, nil
}
}