diff --git a/unit_tests/falco/test_configuration.cpp b/unit_tests/falco/test_configuration.cpp index cba3f047..d3e95a77 100644 --- a/unit_tests/falco/test_configuration.cpp +++ b/unit_tests/falco/test_configuration.cpp @@ -88,16 +88,88 @@ TEST(Configuration, modify_yaml_fields) { std::string key = "base_value.subvalue.subvalue2.boolean"; yaml_helper conf; - + /* Get original value */ conf.load_from_string(sample_yaml); ASSERT_EQ(conf.get_scalar(key, false), true); - + /* Modify the original value */ conf.set_scalar(key, false); ASSERT_EQ(conf.get_scalar(key, true), false); - + /* Modify it again */ conf.set_scalar(key, true); ASSERT_EQ(conf.get_scalar(key, false), true); } + +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"; + setenv(env_var_name.c_str(), env_var_value.c_str(), 1); + yaml_helper conf; + + std::string sample_yaml = + "base_value:\n" + " id: $ENV_VAR\n" + " name: '${ENV_VAR}'\n" + " string: my_string\n" + " invalid: $${ENV_VAR}\n" + " invalid_env: $$ENV_VAR\n" + " escaped: \"${ENV_VAR}\"\n" + " subvalue:\n" + " subvalue2:\n" + " boolean: ${UNSED_XX_X_X_VAR}\n" + "base_value_2:\n" + " sample_list:\n" + " - ${ENV_VAR}\n" + " - ' ${ENV_VAR}'\n" + " - $UNSED_XX_X_X_VAR\n"; + conf.load_from_string(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_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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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 + + 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 + + /* Clear the set environment variable after testing */ + unsetenv(env_var_name.c_str()); +} diff --git a/userspace/falco/yaml_helper.h b/userspace/falco/yaml_helper.h index 10f5564b..b8fc0d94 100644 --- a/userspace/falco/yaml_helper.h +++ b/userspace/falco/yaml_helper.h @@ -72,6 +72,41 @@ public: get_node(node, key); if(node.IsDefined()) { + std::string 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 { + 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] == '$') + { + // 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] == '}') + { + value = value.substr(1); + return convert_str_to_t(value); + } + 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; + } + + // If it's not an environment variable reference, return the value as is return node.as(); } @@ -118,10 +153,10 @@ private: * The provided key string can navigate the document in its * nested nodes, with arbitrary depth. The key string follows * this regular language: - * + * * Key := NodeKey ('.' NodeKey)* * NodeKey := (any)+ ('[' (integer)+ ']')* - * + * * Some examples of accepted key strings: * - NodeName * - ListValue[3].subvalue @@ -146,7 +181,7 @@ private: if (i > 0 && nodeKey.empty() && key[i - 1] != '.') { throw std::runtime_error( - "Parsing error: expected '.' character at pos " + "Parsing error: expected '.' character at pos " + std::to_string(i - 1)); } nodeKey += c; @@ -157,7 +192,7 @@ private: if (nodeKey.empty()) { throw std::runtime_error( - "Parsing error: unexpected character at pos " + "Parsing error: unexpected character at pos " + std::to_string(i)); } ret.reset(ret[nodeKey]); @@ -181,7 +216,7 @@ private: throw std::runtime_error("Config error at key \"" + key + "\": " + std::string(e.what())); } } - + template void get_sequence_from_node(T& ret, const YAML::Node& node) const { @@ -250,7 +285,7 @@ namespace YAML { default: break; } - + return true; } };