plugin API

plugin

package

API reference for the plugin package.

F
function

LoadSo

LoadSo loads a Go plugin (.so) exposing a PluginFactory symbol.

Parameters

path
string

Returns

error
pkg/plugin/so_loader.go:9-27
func LoadSo(path string) (Factory, error)

{
	p, err := stdplugin.Open(path)
	if err != nil {
		return nil, err
	}

	symbol, err := p.Lookup("PluginFactory")
	if err != nil {
		return nil, err
	}

	if factory, ok := symbol.(func() Plugin); ok {
		return factory, nil
	}
	if factory, ok := symbol.(*func() Plugin); ok {
		return *factory, nil
	}
	return nil, errors.New("plugin: PluginFactory has wrong type")
}
I
interface

Plugin

Plugin defines the lifecycle and identity for plugins.

pkg/plugin/api.go:4-8
type Plugin interface

Methods

Name
Method

Returns

string
func Name(...)
Start
Method

Returns

error
func Start(...)
Stop
Method

Returns

error
func Stop(...)
T
type

Factory

Factory creates plugin instances on demand.

pkg/plugin/api.go:11-11
type Factory func() Plugin
S
struct

FactoryRegistry

FactoryRegistry stores plugin factories by name.

pkg/plugin/loader.go:13-15
type FactoryRegistry struct

Methods

Register
Method

Register stores a named factory.

Parameters

name string
factory Factory

Returns

error
func (*FactoryRegistry) Register(name string, factory Factory) error
{
	if _, ok := f.byName[name]; ok {
		return ErrFactoryExists
	}
	f.byName[name] = factory
	return nil
}
Create
Method

Create builds a plugin from a registered factory.

Parameters

name string

Returns

error
func (*FactoryRegistry) Create(name string) (Plugin, error)
{
	factory, ok := f.byName[name]
	if !ok {
		return nil, ErrFactoryNotFound
	}
	return factory(), nil
}

Fields

Name Type Description
byName map[string]Factory
F
function

NewFactoryRegistry

NewFactoryRegistry creates an empty factory registry.

Returns

pkg/plugin/loader.go:18-20
func NewFactoryRegistry() *FactoryRegistry

{
	return &FactoryRegistry{byName: make(map[string]Factory)}
}
S
struct

Registry

Registry manages plugin registration and lifecycle in a deterministic order.

pkg/plugin/registry.go:14-18
type Registry struct

Methods

DiscoverFromValues inspects provided values and registers any that implement Plugin.

Parameters

values ...interface{}

Returns

int
func (*Registry) DiscoverFromValues(values ...interface{}) int
{
	if r == nil {
		return 0
	}

	pluginType := reflect.TypeOf((*Plugin)(nil)).Elem()
	count := 0
	for _, value := range values {
		if value == nil {
			continue
		}

		t := reflect.TypeOf(value)
		if t.Implements(pluginType) {
			p, _ := value.(Plugin)
			if p != nil && r.Register(p) == nil {
				count++
			}
			continue
		}

		if t.Kind() == reflect.Ptr && t.Elem().Implements(pluginType) {
			pluginValue, _ := reflect.ValueOf(value).Elem().Interface().(Plugin)
			if pluginValue != nil && r.Register(pluginValue) == nil {
				count++
			}
		}
	}
	return count
}
Register
Method

Register adds a plugin to the registry.

Parameters

p Plugin

Returns

error
func (*Registry) Register(p Plugin) error
{
	r.mu.Lock()
	defer r.mu.Unlock()

	name := p.Name()
	if _, ok := r.byName[name]; ok {
		return ErrAlreadyRegistered
	}

	r.byName[name] = p
	r.order = append(r.order, name)
	return nil
}
Unregister
Method

Unregister removes a plugin by name.

Parameters

name string
func (*Registry) Unregister(name string)
{
	r.mu.Lock()
	defer r.mu.Unlock()

	delete(r.byName, name)
	for i, current := range r.order {
		if current == name {
			r.order = append(r.order[:i], r.order[i+1:]...)
			break
		}
	}
}
Get
Method

Get returns a plugin and whether it exists.

Parameters

name string

Returns

