chore(userspace,unit_tests): properly report all schema validation warnings from yaml_helper::validate_node().

`-V` option will print all warnings, while normal run will only print foremost warning.

Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
This commit is contained in:
Federico Di Pierro 2024-09-10 11:36:56 +02:00 committed by poiana
parent 2f89a2c140
commit 468037151a
6 changed files with 103 additions and 55 deletions

View File

@ -111,14 +111,14 @@ TEST(Configuration, schema_yaml_helper_validator)
EXPECT_NO_THROW(conf.load_from_string(sample_yaml)); EXPECT_NO_THROW(conf.load_from_string(sample_yaml));
// We pass a string variable but not a schema // We pass a string variable but not a schema
std::string validation; std::vector<std::string> validation;
EXPECT_NO_THROW(conf.load_from_string(sample_yaml, nlohmann::json{}, &validation)); EXPECT_NO_THROW(conf.load_from_string(sample_yaml, nlohmann::json{}, &validation));
EXPECT_EQ(validation, yaml_helper::validation_none); EXPECT_EQ(validation[0], yaml_helper::validation_none);
// We pass a schema but not a string storage for the validation; no validation takes place // We pass a schema but not a string storage for the validation; no validation takes place
EXPECT_NO_THROW(conf.load_from_string(sample_yaml, falco_config.m_config_schema, nullptr)); EXPECT_NO_THROW(conf.load_from_string(sample_yaml, falco_config.m_config_schema, nullptr));
// We pass everything // We pass everything
EXPECT_NO_THROW(conf.load_from_string(sample_yaml, falco_config.m_config_schema, &validation)); EXPECT_NO_THROW(conf.load_from_string(sample_yaml, falco_config.m_config_schema, &validation));
EXPECT_EQ(validation, yaml_helper::validation_ok); EXPECT_EQ(validation[0], yaml_helper::validation_ok);
} }

View File

