pluginhost

package
v0.2.43 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EnvPluginID        = "CLOUDPATH_PLUGIN_ID"
	EnvProtocol        = "CLOUDPATH_PROTOCOL"
	EnvProtocolVersion = "CLOUDPATH_PROTOCOL_VERSION"
	EnvLaunchID        = "CLOUDPATH_LAUNCH_ID"
	EnvProof           = "CLOUDPATH_PROOF"
	EnvPluginEndpoint  = "CLOUDPATH_PLUGIN_ENDPOINT"

	// EnvHandshakeCookie is kept as an alias for EnvProof so earlier launch
	// identity readers and the existing test harness keep working during the
	// migration. New code should use EnvProof.
	EnvHandshakeCookie = "CLOUDPATH_HANDSHAKE_COOKIE"
)

Environment variables used to pass the launch identity and expected protocol into a plugin process. The plugin must echo these back in its handshake line so the host can reject mis-wired or malicious starts.

View Source
const (
	EnvTenant     = "CLOUDPATH_TENANT"
	EnvInstanceID = "CLOUDPATH_INSTANCE_ID"
)

Environment variables injected into a per-instance plugin process so it can tell which tenant/instance it is serving. Shared processes serve several instances and are therefore not given a single identity.

View Source
const ConfigPathKey = "path"

ConfigPathKey is the existing Host-only config-file reference. It is not a plugin JSON property and must not be forwarded to ConfigureInstance.

View Source
const HandshakeMarker = "CP1"

HandshakeMarker is the fixed first field of a plugin handshake line.

Variables

View Source
var (
	ErrInstallationNotFound = errors.New("pluginhost: installation not found")
	ErrInstallationExists   = errors.New("pluginhost: installation already registered")
	ErrInstanceNotFound     = errors.New("pluginhost: instance not found")
	ErrInstanceExists       = errors.New("pluginhost: instance already exists")
	ErrInvalidArgument      = errors.New("pluginhost: invalid argument")
	ErrAmbiguousInstance    = errors.New("pluginhost: multiple running processes require an instance target")
)

Sentinel errors returned by the Manager. Callers can compare with errors.Is to distinguish "does not exist" from other failures.

View Source
var ErrConnectorUnsupported = errors.New("pluginhost: connector plugin kind is not supported")

ErrConnectorUnsupported reports that a Connector plugin cannot be launched by the process host. It is returned before any process or endpoint is created.

Functions

func IsHandshakeLine

func IsHandshakeLine(line string) bool

IsHandshakeLine reports whether line is a structured handshake line. Any such line after the first one is a protocol violation.

func Redact

func Redact(line string) string

Redact removes values of sensitive fields from a single log line. It covers header style (Authorization: Bearer x), key/value style (password=hunter2) and JSON style ("token":"abc"). The replacement is stable so callers and tests can recognize the redaction point.

Types

type CommandSpec

type CommandSpec struct {
	Path string
	Args []string
	Env  []string
}

CommandSpec is the argv-separated description of one plugin process launch. It never contains a shell command string; Path and Args are passed to os/exec verbatim.

type Config

type Config struct {
	PluginID        string
	Kind            Kind
	Protocol        string
	ProtocolVersion uint32
	Command         CommandSpec

	// HandshakeTimeout is how long the host waits for the unique stdout
	// handshake line and the socket authentication frame before killing the
	// process and counting a crash.
	HandshakeTimeout time.Duration
	// ShutdownTimeout is the graceful shutdown deadline; after it the process
	// is killed.
	ShutdownTimeout time.Duration
	// HealthCheckInterval is how often the established RPC client is probed.
	HealthCheckInterval time.Duration
	// MaxRestarts is the crash-loop budget: at most this many re-launches are
	// attempted before the plugin is disabled. Zero disables restarts.
	MaxRestarts int
	BaseBackoff time.Duration
	MaxBackoff  time.Duration
	// LogBufferSize caps the retained redacted log lines per plugin.
	LogBufferSize int
	// Jitter randomizes backoff; default is full jitter in [0, d]. Override
	// with a deterministic function in tests.
	Jitter func(time.Duration) time.Duration
}

