Hello community,
here is the log from the commit of package python-ciscoconfparse for
openSUSE:Factory checked in at 2020-06-14 18:33:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-ciscoconfparse (Old)
and /work/SRC/openSUSE:Factory/.python-ciscoconfparse.new.3606 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ciscoconfparse"
Sun Jun 14 18:33:23 2020 rev:15 rq:814535 version:1.5.5
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-ciscoconfparse/python-ciscoconfparse.changes
2020-04-23 18:38:26.817007091 +0200
+++
/work/SRC/openSUSE:Factory/.python-ciscoconfparse.new.3606/python-ciscoconfparse.changes
2020-06-14 18:35:38.902606812 +0200
@@ -1,0 +2,10 @@
+Sat Jun 13 18:24:01 UTC 2020 - Martin Hauke <[email protected]>
+
+- Update to version 1.5.5
+ * Beta-test new function: find_object_branches()
+- Update to version 1.5.4
+ * Modify IPv4Obj().__add__() and IPv6Obj().__add__() (and __sub__())
+ methods return IPv4Obj()/IPv6Obj() objects.
+ * Add support for int(), bin() and hex() on the IPv4Obj() and IPv6Obj()
+
+-------------------------------------------------------------------
Old:
----
ciscoconfparse-1.5.3.tar.gz
New:
----
ciscoconfparse-1.5.5.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-ciscoconfparse.spec ++++++
--- /var/tmp/diff_new_pack.glEnZY/_old 2020-06-14 18:35:39.466608608 +0200
+++ /var/tmp/diff_new_pack.glEnZY/_new 2020-06-14 18:35:39.470608621 +0200
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%bcond_without python2
Name: python-ciscoconfparse
-Version: 1.5.3
+Version: 1.5.5
Release: 0
Summary: Library for parsing, querying and modifying Cisco IOS-style
configurations
License: GPL-3.0-or-later
++++++ ciscoconfparse-1.5.3.tar.gz -> ciscoconfparse-1.5.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ciscoconfparse-1.5.3/CHANGES
new/ciscoconfparse-1.5.5/CHANGES
--- old/ciscoconfparse-1.5.3/CHANGES 2020-04-12 17:53:07.000000000 +0200
+++ new/ciscoconfparse-1.5.5/CHANGES 2020-06-12 09:17:50.000000000 +0200
@@ -1,3 +1,5 @@
+1.5.5 20200612 Beta-test new function: find_object_branches()
+1.5.4 20200412 Modify IPv4Obj().__add__() and IPv6Obj().__add__() (and
__sub__()) methods return IPv4Obj()/IPv6Obj() objects. Add support for int(),
bin() and hex() on the IPv4Obj() and IPv6Obj()
1.5.3 20200412 Fix IPv6Obj().packed and IPv6Obj().exploded; add
IPv4Obj().packed and IPv4Obj().exploded
1.5.2 20200412 Add __add__() and __sub__() to IPv4Obj() and IPv6Obj();
remove use of IPv6Obj().broadcast in IPv6Obj().__contains__()
1.5.1 20200223 Remove embedded junos debugging
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ciscoconfparse-1.5.3/PKG-INFO
new/ciscoconfparse-1.5.5/PKG-INFO
--- old/ciscoconfparse-1.5.3/PKG-INFO 2020-04-12 17:56:08.000000000 +0200
+++ new/ciscoconfparse-1.5.5/PKG-INFO 2020-06-12 11:02:20.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: ciscoconfparse
-Version: 1.5.3
+Version: 1.5.5
Summary: Parse, Audit, Query, Build, and Modify Cisco IOS-style configurations
Home-page: http://www.pennington.net/py/ciscoconfparse/
Author: David Michael Pennington
@@ -202,6 +202,8 @@
- If you're having problems with general python issues, consider
searching for a solution on `Stack Overflow`_. If you can't find a solution
for your problem or need more help, you can `ask a question`_.
- If you're having problems with your Cisco devices, you can open a
case with `Cisco TAC`_; if you prefer crowd-sourcing, you can ask on the Stack
Exchange `Network Engineering`_ site.
+ If you like slack, we also have a `ciscoconfparse NetworkToCode slack
channel`_, where new releases are announced.
+
.. _Unit-Tests:
Unit-Tests
@@ -299,6 +301,8 @@
.. _`ask a question`: http://stackoverflow.com/questions/ask
+ .. _`ciscoconfparse NetworkToCode slack channel`:
https://app.slack.com/client/T09LQ7E9E/C015B4U8MMF/
+
.. _`Secure IOS Template`:
https://www.cymru.com/Documents/secure-ios-template.html
.. _`Center for Internet Security Benchmarks`:
https://learn.cisecurity.org/benchmarks
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ciscoconfparse-1.5.3/README.rst
new/ciscoconfparse-1.5.5/README.rst
--- old/ciscoconfparse-1.5.3/README.rst 2020-02-20 09:14:33.000000000 +0100
+++ new/ciscoconfparse-1.5.5/README.rst 2020-06-10 01:38:55.000000000 +0200
@@ -194,6 +194,8 @@
- If you're having problems with general python issues, consider searching for
a solution on `Stack Overflow`_. If you can't find a solution for your problem
or need more help, you can `ask a question`_.
- If you're having problems with your Cisco devices, you can open a case with
`Cisco TAC`_; if you prefer crowd-sourcing, you can ask on the Stack Exchange
`Network Engineering`_ site.
+If you like slack, we also have a `ciscoconfparse NetworkToCode slack
channel`_, where new releases are announced.
+
.. _Unit-Tests:
Unit-Tests
@@ -291,6 +293,8 @@
.. _`ask a question`: http://stackoverflow.com/questions/ask
+.. _`ciscoconfparse NetworkToCode slack channel`:
https://app.slack.com/client/T09LQ7E9E/C015B4U8MMF/
+
.. _`Secure IOS Template`:
https://www.cymru.com/Documents/secure-ios-template.html
.. _`Center for Internet Security Benchmarks`:
https://learn.cisecurity.org/benchmarks
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ciscoconfparse-1.5.3/ciscoconfparse/ccp_util.py
new/ciscoconfparse-1.5.5/ciscoconfparse/ccp_util.py
--- old/ciscoconfparse-1.5.3/ciscoconfparse/ccp_util.py 2020-04-12
17:51:45.000000000 +0200
+++ new/ciscoconfparse-1.5.5/ciscoconfparse/ccp_util.py 2020-05-05
11:15:34.000000000 +0200
@@ -253,15 +253,35 @@
self.__repr__(), val)
raise ValueError(errmsg)
+ def __int__(self):
+ """Return this object as an integer"""
+ return self.as_decimal
+
+ def __index__(self):
+ """Return this object as an integer (used for hex() and bin()
operations)"""
+ return self.as_decimal
+
def __add__(self, val):
- """Add an integer to IPv4Obj() and return an integer"""
- assert isinstance(val, int)
- return self.as_decimal + val
+ """Add an integer to IPv4Obj() and return an IPv4Obj()"""
+ assert isinstance(val, int), "Cannot add type: '{}' to
{}".format(type(val), self)
+ orig_prefixlen = self.prefixlen
+ total = self.as_decimal + val
+ assert total <= 4294967295, "Max IPv4 integer exceeded"
+ assert total >= 0, "Min IPv4 integer exceeded"
+ retval = IPv4Obj(total)
+ retval.prefixlen = orig_prefixlen
+ return retval
def __sub__(self, val):
- """Subtract an integer from IPv4Obj() and return an integer"""
- assert isinstance(val, int)
- return self.as_decimal - val
+ """Subtract an integer from IPv4Obj() and return an IPv4Obj()"""
+ assert isinstance(val, int), "Cannot subtract type: '{}' from
{}".format(type(val), self)
+ orig_prefixlen = self.prefixlen
+ total = self.as_decimal - val
+ assert total <= 4294967295, "Max IPv4 integer exceeded"
+ assert total >= 0, "Min IPv4 integer exceeded"
+ retval = IPv4Obj(total)
+ retval.prefixlen = orig_prefixlen
+ return retval
def __contains__(self, val):
# Used for "foo in bar"... python calls bar.__contains__(foo)
@@ -312,6 +332,12 @@
"""Returns the length of the network mask as an integer."""
return self.network_object.prefixlen
+ @prefixlen.setter
+ def prefixlen(self, arg):
+ """prefixlen setter method"""
+ self.network_object = IPv4Network('{0}/{1}'.format(str(self.ip_object),
+ arg), strict=False)
+
@property
def prefixlength(self):
"""Returns the length of the network mask as an integer."""
@@ -462,7 +488,7 @@
except TypeError:
if getattr(arg, 'dna', '')=="IPv6Obj":
ip_str = '{0}/{1}'.format(str(arg.ip_object), arg.prefixlen)
- self.network_object = IPv6Network(ip_str, strict = False)
+ self.network_object = IPv6Network(ip_str, strict=False)
self.ip_object = IPv6Address(str(arg.ip_object))
return None
elif isinstance(arg, IPv6Network):
@@ -542,15 +568,37 @@
self.__repr__(), val)
raise ValueError(errmsg)
+ def __int__(self):
+ """Return this object as an integer"""
+ return self.as_decimal
+
+ def __index__(self):
+ """Return this object as an integer (used for hex() and bin()
operations)"""
+ return self.as_decimal
+
def __add__(self, val):
- """Add an integer to IPv6Obj() and return an integer"""
- assert isinstance(val, int)
- return self.as_decimal + val
+ """Add an integer to IPv6Obj() and return an IPv6Obj()"""
+ assert isinstance(val, int), "Cannot add type: '{}' to
{}".format(type(val), self)
+ orig_prefixlen = self.prefixlen
+ total = self.as_decimal + val
+ error = "Max IPv6 integer exceeded"
+ assert total <= 340282366920938463463374607431768211455, error
+ assert total >= 0, "Min IPv4 integer exceeded"
+ retval = IPv6Obj(total)
+ retval.prefixlen = orig_prefixlen
+ return retval
def __sub__(self, val):
- """Subtract an integer from IPv6Obj() and return an integer"""
- assert isinstance(val, int)
- return self.as_decimal - val
+ """Subtract an integer from IPv6Obj() and return an IPv6Obj()"""
+ assert isinstance(val, int), "Cannot subtract type: '{}' from
{}".format(type(val), self)
+ orig_prefixlen = self.prefixlen
+ total = self.as_decimal - val
+ error = "Max IPv6 integer exceeded"
+ assert total <= 340282366920938463463374607431768211455, error
+ assert total >= 0, "Min IPv4 integer exceeded"
+ retval = IPv6Obj(total)
+ retval.prefixlen = orig_prefixlen
+ return retval
def __contains__(self, val):
# Used for "foo in bar"... python calls bar.__contains__(foo)
@@ -602,6 +650,12 @@
"""Returns the length of the network mask as an integer."""
return self.network_object.prefixlen
+ @prefixlen.setter
+ def prefixlen(self, arg):
+ """prefixlen setter method"""
+ self.network_object = IPv6Network('{0}/{1}'.format(str(self.ip_object),
+ arg), strict=False)
+
@property
def prefixlength(self):
"""Returns the length of the network mask as an integer."""
@@ -660,7 +714,7 @@
num_strings = str(self.ip.exploded).split(':')
num_strings.reverse() # reverse the order
return sum(
- [int(num, 16) * (256**idx) for idx, num in enumerate(num_strings)])
+ [int(num, 16) * (65536**idx) for idx, num in
enumerate(num_strings)])
@property
def as_binary_tuple(self):
@@ -840,14 +894,17 @@
- timeout (float): DNS lookup timeout duration (default: 2.0 seconds)
Returns:
- A set() of :class:`~ccp_util.DNSResponse` instances
+ A set([]) of :class:`~ccp_util.DNSResponse` instances. Refer to the
DNSResponse object in these docs for more information.
>>> from ciscoconfparse.ccp_util import dns_query
->>> dns_query('www.pennington.net', "A", "4.2.2.2")
+>>> dns_query('www.pennington.net', "A", "4.2.2.2", timeout=0.5)
set([<DNSResponse "A" result_str="65.19.187.2">])
->>> answer = dns_query('www.pennington.net', 'A', '4.2.2.2')
->>> str(answer.pop())
+>>> response_set = dns_query('www.pennington.net', 'A', '4.2.2.2')
+>>> aa = response_set.pop()
+>>> aa.result_str
'65.19.187.2'
+>>> aa.error_str
+''
>>>
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/ciscoconfparse-1.5.3/ciscoconfparse/ciscoconfparse.py
new/ciscoconfparse-1.5.5/ciscoconfparse/ciscoconfparse.py
--- old/ciscoconfparse-1.5.3/ciscoconfparse/ciscoconfparse.py 2020-02-26
10:40:59.000000000 +0100
+++ new/ciscoconfparse-1.5.5/ciscoconfparse/ciscoconfparse.py 2020-06-12
10:55:01.000000000 +0200
@@ -4,6 +4,7 @@
from difflib import SequenceMatcher
import logging
import time
+import copy
import sys
import re
import os
@@ -51,7 +52,7 @@
from ciscoconfparse.models_junos import JunosCfgLine
r""" ciscoconfparse.py - Parse, Query, Build, and Modify IOS-style configs
- Copyright (C) 2007-2019 David Michael Pennington
+ Copyright (C) 2007-2020 David Michael Pennington
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -492,6 +493,139 @@
offset += indent_child
return lines
+ def find_object_branches(self, branchspec=(), regex_flags=0):
+ """This method iterates over a tuple of regular expressions in
`branchspec` and returns the matching objects in a list of lists. `branchspec`
expects to start at some root ancestor and walk through the nested object
hierarchy (with no limit on depth).
+
+ Previous CiscoConfParse() methods only handled a single parent regex
and single child regex (such as
:func:`~ciscoconfparse.CiscoConfParse.find_parents_w_child`).
+
+ This method dives beyond a simple parent-child relationship to include
entire family 'branches' (i.e. parent, child, grand-children,
great-grand-children, etc). The net result of handling longer chains of
objects is it flattens what would otherwise be nested loops in your scripts;
this makes parsing heavily-nested configuratations like Palo-Alto and F5 much
simpler. Of course, there are plenty of applications for "flatter" config
formats like IOS.
+
+ This method returns a list of lists (of object 'branches') which are
nested to the same depth required in `branchspec`. However, unlike most other
CiscoConfParse() methods, it returns an explicit `None` if there is no object
match. Returning `None` allows a single search over configs that may not be
uniformly nested in every branch.
+
+ Args:
+ - branchspec (tuple): A tuple of python regular expressions to be
matched.
+ Kwargs:
+ - regex_flags : Defaults to 0.
+
+ Returns:
+ - list. A list of lists of matching
:class:`~ciscoconfparse.IOSCfgLine` objects
+
+ .. code-block:: python
+ :emphasize-lines: 28, 29
+
+ >>> config = [
+ ... 'ltm pool FOO {',
+ ... ' members {',
+ ... ' k8s-05.localdomain:8443 {',
+ ... ' address 192.0.2.5',
+ ... ' session monitor-enabled',
+ ... ' state up',
+ ... ' }',
+ ... ' k8s-06.localdomain:8443 {',
+ ... ' address 192.0.2.6',
+ ... ' session monitor-enabled',
+ ... ' state down',
+ ... ' }',
+ ... ' }',
+ ... '}',
+ ... 'ltm pool BAR {',
+ ... ' members {',
+ ... ' k8s-07.localdomain:8443 {',
+ ... ' address 192.0.2.7',
+ ... ' session monitor-enabled',
+ ... ' state down',
+ ... ' }',
+ ... ' }',
+ ... '}',
+ ... ]
+ >>> parse = CiscoConfParse(config, syntax='junos', comment='#')
+ >>>
+ >>> branchspec = (r'ltm\spool', r'members', r'\S+?:\d+',
r'state\sup')
+ >>> branches = parse.find_object_branches(branchspec=branchspec)
+ >>>
+ >>> # We found three branches which matched all regexes...
+ >>> len(branches)
+ 3
+ >>> # Each branch will always match the length of branchspec
+ >>> len(branches[0])
+ 4
+ >>> # Print out one object 'branch'
+ >>> branches[0]
+ [<IOSCfgLine # 0 'ltm pool FOO'>, <IOSCfgLine # 1 ' members'
(parent is # 0)>, <IOSCfgLine # 2 ' k8s-05.localdomain:8443' (parent is
# 1)>, <IOSCfgLine # 5 ' state up' (parent is # 2)>]
+ >>>
+ >>> # Note: `None` in branches[1][-1] because of no regex match
+ >>> branches[1]
+ [<IOSCfgLine # 0 'ltm pool FOO'>, <IOSCfgLine # 1 ' members'
(parent is # 0)>, <IOSCfgLine # 6 ' k8s-06.localdomain:8443' (parent is
# 1)>, None]
+ >>>
+ >>> branches[2]
+ [<IOSCfgLine # 10 'ltm pool BAR'>, <IOSCfgLine # 11 ' members'
(parent is # 10)>, <IOSCfgLine # 12 ' k8s-07.localdomain:8443' (parent
is # 11)>, None]
+ """
+ assert isinstance(branchspec, tuple), "Please enclose the regular
expressions in a Python tuple"
+
+ def list_matching_children(parent_obj, childspec, regex_flags):
+ ## I'm not using parent_obj.re_search_children() because
+ ## re_search_children() doesn't return None for no match...
+
+ # FIXME: Insert debugging here...
+ #print("PARENT "+str(parent_obj))
+
+ # Get the child objects from parent objects
+ if parent_obj is None:
+ children = self._find_line_OBJ(linespec=childspec,
+ exactmatch=False)
+ else:
+ children = parent_obj.children
+
+ # Find all child objects which match childspec...
+ segment_list = [cobj for cobj in children if re.search(childspec,
cobj.text, regex_flags)]
+ # Return [None] if no children matched...
+ if len(segment_list)==0:
+ segment_list = [None]
+
+ # FIXME: Insert debugging here...
+ #print(" SEGMENTS "+str(segment_list))
+ return segment_list
+
+ branches = list()
+ # iterate over the regular expressions in branchspec
+ for idx, childspec in enumerate(branchspec):
+ # FIXME: Insert debugging here...
+ #print("CHILDSPEC "+childspec)
+ if idx==0:
+ # Get matching 'root' objects from the config
+ next_kids = list_matching_children(parent_obj=None,
+ childspec=childspec, regex_flags=regex_flags)
+ # Start growing branches from the segments we received...
+ branches = [[kid] for kid in next_kids]
+
+ else:
+ new_branches = list()
+ for branch in branches:
+ # Extend existing branches into the new_branches
+ if branch[-1] is not None:
+ # Find children to extend the family branch...
+ next_kids = list_matching_children(
+ parent_obj=branch[-1], childspec=childspec,
+ regex_flags=regex_flags)
+
+ # branches 'grow' here with the matching kids above...
+ branch.append('__INSERT_KID_HERE__')
+ for kid in next_kids:
+ # Fork off a new branch for each matching kid...
+ # If there's a faster alternative to deepcopy(), I
+ # haven't found it...
+ tmp = copy.deepcopy(branch)
+ tmp[-1] = kid
+ new_branches.append(tmp)
+ else:
+ branch.append(None)
+ new_branches.append(branch)
+
+ # Ensure we have the most recent branches...
+ branches = new_branches
+
+ return branches
+
def find_interface_objects(self, intfspec, exactmatch=True):
"""Find all :class:`~models_cisco.IOSCfgLine` or
:class:`~models_cisco.NXOSCfgLine` objects whose text
@@ -583,7 +717,6 @@
>>> # The IOSHostnameLine object has a hostname attribute
>>> obj_list[0].hostname
'MyRouterHostname'
- >>>
"""
if not self.factory:
raise ValueError(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ciscoconfparse-1.5.3/ciscoconfparse/version
new/ciscoconfparse-1.5.5/ciscoconfparse/version
--- old/ciscoconfparse-1.5.3/ciscoconfparse/version 2020-04-12
17:53:15.000000000 +0200
+++ new/ciscoconfparse-1.5.5/ciscoconfparse/version 2020-06-12
09:16:56.000000000 +0200
@@ -1 +1 @@
-1.5.3
+1.5.5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/ciscoconfparse-1.5.3/ciscoconfparse.egg-info/PKG-INFO
new/ciscoconfparse-1.5.5/ciscoconfparse.egg-info/PKG-INFO
--- old/ciscoconfparse-1.5.3/ciscoconfparse.egg-info/PKG-INFO 2020-04-12
17:56:05.000000000 +0200
+++ new/ciscoconfparse-1.5.5/ciscoconfparse.egg-info/PKG-INFO 2020-06-12
11:02:16.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: ciscoconfparse
-Version: 1.5.3
+Version: 1.5.5
Summary: Parse, Audit, Query, Build, and Modify Cisco IOS-style configurations
Home-page: http://www.pennington.net/py/ciscoconfparse/
Author: David Michael Pennington
@@ -202,6 +202,8 @@
- If you're having problems with general python issues, consider
searching for a solution on `Stack Overflow`_. If you can't find a solution
for your problem or need more help, you can `ask a question`_.
- If you're having problems with your Cisco devices, you can open a
case with `Cisco TAC`_; if you prefer crowd-sourcing, you can ask on the Stack
Exchange `Network Engineering`_ site.
+ If you like slack, we also have a `ciscoconfparse NetworkToCode slack
channel`_, where new releases are announced.
+
.. _Unit-Tests:
Unit-Tests
@@ -299,6 +301,8 @@
.. _`ask a question`: http://stackoverflow.com/questions/ask
+ .. _`ciscoconfparse NetworkToCode slack channel`:
https://app.slack.com/client/T09LQ7E9E/C015B4U8MMF/
+
.. _`Secure IOS Template`:
https://www.cymru.com/Documents/secure-ios-template.html
.. _`Center for Internet Security Benchmarks`:
https://learn.cisecurity.org/benchmarks
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/ciscoconfparse-1.5.3/tests/test_CiscoConfParse.py
new/ciscoconfparse-1.5.5/tests/test_CiscoConfParse.py
--- old/ciscoconfparse-1.5.3/tests/test_CiscoConfParse.py 2019-06-05
15:21:26.000000000 +0200
+++ new/ciscoconfparse-1.5.5/tests/test_CiscoConfParse.py 2020-06-12
10:15:09.000000000 +0200
@@ -540,6 +540,186 @@
test_result = parse_c01.find_children_w_parents(**args)
assert result_correct==test_result
+def testValues_find_object_branches_01():
+ """Basic test: find_object_branches() - only look for object text matching
(90% solution)"""
+ config = [
+ 'ltm pool FOO {',
+ ' members {',
+ ' k8s-05.localdomain:8443 {',
+ ' address 192.0.2.5',
+ ' session monitor-enabled',
+ ' state up',
+ ' }',
+ ' k8s-06.localdomain:8443 {',
+ ' address 192.0.2.6',
+ ' session monitor-enabled',
+ ' state down',
+ ' }',
+ ' }',
+ '}',
+ 'ltm pool BAR {',
+ ' members {',
+ ' k8s-07.localdomain:8443 {',
+ ' address 192.0.2.7',
+ ' session monitor-enabled',
+ ' state down',
+ ' }',
+ ' }',
+ '}',
+ ]
+ parse = CiscoConfParse(config, syntax='junos', comment='#')
+ branchspec = (r'ltm\spool', r'members', r'\S+?:\d+', r'state\sup')
+ test_result = parse.find_object_branches(branchspec=branchspec)
+
+ assert len(test_result)==3
+
+ # Test first family branch result...
+ assert test_result[0][0].text.strip()=='ltm pool FOO'
+ assert test_result[0][1].text.strip()=='members'
+ assert test_result[0][2].text.strip()=='k8s-05.localdomain:8443'
+ assert test_result[0][3].text.strip()=='state up'
+
+ # Test second family branch result...
+ assert test_result[1][0].text.strip()=='ltm pool FOO'
+ assert test_result[1][1].text.strip()=='members'
+ assert test_result[1][2].text.strip()=='k8s-06.localdomain:8443'
+ assert test_result[1][3] is None # 'state down' != 'state up'
+
+ # Test third family branch result...
+ assert test_result[2][0].text.strip()=='ltm pool BAR'
+ assert test_result[2][1].text.strip()=='members'
+ assert test_result[2][2].text.strip()=='k8s-07.localdomain:8443'
+ assert test_result[2][3] is None # 'state down' != 'state up'
+
+def testValues_find_object_branches_02():
+ """Basic test: find_object_branches() - only look for object text matching
(90% solution)"""
+ config = [
+ 'ltm pool FOO {',
+ ' members {',
+ ' k8s-05.localdomain:8443 {',
+ ' address 192.0.2.5',
+ ' session monitor-enabled',
+ ' state up',
+ ' }',
+ ' k8s-06.localdomain:8443 {',
+ ' address 192.0.2.6',
+ ' session monitor-enabled',
+ ' state down',
+ ' }',
+ ' }',
+ '}',
+ 'ltm pool BAR {',
+ ' members {',
+ ' k8s-07.localdomain:8443 {',
+ ' address 192.0.2.7',
+ ' session monitor-enabled',
+ ' state down',
+ ' }',
+ ' }',
+ '}',
+ ]
+
+ parse = CiscoConfParse(config, syntax='junos', comment='#')
+ # Test negative lookahead matching in the first regex term... Dont match
+ # 'ltm pool BAR'...
+ branchspec = (r'ltm\spool\s(?!BAR)', r'members', r'\S+?:\d+', r'state\sup')
+ test_result = parse.find_object_branches(branchspec=branchspec)
+
+ assert len(test_result)==2
+
+ # Test first family branch result...
+ assert test_result[0][0].text.strip()=='ltm pool FOO'
+ assert test_result[0][1].text.strip()=='members'
+ assert test_result[0][2].text.strip()=='k8s-05.localdomain:8443'
+ assert test_result[0][3].text.strip()=='state up'
+
+ # Test second family branch result...
+ assert test_result[1][0].text.strip()=='ltm pool FOO'
+ assert test_result[1][1].text.strip()=='members'
+ assert test_result[1][2].text.strip()=='k8s-06.localdomain:8443'
+ assert test_result[1][3] is None # 'state down' != 'state up'
+
+
+def testValues_find_object_branches_03(parse_c01):
+ """Basic test: find_object_branches() - Test for multiple matches to a
vague branchspec term..."""
+
+ # NOTE This is NOT a good example of how to use find_object_branches... I'm
+ # testing to ensure we get the right matches for a vague regular
expression...
+ # this winds up returning a lot of different match object types in the
+ # last branch term
+ branchspec = (r'^interface', r'switchport',)
+ test_result = parse_c01.find_object_branches(branchspec=branchspec)
+
+ assert len(test_result)==19
+
+ assert test_result[0][0].text.strip()=='interface Serial 1/0'
+ assert test_result[0][1] is None # Serial is not a switchport :)
+
+ assert test_result[1][0].text.strip()=='interface GigabitEthernet4/1'
+ assert test_result[1][1].text.strip()=='switchport'
+
+ assert test_result[2][0].text.strip()=='interface GigabitEthernet4/1'
+ assert test_result[2][1].text.strip()=='switchport access vlan 100'
+
+ assert test_result[3][0].text.strip()=='interface GigabitEthernet4/1'
+ assert test_result[3][1].text.strip()=='switchport voice vlan 150'
+
+ assert test_result[4][0].text.strip()=='interface GigabitEthernet4/2'
+ assert test_result[4][1].text.strip()=='switchport'
+
+ assert test_result[5][0].text.strip()=='interface GigabitEthernet4/2'
+ assert test_result[5][1].text.strip()=='switchport access vlan 100'
+
+ assert test_result[6][0].text.strip()=='interface GigabitEthernet4/2'
+ assert test_result[6][1].text.strip()=='switchport voice vlan 150'
+
+ assert test_result[7][0].text.strip()=='interface GigabitEthernet4/3'
+ assert test_result[7][1].text.strip()=='switchport'
+
+ assert test_result[8][0].text.strip()=='interface GigabitEthernet4/3'
+ assert test_result[8][1].text.strip()=='switchport access vlan 100'
+
+ assert test_result[9][0].text.strip()=='interface GigabitEthernet4/3'
+ assert test_result[9][1].text.strip()=='switchport voice vlan 150'
+
+ assert test_result[10][0].text.strip()=='interface GigabitEthernet4/4'
+ assert test_result[10][1] is None #Gi4/4 isn't a switchport (ref regex
term)
+
+ assert test_result[11][0].text.strip()=='interface GigabitEthernet4/5'
+ assert test_result[11][1].text.strip()=='switchport'
+
+ assert test_result[12][0].text.strip()=='interface GigabitEthernet4/5'
+ assert test_result[12][1].text.strip()=='switchport access vlan 110'
+
+ assert test_result[13][0].text.strip()=='interface GigabitEthernet4/6'
+ assert test_result[13][1].text.strip()=='switchport'
+
+ assert test_result[14][0].text.strip()=='interface GigabitEthernet4/6'
+ assert test_result[14][1].text.strip()=='switchport access vlan 110'
+
+ assert test_result[15][0].text.strip()=='interface GigabitEthernet4/7'
+ assert test_result[15][1].text.strip()=='switchport'
+
+ assert test_result[16][0].text.strip()=='interface GigabitEthernet4/7'
+ assert test_result[16][1].text.strip()=='switchport access vlan 110'
+
+ assert test_result[17][0].text.strip()=='interface GigabitEthernet4/8'
+ assert test_result[17][1].text.strip()=='switchport'
+
+ assert test_result[18][0].text.strip()=='interface GigabitEthernet4/8'
+ assert test_result[18][1].text.strip()=='switchport access vlan 110'
+
+def testValues_find_object_branches_04(parse_c01):
+ """Basic test: find_object_branches() - Test that non-existent regex child
levels return `None`"""
+
+ # NOTE This is NOT a good example of using find_object_branches()... I'm
+ # negative testing to ensure we get the right matches for NO regex
+ # matches...
+ branchspec = (r'this', r'dont', 'match', 'at', 'all')
+ test_result = parse_c01.find_object_branches(branchspec=branchspec)
+ result_correct = [[None, None, None, None, None]]
+ assert test_result==result_correct
+
def testValues_find_objects_w_parents(parse_c01):
c01_children_w_parents_switchport = [
' switchport',