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
This commit is contained in:
parent
cddeadcab6
commit
80ab80b6fd
1 changed files with 92 additions and 100 deletions
|
@ -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):
|
||||||
|
items = list()
|
||||||
|
|
||||||
|
if not visited:
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
for o in objects:
|
||||||
|
items.append(o)
|
||||||
|
visited.add(o)
|
||||||
|
for child in o.children:
|
||||||
|
items.extend(self.expand_block([child], visited))
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def diff_line(self, other):
|
||||||
|
diff = list()
|
||||||
for item in self.items:
|
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_children(self, path):
|
|
||||||
obj = self.get_object(path)
|
|
||||||
if obj:
|
|
||||||
return obj.children
|
|
||||||
|
|
||||||
def difference(self, other, path=None, match='line', replace='line'):
|
|
||||||
updates = list()
|
|
||||||
|
|
||||||
config = self.items
|
|
||||||
if path:
|
|
||||||
config = self.get_children(path) or list()
|
|
||||||
|
|
||||||
if match == 'line':
|
|
||||||
for item in config:
|
|
||||||
if item not in other.items:
|
if item not in other.items:
|
||||||
updates.append(item)
|
diff.append(item)
|
||||||
|
return diff
|
||||||
|
|
||||||
elif match == 'strict':
|
def diff_strict(self, other):
|
||||||
if path:
|
diff = list()
|
||||||
current = other.get_children(path) or list()
|
for index, item in enumerate(self.items):
|
||||||
else:
|
|
||||||
current = other.items
|
|
||||||
|
|
||||||
for index, item in enumerate(config):
|
|
||||||
try:
|
try:
|
||||||
if item != current[index]:
|
if item != other.items[index]:
|
||||||
updates.append(item)
|
diff.append(item)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
updates.append(item)
|
diff.append(item)
|
||||||
|
return diff
|
||||||
|
|
||||||
elif match == 'exact':
|
def diff_exact(self, other):
|
||||||
if path:
|
diff = list()
|
||||||
current = other.get_children(path) or list()
|
if len(other.items) != len(self.items):
|
||||||
|
diff.extend(self.items)
|
||||||
else:
|
else:
|
||||||
current = other.items
|
for ours, theirs in itertools.izip(self.items, other.items):
|
||||||
|
|
||||||
if len(current) != len(config):
|
|
||||||
updates.extend(config)
|
|
||||||
else:
|
|
||||||
for ours, theirs in itertools.izip(config, current):
|
|
||||||
if ours != theirs:
|
if ours != theirs:
|
||||||
updates.extend(config)
|
diff.extend(self.items)
|
||||||
break
|
break
|
||||||
|
return diff
|
||||||
|
|
||||||
|
|
||||||
|
def difference(self, other, match='line', replace='line'):
|
||||||
|
try:
|
||||||
|
func = getattr(self, 'diff_%s' % match)
|
||||||
|
updates = func(other)
|
||||||
|
except AttributeError:
|
||||||
|
raise TypeError('invalid value for match keyword')
|
||||||
|
|
||||||
if self._device_os == 'junos':
|
if self._device_os == 'junos':
|
||||||
return updates
|
return updates
|
||||||
|
|
||||||
changes = list()
|
|
||||||
for update in updates:
|
|
||||||
if replace == 'block':
|
if replace == 'block':
|
||||||
if update.parents:
|
parents = list()
|
||||||
changes.append(update.parents[-1])
|
for u in updates:
|
||||||
for child in update.parents[-1].children:
|
if u.parents is None:
|
||||||
changes.append(child)
|
if u not in parents:
|
||||||
|
parents.append(u)
|
||||||
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 [item.text for item in updates]
|
return self.expand_block(parents)
|
||||||
|
|
||||||
|
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…
Add table
Reference in a new issue