Creating a New Provider
This guide explains how to develop a new provider for the Thand Agent. Providers are modular components that implement specific capabilities like authentication, RBAC, notifications, and identity management.
Prerequisites
- Go 1.21+
- Understanding of the Thand Agent architecture
- Access to the
internal/providersdirectory
Provider Structure
All providers must implement the ProviderImpl interface defined in internal/models/provider.go. To simplify implementation, most providers embed *models.BaseProvider, which provides default implementations for many methods.
The Provider Interface
type ProviderImpl interface {
Initialize(identifier string, provider Provider) error
// Base methods (handled by BaseProvider)
GetConfig() *BasicConfig
GetIdentifier() string
GetName() string
GetDescription() string
GetProvider() string
// Capability checks (handled by BaseProvider)
GetCapabilities() *ProviderCapabilities
HasCapability(capability ProviderCapability) bool
// Sub-interfaces
ProviderNotifier
ProviderAuthorizor
ProviderRoleBasedAccessControl
ProviderIdentities
}
Step-by-Step Implementation
1. Create the Provider Package
Create a new directory in internal/providers/ with your provider name (e.g., myprovider).
mkdir internal/providers/myprovider
Create a provider.go (or main.go) file in that directory.
2. Define the Provider Struct
Define your provider struct and embed *models.BaseProvider.
package myprovider
import (
"context"
"github.com/thand-io/agent/internal/models"
)
const ProviderName = "myprovider"
type MyProvider struct {
*models.BaseProvider
}
3. Implement Initialization
Implement the Initialize method to set up the base provider and capabilities.
func (p *MyProvider) Initialize(identifier string, provider models.Provider) error {
// Define what this provider supports
capabilities := &models.ProviderCapabilities{
Roles: &models.RolesConfiguration{
Enabled: true,
Synchronizable: true,
Interval: 60, // Default sync interval in minutes
},
// Add other capabilities...
}
p.BaseProvider = models.NewBaseProvider(
identifier,
provider,
capabilities,
)
// Parse custom configuration if needed
// config := &MyProviderConfig{}
// if err := provider.Config.Parse(config); err != nil { ... }
return nil
}
4. Implement Capabilities
Implement the methods required for the capabilities you enabled.
Authorizer (Authentication)
If your provider supports authentication:
func (p *MyProvider) AuthorizeSession(ctx context.Context, auth *models.AuthorizeUser) (*models.AuthorizeSessionResponse, error) {
// Logic to start authentication flow
return &models.AuthorizeSessionResponse{
Url: "https://auth.example.com/login",
}, nil
}
func (p *MyProvider) CreateSession(ctx context.Context, auth *models.AuthorizeUser) (*models.Session, error) {
// Logic to create a session after successful auth
return &models.Session{ ... }, nil
}
RBAC (Roles & Permissions)
If your provider supports RBAC:
func (p *MyProvider) AuthorizeRole(ctx context.Context, req *models.AuthorizeRoleRequest) (*models.AuthorizeRoleResponse, error) {
// Logic to grant a role to a user
return &models.AuthorizeRoleResponse{}, nil
}
func (p *MyProvider) RevokeRole(ctx context.Context, req *models.RevokeRoleRequest) (*models.RevokeRoleResponse, error) {
// Logic to revoke a role
return &models.RevokeRoleResponse{}, nil
}
// Implement synchronization methods if Synchronizable is true
func (p *MyProvider) SynchronizeRoles(ctx context.Context, req *models.SynchronizeRolesRequest) (*models.SynchronizeRolesResponse, error) {
// Logic to fetch roles from the external system
return &models.SynchronizeRolesResponse{
Roles: []models.ProviderRole{ ... },
}, nil
}
Notifier
If your provider sends notifications:
func (p *MyProvider) SendNotification(ctx context.Context, notification models.NotificationRequest) error {
// Logic to send email, slack message, etc.
return nil
}
5. Register the Provider
Add your provider to internal/providers/registry.go.
import (
// ...
"github.com/thand-io/agent/internal/providers/myprovider"
)
func init() {
// ...
Register(myprovider.ProviderName, func() models.ProviderImpl {
return &myprovider.MyProvider{}
})
}
Configuration
Users configure your provider in their config.yaml.
providers:
my-instance:
name: My Provider Instance
provider: myprovider
enabled: true
config:
api_key: "secret-key"
endpoint: "https://api.example.com"
Access this configuration in your Initialize method or other methods via p.GetConfig().
Best Practices
- Use BaseProvider: Always embed
BaseProviderto handle default behaviors and state management. - Check Capabilities: Before performing an action, check if the capability is enabled using
p.HasCapability(). - Error Handling: Return meaningful errors. Use
models.ErrNotImplementedfor optional methods you don’t support. - Context: Always respect the
context.Contextfor timeouts and cancellation. - Logging: Use the standard logger for debugging and information.
Testing
Create unit tests for your provider logic. You can use the internal/models/provider_test.go utilities to help test common behaviors.