From 1ddd9dc19cc1df1a1ab8c6123283999f9dea6760 Mon Sep 17 00:00:00 2001 From: Anton Popovichenko Date: Thu, 19 Aug 2021 22:26:16 +0200 Subject: Core/Config: Implement config override with env vars (#26811) * Core/Config: Implement config override with env vars Implement overriding of configuration from the .conf file with environment variables. Environment variables keys are autogenerated based on the keys defined in .conf file. Usage example: $ export TC_DATA_DIR=/usr $ TC_WORLD_SERVER_PORT=8080 ./worldserver * Core/Config Fix typo in logs Co-authored-by: Giacomo Pozzoni * Core/Config Fix code style in EnvVarForIniKey Co-authored-by: Shauren * Update tests/common/Config.cpp * Apply suggestions from code review Co-authored-by: Peter Keresztes Schmidt * Apply suggestions from code review Co-authored-by: Peter Keresztes Schmidt Co-authored-by: Anton Popovichenko Co-authored-by: Giacomo Pozzoni Co-authored-by: Shauren Co-authored-by: Peter Keresztes Schmidt --- src/common/Configuration/Config.cpp | 128 +++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-) (limited to 'src/common/Configuration/Config.cpp') diff --git a/src/common/Configuration/Config.cpp b/src/common/Configuration/Config.cpp index 5221c4e920b..3471c568491 100644 --- a/src/common/Configuration/Config.cpp +++ b/src/common/Configuration/Config.cpp @@ -21,6 +21,7 @@ #include "Util.h" #include #include +#include #include #include @@ -57,6 +58,81 @@ namespace return true; } + + // Converts ini keys to the environment variable key (upper snake case). + // Example of conversions: + // SomeConfig => SOME_CONFIG + // myNestedConfig.opt1 => MY_NESTED_CONFIG_OPT_1 + // LogDB.Opt.ClearTime => LOG_DB_OPT_CLEAR_TIME + std::string IniKeyToEnvVarKey(std::string const& key) + { + std::string result; + + const char *str = key.c_str(); + size_t n = key.length(); + + char curr; + bool isEnd; + bool nextIsUpper; + bool currIsNumeric; + bool nextIsNumeric; + + for (size_t i = 0; i < n; ++i) + { + curr = str[i]; + if (curr == ' ' || curr == '.' || curr == '-') + { + result += '_'; + continue; + } + + isEnd = i == n - 1; + if (!isEnd) + { + nextIsUpper = isupper(str[i + 1]); + + // handle "aB" to "A_B" + if (!isupper(curr) && nextIsUpper) + { + result += static_cast(std::toupper(curr)); + result += '_'; + continue; + } + + currIsNumeric = isNumeric(curr); + nextIsNumeric = isNumeric(str[i + 1]); + + // handle "a1" to "a_1" + if (!currIsNumeric && nextIsNumeric) + { + result += static_cast(std::toupper(curr)); + result += '_'; + continue; + } + + // handle "1a" to "1_a" + if (currIsNumeric && !nextIsNumeric) + { + result += static_cast(std::toupper(curr)); + result += '_'; + continue; + } + } + + result += static_cast(std::toupper(curr)); + } + return result; + } + + Optional EnvVarForIniKey(std::string const& key) + { + std::string envKey = "TC_" + IniKeyToEnvVarKey(key); + char* val = std::getenv(envKey.c_str()); + if (!val) + return std::nullopt; + + return std::string(val); + } } bool ConfigMgr::LoadInitial(std::string file, std::vector args, @@ -92,6 +168,29 @@ bool ConfigMgr::LoadAdditionalFile(std::string file, bool keepOnReload, std::str return true; } +std::vector ConfigMgr::OverrideWithEnvVariablesIfAny() +{ + std::lock_guard lock(_configLock); + + std::vector overriddenKeys; + + for (bpt::ptree::value_type& itr: _config) + { + if (!itr.second.empty() || itr.first.empty()) + continue; + + Optional envVar = EnvVarForIniKey(itr.first); + if (!envVar) + continue; + + itr.second = bpt::ptree(*envVar); + + overriddenKeys.push_back(itr.first); + } + + return overriddenKeys; +} + ConfigMgr* ConfigMgr::instance() { static ConfigMgr instance; @@ -108,6 +207,8 @@ bool ConfigMgr::Reload(std::vector& errors) if (!LoadAdditionalFile(additionalFile, false, error)) errors.push_back(std::move(error)); + OverrideWithEnvVariablesIfAny(); + return errors.empty(); } @@ -120,7 +221,22 @@ T ConfigMgr::GetValueDefault(std::string const& name, T def, bool quiet) const } catch (bpt::ptree_bad_path const&) { - if (!quiet) + Optional envVar = EnvVarForIniKey(name); + if (envVar) + { + Optional castedVar = Trinity::StringTo(*envVar); + if (!castedVar) + { + TC_LOG_ERROR("server.loading", "Bad value defined for name %s in environment variables, going to use default instead", name.c_str()); + return def; + } + + if (!quiet) + TC_LOG_WARN("server.loading", "Missing name %s in config file %s, recovered with environment '%s' value.", name.c_str(), _filename.c_str(), envVar->c_str()); + + return *castedVar; + } + else if (!quiet) { TC_LOG_WARN("server.loading", "Missing name %s in config file %s, add \"%s = %s\" to this file", name.c_str(), _filename.c_str(), name.c_str(), std::to_string(def).c_str()); @@ -144,7 +260,15 @@ std::string ConfigMgr::GetValueDefault(std::string const& name, std } catch (bpt::ptree_bad_path const&) { - if (!quiet) + Optional envVar = EnvVarForIniKey(name); + if (envVar) + { + if (!quiet) + TC_LOG_WARN("server.loading", "Missing name %s in config file %s, recovered with environment '%s' value.", name.c_str(), _filename.c_str(), envVar->c_str()); + + return *envVar; + } + else if (!quiet) { TC_LOG_WARN("server.loading", "Missing name %s in config file %s, add \"%s = %s\" to this file", name.c_str(), _filename.c_str(), name.c_str(), def.c_str()); -- cgit v1.2.3