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  }