void MySQLRouterConf::prepare_command_options()

in router/src/router/src/router_conf.cc [212:1062]


void MySQLRouterConf::prepare_command_options(
    CmdArgHandler &arg_handler, const std::string &bootstrap_uri) noexcept {
  using OptionNames = CmdOption::OptionNames;

  bootstrap_uri_ = bootstrap_uri;

  arg_handler.add_option(
      OptionNames({"--account"}),
      "Account (username) to be used by Router when talking to cluster."
      " (bootstrap)",
      CmdOptionValueReq::required, "account",
      [this](const std::string &username) {
        if (username.empty())
          throw std::runtime_error(
              "Value for --account option cannot be empty");
        if (this->bootstrap_options_.count("account"))
          throw std::runtime_error("Option --account can only be given once");
        this->bootstrap_options_["account"] = username;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--account");
      });

  arg_handler.add_option(
      OptionNames({"--account-create"}),
      "Specifies account creation policy (useful for guarding against "
      "accidentally bootstrapping using a wrong account). <mode> is one of:\n"
      "  'always'        - bootstrap only if account doesn't exist\n"
      "  'never'         - bootstrap only if account exists\n"
      "  'if-not-exists' - bootstrap either way (default)\n"
      "This option can only be used if option '--account' is also used.\n"
      "Argument 'never' cannot be used together with option "
      "'--account-host'\n"
      "(bootstrap)",
      CmdOptionValueReq::required, "mode",
      [this](const std::string &create) {
        if (create != "always" && create != "if-not-exists" &&
            create != "never")
          throw std::runtime_error(
              "Invalid value for --account-create option.  Valid values: "
              "always, if-not-exists, never");
        if (this->bootstrap_options_.count("account-create"))
          throw std::runtime_error(
              "Option --account-create can only be given once");
        this->bootstrap_options_["account-create"] = create;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--account-create");
        if (!this->bootstrap_options_.count("account"))
          throw std::runtime_error(
              "Option --account-create can only be used together with "
              "--account.");
      });

  arg_handler.add_option(
      OptionNames({"--account-host"}),
      "Host pattern to be used when creating Router's database user, "
      "default='%'. "
      "It can be used multiple times to provide multiple patterns. "
      "(bootstrap)",
      CmdOptionValueReq::required, "account-host",
      [this](const std::string &host_pattern) {
        std::vector<std::string> &hostnames =
            this->bootstrap_multivalue_options_["account-host"];
        hostnames.push_back(host_pattern);

        // sort and eliminate any non-unique hostnames; we do this to ensure
        // that CREATE USER does not get called twice for the same user@host
        // later on in the ConfigGenerator
        std::sort(hostnames.begin(), hostnames.end());
        auto it = std::unique(hostnames.begin(), hostnames.end());
        hostnames.resize(std::distance(hostnames.begin(), it));
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--account-host");
        const auto it = this->bootstrap_options_.find("account-create");
        if (it != this->bootstrap_options_.end() && it->second == "never")
          throw std::runtime_error(
              "Option '--account-create never' cannot be used together with "
              "'--account-host <host>'");
      });

  if (bootstrap_uri.empty()) {
    arg_handler.add_option(
        OptionNames({"-B", "--bootstrap"}),
        "Bootstrap and configure Router for operation with a MySQL InnoDB "
        "cluster.",
        CmdOptionValueReq::required, "server_url",
        [this](const std::string &server_url) {
          if (server_url.empty()) {
            throw std::runtime_error("Invalid value for --bootstrap/-B option");
          }
          this->bootstrap_uri_ = server_url;
        });
  }

  arg_handler.add_option(
      OptionNames({"--bootstrap-socket"}),
      "Bootstrap and configure Router via a Unix socket",
      CmdOptionValueReq::required, "socket_name",
      [this](const std::string &socket_name) {
        if (socket_name.empty()) {
          throw std::runtime_error(
              "Invalid value for --bootstrap-socket option");
        }

        this->save_bootstrap_option_not_empty("--bootstrap-socket",
                                              "bootstrap_socket", socket_name);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--bootstrap-socket");
      });

  arg_handler.add_option(
      OptionNames({"--client-ssl-cert"}),
      "name of a PEM file containing a SSL certificate used "
      "for accepting TLS connections between client and router",
      CmdOptionValueReq::required, "path",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--client-ssl-cert",
                                              "client_ssl_cert", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--client-ssl-cert");

        if (!bootstrap_options_["client_ssl_cert"].empty() &&
            bootstrap_options_["client_ssl_key"].empty()) {
          throw std::runtime_error(
              "If --client-ssl-cert is set, --client-ssl-key can't be empty.");
        }
      });

  arg_handler.add_option(
      OptionNames({"--client-ssl-cipher"}),
      "list of one or more colon separated cipher names used for accepting "
      "TLS connections between client and router",
      CmdOptionValueReq::required, "",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--client-ssl-cipher",
                                              "client_ssl_cipher", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--client-ssl-cipher");
      });

  arg_handler.add_option(
      OptionNames({"--client-ssl-curves"}),
      "list of one or more colon separated elliptic curve names used for "
      "accepting TLS connections between client and router",
      CmdOptionValueReq::required, "",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--client-ssl-curves",
                                              "client_ssl_curves", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--client-ssl-curves");
      });

  arg_handler.add_option(
      OptionNames({"--client-ssl-key"}),
      "name of a PEM file containing a SSL private key used "
      "for accepting TLS connections between client and router",
      CmdOptionValueReq::required, "path",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--client-ssl-key",
                                              "client_ssl_key", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--client-ssl-key");

        if (!bootstrap_options_["client_ssl_key"].empty() &&
            bootstrap_options_["client_ssl_cert"].empty()) {
          throw std::runtime_error(
              "If --client-ssl-key is set, --client-ssl-cert can't be empty.");
        }
      });

  arg_handler.add_option(
      OptionNames({"--client-ssl-mode"}),
      "SSL mode for connections from client to router. One "
      "of DISABLED, PREFERRED, REQUIRED or PASSTHROUGH.",
      CmdOptionValueReq::required, "mode",
      [this](const auto &value) {
        assert_one_of_ci("--client-ssl-mode", value,
                         {"DISABLED", "PREFERRED", "REQUIRED", "PASSTHROUGH"});

        this->save_bootstrap_option_not_empty(
            "--client-ssl-mode", "client_ssl_mode", make_upper(value));
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--client-ssl-mode");

        if (bootstrap_options_["client_ssl_mode"] == "PASSTHROUGH") {
          auto server_ssl_mode_it = bootstrap_options_.find("server_ssl_mode");
          if (server_ssl_mode_it != bootstrap_options_.end()) {
            if (server_ssl_mode_it->second != "AS_CLIENT") {
              throw std::runtime_error(
                  "--server-ssl-mode must be AS_CLIENT or not specified, if "
                  "--client-ssl-mode is PASSTHROUGH.");
            }
          }
        }
      });
  arg_handler.add_option(
      OptionNames({"--client-ssl-dh-params"}),
      "name of a PEM file containing DH paramaters",
      CmdOptionValueReq::required, "",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--client-ssl-dh-params",
                                              "client_ssl_dh_params", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--client-ssl-dh-params");
      });

  arg_handler.add_option(
      OptionNames({"--conf-base-port"}),
      "Base port to use for listening router ports. (bootstrap)",
      CmdOptionValueReq::required, "port",
      [this](const std::string &port) {
        this->bootstrap_options_["base-port"] = port;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-base-port");
      });

  arg_handler.add_option(
      OptionNames({"--conf-bind-address"}),
      "IP address of the interface to which router's listening sockets "
      "should bind. (bootstrap)",
      CmdOptionValueReq::required, "address",
      [this](const std::string &address) {
        this->bootstrap_options_["bind-address"] = address;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-bind-address");
      });

