From 00343d4e84cf93e59456bc3ce5eeaa1c82007459 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 20 Nov 2023 14:41:55 +0100 Subject: [PATCH 1/4] chore(unit_test,userspace): allow env var to get expanded in yaml even when part of a string. Moreover, support env variable embedding another env variable. Signed-off-by: Federico Di Pierro --- unit_tests/falco/test_configuration.cpp | 70 +++++++++++++++------ userspace/falco/yaml_helper.h | 82 ++++++++++++++++++------- 2 files changed, 110 insertions(+), 42 deletions(-) diff --git a/unit_tests/falco/test_configuration.cpp b/unit_tests/falco/test_configuration.cpp index 47dc8d0f697..174cebd8991 100644 --- a/unit_tests/falco/test_configuration.cpp +++ b/unit_tests/falco/test_configuration.cpp @@ -114,11 +114,14 @@ TEST(Configuration, configuration_environment_variables) // Set an environment variable for testing purposes std::string env_var_value = "envVarValue"; std::string env_var_name = "ENV_VAR"; - std::string default_value = "default"; SET_ENV_VAR(env_var_name.c_str(), env_var_value.c_str()); - yaml_helper conf; - std::string sample_yaml = + std::string embedded_env_var_value = "${ENV_VAR}"; + std::string embedded_env_var_name = "ENV_VAR_EMBEDDED"; + SET_ENV_VAR(embedded_env_var_name.c_str(), embedded_env_var_value.c_str()); + + std::string default_value = "default"; + std::string env_var_sample_yaml = "base_value:\n" " id: $ENV_VAR\n" " name: '${ENV_VAR}'\n" @@ -133,52 +136,81 @@ TEST(Configuration, configuration_environment_variables) " sample_list:\n" " - ${ENV_VAR}\n" " - ' ${ENV_VAR}'\n" - " - $UNSED_XX_X_X_VAR\n"; - conf.load_from_string(sample_yaml); + " - '${ENV_VAR} '\n" + " - $UNSED_XX_X_X_VAR\n" + "paths:\n" + " - ${ENV_VAR}/foo\n" + " - $ENV_VAR/foo\n" + " - /foo/${ENV_VAR}/\n" + " - /${ENV_VAR}/${ENV_VAR}${ENV_VAR}/foo\n" + " - ${ENV_VAR_EMBEDDED}/foo\n"; + yaml_helper conf; + conf.load_from_string(env_var_sample_yaml); /* Check if the base values are defined */ ASSERT_TRUE(conf.is_defined("base_value")); ASSERT_TRUE(conf.is_defined("base_value_2")); + ASSERT_TRUE(conf.is_defined("paths")); ASSERT_FALSE(conf.is_defined("unknown_base_value")); /* Test fetching of a regular string without any environment variable */ - std::string base_value_string = conf.get_scalar("base_value.string", default_value); + auto base_value_string = conf.get_scalar("base_value.string", default_value); ASSERT_EQ(base_value_string, "my_string"); /* Test fetching of escaped environment variable format. Should return the string as-is after stripping the leading `$` */ - std::string base_value_invalid = conf.get_scalar("base_value.invalid", default_value); + auto base_value_invalid = conf.get_scalar("base_value.invalid", default_value); ASSERT_EQ(base_value_invalid, "${ENV_VAR}"); - /* Test fetching of invalid escaped environment variable format. Should return the string as-is */ - std::string base_value_invalid_env = conf.get_scalar("base_value.invalid_env", default_value); + /* Test fetching of invalid escaped environment variable format. Should return the string as-is */ + auto base_value_invalid_env = conf.get_scalar("base_value.invalid_env", default_value); ASSERT_EQ(base_value_invalid_env, "$$ENV_VAR"); /* Test fetching of strings that contain environment variables */ - std::string base_value_id = conf.get_scalar("base_value.id", default_value); + auto base_value_id = conf.get_scalar("base_value.id", default_value); ASSERT_EQ(base_value_id, "$ENV_VAR"); // Does not follow the `${VAR}` format, so it should be treated as a regular string - std::string base_value_name = conf.get_scalar("base_value.name", default_value); + auto base_value_name = conf.get_scalar("base_value.name", default_value); ASSERT_EQ(base_value_name, env_var_value); // Proper environment variable format - std::string base_value_escaped = conf.get_scalar("base_value.escaped", default_value); + auto base_value_escaped = conf.get_scalar("base_value.escaped", default_value); ASSERT_EQ(base_value_escaped, env_var_value); // Environment variable within quotes /* Test fetching of an undefined environment variable. Expected to return the default value.*/ - std::string unknown_boolean = conf.get_scalar("base_value.subvalue.subvalue2.boolean", default_value); + auto unknown_boolean = conf.get_scalar("base_value.subvalue.subvalue2.boolean", default_value); ASSERT_EQ(unknown_boolean, default_value); /* Test fetching of environment variables from a list */ - std::string base_value_2_list_0 = conf.get_scalar("base_value_2.sample_list[0]", default_value); + auto base_value_2_list_0 = conf.get_scalar("base_value_2.sample_list[0]", default_value); ASSERT_EQ(base_value_2_list_0, env_var_value); // Proper environment variable format - std::string base_value_2_list_1 = conf.get_scalar("base_value_2.sample_list[1]", default_value); - ASSERT_EQ(base_value_2_list_1, " ${ENV_VAR}"); // Environment variable preceded by a space, hence treated as a regular string + auto base_value_2_list_1 = conf.get_scalar("base_value_2.sample_list[1]", default_value); + ASSERT_EQ(base_value_2_list_1, " " + env_var_value); // Environment variable preceded by a space, still extracted env var with leading space + + auto base_value_2_list_2 = conf.get_scalar("base_value_2.sample_list[2]", default_value); + ASSERT_EQ(base_value_2_list_2, env_var_value + " "); // Environment variable proceeded by a space, still extracted env var with trailing space - std::string base_value_2_list_2 = conf.get_scalar("base_value_2.sample_list[2]", default_value); - ASSERT_EQ(base_value_2_list_2, "$UNSED_XX_X_X_VAR"); // Does not follow the `${VAR}` format, so should be treated as a regular string + auto base_value_2_list_3 = conf.get_scalar("base_value_2.sample_list[3]", default_value); + ASSERT_EQ(base_value_2_list_3, "$UNSED_XX_X_X_VAR"); // Does not follow the `${VAR}` format, so should be treated as a regular string + + /* Test expansion of environment variables within strings */ + auto path_list_0 = conf.get_scalar("paths[0]", default_value); + ASSERT_EQ(path_list_0, env_var_value + "/foo"); // Even if env var is part of bigger string, it gets expanded + + auto path_list_1 = conf.get_scalar("paths[1]", default_value); + ASSERT_EQ(path_list_1, "$ENV_VAR/foo"); // Does not follow the `${VAR}` format, so should be treated as a regular string + + auto path_list_2 = conf.get_scalar("paths[2]", default_value); + ASSERT_EQ(path_list_2, "/foo/" + env_var_value + "/"); // Even when env var is in the middle of a string. it gets expanded + + auto path_list_3 = conf.get_scalar("paths[3]", default_value); + ASSERT_EQ(path_list_3, "/" + env_var_value + "/" + env_var_value + env_var_value + "/foo"); // Even when the string contains multiple env vars they are correctly expanded + + auto path_list_4 = conf.get_scalar("paths[4]", default_value); + ASSERT_EQ(path_list_4, env_var_value + "/foo"); // Even when the env var contains another env var, it gets correctly double-expanded /* Clear the set environment variable after testing */ - SET_ENV_VAR(env_var_name.c_str(), env_var_value.c_str()); + SET_ENV_VAR(env_var_name.c_str(), ""); + SET_ENV_VAR(embedded_env_var_name.c_str(), ""); } TEST(Configuration, configuration_webserver_ip) diff --git a/userspace/falco/yaml_helper.h b/userspace/falco/yaml_helper.h index 27adeb6a768..7193a3d220a 100644 --- a/userspace/falco/yaml_helper.h +++ b/userspace/falco/yaml_helper.h @@ -31,6 +31,7 @@ limitations under the License. #include #include #include +#include #include "config_falco.h" @@ -77,42 +78,77 @@ class yaml_helper get_node(node, key); if(node.IsDefined()) { - std::string value = node.as(); + auto value = node.as(); // Helper function to convert string to the desired type T auto convert_str_to_t = [&default_value](const std::string& str) -> T { + if (str.empty()) + { + return default_value; + } + + if constexpr (std::is_same_v) + { + return str; + } std::stringstream ss(str); T result; if (ss >> result) return result; return default_value; }; - // If the value starts with `$$`, check for a subsequent `{...}` - if (value.size() >= 3 && value[0] == '$' && value[1] == '$') + auto start_pos = value.find('$'); + while (start_pos != std::string::npos) { - // If after stripping the first `$`, the string format is like `${VAR}`, treat it as a plain string and don't resolve. - if (value[2] == '{' && value[value.size() - 1] == '}') + auto substr = value.substr(start_pos); + // Case 1 -> ${} + if (substr.rfind("${", 0) == 0) { - value = value.substr(1); - return convert_str_to_t(value); + auto end_pos = substr.find('}'); + if (end_pos != std::string::npos) + { + // Eat "${" and "}" when getting the env var name + auto env_str = substr.substr(2, end_pos - 2); + const char* env_value = std::getenv(env_str.c_str()); // Get the environment variable value + if(env_value) + { + // env variable name + "${}" + value.replace(start_pos, env_str.length() + 3, env_value); + } + else + { + value.erase(start_pos, env_str.length() + 3); + } + } + else + { + // There are no "}" chars anymore; just break leaving rest of value untouched. + break; + } } - else return convert_str_to_t(value); - } - - // Check if the value is an environment variable reference - if(value.size() >= 2 && value[0] == '$' && value[1] == '{' && value[value.size() - 1] == '}') - { - // Format: ${ENV_VAR_NAME} - std::string env_var = value.substr(2, value.size() - 3); - - const char* env_value = std::getenv(env_var.c_str()); // Get the environment variable value - if(env_value) return convert_str_to_t(env_value); - - return default_value; + // Case 2 -> $${} + else if (substr.rfind("$${", 0) == 0) + { + auto end_pos = substr.find('}'); + if (end_pos != std::string::npos) + { + // Consume first "$" token + value.erase(start_pos, 1); + } + else + { + // There are no "}" chars anymore; just break leaving rest of value untouched. + break; + } + start_pos++; // consume the second '$' token + } + else + { + start_pos += substr.length(); + } + start_pos = value.find("$", start_pos); } - - // If it's not an environment variable reference, return the value as is - return node.as(); + return convert_str_to_t(value); } return default_value; From a7f2693b543c35f560d6470d7f0603d854ebb89d Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 20 Nov 2023 15:30:12 +0100 Subject: [PATCH 2/4] fix(userspace,unit_tests): fixed bool parsing. Moreover, added some more tests around env vars. Signed-off-by: Federico Di Pierro --- unit_tests/falco/test_configuration.cpp | 30 +++++++++++++++++++++++-- userspace/falco/yaml_helper.h | 2 +- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/unit_tests/falco/test_configuration.cpp b/unit_tests/falco/test_configuration.cpp index 174cebd8991..1e1105ed71e 100644 --- a/unit_tests/falco/test_configuration.cpp +++ b/unit_tests/falco/test_configuration.cpp @@ -120,6 +120,14 @@ TEST(Configuration, configuration_environment_variables) std::string embedded_env_var_name = "ENV_VAR_EMBEDDED"; SET_ENV_VAR(embedded_env_var_name.c_str(), embedded_env_var_value.c_str()); + std::string bool_env_var_value = "true"; + std::string bool_env_var_name = "ENV_VAR_BOOL"; + SET_ENV_VAR(bool_env_var_name.c_str(), bool_env_var_value.c_str()); + + std::string int_env_var_value = "12"; + std::string int_env_var_name = "ENV_VAR_INT"; + SET_ENV_VAR(int_env_var_name.c_str(), int_env_var_value.c_str()); + std::string default_value = "default"; std::string env_var_sample_yaml = "base_value:\n" @@ -143,7 +151,10 @@ TEST(Configuration, configuration_environment_variables) " - $ENV_VAR/foo\n" " - /foo/${ENV_VAR}/\n" " - /${ENV_VAR}/${ENV_VAR}${ENV_VAR}/foo\n" - " - ${ENV_VAR_EMBEDDED}/foo\n"; + " - ${ENV_VAR_EMBEDDED}/foo\n" + "is_test: ${ENV_VAR_BOOL}\n" + "num_test: ${ENV_VAR_INT}\n"; + yaml_helper conf; conf.load_from_string(env_var_sample_yaml); @@ -208,9 +219,24 @@ TEST(Configuration, configuration_environment_variables) auto path_list_4 = conf.get_scalar("paths[4]", default_value); ASSERT_EQ(path_list_4, env_var_value + "/foo"); // Even when the env var contains another env var, it gets correctly double-expanded - /* Clear the set environment variable after testing */ + /* Check that variable expansion is type-aware */ + auto boolean = conf.get_scalar("is_test", false); + ASSERT_EQ(boolean, true); // `true` can be parsed to bool. + + auto boolean_as_str = conf.get_scalar("is_test", "false"); + ASSERT_EQ(boolean_as_str, "true"); // `true` can be parsed to string. + + auto boolean_as_int = conf.get_scalar("is_test", 0); + ASSERT_EQ(boolean_as_int, 0); // `true` cannot be parsed to integer. + + auto integer = conf.get_scalar("num_test", -1); + ASSERT_EQ(integer, 12); + + /* Clear the set environment variables after testing */ SET_ENV_VAR(env_var_name.c_str(), ""); SET_ENV_VAR(embedded_env_var_name.c_str(), ""); + SET_ENV_VAR(bool_env_var_name.c_str(), ""); + SET_ENV_VAR(int_env_var_name.c_str(), ""); } TEST(Configuration, configuration_webserver_ip) diff --git a/userspace/falco/yaml_helper.h b/userspace/falco/yaml_helper.h index 7193a3d220a..3843b03cf7c 100644 --- a/userspace/falco/yaml_helper.h +++ b/userspace/falco/yaml_helper.h @@ -93,7 +93,7 @@ class yaml_helper } std::stringstream ss(str); T result; - if (ss >> result) return result; + if (ss >> std::boolalpha >> result) return result; return default_value; }; From 0977f9866ed2964f6c13a469bcebc59917a44b4e Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 27 Nov 2023 10:36:40 +0100 Subject: [PATCH 3/4] chore(unit_tests): added tests around empty config value resolving to default. Signed-off-by: Federico Di Pierro --- unit_tests/falco/test_configuration.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/unit_tests/falco/test_configuration.cpp b/unit_tests/falco/test_configuration.cpp index 1e1105ed71e..5b4cf689ff3 100644 --- a/unit_tests/falco/test_configuration.cpp +++ b/unit_tests/falco/test_configuration.cpp @@ -128,6 +128,10 @@ TEST(Configuration, configuration_environment_variables) std::string int_env_var_name = "ENV_VAR_INT"; SET_ENV_VAR(int_env_var_name.c_str(), int_env_var_value.c_str()); + std::string empty_env_var_value = ""; + std::string empty_env_var_name = "ENV_VAR_EMPTY"; + SET_ENV_VAR(empty_env_var_name.c_str(), empty_env_var_value.c_str()); + std::string default_value = "default"; std::string env_var_sample_yaml = "base_value:\n" @@ -153,7 +157,8 @@ TEST(Configuration, configuration_environment_variables) " - /${ENV_VAR}/${ENV_VAR}${ENV_VAR}/foo\n" " - ${ENV_VAR_EMBEDDED}/foo\n" "is_test: ${ENV_VAR_BOOL}\n" - "num_test: ${ENV_VAR_INT}\n"; + "num_test: ${ENV_VAR_INT}\n" + "empty_test: ${ENV_VAR_EMPTY}\n"; yaml_helper conf; conf.load_from_string(env_var_sample_yaml); @@ -198,7 +203,7 @@ TEST(Configuration, configuration_environment_variables) ASSERT_EQ(base_value_2_list_1, " " + env_var_value); // Environment variable preceded by a space, still extracted env var with leading space auto base_value_2_list_2 = conf.get_scalar("base_value_2.sample_list[2]", default_value); - ASSERT_EQ(base_value_2_list_2, env_var_value + " "); // Environment variable proceeded by a space, still extracted env var with trailing space + ASSERT_EQ(base_value_2_list_2, env_var_value + " "); // Environment variable followed by a space, still extracted env var with trailing space auto base_value_2_list_3 = conf.get_scalar("base_value_2.sample_list[3]", default_value); ASSERT_EQ(base_value_2_list_3, "$UNSED_XX_X_X_VAR"); // Does not follow the `${VAR}` format, so should be treated as a regular string @@ -232,11 +237,16 @@ TEST(Configuration, configuration_environment_variables) auto integer = conf.get_scalar("num_test", -1); ASSERT_EQ(integer, 12); + // An env var that resolves to an empty string returns default value + auto empty_default_str = conf.get_scalar("empty_test", "test"); + ASSERT_EQ(empty_default_str, "test"); + /* Clear the set environment variables after testing */ SET_ENV_VAR(env_var_name.c_str(), ""); SET_ENV_VAR(embedded_env_var_name.c_str(), ""); SET_ENV_VAR(bool_env_var_name.c_str(), ""); SET_ENV_VAR(int_env_var_name.c_str(), ""); + SET_ENV_VAR(empty_env_var_name.c_str(), ""); } TEST(Configuration, configuration_webserver_ip) From 76274a30ca9e9e2972c568ba4af1cdca6b1ee8ac Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Tue, 5 Dec 2023 09:11:19 +0100 Subject: [PATCH 4/4] new(unit_tests,userspace): properly support env var expansions in all scalar values of yaml file. Signed-off-by: Federico Di Pierro --- unit_tests/falco/test_configuration.cpp | 24 +++- userspace/falco/yaml_helper.h | 175 ++++++++++++++---------- 2 files changed, 120 insertions(+), 79 deletions(-) diff --git a/unit_tests/falco/test_configuration.cpp b/unit_tests/falco/test_configuration.cpp index 5b4cf689ff3..d8b32109dec 100644 --- a/unit_tests/falco/test_configuration.cpp +++ b/unit_tests/falco/test_configuration.cpp @@ -158,7 +158,11 @@ TEST(Configuration, configuration_environment_variables) " - ${ENV_VAR_EMBEDDED}/foo\n" "is_test: ${ENV_VAR_BOOL}\n" "num_test: ${ENV_VAR_INT}\n" - "empty_test: ${ENV_VAR_EMPTY}\n"; + "empty_test: ${ENV_VAR_EMPTY}\n" + "plugins:\n" + " - name: k8saudit\n" + " library_path: /foo/${ENV_VAR}/libk8saudit.so\n" + " open_params: ${ENV_VAR_INT}\n"; yaml_helper conf; conf.load_from_string(env_var_sample_yaml); @@ -191,9 +195,9 @@ TEST(Configuration, configuration_environment_variables) auto base_value_escaped = conf.get_scalar("base_value.escaped", default_value); ASSERT_EQ(base_value_escaped, env_var_value); // Environment variable within quotes - /* Test fetching of an undefined environment variable. Expected to return the default value.*/ + /* Test fetching of an undefined environment variable. Resolves to empty string. */ auto unknown_boolean = conf.get_scalar("base_value.subvalue.subvalue2.boolean", default_value); - ASSERT_EQ(unknown_boolean, default_value); + ASSERT_EQ(unknown_boolean, ""); /* Test fetching of environment variables from a list */ auto base_value_2_list_0 = conf.get_scalar("base_value_2.sample_list[0]", default_value); @@ -237,9 +241,17 @@ TEST(Configuration, configuration_environment_variables) auto integer = conf.get_scalar("num_test", -1); ASSERT_EQ(integer, 12); - // An env var that resolves to an empty string returns default value - auto empty_default_str = conf.get_scalar("empty_test", "test"); - ASSERT_EQ(empty_default_str, "test"); + // An env var that resolves to an empty string returns "" + auto empty_default_str = conf.get_scalar("empty_test", default_value); + ASSERT_EQ(empty_default_str, ""); + + std::list plugins; + conf.get_sequence>(plugins, std::string("plugins")); + std::vector m_plugins{ std::make_move_iterator(std::begin(plugins)), + std::make_move_iterator(std::end(plugins)) }; + ASSERT_EQ(m_plugins[0].m_name, "k8saudit"); + ASSERT_EQ(m_plugins[0].m_library_path, "/foo/" + env_var_value + "/libk8saudit.so"); + ASSERT_EQ(m_plugins[0].m_open_params, "12"); /* Clear the set environment variables after testing */ SET_ENV_VAR(env_var_name.c_str(), ""); diff --git a/userspace/falco/yaml_helper.h b/userspace/falco/yaml_helper.h index 3843b03cf7c..94a7779bd69 100644 --- a/userspace/falco/yaml_helper.h +++ b/userspace/falco/yaml_helper.h @@ -31,13 +31,46 @@ limitations under the License. #include #include #include -#include #include "config_falco.h" #include "event_drops.h" #include "falco_outputs.h" +class yaml_helper; + +class yaml_visitor { +private: + using Callback = std::function; + yaml_visitor(Callback cb): seen(), cb(std::move(cb)) {} + + void operator()(YAML::Node &cur) { + seen.push_back(cur); + if (cur.IsMap()) { + for (YAML::detail::iterator_value pair : cur) { + descend(pair.second); + } + } else if (cur.IsSequence()) { + for (YAML::detail::iterator_value child : cur) { + descend(child); + } + } else if (cur.IsScalar()) { + cb(cur); + } + } + + void descend(YAML::Node &target) { + if (std::find(seen.begin(), seen.end(), target) == seen.end()) { + (*this)(target); + } + } + + std::vector seen; + Callback cb; + + friend class yaml_helper; +}; + /** * @brief An helper class for reading and editing YAML documents */ @@ -50,6 +83,7 @@ class yaml_helper void load_from_string(const std::string& input) { m_root = YAML::Load(input); + pre_process_env_vars(); } /** @@ -58,6 +92,7 @@ class yaml_helper void load_from_file(const std::string& path) { m_root = YAML::LoadFile(path); + pre_process_env_vars(); } /** @@ -78,79 +113,8 @@ class yaml_helper get_node(node, key); if(node.IsDefined()) { - auto value = node.as(); - - // Helper function to convert string to the desired type T - auto convert_str_to_t = [&default_value](const std::string& str) -> T { - if (str.empty()) - { - return default_value; - } - - if constexpr (std::is_same_v) - { - return str; - } - std::stringstream ss(str); - T result; - if (ss >> std::boolalpha >> result) return result; - return default_value; - }; - - auto start_pos = value.find('$'); - while (start_pos != std::string::npos) - { - auto substr = value.substr(start_pos); - // Case 1 -> ${} - if (substr.rfind("${", 0) == 0) - { - auto end_pos = substr.find('}'); - if (end_pos != std::string::npos) - { - // Eat "${" and "}" when getting the env var name - auto env_str = substr.substr(2, end_pos - 2); - const char* env_value = std::getenv(env_str.c_str()); // Get the environment variable value - if(env_value) - { - // env variable name + "${}" - value.replace(start_pos, env_str.length() + 3, env_value); - } - else - { - value.erase(start_pos, env_str.length() + 3); - } - } - else - { - // There are no "}" chars anymore; just break leaving rest of value untouched. - break; - } - } - // Case 2 -> $${} - else if (substr.rfind("$${", 0) == 0) - { - auto end_pos = substr.find('}'); - if (end_pos != std::string::npos) - { - // Consume first "$" token - value.erase(start_pos, 1); - } - else - { - // There are no "}" chars anymore; just break leaving rest of value untouched. - break; - } - start_pos++; // consume the second '$' token - } - else - { - start_pos += substr.length(); - } - start_pos = value.find("$", start_pos); - } - return convert_str_to_t(value); + return node.as(default_value); } - return default_value; } @@ -189,6 +153,71 @@ class yaml_helper private: YAML::Node m_root; + /* + * When loading a yaml file, + * we immediately pre process all scalar values through a visitor private API, + * and resolve any "${env_var}" to its value; + * moreover, any "$${str}" is resolved to simply "${str}". + */ + void pre_process_env_vars() + { + yaml_visitor([](YAML::Node &scalar) { + auto value = scalar.as(); + auto start_pos = value.find('$'); + while (start_pos != std::string::npos) + { + auto substr = value.substr(start_pos); + // Case 1 -> ${} + if (substr.rfind("${", 0) == 0) + { + auto end_pos = substr.find('}'); + if (end_pos != std::string::npos) + { + // Eat "${" and "}" when getting the env var name + auto env_str = substr.substr(2, end_pos - 2); + const char* env_value = std::getenv(env_str.c_str()); // Get the environment variable value + if(env_value) + { + // env variable name + "${}" + value.replace(start_pos, env_str.length() + 3, env_value); + } + else + { + value.erase(start_pos, env_str.length() + 3); + } + } + else + { + // There are no "}" chars anymore; just break leaving rest of value untouched. + break; + } + } + // Case 2 -> $${} + else if (substr.rfind("$${", 0) == 0) + { + auto end_pos = substr.find('}'); + if (end_pos != std::string::npos) + { + // Consume first "$" token + value.erase(start_pos, 1); + } + else + { + // There are no "}" chars anymore; just break leaving rest of value untouched. + break; + } + start_pos++; // consume the second '$' token + } + else + { + start_pos += substr.length(); + } + start_pos = value.find("$", start_pos); + } + scalar = value; + })(m_root); + } + /** * Key is a string representing a node in the YAML document. * The provided key string can navigate the document in its