diff --git a/src/libecflow_light/ecflow/light/Configuration.cc b/src/libecflow_light/ecflow/light/Configuration.cc index 0c3a040..2d8a3cf 100644 --- a/src/libecflow_light/ecflow/light/Configuration.cc +++ b/src/libecflow_light/ecflow/light/Configuration.cc @@ -38,30 +38,6 @@ std::ostream& operator<<(std::ostream& os, const ClientCfg& cfg) { return os; } -namespace implementation_detail { - -std::string replace_env_var(const std::string& value, const Environment& environment) { - static std::regex regex(R"(\$ENV\{([^}]*)\})"); - std::smatch match; - if (bool found = std::regex_match(value, match, regex); found) { - std::string name = match[1]; - if (std::optional variable = environment.get_optional(name); variable) { - // Attempt to retrieve from 'cached' Environment first - return variable->value; - } - else if (std::optional variable = implementation_detail::Environment0::get_variable(name); variable) { - // Then, attempt to retrieve from actual environment variables - return variable->value; - } else { - Log::warning() << Message("Environment variable '", name, "' not found. Replacement not possible...").str() - << std::endl; - } - } - return value; -} - -} // namespace implementation_detail - Configuration Configuration::make_cfg() { Configuration cfg{}; @@ -108,8 +84,8 @@ Configuration Configuration::make_cfg() { std::string version = get("version", "1.0"); // Replace environment variables - host = implementation_detail::replace_env_var(host, environment); - port = implementation_detail::replace_env_var(port, environment); + host = replace_env_var(host, environment); + port = replace_env_var(port, environment); cfg.clients.push_back(ClientCfg::make_cfg(kind, protocol, host, port, version)); diff --git a/src/libecflow_light/ecflow/light/Environment.cc b/src/libecflow_light/ecflow/light/Environment.cc index 0c6c740..ae49ae2 100644 --- a/src/libecflow_light/ecflow/light/Environment.cc +++ b/src/libecflow_light/ecflow/light/Environment.cc @@ -9,3 +9,33 @@ */ #include "ecflow/light/Environment.h" + +#include + +#include "ecflow/light/Log.h" + +namespace ecflow::light { + +std::string replace_env_var(const std::string& parameter, const Environment& environment) { + static std::regex regex(R"(\$ENV\{([^}]*)\})"); + std::smatch match; + if (bool found = std::regex_match(parameter, match, regex); found) { + std::string name = match[1]; + + // Retrieve the variable from the 'cached' Environment + if (std::optional variable = environment.get_optional(name); variable) { + return variable->value; + } + + // Retrieve the variable from the 'OS' Environment + if (std::optional variable = implementation_detail::Environment0::get_variable(name); variable) { + return variable->value; + } + + Log::warning() << Message("Environment variable '", name, "' not found. Replacement not possible...").str() + << std::endl; + } + return parameter; +} + +} // namespace ecflow::light diff --git a/src/libecflow_light/ecflow/light/Environment.h b/src/libecflow_light/ecflow/light/Environment.h index 56b5907..3cb68e6 100644 --- a/src/libecflow_light/ecflow/light/Environment.h +++ b/src/libecflow_light/ecflow/light/Environment.h @@ -164,6 +164,20 @@ class Environment { dict_t environment_; }; +/** + * Replace occurrences of environment variables in the given 'parameter' string, + * using the provided 'environment' for variable lookup. + * + * The expected placeholder for environment variables in the 'parameter' string is: + * $ENV{VARIABLE_NAME} + * + * When the placeholder is found, the corresponding variable is retrieved, either from the 'environment' itself + * or from the OS environment, and its value replaces the corresponding placeholder in the 'parameter' string. + * + * If the variable is not found, a warning is logged, and the original 'parameter' string is returned unchanged. + */ +std::string replace_env_var(const std::string& parameter, const Environment& environment); + } // namespace ecflow::light #endif diff --git a/src/libecflow_light/ecflow/light/Log.h b/src/libecflow_light/ecflow/light/Log.h index 6810d82..7b51d3f 100644 --- a/src/libecflow_light/ecflow/light/Log.h +++ b/src/libecflow_light/ecflow/light/Log.h @@ -12,6 +12,7 @@ #define ECFLOW_LIGHT_LOG_H #include +#include #include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 72106c2..3729fe0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,3 +57,28 @@ ecbuild_add_test( ) target_clangformat(TARGET ${TARGET} CONDITION HAVE_TESTS) + +# ============================================================================== +# Environment Test + +set(TARGET ecflow_light_environment_test) + +set(${TARGET}_srcs + # SOURCES + TestEnvironment.cc +) + +ecbuild_add_test( + TARGET ${TARGET} + SOURCES + ${${TARGET}_srcs} + LIBS + ecflow_light + eckit + CXXFLAGS + ${TEST_CXXFLAGS} + ENVIRONMENT + "ECF_SOME_VARIABLE=1500" +) + +target_clangformat(TARGET ${TARGET} CONDITION HAVE_TESTS) diff --git a/tests/TestEnvironment.cc b/tests/TestEnvironment.cc new file mode 100644 index 0000000..9640297 --- /dev/null +++ b/tests/TestEnvironment.cc @@ -0,0 +1,72 @@ +/* + * (C) Copyright 2023- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include + +#include "ecflow/light/ClientAPI.h" + +namespace ecfl = ecflow::light; + + +namespace ecflow::light::testing { + +CASE("test_environment__can_handle_cached_environment_variables") { + Environment environment = Environment::an_environment() + .with("ECF_RID", "12345") + .with("ECF_NAME", "/path/to/task") + .with("ECF_PASS", "custom_password"); + + auto rid = environment.get_optional("ECF_RID"); + EXPECT(rid && rid->value == "12345"); + auto name = environment.get_optional("ECF_NAME"); + EXPECT(name && name->value == "/path/to/task"); + auto pass = environment.get_optional("ECF_PASS"); + EXPECT(pass && pass->value == "custom_password"); +} + +CASE("test_environment__can_handle_non_cached_environment_variables") { + Environment environment = Environment::an_environment() + .with("ECF_RID", "12345") + .with("ECF_NAME", "/path/to/task") + .with("ECF_PASS", "custom_password"); + + auto nonexistent = environment.get_optional("__NONEXISTENT__"); + EXPECT(!nonexistent); +} + +CASE("test_environment__can_replace_environment_variables") { + Environment environment = Environment::an_environment() + .with("ECF_RID", "12345") + .with("ECF_NAME", "/path/to/task") + .with("ECF_PASS", "custom_password"); + + { + // No replacement actually necessary + auto result = replace_env_var("somevalue", environment); + EXPECT(result == "somevalue"); + } + { + // Replace variable, based on 'cached' environment + auto result = replace_env_var("$ENV{ECF_NAME}", environment); + EXPECT(result == "/path/to/task"); + } + { + // Replace variable, based on 'real' environment + // The following 'SOME_VARIABLE' is set on the environment by CMake + auto result = replace_env_var("$ENV{ECF_SOME_VARIABLE}", environment); + EXPECT(result == "1500"); + } +} + +} // namespace ecflow::light::testing + +int main(int argc, char** argv) { + return eckit::testing::run_tests(argc, argv); +}