Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions config_resolution.go → configresolve/resolve.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
package main
// Package configresolve owns the precedence-aware merge of duckgres
// configuration sources (CLI flags > env > YAML > built-in defaults). It
// produces a Resolved value that the all-in-one duckgres binary, and
// eventually cmd/duckgres-controlplane and cmd/duckgres-worker, all feed
// directly into server / controlplane / duckdbservice startup. The package
// is intentionally separate from configloader (which only owns YAML
// parsing) so configloader can stay light; configresolve is heavy by
// design and pulls in server, controlplane, and server/ducklake.
package configresolve

import (
"strconv"
"strings"
"time"

"github.com/posthog/duckgres/configloader"
"github.com/posthog/duckgres/controlplane"
"github.com/posthog/duckgres/server"
"github.com/posthog/duckgres/server/ducklake"
)

type configCLIInputs struct {
// CLIInputs carries the post-flag.Parse() values from the binary's main
// plus a Set map indicating which flags the user actually provided. The
// caller is the source of truth for "was this flag set", since flag.Visit
// only sees flags that landed in os.Args; defaults look identical to
// unset flags from the resolver's perspective without this signal.
type CLIInputs struct {
Set map[string]bool

Host string
Expand Down Expand Up @@ -71,7 +85,7 @@ type configCLIInputs struct {
QueryLog bool
}

type resolvedConfig struct {
type Resolved struct {
Server server.Config
ProcessMinWorkers int
ProcessMaxWorkers int
Expand Down Expand Up @@ -108,7 +122,7 @@ type resolvedConfig struct {
func intPtr(n int) *int { return &n }
func boolPtr(b bool) *bool { return &b }

func defaultServerConfig() server.Config {
func DefaultServerConfig() server.Config {
return server.Config{
Host: "0.0.0.0",
Port: 5432,
Expand Down Expand Up @@ -139,7 +153,11 @@ func defaultServerConfig() server.Config {
}
}

func resolveEffectiveConfig(fileCfg *FileConfig, cli configCLIInputs, getenv func(string) string, warn func(string)) resolvedConfig {
// ResolveEffective layers CLI inputs on top of env vars on top of YAML on
// top of built-in defaults to produce the runtime config every duckgres
// binary boots from. getenv and warn are pluggable so unit tests can run
// without touching os.Getenv or stderr; nil maps to no-op equivalents.
func ResolveEffective(fileCfg *configloader.FileConfig, cli CLIInputs, getenv func(string) string, warn func(string)) Resolved {
if getenv == nil {
getenv = func(string) string { return "" }
}
Expand All @@ -150,7 +168,7 @@ func resolveEffectiveConfig(fileCfg *FileConfig, cli configCLIInputs, getenv fun
cli.Set = map[string]bool{}
}

cfg := defaultServerConfig()
cfg := DefaultServerConfig()
defaultQueryLog := cfg.QueryLog
var workerQueueTimeout time.Duration
var workerIdleTimeout time.Duration
Expand Down Expand Up @@ -1054,7 +1072,7 @@ func resolveEffectiveConfig(fileCfg *FileConfig, cli configCLIInputs, getenv fun
}
}

return resolvedConfig{
return Resolved{
Server: cfg,
ProcessMinWorkers: processMinWorkers,
ProcessMaxWorkers: processMaxWorkers,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package main
package configresolve

import "testing"

func TestResolveEffectiveConfigDefaultsK8sWorkerServiceAccountToNeutralWorker(t *testing.T) {
resolved := resolveEffectiveConfig(nil, configCLIInputs{}, nil, nil)
func TestResolveEffectiveDefaultsK8sWorkerServiceAccountToNeutralWorker(t *testing.T) {
resolved := ResolveEffective(nil, CLIInputs{}, nil, nil)

if resolved.K8sWorkerServiceAccount != "duckgres-worker" {
t.Fatalf("expected default K8s worker service account duckgres-worker, got %q", resolved.K8sWorkerServiceAccount)
}
}

func TestResolveEffectiveConfigExposesDuckLakeDefaultSpecVersionForControlPlane(t *testing.T) {
resolved := resolveEffectiveConfig(nil, configCLIInputs{}, func(key string) string {
func TestResolveEffectiveExposesDuckLakeDefaultSpecVersionForControlPlane(t *testing.T) {
resolved := ResolveEffective(nil, CLIInputs{}, func(key string) string {
if key == "DUCKGRES_DUCKLAKE_DEFAULT_SPEC_VERSION" {
return "1.1"
}
Expand Down
9 changes: 6 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

"github.com/posthog/duckgres/configloader"
"github.com/posthog/duckgres/configresolve"
"github.com/posthog/duckgres/controlplane"
"github.com/posthog/duckgres/duckdbservice"
"github.com/posthog/duckgres/server"
Expand All @@ -25,8 +26,10 @@ import (
// cmd/duckgres-worker binaries parse the same shape.
type FileConfig = configloader.FileConfig

// Type aliases for the nested configloader types so the rest of main.go's
// resolveEffectiveConfig logic continues to compile unchanged.
// Type aliases for the nested configloader types so main.go's flag-binding
// code continues to refer to FileConfig / ProcessFileConfig / etc. without
// the configloader. prefix on every line. The actual resolver now lives in
// the configresolve package and takes *configloader.FileConfig directly.
type (
ProcessFileConfig = configloader.ProcessFileConfig
K8sFileConfig = configloader.K8sFileConfig
Expand Down Expand Up @@ -285,7 +288,7 @@ func main() {
os.Exit(0)
}

resolved := resolveEffectiveConfig(fileCfg, configCLIInputs{
resolved := configresolve.ResolveEffective(fileCfg, configresolve.CLIInputs{
Set: cliSet,
Host: *host,
Port: *port,
Expand Down
Loading
Loading