Config configures one supervised plugin process.

type ExecRunner

type ExecRunner struct{}

ExecRunner is the production Runner backed by os/exec.

func (ExecRunner) Start

func (ExecRunner) Start(spec CommandSpec) (Process, error)

Start launches the process and attaches platform process-tree control (Job Object on Windows, process group elsewhere).

type Handshake

type Handshake struct {
	Marker          string
	PluginID        string
	Protocol        string
	ProtocolVersion uint32
	Transport       string
	Endpoint        string
	RPC             string
	LaunchID        string
	Proof           string
}

Handshake is the parsed form of a plugin handshake line:

CP1|<plugin-id>|<protocol>=<version>|<transport>|<endpoint>|<rpc>|<launch-id>|<proof>

func ParseHandshake

func ParseHandshake(line string) (Handshake, error)

ParseHandshake parses and structurally validates a handshake line. It does not compare against the expected plugin id, protocol or cookie; that is done by Supervisor.validateHandshake.

func (Handshake) String

func (h Handshake) String() string

String renders the canonical handshake line.

type Health

type Health uint8

Health is the coarse health grade reported by a HealthChecker for one plugin instance. It is deliberately separate from the Supervisor process State: a process can be alive (Healthy process state) while its protocol health is Degraded.

const (
	HealthUnknown Health = iota
	HealthHealthy
	HealthDegraded
)

func (Health) String

func (h Health) String() string

String returns the canonical uppercase health name.

type HealthChecker

type HealthChecker interface {
	Check(ctx context.Context, target HealthTarget) (Health, error)
}

HealthChecker probes one plugin instance. Implementations must return quickly and are called periodically by the Manager, outside the Manager lock.

type HealthFailurePolicy

type HealthFailurePolicy uint8

HealthFailurePolicy selects the Manager action once an instance accumulates HealthFailureThreshold consecutive failed probes.

const (
	// HealthPolicyDisable stops the process. It is the default and does not
	// touch the Supervisor crash budget.
	HealthPolicyDisable HealthFailurePolicy = iota
	// HealthPolicyRestart performs a supervised in-place restart without
	// incrementing the Supervisor crash/restart budget.
	HealthPolicyRestart
)

func (HealthFailurePolicy) String

func (p HealthFailurePolicy) String() string

String returns a stable, human-readable policy name.

type HealthTarget

type HealthTarget struct {
	PluginID   string
	Version    string
	Tenant     string
	InstanceID string
}

HealthTarget identifies the plugin instance being probed.

type Installation

type Installation struct {
	PluginID string
	Version  string
	Path     string
	// Kind selects the protocol client the host builds for this installation.
	// A zero value means KindDriver.
	Kind Kind
}

Installation is one installed version of a plugin on this node. It is the node-level artifact that PluginInstance values are bound to; the same plugin may have multiple versions installed side by side for rolling migrations.

type Instance

type Instance struct {
	ID        string
	Tenant    string
	PluginID  string
	Version   string
	Config    map[string]string
	Isolation Isolation
}

Instance is one bound, running unit of a plugin. It is always owned by exactly one tenant and bound to exactly one installation version at a time.

type InstanceSnapshot

type InstanceSnapshot struct {
	Tenant              string
	InstanceID          string
	PluginID            string
	Version             string
	Isolation           Isolation
	Enabled             bool
	State               State
	Health              Health
	ConsecutiveFailures int
	Restarts            int
	Crashes             int
	Launches            int
	Config              map[string]string
}

InstanceSnapshot is a point-in-time view of one instance for observability and tests. State is the serving process lifecycle state from the Supervisor; Health is the Manager health-loop grade.

type InstanceSpec

type InstanceSpec struct {
	ID        string
	Tenant    string
	PluginID  string
	Version   string
	Config    map[string]string
	Isolation Isolation
}

InstanceSpec is the input for CreateInstance.

type Isolation

type Isolation uint8

