source: nscp/service/cli_parser.hpp @ 9fdde88

0.4.2
Last change on this file since 9fdde88 was 9fdde88, checked in by Michael Medin <michael@…>, 11 months ago
  • Added plugin_id to settings API to allow generating documentation based on module names.
  • Added option --filter <module name> to filter generation based on module name. You can now do this to generate documentation for a given module. nscp settings --settings dummy --generate trac --filter ExternalScripts? --load-all --add-defaults --log error
  • Property mode set to 100644
File size: 19.9 KB
Line 
1#pragma once
2
3#include <boost/program_options.hpp>
4#include "settings_client.hpp"
5#include <nsclient/logger.hpp>
6
7namespace po = boost::program_options;
8class cli_parser {
9       
10
11        NSClient* core_;
12        po::options_description root;
13        po::options_description common;
14        po::options_description settings;
15        po::options_description service;
16        po::options_description client;
17        po::options_description unittest;
18        po::options_description test;
19
20        bool help;
21        bool version;
22        bool log_debug;
23        std::wstring log_level;
24        std::wstring settings_store;
25
26        static nsclient::logging::logger_interface* get_logger() {
27                return nsclient::logging::logger::get_logger();
28        }
29        static void error(unsigned int line, const std::wstring &message) {
30                get_logger()->error(_T("client"), __FILE__, line, message);
31        }
32        static void info(unsigned int line, const std::wstring &message) {
33                get_logger()->info(_T("client"), __FILE__, line, message);
34        }
35
36public:
37        cli_parser(NSClient* core)
38                : core_(core)
39                , root("Allowed first option (Mode of operation)")
40                , common("Common options")
41                , settings("Settings options")
42                , service("Service Options")
43                , client("Client Options")
44                , unittest("Unit-test Options")
45                , test("Test Options")
46                , help(false)
47                , version(false)
48                , log_debug(false)
49        {
50                root.add_options()
51                        ("help", po::bool_switch(&help), "produce help message")
52                        ("version", po::bool_switch(&version), "Show version information")
53                        ;
54
55                common.add_options()
56                        ("settings", po::value<std::wstring>(&settings_store), "Override (temporarily) settings subsystem to use")
57                        ("help", po::bool_switch(&help), "produce help message")
58                        ("debug", po::bool_switch(&log_debug), "Set log level to debug (and show debug information)")
59                        ("log", po::value<std::wstring>(&log_level), "The log level to use")
60                        ("version", po::bool_switch(&version), "Show version information")
61                        ;
62
63                settings.add_options()
64                        ("migrate-to", po::value<std::wstring>(), "Migrate (copy) settings from current store to target store")
65                        ("migrate-from", po::value<std::wstring>(), "Migrate (copy) settings from current store to target store")
66                        ("generate", po::value<std::wstring>(), "(re)Generate a commented settings store or similar KEY can be trac, settings or the target store.")
67                        ("filter", po::value<std::wstring>(), "Filter what to update (only works with generate trac currently).")
68                        ("add-defaults", "Add all default (if missing) values.")
69                        ("load-all", "Load all plugins (currently only used with generate).")
70                        ("path", po::value<std::wstring>()->default_value(_T("")), "Path of key to work with.")
71                        ("key", po::value<std::wstring>()->default_value(_T("")), "Key to work with.")
72                        ("set", po::value<std::wstring>()->implicit_value(_T("")), "Set a key and path to a given value.")
73                        ("switch", po::value<std::wstring>(), "Set default context to use (similar to migrate but does NOT copy values)")
74                        ("show", "Set a value given a key and path.")
75                        ("list", "Set all keys below the path (or root).")
76                        ;
77
78                service.add_options()
79                        ("install", "Install service")
80                        ("uninstall", "Uninstall service")
81                        ("start", "Start service")
82                        ("stop", "Stop service")
83                        ("info", "Show information about service")
84                        ("run", "Run as a service")
85                        ("name", po::value<std::wstring>(), "Name of service")
86                        ("description", po::value<std::wstring>()->default_value(_T("")), "Description of service")
87                        ;
88
89                client.add_options()
90                        ("exec,e", po::value<std::wstring>()->implicit_value(_T("")), "Run a command (execute)")
91                        ("boot,b", "Boot the client before executing command (similar as running the command from test)")
92                        ("query,q", po::value<std::wstring>(), "Run a query with a given name")
93                        ("submit,s", po::value<std::wstring>(), "Name of query to ask")
94                        ("module,M", po::value<std::wstring>(), "Name of module to load (if not specified all modules in ini file will be loaded)")
95                        ("argument,a", po::wvalue<std::vector<std::wstring> >(), "List of arguments (gets -- prefixed automatically)")
96                        ("raw-argument", po::wvalue<std::vector<std::wstring> >(), "List of arguments (does not get -- prefixed)")
97                        ;
98
99                unittest.add_options()
100                        ("language,l", po::value<std::wstring>()->implicit_value(_T("")), "Language tests are written in")
101                        ("argument,a", po::wvalue<std::vector<std::wstring> >(), "List of arguments (gets -- prefixed automatically)")
102                        ("raw-argument", po::wvalue<std::vector<std::wstring> >(), "List of arguments (does not get -- prefixed)")
103                        ;
104
105                test.add_options()
106                        ("log-to-file", "Enable file logger (defaults is console only)")
107                        ;
108
109        }
110
111        bool process_common_options(std::string context, po::options_description &desc) {
112                nsclient::logging::logger::get_logger()->set_console_log(true);
113                if (log_debug) {
114                        log_level = _T("debug");
115                }
116                if (!log_level.empty())
117                        nsclient::logging::logger::set_log_level(log_level);
118                if (!settings_store.empty())
119                        core_->set_settings_context(settings_store);
120
121                if (help) {
122                        std::cout << desc << std::endl;
123                        return true;
124                }
125                if (version) {
126                        std::cout << APPLICATION_NAME << _T(", Version: ") << CURRENT_SERVICE_VERSION << _T(", Platform: ") << SZARCH << std::endl;
127                        return true;
128                }
129                return false;
130        }
131
132        typedef boost::function<int(int, wchar_t**)> handler_function;
133        typedef std::map<std::string,handler_function> handler_map;
134        typedef std::map<std::string,std::string> alias_map;
135
136        handler_map get_handlers() {
137                handler_map handlers;
138                handlers["settings"] = boost::bind(&cli_parser::parse_settings, this, _1, _2);
139                handlers["service"] = boost::bind(&cli_parser::parse_service, this, _1, _2);
140                handlers["client"] = boost::bind(&cli_parser::parse_client, this, _1, _2, _T(""));
141                handlers["test"] = boost::bind(&cli_parser::parse_test, this, _1, _2);
142                handlers["help"] = boost::bind(&cli_parser::parse_help, this, _1, _2);
143                handlers["unit"] = boost::bind(&cli_parser::parse_unittest, this, _1, _2);
144                return handlers;
145        }
146
147        alias_map get_aliases() {
148                alias_map aliases;
149                aliases["nrpe"] = "NRPEClient";
150                aliases["nscp"] = "NSCPClient";
151                aliases["nsca"] = "NSCAClient";
152                aliases["eventlog"] = "CheckEventLog";
153                aliases["python"] = "PythonScript";
154                aliases["py"] = "PythonScript";
155                aliases["lua"] = "LuaScript";
156                aliases["syslog"] = "SyslogClient";
157                aliases["sys"] = "CheckSystem";
158                aliases["wmi"] = "CheckWMI";
159                return aliases;
160        }
161
162        int parse(int argc, wchar_t* argv[]) {
163
164                if (argc > 1 && argv[1][0] != L'-') {
165                        handler_map handlers = get_handlers();
166                        alias_map aliases = get_aliases();
167
168                        std::string mod = utf8::cvt<std::string>(argv[1]);
169                        handler_map::const_iterator it = handlers.find(mod);
170                        if (it != handlers.end())
171                                return it->second(argc-1, &argv[1]);
172
173                        alias_map::const_iterator alias_it = aliases.find(mod);
174                        if (alias_it != aliases.end())
175                                return parse_client(argc-1, &argv[1], utf8::cvt<std::wstring>(alias_it->second));
176
177                        parse_help(argc, argv);
178                        std::cerr << "Invalid module specified: " << mod << std::endl;
179                        return 1;
180                }
181                return parse_help(argc, argv);
182        }
183        int parse_help(int argc, wchar_t* argv[]) {
184                try {
185
186                        po::options_description all("Allowed options");
187                        all.add(root).add(common).add(service).add(settings).add(client).add(test).add(unittest);
188                        std::cout << all << std::endl;
189
190                        std::cerr << "First argument has to be one of the following: ";
191                        handler_map handlers = get_handlers();
192                        BOOST_FOREACH(const handler_map::value_type &itm, handlers) {
193                                std::cerr << itm.first << ", ";
194                        }
195                        std::cerr << std::endl;
196                        std::cerr << "Or on of the following client aliases: ";
197                        alias_map aliases = get_aliases();
198                        BOOST_FOREACH(const alias_map::value_type &itm, aliases) {
199                                std::cerr << itm.first << ", ";
200                        }
201                        std::cerr << std::endl;
202                        return 1;
203                } catch(std::exception & e) {
204                        std::cerr << "Unable to parse root option: " << e.what() << std::endl;
205                        return 1;
206                } catch (...) {
207                        std::cerr << "Unable to parse root option" << std::endl;
208                        return 1;
209                }
210        }
211
212        int parse_test(int argc, wchar_t* argv[]) {
213                try {
214
215                        po::options_description all("Allowed options (test)");
216                        all.add(common).add(test);
217
218                        po::variables_map vm;
219                        po::store(po::parse_command_line(argc, argv, all), vm);
220                        po::notify(vm);
221
222                        if (log_level.empty())
223                                log_level  = _T("debug");
224
225                        if (process_common_options("test", all))
226                                return 1;
227
228                        if (vm.count("log-to-file") == 0) {
229                                nsclient::logging::logger::set_backend("console");
230                        }
231
232                        nsclient::simple_client client(core_);
233                        client.start(log_level);
234                        return 0;
235                } catch(const std::exception & e) {
236                        std::cerr << std::string("Unable to parse command line (test): ") << e.what() << "\n";
237                        return 1;
238                }
239        }
240
241        int parse_settings(int argc, wchar_t* argv[]) {
242                try {
243                        po::options_description all("Allowed options (settings)");
244                        all.add(common).add(settings);
245
246                        po::variables_map vm;
247                        po::store(po::parse_command_line(argc, argv, all), vm);
248                        po::notify(vm);
249
250                        if (process_common_options("settings", all))
251                                return 1;
252
253                        bool def = vm.count("add-defaults")==1;
254                        bool load_all = vm.count("load-all")==1;
255                        std::wstring filter;
256                        if (vm.count("filter"))
257                                filter = vm["filter"].as<std::wstring>();
258
259                        nsclient::settings_client client(core_, log_level, def, load_all, filter);
260                        int ret = -1;
261
262                        if (vm.count("generate")) {
263                                ret = client.generate(vm["generate"].as<std::wstring>());
264                        } else if (vm.count("migrate-to")) {
265                                ret = client.migrate_to(vm["migrate-to"].as<std::wstring>());
266                        } else if (vm.count("migrate-from")) {
267                                ret = client.migrate_from(vm["migrate-from"].as<std::wstring>());
268                        } else if (vm.count("set")) {
269                                ret = client.set(vm["path"].as<std::wstring>(), vm["key"].as<std::wstring>(), vm["set"].as<std::wstring>());
270                        } else if (vm.count("list")) {
271                                ret = client.list(vm["path"].as<std::wstring>());
272                        } else if (vm.count("show")) {
273                                ret = client.show(vm["path"].as<std::wstring>(), vm["key"].as<std::wstring>());
274                        } else if (vm.count("switch")) {
275                                client.switch_context(vm["switch"].as<std::wstring>());
276                                ret = 0;
277                        } else {
278                                std::cout << all << std::endl;
279                                return 1;
280                        }
281
282                        return ret;
283                } catch(const std::exception & e) {
284                        std::cerr << std::string("Unable to parse command line (settings): ") << e.what() << "\n";
285                        return 1;
286                }
287        }
288
289
290        int parse_service(int argc, wchar_t* argv[]) {
291                try {
292                        po::options_description all("Allowed options (service)");
293                        all.add(common).add(service);
294
295                        po::variables_map vm;
296                        po::store(po::parse_command_line(argc, argv, all), vm);
297                        po::notify(vm);
298
299                        if (process_common_options("service", all))
300                                return 1;
301
302                        std::wstring name;
303                        if (vm.count("name")) {
304                                name = vm["name"].as<std::wstring>();
305                        } else {
306                                info(__LINE__, _T("TODO retrieve name from service here"));
307                        }
308                        std::wstring desc;
309                        if (vm.count("description")) {
310                                desc = vm["description"].as<std::wstring>();
311                        } else {
312                                info(__LINE__, _T("TODO retrieve name from service here"));
313                        }
314                        if (nsclient::logging::logger::get_logger()->should_log(NSCAPI::log_level::debug)) {
315                                info(__LINE__, _T("Service name: ") + name);
316                                info(__LINE__, _T("Service description: ") + desc);
317                        }
318
319                        if (vm.count("run")) {
320                                try {
321                                        mainClient.start_and_wait(name);
322                                } catch (...) {
323                                        error(__LINE__, _T("Unknown exception in service"));
324                                }
325                        } else {
326                                nsclient::client::service_manager service_manager(name);
327
328                                if (vm.count("install")) {
329                                        service_manager.install(desc);
330                                } else if (vm.count("uninstall")) {
331                                        service_manager.uninstall();
332                                } else if (vm.count("start")) {
333                                        service_manager.start();
334                                } else if (vm.count("stop")) {
335                                        service_manager.stop();
336                                } else if (vm.count("info")) {
337                                        service_manager.info();
338                                } else {
339                                        std::cerr << "Missing argument" << std::endl;
340                                        return 1;
341                                }
342                        }
343                        return 0;
344                } catch(std::exception & e) {
345                        std::cerr << std::string("Unable to parse command line (settings): ") << e.what() << "\n";
346                        return 1;
347                }
348        }
349
350        struct client_arguments {
351                std::wstring command, combined_query, module;
352                std::vector<std::wstring> arguments;
353                enum modes { exec, query, submit, none, combined};
354                modes mode;
355                bool boot;
356                client_arguments() : mode(none), boot(false) {}
357
358                void debug() {
359                        if (nsclient::logging::logger::get_logger()->should_log(NSCAPI::log_level::debug)) {
360                                info(__LINE__, _T("Module: ") + module);
361                                info(__LINE__, _T("Command: ") + command);
362                                info(__LINE__, _T("Extra Query: ") + combined_query);
363                                info(__LINE__, _T("Mode: ") + strEx::itos(mode));
364                                info(__LINE__, _T("Boot: ") + strEx::itos(boot));
365                                if (!module.empty() && boot)
366                                        info(__LINE__, _T("Warning module and boot specified only THAT module will be loaded"));
367                                std::wstring args;
368                                BOOST_FOREACH(std::wstring s, arguments)
369                                        strEx::append_list(args, s, _T(", "));
370                                info(__LINE__, _T("Arguments: ") + args);
371                        }
372
373                }
374        };
375        int parse_client(int argc, wchar_t* argv[], std::wstring module_ = _T("")) {
376                try {
377                        client_arguments args;
378
379                        args.module = module_;
380                        po::options_description all("Allowed options (client)");
381                        all.add(common).add(client);
382
383                        po::positional_options_description p;
384                        p.add("arguments", -1);
385
386                        po::variables_map vm;
387                        po::wparsed_options parsed =
388                                po::wcommand_line_parser(argc, argv).options(all).allow_unregistered().run();
389                        po::store(parsed, vm);
390                        po::notify(vm);
391
392                        if (process_common_options("client", all))
393                                return 1;
394
395
396                        if (vm.count("exec")) {
397                                args.command = vm["exec"].as<std::wstring>();
398                                args.mode = client_arguments::exec;
399                                if (vm.count("query")) {
400                                        args.combined_query = vm["query"].as<std::wstring>();
401                                        args.mode = client_arguments::combined;
402                                }
403                        } else if (vm.count("query")) {
404                                args.command = vm["query"].as<std::wstring>();
405                                args.mode = client_arguments::query;
406                        } else if (vm.count("submit")) {
407                                args.command = vm["submit"].as<std::wstring>();
408                                args.mode = client_arguments::submit;
409                        }
410
411                        if (vm.count("module"))
412                                args.module = vm["module"].as<std::wstring>();
413
414                        if (vm.count("boot"))
415                                args.boot = true;
416
417                        std::vector<std::wstring> kvp_args;
418                        if (vm.count("argument"))
419                                kvp_args = vm["argument"].as<std::vector<std::wstring> >();
420
421                        args.arguments = po::collect_unrecognized(parsed.options, po::include_positional);
422
423                        BOOST_FOREACH(std::wstring s, kvp_args) {
424                                std::wstring::size_type pos = s.find(L'=');
425                                if (pos == std::wstring::npos)
426                                        args.arguments.push_back(_T("--") + s);
427                                else {
428                                        args.arguments.push_back(_T("--") + s.substr(0,pos));
429                                        args.arguments.push_back(s.substr(pos+1));
430                                }
431                        }
432
433                        if (vm.count("raw-argument"))
434                                kvp_args = vm["raw-argument"].as<std::vector<std::wstring> >();
435                        BOOST_FOREACH(std::wstring s, kvp_args) {
436                                std::wstring::size_type pos = s.find(L'=');
437                                if (pos == std::wstring::npos)
438                                        args.arguments.push_back(s);
439                                else {
440                                        args.arguments.push_back(s.substr(0,pos));
441                                        args.arguments.push_back(s.substr(pos+1));
442                                }
443                        }
444                        return exec_client_mode(args);
445                } catch(const std::exception & e) {
446                        std::wcerr << _T("Client: Unable to parse command line: ") << utf8::to_unicode(e.what()) << std::endl;
447                        return 1;
448                } catch(...) {
449                        std::wcerr << _T("Client: Unable to parse command line: UNKNOWN") << std::endl;
450                        return 1;
451                }
452        }
453
454        int parse_unittest(int argc, wchar_t* argv[]) {
455                try {
456                        client_arguments args;
457                        settings_store = _T("dummy");
458                        po::options_description all("Allowed options (client)");
459                        all.add(common).add(unittest);
460
461                        po::positional_options_description p;
462                        p.add("arguments", -1);
463
464                        po::variables_map vm;
465                        po::wparsed_options parsed =
466                                po::wcommand_line_parser(argc, argv).options(all).allow_unregistered().run();
467                        po::store(parsed, vm);
468                        po::notify(vm);
469
470                        if (process_common_options("unitest", all))
471                                return 1;
472
473
474                        if (vm.count("language")) {
475                                std::wstring lang = vm["language"].as<std::wstring>();
476                                if (lang == _T("python") || lang == _T("py")) {
477                                        args.command = _T("python-script");
478                                        args.combined_query = _T("py_unittest");
479                                        args.mode = client_arguments::combined;
480                                        args.module = _T("PythonScript");
481                                } else {
482                                        std::wcerr << _T("Unknown language: ") << lang << std::endl;
483                                        return 1;
484                                }
485                        } else {
486                                args.command = _T("python-script");
487                                args.combined_query = _T("py_unittest");
488                                args.mode = client_arguments::combined;
489                                args.module = _T("PythonScript");
490                        }
491
492                        std::vector<std::wstring> kvp_args;
493                        if (vm.count("argument"))
494                                kvp_args = vm["argument"].as<std::vector<std::wstring> >();
495
496                        args.arguments = po::collect_unrecognized(parsed.options, po::include_positional);
497
498                        BOOST_FOREACH(std::wstring s, kvp_args) {
499                                std::wstring::size_type pos = s.find(L'=');
500                                if (pos == std::wstring::npos)
501                                        args.arguments.push_back(_T("--") + s);
502                                else {
503                                        args.arguments.push_back(_T("--") + s.substr(0,pos));
504                                        args.arguments.push_back(s.substr(pos+1));
505                                }
506                        }
507
508                        if (vm.count("raw-argument"))
509                                kvp_args = vm["raw-argument"].as<std::vector<std::wstring> >();
510                        BOOST_FOREACH(std::wstring s, kvp_args) {
511                                std::wstring::size_type pos = s.find(L'=');
512                                if (pos == std::wstring::npos)
513                                        args.arguments.push_back(s);
514                                else {
515                                        args.arguments.push_back(s.substr(0,pos));
516                                        args.arguments.push_back(s.substr(pos+1));
517                                }
518                        }
519                        return exec_client_mode(args);
520                } catch(const std::exception & e) {
521                        std::wcerr << _T("Client: Unable to parse command line: ") << utf8::to_unicode(e.what()) << std::endl;
522                        return 1;
523                } catch(...) {
524                        std::wcerr << _T("Client: Unable to parse command line: UNKNOWN") << std::endl;
525                        return 1;
526                }
527        }
528
529        int exec_client_mode(client_arguments &args) {
530                try {
531                        args.debug();
532
533                        core_->boot_init(log_level);
534                        if (args.module.empty())
535                                core_->boot_load_all_plugins();
536                        else
537                                core_->boot_load_plugin(args.module);
538                        core_->boot_start_plugins(args.boot);
539                        int ret = 0;
540                        std::list<std::wstring> resp;
541                        if (args.mode == client_arguments::none) {
542                                args.mode = client_arguments::exec;
543                                std::wcerr << _T("Since no mode was specified assuming --exec (other options are --query and --submit)") << std::endl;
544                        }
545                        if (args.mode == client_arguments::query) {
546                                ret = mainClient.simple_query(args.module, args.command, args.arguments, resp);
547                        } else if (args.mode == client_arguments::exec || args.mode == client_arguments::combined) {
548                                ret = mainClient.simple_exec(args.module, args.command, args.arguments, resp);
549                                if (ret == NSCAPI::returnIgnored) {
550                                        ret = 1;
551                                        std::wcout << _T("Command not found (by module): ") << args.command << std::endl;
552                                        resp.push_back(_T("Command not found: ") + args.command);
553                                        mainClient.simple_exec(args.module, _T("help"), args.arguments, resp);
554                                } else if (args.mode == client_arguments::combined) {
555                                        if (ret == NSCAPI::returnOK) {
556                                                mainClient.reload(_T("service"));
557                                                ret = mainClient.simple_query(args.module, args.combined_query, args.arguments, resp);
558                                        } else {
559                                                std::wcerr << _T("Failed to execute command, will not attempt query") << std::endl;
560                                        }
561                                }
562                        } else if (args.mode == client_arguments::submit) {
563                                std::wcerr << _T("--submit is currently not supported (but you can use --exec submit which is technically the same)") << std::endl;
564                        } else {
565                                std::wcerr << _T("Need to specify one of --exec, --query or --submit") << std::endl;
566                        }
567                        mainClient.stop_unload_plugins_pre();
568                        mainClient.stop_exit_pre();
569                        mainClient.stop_exit_post();
570
571                        BOOST_FOREACH(std::wstring r, resp) {
572                                std::wcout << r << std::endl;
573                        }
574                        return ret;
575                } catch(const std::exception & e) {
576                        std::wcerr << _T("Client: Unable to parse command line: ") << utf8::to_unicode(e.what()) << std::endl;
577                        return 1;
578                } catch(...) {
579                        std::wcerr << _T("Client: Unable to parse command line: UNKNOWN") << std::endl;
580                        return 1;
581                }
582        }
583};
584
585
586
587
588
Note: See TracBrowser for help on using the repository browser.