obm/internal/config/config.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

131 lines
3.7 KiB
Go

// Package config handles loading and parsing obm configuration files.
package config
import (
"fmt"
"os"
)
// Config represents a complete obm configuration with all variables.
type Config struct {
// Project name (for metadata)
Project string `json:"project,omitempty"`
// Provider settings
Provider ProviderConfig `json:"provider"`
// All Terraform variables (name -> value as string)
Variables map[string]string `json:"variables,omitempty"`
}
// ProviderConfig holds provider-specific configuration.
type ProviderConfig struct {
Name string `json:"name"`
Region string `json:"region,omitempty"`
Profile string `json:"profile,omitempty"`
}
// Load reads a config file from the given path (supports .json format).
// For .env files, use ParseDotEnv instead.
func Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config %s: %w", path, err)
}
return ParseConfigJSON(data)
}
// ParseConfigJSON parses JSON config data into a Config struct.
func ParseConfigJSON(data []byte) (*Config, error) {
// For now, we primarily support .env files.
// This function exists for potential JSON configs in the future.
// The config package focuses on .env <-> tfvars conversion.
cfg := &Config{
Variables: make(map[string]string),
}
return cfg, nil
}
// GetValue returns the value for a variable, or the default if not set.
func (c *Config) GetValue(name string) (string, bool) {
if c.Variables == nil {
return "", false
}
v, ok := c.Variables[name]
return v, ok
}
// SetValue sets a variable value.
func (c *Config) SetValue(name, value string) {
if c.Variables == nil {
c.Variables = make(map[string]string)
}
c.Variables[name] = value
}
// GetWithDefault returns the value for a variable, falling back to the
// schema default if not set. Returns empty string if neither exists.
func (c *Config) GetWithDefault(name string) string {
if v, ok := c.GetValue(name); ok {
return v
}
if schema, ok := SchemaMap()[name]; ok {
return schema.Default
}
return ""
}
// Validate checks that all required values are set.
func (c *Config) Validate() error {
for _, v := range RequiredVars() {
if val, ok := c.GetValue(v.Name); !ok || val == "" {
// Required but not set — but check if provider selection makes it optional
// For now, just check cloud_provider is set
if v.Name == "cloud_provider" {
prov, _ := c.GetValue("cloud_provider")
if prov == "" {
return fmt.Errorf("required variable %s is not set", v.Name)
}
} else if v.Name == "hcloud_token" || v.Name == "do_token" {
// Token requirement depends on provider selection
prov, _ := c.GetValue("cloud_provider")
if v.Name == "hcloud_token" && prov == "hetzner" && val == "" {
return fmt.Errorf("required variable %s is not set (provider is hetzner)", v.Name)
}
if v.Name == "do_token" && prov == "digitalocean" && val == "" {
return fmt.Errorf("required variable %s is not set (provider is digitalocean)", v.Name)
}
} else {
return fmt.Errorf("required variable %s is not set", v.Name)
}
}
}
return nil
}
// Merge combines values from another config, with other taking precedence.
func (c *Config) Merge(other *Config) {
if other == nil {
return
}
for k, v := range other.Variables {
c.Variables[k] = v
}
if other.Project != "" {
c.Project = other.Project
}
if other.Provider.Name != "" {
c.Provider = other.Provider
}
}
// Clone returns a deep copy of the config.
func (c *Config) Clone() *Config {
clone := &Config{
Project: c.Project,
Provider: c.Provider,
Variables: make(map[string]string, len(c.Variables)),
}
for k, v := range c.Variables {
clone.Variables[k] = v
}
return clone
}