forked from ansible-network/network-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implements pluggable system for network-engine
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
Showing
9 changed files
with
417 additions
and
327 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|