external

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 22, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package external provides gRPC-based external plugin support for the workflow engine. External plugins run as separate processes communicating over gRPC via hashicorp/go-plugin.

Index

Constants

View Source
const (
	// ProtocolVersion is the plugin protocol version.
	// Increment this when making breaking changes to the gRPC interface.
	ProtocolVersion = 1

	// MagicCookieKey is the environment variable used for the handshake.
	MagicCookieKey = "WORKFLOW_PLUGIN"

	// MagicCookieValue is the expected value for the handshake cookie.
	MagicCookieValue = "workflow-external-plugin-v1"
)

Variables

Handshake is the shared handshake configuration between host and plugins. Both the host (client) and plugin (server) must use identical values.

Functions

This section is empty.

Types

type CallbackServer

type CallbackServer struct {
	pb.UnimplementedEngineCallbackServiceServer
	// contains filtered or unexported fields
}

CallbackServer implements the EngineCallbackService gRPC server. It runs on the host and is called by plugin processes.

func NewCallbackServer

func NewCallbackServer(onTrigger TriggerFunc, lookup ServiceLookupFunc, logger *log.Logger) *CallbackServer

NewCallbackServer creates a new callback server.

func (*CallbackServer) GetService

func (*CallbackServer) Log

func (*CallbackServer) TriggerWorkflow

func (s *CallbackServer) TriggerWorkflow(_ context.Context, req *pb.TriggerWorkflowRequest) (*pb.ErrorResponse, error)

type ExternalPluginAdapter

type ExternalPluginAdapter struct {
	// contains filtered or unexported fields
}

ExternalPluginAdapter wraps a gRPC plugin client to implement plugin.EnginePlugin. The engine sees this as a regular plugin — no changes to engine.go needed.

func NewExternalPluginAdapter

func NewExternalPluginAdapter(name string, client *PluginClient) (*ExternalPluginAdapter, error)

NewExternalPluginAdapter creates an adapter from a connected plugin client.

func (*ExternalPluginAdapter) Capabilities

func (a *ExternalPluginAdapter) Capabilities() []capability.Contract

func (*ExternalPluginAdapter) Dependencies

func (a *ExternalPluginAdapter) Dependencies() []plugin.PluginDependency

func (*ExternalPluginAdapter) Description

func (a *ExternalPluginAdapter) Description() string

func (*ExternalPluginAdapter) EngineManifest

func (a *ExternalPluginAdapter) EngineManifest() *plugin.PluginManifest

func (*ExternalPluginAdapter) ModuleFactories

func (a *ExternalPluginAdapter) ModuleFactories() map[string]plugin.ModuleFactory

func (*ExternalPluginAdapter) ModuleSchemas

func (a *ExternalPluginAdapter) ModuleSchemas() []*schema.ModuleSchema

func (*ExternalPluginAdapter) Name

func (a *ExternalPluginAdapter) Name() string

func (*ExternalPluginAdapter) OnDisable

func (*ExternalPluginAdapter) OnEnable

func (*ExternalPluginAdapter) RegisterRoutes

func (a *ExternalPluginAdapter) RegisterRoutes(_ *http.ServeMux)

func (*ExternalPluginAdapter) StepFactories

func (a *ExternalPluginAdapter) StepFactories() map[string]plugin.StepFactory

func (*ExternalPluginAdapter) TriggerFactories

func (a *ExternalPluginAdapter) TriggerFactories() map[string]plugin.TriggerFactory

func (*ExternalPluginAdapter) UIPages

func (a *ExternalPluginAdapter) UIPages() []plugin.UIPageDef

func (*ExternalPluginAdapter) Version

func (a *ExternalPluginAdapter) Version() string

func (*ExternalPluginAdapter) WiringHooks

func (a *ExternalPluginAdapter) WiringHooks() []plugin.WiringHook

func (*ExternalPluginAdapter) WorkflowHandlers

func (a *ExternalPluginAdapter) WorkflowHandlers() map[string]plugin.WorkflowHandlerFactory

type ExternalPluginManager