#ifndef _WIN32
  arg_handler.add_option(
      OptionNames({"--conf-skip-tcp"}),
      "Whether to disable binding of a TCP port for incoming connections. "
      "(bootstrap)",
      CmdOptionValueReq::none, "",
      [this](const std::string &) {
        this->bootstrap_options_["skip-tcp"] = "1";
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-skip-tcp");
      });
  arg_handler.add_option(
      OptionNames({"--conf-use-sockets"}),
      "Whether to use Unix domain sockets. (bootstrap)",
      CmdOptionValueReq::none, "",
      [this](const std::string &) {
        this->bootstrap_options_["use-sockets"] = "1";
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-use-sockets");
      });
#endif

  arg_handler.add_option(
      OptionNames({"--conf-skip-x-protocol"}),
      "Whether to disable routing of X protocol connections. "
      "(bootstrap)",
      CmdOptionValueReq::none, "",
      [this](const std::string &) {
        this->bootstrap_options_["skip-x-protocol"] = "1";
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-skip-x-protocol");
      });

  arg_handler.add_option(
      OptionNames({"--connect-timeout"}),
      "The time in seconds after which trying to connect to metadata server "
      "should timeout. It is used when bootstrapping and also written to the "
      "configuration file (bootstrap)",
      CmdOptionValueReq::optional, "",
      [this](const std::string &connect_timeout) {
        this->bootstrap_options_["connect-timeout"] = connect_timeout;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--connect-timeout");
      });

  arg_handler.add_option(
      OptionNames({"--conf-use-gr-notifications"}),
      "Whether to enable handling of cluster state change GR notifications.",
      CmdOptionValueReq::optional, "",
      [this](const std::string &value) {
        if (value == "0" || value == "1") {
          this->bootstrap_options_["use-gr-notifications"] = value;
        } else if (value.empty()) {
          this->bootstrap_options_["use-gr-notifications"] = "1";
        } else {
          throw std::runtime_error(
              "Value for parameter '--conf-use-gr-notifications' needs to be "
              "one of: ['0', '1']");
        }
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-use-gr-notifications");
      });

  arg_handler.add_option(
      OptionNames({"--conf-target-cluster"}),
      "Router's target Cluster from the ClusterSet('current' or 'primary').",
      CmdOptionValueReq::required, "",
      [this](const std::string &value) {
        if (this->bootstrap_options_.count("target-cluster-by-name") > 0) {
          throw std::runtime_error(
              "Parameters '--conf-target-cluster' and "
              "'--conf-target-cluster-by-name' are mutually exclusive and "
              "can't be used together");
        }

        std::string value_lowercase{value};
        std::transform(value_lowercase.begin(), value_lowercase.end(),
                       value_lowercase.begin(), ::tolower);

        if (value_lowercase != "primary" && value_lowercase != "current") {
          throw std::runtime_error(
              "Value for parameter '--conf-target-cluster' needs to be one of: "
              "['primary', 'current']");
        }

        this->bootstrap_options_["target-cluster"] = value_lowercase;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-target-cluster");
      });

  arg_handler.add_option(
      OptionNames({"--conf-target-cluster-by-name"}),
      "Name of the target Cluster for the Router when bootstrapping against "
      "the ClusterSet",
      CmdOptionValueReq::required, "",
      [this](const std::string &value) {
        if (this->bootstrap_options_.count("target-cluster") > 0) {
          throw std::runtime_error(
              "Parameters '--conf-target-cluster' and "
              "'--conf-target-cluster-by-name' are mutually exclusive and "
              "can't be used together");
        }
        if (value.empty()) {
          throw std::runtime_error(
              "Value for parameter '--conf-target-cluster-by-name' can't be "
              "empty");
        }
        this->bootstrap_options_["target-cluster-by-name"] = value;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-target-cluster-by-name");
      });

  arg_handler.add_option(
      OptionNames({"--conf-local-cluster"}),
      "Name of the local cluster used for routing purposes",
      CmdOptionValueReq::required, "",
      [this](const std::string &value) {
        if (value.empty()) {
          throw std::runtime_error(
              "Value for parameter '--conf-local-cluster' can't be empty");
        }
        this->bootstrap_options_["local-cluster"] = value;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-local-cluster");
      });

  arg_handler.add_option(
      OptionNames({"-d", "--directory"}),
      "Creates a self-contained directory for a new instance of the Router. "
      "(bootstrap)",
      CmdOptionValueReq::required, "directory",
      [this](const std::string &path) {
        if (path.empty()) {
          throw std::runtime_error("Invalid value for --directory option");
        }
        this->bootstrap_directory_ = path;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("-d/--directory");
      });

  arg_handler.add_option(
      OptionNames({"--force"}),
      "Force reconfiguration of a possibly existing instance of the router. "
      "(bootstrap)",
      CmdOptionValueReq::none, "",
      [this](const std::string &) { this->bootstrap_options_["force"] = "1"; },
      [this](const std::string &) { this->assert_bootstrap_mode("--force"); });

  arg_handler.add_option(
      OptionNames({"--force-password-validation"}),
      "When autocreating database account do not use HASHED password - this is "
      "the default behavior now, this parameter is not needed, it is kept for "
      "backward compatibility."
      "(bootstrap)",
      CmdOptionValueReq::none, "",
      [](const std::string &) {
        // this is now always assumed, so this parameter is ignored, kept for
        // backward compatibility
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--force-password-validation");
      });

  arg_handler.add_option(
      OptionNames({"--master-key-reader"}),
      "The tool that can be used to read master key, it has to be used "
      "together with --master-key-writer. (bootstrap)",
      CmdOptionValueReq::required, "",
      [this](const std::string &master_key_reader) {
        this->keyring_info_.set_master_key_reader(master_key_reader);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--master-key-reader");
        if (this->keyring_info_.get_master_key_reader().empty() !=
            this->keyring_info_.get_master_key_writer().empty())
          throw std::runtime_error(
              "Option --master-key-reader can only be used together with "
              "--master-key-writer.");
      });

  arg_handler.add_option(
      OptionNames({"--master-key-writer"}),
      "The tool that can be used to store master key, it has to be used "
      "together with --master-key-reader. (bootstrap)",
      CmdOptionValueReq::required, "",
      [this](const std::string &master_key_writer) {
        this->keyring_info_.set_master_key_writer(master_key_writer);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--master-key-writer");
        if (this->keyring_info_.get_master_key_reader().empty() !=
            this->keyring_info_.get_master_key_writer().empty())
          throw std::runtime_error(
              "Option --master-key-writer can only be used together with "
              "--master-key-reader.");
      });

  arg_handler.add_option(
      OptionNames({"--name"}),
      "Gives a symbolic name for the router instance. (bootstrap)",
      CmdOptionValueReq::optional, "name",
      [this](const std::string &name) {
        this->bootstrap_options_["name"] = name;
      },
      [this](const std::string &) { this->assert_bootstrap_mode("--name"); });

  arg_handler.add_option(
      OptionNames({"--password-retries"}),
      "Number of the retries for generating the router's user password. "
      "(bootstrap)",
      CmdOptionValueReq::optional, "password-retries",
      [this](const std::string &retries) {
        this->bootstrap_options_["password-retries"] = retries;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--password-retries");
      });

  arg_handler.add_option(
      OptionNames({"--read-timeout"}),
      "The time in seconds after which reads from metadata server should "
      "timeout. It is used when bootstrapping and is also written to "
      "configuration file. (bootstrap)",
      CmdOptionValueReq::optional, "",
      [this](const std::string &read_timeout) {
        this->bootstrap_options_["read-timeout"] = read_timeout;
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--read-timeout");
      });
  arg_handler.add_option(
      OptionNames({"--report-host"}),
      "Host name of this computer (it will be queried from OS if not "
      "provided). "
      "It is used as suffix (the part after '@') in Router's database user "
      "name; "
      "should match host name as seen by the cluster nodes (bootstrap)",
      CmdOptionValueReq::required, "report-host",
      [this](const std::string &hostname) {
        if (!mysql_harness::is_valid_hostname(hostname.c_str()))
          throw std::runtime_error(
              "Option --report-host has an invalid value.");

        auto pr = this->bootstrap_options_.insert({"report-host", hostname});
        if (pr.second == false)
          throw std::runtime_error(
              "Option --report-host can only be used once.");
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--report-host");
      });

  arg_handler.add_option(
      OptionNames({"--server-ssl-ca"}),
      "path name of the Certificate Authority (CA) certificate file in PEM "
      "format. Used when forwarding a client connection from router to a "
      "server.",
      CmdOptionValueReq::required, "path",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--server-ssl-ca",
                                              "server_ssl_ca", value);
      },
      [this](const auto &) { this->assert_bootstrap_mode("--server-ssl-ca"); });

  arg_handler.add_option(
      OptionNames({"--server-ssl-capath"}),
      "path name of the directory that contains trusted SSL Certificate "
      "Authority (CA) certificate files in PEM format. Used when forwarding "
      "a client connection from router to a server.",
      CmdOptionValueReq::required, "directory",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--server-ssl-capath",
                                              "server_ssl_capath", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--server-ssl-capath");
      });

  arg_handler.add_option(
      OptionNames({"--server-ssl-cipher"}),
      "list of one or more colon separated cipher names. Used when "
      "forwarding "
      "client connection from router to a server.",
      CmdOptionValueReq::required, "",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--server-ssl-cipher",
                                              "server_ssl_cipher", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--server-ssl-cipher");
      });

  arg_handler.add_option(
      OptionNames({"--server-ssl-crl"}),
      "path name of the file containing certificate revocation lists in PEM "
      "format. Used when forwarding a client connection from router to a "
      "server.",
      CmdOptionValueReq::required, "path",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--server-ssl-crl",
                                              "server_ssl_crl", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--server-ssl-crl");
      });

  arg_handler.add_option(
      OptionNames({"--server-ssl-crlpath"}),
      "path name of the directory that contains certificate revocation-list "
      "files in PEM format. Used when forwarding a client connection from "
      "router to a server.",
      CmdOptionValueReq::required, "directory",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--server-ssl-crlpath",
                                              "server_ssl_crlpath", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--server-ssl-crlpath");
      });

  arg_handler.add_option(
      OptionNames({"--server-ssl-curves"}),
      "list of one or more colon separated elliptic curve names. Used when "
      "forwarding a client connection from router to a server.",
      CmdOptionValueReq::required, "",
      [this](const auto &value) {
        this->save_bootstrap_option_not_empty("--server-ssl-curves",
                                              "server_ssl_curves", value);
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--server-ssl-curves");
      });

  arg_handler.add_option(
      OptionNames({"--server-ssl-mode"}),
      "SSL mode to use when forwarding a client connection from router to a "
      "server. One of DISABLED, PREFERRED, REQUIRED or AS_CLIENT.",
      CmdOptionValueReq::required, "ssl-mode",
      [this](const auto &value) {
        assert_one_of_ci("--server-ssl-mode", value,
                         {"DISABLED", "PREFERRED", "REQUIRED", "AS_CLIENT"});

        this->save_bootstrap_option_not_empty(
            "--server-ssl-mode", "server_ssl_mode", make_upper(value));
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--server-ssl-mode");
      });

  arg_handler.add_option(
      OptionNames({"--server-ssl-verify"}),
      "verification mode when forwarding a client connection from router to "
      "server. One of DISABLED, VERIFY_CA or VERIFY_IDENTITY.",
      CmdOptionValueReq::required, "verify-mode",
      [this](const auto &value) {
        assert_one_of_ci("--server-ssl-verify", value,
                         {"DISABLED", "VERIFY_CA", "VERIFY_IDENTITY"});

        this->save_bootstrap_option_not_empty(
            "--server-ssl-verify", "server_ssl_verify", make_upper(value));
      },
      [this](const auto &) {
        this->assert_bootstrap_mode("--server-ssl-verify");
      });

  arg_handler.add_option(
      OptionNames({"--ssl-ca"}),
      "Path to SSL CA file to verify server's certificate against when "
      "connecting to the metadata servers",
      CmdOptionValueReq::required, "path",
      [this](const std::string &path) {
        this->save_bootstrap_option_not_empty("--ssl-ca", "ssl_ca", path);
      },
      [this](const std::string &) { this->assert_bootstrap_mode("--ssl-ca"); });

  arg_handler.add_option(
      OptionNames({"--ssl-capath"}),
      "Path to directory containing SSL CA files to verify server's "
      "certificate against when connecting to the metadata servers.",
      CmdOptionValueReq::required, "directory",
      [this](const std::string &path) {
        this->save_bootstrap_option_not_empty("--ssl-capath", "ssl_capath",
                                              path);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--ssl-capath");
      });

  arg_handler.add_option(
      OptionNames({"--ssl-cert"}),
      "Path to a SSL certificate, to be used if client certificate "
      "verification is required when connecting to the metadata servers.",
      CmdOptionValueReq::required, "path",
      [this](const std::string &path) {
        this->save_bootstrap_option_not_empty("--ssl-cert", "ssl_cert", path);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--ssl-cert");
      });

  arg_handler.add_option(
      OptionNames({"--ssl-cipher"}),
      ": separated list of SSL ciphers to allow when connecting to the "
      "metadata servers, if SSL is enabled.",
      CmdOptionValueReq::required, "ciphers",
      [this](const std::string &cipher) {
        this->save_bootstrap_option_not_empty("--ssl-cipher", "ssl_cipher",
                                              cipher);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--ssl-cipher");
      });

  arg_handler.add_option(
      OptionNames({"--ssl-crl"}),
      "Path to SSL CRL file to use when connecting to metadata-servers and "
      "verifying their SSL certificate",
      CmdOptionValueReq::required, "path",
      [this](const std::string &path) {
        this->save_bootstrap_option_not_empty("--ssl-crl", "ssl_crl", path);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--ssl-crl");
      });

  arg_handler.add_option(
      OptionNames({"--ssl-crlpath"}),
      "Path to directory containing SSL CRL files to use when connecting to "
      "metadata-servers and verifying their SSL certificate.",
      CmdOptionValueReq::required, "directory",
      [this](const std::string &path) {
        this->save_bootstrap_option_not_empty("--ssl-crlpath", "ssl_crlpath",
                                              path);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--ssl-crlpath");
      });

  arg_handler.add_option(
      OptionNames({"--ssl-key"}),
      "Path to private key for client SSL certificate, to be used if client "
      "certificate verification is required when connecting to "
      "metadata-servers.",
      CmdOptionValueReq::required, "path",
      [this](const std::string &path) {
        this->save_bootstrap_option_not_empty("--ssl-key", "ssl_key", path);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--ssl-key");
      });

  arg_handler.add_option(
      OptionNames({"--disable-rw-split"}),
      "Do not generate routing section for RW Split endpoint",
      CmdOptionValueReq::none, "",
      [this](const std::string &) {
        this->bootstrap_options_["disable-rw-split"] = "1";
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--disable-rw-split");
      });

  arg_handler.add_option(
      OptionNames({"--disable-rest"}),
      "Disable REST web service for Router monitoring", CmdOptionValueReq::none,
      "",
      [this](const std::string &) {
        this->bootstrap_options_["disable-rest"] = "1";
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--disable-rest");
      });

  arg_handler.add_option(
      OptionNames({"--https-port"}),
      "HTTPS port for Router monitoring REST web service",
      CmdOptionValueReq::required, "https-port",
      [this](const std::string &https_port) {
        this->bootstrap_options_["https-port"] = https_port;
      },
      [this](const std::string &https_port) {
        this->assert_bootstrap_mode("--https-port");
        if (this->bootstrap_options_.count("disable-rest") != 0) {
          throw std::runtime_error(
              "Option --disable-rest is not allowed when using --https-port "
              "option");
        }
        try {
          assert_option_value_in_range(https_port, 1, 65535);
        } catch (const std::exception &e) {
          throw std::runtime_error{
              std::string{"processing --https-port option failed, "} +
              e.what()};
        }
      });

  char ssl_mode_vals[128];
  char ssl_mode_desc[384];
  snprintf(ssl_mode_vals, sizeof(ssl_mode_vals), "%s|%s|%s|%s|%s",
           MySQLSession::kSslModeDisabled, MySQLSession::kSslModePreferred,
           MySQLSession::kSslModeRequired, MySQLSession::kSslModeVerifyCa,
           MySQLSession::kSslModeVerifyIdentity);
  snprintf(ssl_mode_desc, sizeof(ssl_mode_desc),
           "SSL connection mode for use during bootstrap and normal operation, "
           "when connecting to the metadata server. Analogous to --ssl-mode in "
           "mysql client. One of %s. Default = %s. (bootstrap)",
           ssl_mode_vals, MySQLSession::kSslModePreferred);

  arg_handler.add_option(
      OptionNames({"--ssl-mode"}), ssl_mode_desc, CmdOptionValueReq::required,
      "mode",
      [this](const std::string &ssl_mode) {
        try {
          MySQLSession::parse_ssl_mode(
              ssl_mode);  // we only care if this succeeds
          bootstrap_options_["ssl_mode"] = ssl_mode;
        } catch (const std::logic_error &) {
          throw std::runtime_error("Invalid value for --ssl-mode option");
        }
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--ssl-mode");
      });

  arg_handler.add_option(
      OptionNames({"--strict"}),
      "Upgrades account verification failure warning into a fatal error. "
      "(bootstrap)",
      CmdOptionValueReq::none, "",
      [this](const std::string &) { this->bootstrap_options_["strict"] = "1"; },
      [this](const std::string &) { this->assert_bootstrap_mode("--strict"); });

  arg_handler.add_option(
      OptionNames({"--tls-version"}),
      ", separated list of TLS versions to request, if SSL is enabled.",
      CmdOptionValueReq::required, "versions",
      [this](const std::string &version) {
        this->save_bootstrap_option_not_empty("--tls-version", "tls_version",
                                              version);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--tls-version");
      });

  arg_handler.add_option(
      OptionNames({"--conf-set-option"}),
      "Allows forcing selected option in the configuration file when "
      "bootstrapping (--conf-set-option=section_name.option_name=value)",
      CmdOptionValueReq::required, "conf-set-option",
      [this](const std::string &conf_option) {
        std::vector<std::string> &conf_options =
            this->bootstrap_multivalue_options_["conf-set-option"];
        conf_options.push_back(conf_option);
      },
      [this](const std::string &) {
        this->assert_bootstrap_mode("--conf-set-option");
      });

// These are additional Windows-specific options, added (at the time of writing)
// in check_service_operations(). Grep after '--install-service' and you shall
// find.
#ifdef _WIN32
  arg_handler.add_option(
      CmdOption::OptionNames({"--clear-all-credentials"}),
      "Clear the vault, removing all the credentials stored on it",
      CmdOptionValueReq::none, "", [](const std::string &) {
        PasswordVault pv;
        pv.clear_passwords();
        log_info("Removed successfully all passwords from the vault.");
        throw silent_exception();
      });

  // in this context we only want the service-related options to be known and
  // displayed with --help; they are handled elsewhere (main-windows.cc)
  add_service_options(arg_handler);

  arg_handler.add_option(
      CmdOption::OptionNames({"--remove-credentials-section"}),
      "Removes the credentials for the given section",
      CmdOptionValueReq::required, "section_name",
      [](const std::string &value) {
        PasswordVault pv;
        pv.remove_password(value);
        pv.store_passwords();
        log_info("The password was removed successfully.");
        throw silent_exception();
      });

  arg_handler.add_option(
      CmdOption::OptionNames({"--update-credentials-section"}),
      "Updates the credentials for the given section",
      CmdOptionValueReq::required, "section_name",
      [](const std::string &value) {
        std::string prompt = string_format(
            "Enter password for config section '%s'", value.c_str());
        std::string pass = mysqlrouter::prompt_password(prompt);
        PasswordVault pv;
        pv.update_password(value, pass);
        pv.store_passwords();
        log_info("The password was stored in the vault successfully.");
        throw silent_exception();
      });
#endif
}