cli options

This commit is contained in:
Arseniy Kuznetsov
2021-01-03 16:28:26 +01:00
parent cbf04ba9dc
commit 31e32983bb
5 changed files with 82 additions and 58 deletions

View File

@@ -84,7 +84,7 @@ class OSConfig(metaclass = ABCMeta):
return OSXConfig() return OSXConfig()
else: else:
if not quiet: if not quiet:
print('Non-supported platform: {}'.format(sys.platform)) print(f'Non-supported platform: {sys.platform}')
return None return None
@property @property
@@ -136,7 +136,7 @@ class MKTXPConfigHandler:
# MKTXP entries # 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 ''' Registers MKTXP conf entry
''' '''
if entry_name in self.registered_entries(): if entry_name in self.registered_entries():
@@ -144,8 +144,7 @@ class MKTXPConfigHandler:
print('"{0}": entry name already registered'.format(entry_name)) print('"{0}": entry name already registered'.format(entry_name))
return False return False
else: else:
self.config[entry_name] = dict(entry_info._asdict()) self.config[entry_name] = entry_args
print(f'adding entry: {self.config[entry_name]}')
self.config.write() self.config.write()
if not quiet: if not quiet:
print('Entry registered: {0}'.format(entry_name)) print('Entry registered: {0}'.format(entry_name))

View File

@@ -13,8 +13,10 @@
## GNU General Public License for more details. ## GNU General Public License for more details.
import sys import sys
import subprocess
import pkg_resources import pkg_resources
import mktxp.cli.checks.chk_pv import mktxp.cli.checks.chk_pv
from mktxp.utils.utils import run_cmd
from mktxp.cli.options import MKTXPOptionsParser, MKTXPCommands from mktxp.cli.options import MKTXPOptionsParser, MKTXPCommands
from mktxp.cli.config.config import config_handler, ConfigEntry from mktxp.cli.config.config import config_handler, ConfigEntry
from mktxp.basep import MKTXPProcessor from mktxp.basep import MKTXPProcessor
@@ -36,7 +38,7 @@ class MKTXPDispatcher:
self.print_info() self.print_info()
elif args['sub_cmd'] == MKTXPCommands.SHOW: elif args['sub_cmd'] == MKTXPCommands.SHOW:
self.show_entries() self.show_entries(args)
elif args['sub_cmd'] == MKTXPCommands.ADD: elif args['sub_cmd'] == MKTXPCommands.ADD:
self.add_entry(args) self.add_entry(args)
@@ -61,40 +63,46 @@ class MKTXPDispatcher:
''' Prints MKTXP version info ''' Prints MKTXP version info
''' '''
version = pkg_resources.require("mktxp")[0].version 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): def print_info(self):
''' Prints MKTXP general info ''' Prints MKTXP general info
''' '''
print('Mikrotik RouterOS Prometheus Exporter: {}'.format(self.option_parser.script_name)) print(f'{self.option_parser.script_name}: {self.option_parser.description}')
print(self.option_parser.description)
def show_entries(self): def show_entries(self, args):
for entryname in config_handler.registered_entries(): if args['configpath']:
entry = config_handler.entry(entryname) print(f'MKTX config path: {config_handler.usr_conf_data_path}')
else:
print('[{}]'.format(entryname)) for entryname in config_handler.registered_entries():
for field in entry._fields: if args['entry_name'] and entryname != args['entry_name']:
print(' {}: {}'.format(field, getattr(entry, field))) continue
print() 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): def add_entry(self, args):
args.pop('sub_cmd', None) entry_args = {key: value for key, value in args.items() if key not in set(['sub_cmd', 'entry_name'])}
entry_name = args['entry_name'] config_handler.register_entry(entry_name = args['entry_name'], entry_args = entry_args)
args.pop('entry_name', None)
entry_info = ConfigEntry.MKTXPEntry(**args) def edit_entry(self, args):
config_handler.register_entry(entry_name = entry_name, entry_info = entry_info) editor = args['editor']
if not editor:
print(f'No editor to edit the following file with: {config_handler.usr_conf_data_path}')
def edit_entry(self, args): subprocess.check_call([editor, config_handler.usr_conf_data_path])
pass
def delete_entry(self, args): def delete_entry(self, args):
config_handler.unregister_entry(entry_name = args['entry_name']) config_handler.unregister_entry(entry_name = args['entry_name'])
def start_export(self, args): def start_export(self, args):
MKTXPProcessor.start() MKTXPProcessor.start()

