adds additional capabilities to diff of network configs

* diff functions now split out for easier troubleshooting
* added dumps() function to serialize config objects to strings
* difference() can now expand all blocks instead of just singluar blocks
pull/16663/head
Peter Sprygada 8 years ago
parent cddeadcab6
commit 80ab80b6fd

@ -28,6 +28,7 @@ from ansible.module_utils.network import to_list
DEFAULT_COMMENT_TOKENS = ['#', '!'] DEFAULT_COMMENT_TOKENS = ['#', '!']
class ConfigLine(object): class ConfigLine(object):
def __init__(self, text): def __init__(self, text):
@ -106,6 +107,19 @@ def parse(lines, indent, comment_tokens=None):
return config return config
def dumps(objects, output='block'):
if output == 'block':
items = [c.raw for c in objects]
elif output == 'commands':
items = [c.text for c in objects]
elif output == 'lines':
items = list()
for obj in objects:
line = list()
line.extend([p.text for p in obj.parents])
line.append(obj.text)
items.append(' '.join(line))
return '\n'.join(items)
class NetworkConfig(object): class NetworkConfig(object):
@ -113,6 +127,10 @@ class NetworkConfig(object):
self.indent = indent or 1 self.indent = indent or 1
self._config = list() self._config = list()
self._device_os = device_os self._device_os = device_os
self._syntax = 'block' # block, lines, junos
if self._device_os == 'junos':
self._syntax = 'junos'
if contents: if contents:
self.load(contents) self.load(contents)
@ -121,21 +139,10 @@ class NetworkConfig(object):
def items(self): def items(self):
return self._config return self._config
@property
def lines(self):
lines = list()
for item, next_item in get_next(self.items):
if next_item is None:
lines.append(item.line)
elif not next_item.line.startswith(item.line):
lines.append(item.line)
return lines
def __str__(self): def __str__(self):
if self._device_os == 'junos': if self._device_os == 'junos':
lines = self.to_lines(self.expand(self.items)) return dumps(self.expand_line(self.items), 'lines')
return '\n'.join(lines) return dumps(self.expand_line(self.items))
return self.to_block(self.expand(self.items))
def load(self, contents): def load(self, contents):
self._config = parse(contents, indent=self.indent) self._config = parse(contents, indent=self.indent)
@ -152,6 +159,21 @@ class NetworkConfig(object):
if parents == path[:-1]: if parents == path[:-1]:
return item return item
def get_object(self, path):
for item in self.items:
if item.text == path[-1]:
parents = [p.text for p in item.parents]
if parents == path[:-1]:
return item
def get_section_objects(self, path):
if not isinstance(path, list):
path = [path]
obj = self.get_object(path)
if not obj:
raise ValueError('path does not exist in config')
return self.expand_section(obj)
def search(self, regexp, path=None): def search(self, regexp, path=None):
regex = re.compile(r'^%s' % regexp, re.M) regex = re.compile(r'^%s' % regexp, re.M)
@ -177,7 +199,7 @@ class NetworkConfig(object):
regexp = r'%s' % regexp regexp = r'%s' % regexp
return re.findall(regexp, str(self)) return re.findall(regexp, str(self))
def expand(self, objs): def expand_line(self, objs):
visited = set() visited = set()
expanded = list() expanded = list()
for o in objs: for o in objs:
@ -189,35 +211,6 @@ class NetworkConfig(object):
visited.add(o) visited.add(o)
return expanded return expanded
def to_lines(self, objects):
lines = list()
for obj in objects:
line = list()
line.extend([p.text for p in obj.parents])
line.append(obj.text)
lines.append(' '.join(line))
return lines
def to_block(self, section):
return '\n'.join([item.raw for item in section])
def get_section(self, path):
try:
section = self.get_section_objects(path)
if self._device_os == 'junos':
return self.to_lines(section)
return self.to_block(section)
except ValueError:
return list()
def get_section_objects(self, path):
if not isinstance(path, list):
path = [path]
obj = self.get_object(path)
if not obj:
raise ValueError('path does not exist in config')
return self.expand_section(obj)
def expand_section(self, configobj, S=None): def expand_section(self, configobj, S=None):
if S is None: if S is None:
S = list() S = list()
@ -228,74 +221,73 @@ class NetworkConfig(object):
self.expand_section(child, S) self.expand_section(child, S)
return S return S
def get_object(self, path): def expand_block(self, objects, visited=None):
for item in self.items: items = list()
if item.text == path[-1]:
parents = [p.text for p in item.parents]
if parents == path[:-1]:
return item
def get_children(self, path):
obj = self.get_object(path)
if obj:
return obj.children
def difference(self, other, path=None, match='line', replace='line'): if not visited:
updates = list() visited = set()
config = self.items for o in objects:
if path: items.append(o)
config = self.get_children(path) or list() visited.add(o)
for child in o.children:
items.extend(self.expand_block([child], visited))
if match == 'line': return items
for item in config:
if item not in other.items:
updates.append(item)
elif match == 'strict': def diff_line(self, other):
if path: diff = list()
current = other.get_children(path) or list() for item in self.items:
else: if item not in other.items:
current = other.items diff.append(item)
return diff
def diff_strict(self, other):
diff = list()
for index, item in enumerate(self.items):
try:
if item != other.items[index]:
diff.append(item)
except IndexError:
diff.append(item)
return diff
def diff_exact(self, other):
diff = list()
if len(other.items) != len(self.items):
diff.extend(self.items)
else:
for ours, theirs in itertools.izip(self.items, other.items):
if ours != theirs:
diff.extend(self.items)
break
return diff
for index, item in enumerate(config):
try:
if item != current[index]:
updates.append(item)
except IndexError:
updates.append(item)
elif match == 'exact':
if path:
current = other.get_children(path) or list()
else:
current = other.items
if len(current) != len(config): def difference(self, other, match='line', replace='line'):
updates.extend(config) try:
else: func = getattr(self, 'diff_%s' % match)
for ours, theirs in itertools.izip(config, current): updates = func(other)
if ours != theirs: except AttributeError:
updates.extend(config) raise TypeError('invalid value for match keyword')
break
if self._device_os == 'junos': if self._device_os == 'junos':
return updates return updates
changes = list() if replace == 'block':
for update in updates: parents = list()
if replace == 'block': for u in updates:
if update.parents: if u.parents is None:
changes.append(update.parents[-1]) if u not in parents:
for child in update.parents[-1].children: parents.append(u)
changes.append(child)
else: else:
changes.append(update) for p in u.parents:
else: if p not in parents:
changes.append(update) parents.append(p)
updates = self.expand(changes)
return self.expand_block(parents)
return [item.text for item in updates] return self.expand_line(updates)
def replace(self, patterns, repl, parents=None, add_if_missing=False, def replace(self, patterns, repl, parents=None, add_if_missing=False,
ignore_whitespace=True): ignore_whitespace=True):

Loading…
Cancel
Save