diff --git a/unit_tests/falco/test_configuration.cpp b/unit_tests/falco/test_configuration.cpp index eee3873e..4c0d936e 100644 --- a/unit_tests/falco/test_configuration.cpp +++ b/unit_tests/falco/test_configuration.cpp @@ -126,14 +126,6 @@ TEST(Configuration, configuration_config_files_secondary_fail) "foo2: bar2\n" "base_value_2:\n" " id: 2\n"; - const std::string conf_yaml_3 = - "foo3: bar3\n" - "base_value_3:\n" - " id: 3\n" - " name: foo3\n"; - const std::string conf_yaml_4 = - "base_value_4:\n" - " id: 4\n"; std::ofstream outfile("main.yaml"); outfile << main_conf_yaml; @@ -143,21 +135,11 @@ TEST(Configuration, configuration_config_files_secondary_fail) outfile << conf_yaml_2; outfile.close(); - outfile.open("conf_3.yaml"); - outfile << conf_yaml_3; - outfile.close(); - - outfile.open("conf_4.yaml"); - outfile << conf_yaml_4; - outfile.close(); - yaml_helper conf; ASSERT_ANY_THROW(conf.load_from_file("main.yaml")); std::filesystem::remove("main.yaml"); std::filesystem::remove("conf_2.yaml"); - std::filesystem::remove("conf_3.yaml"); - std::filesystem::remove("conf_4.yaml"); } TEST(Configuration, configuration_config_files_ok) @@ -219,6 +201,7 @@ TEST(Configuration, configuration_config_files_ok) ASSERT_EQ(conf.get_scalar("base_value_3.id", 0), 3); ASSERT_TRUE(conf.is_defined("base_value_3.name")); ASSERT_EQ(conf.get_scalar("base_value_3.name", ""), "foo3"); + ASSERT_FALSE(conf.is_defined("base_value_4.id")); // conf_4 is not included std::filesystem::remove("main.yaml"); std::filesystem::remove("conf_2.yaml"); @@ -284,6 +267,7 @@ TEST(Configuration, configuration_config_files_relative_main) std::filesystem::remove(temp_main.string()); std::filesystem::remove("conf_2.yaml"); + std::filesystem::remove("conf_3.yaml"); } TEST(Configuration, configuration_config_files_override) @@ -448,6 +432,73 @@ TEST(Configuration, configuration_config_files_self) std::filesystem::remove("main.yaml"); } +TEST(Configuration, configuration_config_files_directory) +{ + /* + * Test that when main config file includes a config directory, + * the config directory is parsed in lexicographic order, + * and only regular files are parsed. + */ + // Main config includes whole temp directory + const std::string main_conf_yaml = + yaml_helper::configs_key + ": " + std::filesystem::temp_directory_path().string() + "/test\n" + "foo: bar\n" + "base_value:\n" + " id: 1\n" + " name: foo\n"; + const std::string conf_yaml_2 = + "foo2: bar2\n" + "base_value_2:\n" + " id: 2\n"; + const std::string conf_yaml_3 = + "foo2: bar3\n" + "base_value_3:\n" + " id: 3\n" + " name: foo3\n"; + const std::string conf_yaml_4 = + "foo4: bar4\n"; + + std::filesystem::create_directory(std::filesystem::temp_directory_path() / "test"); + + std::ofstream outfile("main.yaml"); + outfile << main_conf_yaml; + outfile.close(); + + outfile.open(std::filesystem::temp_directory_path()/"test/conf_2.yaml"); + outfile << conf_yaml_2; + outfile.close(); + + outfile.open(std::filesystem::temp_directory_path()/"test/conf_3.yaml"); + outfile << conf_yaml_3; + outfile.close(); + + // Create a directory and create a config inside it. We will later check that it was not parsed + std::filesystem::create_directory(std::filesystem::temp_directory_path() / "test" / "foo"); + outfile.open(std::filesystem::temp_directory_path()/"test/foo/conf_4.yaml"); + outfile << conf_yaml_4; + outfile.close(); + + yaml_helper conf; + ASSERT_NO_THROW(conf.load_from_file("main.yaml")); + + ASSERT_TRUE(conf.is_defined("foo")); + ASSERT_EQ(conf.get_scalar("foo", ""), "bar"); + ASSERT_TRUE(conf.is_defined("base_value.id")); + ASSERT_EQ(conf.get_scalar("base_value.id", 0), 1); + ASSERT_TRUE(conf.is_defined("base_value.name")); + ASSERT_EQ(conf.get_scalar("base_value.name", ""), "foo"); + ASSERT_TRUE(conf.is_defined("base_value_2")); + ASSERT_EQ(conf.get_scalar("base_value_2.id", 0), 2); + ASSERT_TRUE(conf.is_defined("base_value_3.id")); + ASSERT_EQ(conf.get_scalar("base_value_3.id", 0), 3); + ASSERT_TRUE(conf.is_defined("foo2")); + ASSERT_EQ(conf.get_scalar("foo2", ""), "bar3"); + ASSERT_FALSE(conf.is_defined("foo4")); + + std::filesystem::remove("main"); + std::filesystem::remove_all(std::filesystem::temp_directory_path()/"test"); +} + TEST(Configuration, configuration_environment_variables) { // Set an environment variable for testing purposes diff --git a/userspace/falco/yaml_helper.h b/userspace/falco/yaml_helper.h index a4184224..e5ee51e0 100644 --- a/userspace/falco/yaml_helper.h +++ b/userspace/falco/yaml_helper.h @@ -116,31 +116,43 @@ public: throw std::runtime_error( "Config error: '" + configs_key + "' directive tried to recursively include main config file: " + path + "."); } - if (std::filesystem::exists(include_file_path) && std::filesystem::is_regular_file(include_file_path)) + if (std::filesystem::exists(include_file_path)) { - auto loaded_nodes = load_from_file_int(include_file_path.string()); - for(auto n : loaded_nodes) + if (std::filesystem::is_regular_file(include_file_path)) { - /* - * To avoid recursion hell, - * we don't support `configs_files` directives from included config files - * (that use load_from_file_int recursively). - */ - const auto &key = n.first.Scalar(); - if (key == configs_key) + include_config_file(include_file_path.string()); + } + else if (std::filesystem::is_directory(include_file_path)) + { + std::vector v; + const auto it_options = std::filesystem::directory_options::follow_directory_symlink + | std::filesystem::directory_options::skip_permission_denied; + for (auto const& dir_entry : std::filesystem::directory_iterator(include_file_path, it_options)) { - throw std::runtime_error( - "Config error: '" + configs_key + "' directive in included config file " + include_file + "."); + if (std::filesystem::is_regular_file(dir_entry.path())) + { + v.push_back(dir_entry.path().string()); + } + // We don't support nested directories + else + { + falco_logger::log(falco_logger::level::WARNING, "Included config file has wrong type: " + dir_entry.path().string()); + } + std::sort(v.begin(), v.end()); + for (const auto &f : v) + { + include_config_file(f); + } } - // We allow to override keys. - // We don't need to use `get_node()` here, - // since key is a top-level one. - m_root[key] = n.second; + } + else + { + falco_logger::log(falco_logger::level::WARNING, "Included config entry has wrong type: " + include_file_path.string()); } } else { - falco_logger::log(falco_logger::level::WARNING, "Included config file unexistent or wrong type: " + include_file_path.string()); + falco_logger::log(falco_logger::level::WARNING, "Included config entry unexistent: " + include_file_path.string()); } } } @@ -210,6 +222,29 @@ private: return root; } + void include_config_file(const std::string& include_file_path) + { + auto loaded_nodes = load_from_file_int(include_file_path); + for(auto n : loaded_nodes) + { + /* + * To avoid recursion hell, + * we don't support `configs_files` directives from included config files + * (that use load_from_file_int recursively). + */ + const auto &key = n.first.Scalar(); + if (key == configs_key) + { + throw std::runtime_error( + "Config error: '" + configs_key + "' directive in included config file " + include_file_path + "."); + } + // We allow to override keys. + // We don't need to use `get_node()` here, + // since key is a top-level one. + m_root[key] = n.second; + } + } + /* * When loading a yaml file, * we immediately pre process all scalar values through a visitor private API,