TEST_F(ConfigTests, test_strip_comments) { std::string json_comments = "// Comment\n // Comment //\n # Comment\n# Comment\n{\"options\":{}}"; // Test support for stripping C++ and hash style comments from config JSON. auto actual = json_comments; stripConfigComments(actual); std::string expected = "{\"options\":{}}\n"; EXPECT_EQ(actual, expected); // Make sure the config update source logic applies the stripping. EXPECT_TRUE(get().update({{"data", json_comments}})); }
Status FilesystemConfigPlugin::genPack(const std::string& name, const std::string& value, std::string& pack) { if (name == "*") { // The config requested a multi-pack. std::vector<std::string> paths; resolveFilePattern(value, paths); pt::ptree multi_pack; for (const auto& path : paths) { std::string content; if (!readFile(path, content)) { LOG(WARNING) << "Cannot read multi-pack file: " << path; continue; } // Assemble an intermediate property tree for simplified parsing. pt::ptree single_pack; stripConfigComments(content); try { std::stringstream json_stream; json_stream << content; pt::read_json(json_stream, single_pack); } catch (const pt::json_parser::json_parser_error& /* e */) { LOG(WARNING) << "Cannot read multi-pack JSON: " << path; continue; } multi_pack.put_child(fs::path(path).stem().string(), single_pack); } // We should have a property tree of pack content mimicking embedded // configuration packs, ready to parse as a string. std::ostringstream output; pt::write_json(output, multi_pack, false); pack = output.str(); if (pack.empty()) { return Status(1, "Multi-pack content empty"); } return Status(0); } boost::system::error_code ec; if (!fs::is_regular_file(value, ec) || ec.value() != errc::success) { return Status(1, value + " is not a valid path"); } return readFile(value, pack); }
Status Config::updateSource(const std::string& source, const std::string& json) { // Compute a 'synthesized' hash using the content before it is parsed. hashSource(source, json); // Remove all packs from this source. schedule_->removeAll(source); // Remove all files from this source. removeFiles(source); // load the config (source.second) into a pt::ptree pt::ptree tree; try { auto clone = json; stripConfigComments(clone); std::stringstream json_stream; json_stream << clone; pt::read_json(json_stream, tree); } catch (const pt::json_parser::json_parser_error& /* e */) { return Status(1, "Error parsing the config JSON"); } // extract the "schedule" key and store it as the main pack if (tree.count("schedule") > 0 && !Registry::external()) { auto& schedule = tree.get_child("schedule"); pt::ptree main_pack; main_pack.add_child("queries", schedule); addPack("main", source, main_pack); } if (tree.count("scheduledQueries") > 0 && !Registry::external()) { auto& scheduled_queries = tree.get_child("scheduledQueries"); pt::ptree queries; for (const std::pair<std::string, pt::ptree>& query : scheduled_queries) { auto query_name = query.second.get<std::string>("name", ""); if (query_name.empty()) { return Status(1, "Error getting name from legacy scheduled query"); } queries.add_child(query_name, query.second); } pt::ptree legacy_pack; legacy_pack.add_child("queries", queries); addPack("legacy_main", source, legacy_pack); } // extract the "packs" key into additional pack objects if (tree.count("packs") > 0 && !Registry::external()) { auto& packs = tree.get_child("packs"); for (const auto& pack : packs) { auto value = packs.get<std::string>(pack.first, ""); if (value.empty()) { // The pack is a JSON object, treat the content as pack data. addPack(pack.first, source, pack.second); } else { genPack(pack.first, source, value); } } } applyParsers(source, tree, false); return Status(0, "OK"); }
Status Config::updateSource(const std::string& name, const std::string& json) { // Compute a 'synthesized' hash using the content before it is parsed. hashSource(name, json); // load the config (source.second) into a pt::ptree pt::ptree tree; try { auto clone = json; stripConfigComments(clone); std::stringstream json_stream; json_stream << clone; pt::read_json(json_stream, tree); } catch (const pt::json_parser::json_parser_error& e) { return Status(1, "Error parsing the config JSON"); } // extract the "schedule" key and store it as the main pack if (tree.count("schedule") > 0 && !Registry::external()) { auto& schedule = tree.get_child("schedule"); pt::ptree main_pack; main_pack.add_child("queries", schedule); addPack("main", name, main_pack); } if (tree.count("scheduledQueries") > 0 && !Registry::external()) { auto& scheduled_queries = tree.get_child("scheduledQueries"); pt::ptree queries; for (const std::pair<std::string, pt::ptree>& query : scheduled_queries) { auto query_name = query.second.get<std::string>("name", ""); if (query_name.empty()) { return Status(1, "Error getting name from legacy scheduled query"); } queries.add_child(query_name, query.second); } pt::ptree legacy_pack; legacy_pack.add_child("queries", queries); addPack("legacy_main", name, legacy_pack); } // extract the "packs" key into additional pack objects if (tree.count("packs") > 0 && !Registry::external()) { auto& packs = tree.get_child("packs"); for (const auto& pack : packs) { auto value = packs.get<std::string>(pack.first, ""); if (value.empty()) { addPack(pack.first, name, pack.second); } else { PluginResponse response; PluginRequest request = { {"action", "genPack"}, {"name", pack.first}, {"value", value}}; Registry::call("config", request, response); if (response.size() == 0 || response[0].count(pack.first) == 0) { continue; } try { pt::ptree pack_tree; std::stringstream pack_stream; pack_stream << response[0][pack.first]; pt::read_json(pack_stream, pack_tree); addPack(pack.first, name, pack_tree); } catch (const pt::json_parser::json_parser_error& e) { LOG(WARNING) << "Error parsing the pack JSON: " << pack.first; } } } } // Iterate each parser. for (const auto& plugin : Registry::all("config_parser")) { std::shared_ptr<ConfigParserPlugin> parser = nullptr; try { parser = std::dynamic_pointer_cast<ConfigParserPlugin>(plugin.second); } catch (const std::bad_cast& e) { LOG(ERROR) << "Error casting config parser plugin: " << plugin.first; } if (parser == nullptr || parser.get() == nullptr) { continue; } // For each key requested by the parser, add a property tree reference. std::map<std::string, pt::ptree> parser_config; for (const auto& key : parser->keys()) { if (tree.count(key) > 0) { parser_config[key] = tree.get_child(key); } else { parser_config[key] = pt::ptree(); } } // The config parser plugin will receive a copy of each property tree for // each top-level-config key. The parser may choose to update the config's // internal state parser->update(name, parser_config); } return Status(0, "OK"); }
Status Config::updateSource(const std::string& name, const std::string& json) { // Compute a 'synthesized' hash using the content before it is parsed. hashSource(name, json); // Remove all packs from this source. schedule_->removeAll(name); // Remove all files from this source. removeFiles(name); // load the config (source.second) into a pt::ptree pt::ptree tree; try { auto clone = json; stripConfigComments(clone); std::stringstream json_stream; json_stream << clone; pt::read_json(json_stream, tree); } catch (const pt::json_parser::json_parser_error& e) { return Status(1, "Error parsing the config JSON"); } // extract the "schedule" key and store it as the main pack if (tree.count("schedule") > 0 && !Registry::external()) { auto& schedule = tree.get_child("schedule"); pt::ptree main_pack; main_pack.add_child("queries", schedule); addPack("main", name, main_pack); } if (tree.count("scheduledQueries") > 0 && !Registry::external()) { auto& scheduled_queries = tree.get_child("scheduledQueries"); pt::ptree queries; for (const std::pair<std::string, pt::ptree>& query : scheduled_queries) { auto query_name = query.second.get<std::string>("name", ""); if (query_name.empty()) { return Status(1, "Error getting name from legacy scheduled query"); } queries.add_child(query_name, query.second); } pt::ptree legacy_pack; legacy_pack.add_child("queries", queries); addPack("legacy_main", name, legacy_pack); } // extract the "packs" key into additional pack objects if (tree.count("packs") > 0 && !Registry::external()) { auto& packs = tree.get_child("packs"); for (const auto& pack : packs) { auto value = packs.get<std::string>(pack.first, ""); if (value.empty()) { // The pack is a JSON object, treat the content as pack data. addPack(pack.first, name, pack.second); } else { // If the pack value is a string (and not a JSON object) then it is a // resource to be handled by the config plugin. PluginResponse response; PluginRequest request = { {"action", "genPack"}, {"name", pack.first}, {"value", value}}; Registry::call("config", request, response); if (response.size() == 0 || response[0].count(pack.first) == 0) { continue; } try { pt::ptree pack_tree; std::stringstream pack_stream; pack_stream << response[0][pack.first]; pt::read_json(pack_stream, pack_tree); addPack(pack.first, name, pack_tree); } catch (const pt::json_parser::json_parser_error& e) { LOG(WARNING) << "Error parsing the pack JSON: " << pack.first; } } } } applyParsers(name, tree, false); return Status(0, "OK"); }