diff options
-rw-r--r-- | src/common/Configuration/Config.cpp | 128 | ||||
-rw-r--r-- | src/common/Configuration/Config.h | 3 | ||||
-rw-r--r-- | src/server/bnetserver/Main.cpp | 5 | ||||
-rw-r--r-- | src/server/worldserver/Main.cpp | 5 | ||||
-rw-r--r-- | tests/common/Config.cpp | 115 |
5 files changed, 254 insertions, 2 deletions
diff --git a/src/common/Configuration/Config.cpp b/src/common/Configuration/Config.cpp index be9ad42031c..9a02627d2b7 100644 --- a/src/common/Configuration/Config.cpp +++ b/src/common/Configuration/Config.cpp @@ -20,6 +20,7 @@ #include "StringConvert.h" #include <boost/property_tree/ini_parser.hpp> #include <algorithm> +#include <cstdlib> #include <memory> #include <mutex> @@ -56,6 +57,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<char>(std::toupper(curr)); + result += '_'; + continue; + } + + currIsNumeric = isNumeric(curr); + nextIsNumeric = isNumeric(str[i + 1]); + + // handle "a1" to "a_1" + if (!currIsNumeric && nextIsNumeric) + { + result += static_cast<char>(std::toupper(curr)); + result += '_'; + continue; + } + + // handle "1a" to "1_a" + if (currIsNumeric && !nextIsNumeric) + { + result += static_cast<char>(std::toupper(curr)); + result += '_'; + continue; + } + } + + result += static_cast<char>(std::toupper(curr)); + } + return result; + } + + Optional<std::string> 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<std::string> args, @@ -91,6 +167,29 @@ bool ConfigMgr::LoadAdditionalFile(std::string file, bool keepOnReload, std::str return true; } +std::vector<std::string> ConfigMgr::OverrideWithEnvVariablesIfAny() +{ + std::lock_guard<std::mutex> lock(_configLock); + + std::vector<std::string> overriddenKeys; + + for (bpt::ptree::value_type& itr: _config) + { + if (!itr.second.empty() || itr.first.empty()) + continue; + + Optional<std::string> 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; @@ -107,6 +206,8 @@ bool ConfigMgr::Reload(std::vector<std::string>& errors) if (!LoadAdditionalFile(additionalFile, false, error)) errors.push_back(std::move(error)); + OverrideWithEnvVariablesIfAny(); + return errors.empty(); } @@ -119,7 +220,22 @@ T ConfigMgr::GetValueDefault(std::string const& name, T def, bool quiet) const } catch (bpt::ptree_bad_path const&) { - if (!quiet) + Optional<std::string> envVar = EnvVarForIniKey(name); + if (envVar) + { + Optional<T> castedVar = Trinity::StringTo<T>(*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()); @@ -143,7 +259,15 @@ std::string ConfigMgr::GetValueDefault<std::string>(std::string const& name, std } catch (bpt::ptree_bad_path const&) { - if (!quiet) + Optional<std::string> 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()); diff --git a/src/common/Configuration/Config.h b/src/common/Configuration/Config.h index 6a37be56bd6..3a67563c1db 100644 --- a/src/common/Configuration/Config.h +++ b/src/common/Configuration/Config.h @@ -34,6 +34,9 @@ public: bool LoadInitial(std::string file, std::vector<std::string> args, std::string& error); bool LoadAdditionalFile(std::string file, bool keepOnReload, std::string& error); + /// Overrides configuration with environment variables and returns overridden keys + std::vector<std::string> OverrideWithEnvVariablesIfAny(); + static ConfigMgr* instance(); bool Reload(std::vector<std::string>& errors); diff --git a/src/server/bnetserver/Main.cpp b/src/server/bnetserver/Main.cpp index 1062233dc7d..65d04438574 100644 --- a/src/server/bnetserver/Main.cpp +++ b/src/server/bnetserver/Main.cpp @@ -113,6 +113,8 @@ int main(int argc, char** argv) return 1; } + std::vector<std::string> overriddenKeys = sConfigMgr->OverrideWithEnvVariablesIfAny(); + sLog->RegisterAppender<AppenderDB>(); sLog->Initialize(nullptr); @@ -129,6 +131,9 @@ int main(int argc, char** argv) } ); + for (std::string const& key : overriddenKeys) + TC_LOG_INFO("server.authserver", "Configuration field '%s' was overridden with environment variable.", key.c_str()); + // Seed the OpenSSL's PRNG here. // That way it won't auto-seed when calling BigNumber::SetRand and slow down the first world login BigNumber seed; diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp index 118bf312170..fcd3b8d022d 100644 --- a/src/server/worldserver/Main.cpp +++ b/src/server/worldserver/Main.cpp @@ -192,6 +192,8 @@ extern int main(int argc, char** argv) return 1; } + std::vector<std::string> overriddenKeys = sConfigMgr->OverrideWithEnvVariablesIfAny(); + std::shared_ptr<Trinity::Asio::IoContext> ioContext = std::make_shared<Trinity::Asio::IoContext>(); sLog->RegisterAppender<AppenderDB>(); @@ -211,6 +213,9 @@ extern int main(int argc, char** argv) } ); + for (std::string const& key : overriddenKeys) + TC_LOG_INFO("server.worldserver", "Configuration field '%s' was overridden with environment variable.", key.c_str()); + OpenSSLCrypto::threadsSetup(); std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); }); diff --git a/tests/common/Config.cpp b/tests/common/Config.cpp new file mode 100644 index 00000000000..fb657c358fd --- /dev/null +++ b/tests/common/Config.cpp @@ -0,0 +1,115 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#include "tc_catch2.h" + +#include "Config.h" +#include <boost/filesystem.hpp> +#include <cstdlib> +#include <string> + +std::string CreateConfigWithMap(std::map<std::string, std::string> const& map) +{ + auto mTempFileRel = boost::filesystem::unique_path("deleteme.ini"); + auto mTempFileAbs = boost::filesystem::temp_directory_path() / mTempFileRel; + std::ofstream iniStream; + iniStream.open(mTempFileAbs.c_str()); + + iniStream << "[test]\n"; + for (auto const& itr : map) + iniStream << itr.first << " = " << itr.second << "\n"; + + iniStream.close(); + + return mTempFileAbs.native(); +} + +TEST_CASE("Envariable variables", "[Config]") +{ + std::map<std::string, std::string> config; + config["Int.Nested"] = "4242"; + config["lower"] = "simpleString"; + config["UPPER"] = "simpleString"; + config["SomeLong.NestedNameWithNumber.Like1"] = "1"; + + auto filePath = CreateConfigWithMap(config); + + std::string err; + REQUIRE(sConfigMgr->LoadInitial(filePath, std::vector<std::string>(), err)); + REQUIRE(err.empty()); + + SECTION("Nested int") + { + REQUIRE(sConfigMgr->GetIntDefault("Int.Nested", 10) == 4242); + + setenv("TC_INT_NESTED", "8080", 1); + REQUIRE(!sConfigMgr->OverrideWithEnvVariablesIfAny().empty()); + REQUIRE(sConfigMgr->GetIntDefault("Int.Nested", 10) == 8080); + } + + SECTION("Simple lower string") + { + REQUIRE(sConfigMgr->GetStringDefault("lower", "") == "simpleString"); + + setenv("TC_LOWER", "envstring", 1); + REQUIRE(!sConfigMgr->OverrideWithEnvVariablesIfAny().empty()); + REQUIRE(sConfigMgr->GetStringDefault("lower", "") == "envstring"); + } + + SECTION("Simple upper string") + { + REQUIRE(sConfigMgr->GetStringDefault("UPPER", "") == "simpleString"); + + setenv("TC_UPPER", "envupperstring", 1); + REQUIRE(!sConfigMgr->OverrideWithEnvVariablesIfAny().empty()); + REQUIRE(sConfigMgr->GetStringDefault("UPPER", "") == "envupperstring"); + } + + SECTION("Long nested name with number") + { + REQUIRE(sConfigMgr->GetFloatDefault("SomeLong.NestedNameWithNumber.Like1", 0) == 1); + + setenv("TC_SOME_LONG_NESTED_NAME_WITH_NUMBER_LIKE_1", "42", 1); + REQUIRE(!sConfigMgr->OverrideWithEnvVariablesIfAny().empty()); + REQUIRE(sConfigMgr->GetFloatDefault("SomeLong.NestedNameWithNumber.Like1", 0) == 42); + } + + SECTION("String that not exist in config") + { + setenv("TC_UNIQUE_STRING", "somevalue", 1); + REQUIRE(sConfigMgr->GetStringDefault("Unique.String", "") == "somevalue"); + } + + SECTION("Int that not exist in config") + { + setenv("TC_UNIQUE_INT", "100", 1); + REQUIRE(sConfigMgr->GetIntDefault("Unique.Int", 1) == 100); + } + + SECTION("Not existing string") + { + REQUIRE(sConfigMgr->GetStringDefault("NotFound.String", "none") == "none"); + } + + SECTION("Not existing int") + { + REQUIRE(sConfigMgr->GetIntDefault("NotFound.Int", 1) == 1); + } + + std::remove(filePath.c_str()); +} |