@ -283,8 +283,7 @@ std::string rule_loader::context::snippet(const falco::load_result::rules_conten
rule_loader::result::result(const std::string &name) rule_loader::result::result(const std::string &name)
: name(name), : name(name),
success(true), success(true)
schema_validation_str(yaml_helper::validation_none)
{ {
} }
@ -300,7 +299,11 @@ bool rule_loader::result::has_warnings()
std::string rule_loader::result::schema_validation() std::string rule_loader::result::schema_validation()
{ {
return schema_validation_str; if (schema_validation_status.empty())
{
return yaml_helper::validation_none;
}
return schema_validation_status[0];
} }
void rule_loader::result::add_error(load_result::error_code ec, const std::string& msg, const context& ctx) void rule_loader::result::add_error(load_result::error_code ec, const std::string& msg, const context& ctx)
@ -318,9 +321,9 @@ void rule_loader::result::add_warning(load_result::warning_code wc, const std::s
warnings.push_back(warn); warnings.push_back(warn);
} }
void rule_loader::result::set_schema_validation_status(const std::string& status) void rule_loader::result::set_schema_validation_status(const std::vector<std::string>& status)
{ {
schema_validation_str = status; schema_validation_status = status;
} }
const std::string& rule_loader::result::as_string(bool verbose, const rules_contents_t& contents) const std::string& rule_loader::result::as_string(bool verbose, const rules_contents_t& contents)
@ -364,9 +367,28 @@ const std::string& rule_loader::result::as_summary_string()
} }
// Only print schema validation info if any validation was requested // Only print schema validation info if any validation was requested
if (schema_validation_str != yaml_helper::validation_none) if (!schema_validation_status.empty())
{ {
os << " | schema validation: " << schema_validation_str; bool schema_valid = schema_validation() == yaml_helper::validation_ok;
// Only print info when there are validation warnings
if (!schema_valid)
{
os << std::endl;
os << schema_validation_status.size() << " schema warnings: [";
bool first = true;
for(auto& status : schema_validation_status)
{
if(!first)
{
os << " ";
}
first = false;
os << status;
}
os << "]";
}
} }
if(!errors.empty()) if(!errors.empty())
@ -442,9 +464,28 @@ const std::string& rule_loader::result::as_verbose_string(const rules_contents_t
} }
// Only print schema validation info if any validation was requested // Only print schema validation info if any validation was requested
if (schema_validation_str != yaml_helper::validation_none) if (!schema_validation_status.empty())
{ {
os << " | schema validation: " << schema_validation_str; bool schema_valid = schema_validation() == yaml_helper::validation_ok;
// Only print info when there are validation warnings
if (!schema_valid)
{
os << std::endl;
os << schema_validation_status.size() << " schema warnings: [";
bool first = true;
for(auto& status : schema_validation_status)
{
if(!first)
{
os << " ";
}
first = false;
os << status;
}
os << "]";
}
} }
if (!errors.empty()) if (!errors.empty())
@ -507,14 +548,17 @@ const nlohmann::json& rule_loader::result::as_json(const rules_contents_t& conte
j["successful"] = success; j["successful"] = success;
// Only print schema validation info if any validation was requested // Only print schema validation info if any validation was requested
if (schema_validation_str != yaml_helper::validation_none) if (!schema_validation_status.empty())
{ {
bool schema_valid = schema_validation_str == yaml_helper::validation_ok; bool schema_valid = schema_validation() == yaml_helper::validation_ok;
j["schema_valid"] = schema_valid; j["schema_valid"] = schema_valid;
j["schema_warnings"] = nlohmann::json::array(); j["schema_warnings"] = nlohmann::json::array();
if (!schema_valid) if (!schema_valid)
{ {
j["schema_warnings"].push_back(schema_validation_str); for (const auto &schema_warning : schema_validation_status)
{
j["schema_warnings"].push_back(schema_warning);
}
} }
} }

View File

@ -248,7 +248,7 @@ namespace rule_loader
const std::string& msg, const std::string& msg,
const context& ctx); const context& ctx);
void set_schema_validation_status(const std::string& status); void set_schema_validation_status(const std::vector<std::string>& status);
std::string schema_validation(); std::string schema_validation();
protected: protected:
@ -256,7 +256,7 @@ namespace rule_loader
const std::string& as_verbose_string(const falco::load_result::rules_contents_t& contents); const std::string& as_verbose_string(const falco::load_result::rules_contents_t& contents);
std::string name; std::string name;
bool success; bool success;
std::string schema_validation_str; std::vector<std::string> schema_validation_status;
std::vector<error> errors; std::vector<error> errors;
std::vector<warning> warnings; std::vector<warning> warnings;

View File