Isolation selects whether an instance shares a plugin process with other instances of the same installation, or gets its own dedicated process.

const (
	// IsolationShared is the default: one process per installation serves all
	// of its instances.
	IsolationShared Isolation = iota
	// IsolationPerInstance gives the instance its own plugin process.
	IsolationPerInstance
)

func (Isolation) String

func (i Isolation) String() string

String returns the stable, canonical isolation name.

type Kind

type Kind uint8

Kind is the plugin contribution kind from the manifest. It selects which protocol client the host builds after the socket handshake: Driver and Application map to their SDK clients; Connector is explicitly unsupported by the process host.

const (
	KindDriver Kind = iota
	KindApplication
	KindConnector
)

func ParseKind

func ParseKind(s string) (Kind, error)

ParseKind parses a manifest kind string case-insensitively. It accepts the canonical manifest values ("Driver", "Application", "Connector") and the wire protocol names ("driver", "application", "connector").

func (Kind) Protocol

func (k Kind) Protocol() string

Protocol returns the protocol name the handshake uses for this kind.

func (Kind) String

func (k Kind) String() string

String returns the lowercase, canonical kind name used on the wire.

type LogEntry

type LogEntry struct {
	Stream string
	Line   string
}

LogEntry is one collected, already-redacted plugin log line.

type Manager

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

Manager owns plugin installations, instances and their supervised processes, plus the periodic health and metrics framework. It is safe for concurrent use.

func NewManager

func NewManager(opts ManagerOptions) *Manager

NewManager builds a Manager and starts its background health loop. Call Close to stop the loop and every supervised process.

func (*Manager) ApplicationClient added in v0.2.3

func (m *Manager) ApplicationClient(pluginID string) (application.ApplicationClient, error)

ApplicationClient is the application-kind equivalent of DriverClient. It rejects ambiguous plugin-only lookups instead of selecting an arbitrary process.

func (*Manager) ApplicationClientForInstance added in v0.2.14

func (m *Manager) ApplicationClientForInstance(tenant, id string) (application.ApplicationClient, error)

ApplicationClientForInstance resolves the exact enabled tenant/instance process; the returned client must be re-resolved after a restart.

func (*Manager) Close

func (m *Manager) Close() error

Close stops the health loop and gracefully shuts down every supervised process. It is idempotent.

func (*Manager) CreateInstance

func (m *Manager) CreateInstance(spec InstanceSpec) (Instance, error)

CreateInstance binds a new instance to an installed version. The instance is created stopped; call Start to launch it.

func (*Manager) Disable

func (m *Manager) Disable(tenant, id string) error

Disable stops the instance. For a shared process it only stops the process when no other enabled instance still uses it.

func (*Manager) DriverClient added in v0.2.0

func (m *Manager) DriverClient(pluginID string) (driver.DriverClient, error)

DriverClient returns a session-scoped client for an unambiguous plugin process. Multiple enabled instances may share it; multiple processes require DriverClientForInstance. Callers must re-resolve after a process restart.

func (*Manager) DriverClientForInstance added in v0.2.14

func (m *Manager) DriverClientForInstance(tenant, id string) (driver.DriverClient, error)

DriverClientForInstance resolves exactly one enabled tenant/instance binding, with no fallback to another version, process or tenant.

func (*Manager) Enable

func (m *Manager) Enable(tenant, id string) error

Enable re-enables a previously disabled instance and relaunches its process.

func (*Manager) ListInstallations

func (m *Manager) ListInstallations() []Installation

ListInstallations returns all registered installations sorted by plugin id then version.

func (*Manager) ListInstances

func (m *Manager) ListInstances(tenant string) []InstanceSnapshot

ListInstances returns a snapshot for every instance owned by tenant, sorted by instance id.

func (*Manager) Metrics

func (m *Manager) Metrics(tenant, id string) (Metrics, error)

Metrics returns a factual resource/health snapshot for one instance. It combines process-level observability from MetricsCollector with the restart count and last-healthy time maintained by the Manager.

