Plugin System Guide
Comprehensive guide to the FinFocus plugin architecture, management, and development.
Table of Contents
Section titled “Table of Contents”- Overview
- Plugin Architecture
- Plugin Management
- Available Plugins
- Plugin Development
- Configuration
- Troubleshooting
Overview
Section titled “Overview”FinFocus Core uses a plugin-based architecture to fetch cost data from various sources. Plugins are standalone binaries that implement the CostSource gRPC service, allowing extensible integration with cloud providers and cost management platforms.
Key Benefits
Section titled “Key Benefits”- Extensibility: Support for new cost data sources without core changes
- Modularity: Independent plugin development and deployment
- Flexibility: Multiple cost data sources for validation and accuracy
- Maintainability: Plugin-specific logic separated from core functionality
Plugin Architecture
Section titled “Plugin Architecture”Communication Protocol
Section titled “Communication Protocol”Plugins communicate with FinFocus Core via gRPC using the finfocus-spec protocol definitions.
graph TD A[FinFocus Core] -->|gRPC| B[Plugin Process] B -->|CostSource API| C[Cloud Provider] B -->|Response| A
D[Plugin Registry] --> A E[Plugin Manifest] --> D F[Plugin Binary] --> BPlugin Lifecycle
Section titled “Plugin Lifecycle”- Discovery: Registry scans
~/.finfocus/plugins/directory and selects the latest version of each plugin (using Semantic Versioning). - Launch: Process launcher starts plugin binary
- Connection: gRPC connection establishment
- Authentication: Plugin authenticates with data source
- Query: Cost data requests and responses
- Cleanup: Process termination and resource cleanup
Directory Structure
Section titled “Directory Structure”~/.finfocus/plugins/├── kubecost/│ └── 1.0.0/│ ├── finfocus-kubecost # Plugin binary│ └── plugin.manifest.json # Optional manifest├── aws-plugin/│ └── 0.1.0/│ ├── finfocus-aws│ └── plugin.manifest.json└── custom-plugin/ └── 0.2.0/ └── finfocus-customPlugin Management
Section titled “Plugin Management”Plugin Versioning
Section titled “Plugin Versioning”FinFocus Core supports side-by-side installation of multiple plugin versions. When performing cost analysis or other operations, the system automatically selects the latest version of each installed plugin based on Semantic Versioning (SemVer) rules.
- Pre-release versions (e.g.,
v1.0.0-alpha) are ignored if a stable version (e.g.,v1.0.0) is present. - Invalid versions are skipped with a warning.
- Corrupted directories are skipped with a warning.
To use a specific older version, you must currently uninstall newer versions or use a custom plugin directory.
List Available Plugins
Section titled “List Available Plugins”# List all discovered pluginsfinfocus plugin listSample Output:
NAME VERSION STATUS SUPPORTS BINARYkubecost 1.0.0 ready projected,actual finfocus-kubecostaws-plugin 0.1.0 ready projected finfocus-awsazure-cost 0.2.0 error - finfocus-azureValidate Plugin Installation
Section titled “Validate Plugin Installation”# Validate all pluginsfinfocus plugin validate
# Validate specific pluginfinfocus plugin validate --adapter kubecostValidation Checks:
- Binary exists and is executable
- Plugin responds to gRPC health checks
- Required capabilities are supported
- Authentication configuration is valid
Plugin Installation
Section titled “Plugin Installation”Manual Installation
Section titled “Manual Installation”# Create plugin directorymkdir -p ~/.finfocus/plugins/kubecost/1.0.0/
# Download and install plugin binarycurl -L https://github.com/rshade/finfocus-plugin-kubecost/releases/latest/download/finfocus-kubecost-linux-amd64 \ -o ~/.finfocus/plugins/kubecost/1.0.0/finfocus-kubecost
# Make executablechmod +x ~/.finfocus/plugins/kubecost/1.0.0/finfocus-kubecost
# Optional: Create manifestcat > ~/.finfocus/plugins/kubecost/1.0.0/plugin.manifest.json << EOF{ "name": "kubecost", "version": "1.0.0", "binary": "finfocus-kubecost", "supports": ["projected_cost", "actual_cost"], "description": "Kubecost integration for Kubernetes cost analysis"}EOFPackage Manager Installation (Future)
Section titled “Package Manager Installation (Future)”# Coming soonfinfocus plugin install kubecost@1.0.0finfocus plugin install aws-plugin@latestPlugin Configuration
Section titled “Plugin Configuration”Environment Variables
Section titled “Environment Variables”Most plugins use environment variables for configuration:
# Kubecost plugin configurationexport KUBECOST_API_URL="http://kubecost.example.com:9090"export KUBECOST_API_TOKEN="your-api-token"
# AWS plugin configurationexport AWS_ACCESS_KEY_ID="your-access-key"export AWS_SECRET_ACCESS_KEY="your-secret-key"export AWS_REGION="us-west-2"
# Azure plugin configurationexport AZURE_CLIENT_ID="your-client-id"export AZURE_CLIENT_SECRET="your-client-secret"export AZURE_TENANT_ID="your-tenant-id"Configuration Files
Section titled “Configuration Files”Some plugins support configuration files:
api_url: 'http://kubecost.example.com:9090'api_token: 'your-api-token'timeout: 30sretry_attempts: 3clusters: - name: 'production' context: 'prod-k8s' - name: 'staging' context: 'staging-k8s'Available Plugins
Section titled “Available Plugins”Kubecost Plugin
Section titled “Kubecost Plugin”Repository: finfocus-plugin-kubecost
Capabilities:
- Actual cost data from Kubecost API
- Kubernetes workload cost attribution
- Pod, namespace, and cluster-level costs
- Multi-cluster support
Configuration:
export KUBECOST_API_URL="http://kubecost.example.com:9090"export KUBECOST_API_TOKEN="optional-api-token"Usage:
finfocus cost actual --pulumi-json plan.json --from 2025-01-01 --adapter kubecostAWS Pricing Plugin
Section titled “AWS Pricing Plugin”Repository: finfocus-plugin-aws (planned)
Capabilities:
- AWS Cost Explorer integration
- EC2, RDS, S3 pricing data
- Reserved instance optimization
- Multi-account cost aggregation
Configuration:
export AWS_ACCESS_KEY_ID="your-access-key"export AWS_SECRET_ACCESS_KEY="your-secret-key"export AWS_REGION="us-west-2"Azure Cost Management Plugin
Section titled “Azure Cost Management Plugin”Repository: finfocus-plugin-azure (planned)
Capabilities:
- Azure Cost Management API integration
- Resource group cost breakdown
- Azure subscription analysis
- Reserved instance tracking
GCP Cloud Billing Plugin
Section titled “GCP Cloud Billing Plugin”Repository: finfocus-plugin-gcp (planned)
Capabilities:
- Google Cloud Billing API integration
- Project and folder cost attribution
- Committed use discount tracking
- BigQuery cost analysis
Plugin Development
Section titled “Plugin Development”Getting Started
Section titled “Getting Started”Prerequisites
Section titled “Prerequisites”- Go: Version 1.25.8 or later
- Protocol Buffers: For gRPC service definitions
- finfocus-spec: Protocol definitions repository
Project Setup
Section titled “Project Setup”# Create new plugin projectmkdir finfocus-plugin-mycloudcd finfocus-plugin-mycloud
# Initialize Go modulego mod init github.com/yourusername/finfocus-plugin-mycloud
# Add finfocus-spec dependencygo get github.com/rshade/finfocus-spec/sdk/go@latestImplementation Guide
Section titled “Implementation Guide”1. Implement CostSource Interface
Section titled “1. Implement CostSource Interface”package main
import ( "context" "fmt"
pb "github.com/rshade/finfocus-spec/sdk/go/proto/finfocus/v1" "google.golang.org/grpc")
type MyCloudPlugin struct { pb.UnimplementedCostSourceServiceServer client *MyCloudClient // Your cloud provider client}
func (p *MyCloudPlugin) Name(ctx context.Context, req *pb.NameRequest) (*pb.NameResponse, error) { return &pb.NameResponse{ Name: "mycloud", Version: "1.0.0", }, nil}
func (p *MyCloudPlugin) GetProjectedCost(ctx context.Context, req *pb.GetProjectedCostRequest) (*pb.GetProjectedCostResponse, error) { // Implement projected cost logic resources := req.GetResources() var costItems []*pb.CostItem
for _, resource := range resources { cost, err := p.calculateProjectedCost(resource) if err != nil { continue // Skip resources we can't price }
costItems = append(costItems, &pb.CostItem{ ResourceId: resource.GetId(), ResourceType: resource.GetType(), MonthlyCost: cost.Monthly, HourlyCost: cost.Hourly, Currency: "USD", Notes: cost.Notes, }) }
return &pb.GetProjectedCostResponse{ CostItems: costItems, }, nil}
func (p *MyCloudPlugin) GetActualCost(ctx context.Context, req *pb.GetActualCostRequest) (*pb.GetActualCostResponse, error) { // Implement actual cost logic resources := req.GetResources() fromTime := req.GetFromTime().AsTime() toTime := req.GetToTime().AsTime()
var costItems []*pb.CostItem
for _, resource := range resources { cost, err := p.getActualCost(resource, fromTime, toTime) if err != nil { continue }
costItems = append(costItems, &pb.CostItem{ ResourceId: resource.GetId(), ResourceType: resource.GetType(), TotalCost: cost.Total, Currency: "USD", StartTime: req.GetFromTime(), EndTime: req.GetToTime(), DailyCosts: cost.Daily, }) }
return &pb.GetActualCostResponse{ CostItems: costItems, }, nil}2. Implement Main Function
Section titled “2. Implement Main Function”func main() { // Create gRPC server lis, err := net.Listen("tcp", ":0") // Random available port if err != nil { log.Fatalf("Failed to listen: %v", err) }
// Print the port for FinFocus Core to connect to addr := lis.Addr().(*net.TCPAddr) fmt.Printf("PLUGIN_PORT=%d\n", addr.Port)
// Create and register plugin s := grpc.NewServer() plugin := &MyCloudPlugin{ client: NewMyCloudClient(), // Initialize your API client }
pb.RegisterCostSourceServiceServer(s, plugin)
// Start serving log.Printf("Plugin serving on port %d", addr.Port) if err := s.Serve(lis); err != nil { log.Fatalf("Failed to serve: %v", err) }}3. Resource Mapping
Section titled “3. Resource Mapping”func (p *MyCloudPlugin) calculateProjectedCost(resource *pb.ResourceDescriptor) (*Cost, error) { // Map Pulumi resource types to cloud provider services switch resource.GetType() { case "mycloud:compute/instance:Instance": return p.getComputeCost(resource) case "mycloud:storage/bucket:Bucket": return p.getStorageCost(resource) case "mycloud:database/cluster:Cluster": return p.getDatabaseCost(resource) default: return nil, fmt.Errorf("unsupported resource type: %s", resource.GetType()) }}
func (p *MyCloudPlugin) getComputeCost(resource *pb.ResourceDescriptor) (*Cost, error) { // Extract instance size from resource properties properties := resource.GetProperties() instanceSize := properties["instanceSize"]
// Query pricing API pricing, err := p.client.GetInstancePricing(instanceSize) if err != nil { return nil, err }
return &Cost{ Hourly: pricing.HourlyRate, Monthly: pricing.HourlyRate * 730, // 730 hours per month Notes: fmt.Sprintf("%s on-demand pricing", instanceSize), }, nil}Plugin Testing
Section titled “Plugin Testing”Unit Testing
Section titled “Unit Testing”func TestMyCloudPlugin(t *testing.T) { plugin := &MyCloudPlugin{ client: &MockMyCloudClient{}, // Mock client for testing }
req := &pb.GetProjectedCostRequest{ Resources: []*pb.ResourceDescriptor{ { Id: "test-instance", Type: "mycloud:compute/instance:Instance", Properties: map[string]string{ "instanceSize": "medium", }, }, }, }
resp, err := plugin.GetProjectedCost(context.Background(), req) assert.NoError(t, err) assert.Len(t, resp.CostItems, 1) assert.Equal(t, "test-instance", resp.CostItems[0].ResourceId)}Integration Testing
Section titled “Integration Testing”# Build plugingo build -o finfocus-mycloud
# Test with FinFocus Coremkdir -p ~/.finfocus/plugins/mycloud/1.0.0/cp finfocus-mycloud ~/.finfocus/plugins/mycloud/1.0.0/
# Validate pluginfinfocus plugin validate --adapter mycloud
# Test cost calculationfinfocus cost projected --pulumi-json test-plan.json --adapter mycloudBuilding and Distribution
Section titled “Building and Distribution”Cross-Platform Builds
Section titled “Cross-Platform Builds”# Build for multiple platformsGOOS=linux GOARCH=amd64 go build -o finfocus-mycloud-linux-amd64GOOS=darwin GOARCH=amd64 go build -o finfocus-mycloud-darwin-amd64GOOS=windows GOARCH=amd64 go build -o finfocus-mycloud-windows-amd64.exeGitHub Releases
Section titled “GitHub Releases”name: Releaseon: push: tags: ['v*']jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1.25.8'
- name: Build binaries run: | GOOS=linux GOARCH=amd64 go build -o finfocus-mycloud-linux-amd64 GOOS=darwin GOARCH=amd64 go build -o finfocus-mycloud-darwin-amd64 GOOS=windows GOARCH=amd64 go build -o finfocus-mycloud-windows-amd64.exe
- name: Create release uses: softprops/action-gh-release@v2 with: files: | finfocus-mycloud-*Configuration
Section titled “Configuration”Plugin Manifest
Section titled “Plugin Manifest”Optional plugin.manifest.json file provides metadata:
{ "name": "mycloud", "version": "1.0.0", "description": "MyCloud cost integration plugin", "binary": "finfocus-mycloud", "supports": ["projected_cost", "actual_cost"], "author": "Your Name", "homepage": "https://github.com/yourusername/finfocus-plugin-mycloud", "config": { "required_env": ["MYCLOUD_API_KEY"], "optional_env": ["MYCLOUD_REGION", "MYCLOUD_TIMEOUT"], "config_file": "config.yaml" }}Authentication
Section titled “Authentication”Environment Variables (Recommended)
Section titled “Environment Variables (Recommended)”export MYCLOUD_API_KEY="your-api-key"export MYCLOUD_API_SECRET="your-api-secret"export MYCLOUD_REGION="us-west-2"Configuration Files
Section titled “Configuration Files”api: key: 'your-api-key' secret: 'your-api-secret' endpoint: 'https://api.mycloud.com' timeout: 30s
regions: - 'us-west-2' - 'us-east-1'
cache: enabled: true ttl: '1h'Credential Providers
Section titled “Credential Providers”// Support multiple credential sourcestype CredentialProvider interface { GetCredentials(ctx context.Context) (*Credentials, error)}
// Environment variable providertype EnvCredentialProvider struct{}
func (p *EnvCredentialProvider) GetCredentials(ctx context.Context) (*Credentials, error) { return &Credentials{ APIKey: os.Getenv("MYCLOUD_API_KEY"), APISecret: os.Getenv("MYCLOUD_API_SECRET"), Region: os.Getenv("MYCLOUD_REGION"), }, nil}
// Configuration file providertype FileCredentialProvider struct { ConfigPath string}
func (p *FileCredentialProvider) GetCredentials(ctx context.Context) (*Credentials, error) { // Load from config file config, err := loadConfigFile(p.ConfigPath) if err != nil { return nil, err }
return &Credentials{ APIKey: config.API.Key, APISecret: config.API.Secret, Region: config.API.Region, }, nil}Troubleshooting
Section titled “Troubleshooting”Common Plugin Issues
Section titled “Common Plugin Issues”Plugin Not Found
Section titled “Plugin Not Found”# Check plugin directory structurels -la ~/.finfocus/plugins/
# Verify binary exists and is executablels -la ~/.finfocus/plugins/*/*/finfocus-*chmod +x ~/.finfocus/plugins/*/*/finfocus-*Connection Failures
Section titled “Connection Failures”# Test plugin directly~/.finfocus/plugins/kubecost/1.0.0/finfocus-kubecost
# Check network connectivitycurl -v http://kubecost.example.com:9090/api/v1/costDataModel
# Verify authenticationexport KUBECOST_API_TOKEN="test-token"finfocus plugin validate --adapter kubecostAuthentication Issues
Section titled “Authentication Issues”# Check environment variablesenv | grep -E "(AWS|AZURE|GCP|KUBECOST)"
# Test API accesscurl -H "Authorization: Bearer $API_TOKEN" https://api.provider.com/test
# Review plugin logsfinfocus --debug cost actual --adapter kubecost --from 2025-01-01Debug Mode
Section titled “Debug Mode”# Enable debug loggingfinfocus --debug plugin validate
# Plugin-specific debuggingexport PLUGIN_DEBUG=1export PLUGIN_LOG_LEVEL=debugfinfocus cost actual --adapter kubecost --from 2025-01-01Plugin Development Debugging
Section titled “Plugin Development Debugging”// Add logging to pluginimport "log"
func (p *MyCloudPlugin) GetProjectedCost(ctx context.Context, req *pb.GetProjectedCostRequest) (*pb.GetProjectedCostResponse, error) { log.Printf("GetProjectedCost called with %d resources", len(req.GetResources()))
for _, resource := range req.GetResources() { log.Printf("Processing resource: %s (%s)", resource.GetId(), resource.GetType()) }
// ... implementation}Related Documentation
Section titled “Related Documentation”- User Guide - Using plugins for cost analysis
- Cost Calculations - Understanding cost methodologies
- Installation - Setting up plugins
- Troubleshooting - Common issues and solutions
Community Plugins
Section titled “Community Plugins”Contributing a Plugin
Section titled “Contributing a Plugin”- Implement the CostSource interface
- Add comprehensive tests
- Create documentation
- Submit to plugin registry (coming soon)
Plugin Registry (Future)
Section titled “Plugin Registry (Future)”# Search available pluginsfinfocus plugin search aws
# Install from registryfinfocus plugin install kubecost@1.0.0
# Update pluginsfinfocus plugin update --all