new(unit_tests,userspace): properly support env var expansions in all scalar values of yaml file.

Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
This commit is contained in:
Federico Di Pierro 2023-12-05 09:11:19 +01:00 committed by poiana
parent 3b095a5eda
commit cbbcb61153
2 changed files with 120 additions and 79 deletions

View File

@ -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<std::string>("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<std::string>("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<std::string>("base_value_2.sample_list[0]", default_value);
@ -237,9 +241,17 @@ TEST(Configuration, configuration_environment_variables)
auto integer = conf.get_scalar<int32_t>("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<std::string>("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<std::string>("empty_test", default_value);
ASSERT_EQ(empty_default_str, "");
std::list<falco_configuration::plugin_config> plugins;
conf.get_sequence<std::list<falco_configuration::plugin_config>>(plugins, std::string("plugins"));
std::vector<falco_configuration::plugin_config> 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(), "");

View File

@ -31,13 +31,46 @@ limitations under the License.
#include <set>
#include <iostream>
#include <fstream>
#include <type_traits>
#include "config_falco.h"
#include "event_drops.h"
#include "falco_outputs.h"
class yaml_helper;
class yaml_visitor {
private:
using Callback = std::function<void(YAML::Node&)>;
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<YAML::Node> seen;
Callback cb;
friend class yaml_helper;
};
/**
* @brief An helper class for reading and editing YAML documents
*/
@ -50,6 +83,7 @@ public:
void load_from_string(const std::string& input)
{
m_root = YAML::Load(input);
pre_process_env_vars();
}
/**
@ -58,6 +92,7 @@ public:
void load_from_file(const std::string& path)
{
m_root = YAML::LoadFile(path);
pre_process_env_vars();
}
/**
@ -78,25 +113,56 @@ public:
get_node(node, key);
if(node.IsDefined())
{
auto value = node.as<std::string>();
// 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 node.as<T>(default_value);
}
return default_value;
}
if constexpr (std::is_same_v<T, std::string>)
/**
* Set the node identified by key to value.
*/
template<typename T>
void set_scalar(const std::string& key, const T& value)
{
return str;
YAML::Node node;
get_node(node, key);
node = value;
}
std::stringstream ss(str);
T result;
if (ss >> std::boolalpha >> result) return result;
return default_value;
};
/**
* Get the sequence value from the node identified by key.
*/
template<typename T>
void get_sequence(T& ret, const std::string& key) const
{
YAML::Node node;
get_node(node, key);
return get_sequence_from_node<T>(ret, node);
}
/**
* Return true if the node identified by key is defined.
*/
bool is_defined(const std::string& key) const
{
YAML::Node node;
get_node(node, key);
return node.IsDefined();
}
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<std::string>();
auto start_pos = value.find('$');
while (start_pos != std::string::npos)
{
@ -148,47 +214,10 @@ public:
}
start_pos = value.find("$", start_pos);
}
return convert_str_to_t(value);
scalar = value;
})(m_root);
}
return default_value;
}
/**
* Set the node identified by key to value.
*/
template<typename T>
void set_scalar(const std::string& key, const T& value)
{
YAML::Node node;
get_node(node, key);
node = value;
}
/**
* Get the sequence value from the node identified by key.
*/
template<typename T>
void get_sequence(T& ret, const std::string& key) const
{
YAML::Node node;
get_node(node, key);
return get_sequence_from_node<T>(ret, node);
}
/**
* Return true if the node identified by key is defined.
*/
bool is_defined(const std::string& key) const
{
YAML::Node node;
get_node(node, key);
return node.IsDefined();
}
private:
YAML::Node m_root;
/**
* Key is a string representing a node in the YAML document.
* The provided key string can navigate the document in its