bool
func (*Registry) Get(name string) (Plugin, bool)
{
	r.mu.RLock()
	defer r.mu.RUnlock()

	plugin, ok := r.byName[name]
	return plugin, ok
}
Names
Method

Names returns registered plugin names in insertion order.

Returns

[]string
func (*Registry) Names() []string
{
	r.mu.RLock()
	defer r.mu.RUnlock()

	names := make([]string, len(r.order))
	copy(names, r.order)
	return names
}
StartAll
Method

StartAll starts plugins in insertion order and returns any collected errors.

Returns

[]error
func (*Registry) StartAll() []error
{
	names := r.Names()
	var errs []error
	for _, name := range names {
		p, ok := r.Get(name)
		if !ok {
			continue
		}
		if err := p.Start(); err != nil {
			errs = append(errs, err)
		}
	}
	return errs
}
StopAll
Method

StopAll stops plugins in reverse insertion order and returns any collected errors.

Returns

[]error
func (*Registry) StopAll() []error
{
	names := r.Names()
	var errs []error
	for i := len(names) - 1; i >= 0; i-- {
		p, ok := r.Get(names[i])
		if !ok {
			continue
		}
		if err := p.Stop(); err != nil {
			errs = append(errs, err)
		}
	}
	return errs
}

Fields

Name Type Description
mu sync.RWMutex
order []string
byName map[string]Plugin
F
function

NewRegistry

NewRegistry creates an empty plugin registry.

Returns

pkg/plugin/registry.go:21-23
func NewRegistry() *Registry

{
	return &Registry{byName: make(map[string]Plugin)}
}
F
function

RegisterRouteProviders

RegisterRouteProviders inspects registered plugins and forwards any routes they expose.

Parameters

pkg/plugin/router_integration.go:6-23
func RegisterRouteProviders(r *Registry)

{
	if r == nil {
		return
	}

	for _, name := range r.Names() {
		p, ok := r.Get(name)
		if !ok {
			continue
		}
		if provider, ok := p.(interface{ Routes() []registry.Route }); ok {
			routes := provider.Routes()
			if len(routes) > 0 {
				registry.RegisterRoutes(func() []registry.Route { return routes })
			}
		}
	}
}
S
struct

ExecSandbox

ExecSandbox runs a plugin as an external process using a simple JSON-over-stdio protocol.

pkg/plugin/sandbox_exec.go:14-19
type ExecSandbox struct

Methods

Start
Method

Start launches the external process and waits for a ready signal.

Returns

error
func (*ExecSandbox) Start() error
{
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.cmd.Process != nil {
		return errors.New("plugin: sandbox already started")
	}

	in, err := e.cmd.StdinPipe()
	if err != nil {
		return err
	}
	out, err := e.cmd.StdoutPipe()
	if err != nil {
		return err
	}

	e.in = in
	e.out = out
	if err := e.cmd.Start(); err != nil {
		return err
	}

	decoder := json.NewDecoder(e.out)
	ready := make(chan error, 1)
	go func() {
		var msg map[string]any
		if err := decoder.Decode(&msg); err != nil {
			ready <- err
			return
		}
		if ok, _ := msg["ready"].(bool); ok {
			ready <- nil
			return
		}
		ready <- errors.New("plugin: unexpected ready message")
	}()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	select {
	case err := <-ready:
		return err
	case <-ctx.Done():
		return ctx.Err()
	}
}
Stop
Method

Stop sends a stop command and waits for the external process to exit.

Returns

error
func (*ExecSandbox) Stop() error
{
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.cmd.Process == nil {
		return errors.New("plugin: sandbox not started")
	}

	if err := json.NewEncoder(e.in).Encode(map[string]any{"cmd": "stop"}); err != nil {
		return err
	}
	return e.cmd.Wait()
}

Fields

Name Type Description
cmd *exec.Cmd
in io.WriteCloser
out io.ReadCloser
mu sync.Mutex
F
function

NewExecSandbox

NewExecSandbox creates a sandbox around the provided binary path and arguments.

Parameters

path
string
args
...string

Returns

pkg/plugin/sandbox_exec.go:22-24
func NewExecSandbox(path string, args ...string) *ExecSandbox

{
	return &ExecSandbox{cmd: exec.Command(path, args...)}
}