summaryrefslogtreecommitdiff
path: root/lib/ansible/module_utils/network
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/module_utils/network')
-rw-r--r--lib/ansible/module_utils/network/common/cfg/base.py24
-rw-r--r--lib/ansible/module_utils/network/common/config.py468
-rw-r--r--lib/ansible/module_utils/network/common/facts/facts.py132
-rw-r--r--lib/ansible/module_utils/network/common/netconf.py162
-rw-r--r--lib/ansible/module_utils/network/common/network.py249
-rw-r--r--lib/ansible/module_utils/network/common/parsing.py305
-rw-r--r--lib/ansible/module_utils/network/common/utils.py643
-rw-r--r--lib/ansible/module_utils/network/netconf/netconf.py137
-rw-r--r--lib/ansible/module_utils/network/restconf/restconf.py57
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)