loader.go
Rama master · réplica de lectura del repositorio original.
package aastro
import (
"errors"
"fmt"
"plugin"
"slices"
"strings"
"go.uber.org/zap"
"github.com/voidrunner3074/aastro/sdk"
)
const (
sourceBuiltin = "builtin"
sourceFile = "file"
)
const (
builtinPluginsPath = "/usr/local/lib/aastro/plugins/"
builtinMiddlewaresPath = "/usr/local/lib/aastro/middlewares/"
)
const extSo = ".so"
func initPlugins(cfgs []PluginConfig, log *zap.Logger) ([]sdk.Plugin, error) {
plugins := make([]sdk.Plugin, 0, len(cfgs))
for _, cfg := range cfgs {
if slices.ContainsFunc(plugins, func(p sdk.Plugin) bool {
return p.Info().Name == cfg.Name
}) {
continue
}
soPath, err := resolveSoPath(cfg.Source, cfg.Name, cfg.Path, builtinPluginsPath)
if err != nil {
return nil, fmt.Errorf("resolve .so path: %w", err)
}
plugin, err := loadPlugin(soPath, cfg.Config, log)
if err != nil {
if cfg.Source == sourceBuiltin {
return nil, fmt.Errorf("cannot load builtin plugin %q: %w", cfg.Name, err)
}
return nil, fmt.Errorf("cannot load plugin %q from path %q: %w", cfg.Name, cfg.Path, err)
}
log.Info("plugin initialized", zap.String("name", plugin.Info().Name))
plugins = append(plugins, plugin)
}
return plugins, nil
}
func initMiddlewares(cfgs []MiddlewareConfig, log *zap.Logger) ([]sdk.Middleware, error) {
middlewares := make([]sdk.Middleware, 0, len(cfgs))
for _, cfg := range cfgs {
if slices.ContainsFunc(middlewares, func(m sdk.Middleware) bool {
return m.Name() == cfg.Name
}) {
continue
}
soPath, err := resolveSoPath(cfg.Source, cfg.Name, cfg.Path, builtinMiddlewaresPath)
if err != nil {
return nil, fmt.Errorf("resolve .so path: %w", err)
}
middleware, err := loadMiddleware(soPath, cfg.Config, log)
if err != nil {
if cfg.Source == sourceBuiltin {
return nil, fmt.Errorf("cannot load builtin middleware %q: %w", cfg.Name, err)
}
return nil, fmt.Errorf("cannot load middleware %q from path %q: %w", cfg.Name, cfg.Path, err)
}
log.Info("middleware initialized", zap.String("name", middleware.Name()))
middlewares = append(middlewares, middleware)
}
return middlewares, nil
}
func resolveSoPath(source, name, filePath, builtinPath string) (string, error) {
switch source {
case sourceBuiltin:
return builtinPath + name + extSo, nil
case sourceFile:
base := filePath
if !strings.HasSuffix(base, "/") {
base += "/"
}
return base + name + extSo, nil
default:
return "", fmt.Errorf("invalid plugin source %q", source)
}
}
func loadPlugin(path string, cfg map[string]interface{}, log *zap.Logger) (sdk.Plugin, error) {
factory, err := loadSymbol[func() sdk.Plugin](path, "NewPlugin", log)
if err != nil {
return nil, fmt.Errorf("load plugin symbol: %w", err)
}
p := factory()
if p == nil {
return nil, fmt.Errorf("plugin factory for path %q returned nil", path)
}
if err = p.Init(cfg); err != nil {
return nil, fmt.Errorf("init plugin %s: %w", p.Info().Name, err)
}
return p, nil
}
func loadMiddleware(path string, cfg map[string]interface{}, log *zap.Logger) (sdk.Middleware, error) {
factory, err := loadSymbol[func() sdk.Middleware](path, "NewMiddleware", log)
if err != nil {
return nil, fmt.Errorf("load middleware symbol: %w", err)
}
mw := factory()
if mw == nil {
return nil, fmt.Errorf("middleware factory for path %q returned nil", path)
}
if err = mw.Init(cfg); err != nil {
return nil, fmt.Errorf("init middleware %s: %w", mw.Name(), err)
}
return mw, nil
}
func loadSymbol[T any](path, symbol string, log *zap.Logger) (T, error) {
var zero T
log = log.With(zap.String("path", path))
p, err := plugin.Open(path)
if err != nil {
return zero, fmt.Errorf("open plugin: %w", err)
}
sym, err := p.Lookup(symbol)
if err != nil {
return zero, fmt.Errorf("lookup symbol: %w", err)
}
factory, ok := sym.(T)
if !ok {
return zero, errors.New("symbol has wrong signature")
}
pl := factory
log.Debug("symbol loaded successfully")
return pl, nil
}