func (*Manager) ReconcileInstance added in v0.2.14

func (m *Manager) ReconcileInstance(ctx context.Context, spec InstanceSpec, enabled bool) error

ReconcileInstance applies a complete instance definition. Candidate processes must establish an authenticated, serving session before replacing a live binding. Config updates use the existing per-instance RPC, not process-wide environment variables, so shared siblings (including other tenants) stay up. No plugin data is removed. A disabled definition need not be installed.

func (*Manager) RegisterInstallation

func (m *Manager) RegisterInstallation(inst Installation) error

RegisterInstallation records one installed plugin version. It fails if the same (plugin id, version) is already registered.

func (*Manager) Remove

func (m *Manager) Remove(tenant, id string, opts ...RemoveOption) (RemoveResult, error)

Remove deletes an instance and stops its process when it is no longer needed. Plugin data is preserved by default; use WithPurge to delete it.

func (*Manager) Snapshot

func (m *Manager) Snapshot(tenant, id string) (InstanceSnapshot, error)

Snapshot returns the current state of one instance. Tenant scoping is strict: a wrong tenant is indistinguishable from a missing instance.

func (*Manager) Start

func (m *Manager) Start(tenant, id string) error

Start launches the instance's process (or binds it to the running shared process). It is idempotent for an already-enabled instance.

type ManagerOptions

type ManagerOptions struct {
	Runner           Runner
	Logger           *slog.Logger
	Protocol         string
	ProtocolVersion  uint32
	HealthChecker    HealthChecker
	MetricsCollector MetricsCollector

	// HealthCheckInterval is how often running instances are probed.
	HealthCheckInterval time.Duration
	// HealthFailureThreshold is the number of consecutive failed probes before
	// HealthFailurePolicy is applied.
	HealthFailureThreshold int
	// HealthFailurePolicy selects restart or disable after the threshold.
	HealthFailurePolicy HealthFailurePolicy

	// Supervisor tuning, forwarded to every supervised process.
	HandshakeTimeout time.Duration
	ShutdownTimeout  time.Duration
	MaxRestarts      int
	BaseBackoff      time.Duration
	MaxBackoff       time.Duration
	LogBufferSize    int
	Jitter           func(time.Duration) time.Duration

	// CommandArgs/CommandEnv are the default argv/env used for every launched
	// process (Env is supplemented with the launch identity).
	CommandArgs []string
	CommandEnv  []string
}

ManagerOptions configures a Manager. All fields are optional; sensible defaults are applied when a field is left zero.

type Metrics

type Metrics struct {
	// CPUTime is total CPU time consumed by the plugin process.
	CPUTime time.Duration
	// RSSBytes is resident set size in bytes (0 when unavailable).
	RSSBytes int64
	// Handles is the open handle count, or -1 when unavailable.
	Handles int
	// Goroutines is the live goroutine count, or -1 when unavailable.
	Goroutines int
	// MessageRate is messages per second over the observation window.
	MessageRate float64
	// RestartCount is the process restart count maintained by the Manager.
	RestartCount int
	// LastHealthy is the last time the health loop observed HEALTHY (zero when
	// never observed).
	LastHealthy time.Time
}

Metrics is a factual point-in-time snapshot of one plugin instance. Fields the current platform cannot observe are reported as unavailable rather than fabricated.

type MetricsCollector

type MetricsCollector interface {
	Collect(ctx context.Context, target MetricsTarget) (Metrics, error)
}

MetricsCollector observes process-level resource metrics for one instance. Implementations must return quickly and may report unavailable fields.

type MetricsTarget

type MetricsTarget struct {
	PluginID   string
	Version    string
	Tenant     string
	InstanceID string
}

MetricsTarget identifies the plugin instance whose metrics are collected.

type Process

type Process interface {
	Wait() error
	Signal(os.Signal) error
	Kill() error
	Pid() int
	Stdout() io.ReadCloser
	Stderr() io.ReadCloser
}

Process is the running plugin process as seen by the Supervisor. It is the seam that lets tests inject an in-memory fake instead of a real binary.