View File

@@ -14,7 +14,7 @@
import os import os
from argparse import ArgumentParser, HelpFormatter from argparse import ArgumentParser, HelpFormatter
from mktxp.cli.config.config import config_handler, MKTXPConfigKeys 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: class MKTXPCommands:
@@ -29,13 +29,13 @@ class MKTXPCommands:
@classmethod @classmethod
def commands_meta(cls): def commands_meta(cls):
return ''.join(('{', return ''.join(('{',
'{}, '.format(cls.INFO), f'{cls.INFO}, ',
'{}'.format(cls.VERSION), f'{cls.VERSION}, ',
'{}'.format(cls.SHOW), f'{cls.SHOW}, ',
'{}'.format(cls.ADD), f'{cls.ADD}, ',
'{}'.format(cls.EDIT), f'{cls.EDIT}, ',
'{}'.format(cls.DELETE), f'{cls.DELETE}, ',
'{}'.format(cls.START), f'{cls.START}',
'}')) '}'))
class MKTXPOptionsParser: class MKTXPOptionsParser:
@@ -45,7 +45,8 @@ class MKTXPOptionsParser:
self._script_name = 'MKTXP' self._script_name = 'MKTXP'
self._description = \ self._description = \
''' '''
Prometheus Exporter for Mikrotik RouterOS Devices Prometheus Exporter for Mikrotik RouterOS Devices.
''' '''
@property @property
@@ -94,9 +95,13 @@ class MKTXPOptionsParser:
formatter_class=MKTXPHelpFormatter) formatter_class=MKTXPHelpFormatter)
# Show command # Show command
subparsers.add_parser(MKTXPCommands.SHOW, show_parser = subparsers.add_parser(MKTXPCommands.SHOW,
description = 'Displays MKTXP router entries', description = 'Displays MKTXP config router entries',
formatter_class=MKTXPHelpFormatter) 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 command
add_parser = subparsers.add_parser(MKTXPCommands.ADD, add_parser = subparsers.add_parser(MKTXPCommands.ADD,
@@ -159,15 +164,14 @@ class MKTXPOptionsParser:
help = "Export CAPsMAN metrics", help = "Export CAPsMAN metrics",
action = 'store_true') action = 'store_true')
#'hostname', 'port', 'username', 'password', 'use_ssl', 'ssl_certificate', 'dhcp', 'dhcp_lease', 'pool', 'interface', 'monitor', 'route', 'wireless', and 'capsman'
# Edit command # Edit command
edit_parser = subparsers.add_parser(MKTXPCommands.EDIT, edit_parser = subparsers.add_parser(MKTXPCommands.EDIT,
description = 'Edits an existing MKTXP router entry', description = 'Edits an existing MKTXP router entry',
formatter_class=MKTXPHelpFormatter) formatter_class=MKTXPHelpFormatter)
required_args_group = edit_parser.add_argument_group('Required Arguments') edit_parser.add_argument('-ed', '--editor', dest='editor',
self._add_entry_name(required_args_group, registered_only = True, help = "Name of entry to edit") help = f"command line editor to use ({self._system_editor()} by default)",
default = self._system_editor(),
type = str)
# Delete command # Delete command
delete_parser = subparsers.add_parser(MKTXPCommands.DELETE, delete_parser = subparsers.add_parser(MKTXPCommands.DELETE,
@@ -181,7 +185,6 @@ class MKTXPOptionsParser:
description = 'Starts exporting Miktorik Router Metrics', description = 'Starts exporting Miktorik Router Metrics',
formatter_class=MKTXPHelpFormatter) formatter_class=MKTXPHelpFormatter)
# Options checking # Options checking
def _check_args(self, args, parser): def _check_args(self, args, parser):
''' Validation of supplied CLI arguments ''' Validation of supplied CLI arguments
@@ -189,13 +192,13 @@ class MKTXPOptionsParser:
# check if there is a cmd to execute # check if there is a cmd to execute
self._check_cmd_args(args, parser) 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 # Registered Entry name could be a partial match, need to expand
args['entry_name'] = UniquePartialMatchList(config_handler.registered_entries()).find(args['entry_name']) 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()): 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() parser.exit()
def _check_cmd_args(self, args, parser): def _check_cmd_args(self, args, parser):
@@ -227,7 +230,7 @@ class MKTXPOptionsParser:
''' '''
path_arg = FSHelper.full_path(path_arg) path_arg = FSHelper.full_path(path_arg)
if not (os.path.exists(path_arg) and os.path.isdir(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: else:
return path_arg return path_arg
@@ -237,16 +240,16 @@ class MKTXPOptionsParser:
''' '''
path_arg = FSHelper.full_path(path_arg) path_arg = FSHelper.full_path(path_arg)
if not (os.path.exists(path_arg) and os.path.isfile(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: else:
return path_arg return path_arg
@staticmethod @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', parser.add_argument('-en', '--entry-name', dest = 'entry_name',
type = str, type = str,
metavar = config_handler.registered_entries() if registered_only else None, 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, choices = UniquePartialMatchList(config_handler.registered_entries())if registered_only else None,
help = help) help = help)
@@ -255,6 +258,19 @@ class MKTXPOptionsParser:
required_args_group = parser.add_argument_group('Required Arguments') required_args_group = parser.add_argument_group('Required Arguments')
MKTXPOptionsParser._add_entry_name(required_args_group) 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): class MKTXPHelpFormatter(HelpFormatter):
''' Custom formatter for ArgumentParser ''' Custom formatter for ArgumentParser
@@ -284,4 +300,3 @@ class MKTXPHelpFormatter(HelpFormatter):
parts[-1] += ' %s'%args_string parts[-1] += ' %s'%args_string
return ', '.join(parts) return ', '.join(parts)

View File

@@ -13,6 +13,7 @@
import ssl import ssl
import socket import socket
from datetime import datetime
from routeros_api import RouterOsApiPool from routeros_api import RouterOsApiPool
@@ -28,7 +29,7 @@ class RouterAPIConnection:
self.router_entry = router_entry self.router_entry = router_entry
ctx = None 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 = ssl.create_default_context()
ctx.set_ciphers('ADH:@SECLEVEL=0') ctx.set_ciphers('ADH:@SECLEVEL=0')
@@ -38,7 +39,7 @@ class RouterAPIConnection:
password = self.router_entry.password, password = self.router_entry.password,
port = self.router_entry.port, port = self.router_entry.port,
plaintext_login = True, plaintext_login = True,
use_ssl = True, use_ssl = self.router_entry.use_ssl,
ssl_context = ctx) ssl_context = ctx)
self.connection.socket_timeout = 2 self.connection.socket_timeout = 2
@@ -58,12 +59,13 @@ class RouterAPIConnection:
def connect(self): def connect(self):
if self.is_connected(): if self.is_connected():
return return
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try: 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() 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: 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 raise
def router_api(self): def router_api(self):

View File

@@ -39,14 +39,14 @@ def temp_dir(quiet = True):
class CmdProcessingError(Exception): class CmdProcessingError(Exception):
pass pass
def run_cmd(cmd, shell = False): def run_cmd(cmd, shell = False, quiet = False):
''' Runs shell commands in a separate process ''' Runs shell commands in a separate process
''' '''
if not shell: if not shell:
cmd = shlex.split(cmd) cmd = shlex.split(cmd)
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell = shell) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell = shell)
output = proc.communicate()[0].decode('utf-8') output = proc.communicate()[0].decode('utf-8')
if proc.returncode != 0: if proc.returncode != 0 and not quiet:
raise CmdProcessingError(output) raise CmdProcessingError(output)
return output return output