Plugin System Overview
The s9s plugin system allows extending the functionality of s9s through modular plugins. Plugins can provide new views, overlay data on existing views, provide data to other plugins, and more.
Table of Contents
- Plugin Architecture
- Core Components
- Creating a Plugin
- Available Plugins
- Plugin Development Guidelines
- Testing Plugins
Plugin Architecture
Plugins in s9s are implemented as Go shared libraries (
.soCore Components
-
Plugin Interface (
)internal/plugin/interface.go- Base interface that all plugins must implement
Plugin - Specialized interfaces for different plugin types:
- - Provides custom views
ViewPlugin - - Overlays data on existing views
OverlayPlugin - - Provides data to other plugins
DataPlugin - - Runtime configuration support
ConfigurablePlugin - - Event hooks
HookablePlugin
- Base
-
Plugin Manager (
)internal/plugin/manager.go- Manages plugin lifecycle (init, start, stop)
- Handles plugin dependencies
- Performs health checks
- Manages plugin state
-
Plugin Registry (
)internal/plugin/registry.go- Registers and indexes plugins
- Resolves dependencies
- Manages plugin metadata
Creating a Plugin
Basic Plugin Structure
package myplugin import ( "context" "github.com/jontk/s9s/internal/plugin" ) type MyPlugin struct { // Plugin state } func New() *MyPlugin { return &MyPlugin{} } func (p *MyPlugin) GetInfo() plugin.Info { return plugin.Info{ Name: "my-plugin", Version: "1.0.0", Description: "My awesome plugin", Author: "Your Name", License: "MIT", Requires: []string{}, // Dependencies Provides: []string{"feature1", "feature2"}, } } func (p *MyPlugin) Init(ctx context.Context, config map[string]interface{}) error { // Initialize plugin with configuration return nil } func (p *MyPlugin) Start(ctx context.Context) error { // Start plugin services return nil } func (p *MyPlugin) Stop(ctx context.Context) error { // Clean up resources return nil } func (p *MyPlugin) Health() plugin.HealthStatus { return plugin.HealthStatus{ Healthy: true, Status: "healthy", Message: "Plugin is running", } }
View Plugin Example
type MyViewPlugin struct { MyPlugin // Embed base plugin } func (p *MyViewPlugin) GetViews() []plugin.ViewInfo { return []plugin.ViewInfo{ { ID: "my-view", Name: "My View", Description: "Custom view for my data", Icon: "chart", Shortcut: "m", Category: "monitoring", }, } } func (p *MyViewPlugin) CreateView(ctx context.Context, viewID string) (plugin.View, error) { if viewID == "my-view" { return NewMyView(), nil } return nil, fmt.Errorf("unknown view: %s", viewID) }
Overlay Plugin Example
type MyOverlayPlugin struct { MyPlugin // Embed base plugin } func (p *MyOverlayPlugin) GetOverlays() []plugin.OverlayInfo { return []plugin.OverlayInfo{ { ID: "cpu-usage", Name: "CPU Usage", Description: "Adds CPU usage to job view", TargetViews: []string{"jobs"}, Priority: 100, }, } } func (p *MyOverlayPlugin) CreateOverlay(ctx context.Context, overlayID string) (plugin.Overlay, error) { if overlayID == "cpu-usage" { return NewCPUOverlay(), nil } return nil, fmt.Errorf("unknown overlay: %s", overlayID) }
Plugin Configuration
Plugins can define their configuration schema:
ConfigSchema: map[string]plugin.ConfigField{ "endpoint": { Type: "string", Description: "API endpoint URL", Default: "http://localhost:8080", Required: true, Validation: "^https?://", }, "refresh_rate": { Type: "int", Description: "Refresh rate in seconds", Default: 30, Required: false, }, }
Configuration is passed to the plugin during initialization and can be updated at runtime if the plugin implements
ConfigurablePluginPlugin Lifecycle
- Registration: Plugin is registered with the manager
- Initialization: is called with configuration
Init() - Start: is called to begin operations
Start() - Running: Plugin performs its functions
- Health Checks: Manager periodically calls
Health() - Stop: is called for graceful shutdown
Stop()
Available Plugins
Observability Plugin
Provides Prometheus integration for real-time metrics monitoring.
Features:
- Real-time CPU, memory, disk, and network metrics
- Job-level resource usage from cgroup-exporter
- Metric overlays on existing views
- Alert monitoring
- Historical data analysis
Configuration:
plugins: - name: observability enabled: true config: prometheus: endpoint: "http://prometheus:9090" timeout: "10s" refreshInterval: "30s" display: showOverlays: true enableAlerts: true
For detailed documentation on the observability plugin, see Observability Plugin.
Plugin Development Guidelines
- Error Handling: Always return meaningful errors
- Context Awareness: Respect context cancellation
- Resource Management: Clean up resources in
Stop() - Health Reporting: Provide accurate health status
- Configuration Validation: Validate config in
Init() - Logging: Use the debug logger for diagnostics
- Testing: Write unit tests for your plugin
Testing Plugins
func TestMyPlugin(t *testing.T) { plugin := New() // Test initialization err := plugin.Init(context.Background(), map[string]interface{}{ "endpoint": "http://test:8080", }) if err != nil { t.Fatalf("Init failed: %v", err) } // Test start err = plugin.Start(context.Background()) if err != nil { t.Fatalf("Start failed: %v", err) } // Test health health := plugin.Health() if !health.Healthy { t.Errorf("Plugin unhealthy: %s", health.Message) } // Test stop err = plugin.Stop(context.Background()) if err != nil { t.Fatalf("Stop failed: %v", err) } }
Future Enhancements
- External plugin loading (shared libraries)
- Plugin marketplace/registry
- Plugin sandboxing
- Inter-plugin communication
- Plugin versioning and updates
- Resource limits enforcement
Next Steps
To get started developing plugins, see the Plugin Development Guide.