feat(P02): HCL config file parsing — internal/config package (REQ-054)

New internal/config package: Config struct (HCL tags), Load(paths...),
MergeOverrides(flags, env) with flag>env>file>default precedence (D-039).
No package-level state (AD-023). --config persistent flag on root command;
daemon uses cfg.ListenAddr when flag at default. 11 config tests + 2 cli tests.

---ci---
project: orca
phase: 2
milestone: v0.7
status: verify
requirements:
  covered: [REQ-054]
  partial: []
---/ci---
This commit is contained in:
Jon Chery
2026-08-04 00:09:33 +00:00
parent 27f2abf8fb
commit cd07e435d9
7 changed files with 475 additions and 1 deletions
+5 -1
View File
@@ -34,10 +34,14 @@ var daemonCmd = &cobra.Command{
defer closer()
log := newLogger()
addr := daemonAddr
if cfg := configFromCtx(cmd.Context()); cfg != nil && cfg.ListenAddr != "" && addr == ":8080" {
addr = cfg.ListenAddr
}
srv := daemon.NewServer(daemon.Options{
DB: db,
Log: log,
Addr: daemonAddr,
Addr: addr,
Actor: "daemon",
})
+21
View File
@@ -1,13 +1,18 @@
package cli
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/spf13/cobra"
"git.cloudinit.dev/coreci/orca/internal/config"
)
type configCtxKey struct{}
var (
version = "0.1.0-dev"
gitCommit = "unknown"
@@ -33,6 +38,13 @@ over feature richness.`,
return fmt.Errorf("set ORCA_HOME for --system: %w", err)
}
}
if configPath != "" {
cfg, err := config.Load(configPath)
if err != nil {
return fmt.Errorf("load config %s: %w", configPath, err)
}
cmd.SetContext(context.WithValue(cmd.Context(), configCtxKey{}, cfg))
}
return nil
},
}
@@ -40,11 +52,20 @@ over feature richness.`,
var (
jsonOutput bool
systemNamespace bool
configPath string
)
func init() {
rootCmd.PersistentFlags().BoolVar(&jsonOutput, "json", false, "output in JSON format")
rootCmd.PersistentFlags().BoolVar(&systemNamespace, "system", false, "use system-level namespace root (/root/.orca) instead of user-level (~/.orca)")
rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "path to config.hcl (overrides ~/.orca/config.hcl)")
}
func configFromCtx(ctx context.Context) *config.Config {
if v, ok := ctx.Value(configCtxKey{}).(*config.Config); ok {
return v
}
return nil
}
func Execute() error {
+47
View File
@@ -1,8 +1,11 @@
package cli
import (
"os"
"strings"
"testing"
"git.cloudinit.dev/coreci/orca/internal/config"
)
func TestVersionCommandExists(t *testing.T) {
@@ -65,3 +68,47 @@ func TestRootHelpMentionsKeyPillars(t *testing.T) {
}
}
}
func TestConfigFlagRegistered(t *testing.T) {
f := rootCmd.PersistentFlags().Lookup("config")
if f == nil {
t.Fatal("--config persistent flag not registered")
}
if f.DefValue != "" {
t.Errorf("--config default = %q, want empty", f.DefValue)
}
}
func TestConfigFlagLoadsFile(t *testing.T) {
dir := t.TempDir()
cfgPath := dir + "/config.hcl"
cfgContent := `db_path = "` + dir + `/test.db"
listen_addr = "127.0.0.1:9999"
ca_path = "` + dir + `/ca.crt"
server_cert_path = "` + dir + `/server.crt"
server_key_path = "` + dir + `/server.key"
node_capacity {
cpu = 4
memory_mb = 8192
}
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
old := configPath
configPath = cfgPath
defer func() { configPath = old }()
cfg, err := config.Load(cfgPath)
if err != nil {
t.Fatalf("load config: %v", err)
}
if cfg.ListenAddr != "127.0.0.1:9999" {
t.Errorf("listen_addr = %q, want 127.0.0.1:9999", cfg.ListenAddr)
}
if cfg.NodeCapacity == nil || cfg.NodeCapacity.CPU != 4 {
t.Errorf("node_capacity.cpu not parsed, got %+v", cfg.NodeCapacity)
}
}