package config import ( "fmt" "os" "github.com/hashicorp/hcl/v2/hclsimple" ) type CapacityConfig struct { CPU int `hcl:"cpu,optional"` MemoryMB int `hcl:"memory_mb,optional"` } type Config struct { DBPath string `hcl:"db_path,optional"` ListenAddr string `hcl:"listen_addr,optional"` CAPath string `hcl:"ca_path,optional"` ServerCertPath string `hcl:"server_cert_path,optional"` ServerKeyPath string `hcl:"server_key_path,optional"` NodeCapacity *CapacityConfig `hcl:"node_capacity,block"` } type Flags struct { DBPath *string ListenAddr *string CAPath *string ServerCertPath *string ServerKeyPath *string CPU *int MemoryMB *int } type Environ map[string]string func Load(paths ...string) (*Config, error) { for _, p := range paths { if _, err := os.Stat(p); err != nil { continue } data, err := os.ReadFile(p) if err != nil { return nil, fmt.Errorf("read config %s: %w", p, err) } var cfg Config if err := hclsimple.Decode(p, data, nil, &cfg); err != nil { return nil, fmt.Errorf("decode config %s: %w", p, err) } return &cfg, nil } return &Config{}, nil } func (c *Config) MergeOverrides(flags Flags, env Environ) *Config { out := &Config{ DBPath: c.DBPath, ListenAddr: c.ListenAddr, CAPath: c.CAPath, ServerCertPath: c.ServerCertPath, ServerKeyPath: c.ServerKeyPath, NodeCapacity: c.NodeCapacity, } applyStr := func(flag *string, envKey, fileVal string) string { if flag != nil { return *flag } if v, ok := env[envKey]; ok && v != "" { return v } return fileVal } out.DBPath = applyStr(flags.DBPath, "ORCA_DB", out.DBPath) out.ListenAddr = applyStr(flags.ListenAddr, "ORCA_LISTEN_ADDR", out.ListenAddr) out.CAPath = applyStr(flags.CAPath, "ORCA_CA_PATH", out.CAPath) out.ServerCertPath = applyStr(flags.ServerCertPath, "ORCA_SERVER_CERT_PATH", out.ServerCertPath) out.ServerKeyPath = applyStr(flags.ServerKeyPath, "ORCA_SERVER_KEY_PATH", out.ServerKeyPath) if out.NodeCapacity == nil { out.NodeCapacity = &CapacityConfig{} } else { nc := *out.NodeCapacity out.NodeCapacity = &nc } if flags.CPU != nil { out.NodeCapacity.CPU = *flags.CPU } else if v, ok := env["ORCA_NODE_CPU"]; ok && v != "" { if n, err := atoi(v); err == nil { out.NodeCapacity.CPU = n } } if flags.MemoryMB != nil { out.NodeCapacity.MemoryMB = *flags.MemoryMB } else if v, ok := env["ORCA_NODE_MEMORY_MB"]; ok && v != "" { if n, err := atoi(v); err == nil { out.NodeCapacity.MemoryMB = n } } return out } func atoi(s string) (int, error) { n := 0 if s == "" { return 0, fmt.Errorf("empty") } neg := false i := 0 if s[0] == '-' { neg = true i = 1 } for ; i < len(s); i++ { if s[i] < '0' || s[i] > '9' { return 0, fmt.Errorf("bad") } n = n*10 + int(s[i]-'0') } if neg { n = -n } return n, nil }