- 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
120 lines
3.9 KiB
Go
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
|
|
}
|