diff options
Diffstat (limited to 'lib/ansible/module_utils/network')
-rw-r--r-- | lib/ansible/module_utils/network/common/cfg/base.py | 24 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/common/config.py | 468 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/common/facts/facts.py | 132 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/common/netconf.py | 162 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/common/network.py | 249 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/common/parsing.py | 305 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/common/utils.py | 643 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/netconf/netconf.py | 137 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/restconf/restconf.py | 57 |
9 files changed, 0 insertions, 2177 deletions
diff --git a/lib/ansible/module_utils/network/common/cfg/base.py b/lib/ansible/module_utils/network/common/cfg/base.py deleted file mode 100644 index 5901dc767d..0000000000 --- a/lib/ansible/module_utils/network/common/cfg/base.py +++ /dev/null @@ -1,24 +0,0 @@ -# -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The base class for all resource modules -""" - -from ansible.module_utils.network.common.network import get_resource_connection - - -class ConfigBase(object): - """ The base class for all resource modules - """ - ACTION_STATES = ['merged', 'replaced', 'overridden', 'deleted'] - - def __init__(self, module): - self._module = module - self.state = module.params['state'] - self._connection = None - - if self.state not in ['rendered', 'parsed']: - self._connection = get_resource_connection(module) diff --git a/lib/ansible/module_utils/network/common/config.py b/lib/ansible/module_utils/network/common/config.py deleted file mode 100644 index 974d346098..0000000000 --- a/lib/ansible/module_utils/network/common/config.py +++ /dev/null @@ -1,468 +0,0 @@ -# This code is part of Ansible, but is an independent component. -# This particular file snippet, and this file snippet only, is BSD licensed. -# Modules you write using this snippet, which is embedded dynamically by Ansible -# still belong to the author of the module, and may assign their own license -# to the complete work. -# -# (c) 2016 Red Hat Inc. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -import re -import hashlib - -from ansible.module_utils.six.moves import zip -from ansible.module_utils._text import to_bytes, to_native - -DEFAULT_COMMENT_TOKENS = ['#', '!', '/*', '*/', 'echo'] - -DEFAULT_IGNORE_LINES_RE = set([ - re.compile(r"Using \d+ out of \d+ bytes"), - re.compile(r"Building configuration"), - re.compile(r"Current configuration : \d+ bytes") -]) - - -try: - Pattern = re._pattern_type -except AttributeError: - Pattern = re.Pattern - - -class ConfigLine(object): - - def __init__(self, raw): - self.text = str(raw).strip() - self.raw = raw - self._children = list() - self._parents = list() - - def __str__(self): - return self.raw - - def __eq__(self, other): - return self.line == other.line - - def __ne__(self, other): - return not self.__eq__(other) - - def __getitem__(self, key): - for item in self._children: - if item.text == key: - return item - raise KeyError(key) - - @property - def line(self): - line = self.parents - line.append(self.text) - return ' '.join(line) - - @property - def children(self): - return _obj_to_text(self._children) - - @property - def child_objs(self): - return self._children - - @property - def parents(self): - return _obj_to_text(self._parents) - - @property - def path(self): - config = _obj_to_raw(self._parents) - config.append(self.raw) - return '\n'.join(config) - - @property - def has_children(self): - return len(self._children) > 0 - - @property - def has_parents(self): - return len(self._parents) > 0 - - def add_child(self, obj): - if not isinstance(obj, ConfigLine): - raise AssertionError('child must be of type `ConfigLine`') - self._children.append(obj) - - -def ignore_line(text, tokens=None): - for item in (tokens or DEFAULT_COMMENT_TOKENS): - if text.startswith(item): - return True - for regex in DEFAULT_IGNORE_LINES_RE: - if regex.match(text): - return True - - -def _obj_to_text(x): - return [o.text for o in x] - - -def _obj_to_raw(x): - return [o.raw for o in x] - - -def _obj_to_block(objects, visited=None): - items = list() - for o in objects: - if o not in items: - items.append(o) - for child in o._children: - if child not in items: - items.append(child) - return _obj_to_raw(items) - - -def dumps(objects, output='block', comments=False): - if output == 'block': - items = _obj_to_block(objects) - elif output == 'commands': - items = _obj_to_text(objects) - elif output == 'raw': - items = _obj_to_raw(objects) - else: - raise TypeError('unknown value supplied for keyword output') - - if output == 'block': - if comments: - for index, item in enumerate(items): - nextitem = index + 1 - if nextitem < len(items) and not item.startswith(' ') and items[nextitem].startswith(' '): - item = '!\n%s' % item - items[index] = item - items.append('!') - items.append('end') - - return '\n'.join(items) - - -class NetworkConfig(object): - - def __init__(self, indent=1, contents=None, ignore_lines=None): - self._indent = indent - self._items = list() - self._config_text = None - - if ignore_lines: - for item in ignore_lines: - if not isinstance(item, Pattern): - item = re.compile(item) - DEFAULT_IGNORE_LINES_RE.add(item) - - if contents: - self.load(contents) - - @property - def items(self): - return self._items - - @property - def config_text(self): - return self._config_text - - @property - def sha1(self): - sha1 = hashlib.sha1() - sha1.update(to_bytes(str(self), errors='surrogate_or_strict')) - return sha1.digest() - - def __getitem__(self, key): - for line in self: - if line.text == key: - return line - raise KeyError(key) - - def __iter__(self): - return iter(self._items) - - def __str__(self): - return '\n'.join([c.raw for c in self.items]) - - def __len__(self): - return len(self._items) - - def load(self, s): - self._config_text = s - self._items = self.parse(s) - - def loadfp(self, fp): - with open(fp) as f: - return self.load(f.read()) - - def parse(self, lines, comment_tokens=None): - toplevel = re.compile(r'\S') - childline = re.compile(r'^\s*(.+)$') - entry_reg = re.compile(r'([{};])') - - ancestors = list() - config = list() - - indents = [0] - - for linenum, line in enumerate(to_native(lines, errors='surrogate_or_strict').split('\n')): - text = entry_reg.sub('', line).strip() - - cfg = ConfigLine(line) - - if not text or ignore_line(text, comment_tokens): - continue - - # handle top level commands - if toplevel.match(line): - ancestors = [cfg] - indents = [0] - - # handle sub level commands - else: - match = childline.match(line) - line_indent = match.start(1) - - if line_indent < indents[-1]: - while indents[-1] > line_indent: - indents.pop() - - if line_indent > indents[-1]: - indents.append(line_indent) - - curlevel = len(indents) - 1 - parent_level = curlevel - 1 - - cfg._parents = ancestors[:curlevel] - - if curlevel > len(ancestors): - config.append(cfg) - continue - - for i in range(curlevel, len(ancestors)): - ancestors.pop() - - ancestors.append(cfg) - ancestors[parent_level].add_child(cfg) - - config.append(cfg) - - return config - - def get_object(self, path): - for item in self.items: - if item.text == path[-1]: - if item.parents == path[:-1]: - return item - - def get_block(self, path): - if not isinstance(path, list): - raise AssertionError('path argument must be a list object') - obj = self.get_object(path) - if not obj: - raise ValueError('path does not exist in config') - return self._expand_block(obj) - - def get_block_config(self, path): - block = self.get_block(path) - return dumps(block, 'block') - - def _expand_block(self, configobj, S=None): - if S is None: - S = list() - S.append(configobj) - for child in configobj._children: - if child in S: - continue - self._expand_block(child, S) - return S - - def _diff_line(self, other): - updates = list() - for item in self.items: - if item not in other: - updates.append(item) - return updates - - def _diff_strict(self, other): - updates = list() - # block extracted from other does not have all parents - # but the last one. In case of multiple parents we need - # to add additional parents. - if other and isinstance(other, list) and len(other) > 0: - start_other = other[0] - if start_other.parents: - for parent in start_other.parents: - other.insert(0, ConfigLine(parent)) - for index, line in enumerate(self.items): - try: - if str(line).strip() != str(other[index]).strip(): - updates.append(line) - except (AttributeError, IndexError): - updates.append(line) - return updates - - def _diff_exact(self, other): - updates = list() - if len(other) != len(self.items): - updates.extend(self.items) - else: - for ours, theirs in zip(self.items, other): - if ours != theirs: - updates.extend(self.items) - break - return updates - - def difference(self, other, match='line', path=None, replace=None): - """Perform a config diff against the another network config - - :param other: instance of NetworkConfig to diff against - :param match: type of diff to perform. valid values are 'line', - 'strict', 'exact' - :param path: context in the network config to filter the diff - :param replace: the method used to generate the replacement lines. - valid values are 'block', 'line' - - :returns: a string of lines that are different - """ - if path and match != 'line': - try: - other = other.get_block(path) - except ValueError: - other = list() - else: - other = other.items - - # generate a list of ConfigLines that aren't in other - meth = getattr(self, '_diff_%s' % match) - updates = meth(other) - - if replace == 'block': - parents = list() - for item in updates: - if not item.has_parents: - parents.append(item) - else: - for p in item._parents: - if p not in parents: - parents.append(p) - - updates = list() - for item in parents: - updates.extend(self._expand_block(item)) - - visited = set() - expanded = list() - - for item in updates: - for p in item._parents: - if p.line not in visited: - visited.add(p.line) - expanded.append(p) - expanded.append(item) - visited.add(item.line) - - return expanded - - def add(self, lines, parents=None): - ancestors = list() - offset = 0 - obj = None - - # global config command - if not parents: - for line in lines: - # handle ignore lines - if ignore_line(line): - continue - - item = ConfigLine(line) - item.raw = line - if item not in self.items: - self.items.append(item) - - else: - for index, p in enumerate(parents): - try: - i = index + 1 - obj = self.get_block(parents[:i])[0] - ancestors.append(obj) - - except ValueError: - # add parent to config - offset = index * self._indent - obj = ConfigLine(p) - obj.raw = p.rjust(len(p) + offset) - if ancestors: - obj._parents = list(ancestors) - ancestors[-1]._children.append(obj) - self.items.append(obj) - ancestors.append(obj) - - # add child objects - for line in lines: - # handle ignore lines - if ignore_line(line): - continue - - # check if child already exists - for child in ancestors[-1]._children: - if child.text == line: - break - else: - offset = len(parents) * self._indent - item = ConfigLine(line) - item.raw = line.rjust(len(line) + offset) - item._parents = ancestors - ancestors[-1]._children.append(item) - self.items.append(item) - - -class CustomNetworkConfig(NetworkConfig): - - def items_text(self): - return [item.text for item in self.items] - - def expand_section(self, configobj, S=None): - if S is None: - S = list() - S.append(configobj) - for child in configobj.child_objs: - if child in S: - continue - self.expand_section(child, S) - return S - - 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) - 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) diff --git a/lib/ansible/module_utils/network/common/facts/facts.py b/lib/ansible/module_utils/network/common/facts/facts.py deleted file mode 100644 index 1ebf31d7bd..0000000000 --- a/lib/ansible/module_utils/network/common/facts/facts.py +++ /dev/null @@ -1,132 +0,0 @@ -# -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The facts base class -this contains methods common to all facts subsets -""" -from ansible.module_utils.network.common.network import get_resource_connection -from ansible.module_utils.six import iteritems - - -class FactsBase(object): - """ - The facts base class - """ - def __init__(self, module): - self._module = module - self._warnings = [] - self._gather_subset = module.params.get('gather_subset') - self._gather_network_resources = module.params.get('gather_network_resources') - self._connection = None - if module.params.get('state') not in ['rendered', 'parsed']: - self._connection = get_resource_connection(module) - - self.ansible_facts = {'ansible_network_resources': {}} - self.ansible_facts['ansible_net_gather_network_resources'] = list() - self.ansible_facts['ansible_net_gather_subset'] = list() - - if not self._gather_subset: - self._gather_subset = ['!config'] - if not self._gather_network_resources: - self._gather_network_resources = ['!all'] - - def gen_runable(self, subsets, valid_subsets, resource_facts=False): - """ Generate the runable subset - - :param module: The module instance - :param subsets: The provided subsets - :param valid_subsets: The valid subsets - :param resource_facts: A boolean flag - :rtype: list - :returns: The runable subsets - """ - runable_subsets = set() - exclude_subsets = set() - minimal_gather_subset = set() - if not resource_facts: - minimal_gather_subset = frozenset(['default']) - - for subset in subsets: - if subset == 'all': - runable_subsets.update(valid_subsets) - continue - if subset == 'min' and minimal_gather_subset: - runable_subsets.update(minimal_gather_subset) - continue - if subset.startswith('!'): - subset = subset[1:] - if subset == 'min': - exclude_subsets.update(minimal_gather_subset) - continue - if subset == 'all': - exclude_subsets.update( - valid_subsets - minimal_gather_subset) - continue - exclude = True - else: - exclude = False - - if subset not in valid_subsets: - self._module.fail_json(msg='Subset must be one of [%s], got %s' % - (', '.join(sorted([item for item in valid_subsets])), subset)) - - if exclude: - exclude_subsets.add(subset) - else: - runable_subsets.add(subset) - - if not runable_subsets: - runable_subsets.update(valid_subsets) - runable_subsets.difference_update(exclude_subsets) - return runable_subsets - - def get_network_resources_facts(self, facts_resource_obj_map, resource_facts_type=None, data=None): - """ - :param fact_resource_subsets: - :param data: previously collected configuration - :return: - """ - if not resource_facts_type: - resource_facts_type = self._gather_network_resources - - restorun_subsets = self.gen_runable(resource_facts_type, frozenset(facts_resource_obj_map.keys()), resource_facts=True) - if restorun_subsets: - self.ansible_facts['ansible_net_gather_network_resources'] = list(restorun_subsets) - instances = list() - for key in restorun_subsets: - fact_cls_obj = facts_resource_obj_map.get(key) - if fact_cls_obj: - instances.append(fact_cls_obj(self._module)) - else: - self._warnings.extend(["network resource fact gathering for '%s' is not supported" % key]) - - for inst in instances: - inst.populate_facts(self._connection, self.ansible_facts, data) - - def get_network_legacy_facts(self, fact_legacy_obj_map, legacy_facts_type=None): - if not legacy_facts_type: - legacy_facts_type = self._gather_subset - - runable_subsets = self.gen_runable(legacy_facts_type, frozenset(fact_legacy_obj_map.keys())) - if runable_subsets: - facts = dict() - # default subset should always returned be with legacy facts subsets - if 'default' not in runable_subsets: - runable_subsets.add('default') - self.ansible_facts['ansible_net_gather_subset'] = list(runable_subsets) - - instances = list() - for key in runable_subsets: - instances.append(fact_legacy_obj_map[key](self._module)) - - for inst in instances: - inst.populate() - facts.update(inst.facts) - self._warnings.extend(inst.warnings) - - for key, value in iteritems(facts): - key = 'ansible_net_%s' % key - self.ansible_facts[key] = value diff --git a/lib/ansible/module_utils/network/common/netconf.py b/lib/ansible/module_utils/network/common/netconf.py deleted file mode 100644 index 84f3a4e948..0000000000 --- a/lib/ansible/module_utils/network/common/netconf.py +++ /dev/null @@ -1,162 +0,0 @@ -# This code is part of Ansible, but is an independent component. -# This particular file snippet, and this file snippet only, is BSD licensed. -# Modules you write using this snippet, which is embedded dynamically by Ansible -# still belong to the author of the module, and may assign their own license -# to the complete work. -# -# (c) 2017 Red Hat Inc. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -import sys - -from ansible.module_utils._text import to_text, to_bytes -from ansible.module_utils.connection import Connection, ConnectionError - -try: - from ncclient.xml_ import NCElement, new_ele, sub_ele - HAS_NCCLIENT = True -except (ImportError, AttributeError): - HAS_NCCLIENT = False - -try: - from lxml.etree import Element, fromstring, XMLSyntaxError -except ImportError: - from xml.etree.ElementTree import Element, fromstring - if sys.version_info < (2, 7): - from xml.parsers.expat import ExpatError as XMLSyntaxError - else: - from xml.etree.ElementTree import ParseError as XMLSyntaxError - -NS_MAP = {'nc': "urn:ietf:params:xml:ns:netconf:base:1.0"} - - -def exec_rpc(module, *args, **kwargs): - connection = NetconfConnection(module._socket_path) - return connection.execute_rpc(*args, **kwargs) - - -class NetconfConnection(Connection): - - def __init__(self, socket_path): - super(NetconfConnection, self).__init__(socket_path) - - def __rpc__(self, name, *args, **kwargs): - """Executes the json-rpc and returns the output received - from remote device. - :name: rpc method to be executed over connection plugin that implements jsonrpc 2.0 - :args: Ordered list of params passed as arguments to rpc method - :kwargs: Dict of valid key, value pairs passed as arguments to rpc method - - For usage refer the respective connection plugin docs. - """ - self.check_rc = kwargs.pop('check_rc', True) - self.ignore_warning = kwargs.pop('ignore_warning', True) - - response = self._exec_jsonrpc(name, *args, **kwargs) - if 'error' in response: - rpc_error = response['error'].get('data') - return self.parse_rpc_error(to_bytes(rpc_error, errors='surrogate_then_replace')) - - return fromstring(to_bytes(response['result'], errors='surrogate_then_replace')) - - def parse_rpc_error(self, rpc_error): - if self.check_rc: - try: - error_root = fromstring(rpc_error) - root = Element('root') - root.append(error_root) - - error_list = root.findall('.//nc:rpc-error', NS_MAP) - if not error_list: - raise ConnectionError(to_text(rpc_error, errors='surrogate_then_replace')) - - warnings = [] - for error in error_list: - message_ele = error.find('./nc:error-message', NS_MAP) - - if message_ele is None: - message_ele = error.find('./nc:error-info', NS_MAP) - - message = message_ele.text if message_ele is not None else None - - severity = error.find('./nc:error-severity', NS_MAP).text - - if severity == 'warning' and self.ignore_warning and message is not None: - warnings.append(message) - else: - raise ConnectionError(to_text(rpc_error, errors='surrogate_then_replace')) - return warnings - except XMLSyntaxError: - raise ConnectionError(rpc_error) - - -def transform_reply(): - return b'''<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> - <xsl:output method="xml" indent="no"/> - - <xsl:template match="/|comment()|processing-instruction()"> - <xsl:copy> - <xsl:apply-templates/> - </xsl:copy> - </xsl:template> - - <xsl:template match="*"> - <xsl:element name="{local-name()}"> - <xsl:apply-templates select="@*|node()"/> - </xsl:element> - </xsl:template> - - <xsl:template match="@*"> - <xsl:attribute name="{local-name()}"> - <xsl:value-of select="."/> - </xsl:attribute> - </xsl:template> - </xsl:stylesheet> - ''' - - -# Note: Workaround for ncclient 0.5.3 -def remove_namespaces(data): - if not HAS_NCCLIENT: - raise ImportError("ncclient is required but does not appear to be installed. " - "It can be installed using `pip install ncclient`") - return NCElement(data, transform_reply()).data_xml - - -def build_root_xml_node(tag): - return new_ele(tag) - - -def build_child_xml_node(parent, tag, text=None, attrib=None): - element = sub_ele(parent, tag) - if text: - element.text = to_text(text) - if attrib: - element.attrib.update(attrib) - return element - - -def build_subtree(parent, path): - element = parent - for field in path.split('/'): - sub_element = build_child_xml_node(element, field) - element = sub_element - return element diff --git a/lib/ansible/module_utils/network/common/network.py b/lib/ansible/module_utils/network/common/network.py deleted file mode 100644 index e76d31e983..0000000000 --- a/lib/ansible/module_utils/network/common/network.py +++ /dev/null @@ -1,249 +0,0 @@ -# This code is part of Ansible, but is an independent component. -# This particular file snippet, and this file snippet only, is BSD licensed. -# Modules you write using this snippet, which is embedded dynamically by Ansible -# still belong to the author of the module, and may assign their own license -# to the complete work. -# -# Copyright (c) 2015 Peter Sprygada, <psprygada@ansible.com> -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import traceback -import json - -from ansible.module_utils._text import to_text, to_native -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.basic import env_fallback -from ansible.module_utils.connection import Connection, ConnectionError -from ansible.module_utils.network.common.netconf import NetconfConnection -from ansible.module_utils.network.common.parsing import Cli -from ansible.module_utils.six import iteritems - - -NET_TRANSPORT_ARGS = dict( - host=dict(required=True), - port=dict(type='int'), - - username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), - password=dict(no_log=True, fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD'])), - ssh_keyfile=dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), - - authorize=dict(default=False, fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'), - auth_pass=dict(no_log=True, fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS'])), - - provider=dict(type='dict', no_log=True), - transport=dict(choices=list()), - - timeout=dict(default=10, type='int') -) - -NET_CONNECTION_ARGS = dict() - -NET_CONNECTIONS = dict() - - -def _transitional_argument_spec(): - argument_spec = {} - for key, value in iteritems(NET_TRANSPORT_ARGS): - value['required'] = False - argument_spec[key] = value - return argument_spec - - -def to_list(val): - if isinstance(val, (list, tuple)): - return list(val) - elif val is not None: - return [val] - else: - return list() - - -class ModuleStub(object): - def __init__(self, argument_spec, fail_json): - self.params = dict() - for key, value in argument_spec.items(): - self.params[key] = value.get('default') - self.fail_json = fail_json - - -class NetworkError(Exception): - - def __init__(self, msg, **kwargs): - super(NetworkError, self).__init__(msg) - self.kwargs = kwargs - - -class Config(object): - - def __init__(self, connection): - self.connection = connection - - def __call__(self, commands, **kwargs): - lines = to_list(commands) - return self.connection.configure(lines, **kwargs) - - def load_config(self, commands, **kwargs): - commands = to_list(commands) - return self.connection.load_config(commands, **kwargs) - - def get_config(self, **kwargs): - return self.connection.get_config(**kwargs) - - def save_config(self): - return self.connection.save_config() - - -class NetworkModule(AnsibleModule): - - def __init__(self, *args, **kwargs): - connect_on_load = kwargs.pop('connect_on_load', True) - - argument_spec = NET_TRANSPORT_ARGS.copy() - argument_spec['transport']['choices'] = NET_CONNECTIONS.keys() - argument_spec.update(NET_CONNECTION_ARGS.copy()) - - if kwargs.get('argument_spec'): - argument_spec.update(kwargs['argument_spec']) - kwargs['argument_spec'] = argument_spec - - super(NetworkModule, self).__init__(*args, **kwargs) - - self.connection = None - self._cli = None - self._config = None - - try: - transport = self.params['transport'] or '__default__' - cls = NET_CONNECTIONS[transport] - self.connection = cls() - except KeyError: - self.fail_json(msg='Unknown transport or no default transport specified') - except (TypeError, NetworkError) as exc: - self.fail_json(msg=to_native(exc), exception=traceback.format_exc()) - - if connect_on_load: - self.connect() - - @property - def cli(self): - if not self.connected: - self.connect() - if self._cli: - return self._cli - self._cli = Cli(self.connection) - return self._cli - - @property - def config(self): - if not self.connected: - self.connect() - if self._config: - return self._config - self._config = Config(self.connection) - return self._config - - @property - def connected(self): - return self.connection._connected - - def _load_params(self): - super(NetworkModule, self)._load_params() - provider = self.params.get('provider') or dict() - for key, value in provider.items(): - for args in [NET_TRANSPORT_ARGS, NET_CONNECTION_ARGS]: - if key in args: - if self.params.get(key) is None and value is not None: - self.params[key] = value - - def connect(self): - try: - if not self.connected: - self.connection.connect(self.params) - if self.params['authorize']: - self.connection.authorize(self.params) - self.log('connected to %s:%s using %s' % (self.params['host'], - self.params['port'], self.params['transport'])) - except NetworkError as exc: - self.fail_json(msg=to_native(exc), exception=traceback.format_exc()) - - def disconnect(self): - try: - if self.connected: - self.connection.disconnect() - self.log('disconnected from %s' % self.params['host']) - except NetworkError as exc: - self.fail_json(msg=to_native(exc), exception=traceback.format_exc()) - - -def register_transport(transport, default=False): - def register(cls): - NET_CONNECTIONS[transport] = cls - if default: - NET_CONNECTIONS['__default__'] = cls - return cls - return register - - -def add_argument(key, value): - NET_CONNECTION_ARGS[key] = value - - -def get_resource_connection(module): - if hasattr(module, '_connection'): - return module._connection - - capabilities = get_capabilities(module) - network_api = capabilities.get('network_api') - if network_api in ('cliconf', 'nxapi', 'eapi', 'exosapi'): - module._connection = Connection(module._socket_path) - elif network_api == 'netconf': - module._connection = NetconfConnection(module._socket_path) - elif network_api == "local": - # This isn't supported, but we shouldn't fail here. - # Set the connection to a fake connection so it fails sensibly. - module._connection = LocalResourceConnection(module) - else: - module.fail_json(msg='Invalid connection type {0!s}'.format(network_api)) - - return module._connection - - -def get_capabilities(module): - if hasattr(module, 'capabilities'): - return module._capabilities - try: - capabilities = Connection(module._socket_path).get_capabilities() - except ConnectionError as exc: - module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) - except AssertionError: - # No socket_path, connection most likely local. - return dict(network_api="local") - module._capabilities = json.loads(capabilities) - - return module._capabilities - - -class LocalResourceConnection: - def __init__(self, module): - self.module = module - - def get(self, *args, **kwargs): - self.module.fail_json(msg="Network resource modules not supported over local connection.") diff --git a/lib/ansible/module_utils/network/common/parsing.py b/lib/ansible/module_utils/network/common/parsing.py deleted file mode 100644 index 3e338c82ba..0000000000 --- a/lib/ansible/module_utils/network/common/parsing.py +++ /dev/null @@ -1,305 +0,0 @@ -# This code is part of Ansible, but is an independent component. -# This particular file snippet, and this file snippet only, is BSD licensed. -# Modules you write using this snippet, which is embedded dynamically by Ansible -# still belong to the author of the module, and may assign their own license -# to the complete work. -# -# Copyright (c) 2015 Peter Sprygada, <psprygada@ansible.com> -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import re -import shlex -import time - -from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE, BOOLEANS_FALSE -from ansible.module_utils.six import string_types, text_type -from ansible.module_utils.six.moves import zip - - -def to_list(val): - if isinstance(val, (list, tuple)): - return list(val) - elif val is not None: - return [val] - else: - return list() - - -class FailedConditionsError(Exception): - def __init__(self, msg, failed_conditions): - super(FailedConditionsError, self).__init__(msg) - self.failed_conditions = failed_conditions - - -class FailedConditionalError(Exception): - def __init__(self, msg, failed_conditional): - super(FailedConditionalError, self).__init__(msg) - self.failed_conditional = failed_conditional - - -class AddCommandError(Exception): - def __init__(self, msg, command): - super(AddCommandError, self).__init__(msg) - self.command = command - - -class AddConditionError(Exception): - def __init__(self, msg, condition): - super(AddConditionError, self).__init__(msg) - self.condition = condition - - -class Cli(object): - - def __init__(self, connection): - self.connection = connection - self.default_output = connection.default_output or 'text' - self._commands = list() - - @property - def commands(self): - return [str(c) for c in self._commands] - - def __call__(self, commands, output=None): - objects = list() - for cmd in to_list(commands): - objects.append(self.to_command(cmd, output)) - return self.connection.run_commands(objects) - - def to_command(self, command, output=None, prompt=None, response=None, **kwargs): - output = output or self.default_output - if isinstance(command, Command): - return command - if isinstance(prompt, string_types): - prompt = re.compile(re.escape(prompt)) - return Command(command, output, prompt=prompt, response=response, **kwargs) - - def add_commands(self, commands, output=None, **kwargs): - for cmd in commands: - self._commands.append(self.to_command(cmd, output, **kwargs)) - - def run_commands(self): - responses = self.connection.run_commands(self._commands) - for resp, cmd in zip(responses, self._commands): - cmd.response = resp - - # wipe out the commands list to avoid issues if additional - # commands are executed later - self._commands = list() - - return responses - - -class Command(object): - - def __init__(self, command, output=None, prompt=None, response=None, - **kwargs): - - self.command = command - self.output = output - self.command_string = command - - self.prompt = prompt - self.response = response - - self.args = kwargs - - def __str__(self): - return self.command_string - - -class CommandRunner(object): - - def __init__(self, module): - self.module = module - - self.items = list() - self.conditionals = set() - - self.commands = list() - - self.retries = 10 - self.interval = 1 - - self.match = 'all' - - self._default_output = module.connection.default_output - - def add_command(self, command, output=None, prompt=None, response=None, - **kwargs): - if command in [str(c) for c in self.commands]: - raise AddCommandError('duplicated command detected', command=command) - cmd = self.module.cli.to_command(command, output=output, prompt=prompt, - response=response, **kwargs) - self.commands.append(cmd) - - def get_command(self, command, output=None): - for cmd in self.commands: - if cmd.command == command: - return cmd.response - raise ValueError("command '%s' not found" % command) - - def get_responses(self): - return [cmd.response for cmd in self.commands] - - def add_conditional(self, condition): - try: - self.conditionals.add(Conditional(condition)) - except AttributeError as exc: - raise AddConditionError(msg=str(exc), condition=condition) - - def run(self): - while self.retries > 0: - self.module.cli.add_commands(self.commands) - responses = self.module.cli.run_commands() - - for item in list(self.conditionals): - if item(responses): - if self.match == 'any': - return item - self.conditionals.remove(item) - - if not self.conditionals: - break - - time.sleep(self.interval) - self.retries -= 1 - else: - failed_conditions = [item.raw for item in self.conditionals] - errmsg = 'One or more conditional statements have not been satisfied' - raise FailedConditionsError(errmsg, failed_conditions) - - -class Conditional(object): - """Used in command modules to evaluate waitfor conditions - """ - - OPERATORS = { - 'eq': ['eq', '=='], - 'neq': ['neq', 'ne', '!='], - 'gt': ['gt', '>'], - 'ge': ['ge', '>='], - 'lt': ['lt', '<'], - 'le': ['le', '<='], - 'contains': ['contains'], - 'matches': ['matches'] - } - - def __init__(self, conditional, encoding=None): - self.raw = conditional - self.negate = False - try: - components = shlex.split(conditional) - key, val = components[0], components[-1] - op_components = components[1:-1] - if 'not' in op_components: - self.negate = True - op_components.pop(op_components.index('not')) - op = op_components[0] - - except ValueError: - raise ValueError('failed to parse conditional') - - self.key = key - self.func = self._func(op) - self.value = self._cast_value(val) - - def __call__(self, data): - value = self.get_value(dict(result=data)) - if not self.negate: - return self.func(value) - else: - return not self.func(value) - - def _cast_value(self, value): - if value in BOOLEANS_TRUE: - return True - elif value in BOOLEANS_FALSE: - return False - elif re.match(r'^\d+\.d+$', value): - return float(value) - elif re.match(r'^\d+$', value): - return int(value) - else: - return text_type(value) - - def _func(self, oper): - for func, operators in self.OPERATORS.items(): - if oper in operators: - return getattr(self, func) - raise AttributeError('unknown operator: %s' % oper) - - def get_value(self, result): - try: - return self.get_json(result) - except (IndexError, TypeError, AttributeError): - msg = 'unable to apply conditional to result' - raise FailedConditionalError(msg, self.raw) - - def get_json(self, result): - string = re.sub(r"\[[\'|\"]", ".", self.key) - string = re.sub(r"[\'|\"]\]", ".", string) - parts = re.split(r'\.(?=[^\]]*(?:\[|$))', string) - for part in parts: - match = re.findall(r'\[(\S+?)\]', part) - if match: - key = part[:part.find('[')] - result = result[key] - for m in match: - try: - m = int(m) - except ValueError: - m = str(m) - result = result[m] - else: - result = result.get(part) - return result - - def number(self, value): - if '.' in str(value): - return float(value) - else: - return int(value) - - def eq(self, value): - return value == self.value - - def neq(self, value): - return value != self.value - - def gt(self, value): - return self.number(value) > self.value - - def ge(self, value): - return self.number(value) >= self.value - - def lt(self, value): - return self.number(value) < self.value - - def le(self, value): - return self.number(value) <= self.value - - def contains(self, value): - return str(self.value) in value - - def matches(self, value): - match = re.search(self.value, value, re.M) - return match is not None diff --git a/lib/ansible/module_utils/network/common/utils.py b/lib/ansible/module_utils/network/common/utils.py deleted file mode 100644 index 8031738781..0000000000 --- a/lib/ansible/module_utils/network/common/utils.py +++ /dev/null @@ -1,643 +0,0 @@ -# This code is part of Ansible, but is an independent component. -# This particular file snippet, and this file snippet only, is BSD licensed. -# Modules you write using this snippet, which is embedded dynamically by Ansible -# still belong to the author of the module, and may assign their own license -# to the complete work. -# -# (c) 2016 Red Hat Inc. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -# Networking tools for network modules only - -import re -import ast -import operator -import socket -import json - -from itertools import chain - -from ansible.module_utils._text import to_text, to_bytes -from ansible.module_utils.common._collections_compat import Mapping -from ansible.module_utils.six import iteritems, string_types -from ansible.module_utils import basic -from ansible.module_utils.parsing.convert_bool import boolean - -# Backwards compatibility for 3rd party modules -# TODO(pabelanger): With move to ansible.netcommon, we should clean this code -# up and have modules import directly themself. -from ansible.module_utils.common.network import ( # noqa: F401 - to_bits, is_netmask, is_masklen, to_netmask, to_masklen, to_subnet, to_ipv6_network, VALID_MASKS -) - -try: - from jinja2 import Environment, StrictUndefined - from jinja2.exceptions import UndefinedError - HAS_JINJA2 = True -except ImportError: - HAS_JINJA2 = False - - -OPERATORS = frozenset(['ge', 'gt', 'eq', 'neq', 'lt', 'le']) -ALIASES = frozenset([('min', 'ge'), ('max', 'le'), ('exactly', 'eq'), ('neq', 'ne')]) - - -def to_list(val): - if isinstance(val, (list, tuple, set)): - return list(val) - elif val is not None: - return [val] - else: - return list() - - -def to_lines(stdout): - for item in stdout: - if isinstance(item, string_types): - item = to_text(item).split('\n') - yield item - - -def transform_commands(module): - transform = ComplexList(dict( - command=dict(key=True), - output=dict(), - prompt=dict(type='list'), - answer=dict(type='list'), - newline=dict(type='bool', default=True), - sendonly=dict(type='bool', default=False), - check_all=dict(type='bool', default=False), - ), module) - - return transform(module.params['commands']) - - -def sort_list(val): - if isinstance(val, list): - return sorted(val) - return val - - -class Entity(object): - """Transforms a dict to with an argument spec - - This class will take a dict and apply an Ansible argument spec to the - values. The resulting dict will contain all of the keys in the param - with appropriate values set. - - Example:: - - argument_spec = dict( - command=dict(key=True), - display=dict(default='text', choices=['text', 'json']), - validate=dict(type='bool') - ) - transform = Entity(module, argument_spec) - value = dict(command='foo') - result = transform(value) - print result - {'command': 'foo', 'display': 'text', 'validate': None} - - Supported argument spec: - * key - specifies how to map a single value to a dict - * read_from - read and apply the argument_spec from the module - * required - a value is required - * type - type of value (uses AnsibleModule type checker) - * fallback - implements fallback function - * choices - set of valid options - * default - default value - """ - - def __init__(self, module, attrs=None, args=None, keys=None, from_argspec=False): - args = [] if args is None else args - - self._attributes = attrs or {} - self._module = module - - for arg in args: - self._attributes[arg] = dict() - if from_argspec: - self._attributes[arg]['read_from'] = arg - if keys and arg in keys: - self._attributes[arg]['key'] = True - - self.attr_names = frozenset(self._attributes.keys()) - - _has_key = False - - for name, attr in iteritems(self._attributes): - if attr.get('read_from'): - if attr['read_from'] not in self._module.argument_spec: - module.fail_json(msg='argument %s does not exist' % attr['read_from']) - spec = self._module.argument_spec.get(attr['read_from']) - for key, value in iteritems(spec): - if key not in attr: - attr[key] = value - - if attr.get('key'): - if _has_key: - module.fail_json(msg='only one key value can be specified') - _has_key = True - attr['required'] = True - - def serialize(self): - return self._attributes - - def to_dict(self, value): - obj = {} - for name, attr in iteritems(self._attributes): - if attr.get('key'): - obj[name] = value - else: - obj[name] = attr.get('default') - return obj - - def __call__(self, value, strict=True): - if not isinstance(value, dict): - value = self.to_dict(value) - - if strict: - unknown = set(value).difference(self.attr_names) - if unknown: - self._module.fail_json(msg='invalid keys: %s' % ','.join(unknown)) - - for name, attr in iteritems(self._attributes): - if value.get(name) is None: - value[name] = attr.get('default') - - if attr.get('fallback') and not value.get(name): - fallback = attr.get('fallback', (None,)) - fallback_strategy = fallback[0] - fallback_args = [] - fallback_kwargs = {} - if fallback_strategy is not None: - for item in fallback[1:]: - if isinstance(item, dict): - fallback_kwargs = item - else: - fallback_args = item - try: - value[name] = fallback_strategy(*fallback_args, **fallback_kwargs) - except basic.AnsibleFallbackNotFound: - continue - - if attr.get('required') and value.get(name) is None: - self._module.fail_json(msg='missing required attribute %s' % name) - - if 'choices' in attr: - if value[name] not in attr['choices']: - self._module.fail_json(msg='%s must be one of %s, got %s' % (name, ', '.join(attr['choices']), value[name])) - - if value[name] is not None: - value_type = attr.get('type', 'str') - type_checker = self._module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type] - type_checker(value[name]) - elif value.get(name): - value[name] = self._module.params[name] - - return value - - -class EntityCollection(Entity): - """Extends ```Entity``` to handle a list of dicts """ - - def __call__(self, iterable, strict=True): - if iterable is None: - iterable = [super(EntityCollection, self).__call__(self._module.params, strict)] - - if not isinstance(iterable, (list, tuple)): - self._module.fail_json(msg='value must be an iterable') - - return [(super(EntityCollection, self).__call__(i, strict)) for i in iterable] - - -# these two are for backwards compatibility and can be removed once all of the -# modules that use them are updated -class ComplexDict(Entity): - def __init__(self, attrs, module, *args, **kwargs): - super(ComplexDict, self).__init__(module, attrs, *args, **kwargs) - - -class ComplexList(EntityCollection): - def __init__(self, attrs, module, *args, **kwargs): - super(ComplexList, self).__init__(module, attrs, *args, **kwargs) - - -def dict_diff(base, comparable): - """ Generate a dict object of differences - - This function will compare two dict objects and return the difference - between them as a dict object. For scalar values, the key will reflect - the updated value. If the key does not exist in `comparable`, then then no - key will be returned. For lists, the value in comparable will wholly replace - the value in base for the key. For dicts, the returned value will only - return keys that are different. - - :param base: dict object to base the diff on - :param comparable: dict object to compare against base - - :returns: new dict object with differences - """ - if not isinstance(base, dict): - raise AssertionError("`base` must be of type <dict>") - if not isinstance(comparable, dict): - if comparable is None: - comparable = dict() - else: - raise AssertionError("`comparable` must be of type <dict>") - - updates = dict() - - for key, value in iteritems(base): - if isinstance(value, dict): - item = comparable.get(key) - if item is not None: - sub_diff = dict_diff(value, comparable[key]) - if sub_diff: - updates[key] = sub_diff - else: - comparable_value = comparable.get(key) - if comparable_value is not None: - if sort_list(base[key]) != sort_list(comparable_value): - updates[key] = comparable_value - - for key in set(comparable.keys()).difference(base.keys()): - updates[key] = comparable.get(key) - - return updates - - -def dict_merge(base, other): - """ Return a new dict object that combines base and other - - This will create a new dict object that is a combination of the key/value - pairs from base and other. When both keys exist, the value will be - selected from other. If the value is a list object, the two lists will - be combined and duplicate entries removed. - - :param base: dict object to serve as base - :param other: dict object to combine with base - - :returns: new combined dict object - """ - if not isinstance(base, dict): - raise AssertionError("`base` must be of type <dict>") - if not isinstance(other, dict): - raise AssertionError("`other` must be of type <dict>") - - combined = dict() - - for key, value in iteritems(base): - if isinstance(value, dict): - if key in other: - item = other.get(key) - if item is not None: - if isinstance(other[key], Mapping): - combined[key] = dict_merge(value, other[key]) - else: - combined[key] = other[key] - else: - combined[key] = item - else: - combined[key] = value - elif isinstance(value, list): - if key in other: - item = other.get(key) - if item is not None: - try: - combined[key] = list(set(chain(value, item))) - except TypeError: - value.extend([i for i in item if i not in value]) - combined[key] = value - else: - combined[key] = item - else: - combined[key] = value - else: - if key in other: - other_value = other.get(key) - if other_value is not None: - if sort_list(base[key]) != sort_list(other_value): - combined[key] = other_value - else: - combined[key] = value - else: - combined[key] = other_value - else: - combined[key] = value - - for key in set(other.keys()).difference(base.keys()): - combined[key] = other.get(key) - - return combined - - -def param_list_to_dict(param_list, unique_key="name", remove_key=True): - """Rotates a list of dictionaries to be a dictionary of dictionaries. - - :param param_list: The aforementioned list of dictionaries - :param unique_key: The name of a key which is present and unique in all of param_list's dictionaries. The value - behind this key will be the key each dictionary can be found at in the new root dictionary - :param remove_key: If True, remove unique_key from the individual dictionaries before returning. - """ - param_dict = {} - for params in param_list: - params = params.copy() - if remove_key: - name = params.pop(unique_key) - else: - name = params.get(unique_key) - param_dict[name] = params - - return param_dict - - -def conditional(expr, val, cast=None): - match = re.match(r'^(.+)\((.+)\)$', str(expr), re.I) - if match: - op, arg = match.groups() - else: - op = 'eq' - if ' ' in str(expr): - raise AssertionError('invalid expression: cannot contain spaces') - arg = expr - - if cast is None and val is not None: - arg = type(val)(arg) - elif callable(cast): - arg = cast(arg) - val = cast(val) - - op = next((oper for alias, oper in ALIASES if op == alias), op) - - if not hasattr(operator, op) and op not in OPERATORS: - raise ValueError('unknown operator: %s' % op) - - func = getattr(operator, op) - return func(val, arg) - - -def ternary(value, true_val, false_val): - ''' value ? true_val : false_val ''' - if value: - return true_val - else: - return false_val - - -def remove_default_spec(spec): - for item in spec: - if 'default' in spec[item]: - del spec[item]['default'] - - -def validate_ip_address(address): - try: - socket.inet_aton(address) - except socket.error: - return False - return address.count('.') == 3 - - -def validate_ip_v6_address(address): - try: - socket.inet_pton(socket.AF_INET6, address) - except socket.error: - return False - return True - - -def validate_prefix(prefix): - if prefix and not 0 <= int(prefix) <= 32: - return False - return True - - -def load_provider(spec, args): - provider = args.get('provider') or {} - for key, value in iteritems(spec): - if key not in provider: - if 'fallback' in value: - provider[key] = _fallback(value['fallback']) - elif 'default' in value: - provider[key] = value['default'] - else: - provider[key] = None - if 'authorize' in provider: - # Coerce authorize to provider if a string has somehow snuck in. - provider['authorize'] = boolean(provider['authorize'] or False) - args['provider'] = provider - return provider - - -def _fallback(fallback): - strategy = fallback[0] - args = [] - kwargs = {} - - for item in fallback[1:]: - if isinstance(item, dict): - kwargs = item - else: - args = item - try: - return strategy(*args, **kwargs) - except basic.AnsibleFallbackNotFound: - pass - - -def generate_dict(spec): - """ - Generate dictionary which is in sync with argspec - - :param spec: A dictionary that is the argspec of the module - :rtype: A dictionary - :returns: A dictionary in sync with argspec with default value - """ - obj = {} - if not spec: - return obj - - for key, val in iteritems(spec): - if 'default' in val: - dct = {key: val['default']} - elif 'type' in val and val['type'] == 'dict': - dct = {key: generate_dict(val['options'])} - else: - dct = {key: None} - obj.update(dct) - return obj - - -def parse_conf_arg(cfg, arg): - """ - Parse config based on argument - - :param cfg: A text string which is a line of configuration. - :param arg: A text string which is to be matched. - :rtype: A text string - :returns: A text string if match is found - """ - match = re.search(r'%s (.+)(\n|$)' % arg, cfg, re.M) - if match: - result = match.group(1).strip() - else: - result = None - return result - - -def parse_conf_cmd_arg(cfg, cmd, res1, res2=None, delete_str='no'): - """ - Parse config based on command - - :param cfg: A text string which is a line of configuration. - :param cmd: A text string which is the command to be matched - :param res1: A text string to be returned if the command is present - :param res2: A text string to be returned if the negate command - is present - :param delete_str: A text string to identify the start of the - negate command - :rtype: A text string - :returns: A text string if match is found - """ - match = re.search(r'\n\s+%s(\n|$)' % cmd, cfg) - if match: - return res1 - if res2 is not None: - match = re.search(r'\n\s+%s %s(\n|$)' % (delete_str, cmd), cfg) - if match: - return res2 - return None - - -def get_xml_conf_arg(cfg, path, data='text'): - """ - :param cfg: The top level configuration lxml Element tree object - :param path: The relative xpath w.r.t to top level element (cfg) - to be searched in the xml hierarchy - :param data: The type of data to be returned for the matched xml node. - Valid values are text, tag, attrib, with default as text. - :return: Returns the required type for the matched xml node or else None - """ - match = cfg.xpath(path) - if len(match): - if data == 'tag': - result = getattr(match[0], 'tag') - elif data == 'attrib': - result = getattr(match[0], 'attrib') - else: - result = getattr(match[0], 'text') - else: - result = None - return result - - -def remove_empties(cfg_dict): - """ - Generate final config dictionary - - :param cfg_dict: A dictionary parsed in the facts system - :rtype: A dictionary - :returns: A dictionary by eliminating keys that have null values - """ - final_cfg = {} - if not cfg_dict: - return final_cfg - - for key, val in iteritems(cfg_dict): - dct = None - if isinstance(val, dict): - child_val = remove_empties(val) - if child_val: - dct = {key: child_val} - elif (isinstance(val, list) and val - and all([isinstance(x, dict) for x in val])): - child_val = [remove_empties(x) for x in val] - if child_val: - dct = {key: child_val} - elif val not in [None, [], {}, (), '']: - dct = {key: val} - if dct: - final_cfg.update(dct) - return final_cfg - - -def validate_config(spec, data): - """ - Validate if the input data against the AnsibleModule spec format - :param spec: Ansible argument spec - :param data: Data to be validated - :return: - """ - params = basic._ANSIBLE_ARGS - basic._ANSIBLE_ARGS = to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': data})) - validated_data = basic.AnsibleModule(spec).params - basic._ANSIBLE_ARGS = params - return validated_data - - -def search_obj_in_list(name, lst, key='name'): - if not lst: - return None - else: - for item in lst: - if item.get(key) == name: - return item - - -class Template: - - def __init__(self): - if not HAS_JINJA2: - raise ImportError("jinja2 is required but does not appear to be installed. " - "It can be installed using `pip install jinja2`") - - self.env = Environment(undefined=StrictUndefined) - self.env.filters.update({'ternary': ternary}) - - def __call__(self, value, variables=None, fail_on_undefined=True): - variables = variables or {} - - if not self.contains_vars(value): - return value - - try: - value = self.env.from_string(value).render(variables) - except UndefinedError: - if not fail_on_undefined: - return None - raise - - if value: - try: - return ast.literal_eval(value) - except Exception: - return str(value) - else: - return None - - def contains_vars(self, data): - if isinstance(data, string_types): - for marker in (self.env.block_start_string, self.env.variable_start_string, self.env.comment_start_string): - if marker in data: - return True - return False diff --git a/lib/ansible/module_utils/network/netconf/netconf.py b/lib/ansible/module_utils/network/netconf/netconf.py deleted file mode 100644 index bd37f14931..0000000000 --- a/lib/ansible/module_utils/network/netconf/netconf.py +++ /dev/null @@ -1,137 +0,0 @@ -# -# (c) 2018 Red Hat, Inc. -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see <http://www.gnu.org/licenses/>. -# -import json - -from copy import deepcopy -from contextlib import contextmanager - -try: - from lxml.etree import fromstring, tostring -except ImportError: - from xml.etree.ElementTree import fromstring, tostring - -from ansible.module_utils._text import to_text, to_bytes -from ansible.module_utils.connection import Connection, ConnectionError -from ansible.module_utils.network.common.netconf import NetconfConnection - - -IGNORE_XML_ATTRIBUTE = () - - -def get_connection(module): - if hasattr(module, '_netconf_connection'): - return module._netconf_connection - - capabilities = get_capabilities(module) - network_api = capabilities.get('network_api') - if network_api == 'netconf': - module._netconf_connection = NetconfConnection(module._socket_path) - else: - module.fail_json(msg='Invalid connection type %s' % network_api) - - return module._netconf_connection - - -def get_capabilities(module): - if hasattr(module, '_netconf_capabilities'): - return module._netconf_capabilities - - capabilities = Connection(module._socket_path).get_capabilities() - module._netconf_capabilities = json.loads(capabilities) - return module._netconf_capabilities - - -def lock_configuration(module, target=None): - conn = get_connection(module) - return conn.lock(target=target) - - -def unlock_configuration(module, target=None): - conn = get_connection(module) - return conn.unlock(target=target) - - -@contextmanager -def locked_config(module, target=None): - try: - lock_configuration(module, target=target) - yield - finally: - unlock_configuration(module, target=target) - - -def get_config(module, source, filter=None, lock=False): - conn = get_connection(module) - try: - locked = False - if lock: - conn.lock(target=source) - locked = True - response = conn.get_config(source=source, filter=filter) - - except ConnectionError as e: - module.fail_json(msg=to_text(e, errors='surrogate_then_replace').strip()) - - finally: - if locked: - conn.unlock(target=source) - - return response - - -def get(module, filter, lock=False): - conn = get_connection(module) - try: - locked = False - if lock: - conn.lock(target='running') - locked = True - - response = conn.get(filter=filter) - - except ConnectionError as e: - module.fail_json(msg=to_text(e, errors='surrogate_then_replace').strip()) - - finally: - if locked: - conn.unlock(target='running') - - return response - - -def dispatch(module, request): - conn = get_connection(module) - try: - response = conn.dispatch(request) - except ConnectionError as e: - module.fail_json(msg=to_text(e, errors='surrogate_then_replace').strip()) - - return response - - -def sanitize_xml(data): - tree = fromstring(to_bytes(deepcopy(data), errors='surrogate_then_replace')) - for element in tree.getiterator(): - # remove attributes - attribute = element.attrib - if attribute: - for key in list(attribute): - if key not in IGNORE_XML_ATTRIBUTE: - attribute.pop(key) - return to_text(tostring(tree), errors='surrogate_then_replace').strip() diff --git a/lib/ansible/module_utils/network/restconf/restconf.py b/lib/ansible/module_utils/network/restconf/restconf.py deleted file mode 100644 index 81a26bff44..0000000000 --- a/lib/ansible/module_utils/network/restconf/restconf.py +++ /dev/null @@ -1,57 +0,0 @@ -# This code is part of Ansible, but is an independent component. -# This particular file snippet, and this file snippet only, is BSD licensed. -# Modules you write using this snippet, which is embedded dynamically by Ansible -# still belong to the author of the module, and may assign their own license -# to the complete work. -# -# (c) 2018 Red Hat Inc. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from ansible.module_utils.connection import Connection - - -def get(module, path=None, content=None, fields=None, output='json'): - if path is None: - raise ValueError('path value must be provided') - if content: - path += '?' + 'content=%s' % content - if fields: - path += '?' + 'field=%s' % fields - - accept = None - if output == 'xml': - accept = 'application/yang-data+xml' - - connection = Connection(module._socket_path) - return connection.send_request(None, path=path, method='GET', accept=accept) - - -def edit_config(module, path=None, content=None, method='GET', format='json'): - if path is None: - raise ValueError('path value must be provided') - - content_type = None - if format == 'xml': - content_type = 'application/yang-data+xml' - - connection = Connection(module._socket_path) - return connection.send_request(content, path=path, method=method, content_type=content_type) |