github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/config/config.go (about) 1 /* 2 * Copyright (C) 2019 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package config 19 20 import ( 21 "math/big" 22 "os" 23 "strings" 24 "sync" 25 "time" 26 27 "github.com/BurntSushi/toml" 28 "github.com/mysteriumnetwork/node/eventbus" 29 "github.com/mysteriumnetwork/node/metadata" 30 "github.com/mysteriumnetwork/node/utils/jsonutil" 31 "github.com/pkg/errors" 32 "github.com/rs/zerolog/log" 33 "github.com/spf13/cast" 34 "github.com/urfave/cli/v2" 35 ) 36 37 // Config stores application configuration in 3 separate maps (listed from the lowest priority to the highest): 38 // 39 // • Default values 40 // 41 // • User configuration (config.toml) 42 // 43 // • CLI flags 44 type Config struct { 45 userConfigLocation string 46 defaults map[string]interface{} 47 user map[string]interface{} 48 cli map[string]interface{} 49 eventBus eventbus.EventBus 50 mu sync.RWMutex 51 } 52 53 // Current global configuration instance. 54 var Current = NewConfig() 55 56 // NewConfig creates a new configuration instance. 57 func NewConfig() *Config { 58 return &Config{ 59 userConfigLocation: "", 60 defaults: make(map[string]interface{}), 61 user: make(map[string]interface{}), 62 cli: make(map[string]interface{}), 63 } 64 } 65 66 func (cfg *Config) userConfigLoaded() bool { 67 cfg.mu.RLock() 68 defer cfg.mu.RUnlock() 69 return cfg.userConfigLocation != "" 70 } 71 72 // EnableEventPublishing enables config event publishing to the event bus. 73 func (cfg *Config) EnableEventPublishing(eb eventbus.EventBus) { 74 cfg.mu.Lock() 75 defer cfg.mu.Unlock() 76 cfg.eventBus = eb 77 } 78 79 // LoadUserConfig loads and remembers user config location. 80 func (cfg *Config) LoadUserConfig(location string) error { 81 log.Debug().Msg("Loading user configuration: " + location) 82 cfg.mu.Lock() 83 defer cfg.mu.Unlock() 84 cfg.userConfigLocation = location 85 _, err := toml.DecodeFile(cfg.userConfigLocation, &cfg.user) 86 if err != nil { 87 return errors.Wrap(err, "failed to decode configuration file") 88 } 89 cfgJson, err := jsonutil.ToJson(cfg.user) 90 if err != nil { 91 return err 92 } 93 log.Info().Msg("User configuration loaded: \n" + cfgJson) 94 return nil 95 } 96 97 // SaveUserConfig saves user configuration to the file from which it was loaded. 98 func (cfg *Config) SaveUserConfig() error { 99 log.Info().Msg("Saving user configuration") 100 cfg.mu.RLock() 101 defer cfg.mu.RUnlock() 102 if !cfg.userConfigLoaded() { 103 return errors.New("user configuration cannot be saved, because it must be loaded first") 104 } 105 var out strings.Builder 106 err := toml.NewEncoder(&out).Encode(cfg.user) 107 if err != nil { 108 return errors.Wrap(err, "failed to write configuration as toml") 109 } 110 err = os.WriteFile(cfg.userConfigLocation, []byte(out.String()), 0700) 111 if err != nil { 112 return errors.Wrap(err, "failed to write configuration to file") 113 } 114 cfgJson, err := jsonutil.ToJson(cfg.user) 115 if err != nil { 116 return err 117 } 118 log.Info().Msg("User configuration written: \n" + cfgJson) 119 return nil 120 } 121 122 // GetDefaultConfig returns default configuration. 123 func (cfg *Config) GetDefaultConfig() map[string]interface{} { 124 cfg.mu.RLock() 125 defer cfg.mu.RUnlock() 126 return deepCopyStrMap(cfg.defaults) 127 } 128 129 // GetUserConfig returns user configuration. 130 func (cfg *Config) GetUserConfig() map[string]interface{} { 131 cfg.mu.RLock() 132 defer cfg.mu.RUnlock() 133 return deepCopyStrMap(cfg.user) 134 } 135 136 // GetConfig returns current configuration. 137 func (cfg *Config) GetConfig() map[string]interface{} { 138 cfg.mu.RLock() 139 defer cfg.mu.RUnlock() 140 config := make(map[string]interface{}) 141 mergeMaps(deepCopyStrMap(cfg.defaults), config, nil) 142 mergeMaps(deepCopyStrMap(cfg.user), config, nil) 143 mergeMaps(deepCopyStrMap(cfg.cli), config, nil) 144 return deepCopyStrMap(config) 145 } 146 147 // SetDefault sets default value for key. 148 func (cfg *Config) SetDefault(key string, value interface{}) { 149 cfg.set(cfg.defaults, key, value) 150 } 151 152 // SetUser sets user configuration value for key. 153 func (cfg *Config) SetUser(key string, value interface{}) { 154 func() { 155 cfg.mu.RLock() 156 defer cfg.mu.RUnlock() 157 if cfg.eventBus != nil { 158 cfg.eventBus.Publish(AppTopicConfig(key), value) 159 } 160 }() 161 cfg.set(cfg.user, key, value) 162 } 163 164 // SetCLI sets value passed via CLI flag for key. 165 func (cfg *Config) SetCLI(key string, value interface{}) { 166 cfg.set(cfg.cli, key, value) 167 } 168 169 // RemoveUser removes user configuration value for key. 170 func (cfg *Config) RemoveUser(key string) { 171 cfg.remove(cfg.user, key) 172 } 173 174 // RemoveCLI removes configured CLI flag value by key. 175 func (cfg *Config) RemoveCLI(key string) { 176 cfg.remove(cfg.cli, key) 177 } 178 179 // set sets value to a particular configuration value map. 180 func (cfg *Config) set(configMap map[string]interface{}, key string, value interface{}) { 181 key = strings.ToLower(key) 182 segments := strings.Split(key, ".") 183 184 lastKey := strings.ToLower(segments[len(segments)-1]) 185 186 cfg.mu.Lock() 187 defer cfg.mu.Unlock() 188 deepestMap := deepSearch(configMap, segments[0:len(segments)-1]) 189 190 // set innermost value 191 deepestMap[lastKey] = value 192 } 193 194 // remove removes a configured value from a particular configuration map. 195 func (cfg *Config) remove(configMap map[string]interface{}, key string) { 196 key = strings.ToLower(key) 197 segments := strings.Split(key, ".") 198 199 lastKey := strings.ToLower(segments[len(segments)-1]) 200 201 cfg.mu.Lock() 202 defer cfg.mu.Unlock() 203 deepestMap := deepSearch(configMap, segments[0:len(segments)-1]) 204 205 // set innermost value 206 delete(deepestMap, lastKey) 207 } 208 209 // Get returns stored config value as-is. 210 func (cfg *Config) Get(key string) interface{} { 211 segments := strings.Split(strings.ToLower(key), ".") 212 cfg.mu.RLock() 213 defer cfg.mu.RUnlock() 214 cliValue := SearchMap(cfg.cli, segments) 215 if cliValue != nil { 216 log.Debug().Msgf("Returning CLI value %v:%v", key, cliValue) 217 return copyValue(cliValue) 218 } 219 userValue := SearchMap(cfg.user, segments) 220 if userValue != nil { 221 log.Debug().Msgf("Returning user config value %v:%v", key, userValue) 222 return copyValue(userValue) 223 } 224 defaultValue := SearchMap(cfg.defaults, segments) 225 log.Trace().Msgf("Returning default value %v:%v", key, defaultValue) 226 return copyValue(defaultValue) 227 } 228 229 // returns scalar values as is. deep-copies maps. 230 func copyValue(value interface{}) interface{} { 231 switch v := value.(type) { 232 case map[string]interface{}: 233 return deepCopyStrMap(v) 234 default: 235 return v 236 } 237 } 238 239 // deepcopy for nested config structures. 240 func deepCopyStrMap(src map[string]interface{}) map[string]interface{} { 241 dst := make(map[string]interface{}) 242 for sk, sv := range src { 243 switch typedValue := sv.(type) { 244 case map[string]interface{}: 245 dst[sk] = deepCopyStrMap(typedValue) 246 default: 247 dst[sk] = typedValue 248 } 249 } 250 return dst 251 } 252 253 // GetBool returns config value as bool. 254 func (cfg *Config) GetBool(key string) bool { 255 return cast.ToBool(cfg.Get(key)) 256 } 257 258 // GetInt returns config value as int. 259 func (cfg *Config) GetInt(key string) int { 260 return cast.ToInt(cfg.Get(key)) 261 } 262 263 // GetInt64 returns config value as int64. 264 func (cfg *Config) GetInt64(key string) int64 { 265 return cast.ToInt64(cfg.Get(key)) 266 } 267 268 // GetUInt64 returns config value as uint64. 269 func (cfg *Config) GetUInt64(key string) uint64 { 270 return cast.ToUint64(cfg.Get(key)) 271 } 272 273 // GetFloat64 returns config value as float64. 274 func (cfg *Config) GetFloat64(key string) float64 { 275 return cast.ToFloat64(cfg.Get(key)) 276 } 277 278 // GetDuration returns config value as duration. 279 func (cfg *Config) GetDuration(key string) time.Duration { 280 return cast.ToDuration(cfg.Get(key)) 281 } 282 283 // GetString returns config value as string. 284 func (cfg *Config) GetString(key string) string { 285 return cast.ToString(cfg.Get(key)) 286 } 287 288 // GetStringSlice returns config value as []string. 289 func (cfg *Config) GetStringSlice(key string) []string { 290 return cast.ToStringSlice(cfg.Get(key)) 291 } 292 293 // ParseBoolFlag parses a cli.BoolFlag from command's context and 294 // sets default and CLI values to the application configuration. 295 func (cfg *Config) ParseBoolFlag(ctx *cli.Context, flag cli.BoolFlag) { 296 cfg.SetDefault(flag.Name, flag.Value) 297 if ctx.IsSet(flag.Name) { 298 cfg.SetCLI(flag.Name, ctx.Bool(flag.Name)) 299 } else { 300 cfg.RemoveCLI(flag.Name) 301 } 302 } 303 304 // ParseIntFlag parses a cli.IntFlag from command's context and 305 // sets default and CLI values to the application configuration. 306 func (cfg *Config) ParseIntFlag(ctx *cli.Context, flag cli.IntFlag) { 307 cfg.SetDefault(flag.Name, flag.Value) 308 if ctx.IsSet(flag.Name) { 309 cfg.SetCLI(flag.Name, ctx.Int(flag.Name)) 310 } else { 311 cfg.RemoveCLI(flag.Name) 312 } 313 } 314 315 // ParseUInt64Flag parses a cli.Uint64Flag from command's context and 316 // sets default and CLI values to the application configuration. 317 func (cfg *Config) ParseUInt64Flag(ctx *cli.Context, flag cli.Uint64Flag) { 318 cfg.SetDefault(flag.Name, flag.Value) 319 if ctx.IsSet(flag.Name) { 320 cfg.SetCLI(flag.Name, ctx.Uint64(flag.Name)) 321 } else { 322 cfg.RemoveCLI(flag.Name) 323 } 324 } 325 326 // ParseInt64Flag parses a cli.Int64Flag from command's context and 327 // sets default and CLI values to the application configuration. 328 func (cfg *Config) ParseInt64Flag(ctx *cli.Context, flag cli.Int64Flag) { 329 cfg.SetDefault(flag.Name, flag.Value) 330 if ctx.IsSet(flag.Name) { 331 cfg.SetCLI(flag.Name, ctx.Int64(flag.Name)) 332 } else { 333 cfg.RemoveCLI(flag.Name) 334 } 335 } 336 337 // ParseFloat64Flag parses a cli.Float64Flag from command's context and 338 // sets default and CLI values to the application configuration. 339 func (cfg *Config) ParseFloat64Flag(ctx *cli.Context, flag cli.Float64Flag) { 340 cfg.SetDefault(flag.Name, flag.Value) 341 if ctx.IsSet(flag.Name) { 342 cfg.SetCLI(flag.Name, ctx.Float64(flag.Name)) 343 } else { 344 cfg.RemoveCLI(flag.Name) 345 } 346 } 347 348 // ParseDurationFlag parses a cli.DurationFlag from command's context and 349 // sets default and CLI values to the application configuration. 350 func (cfg *Config) ParseDurationFlag(ctx *cli.Context, flag cli.DurationFlag) { 351 cfg.SetDefault(flag.Name, flag.Value) 352 if ctx.IsSet(flag.Name) { 353 cfg.SetCLI(flag.Name, ctx.Duration(flag.Name)) 354 } else { 355 cfg.RemoveCLI(flag.Name) 356 } 357 } 358 359 // ParseStringFlag parses a cli.StringFlag from command's context and 360 // sets default and CLI values to the application configuration. 361 func (cfg *Config) ParseStringFlag(ctx *cli.Context, flag cli.StringFlag) { 362 cfg.SetDefault(flag.Name, flag.Value) 363 if ctx.IsSet(flag.Name) { 364 cfg.SetCLI(flag.Name, ctx.String(flag.Name)) 365 } else { 366 cfg.RemoveCLI(flag.Name) 367 } 368 } 369 370 // ParseStringSliceFlag parses a cli.StringSliceFlag from command's context and 371 // sets default and CLI values to the application configuration. 372 func (cfg *Config) ParseStringSliceFlag(ctx *cli.Context, flag cli.StringSliceFlag) { 373 var value []string = nil 374 if flag.Value != nil { 375 value = flag.Value.Value() 376 } 377 cfg.SetDefault(flag.Name, value) 378 if ctx.IsSet(flag.Name) { 379 cfg.SetCLI(flag.Name, ctx.StringSlice(flag.Name)) 380 } else { 381 cfg.RemoveCLI(flag.Name) 382 } 383 } 384 385 // ParseBlockchainNetworkFlag parses a cli.StringFlag as a blockchain network 386 // from command's context and sets default values for network parameters 387 // and CLI values for the network to the application configuration. 388 func (cfg *Config) ParseBlockchainNetworkFlag(ctx *cli.Context, flag cli.StringFlag) { 389 cfg.SetDefault(flag.Name, flag.Value) 390 if ctx.IsSet(flag.Name) { 391 network, err := ParseBlockchainNetwork(ctx.String(flag.Name)) 392 if err != nil { 393 log.Err(err).Msg("invalid network option used as flag, ignoring") 394 cfg.RemoveCLI(flag.Name) 395 return 396 } 397 cfg.SetCLI(flag.Name, strings.ToLower(string(network))) 398 cfg.SetDefaultsByNetwork(network) 399 } else { 400 cfg.RemoveCLI(flag.Name) 401 } 402 } 403 404 // SetDefaultsByNetwork sets defaults in config according to the given blockchain network. 405 func (cfg *Config) SetDefaultsByNetwork(network BlockchainNetwork) { 406 var flags map[string]any 407 switch network { 408 case Mainnet: 409 flags = metadata.MainnetDefinition.GetDefaultFlagValues() 410 case Testnet: 411 flags = metadata.TestnetDefinition.GetDefaultFlagValues() 412 case Localnet: 413 flags = metadata.LocalnetDefinition.GetDefaultFlagValues() 414 default: 415 log.Error().Str("network", string(network)).Msg("cannot handle this blockchain network option, ignoring") 416 return 417 } 418 for flagName, flagValue := range flags { 419 cfg.SetDefault(flagName, flagValue) 420 } 421 } 422 423 // GetBool shorthand for getting current configuration value for cli.BoolFlag. 424 func GetBool(flag cli.BoolFlag) bool { 425 return Current.GetBool(flag.Name) 426 } 427 428 // GetInt shorthand for getting current configuration value for cli.IntFlag. 429 func GetInt(flag cli.IntFlag) int { 430 return Current.GetInt(flag.Name) 431 } 432 433 // GetInt64 shorthand for getting current configuration value for cli.IntFlag. 434 func GetInt64(flag cli.Int64Flag) int64 { 435 return Current.GetInt64(flag.Name) 436 } 437 438 // GetString shorthand for getting current configuration value for cli.StringFlag. 439 func GetString(flag cli.StringFlag) string { 440 return Current.GetString(flag.Name) 441 } 442 443 // GetBlockchainNetwork shorthand for getting current configuration value for blockchain network. 444 func GetBlockchainNetwork(flag cli.StringFlag) BlockchainNetwork { 445 return BlockchainNetwork(Current.GetString(flag.Name)) 446 } 447 448 // GetStringSlice shorthand for getting current configuration value for cli.StringSliceFlag. 449 func GetStringSlice(flag cli.StringSliceFlag) []string { 450 return Current.GetStringSlice(flag.Name) 451 } 452 453 // GetDuration shorthand for getting current configuration value for cli.DurationFlag. 454 func GetDuration(flag cli.DurationFlag) time.Duration { 455 return Current.GetDuration(flag.Name) 456 } 457 458 // GetUInt64 shorthand for getting current configuration value for cli.Uint64Flag. 459 func GetUInt64(flag cli.Uint64Flag) uint64 { 460 return Current.GetUInt64(flag.Name) 461 } 462 463 // GetBigInt shorthand for getting and parsing a configuration value for cli.StringFlag that's a big.Int. 464 func GetBigInt(flag cli.StringFlag) *big.Int { 465 b, _ := new(big.Int).SetString(Current.GetString(flag.Name), 10) 466 return b 467 } 468 469 // GetFloat64 shorthand for getting current configuration value for cli.Uint64Flag. 470 func GetFloat64(flag cli.Float64Flag) float64 { 471 return Current.GetFloat64(flag.Name) 472 } 473 474 // AppTopicConfig returns event bus topic for the given config key to listen for its updates. 475 func AppTopicConfig(configKey string) string { 476 return "config:" + configKey 477 }