diff --git a/mktxp/cli/config/config.py b/mktxp/cli/config/config.py index 6de8388..bf55df1 100755 --- a/mktxp/cli/config/config.py +++ b/mktxp/cli/config/config.py @@ -84,7 +84,7 @@ class OSConfig(metaclass = ABCMeta): return OSXConfig() else: if not quiet: - print('Non-supported platform: {}'.format(sys.platform)) + print(f'Non-supported platform: {sys.platform}') return None @property @@ -136,7 +136,7 @@ class MKTXPConfigHandler: # MKTXP entries ############## - def register_entry(self, entry_name, entry_info, quiet = False): + def register_entry(self, entry_name, entry_args, quiet = False): ''' Registers MKTXP conf entry ''' if entry_name in self.registered_entries(): @@ -144,8 +144,7 @@ class MKTXPConfigHandler: print('"{0}": entry name already registered'.format(entry_name)) return False else: - self.config[entry_name] = dict(entry_info._asdict()) - print(f'adding entry: {self.config[entry_name]}') + self.config[entry_name] = entry_args self.config.write() if not quiet: print('Entry registered: {0}'.format(entry_name)) diff --git a/mktxp/cli/dispatch.py b/mktxp/cli/dispatch.py index fe3d31d..6b7a7ef 100755 --- a/mktxp/cli/dispatch.py +++ b/mktxp/cli/dispatch.py @@ -13,8 +13,10 @@ ## GNU General Public License for more details. import sys +import subprocess import pkg_resources import mktxp.cli.checks.chk_pv +from mktxp.utils.utils import run_cmd from mktxp.cli.options import MKTXPOptionsParser, MKTXPCommands from mktxp.cli.config.config import config_handler, ConfigEntry from mktxp.basep import MKTXPProcessor @@ -36,7 +38,7 @@ class MKTXPDispatcher: self.print_info() elif args['sub_cmd'] == MKTXPCommands.SHOW: - self.show_entries() + self.show_entries(args) elif args['sub_cmd'] == MKTXPCommands.ADD: self.add_entry(args) @@ -61,40 +63,46 @@ class MKTXPDispatcher: ''' Prints MKTXP version info ''' version = pkg_resources.require("mktxp")[0].version - print('Mikrotik RouterOS Prometheus Exporter version {}'.format(version)) + print(f'Mikrotik RouterOS Prometheus Exporter version {version}') def print_info(self): ''' Prints MKTXP general info ''' - print('Mikrotik RouterOS Prometheus Exporter: {}'.format(self.option_parser.script_name)) - print(self.option_parser.description) + print(f'{self.option_parser.script_name}: {self.option_parser.description}') - def show_entries(self): - for entryname in config_handler.registered_entries(): - entry = config_handler.entry(entryname) - - print('[{}]'.format(entryname)) - for field in entry._fields: - print(' {}: {}'.format(field, getattr(entry, field))) - print() + def show_entries(self, args): + if args['configpath']: + print(f'MKTX config path: {config_handler.usr_conf_data_path}') + else: + for entryname in config_handler.registered_entries(): + if args['entry_name'] and entryname != args['entry_name']: + continue + entry = config_handler.entry(entryname) + print(f'[{entryname}]') + divider_fields = set(['username', 'use_ssl', 'dhcp']) + for field in entry._fields: + if field == 'password': + print(f' {field}: {"*" * len(entry.password)}') + else: + if field in divider_fields: + print() + print(f' {field}: {getattr(entry, field)}') + print('\n') def add_entry(self, args): - args.pop('sub_cmd', None) - entry_name = args['entry_name'] - args.pop('entry_name', None) + entry_args = {key: value for key, value in args.items() if key not in set(['sub_cmd', 'entry_name'])} + config_handler.register_entry(entry_name = args['entry_name'], entry_args = entry_args) - entry_info = ConfigEntry.MKTXPEntry(**args) - config_handler.register_entry(entry_name = entry_name, entry_info = entry_info) - - - def edit_entry(self, args): - pass + def edit_entry(self, args): + editor = args['editor'] + if not editor: + print(f'No editor to edit the following file with: {config_handler.usr_conf_data_path}') + subprocess.check_call([editor, config_handler.usr_conf_data_path]) def delete_entry(self, args): config_handler.unregister_entry(entry_name = args['entry_name']) - def start_export(self, args): MKTXPProcessor.start() diff --git a/mktxp/cli/options.py b/mktxp/cli/options.py index 539cc22..6caeb94 100755 --- a/mktxp/cli/options.py +++ b/mktxp/cli/options.py @@ -14,7 +14,7 @@ import os from argparse import ArgumentParser, HelpFormatter from mktxp.cli.config.config import config_handler, MKTXPConfigKeys -from mktxp.utils.utils import FSHelper, UniquePartialMatchList +from mktxp.utils.utils import FSHelper, UniquePartialMatchList, run_cmd class MKTXPCommands: @@ -29,13 +29,13 @@ class MKTXPCommands: @classmethod def commands_meta(cls): return ''.join(('{', - '{}, '.format(cls.INFO), - '{}'.format(cls.VERSION), - '{}'.format(cls.SHOW), - '{}'.format(cls.ADD), - '{}'.format(cls.EDIT), - '{}'.format(cls.DELETE), - '{}'.format(cls.START), + f'{cls.INFO}, ', + f'{cls.VERSION}, ', + f'{cls.SHOW}, ', + f'{cls.ADD}, ', + f'{cls.EDIT}, ', + f'{cls.DELETE}, ', + f'{cls.START}', '}')) class MKTXPOptionsParser: @@ -45,7 +45,8 @@ class MKTXPOptionsParser: self._script_name = 'MKTXP' self._description = \ ''' - Prometheus Exporter for Mikrotik RouterOS Devices + Prometheus Exporter for Mikrotik RouterOS Devices. + ''' @property @@ -94,9 +95,13 @@ class MKTXPOptionsParser: formatter_class=MKTXPHelpFormatter) # Show command - subparsers.add_parser(MKTXPCommands.SHOW, - description = 'Displays MKTXP router entries', + show_parser = subparsers.add_parser(MKTXPCommands.SHOW, + description = 'Displays MKTXP config router entries', formatter_class=MKTXPHelpFormatter) + self._add_entry_name(show_parser, registered_only = True, required = False, help = "Config entry name") + show_parser.add_argument('-cp', '--configpath', dest='configpath', + help = "Shows MKTXP config file path", + action = 'store_true') # Add command add_parser = subparsers.add_parser(MKTXPCommands.ADD, @@ -159,15 +164,14 @@ class MKTXPOptionsParser: help = "Export CAPsMAN metrics", action = 'store_true') -#'hostname', 'port', 'username', 'password', 'use_ssl', 'ssl_certificate', 'dhcp', 'dhcp_lease', 'pool', 'interface', 'monitor', 'route', 'wireless', and 'capsman' - # Edit command edit_parser = subparsers.add_parser(MKTXPCommands.EDIT, description = 'Edits an existing MKTXP router entry', formatter_class=MKTXPHelpFormatter) - required_args_group = edit_parser.add_argument_group('Required Arguments') - self._add_entry_name(required_args_group, registered_only = True, help = "Name of entry to edit") - + edit_parser.add_argument('-ed', '--editor', dest='editor', + help = f"command line editor to use ({self._system_editor()} by default)", + default = self._system_editor(), + type = str) # Delete command delete_parser = subparsers.add_parser(MKTXPCommands.DELETE, @@ -181,7 +185,6 @@ class MKTXPOptionsParser: description = 'Starts exporting Miktorik Router Metrics', formatter_class=MKTXPHelpFormatter) - # Options checking def _check_args(self, args, parser): ''' Validation of supplied CLI arguments @@ -189,13 +192,13 @@ class MKTXPOptionsParser: # check if there is a cmd to execute self._check_cmd_args(args, parser) - if args['sub_cmd'] in (MKTXPCommands.EDIT, MKTXPCommands.DELETE): + if args['sub_cmd'] == MKTXPCommands.DELETE: # Registered Entry name could be a partial match, need to expand args['entry_name'] = UniquePartialMatchList(config_handler.registered_entries()).find(args['entry_name']) - if args['sub_cmd'] == MKTXPCommands.ADD: + elif args['sub_cmd'] == MKTXPCommands.ADD: if args['entry_name'] in (config_handler.registered_entries()): - print('"{0}": entry name already exists'.format(args['entry_name'])) + print(f"{args['entry_name']}: entry name already exists") parser.exit() def _check_cmd_args(self, args, parser): @@ -227,7 +230,7 @@ class MKTXPOptionsParser: ''' path_arg = FSHelper.full_path(path_arg) if not (os.path.exists(path_arg) and os.path.isdir(path_arg)): - parser.error('"{}" does not seem to be an existing directory path'.format(path_arg)) + parser.error(f'"{path_arg}" does not seem to be an existing directory path') else: return path_arg @@ -237,16 +240,16 @@ class MKTXPOptionsParser: ''' path_arg = FSHelper.full_path(path_arg) if not (os.path.exists(path_arg) and os.path.isfile(path_arg)): - parser.error('"{}" does not seem to be an existing file path'.format(path_arg)) + parser.error('"{path_arg}" does not seem to be an existing file path') else: return path_arg @staticmethod - def _add_entry_name(parser, registered_only = False, help = 'MKTXP Entry name'): + def _add_entry_name(parser, registered_only = False, required = True, help = 'MKTXP Entry name'): parser.add_argument('-en', '--entry-name', dest = 'entry_name', type = str, metavar = config_handler.registered_entries() if registered_only else None, - required = True, + required = required, choices = UniquePartialMatchList(config_handler.registered_entries())if registered_only else None, help = help) @@ -255,6 +258,19 @@ class MKTXPOptionsParser: required_args_group = parser.add_argument_group('Required Arguments') MKTXPOptionsParser._add_entry_name(required_args_group) + @staticmethod + def _system_editor(): + editor = os.environ.get('EDITOR') + if editor: + return editor + + commands = ['which nano', 'which vi', 'which vim'] + for command in commands: + editor = run_cmd(command, quiet = True).rstrip() + if editor: + break + return editor + class MKTXPHelpFormatter(HelpFormatter): ''' Custom formatter for ArgumentParser @@ -284,4 +300,3 @@ class MKTXPHelpFormatter(HelpFormatter): parts[-1] += ' %s'%args_string return ', '.join(parts) - diff --git a/mktxp/router_connection.py b/mktxp/router_connection.py index b24042d..fc2fd93 100644 --- a/mktxp/router_connection.py +++ b/mktxp/router_connection.py @@ -13,6 +13,7 @@ import ssl import socket +from datetime import datetime from routeros_api import RouterOsApiPool @@ -28,7 +29,7 @@ class RouterAPIConnection: self.router_entry = router_entry ctx = None - if not self.router_entry.ssl_certificate: + if self.router_entry.use_ssl and not self.router_entry.ssl_certificate: ctx = ssl.create_default_context() ctx.set_ciphers('ADH:@SECLEVEL=0') @@ -38,7 +39,7 @@ class RouterAPIConnection: password = self.router_entry.password, port = self.router_entry.port, plaintext_login = True, - use_ssl = True, + use_ssl = self.router_entry.use_ssl, ssl_context = ctx) self.connection.socket_timeout = 2 @@ -58,12 +59,13 @@ class RouterAPIConnection: def connect(self): if self.is_connected(): return + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") try: - print('Connecting to router {0}@{1}'.format(self.router_name, self.router_entry.hostname)) + print(f'Connecting to router {self.router_name}@{self.router_entry.hostname}') self.api = self.connection.get_api() - print('Connection to router {0}@{1} has been established'.format(self.router_name, self.router_entry.hostname)) + print(f'{current_time} Connection to router {self.router_name}@{self.router_entry.hostname} has been established') except (socket.error, socket.timeout, Exception) as ex: - print('Connection to router {0}@{1} has failed: {2}'.format(self.router_name, self.router_entry.hostname, ex)) + print(f'{current_time} Connection to router {self.router_name}@{self.router_entry.hostname} has failed: {ex}') raise def router_api(self): diff --git a/mktxp/utils/utils.py b/mktxp/utils/utils.py index f961326..6ae9104 100755 --- a/mktxp/utils/utils.py +++ b/mktxp/utils/utils.py @@ -39,14 +39,14 @@ def temp_dir(quiet = True): class CmdProcessingError(Exception): pass -def run_cmd(cmd, shell = False): +def run_cmd(cmd, shell = False, quiet = False): ''' Runs shell commands in a separate process ''' if not shell: cmd = shlex.split(cmd) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell = shell) output = proc.communicate()[0].decode('utf-8') - if proc.returncode != 0: + if proc.returncode != 0 and not quiet: raise CmdProcessingError(output) return output