github.com/rudderlabs/rudder-go-kit@v0.30.0/config/config.go (about)

     1  // Package config uses the exact same precedence order as Viper, where
     2  // each item takes precedence over the item below it:
     3  //
     4  //   - explicit call to Set (case insensitive)
     5  //   - flag (case insensitive)
     6  //   - env (case sensitive - see notes below)
     7  //   - config (case insensitive)
     8  //   - key/value store (case insensitive)
     9  //   - default (case insensitive)
    10  //
    11  // Environment variable resolution is performed based on the following rules:
    12  //   - If the key contains only uppercase characters, numbers and underscores, the environment variable is looked up in its entirety, e.g. SOME_VARIABLE -> SOME_VARIABLE
    13  //   - In all other cases, the environment variable is transformed before being looked up as following:
    14  //     1. camelCase is converted to snake_case, e.g. someVariable -> some_variable
    15  //     2. dots (.) are replaced with underscores (_), e.g. some.variable -> some_variable
    16  //     3. the resulting string is uppercased and prefixed with ${PREFIX}_ (default RSERVER_), e.g. some_variable -> RSERVER_SOME_VARIABLE
    17  //
    18  // Order of keys:
    19  //
    20  //		When registering a variable with multiple keys, the order of the keys is important as it determines the
    21  //		hierarchical order of the keys.
    22  //		The first key is the most important one, and the last key is the least important one.
    23  //		Example:
    24  //		config.RegisterDurationConfigVariable(90, &cmdTimeout, true, time.Second,
    25  //			"JobsDB.Router.CommandRequestTimeout",
    26  //			"JobsDB.CommandRequestTimeout",
    27  //		)
    28  //
    29  //		In the above example "JobsDB.Router.CommandRequestTimeout" is checked first. If it doesn't exist then
    30  //	    JobsDB.CommandRequestTimeout is checked.
    31  //
    32  //	    WARNING: for this reason, registering with the same keys but in a different order is going to return two
    33  //	    different variables.
    34  package config
    35  
    36  import (
    37  	"fmt"
    38  	"regexp"
    39  	"strconv"
    40  	"strings"
    41  	"sync"
    42  	"time"
    43  
    44  	"github.com/spf13/viper"
    45  )
    46  
    47  const DefaultEnvPrefix = "RSERVER"
    48  
    49  // regular expression matching lowercase letter followed by an uppercase letter
    50  var camelCaseMatch = regexp.MustCompile("([a-z0-9])([A-Z])")
    51  
    52  // regular expression matching uppercase letters contained in environment variable names
    53  var upperCaseMatch = regexp.MustCompile("^[A-Z0-9_]+$")
    54  
    55  // Default is the singleton config instance
    56  var Default *Config
    57  
    58  func init() {
    59  	Default = New()
    60  }
    61  
    62  // Reset resets the default, singleton config instance.
    63  // Shall only be used by tests, until we move to a proper DI framework
    64  func Reset() {
    65  	Default = New()
    66  }
    67  
    68  type Opt func(*Config)
    69  
    70  // WithEnvPrefix sets the environment variable prefix (default: RSERVER)
    71  func WithEnvPrefix(prefix string) Opt {
    72  	return func(c *Config) {
    73  		c.envPrefix = prefix
    74  	}
    75  }
    76  
    77  // New creates a new config instance
    78  func New(opts ...Opt) *Config {
    79  	c := &Config{
    80  		envPrefix:             DefaultEnvPrefix,
    81  		reloadableVars:        make(map[string]any),
    82  		reloadableVarsMisuses: make(map[string]string),
    83  	}
    84  	for _, opt := range opts {
    85  		opt(c)
    86  	}
    87  	c.load()
    88  	return c
    89  }
    90  
    91  // Config is the entry point for accessing configuration
    92  type Config struct {
    93  	vLock                   sync.RWMutex // protects reading and writing to the config (viper is not thread-safe)
    94  	v                       *viper.Viper
    95  	hotReloadableConfigLock sync.RWMutex // protects map holding hot reloadable config keys
    96  	hotReloadableConfig     map[string][]*configValue
    97  	envsLock                sync.RWMutex // protects the envs map below
    98  	envs                    map[string]string
    99  	envPrefix               string // prefix for environment variables
   100  	reloadableVars          map[string]any
   101  	reloadableVarsMisuses   map[string]string
   102  	reloadableVarsLock      sync.RWMutex // used to protect both the reloadableVars and reloadableVarsMisuses maps
   103  	configPath              string
   104  	configPathErr           error
   105  	godotEnvErr             error
   106  }
   107  
   108  // GetBool gets bool value from config
   109  func GetBool(key string, defaultValue bool) (value bool) {
   110  	return Default.GetBool(key, defaultValue)
   111  }
   112  
   113  // GetBool gets bool value from config
   114  func (c *Config) GetBool(key string, defaultValue bool) (value bool) {
   115  	c.vLock.RLock()
   116  	defer c.vLock.RUnlock()
   117  	if !c.isSetInternal(key) {
   118  		return defaultValue
   119  	}
   120  	return c.v.GetBool(key)
   121  }
   122  
   123  // GetInt gets int value from config
   124  func GetInt(key string, defaultValue int) (value int) {
   125  	return Default.GetInt(key, defaultValue)
   126  }
   127  
   128  // GetInt gets int value from config
   129  func (c *Config) GetInt(key string, defaultValue int) (value int) {
   130  	c.vLock.RLock()
   131  	defer c.vLock.RUnlock()
   132  	if !c.isSetInternal(key) {
   133  		return defaultValue
   134  	}
   135  	return c.v.GetInt(key)
   136  }
   137  
   138  // GetStringMap gets string map value from config
   139  func GetStringMap(key string, defaultValue map[string]interface{}) (value map[string]interface{}) {
   140  	return Default.GetStringMap(key, defaultValue)
   141  }
   142  
   143  // GetStringMap gets string map value from config
   144  func (c *Config) GetStringMap(key string, defaultValue map[string]interface{}) (value map[string]interface{}) {
   145  	c.vLock.RLock()
   146  	defer c.vLock.RUnlock()
   147  	if !c.isSetInternal(key) {
   148  		return defaultValue
   149  	}
   150  	return c.v.GetStringMap(key)
   151  }
   152  
   153  // MustGetInt gets int value from config or panics if the config doesn't exist
   154  func MustGetInt(key string) (value int) {
   155  	return Default.MustGetInt(key)
   156  }
   157  
   158  // MustGetInt gets int value from config or panics if the config doesn't exist
   159  func (c *Config) MustGetInt(key string) (value int) {
   160  	c.vLock.RLock()
   161  	defer c.vLock.RUnlock()
   162  	if !c.isSetInternal(key) {
   163  		panic(fmt.Errorf("config key %s not found", key))
   164  	}
   165  	return c.v.GetInt(key)
   166  }
   167  
   168  // GetInt64 gets int64 value from config
   169  func GetInt64(key string, defaultValue int64) (value int64) {
   170  	return Default.GetInt64(key, defaultValue)
   171  }
   172  
   173  // GetInt64 gets int64 value from config
   174  func (c *Config) GetInt64(key string, defaultValue int64) (value int64) {
   175  	c.vLock.RLock()
   176  	defer c.vLock.RUnlock()
   177  	if !c.isSetInternal(key) {
   178  		return defaultValue
   179  	}
   180  	return c.v.GetInt64(key)
   181  }
   182  
   183  // GetFloat64 gets float64 value from config
   184  func GetFloat64(key string, defaultValue float64) (value float64) {
   185  	return Default.GetFloat64(key, defaultValue)
   186  }
   187  
   188  // GetFloat64 gets float64 value from config
   189  func (c *Config) GetFloat64(key string, defaultValue float64) (value float64) {
   190  	c.vLock.RLock()
   191  	defer c.vLock.RUnlock()
   192  	if !c.isSetInternal(key) {
   193  		return defaultValue
   194  	}
   195  	return c.v.GetFloat64(key)
   196  }
   197  
   198  // GetString gets string value from config
   199  func GetString(key, defaultValue string) (value string) {
   200  	return Default.GetString(key, defaultValue)
   201  }
   202  
   203  // GetString gets string value from config
   204  func (c *Config) GetString(key, defaultValue string) (value string) {
   205  	c.vLock.RLock()
   206  	defer c.vLock.RUnlock()
   207  	if !c.isSetInternal(key) {
   208  		return defaultValue
   209  	}
   210  	return c.v.GetString(key)
   211  }
   212  
   213  // MustGetString gets string value from config or panics if the config doesn't exist
   214  func MustGetString(key string) (value string) {
   215  	return Default.MustGetString(key)
   216  }
   217  
   218  // MustGetString gets string value from config or panics if the config doesn't exist
   219  func (c *Config) MustGetString(key string) (value string) {
   220  	c.vLock.RLock()
   221  	defer c.vLock.RUnlock()
   222  	if !c.isSetInternal(key) {
   223  		panic(fmt.Errorf("config key %s not found", key))
   224  	}
   225  	return c.v.GetString(key)
   226  }
   227  
   228  // GetStringSlice gets string slice value from config
   229  func GetStringSlice(key string, defaultValue []string) (value []string) {
   230  	return Default.GetStringSlice(key, defaultValue)
   231  }
   232  
   233  // GetStringSlice gets string slice value from config
   234  func (c *Config) GetStringSlice(key string, defaultValue []string) (value []string) {
   235  	c.vLock.RLock()
   236  	defer c.vLock.RUnlock()
   237  	if !c.isSetInternal(key) {
   238  		return defaultValue
   239  	}
   240  	return c.v.GetStringSlice(key)
   241  }
   242  
   243  // GetDuration gets duration value from config
   244  func GetDuration(key string, defaultValueInTimescaleUnits int64, timeScale time.Duration) (value time.Duration) {
   245  	return Default.GetDuration(key, defaultValueInTimescaleUnits, timeScale)
   246  }
   247  
   248  // GetDuration gets duration value from config
   249  func (c *Config) GetDuration(key string, defaultValueInTimescaleUnits int64, timeScale time.Duration) (value time.Duration) {
   250  	c.vLock.RLock()
   251  	defer c.vLock.RUnlock()
   252  	if !c.isSetInternal(key) {
   253  		return time.Duration(defaultValueInTimescaleUnits) * timeScale
   254  	} else {
   255  		v := c.v.GetString(key)
   256  		parseDuration, err := time.ParseDuration(v)
   257  		if err == nil {
   258  			return parseDuration
   259  		} else {
   260  			_, err = strconv.ParseFloat(v, 64)
   261  			if err == nil {
   262  				return c.v.GetDuration(key) * timeScale
   263  			} else {
   264  				return time.Duration(defaultValueInTimescaleUnits) * timeScale
   265  			}
   266  		}
   267  	}
   268  }
   269  
   270  // IsSet checks if config is set for a key
   271  func IsSet(key string) bool {
   272  	return Default.IsSet(key)
   273  }
   274  
   275  // IsSet checks if config is set for a key
   276  func (c *Config) IsSet(key string) bool {
   277  	c.vLock.RLock()
   278  	defer c.vLock.RUnlock()
   279  	return c.isSetInternal(key)
   280  }
   281  
   282  // isSetInternal checks if config is set for a key. Caller needs to hold a read lock on vLock.
   283  func (c *Config) isSetInternal(key string) bool {
   284  	c.bindEnv(key)
   285  	return c.v.IsSet(key)
   286  }
   287  
   288  // Override Config by application or command line
   289  
   290  // Set override existing config
   291  func Set(key string, value interface{}) {
   292  	Default.Set(key, value)
   293  }
   294  
   295  // Set override existing config
   296  func (c *Config) Set(key string, value interface{}) {
   297  	c.vLock.Lock()
   298  	c.v.Set(key, value)
   299  	c.vLock.Unlock()
   300  	c.onConfigChange()
   301  }
   302  
   303  func getReloadableMapKeys[T configTypes](v T, orderedKeys ...string) (string, string) {
   304  	k := fmt.Sprintf("%T:%s", v, strings.Join(orderedKeys, ","))
   305  	return k, fmt.Sprintf("%s:%v", k, v)
   306  }
   307  
   308  func getOrCreatePointer[T configTypes](
   309  	m map[string]any, dvs map[string]string, // this function MUST receive maps that are already initialized
   310  	lock *sync.RWMutex, defaultValue T, orderedKeys ...string,
   311  ) (ptr *Reloadable[T], exists bool) {
   312  	key, dvKey := getReloadableMapKeys(defaultValue, orderedKeys...)
   313  
   314  	lock.Lock()
   315  	defer lock.Unlock()
   316  
   317  	defer func() {
   318  		if _, ok := dvs[key]; !ok {
   319  			dvs[key] = dvKey
   320  		}
   321  		if dvs[key] != dvKey {
   322  			panic(fmt.Errorf(
   323  				"Detected misuse of config variable registered with different default values %+v - %+v\n",
   324  				dvs[key], dvKey,
   325  			))
   326  		}
   327  	}()
   328  
   329  	if p, ok := m[key]; ok {
   330  		return p.(*Reloadable[T]), true
   331  	}
   332  
   333  	p := &Reloadable[T]{}
   334  	m[key] = p
   335  	return p, false
   336  }
   337  
   338  // bindEnv handles rudder server's unique snake case replacement by registering
   339  // the environment variables to viper, that would otherwise be ignored.
   340  // Viper uppercases keys before sending them to its EnvKeyReplacer, thus
   341  // the replacer cannot detect camelCase keys.
   342  func (c *Config) bindEnv(key string) {
   343  	envVar := key
   344  	if !upperCaseMatch.MatchString(key) {
   345  		envVar = ConfigKeyToEnv(c.envPrefix, key)
   346  	}
   347  	// bind once
   348  	c.envsLock.RLock()
   349  	if _, ok := c.envs[key]; !ok {
   350  		c.envsLock.RUnlock()
   351  		c.envsLock.Lock() // don't really care about race here, setting the same value
   352  		c.envs[strings.ToUpper(key)] = envVar
   353  		c.envsLock.Unlock()
   354  	} else {
   355  		c.envsLock.RUnlock()
   356  	}
   357  }
   358  
   359  type envReplacer struct {
   360  	c *Config
   361  }
   362  
   363  func (r *envReplacer) Replace(s string) string {
   364  	r.c.envsLock.RLock()
   365  	defer r.c.envsLock.RUnlock()
   366  	if v, ok := r.c.envs[s]; ok {
   367  		return v
   368  	}
   369  	return s // bound environment variables
   370  }
   371  
   372  // Fallback environment variables supported (historically) by rudder-server
   373  func bindLegacyEnv(v *viper.Viper) {
   374  	_ = v.BindEnv("DB.host", "JOBS_DB_HOST")
   375  	_ = v.BindEnv("DB.user", "JOBS_DB_USER")
   376  	_ = v.BindEnv("DB.name", "JOBS_DB_DB_NAME")
   377  	_ = v.BindEnv("DB.port", "JOBS_DB_PORT")
   378  	_ = v.BindEnv("DB.password", "JOBS_DB_PASSWORD")
   379  	_ = v.BindEnv("DB.sslMode", "JOBS_DB_SSL_MODE")
   380  	_ = v.BindEnv("SharedDB.dsn", "SHARED_DB_DSN")
   381  }