aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/Configuration/Config.cpp128
-rw-r--r--src/common/Configuration/Config.h3
-rw-r--r--src/server/authserver/Main.cpp5
-rw-r--r--src/server/worldserver/Main.cpp5
-rw-r--r--tests/common/Config.cpp115
5 files changed, 254 insertions, 2 deletions
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 <boost/property_tree/ini_parser.hpp>
#include <algorithm>
+#include <cstdlib>
#include <memory>
#include <mutex>
@@ -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<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,
@@ -92,6 +168,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;
@@ -108,6 +207,8 @@ bool ConfigMgr::Reload(std::vector<std::string>& 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<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());
@@ -144,7 +260,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 ce1763b4dfd..3e8dabe8ce3 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/authserver/Main.cpp b/src/server/authserver/Main.cpp
index 7a6a5037676..af0acb97236 100644
--- a/src/server/authserver/Main.cpp
+++ b/src/server/authserver/Main.cpp
@@ -108,6 +108,8 @@ int main(int argc, char** argv)
return 1;
}
+ std::vector<std::string> overriddenKeys = sConfigMgr->OverrideWithEnvVariablesIfAny();
+
sLog->RegisterAppender<AppenderDB>();
sLog->Initialize(nullptr);
@@ -124,6 +126,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());
+
// authserver PID file creation
std::string pidFile = sConfigMgr->GetStringDefault("PidFile", "");
if (!pidFile.empty())
diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp
index 079050e4b7e..418b5de5eb1 100644
--- a/src/server/worldserver/Main.cpp
+++ b/src/server/worldserver/Main.cpp
@@ -146,6 +146,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>();
@@ -165,6 +167,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());
+}