github.com/rudderlabs/rudder-go-kit@v0.30.0/config/load.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "reflect" 6 "slices" 7 "time" 8 9 "github.com/fsnotify/fsnotify" 10 "github.com/joho/godotenv" 11 "github.com/spf13/viper" 12 ) 13 14 func (c *Config) load() { 15 c.hotReloadableConfig = make(map[string][]*configValue) 16 c.envs = make(map[string]string) 17 18 c.godotEnvErr = godotenv.Load() 19 20 configPath := getEnv("CONFIG_PATH", "./config/config.yaml") 21 22 v := viper.NewWithOptions(viper.EnvKeyReplacer(&envReplacer{c: c})) 23 v.AutomaticEnv() 24 bindLegacyEnv(v) 25 26 v.SetConfigFile(configPath) 27 28 // Find and read the config file 29 // If config.yaml is not found or error with parsing. Use the default config values instead 30 c.configPathErr = v.ReadInConfig() 31 c.configPath = v.ConfigFileUsed() 32 33 v.OnConfigChange(func(e fsnotify.Event) { 34 c.onConfigChange() 35 }) 36 v.WatchConfig() 37 38 c.v = v 39 } 40 41 // ConfigFileUsed returns the file used to load the config. 42 // If we failed to load the config file, it also returns an error. 43 func (c *Config) ConfigFileUsed() (string, error) { 44 return c.configPath, c.configPathErr 45 } 46 47 // DotEnvLoaded returns an error if there was an error loading the .env file. 48 // It returns nil otherwise. 49 func (c *Config) DotEnvLoaded() error { 50 return c.godotEnvErr 51 } 52 53 func (c *Config) onConfigChange() { 54 defer func() { 55 if r := recover(); r != nil { 56 err := fmt.Errorf("cannot update Config Variables: %v", r) 57 fmt.Println(err) 58 } 59 }() 60 c.hotReloadableConfigLock.RLock() 61 defer c.hotReloadableConfigLock.RUnlock() 62 c.checkAndHotReloadConfig(c.hotReloadableConfig) 63 } 64 65 func (c *Config) checkAndHotReloadConfig(configMap map[string][]*configValue) { 66 for key, configValArr := range configMap { 67 for _, configVal := range configValArr { 68 value := configVal.value 69 switch value := value.(type) { 70 case *int, *Reloadable[int]: 71 var _value int 72 var isSet bool 73 for _, key := range configVal.keys { 74 if c.IsSet(key) { 75 isSet = true 76 _value = c.GetInt(key, configVal.defaultValue.(int)) 77 break 78 } 79 } 80 if !isSet { 81 _value = configVal.defaultValue.(int) 82 } 83 _value = _value * configVal.multiplier.(int) 84 swapHotReloadableConfig(key, "%d", configVal, value, _value, compare[int]()) 85 case *int64, *Reloadable[int64]: 86 var _value int64 87 var isSet bool 88 for _, key := range configVal.keys { 89 if c.IsSet(key) { 90 isSet = true 91 _value = c.GetInt64(key, configVal.defaultValue.(int64)) 92 break 93 } 94 } 95 if !isSet { 96 _value = configVal.defaultValue.(int64) 97 } 98 _value = _value * configVal.multiplier.(int64) 99 swapHotReloadableConfig(key, "%d", configVal, value, _value, compare[int64]()) 100 case *string, *Reloadable[string]: 101 var _value string 102 var isSet bool 103 for _, key := range configVal.keys { 104 if c.IsSet(key) { 105 isSet = true 106 _value = c.GetString(key, configVal.defaultValue.(string)) 107 break 108 } 109 } 110 if !isSet { 111 _value = configVal.defaultValue.(string) 112 } 113 swapHotReloadableConfig(key, "%q", configVal, value, _value, compare[string]()) 114 case *time.Duration, *Reloadable[time.Duration]: 115 var _value time.Duration 116 var isSet bool 117 for _, key := range configVal.keys { 118 if c.IsSet(key) { 119 isSet = true 120 _value = c.GetDuration(key, configVal.defaultValue.(int64), configVal.multiplier.(time.Duration)) 121 break 122 } 123 } 124 if !isSet { 125 _value = time.Duration(configVal.defaultValue.(int64)) * configVal.multiplier.(time.Duration) 126 } 127 swapHotReloadableConfig(key, "%d", configVal, value, _value, compare[time.Duration]()) 128 case *bool, *Reloadable[bool]: 129 var _value bool 130 var isSet bool 131 for _, key := range configVal.keys { 132 if c.IsSet(key) { 133 isSet = true 134 _value = c.GetBool(key, configVal.defaultValue.(bool)) 135 break 136 } 137 } 138 if !isSet { 139 _value = configVal.defaultValue.(bool) 140 } 141 swapHotReloadableConfig(key, "%v", configVal, value, _value, compare[bool]()) 142 case *float64, *Reloadable[float64]: 143 var _value float64 144 var isSet bool 145 for _, key := range configVal.keys { 146 if c.IsSet(key) { 147 isSet = true 148 _value = c.GetFloat64(key, configVal.defaultValue.(float64)) 149 break 150 } 151 } 152 if !isSet { 153 _value = configVal.defaultValue.(float64) 154 } 155 _value = _value * configVal.multiplier.(float64) 156 swapHotReloadableConfig(key, "%v", configVal, value, _value, compare[float64]()) 157 case *[]string, *Reloadable[[]string]: 158 var _value []string 159 var isSet bool 160 for _, key := range configVal.keys { 161 if c.IsSet(key) { 162 isSet = true 163 _value = c.GetStringSlice(key, configVal.defaultValue.([]string)) 164 break 165 } 166 } 167 if !isSet { 168 _value = configVal.defaultValue.([]string) 169 } 170 swapHotReloadableConfig(key, "%v", configVal, value, _value, func(a, b []string) bool { 171 return slices.Compare(a, b) == 0 172 }) 173 case *map[string]interface{}, *Reloadable[map[string]interface{}]: 174 var _value map[string]interface{} 175 var isSet bool 176 for _, key := range configVal.keys { 177 if c.IsSet(key) { 178 isSet = true 179 _value = c.GetStringMap(key, configVal.defaultValue.(map[string]interface{})) 180 break 181 } 182 } 183 if !isSet { 184 _value = configVal.defaultValue.(map[string]interface{}) 185 } 186 swapHotReloadableConfig(key, "%v", configVal, value, _value, func(a, b map[string]interface{}) bool { 187 return mapDeepEqual(a, b) 188 }) 189 } 190 } 191 } 192 } 193 194 func swapHotReloadableConfig[T configTypes]( 195 key, placeholder string, configVal *configValue, ptr any, newValue T, 196 compare func(T, T) bool, 197 ) { 198 if value, ok := ptr.(*T); ok { 199 if !compare(*value, newValue) { 200 fmt.Printf("The value of key %q & variable %p changed from "+placeholder+" to "+placeholder+"\n", 201 key, configVal, *value, newValue, 202 ) 203 *value = newValue 204 } 205 return 206 } 207 reloadableValue, _ := configVal.value.(*Reloadable[T]) 208 if oldValue, swapped := reloadableValue.swapIfNotEqual(newValue, compare); swapped { 209 fmt.Printf("The value of key %q & variable %p changed from "+placeholder+" to "+placeholder+"\n", 210 key, configVal, oldValue, newValue, 211 ) 212 } 213 } 214 215 type configValue struct { 216 value interface{} 217 multiplier interface{} 218 defaultValue interface{} 219 keys []string 220 } 221 222 func newConfigValue(value, multiplier, defaultValue interface{}, keys []string) *configValue { 223 return &configValue{ 224 value: value, 225 multiplier: multiplier, 226 defaultValue: defaultValue, 227 keys: keys, 228 } 229 } 230 231 func mapDeepEqual[K comparable, V any](a, b map[K]V) bool { 232 if len(a) != len(b) { 233 return false 234 } 235 for k, v := range a { 236 if w, ok := b[k]; !ok || !reflect.DeepEqual(v, w) { 237 return false 238 } 239 } 240 return true 241 }