@ -788,11 +788,11 @@ bool rule_loader::reader::read(rule_loader::configuration& cfg, collector& colle
{ {
std::vector<YAML::Node> docs; std::vector<YAML::Node> docs;
yaml_helper reader; yaml_helper reader;
std::string schema_validation; std::vector<std::string> schema_warnings;
rule_loader::context ctx(cfg.name); rule_loader::context ctx(cfg.name);
try try
{ {
docs = reader.loadall_from_string(cfg.content, schema, &schema_validation); docs = reader.loadall_from_string(cfg.content, schema, &schema_warnings);
} }
catch (YAML::ParserException& e) catch (YAML::ParserException& e)
{ {
@ -810,7 +810,7 @@ bool rule_loader::reader::read(rule_loader::configuration& cfg, collector& colle
cfg.res->add_error(falco::load_result::LOAD_ERR_YAML_PARSE, "unknown YAML parsing error", ctx); cfg.res->add_error(falco::load_result::LOAD_ERR_YAML_PARSE, "unknown YAML parsing error", ctx);
return false; return false;
} }
cfg.res->set_schema_validation_status(schema_validation); cfg.res->set_schema_validation_status(schema_warnings);
for (auto doc = docs.begin(); doc != docs.end(); doc++) for (auto doc = docs.begin(); doc != docs.end(); doc++)
{ {
if (doc->IsDefined() && !doc->IsNull()) if (doc->IsDefined() && !doc->IsNull())

View File

@ -90,27 +90,23 @@ public:
* Load all the YAML document represented by the input string. * Load all the YAML document represented by the input string.
* Since this is used by rule loader, does not process env vars. * Since this is used by rule loader, does not process env vars.
*/ */
std::vector<YAML::Node> loadall_from_string(const std::string& input, const nlohmann::json& schema={}, std::string *validation=nullptr) std::vector<YAML::Node> loadall_from_string(const std::string& input, const nlohmann::json& schema={}, std::vector<std::string> *schema_warnings=nullptr)
{ {
auto nodes = YAML::LoadAll(input); auto nodes = YAML::LoadAll(input);
if (validation) if (schema_warnings)
{ {
schema_warnings->clear();
if(!schema.empty()) if(!schema.empty())
{ {
// Validate each node. // Validate each node.
for(const auto& node : nodes) for(const auto& node : nodes)
{ {
*validation = validate_node(node, schema); validate_node(node, schema, schema_warnings);
if (*validation != validation_ok)
{
// Return first error
break;
}
} }
} }
else else
{ {
*validation = validation_none; schema_warnings->push_back(validation_none);
} }
} }
return nodes; return nodes;
@ -119,20 +115,21 @@ public:
/** /**
* Load the YAML document represented by the input string. * Load the YAML document represented by the input string.
*/ */
void load_from_string(const std::string& input, const nlohmann::json& schema={}, std::string *validation=nullptr) void load_from_string(const std::string& input, const nlohmann::json& schema={}, std::vector<std::string> *schema_warnings=nullptr)
{ {
m_root = YAML::Load(input); m_root = YAML::Load(input);
pre_process_env_vars(m_root); pre_process_env_vars(m_root);
if (validation) if (schema_warnings)
{ {
schema_warnings->clear();
if(!schema.empty()) if(!schema.empty())
{ {
*validation = validate_node(m_root, schema); validate_node(m_root, schema, schema_warnings);
} }
else else
{ {
*validation = validation_none; schema_warnings->push_back(validation_none);
} }
} }
} }
@ -140,14 +137,14 @@ public:
/** /**
* Load the YAML document from the given file path. * Load the YAML document from the given file path.
*/ */
void load_from_file(const std::string& path, const nlohmann::json& schema={}, std::string *validation=nullptr) void load_from_file(const std::string& path, const nlohmann::json& schema={}, std::vector<std::string> *schema_warnings=nullptr)
{ {
m_root = load_from_file_int(path, schema, validation); m_root = load_from_file_int(path, schema, schema_warnings);
} }
void include_config_file(const std::string& include_file_path, const nlohmann::json& schema={}, std::string *validation=nullptr) void include_config_file(const std::string& include_file_path, const nlohmann::json& schema={}, std::vector<std::string> *schema_warnings=nullptr)
{ {
auto loaded_nodes = load_from_file_int(include_file_path, schema, validation); auto loaded_nodes = load_from_file_int(include_file_path, schema, schema_warnings);
for(auto n : loaded_nodes) for(auto n : loaded_nodes)
{ {
/* /*
@ -243,26 +240,27 @@ public:
private: private:
YAML::Node m_root; YAML::Node m_root;
YAML::Node load_from_file_int(const std::string& path, const nlohmann::json& schema={}, std::string *validation=nullptr) YAML::Node load_from_file_int(const std::string& path, const nlohmann::json& schema, std::vector<std::string> *schema_warnings)
{ {
auto root = YAML::LoadFile(path); auto root = YAML::LoadFile(path);
pre_process_env_vars(root); pre_process_env_vars(root);
if (validation) if (schema_warnings)
{ {
schema_warnings->clear();
if(!schema.empty()) if(!schema.empty())
{ {
*validation = validate_node(root, schema); validate_node(root, schema, schema_warnings);
} }
else else
{ {
*validation = validation_none; schema_warnings->push_back(validation_none);
} }
} }
return root; return root;
} }
std::string validate_node(const YAML::Node &node, const nlohmann::json& schema={}) void validate_node(const YAML::Node &node, const nlohmann::json& schema, std::vector<std::string> *schema_warnings)
{ {
// Validate the yaml against our json schema // Validate the yaml against our json schema
valijson::Schema schemaDef; valijson::Schema schemaDef;
@ -277,16 +275,18 @@ private:
{ {
valijson::ValidationResults::Error error; valijson::ValidationResults::Error error;
// report only the top-most error // report only the top-most error
if (validationResults.popError(error)) while (validationResults.popError(error))
{ {
return std::string(validation_failed + " for ") schema_warnings->push_back(std::string(validation_failed + " for ")
+ std::accumulate(error.context.begin(), error.context.end(), std::string("")) + std::accumulate(error.context.begin(), error.context.end(), std::string(""))
+ ": " + ": "
+ error.description; + error.description);
} }
return validation_failed;
} }
return validation_ok; else
{
schema_warnings->push_back(validation_ok);
}
} }
/* /*

View File

@ -93,12 +93,13 @@ falco_configuration::falco_configuration():
config_loaded_res falco_configuration::init_from_content(const std::string& config_content, const std::vector<std::string>& cmdline_options, const std::string& filename) config_loaded_res falco_configuration::init_from_content(const std::string& config_content, const std::vector<std::string>& cmdline_options, const std::string& filename)
{ {
config_loaded_res res; config_loaded_res res;
std::string validation_status; std::vector<std::string> validation_status;
m_config.load_from_string(config_content, m_config_schema, &validation_status); m_config.load_from_string(config_content, m_config_schema, &validation_status);
init_cmdline_options(cmdline_options); init_cmdline_options(cmdline_options);
res[filename] = validation_status; // Only report top most schema validation status
res[filename] = validation_status[0];
load_yaml(filename); load_yaml(filename);
return res; return res;
@ -107,7 +108,7 @@ config_loaded_res falco_configuration::init_from_content(const std::string& conf
config_loaded_res falco_configuration::init_from_file(const std::string& conf_filename, const std::vector<std::string> &cmdline_options) config_loaded_res falco_configuration::init_from_file(const std::string& conf_filename, const std::vector<std::string> &cmdline_options)
{ {
config_loaded_res res; config_loaded_res res;
std::string validation_status; std::vector<std::string> validation_status;
try try
{ {
m_config.load_from_file(conf_filename, m_config_schema, &validation_status); m_config.load_from_file(conf_filename, m_config_schema, &validation_status);
@ -119,7 +120,8 @@ config_loaded_res falco_configuration::init_from_file(const std::string& conf_fi
} }
init_cmdline_options(cmdline_options); init_cmdline_options(cmdline_options);
res[conf_filename] = validation_status; // Only report top most schema validation status
res[conf_filename] = validation_status[0];
merge_config_files(conf_filename, res); merge_config_files(conf_filename, res);
load_yaml(conf_filename); load_yaml(conf_filename);
@ -138,7 +140,7 @@ std::string falco_configuration::dump()
// filenames and folders specified in config (minus the skipped ones). // filenames and folders specified in config (minus the skipped ones).
void falco_configuration::merge_config_files(const std::string& config_name, config_loaded_res &res) void falco_configuration::merge_config_files(const std::string& config_name, config_loaded_res &res)
{ {
std::string validation_status; std::vector<std::string> validation_status;
m_loaded_configs_filenames.push_back(config_name); m_loaded_configs_filenames.push_back(config_name);
const auto ppath = std::filesystem::path(config_name); const auto ppath = std::filesystem::path(config_name);
// Parse files to be included // Parse files to be included
@ -161,7 +163,8 @@ void falco_configuration::merge_config_files(const std::string& config_name, con
{ {
m_loaded_configs_filenames.push_back(include_file); m_loaded_configs_filenames.push_back(include_file);
m_config.include_config_file(include_file_path.string(), m_config_schema, &validation_status); m_config.include_config_file(include_file_path.string(), m_config_schema, &validation_status);
res[include_file_path.string()] = validation_status; // Only report top most schema validation status
res[include_file_path.string()] = validation_status[0];
} }
else if (std::filesystem::is_directory(include_file_path)) else if (std::filesystem::is_directory(include_file_path))
{ {
@ -180,7 +183,8 @@ void falco_configuration::merge_config_files(const std::string& config_name, con
for (const auto &f : v) for (const auto &f : v)
{ {
m_config.include_config_file(f, m_config_schema, &validation_status); m_config.include_config_file(f, m_config_schema, &validation_status);
res[f] = validation_status; // Only report top most schema validation status
res[f] = validation_status[0];
} }
} }
} }