type ExternalPluginManager struct {
	// contains filtered or unexported fields
}

ExternalPluginManager discovers, loads, and manages external plugin subprocesses. Each plugin lives in its own subdirectory under the plugins directory and communicates with the host via gRPC through the go-plugin framework.

func NewExternalPluginManager

func NewExternalPluginManager(pluginsDir string, logger *log.Logger) *ExternalPluginManager

NewExternalPluginManager creates a new manager that scans the given directory for plugins.

func (*ExternalPluginManager) DiscoverPlugins

func (m *ExternalPluginManager) DiscoverPlugins() ([]string, error)

DiscoverPlugins scans the plugins directory for subdirectories that contain a plugin.json manifest and an executable binary matching the directory name. It returns the list of discovered plugin names.

func (*ExternalPluginManager) IsLoaded

func (m *ExternalPluginManager) IsLoaded(name string) bool

IsLoaded returns true if the named plugin is currently loaded.

func (*ExternalPluginManager) LoadPlugin

func (m *ExternalPluginManager) LoadPlugin(name string) (*ExternalPluginAdapter, error)

LoadPlugin starts the named plugin subprocess, performs the handshake, and creates an ExternalPluginAdapter. The plugin must have been previously discovered via DiscoverPlugins.

func (*ExternalPluginManager) LoadedPlugins

func (m *ExternalPluginManager) LoadedPlugins() []string

LoadedPlugins returns the names of all currently loaded plugins.

func (*ExternalPluginManager) ReloadPlugin

func (m *ExternalPluginManager) ReloadPlugin(name string) (*ExternalPluginAdapter, error)

ReloadPlugin unloads and then loads the named plugin.

func (*ExternalPluginManager) Shutdown

func (m *ExternalPluginManager) Shutdown()

Shutdown kills all loaded plugin subprocesses.

func (*ExternalPluginManager) UnloadPlugin

func (m *ExternalPluginManager) UnloadPlugin(name string) error

UnloadPlugin stops the named plugin subprocess and removes it from the internal map.

type GRPCPlugin

type GRPCPlugin struct {
	goplugin.Plugin
	// CallbackServer is the host-side callback implementation.
	// When non-nil, it will be registered on the broker for plugin access.
	CallbackServer *CallbackServer
}

GRPCPlugin implements go-plugin's Plugin and GRPCPlugin interfaces. It bridges between go-plugin's plugin system and our gRPC services.

func (*GRPCPlugin) GRPCClient

func (p *GRPCPlugin) GRPCClient(_ context.Context, broker *goplugin.GRPCBroker, c *grpc.ClientConn) (any, error)

GRPCClient returns the client wrapper (host side). This is called by the host process to get a client that talks to the plugin.

func (*GRPCPlugin) GRPCServer

func (p *GRPCPlugin) GRPCServer(broker *goplugin.GRPCBroker, s *grpc.Server) error

GRPCServer registers the plugin service on the gRPC server (plugin side). This is called by the plugin process.

type PluginClient

type PluginClient struct {
	// contains filtered or unexported fields
}

PluginClient wraps the gRPC client for the plugin service.

type PluginHandler

type PluginHandler struct {
	// contains filtered or unexported fields
}

PluginHandler provides HTTP API endpoints for managing external plugins.

func NewPluginHandler

func NewPluginHandler(manager *ExternalPluginManager) *PluginHandler

NewPluginHandler creates a new handler backed by the given external plugin manager.

func (*PluginHandler) RegisterRoutes

