Skip to content

Commit

Permalink
Config: Implement config override with env vars (#502)
Browse files Browse the repository at this point in the history
* 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 CM_DATA_DIR=/usr
$ CM_WORLD_SERVER_PORT=8080 ./mangosd

* Update env var key format and encapsulated env loading logic.

The new env key format:
Mangosd_Rate_Health
Mangosd_DataDir

* Add suggestions from code review.

* Add missing includes.
  • Loading branch information
walkline authored Mar 8, 2024
1 parent bb5c608 commit 6202f30
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 11 deletions.
10 changes: 10 additions & 0 deletions src/game/Anticheat/module/anticheat.conf.dist.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
# This file is intended to be used in a setting where the anticheat should run
# purely in a read-only mode. It will do all of its tasks, but take no action,
# aside from sending out notifications. It is intended for testing purposes.
#
# To overwrite configuration fields with environment variables
# use the following pattern to generate environment variable names:
#
# For Enable:
# export Anticheat_Enable=0
#
# For IPBanDelay.Max:
# export Anticheat_IPBanDelay_Max=120
#

[AnticheatConf]
ConfVersion=2017102301
Expand Down
2 changes: 1 addition & 1 deletion src/game/Anticheat/module/libanticheat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ namespace NamreebAnticheat
{
void AnticheatLib::Reload()
{
if (!sAnticheatConfig.SetSource(_LIB_ANTICHEAT_CONFIG))
if (!sAnticheatConfig.SetSource(_LIB_ANTICHEAT_CONFIG, "Anticheat_"))
sLog.outError("[Anticheat] Could not find configuration file %s.", _LIB_ANTICHEAT_CONFIG);

// the configuration setting must be loaded before the database data because the current config settings
Expand Down
2 changes: 1 addition & 1 deletion src/game/AuctionHouseBot/AuctionHouseBot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ AuctionHouseBot::~AuctionHouseBot()

void AuctionHouseBot::Initialize()
{
if (!m_ahBotCfg.SetSource(m_configFileName))
if (!m_ahBotCfg.SetSource(m_configFileName, "Mangosd_"))
{
// set buy/sell chance to 0, this prevents Update() from accessing uninitialized variables
m_chanceBuy = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/mangosd/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ int main(int argc, char* argv[])
}
#endif

if (!sConfig.SetSource(configFile))
if (!sConfig.SetSource(configFile, "Mangosd_"))
{
sLog.outError("Could not find configuration file %s.", configFile.c_str());
Log::WaitBeforeContinueIfNeed();
Expand Down
12 changes: 11 additions & 1 deletion src/mangosd/mangosd.conf.dist.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
#####################################
# MaNGOS Configuration file #
# MaNGOS Configuration file
#
# To overwrite configuration fields with environment variables
# use the following pattern to generate environment variable names:
#
# For Rate.Health:
# export Mangosd_Rate_Health=1.2
#
# For DataDir:
# export Mangosd_DataDir=/tmp
#
#####################################

[MangosdConf]
Expand Down
2 changes: 1 addition & 1 deletion src/realmd/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ int main(int argc, char* argv[])
}
#endif

if (!sConfig.SetSource(configFile))
if (!sConfig.SetSource(configFile, "Realmd_"))
{
sLog.outError("Could not find configuration file %s.", configFile.c_str());
Log::WaitBeforeContinueIfNeed();
Expand Down
12 changes: 11 additions & 1 deletion src/realmd/realmd.conf.dist.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
############################################
# MaNGOS realmd configuration file #
# MaNGOS realmd configuration file
#
# To overwrite configuration fields with environment variables
# use the following pattern to generate environment variable names:
#
# For WrongPass.MaxCount:
# export Realmd_WrongPass_MaxCount=10
#
# For RealmServerPort:
# export Realmd_RealmServerPort=3333
#
############################################

[RealmdConf]
Expand Down
44 changes: 40 additions & 4 deletions src/shared/Config/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,26 @@

#include "Config.h"
#include "Policies/Singleton.h"
#include "Util/Util.h"
#include <mutex>

#include <boost/algorithm/string.hpp>

#include <unordered_map>
#include <string>
#include <fstream>
#include <cstdlib>
#include <optional>
#include <algorithm>

INSTANTIATE_SINGLETON_1(Config);

bool Config::SetSource(const std::string& file)
std::optional<std::string> EnvVarForIniKey(std::string const&, std::string const&);

bool Config::SetSource(const std::string& file, const std::string& envVarPrefix)
{
m_filename = file;
m_envVarPrefix = envVarPrefix;

return Reload();
}
Expand Down Expand Up @@ -62,9 +69,14 @@ bool Config::Reload()
if (equals == std::string::npos)
return false;

auto const entry = boost::algorithm::trim_copy(boost::algorithm::to_lower_copy(line.substr(0, equals)));
auto const value = boost::algorithm::trim_copy_if(boost::algorithm::trim_copy(line.substr(equals + 1)), boost::algorithm::is_any_of("\""));
auto const trimmedEntry = boost::algorithm::trim_copy(line.substr(0, equals));
auto const entry = boost::algorithm::to_lower_copy(trimmedEntry);
auto value = boost::algorithm::trim_copy_if(boost::algorithm::trim_copy(line.substr(equals + 1)), boost::algorithm::is_any_of("\""));

std::optional<std::string> envValue = EnvVarForIniKey(m_envVarPrefix, trimmedEntry);
if (envValue)
value = *envValue;

newEntries[entry] = value;
}
while (in.good());
Expand All @@ -85,8 +97,21 @@ const std::string Config::GetStringDefault(const std::string& name, const std::s
auto const nameLower = boost::algorithm::to_lower_copy(name);

auto const entry = m_entries.find(nameLower);

if (entry == m_entries.cend())
{
std::optional<std::string> envVar = EnvVarForIniKey(m_envVarPrefix, name);
if (envVar)
{
sLog.outString("Missing key '%s' in config file '%s', recovered with environment '%s' value.", name.c_str(), m_filename.c_str(), envVar->c_str());

return entry == m_entries.cend() ? def : entry->second;
return *envVar;
}

return def;
}

return entry->second;
}

bool Config::GetBoolDefault(const std::string& name, bool def) const
Expand All @@ -113,3 +138,14 @@ float Config::GetFloatDefault(const std::string& name, float def) const
return std::stof(value);
}

std::optional<std::string> EnvVarForIniKey(std::string const& prefix, std::string const& key)
{
std::string escapedKey = key;
std::replace(escapedKey.begin(), escapedKey.end(), '.', '_');
std::string envKey = prefix + escapedKey;
char* val = std::getenv(envKey.c_str());
if (!val)
return {};

return std::string(val);
}
3 changes: 2 additions & 1 deletion src/shared/Config/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ class Config
{
private:
std::string m_filename;
std::string m_envVarPrefix;
std::unordered_map<std::string, std::string> m_entries; // keys are converted to lower case. values cannot be.

public:
bool SetSource(const std::string& file);
bool SetSource(const std::string& file, const std::string& envVarPrefix);
bool Reload();

bool IsSet(const std::string& name) const;
Expand Down

1 comment on commit 6202f30

@NeatElves
Copy link

@NeatElves NeatElves commented on 6202f30 Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff --git a/src/game/PlayerBot/Base/PlayerbotMgr.cpp b/src/game/PlayerBot/Base/PlayerbotMgr.cpp
index 3b416be28..89e3f5b71 100644
--- a/src/game/PlayerBot/Base/PlayerbotMgr.cpp
+++ b/src/game/PlayerBot/Base/PlayerbotMgr.cpp
@@ -41,7 +41,7 @@ Config botConfig;
 void PlayerbotMgr::SetInitialWorldSettings()
 {
     //Get playerbot configuration file
-    if (!botConfig.SetSource(_PLAYERBOT_CONFIG))
+    if (!botConfig.SetSource(_PLAYERBOT_CONFIG, "Playerbot_"))
         sLog.outError("Playerbot: Unable to open configuration file. Database will be unaccessible. Configuration values will use default.");
     else
         sLog.outString("Playerbot: Using configuration file %s", _PLAYERBOT_CONFIG.c_str());

Please sign in to comment.