type RemoveOption

type RemoveOption func(*removeOptions)

RemoveOption changes Remove behavior.

func WithPurge

func WithPurge() RemoveOption

WithPurge requests that plugin data be deleted during Remove. Purge is a separate, explicit, high-risk option and is off by default.

type RemoveResult

type RemoveResult struct {
	// Purged is true only when an explicit purge option was requested.
	Purged bool
	// DataPreserved is true when plugin data was left in place. It is the
	// default Remove semantics.
	DataPreserved bool
}

RemoveResult reports what happened to plugin data during Remove.

type Runner

type Runner interface {
	Start(spec CommandSpec) (Process, error)
}

Runner starts plugin processes from a CommandSpec.

type Snapshot

type Snapshot struct {
	State              State
	Crashes            int
	Restarts           int
	Launches           int
	HandshakeCompleted bool
	Backoffs           []time.Duration
	// Kind is the protocol kind the supervisor was configured for.
	Kind Kind
	// Endpoint is the most recent launch endpoint (loopback TCP or Unix
	// socket path). It is a local-only address and safe to surface.
	Endpoint string
	// RPCConnections is the number of authenticated RPC connections the host
	// has accepted across the current launch. Rejected proofs never count.
	RPCConnections int
}

Snapshot is a point-in-time view of the supervisor for observability and tests.

type State

type State uint8

State is the lifecycle state of one plugin process under the Supervisor. The canonical uppercase names are the UI/observability contract and must remain stable.

const (
	StateStopped State = iota
	StateStarting
	StateHealthy
	StateDegraded
	StateCrashed
	StateBackoff
	StateDisabled
)

func (State) CanTransition

func (s State) CanTransition(to State) bool

CanTransition reports whether from -> to is a legal transition.

func (State) String

func (s State) String() string

String returns the canonical uppercase state name.

type Supervisor

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

Supervisor owns the lifecycle of one plugin process.

func NewSupervisor

func NewSupervisor(cfg Config, runner Runner, logger *slog.Logger) *Supervisor

NewSupervisor builds a Supervisor. A nil runner uses the production ExecRunner; a nil logger discards logs.

func (*Supervisor) ApplicationClient added in v0.2.3

func (s *Supervisor) ApplicationClient() application.ApplicationClient

ApplicationClient returns the ApplicationClient of the current launch for application-kind plugins, or nil while the process is starting, crashed, or disabled. The returned client is tied to the current session and must be re-resolved after a restart.

func (*Supervisor) Disable

func (s *Supervisor) Disable()

Disable requests an immediate stop and prevents any further launches until Enable is called.

func (*Supervisor) DriverClient added in v0.2.0

func (s *Supervisor) DriverClient() driver.DriverClient

DriverClient returns the DriverClient of the current launch, or nil while the process is starting, crashed, or disabled. The returned client is tied to the current session and must be re-resolved after a restart.

func (*Supervisor) Enable

func (s *Supervisor) Enable()

Enable allows the supervisor to start the plugin again.

func (*Supervisor) Logs

func (s *Supervisor) Logs() []LogEntry

Logs returns the retained, already-redacted plugin log lines.

func (*Supervisor) Restart

func (s *Supervisor) Restart()

Restart requests a supervised in-place restart: the current process is killed and a fresh one is launched inside the same Run loop without touching the crash/restart budget. The Manager health failure policy uses this so health restarts are never counted as crashes.

func (*Supervisor) Run

func (s *Supervisor) Run(ctx context.Context) error

Run supervises the plugin until ctx is canceled. On cancellation it performs a graceful shutdown (RPC Shutdown, then signal, then kill after ShutdownTimeout). Run returns nil after a graceful stop and ctx.Err() when it was already shutting down.

func (*Supervisor) Snapshot

func (s *Supervisor) Snapshot() Snapshot

Snapshot returns a point-in-time view of the supervisor.

func (*Supervisor) State

func (s *Supervisor) State() State

State returns the current lifecycle state.

Jump to

Keyboard shortcuts

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