Files
go-pluginserver/pluginserver.go
2019-12-11 14:16:51 -08:00

202 lines
4.3 KiB
Go

package main
import (
"fmt"
"path"
"plugin"
"reflect"
"strings"
"sync"
)
// --- PluginServer --- //
// Holds the execution status of the plugin server.
type PluginServer struct {
lock sync.RWMutex
pluginsDir string
plugins map[string]*pluginData
instances map[int]*instanceData
events map[int]*eventData
nextInstanceId int
nextEventId int
}
// Create a new server context.
func newServer() *PluginServer {
return &PluginServer{
plugins: map[string]*pluginData{},
instances: map[int]*instanceData{},
events: map[int]*eventData{},
}
}
// SetPluginDir tells the server where to find the plugins.
//
// RPC exported method
func (s *PluginServer) SetPluginDir(dir string, reply *string) error {
s.lock.Lock()
s.pluginsDir = dir
s.lock.Unlock()
*reply = "ok"
return nil
}
// --- pluginData --- //
type pluginData struct {
name string
code *plugin.Plugin
constructor func() interface{}
config interface{}
}
func (s PluginServer) loadPlugin(name string) (plug *pluginData, err error) {
s.lock.Lock()
defer s.lock.Unlock()
plug, ok := s.plugins[name]
if ok {
return
}
code, err := plugin.Open(path.Join(s.pluginsDir, name+".so"))
if err != nil {
err = fmt.Errorf("failed to open plugin %s: %w", name, err)
return
}
constructorSymbol, err := code.Lookup("New")
if err != nil {
err = fmt.Errorf("No constructor function on plugin %s: %w", name, err)
return
}
constructor, ok := constructorSymbol.(func() interface{})
if !ok {
err = fmt.Errorf("Wrong constructor signature on plugin %s: %w", name, err)
return
}
plug = &pluginData{
name: name,
code: code,
constructor: constructor,
config: constructor(),
}
s.plugins[name] = plug
return
}
func getSchemaType(t reflect.Type) (string, bool) {
switch t.Kind() {
case reflect.String:
return `"string"`, true
case reflect.Bool:
return `"boolean"`, true
case reflect.Int, reflect.Int32:
return `"integer"`, true
case reflect.Uint, reflect.Uint32:
return `"integer","between":[0,2147483648]`, true
case reflect.Float32, reflect.Float64:
return `"number"`, true
case reflect.Array:
elemType, ok := getSchemaType(t.Elem())
if !ok {
break
}
return `"array","elements":{"type":` + elemType + `}`, true
case reflect.Map:
kType, ok := getSchemaType(t.Key())
vType, ok := getSchemaType(t.Elem())
if !ok {
break
}
return `"map","keys":{"type":` + kType + `},"values":{"type":` + vType + `}`, true
case reflect.Struct:
var out strings.Builder
out.WriteString(`"record","fields":[`)
n := t.NumField()
for i := 0; i < n; i++ {
field := t.Field(i)
typeDecl, ok := getSchemaType(field.Type)
if !ok {
// ignore unrepresentable types
continue
}
if i > 0 {
out.WriteString(`,`)
}
name := field.Tag.Get("json")
if name == "" {
name = strings.ToLower(field.Name)
}
out.WriteString(`{"`)
out.WriteString(name)
out.WriteString(`":{"type":`)
out.WriteString(typeDecl)
out.WriteString(`}}`)
}
out.WriteString(`]`)
return out.String(), true
}
return "", false
}
// Information obtained from a plugin's compiled code.
type PluginInfo struct {
Name string // plugin name
Phases []string // events it can handle
Version string // version number
Priority int // priority info
Schema string // JSON representation of the config schema
}
// GetPluginInfo loads and retrieves information from the compiled plugin.
// TODO: reload if the plugin code has been updated.
//
// RPC exported method
func (s PluginServer) GetPluginInfo(name string, info *PluginInfo) error {
plug, err := s.loadPlugin(name)
if err != nil {
return err
}
*info = PluginInfo{Name: name}
handlers := getHandlers(plug.config)
info.Phases = make([]string, len(handlers))
var i = 0
for name := range handlers {
info.Phases[i] = name
i++
}
v, _ := plug.code.Lookup("Version")
if v != nil {
info.Version = v.(string)
}
prio, _ := plug.code.Lookup("Priority")
if prio != nil {
info.Priority = prio.(int)
}
var out strings.Builder
out.WriteString(`{"name":"`)
out.WriteString(name)
out.WriteString(`","fields":[{"config":{"type":`)
st, _ := getSchemaType(reflect.TypeOf(plug.config).Elem())
out.WriteString(st)
out.WriteString(`}}]}`)
info.Schema = out.String()
return nil
}