Skip to content
Closed
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
113 changes: 108 additions & 5 deletions cmd/cachewd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net"
"net/http"
"os"
"reflect"
"slices"
"strings"
"time"
Expand Down Expand Up @@ -46,8 +47,30 @@ var cli struct {
}

func main() {
kctx, providersConfig := parseConfig()

ctx := context.Background()
logger, ctx := logging.Configure(ctx, cli.LoggingConfig)

startServer(ctx, logger, kctx, providersConfig)
}

func parseConfig() (*kong.Context, *hcl.AST) {
// 1. Get defaults
defaults := struct{ GlobalConfig }{}
_, err := kong.New(&defaults, kong.Exit(func(int) {}))
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting defaults: %v\n", err)
os.Exit(1)
}

// 2. Parse CLI/env
kctx := kong.Parse(&cli, kong.DefaultEnvars("CACHEW"))

// 3. Save CLI/env values that differ from defaults (these take precedence)
saved := saveNonDefaultValues(&cli.GlobalConfig, &defaults.GlobalConfig)

// 4. Parse and unmarshal HCL (this overwrites cli.GlobalConfig)
ast, err := hcl.Parse(cli.Config)
kctx.FatalIfErrorf(err)

Expand All @@ -56,9 +79,13 @@ func main() {
err = hcl.UnmarshalAST(globalConfig, &cli.GlobalConfig)
kctx.FatalIfErrorf(err)

ctx := context.Background()
logger, ctx := logging.Configure(ctx, cli.LoggingConfig)
// 5. Restore CLI/env values (precedence: defaults < HCL < env < CLI)
restoreValues(&cli.GlobalConfig, saved)

return kctx, providersConfig
}

func startServer(ctx context.Context, logger *slog.Logger, kctx *kong.Context, providersConfig *hcl.AST) {
scheduler := jobscheduler.New(ctx, cli.SchedulerConfig)

cr := cache.NewRegistry()
Expand Down Expand Up @@ -107,11 +134,11 @@ func main() {
_, _ = w.Write([]byte("OK")) //nolint:errcheck
})

err = config.Load(ctx, cr, sr, providersConfig, mux, parseEnvars())
err := config.Load(ctx, cr, sr, providersConfig, mux, parseEnvars())
kctx.FatalIfErrorf(err)

metricsClient, err := metrics.New(ctx, cli.MetricsConfig)
kctx.FatalIfErrorf(err, "failed to create metrics client")
metricsClient, metricsErr := metrics.New(ctx, cli.MetricsConfig)
kctx.FatalIfErrorf(metricsErr, "failed to create metrics client")
defer func() {
if err := metricsClient.Close(); err != nil {
logger.ErrorContext(ctx, "failed to close metrics client", "error", err)
Expand Down Expand Up @@ -160,3 +187,79 @@ func parseEnvars() map[string]string {
}
return envars
}

// buildFieldPath constructs a dot-separated field path.
func buildFieldPath(path, fieldName string) string {
if path != "" {
return path + "." + fieldName
}
return fieldName
}

// saveNonDefaultValues recursively saves field values that differ from defaults.
// Returns a map of field paths to their values.
func saveNonDefaultValues(target, defaults *GlobalConfig) map[string]any {
saved := make(map[string]any)
saveFieldValues(reflect.ValueOf(target).Elem(), reflect.ValueOf(defaults).Elem(), "", saved)
return saved
}

func saveFieldValues(targetVal, defaultsVal reflect.Value, path string, saved map[string]any) {
targetType := targetVal.Type()

for i := range targetVal.NumField() {
field := targetType.Field(i)
targetField := targetVal.Field(i)
defaultField := defaultsVal.Field(i)

// Skip unexported fields
if !targetField.CanSet() {
continue
}

fieldPath := buildFieldPath(path, field.Name)

// If the field is a struct, recurse into it
if targetField.Kind() == reflect.Struct {
saveFieldValues(targetField, defaultField, fieldPath, saved)
continue
}

// If the field differs from default, save it
if !reflect.DeepEqual(targetField.Interface(), defaultField.Interface()) {
saved[fieldPath] = targetField.Interface()
}
}
}

// restoreValues recursively restores saved values back into the target struct.
func restoreValues(target *GlobalConfig, saved map[string]any) {
restoreFieldValues(reflect.ValueOf(target).Elem(), "", saved)
}

func restoreFieldValues(targetVal reflect.Value, path string, saved map[string]any) {
targetType := targetVal.Type()

for i := range targetVal.NumField() {
field := targetType.Field(i)
targetField := targetVal.Field(i)

// Skip unexported fields
if !targetField.CanSet() {
continue
}

fieldPath := buildFieldPath(path, field.Name)

// If the field is a struct, recurse into it
if targetField.Kind() == reflect.Struct {
restoreFieldValues(targetField, fieldPath, saved)
continue
}

// If we have a saved value for this field, restore it
if savedValue, ok := saved[fieldPath]; ok {
targetField.Set(reflect.ValueOf(savedValue))
}
}
}