Plugin Development¶
Create external plugins to extend uptool with custom integrations.
Overview¶
Built-in vs Plugin:
- Built-in: Compiled into uptool (npm, Helm, Terraform) - for widely-used ecosystems
- Plugin: External
.solibrary - for custom/experimental/proprietary integrations
Plugins allow custom integrations without forking uptool.
Plugin Interface¶
Implement the engine.Integration interface:
type Integration interface {
Name() string
Detect(ctx context.Context, repoRoot string) ([]*Manifest, error)
Plan(ctx context.Context, manifest *Manifest) (*UpdatePlan, error)
Apply(ctx context.Context, plan *UpdatePlan) (*ApplyResult, error)
Validate(ctx context.Context, manifest *Manifest) error
}
Export a RegisterWith function:
func RegisterWith(register func(name string, constructor func() engine.Integration)) {
register("yourintegration", New)
}
Creating a Plugin¶
1. Project Structure¶
2. Implement Integration¶
// plugin.go
package main
import (
"context"
"github.com/santosr2/uptool/internal/engine"
)
type MyIntegration struct{}
func New() engine.Integration {
return &MyIntegration{}
}
func (i *MyIntegration) Name() string {
return "myintegration"
}
func (i *MyIntegration) Detect(ctx context.Context, repoRoot string) ([]*engine.Manifest, error) {
// Find manifest files
return manifests, nil
}
func (i *MyIntegration) Plan(ctx context.Context, manifest *engine.Manifest) (*engine.UpdatePlan, error) {
// Query registry for updates
return plan, nil
}
func (i *MyIntegration) Apply(ctx context.Context, plan *engine.UpdatePlan) (*engine.ApplyResult, error) {
// Update manifest file
return result, nil
}
func (i *MyIntegration) Validate(ctx context.Context, manifest *engine.Manifest) error {
// Validate manifest syntax
return nil
}
3. Plugin Entry Point¶
// main.go
package main
import "github.com/santosr2/uptool/internal/engine"
func RegisterWith(register func(name string, constructor func() engine.Integration)) {
register("myintegration", New)
}
func main() {}
4. Build Plugin¶
Plugin Discovery¶
uptool searches for plugins in these locations (in order):
./plugins/- Current directory~/.config/uptool/plugins/- User config/etc/uptool/plugins/- System-wide$UPTOOL_PLUGIN_DIR- Custom location
Install:
Verify:
Testing¶
Unit Tests¶
// plugin_test.go
package main
import (
"context"
"testing"
)
func TestDetect(t *testing.T) {
integration := New()
manifests, err := integration.Detect(context.Background(), "./testdata")
if err != nil {
t.Fatal(err)
}
if len(manifests) != 1 {
t.Errorf("expected 1 manifest, got %d", len(manifests))
}
}
Integration Testing¶
# Build and test
go build -buildmode=plugin -o myintegration.so .
cp myintegration.so ~/.config/uptool/plugins/
uptool scan --only=myintegration
Best Practices¶
- Version compatibility: Match uptool's Go version and dependencies
- Error handling: Return descriptive errors with context
- Logging: Use structured logging, avoid print statements
- Context: Respect context cancellation
- Resource cleanup: Close files, network connections
- Testing: >70% coverage target
- Documentation: Add README with usage examples
Example Plugin¶
See examples/plugins/python/ for a complete example:
- Detects
pyproject.toml,requirements.txt,Pipfile - Queries PyPI registry
- Updates dependency versions
Distribution¶
GitHub Release¶
# .goreleaser.yml
builds:
- id: plugin
main: .
flags:
- -buildmode=plugin
goos: [linux, darwin]
goarch: [amd64, arm64]
Installation Script¶
#!/bin/bash
PLUGIN_DIR="${HOME}/.config/uptool/plugins"
mkdir -p "$PLUGIN_DIR"
curl -LO "https://github.com/you/plugin/releases/latest/download/plugin-$(uname -s)-$(uname -m).so"
mv plugin-*.so "$PLUGIN_DIR/myplugin.so"
Limitations¶
- Plugin must be compiled with same Go version as uptool
- Shared libraries are OS/arch specific
- Cannot modify core engine behavior
- Plugin crashes may crash uptool
See Also¶
- Integration Examples - Built-in integration code
- API Reference - Engine API documentation
- CONTRIBUTING.md - Development guidelines