- 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
131 lines
3.7 KiB
Go
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
|
|
}
|