summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2018-09-12 17:42:44 +0000
committerSara Golemon <sara.golemon@mongodb.com>2018-09-17 17:36:26 +0000
commit9011c8cff2985a1f60d558f62fe0d712b48b9b99 (patch)
tree11bb8004f38e4a0a3c8c98d51529197b62084828
parent17af0c46faacd8bcd7dd99cfc668011e7911e18c (diff)
downloadmongo-9011c8cff2985a1f60d558f62fe0d712b48b9b99.tar.gz
SERVER-37099 Support canonicalizing options during options parse
-rw-r--r--src/mongo/util/options_parser/option_description.cpp5
-rw-r--r--src/mongo/util/options_parser/option_description.h10
-rw-r--r--src/mongo/util/options_parser/options_parser.cpp78
-rw-r--r--src/mongo/util/options_parser/options_parser_test.cpp26
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) {