func (h *PluginHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers the external plugin management HTTP routes on the given mux.

type RemoteModule

type RemoteModule struct {
	// contains filtered or unexported fields
}

RemoteModule implements modular.Module by delegating to a gRPC plugin.

func NewRemoteModule

func NewRemoteModule(name, handleID string, client pb.PluginServiceClient) *RemoteModule

NewRemoteModule creates a remote module proxy.

func (*RemoteModule) Dependencies

func (m *RemoteModule) Dependencies() []string

func (*RemoteModule) Destroy

func (m *RemoteModule) Destroy() error

Destroy releases the remote module resources.

func (*RemoteModule) Init

func (m *RemoteModule) Init(app modular.Application) error

func (*RemoteModule) InvokeService

func (m *RemoteModule) InvokeService(method string, args map[string]any) (map[string]any, error)

InvokeService calls a named method on the remote module's service interface.

func (*RemoteModule) Name

func (m *RemoteModule) Name() string

func (*RemoteModule) ProvidesServices

func (m *RemoteModule) ProvidesServices() []string

func (*RemoteModule) RegisterConfig

func (m *RemoteModule) RegisterConfig(app modular.Application) error

func (*RemoteModule) RequiresServices

func (m *RemoteModule) RequiresServices() []string

func (*RemoteModule) Start

func (m *RemoteModule) Start(ctx context.Context) error

func (*RemoteModule) Stop

func (m *RemoteModule) Stop(ctx context.Context) error

type RemoteStep

type RemoteStep struct {
	// contains filtered or unexported fields
}

RemoteStep implements module.PipelineStep by delegating to a gRPC plugin.

func NewRemoteStep

func NewRemoteStep(name, handleID string, client pb.PluginServiceClient) *RemoteStep

NewRemoteStep creates a remote step proxy.

func (*RemoteStep) Destroy

func (s *RemoteStep) Destroy() error

Destroy releases the remote step resources.

func (*RemoteStep) Execute

func (*RemoteStep) Name

func (s *RemoteStep) Name() string

type ServiceLookupFunc

type ServiceLookupFunc func(name string) bool

ServiceLookupFunc checks if a named service exists in the host.

type TriggerFunc

type TriggerFunc func(triggerType, action string, data map[string]any) error

TriggerFunc is called when a plugin fires a workflow trigger.

type UIManifest added in v0.1.1

type UIManifest struct {
	// Name is the plugin identifier. Must match the plugin directory name.
	Name string `json:"name"`
	// Version is the plugin version string (e.g. "1.0.0").
	Version string `json:"version"`
	// Description is a short human-readable description of the plugin.
	Description string `json:"description,omitempty"`
	// NavItems declares navigation entries contributed to the admin UI.
	NavItems []UINavItem `json:"navItems,omitempty"`
	// AssetDir is the subdirectory within the plugin directory that holds
	// static assets. Defaults to "assets" when empty.
	AssetDir string `json:"assetDir,omitempty"`
}

UIManifest describes the UI contribution of a go-plugin UI plugin. Place this as "ui.json" in the plugin directory alongside "plugin.json".

Directory Layout

plugins/
  my-ui-plugin/
    my-ui-plugin  (binary, required for API handlers; optional for asset-only plugins)
    plugin.json   (gRPC plugin manifest; required when binary is present)
    ui.json       (UI manifest: nav items and asset location)
    assets/       (static files: HTML, CSS, JS, images, etc.)
      index.html
      main.js

Asset Versioning

Include a version hash in asset filenames (e.g. main.abc123.js) and reference them from index.html so browsers pick up new files after hot-deploy.

Hot-Reload

After updating assets or ui.json, call:

POST /api/v1/plugins/ui/{name}/reload

This re-reads the manifest and serves the new files without restarting the workflow engine.

Hot-Deploy

Replace the plugin binary and/or assets directory with the new version, then call the reload endpoint above.

type UINavItem added in v0.1.1

type UINavItem struct {
	// ID is the unique page identifier (e.g. "my-plugin-dashboard").
	ID string `json:"id"`
	// Label is the human-readable navigation label shown in the sidebar.
	Label string `json:"label"`
	// Icon is an emoji or icon token rendered alongside the label.
	Icon string `json:"icon,omitempty"`
	// Category groups navigation entries:
	//   "global"   – always visible top-level entries
	//   "workflow" – shown only when a workflow is open
	//   "plugin"   – plugin-contributed entries (default)
	//   "tools"    – administrative tooling entries
	Category string `json:"category,omitempty"`
	// Order controls the sort position within the category (lower = higher up).
	Order int `json:"order,omitempty"`
	// RequiredRole is the minimum role needed to see this page
	// (e.g. "viewer", "editor", "admin", "operator").
	RequiredRole string `json:"requiredRole,omitempty"`
	// RequiredPermission is a specific permission key required to see this page
	// (e.g. "plugins.manage").
	RequiredPermission string `json:"requiredPermission,omitempty"`
}

UINavItem describes a single entry in the admin UI navigation sidebar.

type UIPluginEntry added in v0.1.1

type UIPluginEntry struct {
	// Manifest is the parsed ui.json for this plugin.
	Manifest UIManifest
	// AssetsDir is the absolute path to the plugin's static assets directory.
	AssetsDir string
}

UIPluginEntry holds the runtime state of a loaded UI plugin.

type UIPluginHandler added in v0.1.1

type UIPluginHandler struct {
	// contains filtered or unexported fields
}

UIPluginHandler provides HTTP API endpoints for managing UI plugins.

Routes registered by RegisterRoutes:

GET  /api/v1/plugins/ui                       – list loaded UI plugins
GET  /api/v1/plugins/ui/available             – list all discovered UI plugins
GET  /api/v1/plugins/ui/{name}/manifest       – get a plugin's UI manifest
POST /api/v1/plugins/ui/{name}/load           – load a UI plugin
POST /api/v1/plugins/ui/{name}/unload         – unload a UI plugin
POST /api/v1/plugins/ui/{name}/reload         – hot-reload a UI plugin
GET  /api/v1/plugins/ui/{name}/assets/{path…} – serve static assets

func NewUIPluginHandler added in v0.1.1

func NewUIPluginHandler(manager *UIPluginManager) *UIPluginHandler

NewUIPluginHandler creates a new handler backed by the given UIPluginManager.

func (*UIPluginHandler) RegisterRoutes added in v0.1.1

func (h *UIPluginHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers all UI plugin HTTP routes on mux.

type UIPluginInfo added in v0.1.1

type UIPluginInfo struct {
	Name        string      `json:"name"`
	Version     string      `json:"version"`
	Description string      `json:"description,omitempty"`
	NavItems    []UINavItem `json:"navItems,omitempty"`
	Loaded      bool        `json:"loaded"`
}

UIPluginInfo is the JSON representation of a UI plugin for API responses.

type UIPluginManager added in v0.1.1

type UIPluginManager struct {
	// contains filtered or unexported fields
}

UIPluginManager discovers and manages UI plugins under a shared plugins directory. Each UI plugin is a subdirectory that contains a "ui.json" manifest and an optional "assets" subdirectory with static files.

Hot-reload

Calling ReloadPlugin re-reads the manifest and the assets directory from disk without restarting the workflow engine. The static file server for the plugin is updated atomically so in-flight requests are not interrupted.

Integration with PluginManager navigation

Call UIPages to get UIPageDef entries for a loaded UI plugin, then register a UIPluginNativePlugin wrapper with a PluginManager to surface those entries through the standard navigation API.

func NewUIPluginManager added in v0.1.1

func NewUIPluginManager(pluginsDir string, logger *log.Logger) *UIPluginManager

NewUIPluginManager creates a manager that scans the given directory for UI plugins (subdirectories containing a "ui.json" manifest).

func (*UIPluginManager) AllUIPluginInfos added in v0.1.1

func (m *UIPluginManager) AllUIPluginInfos() []UIPluginInfo

AllUIPluginInfos returns summary information for every currently loaded UI plugin.

func (*UIPluginManager) AsNativePlugin added in v0.1.1

func (m *UIPluginManager) AsNativePlugin(name string) plugin.NativePlugin

AsNativePlugin returns a plugin.NativePlugin implementation that surfaces the named UI plugin's navigation entries through the standard PluginManager API. Returns nil if the plugin is not loaded.

Use this to register a UI plugin's nav items with a PluginManager:

if np := uiMgr.AsNativePlugin("my-ui-plugin"); np != nil {
    _ = pluginMgr.Register(np)
    _ = pluginMgr.Enable("my-ui-plugin")
}

func (*UIPluginManager) DiscoverPlugins added in v0.1.1

func (m *UIPluginManager) DiscoverPlugins() ([]string, error)

DiscoverPlugins scans the plugins directory and returns names of all subdirectories that contain a "ui.json" manifest file.

func (*UIPluginManager) GetPlugin added in v0.1.1

func (m *UIPluginManager) GetPlugin(name string) (*UIPluginEntry, bool)

GetPlugin returns the entry for the named UI plugin. The second return value is false if the plugin is not loaded.

func (*UIPluginManager) IsLoaded added in v0.1.1

func (m *UIPluginManager) IsLoaded(name string) bool

IsLoaded returns true if the named UI plugin is currently loaded.

func (*UIPluginManager) LoadPlugin added in v0.1.1

func (m *UIPluginManager) LoadPlugin(name string) error

LoadPlugin reads the "ui.json" manifest for the named plugin and registers it. If the plugin is already loaded it is replaced (hot-reload semantics).

func (*UIPluginManager) LoadedPlugins added in v0.1.1

func (m *UIPluginManager) LoadedPlugins() []string

LoadedPlugins returns the names of all currently loaded UI plugins.

func (*UIPluginManager) ReloadPlugin added in v0.1.1

func (m *UIPluginManager) ReloadPlugin(name string) error

ReloadPlugin re-reads the manifest and assets directory from disk for the named plugin. This is the primary hot-reload mechanism: deploy updated assets to the plugin directory, then call this method.

func (*UIPluginManager) ServeAssets added in v0.1.1

func (m *UIPluginManager) ServeAssets(name string) http.Handler

ServeAssets returns an http.Handler that serves the static assets of the named plugin directly from its assets directory. Returns nil if the plugin is not loaded or its assets directory does not exist.

func (*UIPluginManager) UIPages added in v0.1.1

func (m *UIPluginManager) UIPages(name string) []plugin.UIPageDef

UIPages converts the nav items declared in a loaded UI plugin's manifest into plugin.UIPageDef entries compatible with the PluginManager navigation system.

func (*UIPluginManager) UnloadPlugin added in v0.1.1

func (m *UIPluginManager) UnloadPlugin(name string) error

UnloadPlugin removes a UI plugin from the manager. Returns an error if the plugin is not currently loaded.

type UIPluginNativePlugin added in v0.1.1

type UIPluginNativePlugin struct {
	// contains filtered or unexported fields
}

UIPluginNativePlugin adapts a loaded UI plugin as a plugin.NativePlugin so its navigation entries are visible through the standard PluginManager API. It implements a read-through to the UIPluginManager so that hot-reloads automatically update the navigation data returned by UIPages.

func (*UIPluginNativePlugin) Dependencies added in v0.1.1

func (p *UIPluginNativePlugin) Dependencies() []plugin.PluginDependency

func (*UIPluginNativePlugin) Description added in v0.1.1

func (p *UIPluginNativePlugin) Description() string

func (*UIPluginNativePlugin) Name added in v0.1.1

func (p *UIPluginNativePlugin) Name() string

func (*UIPluginNativePlugin) OnDisable added in v0.1.1

func (*UIPluginNativePlugin) OnEnable added in v0.1.1

func (*UIPluginNativePlugin) RegisterRoutes added in v0.1.1

func (p *UIPluginNativePlugin) RegisterRoutes(_ *http.ServeMux)

func (*UIPluginNativePlugin) UIPages added in v0.1.1

func (p *UIPluginNativePlugin) UIPages() []plugin.UIPageDef

UIPages reads the current nav items from the UIPluginManager so that hot-reloads (which call UIPluginManager.ReloadPlugin) are reflected immediately without re-registering the plugin.

func (*UIPluginNativePlugin) Version added in v0.1.1

func (p *UIPluginNativePlugin) Version() string

Directories

Path Synopsis
Package sdk provides the public API for building external workflow plugins.
Package sdk provides the public API for building external workflow plugins.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL