24#include <nlohmann/json.hpp>
97template<
typename AgentT>
99 static_assert(std::is_base_of_v<Agent, AgentT>,
100 "AgentT must derive from Mads::Agent");
101 static_assert(std::is_constructible_v<AgentT, std::string, std::string>,
102 "AgentT must be constructible from (std::string, std::string)");
106 using AgentT::fetch_settings;
156 : AgentT(name, std::move(settings_uri)), _options(std::move(name)) {}
163 cxxopts::OptionAdder
options() {
return _options.add_options(); }
183 _options.add_options()
185 (
"s,settings",
"Settings file path/URI",
186 cxxopts::value<std::string>())
187 (
"S,save-settings",
"Save settings to ini file",
188 cxxopts::value<std::string>())
189 (
"settings-timeout",
"Timeout in milliseconds for reading settings from broker ('0' = no timeout)",
190 cxxopts::value<int>()->default_value(
"0"))
191 (
"r,room",
"Service discovery room name", cxxopts::value<std::string>()->implicit_value(MADS_SERVICE_ROOM))
192 (
"crypto",
"Enable CURVE encryption for broker communication")
194 (
"keys_dir",
"Directory where CURVE keys are stored",
195 cxxopts::value<std::string>()->implicit_value(
Mads::exec_dir(
"../etc")))
196 (
"key_broker",
"Name of the broker key file (without .key extension)",
197 cxxopts::value<std::string>()->implicit_value(
"broker"))
198 (
"key_client",
"Name of the client key file (without .key extension)",
199 cxxopts::value<std::string>()->implicit_value(
"client"))
200 (
"auth_verbose",
"Enable verbose authentication messages")
201 (
"v,version",
"Print version")
202 (
"h,help",
"Print usage");
210 _options.add_options()
212 (
"n,name",
"Agent name", cxxopts::value<std::string>())
213 (
"i,agent-id",
"Agent ID to be added to JSON frames",
214 cxxopts::value<std::string>());
226 name = name.substr(name.find_last_of(
"\\") + 1);
227 name = name.substr(0, name.find(
"."));
229 name = name.substr(name.find_last_of(
"/") + 1);
231 const size_t pos = name.rfind(
'-');
232 if (pos != std::string::npos) {
233 this->_name = name.substr(pos + 1);
235 this->_name = std::move(name);
243 _options.add_options()
244 (
"b,dont-block",
"don't block on read");
251 _options.add_options()
252 (
"q,queue-size",
"ZMQ socket queue size (default 1000)",
253 cxxopts::value<int>());
268 _parsed_options = _options.parse(argc, argv);
269 }
catch (
const cxxopts::exceptions::exception &e) {
270 print_parse_error_and_exit(argc, argv, e.what());
272 const auto unmatched = _parsed_options.unmatched();
273 if (!unmatched.empty()) {
274 std::string message =
275 unmatched.size() == 1 ?
"Unexpected CLI argument: "
276 :
"Unexpected CLI arguments: ";
278 for (
const auto &argument : unmatched) {
285 print_parse_error_and_exit(argc, argv, message);
287 return _parsed_options;
307 template<
typename SaveAgentT>
309 const cxxopts::ParseResult &parsed,
const cxxopts::Options &parser,
310 char *argv[], std::string default_settings_uri = SETTINGS_URI,
311 std::ostream &out = std::cout, std::ostream &err = std::cerr) {
312 static_assert(std::is_base_of_v<Agent, SaveAgentT>,
313 "SaveAgentT must derive from Mads::Agent");
315 std::is_constructible_v<SaveAgentT, std::string, std::string>,
316 "SaveAgentT must be constructible from (std::string, std::string)");
318 if (parsed.count(
"help")) {
319 out << argv[0] <<
" ver. " << LIB_VERSION << std::endl;
320 out << parser.help() << std::endl;
324 if (parsed.count(
"version")) {
325 out << LIB_VERSION << std::endl;
329 if (parsed.count(
"save-settings")) {
331 cli_options_from_parse_result(parsed, std::move(default_settings_uri));
332 const auto output_path = parsed[
"save-settings"].as<std::string>();
333 SaveAgentT obj(argv[0], cli_options.settings_uri);
334 if (cli_options.crypto.enabled) {
335 obj.set_key_dir(cli_options.crypto.key_dir);
336 obj.client_key_name = cli_options.crypto.client_key_name;
337 obj.server_key_name = cli_options.crypto.server_key_name;
338 obj.auth_verbose = cli_options.crypto.auth_verbose;
341 if (cli_options.settings_timeout > 0) {
342 obj.set_settings_timeout(cli_options.settings_timeout);
344 obj.init(cli_options.crypto.enabled);
345 obj.save_settings(output_path);
346 }
catch (
const AgentError &e) {
347#ifndef MADS_AGENT_NO_INFO
350 err <<
"Error saving local settings: " << e.what();
351#ifndef MADS_AGENT_NO_INFO
356 }
catch (
const std::exception &e) {
357#ifndef MADS_AGENT_NO_INFO
360 err <<
"Error saving settings: " << e.what();
361#ifndef MADS_AGENT_NO_INFO
367#ifndef MADS_AGENT_NO_INFO
370 out <<
"Settings saved to " << output_path;
371#ifndef MADS_AGENT_NO_INFO
389 void init(
const cxxopts::ParseResult &parsed,
390 std::string default_settings_uri = SETTINGS_URI,
391 bool install_watchdog =
true) {
392 const auto &cli_options =
393 resolve_cli_options(parsed, std::move(default_settings_uri));
394 apply_cli_options(cli_options);
395 AgentT::init(cli_options.crypto.enabled, install_watchdog);
396 _settings = this->get_settings();
414 std::string default_settings_uri = SETTINGS_URI) {
415 const auto &cli_options =
416 resolve_cli_options(parsed, std::move(default_settings_uri));
417 apply_cli_options(cli_options);
418 AgentT::fetch_settings(cli_options.crypto.enabled);
419 _settings = this->get_settings();
430 void connect(std::chrono::milliseconds delay = std::chrono::milliseconds(250)) {
431 AgentT::connect(delay);
432 if (_events_enabled) {
433 this->register_event(event_type::startup);
441 if (_events_enabled && this->is_connected()) {
442 this->register_event(event_type::shutdown);
444 AgentT::disconnect();
456 if (!_settings.contains(
"receive_timeout") ||
457 _settings.at(
"receive_timeout").is_null()) {
460 const auto &receive_timeout = _settings.at(
"receive_timeout");
461 if (receive_timeout.is_number_integer()) {
462 this->set_receive_timeout(receive_timeout.template get<int>());
470 if (_parsed_options.count(
"queue-size") != 0) {
471 const int queue_size = _parsed_options[
"queue-size"].template as<int>();
472 this->set_high_watermark(queue_size);
475 if (!_settings.contains(
"queue_size") ||
476 _settings.at(
"queue_size").is_null()) {
479 this->set_high_watermark(_settings.value(
"queue_size", 1000));
490 if (!this->restart()) {
493 auto cmd = std::string(MADS_PREFIX) + argv[0];
494 out <<
"Restarting " << cmd <<
"..." << std::endl;
496 _execvp(cmd.c_str(), argv);
498 execvp(cmd.c_str(), argv);
505 static std::optional<T> option_value(
const cxxopts::ParseResult &parsed,
506 const std::string &name) {
507 if (parsed.count(name) == 0) {
510 return parsed[name].as<T>();
513 static void print_status(std::ostream &out,
const std::string &message) {
514#ifndef MADS_AGENT_NO_INFO
518#ifndef MADS_AGENT_NO_INFO
524 [[noreturn]]
void print_parse_error_and_exit(
525 int argc,
char *argv[],
const std::string &message)
const {
526#ifndef MADS_AGENT_NO_INFO
527 std::cerr << fg::red;
529 std::cerr <<
"Error parsing command line: " << message;
530#ifndef MADS_AGENT_NO_INFO
531 std::cerr << fg::reset;
533 std::cerr << std::endl << std::endl;
534 std::cerr << _options.help() << std::endl;
535 if (argc > 0 && argv !=
nullptr && argv[0] !=
nullptr) {
536 std::cerr <<
"Run '" << argv[0] <<
" --help' for usage." << std::endl;
538 std::exit(EXIT_FAILURE);
541 static CliOptions cli_options_from_parse_result(
542 const cxxopts::ParseResult &parsed,
543 std::string default_settings_uri = SETTINGS_URI) {
545 options.settings_uri = std::move(default_settings_uri);
547 option_value<int>(parsed,
"settings-timeout").value_or(0);
549 if (
auto settings = option_value<std::string>(parsed,
"settings")) {
550 options.settings_uri = *settings;
552 if (
auto name = option_value<std::string>(parsed,
"name")) {
555 if (
auto agent_id = option_value<std::string>(parsed,
"agent-id")) {
559 if (parsed.count(
"crypto") != 0) {
561 if (
auto key_dir = option_value<std::string>(parsed,
"keys_dir")) {
562 options.crypto.key_dir = *key_dir;
564 if (
auto server_key_name =
565 option_value<std::string>(parsed,
"key_broker")) {
566 options.crypto.server_key_name = *server_key_name;
568 if (
auto client_key_name =
569 option_value<std::string>(parsed,
"key_client")) {
570 options.crypto.client_key_name = *client_key_name;
572 if (parsed.count(
"auth_verbose") != 0) {
577 if (parsed.count(
"room") && !parsed[
"room"].as<std::string>().empty()) {
578 auto room = parsed[
"room"].as<std::string>();
579 auto discovery_service = ServiceDiscovery(MADS_SERVICE_PORT);
580 cout << fg::gray << style::italic <<
"Using "
581 << (
options.crypto.enabled ?
"encrypted" :
"unencrypted")
582 <<
" service discovery in room '"
583 << parsed[
"room"].as<std::string>() <<
"'..." << std::endl;
584 const auto service = discovery_service.discover(room, 5000ms);
585 options.settings_uri =
"tcp://" + service.ip +
":" +
586 std::to_string(service.ports.at(
"settings"));
587 cout <<
"Found broker on " << service.hostname
588 <<
" providing settings at: " <<
options.settings_uri
589 << style::reset << fg::reset << std::endl;
590 if (
options.crypto.enabled != service.encrypted) {
591 throw std::runtime_error(
592 "CLI encryption setting does not match discovered service encryption");
603 const CliOptions &resolve_cli_options(
const cxxopts::ParseResult &parsed,
604 std::string default_settings_uri) {
607 cli_options_from_parse_result(parsed, std::move(default_settings_uri));
609 return *_cli_options;
612 void apply_cli_options(
const CliOptions &cli_options) {
613 configure_from_cli_options(cli_options);
614 if (cli_options.settings_timeout > 0) {
615 print_status(std::cout,
"Using settings timeout of " +
616 std::to_string(cli_options.settings_timeout) +
618 this->set_settings_timeout(cli_options.settings_timeout);
622 void configure_from_cli_options(
const CliOptions &
options) {
623 this->_settings_uri =
options.settings_uri;
626 const auto &name = *
options.agent_name;
627 const size_t pos = name.rfind(
'-');
628 if (pos != std::string::npos) {
629 this->_name = name.substr(pos + 1);
636 this->set_agent_id(*
options.agent_id);
640 this->set_key_dir(
options.crypto.key_dir);
641 this->client_key_name =
options.crypto.client_key_name;
642 this->server_key_name =
options.crypto.server_key_name;
647 bool _events_enabled =
false;
648 nlohmann::json _settings;
649 cxxopts::Options _options;
650 cxxopts::ParseResult _parsed_options;
651 std::optional<CliOptions> _cli_options;
656template<
typename AgentT>
MADS_EXPORT const char * agent_id(agent_t agent)
Returns the current agent identifier.
Application-facing wrapper for Agent and Agent subclasses.
cxxopts::ParseResult & parse_options(int argc, char *argv[])
Parse the owned cxxopts parser.
AgentAppT(std::string name, std::string settings_uri)
Construct an AgentAppT with an owned cxxopts parser.
void connect(std::chrono::milliseconds delay=std::chrono::milliseconds(250))
Connect the wrapped agent and optionally register startup.
void fetch_settings(const cxxopts::ParseResult &parsed, std::string default_settings_uri=SETTINGS_URI)
Apply parsed CLI options and fetch settings (and any broker-served attachment) without binding the wr...
void apply_queue_size()
Apply queue size from CLI or cached settings when present.
const cxxopts::Options & raw_options() const
Access the owned cxxopts parser.
void add_dont_block_option()
Add the non-blocking receive option.
void add_queue_size_option()
Add the ZMQ socket queue size option.
static int handle_standard_exit_options(const cxxopts::ParseResult &parsed, const cxxopts::Options &parser, char *argv[], std::string default_settings_uri=SETTINGS_URI, std::ostream &out=std::cout, std::ostream &err=std::cerr)
Handle standard options that terminate an executable early.
void enable_events(bool enabled=true)
Enable automatic startup and shutdown event registration.
void init(const cxxopts::ParseResult &parsed, std::string default_settings_uri=SETTINGS_URI, bool install_watchdog=true)
Apply parsed CLI options and initialize the wrapped agent.
bool restart_if_requested(char *argv[], std::ostream &out=std::cout)
Restart the current executable if requested by remote control.
void disconnect()
Optionally register shutdown and disconnect the wrapped agent.
void apply_receive_timeout()
Apply receive_timeout from cached settings when present.
cxxopts::OptionAdder options()
Access the common option adder for fluent cxxopts declarations.
const nlohmann::json & settings_json() const
Return cached settings loaded during init().
cxxopts::Options & raw_options()
Access the owned cxxopts parser.
void add_common_options()
Add common MADS executable options to the owned parser.
void set_agent_name(std::string name)
Override the wrapped agent name before initialization.
void add_agent_identity_options()
Add optional agent identity options.
std::string exec_dir(std::string relative="")
AgentAppT< Agent > AgentApp
AgentAppT< AgentT > AgentAppFor
Common options parsed for agent executables.
int settings_timeout
Timeout in milliseconds for reading settings from broker.
std::string settings_uri
Settings path or broker URI.
std::optional< std::string > agent_name
Optional agent name override.
CryptoOptions crypto
CURVE encryption configuration.
std::optional< std::string > agent_id
Optional agent identifier added to outgoing JSON frames.
CURVE encryption options parsed from the command line.
std::string server_key_name
Broker/server public key file name without extension.
std::string client_key_name
Client key file name without extension.
bool enabled
Whether CURVE encryption is enabled.
std::filesystem::path key_dir
Directory containing CURVE key files.