Files
query-orchestration/internal/serviceconfig/common.go
T
Michael McGuinness 71f9802e1a Merged in feature/splitqueryrunning (pull request #57)
Split Query Running + Debugging Full Flow

* completedquerysyncrunner

* spliitinglogic

* synccomplete

* informdependents

* only push same collector

* deps

* livetesting

* foundissue

* some issues resolved

* activeupdate

* collectorupdatefixes

* fix dbquesries

* tests

* tests

* pollingdebug
2025-02-11 15:22:59 +00:00

219 lines
5.3 KiB
Go

package serviceconfig
import (
"errors"
"fmt"
"log/slog"
"queryorchestration/internal/serviceconfig/aws"
"queryorchestration/internal/serviceconfig/database"
"queryorchestration/internal/serviceconfig/logger"
"queryorchestration/internal/serviceconfig/observability"
"queryorchestration/internal/serviceconfig/queue"
"reflect"
"strings"
"github.com/caarlos0/env/v11"
"github.com/joho/godotenv"
)
// Common Types
// ------------
// Logging
// BaseConfig provides common configuration fields and functionality
// that can be embedded in service-specific configs.
type BaseConfig struct {
logger.LogConfig
observability.ObsConfig
database.DBConfig
aws.AWSConfig
queue.QueueConfig
// miscellaneous fields uncategorized
// PWD will replace the BASE_PATH env var
Pwd string `env:"PWD,required,notEmpty"`
// BASE_PATH will override the PWD env var if present
BasePath string `env:"BASE_PATH"`
}
// Interfaces
// ----------
// ConfigProvider defines the basic requirements for all configuration types.
// Implementations must provide logging capabilities and configuration display.
type ConfigProvider interface {
database.ConfigProvider
observability.ConfigProvider
logger.ConfigProvider
aws.ConfigProvider
queue.ConfigProvider
GetBasePath() string
SetBasePath(string)
SetDBConfig(*database.DBConfig)
SetAWSConfig(*aws.AWSConfig)
}
// Configuration Methods
// -------------------
// InitializeConfig sets up a configuration instance by loading environment
// variables and initializing the logger. It expects a ConfigProvider implementation.
// Returns an error if initialization fails.
func getBaseConfig(cfg ConfigProvider) *BaseConfig {
if cfg == nil {
return nil
}
t := reflect.ValueOf(cfg).Type()
if t == reflect.TypeOf(&BaseConfig{}) {
v := reflect.ValueOf(cfg).Interface().(*BaseConfig)
return v
} else if t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct {
return nil
}
val := reflect.ValueOf(cfg).Elem()
var baseConfig *BaseConfig
for i := 0; i < val.NumField() && baseConfig == nil; i++ {
b := val.Field(i)
intType := reflect.TypeOf((*ConfigProvider)(nil)).Elem()
if b.Type().Implements(intType) {
baseConfig = getBaseConfig(b.Interface().(ConfigProvider))
} else if reflect.PointerTo(b.Type()).Implements(intType) {
baseConfig = getBaseConfig(b.Addr().Interface().(ConfigProvider))
}
}
return baseConfig
}
func InitializeConfig(cfg ConfigProvider) error {
baseConfig := getBaseConfig(cfg)
if baseConfig == nil {
return errors.New("no BaseConfig found in the provided config struct")
}
baseConfig.SetDefaultLogger()
if err := godotenv.Load(); err != nil {
slog.Warn("No .env file found or error loading it", "error", err)
}
if err := env.Parse(cfg); err != nil {
if envErr, ok := err.(env.AggregateError); ok {
slog.Error("Missing required environment variables:")
for _, e := range envErr.Errors {
slog.Error(e.Error())
}
} else {
slog.Error("Error parsing environment variables", "error", err)
}
return err
}
baseConfig.SetDefaultLogger()
return nil
}
func (b *BaseConfig) GetBasePath() string {
if b.BasePath != "" {
return b.BasePath
}
return b.Pwd
}
func (b *BaseConfig) SetBasePath(p string) {
b.BasePath = p
}
func (b *BaseConfig) SetDBConfig(cfg *database.DBConfig) {
b.DBConfig = *cfg
}
func (b *BaseConfig) SetAWSConfig(cfg *aws.AWSConfig) {
b.AWSConfig = *cfg
}
func (b *BaseConfig) PrintConfig(prefixSecret string) {
b.printConfigRecursive(reflect.ValueOf(b), "", prefixSecret, make(map[uintptr]bool))
}
func (b *BaseConfig) printConfigRecursive(val reflect.Value, prefix string, prefixSecret string, visited map[uintptr]bool) {
// Handle pointer dereference
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
// Prevent infinite recursion using the memory address
// Recommended way to get the uintptr
addr := uintptr(val.Addr().UnsafePointer())
if visited[addr] {
return
}
visited[addr] = true
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType := typ.Field(i)
// Skip unexported fields
if !fieldType.IsExported() {
continue
}
fieldName := fieldType.Name
fullPath := prefix + fieldName
// Check for env tag
envTag := fieldType.Tag.Get("env")
if envTag == "" {
// If this is a struct, we still need to recurse into it
// as it might contain fields with env tags
if field.Kind() == reflect.Struct {
b.printConfigRecursive(field, fullPath+".", prefixSecret, visited)
}
continue
}
// Handle different kinds of fields
switch field.Kind() {
case reflect.Struct:
// If it's a struct AND has an env tag, print it and recurse
var valueStr string
if field.CanInterface() {
valueStr = fmt.Sprintf("%+v", field.Interface())
}
b.Logger.Info("Struct Config value",
"key", fullPath,
"value", valueStr)
b.printConfigRecursive(field, fullPath+".", prefixSecret, visited)
default:
var valueStr string
if field.Kind() == reflect.String {
valueStr = field.String()
} else {
valueStr = fmt.Sprintf("%v", field.Interface())
}
// Mask sensitive values
if strings.Contains(strings.ToLower(fieldName), strings.ToLower(prefixSecret)) {
if len(valueStr) > 3 {
valueStr = valueStr[:3] + "..."
}
}
b.Logger.Info("Config value",
"key", fullPath,
"value", valueStr)
}
}
}