def getvendor(self): """ """ vendor = None for commandset in self.commandsets: if not (hasstring(commandset['command']) and (hasstring(commandset['result']) or haslist(commandset['result']))): continue lines = self.command(commandset['command']) if hasstring(commandset['result']): for line in lines: if line == commandset['result']: vendor = commandset['vendor'] break elif haslist(commandset['result']): for line in lines: if line in commandset['result']: vendor = commandset['vendor'] break if vendor: break if not vendor: vendor = 'unknown' return vendor
def removeentry(self, branch, find=''): """Removes entries from remote host based onto <find>. If <find> is note set, it will remove everything under that <branch>. That method compares the configuration before and after command execution to detect changes and form the bool result. :param branch: (str) Branch of commands. :param find: (str) Mikrotik CLI filter. :return: (bool) True when <propvals> have changed the configuration. If for some reason, configuration remains unchanged False will be returned, but the <self.errc> will be zero. It will also return False in case of error. """ if not hasstring(branch): return self.err(1) branch = branchfix(branch) if not haskey(self.branch, branch, dict): return self.err(2, branch) if self.branch[branch]['class'] != 'list': return False if self.branch[branch]['readonly']: return False if not hasstring(find): find = '' # Count entries before remove command command = ':put [:len [{} find]]'.format(branch) entries_c0 = self.command(command) if not entries_c0: return self.err(3) # Remove command command = '{} remove [find {}]'.format(branch, find) results = self.command(command, hasstdout=False) if results: return self.err(4, results) # Count entries after remove command command = ':put [:len [{} find]]'.format(branch) entries_c1 = self.command(command) if not entries_c1: return self.err(5) # Compare Before and After remove command if entries_c0 != entries_c1: return True # Changed return False # Not changed != Failed
def upload_file(self, local, remote): """Uploads local files to remote host. :param local: (str) Local path. :param remote: (str) Remote path. :return: (bool) True on success, False on failure. """ if not isfile(local): return self.err(1, local) if not hasstring(remote): return self.err(2, remote) if self.isdir_remote(remote) and remote[-1] != '/': remote += '/' if remote[-1] == '/': remote += local.split('/')[-1] if not self.isdir_remote('/'.join(remote.split('/')[:-1]), True): return self.err(3, remote) try: self.connection.put(local, remote) return True except Exception: _, message = getexcept() return self.err(4, message)
def download_file(self, remote, local): """Downloads remote files to local path. :param remote: (str) Remote path. :param local: (str) Local path. :return: (bool) True on success, False on failure. """ if not self.isfile_remote(remote): return self.err(1, remote) if not hasstring(local): return self.err(2, local) if isdir(local) and local[-1] != '/': local += '/' if local[-1] == '/': local += remote.split('/')[:-1] if not isdir('/'.join(local.split('/')[:-1]), True): return self.err(3, local) try: self.connection.get(remote, local) return True except Exception: _, message = getexcept() return self.err(4, message)
def mkdir_remote(self, data): """Creates remote directories. :param data: (str) Path. :return: (bool) True on success, False on failure. """ if not hasstring(data): return False if data[-1] == '/': data = data[:-1] path = data.split('/') path_c = len(path) index = 1 for index in range(0, path_c): try: if stat.S_ISREG( self.connection.stat( '/'.join(path[0:index + 1])).st_mode): return False except IOError: index -= 1 break if index < path_c - 1: try: for index in range(index + 1, path_c): self.connection.mkdir('/'.join(path[0:index + 1])) except IOError: _, message = getexcept() return self.err(1, message) return True
def wtrim(data): """Trim white space. :param data: (str) Input to be trimmed. :return: (str) The input trimmed. """ if hasstring(data): return re.sub(r'\s+', ' ', data).strip() return ''
def ifnull(data, payload=''): """Equivalent to IFNULL of MySQL. :param data: (str) Input to be checked. :param payload: (str) Payload to return if input is empty. :return: (str) Data or payload. """ if hasstring(data): return data return payload
def branchfix(data): """Applies some fixes to branch part of the command. :param data: (str) Branch. :return: Branch fixed. """ if hasstring(data): return re.sub(r'\s\s+', ' ', data.strip()).replace('/ ', '/') return ''
def properties_to_list(data): """Converts array of properties to list. :param data: (str / list) List or Comma/Space seperated properties. :return: (list) List of properties. """ if haslist(data): return data if hasstring(data): return wtrim(data.replace(',', ' ')).split(' ') return []
def getinfo_interfaces(self, interface=None): """Meta method. Retrieves information from Router. """ branch = '/interface' properties = ['name', 'type', 'mac-address'] results1 = self.getvalues(branch, properties, find='dynamic!=yes', iid=True) branch = '/ip address' properties = ['address', 'interface'] results2 = self.getvalues(branch, properties) results = {} if results1: for result1 in results1: name = None if haskey(result1, 'name'): name = result1['name'] if not hasstring(name): continue results[name] = { '.id': result1['.id'], 'type': result1['type'], 'mac_address': result1['mac-address'], 'ip_address': [] } # Bug: PPPoE intrfaces from ISP will have irregular network # address if results2: for result2 in results2: if result2['interface'] == name: address = result2['address'].split('/') network = ipaddress.IPv4Network(result2[ 'address'].decode('utf-8'), False) ip_address = { 'address': address[0], 'nbits': address[1], 'netmask': str( network.netmask).encode('utf-8'), 'network': str( network.network_address).encode('utf-8'), 'broadcast': str( network.broadcast_address).encode('utf-8') } results[name]['ip_address'].append(ip_address) return results
def __init__(self, host, port=22, username='******', password='', pkey_string='', pkey_file=''): """Initializes a SSHCommon object. :param host: (str) Remote host. It can be IPv4, IPv6 or hostname. :param port: (int) SSH Port. :param username: (str) Username. :param password: (str) Password. :param pkey_string: (file) Private Key. :param pkey_file: (file) Private Key Path. :return: (obj) SSH Client. """ super(SSHCommon, self).__init__() if ishost(host): self.host = host else: self.err(1, host) if isport(port): self.port = int(port) else: self.err(2, port) if hasstring(username): self.username = username else: self.err(3, username) # There is no need to validate the existence of (password OR pkey_file) # because the router might have empty password and no public key set. if pkey_file: if not isfile(pkey_file): self.err(4, pkey_file) if pkey_string: if not ispkey(pkey_string): self.err(5) self.password = password self.pkey_string = pkey_string self.pkey_file = pkey_file if self.errc() == 0: self.status = 0
def isfile_remote(self, data): """Checks if remote file exists. :param data: (str) Path. :return: (bool) True on success, False on failure. """ if not hasstring(data): return False try: return stat.S_ISREG(self.connection.stat(data).st_mode) except IOError: return False
def checkline_falsepos(self, data=''): """Checks false-positives. For error messages that begin with the word 'failure'. :param data: (str) Data to be checked. :return: (bool) True on success, False on failure. """ if not hasstring(data): return self.err(1) for regex in self.message_regexes: if regex.match(data): return True return False
def upload(self, local, remote): """Uploads local files or directories to remote host. :param local: (str) Local path. :param remote: (str) Remote path. :return: (bool) True on success, False on failure. """ if not hasstring(remote): return self.err(1) if isfile(local): return self.upload_file(local, remote) if isdir(local): return self.upload_dir(local, remote) return False
def isdir_remote(self, data, makedirs=False): """Checks if remote directory exists. :param data: (str) Path. :param makedirs: (bool) If True, creates the whole path. Equilevant to <mkdir -p>. :return: (bool) True on success, False on failure. """ if not hasstring(data): return False try: return stat.S_ISDIR(self.connection.stat(data).st_mode) except IOError: if makedirs: return self.mkdir_remote(data)
def checkline(self, data=''): """Checks the input against a list of common errors. :param data: (str) Data to be checked. :return: (bool) True on success, False on failure. """ if not hasstring(data): return True data = data.strip() for index, error in enumerate(self.message_errors): if data.find(error) == 0: if error == 'failure:': return self.checkline_falsepos(data) return self.err(index, data) return True
def writejson(filename, data): """Saves the (dict) data to JSON file. :param filename: (str) Filename to write JSON to. :param data: (dict) Dict to save to JSON file. :return: (bool) True on success, False on failure. """ if not hasstring(filename): return False if not isdir('/'.join(filename.split('/')[:-1]), True): return False if not isinstance(data, dict): return False try: with open(filename, 'w') as handler: json.dump(data, handler) except IOError: return getexcept() return True
def commands(self, commands, raw=False, connect=True): """Executes a list of command on the remote host using the self.command() method. :param commands: (list) The list of commands to be executed. :param raw: (bool) Returns all results without filtering lines that start with #. :param connect: (bool) Connects to host, if it is not connected already. :return: (list) The execution result. """ status = 0 if hasstring(commands): commands = [commands] # maybe if it is only one command, exec it and skip to end elif not haslist(commands): self.err(1) return None if self.status < 1 and connect: if self.connect(): status = 1 if self.status < 1: self.err(2, self.status) return None results = [] for index, command in enumerate(commands): if not command: continue result = self.command(command, raw, hasstdout=False) results.append(result) if self.errc(): return self.err(2, 'commands[{}]: {}'.format(index, command)) if status == 1: self.disconnect() return results
def csv_parse(lines): """Converts the CSV-expected input to array. :param lines: (str / [str]) CSV input - it can either linefeeded string or list. :return: ([str]) String Array or None on error. """ if not lines: return None if not haslist(lines): if hasstring(lines): lines = lines.split('\n') else: return None try: return list(csv.reader(lines, delimiter=',', quotechar='"')) except Exception: getexcept() return None
def propvals_to_dict(data): """Converts string pairs of properties and values to structured dictionary. :param data: (str) Properties and values. :return: (dict) Structured dictionary. """ if not hasstring(data): return {} results = {} regex = re.compile(r'(\S+=".+"|\S+=\S+)') matches = set(regex.findall(data)) for match in matches: eql = match.find('=') results[match[0:eql]] = valuefix(match[eql + 1:]) return results
def test_hasstring(self): """Test if input is string. """ none0 = None int0 = 0 int1 = 1 int2 = -1 str0 = '' str1 = 'abcd' list0 = [] list1 = ['ab', 'cd'] dict0 = {} dict1 = {'ab': 'cd'} set0 = set() set1 = set(['a', 'b', 1, 2]) tuple0 = () tuple1 = ('a', 'b', 1, 2) self.assertFalse(valid.hasstring(none0)) self.assertFalse(valid.hasstring(int0)) self.assertFalse(valid.hasstring(int1)) self.assertFalse(valid.hasstring(int2)) self.assertFalse(valid.hasstring(str0)) self.assertTrue(valid.hasstring(str1)) # assertTrue self.assertFalse(valid.hasstring(list0)) self.assertFalse(valid.hasstring(list1)) self.assertFalse(valid.hasstring(dict0)) self.assertFalse(valid.hasstring(dict1)) self.assertFalse(valid.hasstring(set0)) self.assertFalse(valid.hasstring(set1)) self.assertFalse(valid.hasstring(tuple0)) self.assertFalse(valid.hasstring(tuple1))
def command(self, command, raw=False, connect=True, hasstdout=True): """Executes the <command> on the remote host and returns the results. :param command: (str) The command that has to be executed. :param raw: (bool) Returns all results without filtering lines that start with #. :param connect: (bool) Connects to host, if it is not connected already. :param hasstdout: (bool) Is it expected the command to give output? :return: (list) The execution result. """ status = 0 self.history.append(command) if not hasstring(command): self.err(1) return None if self.status < 1 and connect: if self.connect(): status = 1 if self.status < 1: self.err(2, self.status) return None if self.reseterrors: self.err0() lines = [] try: # stdin, stdout, stderr = ... _, stdout, stderr = self.connection.exec_command(command) lines = stdout.read().replace('\r', '').split('\n') # Mikrotik CLI is not producing stderr. Linux does. if not (hasstring(lines) or haslist(lines)): lines = stderr.read().replace('\r', '').split('\n') except Exception: _, message = getexcept() self.err(3, message) finally: if status == 1: self.disconnect() ##### fix the logic ^^^ \/\/\/\ of get/except/disconnect results = [] if raw: for line in lines: results.append(line) elif lines: for line in lines: if line: if line[0] != '#': results.append(line.strip()) if not haslist(results) and hasstdout: self.err(4, command) return results
def getvalues(self, branch, properties, find='', csvout=False, iid=False): """Retrieves requested values from remote host. :param branch: (str) Branch of commands. :param properties: (str / list) List or Comma / Space separated properties. :param find: (str) Mikrotik CLI filter. :param csvout: (bool) Output in CSV File. :param iid: (bool) Adds $id to output. :return: (list) CSV formatted output. Example output if <csvout=True>: Return of <self.command>: [ "value1,value2,value3,...", "value1,value2,value3,...", ... ] Example output if <csvout=False>: Return of <csv_to_list_dict>: [ { results"variable1": "value1", ... } ... ] """ results = [] if not hasstring(branch): self.err(1) return None branch = branchfix(branch) if not haskey(self.branch, branch, dict): self.err(2, branch) return None properties = properties_to_list(properties) if not haslist(properties): self.err(3) return None command = '' commands = [] if (self.branch[branch]['class'] == 'settings' or (self.branch[branch]['class'] == 'list' and find and find.find('=') < 1)): for prop in properties: commands.append('[{} get {} {}]'.format(branch, find, prop)) command = ':put ({})'.format('.",".'.join(commands)) elif self.branch[branch]['class'] == 'list': if iid: commands.append('$i') for prop in properties: commands.append('[{} get $i {}]'.format(branch, prop)) command = (':foreach i in=[' + branch + ' find ' + find + '] ' 'do={:put (' + '.",".'.join(commands) + ')}') else: self.err(4, branch) return None lines = self.command(command) if not lines or self.errc(): self.err(5, command) return None if csvout: results = lines else: results = csv_to_listdict(properties, lines, self.branch[branch], iid) return results
def setvalues(self, branch, propvals='', find=''): """Sets requested values to remote host. That method compares the configuration before and after command execution to detect changes and form the bool result. :param branch: (str) Branch of commands. :param propvals: (dict) Dictionary of Variables=Values. :param find: (str) Mikrotik CLI filter. :return: (bool) True when <propvals> have changed the configuration. If for some reason, configuration remains unchanged False will be returned, but the <self.errc> will be zero. It will also return False in case of error. """ if not hasstring(branch): return self.err(1) branch = branchfix(branch) if not haskey(self.branch, branch, dict): return self.err(2, branch) if self.branch[branch]['readonly']: return False if not hasstring(propvals): return self.err(3) # Parse propvals to properties propvals_d = propvals_to_dict(propvals) if not hasdict(propvals_d): return self.err(4) properties = [] for prop in propvals_d: properties.append(prop) # Create the find command, if find exists find_command = '' iid = True if self.branch[branch]['class'] == 'list': if hasstring(find): if find.find('=') > 1: find_command = '[find {}] '.format(find) else: iid = False find_command = find + ' ' # Get values before updates getvalues0 = self.getvalues(branch, properties, find, False, iid) if not getvalues0: return self.err(5) # Exit if Get is same as update if not propvals_diff_getvalues(propvals_d, getvalues0): return False # There are no changes to apply # Update command command = '{} set {}{}'.format(branch, find_command, propvals) results = self.command(command, hasstdout=False) if results: self.err(6, command) return self.err(7, results) # Get values after update getvalues1 = self.getvalues(branch, properties, find, False, iid) if not getvalues1: return self.err(8) # Compare Before and After update command if getvalues0 != getvalues1: return True # Changed return False # Not changed != Failed
def addentry(self, branch, propvals=''): """Adds new entries to remote host. That method compares the configuration before and after command execution to detect changes and form the bool result. :param branch: (str) Branch of commands :param propvals: (str) Space seperated pairs of Variable=Value :return: (bool) True when <propvals> have changed the configuration. If for some reason, configuration remains unchanged False will be returned, but the <self.errc> will be zero. It will also return False in case of error. """ if not hasstring(branch): return self.err(1) branch = branchfix(branch) if not haskey(self.branch, branch, dict): return self.err(2, branch) if self.branch[branch]['class'] != 'list': return False if self.branch[branch]['readonly']: return False if not hasstring(propvals): return self.err(3) # Count entries before add command command = ':put [:len [{} find]]'.format(branch) entries_c0 = self.command(command) if not entries_c0: return self.err(4) # Check if such an entry exists if self.branch[branch]['id']: propvals_d = propvals_to_dict(propvals) prop = self.branch[branch]['id'][0] if haskey(propvals_d, prop): command = ':put [:len [{} find {}={}]]'.format(branch, prop, propvals_d[prop]) result = self.command(command) if result[0] != '0': return False # Add Command command = '{} add {}'.format(branch, propvals) results = self.command(command, hasstdout=False) if results: if self.checkline_falsepos(results[0]): self.err0() return False # Not changed != Failed self.err(5, command) return self.err(6, results) # Count entries after add command command = ':put [:len [{} find]]'.format(branch) entries_c1 = self.command(command) if not entries_c1: return self.err(7) # Compare Before and After add command if entries_c0 != entries_c1: return True # Changed return False # Not changed != Failed