diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2018-09-12 17:42:44 +0000 |
---|---|---|
committer | Sara Golemon <sara.golemon@mongodb.com> | 2018-09-17 17:36:26 +0000 |
commit | 9011c8cff2985a1f60d558f62fe0d712b48b9b99 (patch) | |
tree | 11bb8004f38e4a0a3c8c98d51529197b62084828 | |
parent | 17af0c46faacd8bcd7dd99cfc668011e7911e18c (diff) | |
download | mongo-9011c8cff2985a1f60d558f62fe0d712b48b9b99.tar.gz |
SERVER-37099 Support canonicalizing options during options parse
4 files changed, 89 insertions, 30 deletions
diff --git a/src/mongo/util/options_parser/option_description.cpp b/src/mongo/util/options_parser/option_description.cpp index 4b0f80b4334..dde557ba508 100644 --- a/src/mongo/util/options_parser/option_description.cpp +++ b/src/mongo/util/options_parser/option_description.cpp @@ -265,5 +265,10 @@ OptionDescription& OptionDescription::format(const std::string& regexFormat, return addConstraint(new StringFormatKeyConstraint(_dottedName, regexFormat, displayFormat)); } +OptionDescription& OptionDescription::canonicalize(Canonicalize_t canonicalize) { + _canonicalize = std::move(canonicalize); + return *this; +} + } // namespace optionenvironment } // namespace mongo diff --git a/src/mongo/util/options_parser/option_description.h b/src/mongo/util/options_parser/option_description.h index 98f9ca06e7e..29b340082a3 100644 --- a/src/mongo/util/options_parser/option_description.h +++ b/src/mongo/util/options_parser/option_description.h @@ -193,6 +193,13 @@ public: OptionDescription& format(const std::string& regexFormat, const std::string& displayFormat); /** + * Specifies that this option should be canonicalized immediately after initial parse. + * Callback may alter the contents of the setting, rename the key its stored to, etc... + */ + using Canonicalize_t = std::function<Status(Environment*)>; + OptionDescription& canonicalize(Canonicalize_t); + + /** * Adds a constraint for this option. During parsing, this Constraint will be added to the * result Environment, ensuring that it will get checked when the environment is validated. * See the documentation on the Constraint and Environment classes for more details. @@ -229,6 +236,9 @@ public: std::vector<std::string> _deprecatedDottedNames; // Deprecated single names - aliases for '_singleName'. std::vector<std::string> _deprecatedSingleNames; + + // Canonicalizer method. + Canonicalize_t _canonicalize; }; } // namespace optionenvironment diff --git a/src/mongo/util/options_parser/options_parser.cpp b/src/mongo/util/options_parser/options_parser.cpp index af8465de28c..75c27f2c4ae 100644 --- a/src/mongo/util/options_parser/options_parser.cpp +++ b/src/mongo/util/options_parser/options_parser.cpp @@ -493,7 +493,7 @@ StatusWith<YAML::Node> runYAMLExpansion(const YAML::Node& node, Status YAMLNodeToValue(const YAML::Node& YAMLNode, const std::vector<OptionDescription>& options_vector, const Key& key, - Key* canonicalKey, + OptionDescription const** option, Value* value, const OptionsParser::ConfigExpand& configExpand) { bool isRegistered = false; @@ -525,7 +525,7 @@ Status YAMLNodeToValue(const YAML::Node& YAMLNode, if (key == iterator->_dottedName || isDeprecated) { isRegistered = true; type = iterator->_type; - *canonicalKey = iterator->_dottedName; + *option = &*iterator; if (isDeprecated) { warning() << "Option: " << key << " is deprecated. Please use " << iterator->_dottedName << " instead."; @@ -629,11 +629,17 @@ Status YAMLNodeToValue(const YAML::Node& YAMLNode, return stringToValue(stringVal, type, key, value); } +Status canonicalizeOption(const OptionDescription& option, Environment* env) { + if (!option._canonicalize) { + return Status::OK(); + } + + return option._canonicalize(env); +} + Status checkLongName(const po::variables_map& vm, const std::string& singleName, - const std::string& canonicalSingleName, - const std::string& dottedName, - const OptionType& type, + const OptionDescription& option, Environment* environment, bool* optionAdded) { // Trim off the short option from our name so we can look it up correctly in our map @@ -654,22 +660,22 @@ Status checkLongName(const po::variables_map& vm, } if (vm.count(long_name)) { - if (!vm[long_name].defaulted() && singleName != canonicalSingleName) { + if (!vm[long_name].defaulted() && singleName != option._singleName) { warning() << "Option: " << singleName << " is deprecated. Please use " - << canonicalSingleName << " instead."; + << option._singleName << " instead."; } else if (long_name == "sslMode") { warning() << "Option: sslMode is deprecated. Please use tlsMode instead."; } Value optionValue; - Status ret = boostAnyToValue(vm[long_name].value(), type, long_name, &optionValue); + Status ret = boostAnyToValue(vm[long_name].value(), option._type, long_name, &optionValue); if (!ret.isOK()) { return ret; } // If this is really a StringMap, try to split on "key=value" for each element // in our StringVector - if (type == StringMap) { + if (option._type == StringMap) { StringVector_t keyValueVector; ret = optionValue.get(&keyValueVector); if (!ret.isOK()) { @@ -689,7 +695,7 @@ Status checkLongName(const po::variables_map& vm, // Make sure we aren't setting an option to two different values if (mapValue.count(key) > 0 && mapValue[key] != value) { StringBuilder sb; - sb << "Key Value Option: " << dottedName + sb << "Key Value Option: " << option._dottedName << " has a duplicate key from the same source: " << key; return Status(ErrorCodes::BadValue, sb.str()); } @@ -698,7 +704,15 @@ Status checkLongName(const po::variables_map& vm, optionValue = Value(mapValue); } if (!(*optionAdded)) { - environment->set(dottedName, optionValue).transitional_ignore(); + auto ret = environment->set(option._dottedName, optionValue); + if (!ret.isOK()) { + return ret; + } + + ret = canonicalizeOption(option, environment); + if (!ret.isOK()) { + return ret; + } } else if (!vm[long_name].defaulted()) { StringBuilder sb; sb << "Error parsing command line: Multiple occurrences of option \"" << long_name @@ -727,13 +741,7 @@ Status addBoostVariablesToEnvironment(const po::variables_map& vm, for (const OptionDescription& od : options_vector) { bool optionAdded = false; - ret = checkLongName(vm, - od._singleName, - od._singleName, - od._dottedName, - od._type, - environment, - &optionAdded); + ret = checkLongName(vm, od._singleName, od, environment, &optionAdded); if (!ret.isOK()) { return ret; @@ -741,13 +749,7 @@ Status addBoostVariablesToEnvironment(const po::variables_map& vm, for (const std::string& deprecatedSingleName : od._deprecatedSingleNames) { - ret = checkLongName(vm, - deprecatedSingleName, - od._singleName, - od._dottedName, - od._type, - environment, - &optionAdded); + ret = checkLongName(vm, deprecatedSingleName, od, environment, &optionAdded); if (!ret.isOK()) { return ret; @@ -836,26 +838,32 @@ Status addYAMLNodesToEnvironment(const YAML::Node& root, return ret; } } else { - Key canonicalKey; + OptionDescription const* option = nullptr; Value optionValue; Status ret = YAMLNodeToValue( - YAMLNode, options_vector, dottedName, &canonicalKey, &optionValue, expand); + YAMLNode, options_vector, dottedName, &option, &optionValue, expand); if (!ret.isOK()) { return ret; } + invariant(option); Value dummyVal; - if (environment->get(canonicalKey, &dummyVal).isOK()) { + if (environment->get(option->_dottedName, &dummyVal).isOK()) { StringBuilder sb; sb << "Error parsing YAML config: duplicate key: " << dottedName - << "(canonical key: " << canonicalKey << ")"; + << "(canonical key: " << option->_dottedName << ")"; return Status(ErrorCodes::BadValue, sb.str()); } // Only add the value if it is not empty. YAMLNodeToValue will set the // optionValue to an empty Value if we should not set it in the Environment. if (!optionValue.isEmpty()) { - ret = environment->set(canonicalKey, optionValue); + ret = environment->set(option->_dottedName, optionValue); + if (!ret.isOK()) { + return ret; + } + + ret = canonicalizeOption(*option, environment); if (!ret.isOK()) { return ret; } @@ -911,6 +919,11 @@ Status addCompositions(const OptionSection& options, const Environment& source, if (!ret.isOK()) { return ret; } + + ret = canonicalizeOption(*iterator, dest); + if (!ret.isOK()) { + return ret; + } } } else if (iterator->_type == StringMap) { StringMap_t sourceValue; @@ -942,6 +955,11 @@ Status addCompositions(const OptionSection& options, const Environment& source, if (!ret.isOK()) { return ret; } + + ret = canonicalizeOption(*iterator, dest); + if (!ret.isOK()) { + return ret; + } } } else { StringBuilder sb; diff --git a/src/mongo/util/options_parser/options_parser_test.cpp b/src/mongo/util/options_parser/options_parser_test.cpp index beeea6c6448..1da9570f41d 100644 --- a/src/mongo/util/options_parser/options_parser_test.cpp +++ b/src/mongo/util/options_parser/options_parser_test.cpp @@ -4721,6 +4721,32 @@ void TestFile(std::vector<unsigned char> contents, bool valid) { } } +TEST(YAMLConfigFile, canonicalize) { + moe::OptionSection opts; + opts.addOptionChaining("net.bindIpAll", "bind_ip_all", moe::Switch, "Bind all addresses") + .incompatibleWith("net.bindIp") + .canonicalize([](moe::Environment* env) { + auto status = env->remove("net.bindIpAll"); + if (!status.isOK()) { + return status; + } + return env->set("net.bindIp", moe::Value("0.0.0.0")); + }); + opts.addOptionChaining("net.bindIp", "bind_ip", moe::String, "Bind specific addresses") + .incompatibleWith("net.bindIpAll"); + + moe::OptionsParser parser; + moe::Environment env; + std::vector<std::string> argv = { + "binary", "--bind_ip_all", + }; + std::map<std::string, std::string> env_map; + ASSERT_OK(parser.run(opts, argv, env_map, &env)); + ASSERT_EQ(env.count("net.bindIp"), 1); + ASSERT_EQ(env.count("net.bindIpAll"), 0); + ASSERT_EQ(env["net.bindIp"].as<std::string>(), "0.0.0.0"); +} + #if defined(_WIN32) // Positive: Validate a UTF-16 file with a BOM can be parsed TEST(YAMLConfigFile, UTF16WithBOMFile) { |