Skip to content

Commit

Permalink
implements pluggable system for network-engine
Browse files Browse the repository at this point in the history
This change implements a pluggable library for network-engine for
loading plugins to handle parsing and templating.  This will promote
resuse of the template and parsing enginer for other network roles.
  • Loading branch information
Peter Sprygada committed Mar 25, 2018
1 parent baaa94a commit 00e1d85
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 327 deletions.
380 changes: 54 additions & 326 deletions action_plugins/text_parser.py

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
---
# defaults file for network-engine
# defaults file for network-engine
#
network_engine_path: "{{ role_path }}"
network_engine_lib_path: "{{ network_engine_path }}/lib"
Empty file added lib/network_engine/__init__.py
Empty file.
25 changes: 25 additions & 0 deletions lib/network_engine/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# (c) 2018, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from ansible.plugins.loader import PluginLoader

template_loader = PluginLoader(
'TemplateEngine',
'network_engine.plugins.template',
None,
'template_plugins',
required_base_class='TemplateBase'
)

parser_loader = PluginLoader(
'ParserEngine',
'network_engine.plugins.parser',
None,
'parser_plugins',
#required_base_class='ParserBase'
)


Empty file.
168 changes: 168 additions & 0 deletions lib/network_engine/plugins/parser/pattern_match.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# (c) 2018, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import re

from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_text
from ansible.errors import AnsibleError

try:
from ansible.module_utils.network.common.utils import to_list
except ImportError:
# keep role compatible with Ansible 2.4
from ansible.module_utils.network_common import to_list

get_value = lambda m, i: m.group(i) if m else None

class ParserEngine(object):

def __init__(self, text):
self.text = text

def match(self, regex, match_all=None, match_until=None, match_greedy=None):
""" Perform the regular expression match against the contents
:args regex: The regular expression pattern to use
:args contents: The contents to run the pattern against
:args match_all: Specifies if all matches of pattern should be returned
or just the first occurence
:returns: list object of matches or None if there where no matches found
"""
contents = self.text

if match_greedy:
return self._match_greedy(contents, regex, end=match_until, match_all=match_all)
elif match_all:
return self._match_all(contents, regex)
else:
return self._match(contents, regex)

def _match_all(self, contents, pattern):
match = self.re_matchall(pattern, contents)
if match:
return match

def _match(self, contents, pattern):
match = self.re_search(pattern, contents)
if match:
return match

def _match_greedy(self, contents, start, end=None, match_all=None):
""" Filter a section of the contents text for matching
:args contents: The contents to match against
:args start: The start of the section data
:args end: The end of the section data
:args match_all: Whether or not to match all of the instances
:returns: a list object of all matches
"""
section_data = list()

if match_all:
while True:
section_range = self._get_section_range(contents, start, end)
if not section_range:
break

sidx, eidx = section_range

if eidx is not None:
section_data.append(contents[sidx: eidx])
contents = contents[eidx:]
else:
section_data.append(contents[sidx:])
break

else:
section_data.append(contents)

return section_data

def _get_section_range(self, contents, start, end=None):

context_start_re = re.compile(start, re.M)
if end:
context_end_re = re.compile(end, re.M)
include_end = True
else:
context_end_re = context_start_re
include_end = False

context_start = re.search(context_start_re, contents)
if not context_start:
return

string_start = context_start.start()
end = context_start.end() + 1

context_end = re.search(context_end_re, contents[end:])
if not context_end:
return (string_start, None)

if include_end:
string_end = end + context_end.end()
else:
string_end = end + context_end.start()

return (string_start, string_end)

def _get_context_data(self, entry, contents):
name = entry['name']

context = entry.get('context', {})
context_data = list()

if context:
while True:
context_range = self._get_context_range(name, context, contents)

if not context_range:
break

start, end = context_range

if end is not None:
context_data.append(contents[start: end])
contents = contents[end:]
else:
context_data.append(contents[start:])
break

else:
context_data.append(contents)

return context_data

def re_search(self, regex, value):
obj = {}
regex = re.compile(regex, re.M)
match = regex.search(value)
if match:
items = list(match.groups())
if regex.groupindex:
for name, index in iteritems(regex.groupindex):
obj[name] = items[index - 1]
obj['matches'] = items
return obj or None

