Source code for ncdiff.runningconfig

import re
import logging

# create a logger for this module
logger = logging.getLogger(__name__)


class ListDiff(object):
    '''ListDiff

    Abstraction of diff between two lists. Each item in the list is a tuple.
    If all tuples are same, two lists are considered to be same. This class is
    intended to be used by class RunningConfigDiff.

    Attributes
    ----------
    diff : `list`
        A list whose items are tuples. The first item in the tuple is
        the config line and the second item is its next level config. If a
        config line does not have next level config, the second item value is
        None. Otherwise, the value is a list of tuples. The third item in the
        tuple has three possible values: '+' means the config is added; '-'
        means it is deleted; '' means it remiains unchnaged but for some reason
        it is needed (e.g., as a position reference).
    '''

    def __init__(self, list1, list2):
        self.list1 = list1
        self.list2 = list2

    @property
    def diff(self):
        return self.compare(self.list1, self.list2)

    @staticmethod
    def compare(list1, list2):
        diff_1 = []
        key_list_1 = [i[0] for i in list1]
        key_list_2 = [i[0] for i in list2]
        common_key_list = [k for k in key_list_1 if k in key_list_2]
        if common_key_list:
            common_1 = list(filter(lambda x: x in common_key_list, key_list_1))
            common_2 = list(filter(lambda x: x in common_key_list, key_list_2))
            common_key_list = []
            list1_index = 0
            for k2 in common_2:
                if k2 in common_1[list1_index:]:
                    common_key_list.append(k2)
                    list1_index = common_1.index(k2)

        previous_index_1 = previous_index_2 = 0
        for key in common_key_list:
            current_index_1 = key_list_1.index(key)
            current_index_2 = key_list_2.index(key)

            # Stuff in list1 before a common key but not in list2
            for k, v, i in list1[previous_index_1:current_index_1]:
                diff_1.append((k, v, '-'))

            # Stuff in list2 before a common key but not in list1
            for k, v, i in list2[previous_index_2:current_index_2]:
                diff_1.append((k, v, '+'))

            # Stuff of the common key itself
            if list1[current_index_1][1] == list2[current_index_2][1]:
                if list1[current_index_1][1] is None:
                    diff_1.append((key, None, '?'))
                else:
                    diff_1.append((key, [
                        (k, v, '')
                        for k, v, i in list1[current_index_1][1]], '?'))
            else:
                if (
                    list1[current_index_1][1] is not None and
                    list2[current_index_2][1] is not None
                ):
                    diff_1.append((key, ListDiff.compare(
                        list1[current_index_1][1],
                        list2[current_index_2][1],
                    ), ''))
                elif list1[current_index_1][1] is not None:
                    diff_1.append((key, [
                        (k, v, '-')
                        for k, v, i in list1[current_index_1][1]], ''))
                elif list2[current_index_2][1] is not None:
                    diff_1.append((key, [
                        (k, v, '+')
                        for k, v, i in list2[current_index_2][1]], ''))

            previous_index_1 = current_index_1 + 1
            previous_index_2 = current_index_2 + 1

        # Stuff after all common keys
        for k, v, i in list1[previous_index_1:]:
            diff_1.append((k, v, '-'))
        for k, v, i in list2[previous_index_2:]:
            diff_1.append((k, v, '+'))

        # Cleanup
        diff_2 = []
        keys_before = set()
        for index, item in enumerate(diff_1):
            key, value, info = item
            if info == '?':
                keys_after = set([k for k, v, i in diff_1[index+1:]])
                if keys_before & keys_after:
                    diff_2.append((key, value, ''))
            else:
                diff_2.append((key, value, info))
            keys_before.add(key)

        return diff_2


[docs]class RunningConfigDiff(object): '''RunningConfigDiff Abstraction of diff between two Cisco running-configs. It supports str() which returns a string showing the differences between running1 and running2. Attributes ---------- running1 : `str` First Cisco running-config. running2 : `str` Second Cisco running-config. diff : `list` A list from class ListDiff attribute diff. ''' def __init__(self, running1, running2): ''' __init__ instantiates a RunningConfigDiff instance. ''' self.running1 = running1 self.running2 = running2 def __bool__(self): return bool(self.diff) def __str__(self): return self.list2config(self.diff, diff_type='') def __eq__(self, other): if str(self) == str(other): return True else: return False @property def diff(self): list1 = self.running2list(self.running1) list2 = self.running2list(self.running2) diff_list = ListDiff(list1, list2).diff if diff_list: return diff_list else: return None def running2list(self, str_in): str_in = str_in.replace('exit-address-family', ' exit-address-family') return self.config2list(str_in) def config2list(self, str_in): list_ret = [] last_line = '' last_section = '' last_indentation = 0 for line in str_in.splitlines(): if len(line.strip()) > 22 and \ line[:22] == 'Building configuration': continue if len(line.strip()) > 21 and \ line[:21] == 'Current configuration': continue if len(line.strip()) == 0: continue if re.search('^ *!', line): continue if line[0] == ' ': current_indentation = len(re.search('^ *', line).group(0)) if last_indentation == 0: last_indentation = current_indentation # There might be special cases. For example, the following # running-config: # ip dhcp class CLASS1 # relay agent information # relay-information hex 01040101030402020102 # should be considered as: # ip dhcp class CLASS1 # relay agent information # relay-information hex 01040101030402020102 if current_indentation < last_indentation: last_section += ' ' * (last_indentation + 1) + \ line[current_indentation:] + '\n' else: last_section += line[last_indentation:] + '\n' else: if last_line: if last_indentation > 0: list_ret.append(( last_line, self.config2list(last_section), '')) else: list_ret.append((last_line, None, '')) last_line = line last_section = '' last_indentation = 0 if last_indentation > 0: list_ret.append((last_line, self.config2list(last_section), '')) else: list_ret.append((last_line, None, '')) return list_ret def list2config(self, list_in, diff_type=None): str_ret = '' if list_in is None: return str_ret for k, v, i in list_in: if k == '': continue if diff_type == '': local_diff_type = i else: local_diff_type = diff_type if diff_type is None: str_ret += ' ' + k + '\n' else: prefix = local_diff_type if local_diff_type != '' else ' ' str_ret += prefix + ' ' + k + '\n' if v is not None: str_ret += self.indent( self.list2config(v, diff_type=local_diff_type), ) return str_ret def indent(self, str_in): str_ret = '' for line in str_in.splitlines(): if line: if line[0] in '-+': diff_type = line[0] line = line[1:] str_ret += diff_type + ' ' + line + '\n' else: str_ret += ' ' + line + '\n' return str_ret