obm/internal/provider/provider.go
MermaidMan 33d9a2cb2e deploy walkthrough, API validation, inference client, Hetzner provider
- Interactive deploy command with 8-step walkthrough:
  framework → provider → token → SSH → server → inference → tailscale → discord
- .env file generation from walkthrough config
- DeploymentConfig struct with framework-aware defaults
- Inference API client with validation for Venice, OpenRouter, OpenAI, Anthropic
- Hetzner Cloud provider: token validation, SSH key listing
- DotEnv parser/writer with schema validation
- Destroy command with confirmation prompt
- Validation subcommand for checking existing .env files
- All tests passing, go vet clean
2026-05-22 15:29:27 +00:00

120 lines
3.9 KiB
Go

// Package provider defines the interface for cloud providers (Hetzner, DigitalOcean)
// and provides a registry for provider implementations.
package provider
import (
"context"
"fmt"
"github.com/openboatmobile/obm/internal/validation"
)
// Provider is the interface that cloud providers must implement.
// It extends validation.Validatable with lifecycle methods for server management.
type Provider interface {
// Name returns the provider identifier (e.g. "hetzner", "digitalocean").
Name() string
// ProviderName returns the display name (e.g. "Hetzner Cloud").
// Satisfies validation.Validatable.
ProviderName() string
// Validate performs a quick credential check. Returns nil if credentials
// are valid, or an error describing what's wrong.
// For structured validation with detailed results, use the validation framework.
Validate(ctx context.Context) error
// Checks returns all validation checks for this provider.
// Provider implementations should inspect their config to decide which
// checks are applicable (e.g. skip SSH checks if no token is configured).
Checks(ctx context.Context) []validation.Check
// TokenEnvKey returns the environment variable name for the API token
// (e.g. "HCLOUD_TOKEN", "DIGITALOCEAN_TOKEN").
TokenEnvKey() string
// SetToken configures the API token for this provider.
SetToken(token string)
// GetToken returns the currently configured token (may be empty).
GetToken() string
}
// BaseProvider provides shared fields and methods for provider implementations.
// Embed this in concrete provider structs to avoid reimplementing the common methods.
type BaseProvider struct {
DisplayName string
Identifier string
TokenKey string
token string
}
func (b *BaseProvider) Name() string { return b.Identifier }
func (b *BaseProvider) ProviderName() string { return b.DisplayName }
func (b *BaseProvider) TokenEnvKey() string { return b.TokenKey }
func (b *BaseProvider) SetToken(t string) { b.token = t }
func (b *BaseProvider) GetToken() string { return b.token }
// Validate performs a quick check: just verifies a token is set.
// Concrete providers should override this to also call the API.
func (b *BaseProvider) Validate(ctx context.Context) error {
if b.token == "" {
return fmt.Errorf("%s: no API token configured (set %s)", b.DisplayName, b.TokenKey)
}
return nil
}
// Registry holds factory functions for providers, keyed by name.
// Each factory returns a new, zero-value provider ready for configuration.
var Registry = map[string]func() Provider{}
// Register adds a provider factory to the global registry.
func Register(name string, factory func() Provider) {
Registry[name] = factory
}
// Get looks up a provider by name and creates a new instance.
// Returns an error if the name is not registered.
func Get(name string) (Provider, error) {
factory, ok := Registry[name]
if !ok {
available := make([]string, 0, len(Registry))
for k := range Registry {
available = append(available, k)
}
return nil, fmt.Errorf("unknown provider %q (available: %v)", name, available)
}
return factory(), nil
}
// Names returns all registered provider names.
func Names() []string {
names := make([]string, 0, len(Registry))
for k := range Registry {
names = append(names, k)
}
return names
}
// ValidateAll runs the validation framework for all providers that have tokens configured.
// Returns a slice of reports (one per provider that was checked) and a boolean indicating
// whether all validations passed.
func ValidateAll(ctx context.Context, providers []Provider) ([]*validation.Report, bool) {
reports := make([]*validation.Report, 0, len(providers))
allPassed := true
for _, p := range providers {
if p.GetToken() == "" {
// Skip providers with no token configured
continue
}
runner := validation.NewRunner(p)
report := runner.Run(ctx)
reports = append(reports, report)
if report.HasFailures() {
allPassed = false
}
}
return reports, allPassed
}