def re_matchall(self, regex, value):
objects = list()
regex = re.compile(regex)
for match in re.findall(regex.pattern, value, re.M):
obj = {}
obj['matches'] = match
if regex.groupindex:
for name, index in iteritems(regex.groupindex):
if len(regex.groupindex) == 1:
obj[name] = match
else:
obj[name] = match[index - 1]
objects.append(obj)
return objects

73 changes: 73 additions & 0 deletions lib/network_engine/plugins/template/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# (c) 2018, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import collections

from ansible.module_utils.six import iteritems, string_types
from ansible.errors import AnsibleError

try:
from ansible.module_utils.network.common.utils import to_list
except ImportError:
# keep role compatible with Ansible 2.4
from ansible.module_utils.network_common import to_list


class TemplateBase(object):

def __init__(self, templar):
self._templar = templar

def __call__(self, data, variables, convert_bare=False):
return self.template(data, variables, convert_bare)

def run(self, template, variables):
pass

def template(self, data, variables, convert_bare=False):

if isinstance(data, collections.Mapping):
templated_data = {}
for key, value in iteritems(data):
templated_key = self.template(key, variables, convert_bare=convert_bare)
templated_value = self.template(value, variables, convert_bare=convert_bare)
templated_data[templated_key] = templated_value
return templated_data

elif isinstance(data, collections.Iterable) and not isinstance(data, string_types):
return [self.template(i, variables, convert_bare=convert_bare) for i in data]

else:
data = data or {}
tmp_avail_vars = self._templar._available_variables
self._templar.set_available_variables(variables)
try:
resp = self._templar.template(data, convert_bare=convert_bare)
resp = self._coerce_to_native(resp)
except AnsibleUndefinedVariable:
resp = None
pass
finally:
self._templar.set_available_variables(tmp_avail_vars)
return resp

def _coerce_to_native(self, value):
if not isinstance(value, bool):
try:
value = int(value)
except Exception as exc:
if value is None or len(value) == 0:
return None
pass
return value

def _update(self, d, u):
for k, v in u.iteritems():
if isinstance(v, collections.Mapping):
d[k] = self._update(d.get(k, {}), v)
else:
d[k] = v
return d
81 changes: 81 additions & 0 deletions lib/network_engine/plugins/template/json_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# (c) 2018, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import collections

from ansible.module_utils.six import string_types

from network_engine.plugins.template import TemplateBase


class TemplateEngine(TemplateBase):

def run(self, template, variables=None):

templated_items = {}

for item in template:
key = self.template(item['key'], variables)

# FIXME moving to the plugin system breaks this
#when = item.get('when')
#if when is not None:
# if not self._check_conditional(when, variables):
# continue

if 'value' in item:
value = item.get('value')
items = None
item_type = None

elif 'object' in item:
items = item.get('object')
item_type = 'dict'

elif 'elements' in item:
items = item.get('elements')
item_type = 'list'

loop = item.get('repeat_for')
loop_data = self.template(loop, variables) if loop else None
loop_var = item.get('repeat_var', 'item')

if items:
if loop:
if isinstance(loop_data, collections.Iterable) and not isinstance(loop_data, string_types):
templated_value = list()

for loop_item in loop_data:
variables[loop_var] = loop_item
templated_value.append(self.run(items, variables))

if item_type == 'list':
templated_items[key] = templated_value

elif item_type == 'dict':
if key not in templated_items:
templated_items[key] = {}

for t in templated_value:
templated_items[key] = self._update(templated_items[key], t)
else:
templated_items[key] = []

else:
val = self.run(items, variables)

if item_type == 'list':
templated_value = [val]
else:
templated_value = val

templated_items[key] = templated_value

else:
templated_value = self.template(value, variables)
templated_items[key] = templated_value

return templated_items
12 changes: 12 additions & 0 deletions lib/network_engine/plugins/template/normal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# (c) 2018, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from network_engine.plugins.template import TemplateBase


class TemplateEngine(TemplateBase):
pass

0 comments on commit 00e1d85

Please sign in to comment.