diff --git a/lib/ansible/module_utils/compat/ipaddress.py b/lib/ansible/module_utils/compat/ipaddress.py deleted file mode 100644 index c46ad72a090..00000000000 --- a/lib/ansible/module_utils/compat/ipaddress.py +++ /dev/null @@ -1,2476 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Ansible, but is an independent component. -# This particular file, and this file only, is based on -# Lib/ipaddress.py of cpython -# It is licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -# -# 1. This LICENSE AGREEMENT is between the Python Software Foundation -# ("PSF"), and the Individual or Organization ("Licensee") accessing and -# otherwise using this software ("Python") in source or binary form and -# its associated documentation. -# -# 2. Subject to the terms and conditions of this License Agreement, PSF hereby -# grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -# analyze, test, perform and/or display publicly, prepare derivative works, -# distribute, and otherwise use Python alone or in any derivative version, -# provided, however, that PSF's License Agreement and PSF's notice of copyright, -# i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -# 2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved" -# are retained in Python alone or in any derivative version prepared by Licensee. -# -# 3. In the event Licensee prepares a derivative work that is based on -# or incorporates Python or any part thereof, and wants to make -# the derivative work available to others as provided herein, then -# Licensee hereby agrees to include in any such work a brief summary of -# the changes made to Python. -# -# 4. PSF is making Python available to Licensee on an "AS IS" -# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -# INFRINGE ANY THIRD PARTY RIGHTS. -# -# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. -# -# 6. This License Agreement will automatically terminate upon a material -# breach of its terms and conditions. -# -# 7. Nothing in this License Agreement shall be deemed to create any -# relationship of agency, partnership, or joint venture between PSF and -# Licensee. This License Agreement does not grant permission to use PSF -# trademarks or trade name in a trademark sense to endorse or promote -# products or services of Licensee, or any third party. -# -# 8. By copying, installing or otherwise using Python, Licensee -# agrees to be bound by the terms and conditions of this License -# Agreement. - -# Copyright 2007 Google Inc. -# Licensed to PSF under a Contributor Agreement. - -"""A fast, lightweight IPv4/IPv6 manipulation library in Python. - -This library is used to create/poke/manipulate IPv4 and IPv6 addresses -and networks. - -""" - -from __future__ import unicode_literals - - -import itertools -import struct - - -# The following makes it easier for us to script updates of the bundled code and is not part of -# upstream -_BUNDLED_METADATA = {"pypi_name": "ipaddress", "version": "1.0.22"} - -__version__ = '1.0.22' - -# Compatibility functions -_compat_int_types = (int,) -try: - _compat_int_types = (int, long) -except NameError: - pass -try: - _compat_str = unicode -except NameError: - _compat_str = str - assert bytes != str -if b'\0'[0] == 0: # Python 3 semantics - def _compat_bytes_to_byte_vals(byt): - return byt -else: - def _compat_bytes_to_byte_vals(byt): - return [struct.unpack(b'!B', b)[0] for b in byt] -try: - _compat_int_from_byte_vals = int.from_bytes -except AttributeError: - def _compat_int_from_byte_vals(bytvals, endianess): - assert endianess == 'big' - res = 0 - for bv in bytvals: - assert isinstance(bv, _compat_int_types) - res = (res << 8) + bv - return res - - -def _compat_to_bytes(intval, length, endianess): - assert isinstance(intval, _compat_int_types) - assert endianess == 'big' - if length == 4: - if intval < 0 or intval >= 2 ** 32: - raise struct.error("integer out of range for 'I' format code") - return struct.pack(b'!I', intval) - elif length == 16: - if intval < 0 or intval >= 2 ** 128: - raise struct.error("integer out of range for 'QQ' format code") - return struct.pack(b'!QQ', intval >> 64, intval & 0xffffffffffffffff) - else: - raise NotImplementedError() - - -if hasattr(int, 'bit_length'): - # Not int.bit_length , since that won't work in 2.7 where long exists - def _compat_bit_length(i): - return i.bit_length() -else: - def _compat_bit_length(i): - for res in itertools.count(): - if i >> res == 0: - return res - - -def _compat_range(start, end, step=1): - assert step > 0 - i = start - while i < end: - yield i - i += step - - -class _TotalOrderingMixin(object): - __slots__ = () - - # Helper that derives the other comparison operations from - # __lt__ and __eq__ - # We avoid functools.total_ordering because it doesn't handle - # NotImplemented correctly yet (http://bugs.python.org/issue10042) - def __eq__(self, other): - raise NotImplementedError - - def __ne__(self, other): - equal = self.__eq__(other) - if equal is NotImplemented: - return NotImplemented - return not equal - - def __lt__(self, other): - raise NotImplementedError - - def __le__(self, other): - less = self.__lt__(other) - if less is NotImplemented or not less: - return self.__eq__(other) - return less - - def __gt__(self, other): - less = self.__lt__(other) - if less is NotImplemented: - return NotImplemented - equal = self.__eq__(other) - if equal is NotImplemented: - return NotImplemented - return not (less or equal) - - def __ge__(self, other): - less = self.__lt__(other) - if less is NotImplemented: - return NotImplemented - return not less - - -IPV4LENGTH = 32 -IPV6LENGTH = 128 - - -class AddressValueError(ValueError): - """A Value Error related to the address.""" - - -class NetmaskValueError(ValueError): - """A Value Error related to the netmask.""" - - -def ip_address(address): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP address. Either IPv4 or - IPv6 addresses may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Address or IPv6Address object. - - Raises: - ValueError: if the *address* passed isn't either a v4 or a v6 - address - - """ - try: - return IPv4Address(address) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Address(address) - except (AddressValueError, NetmaskValueError): - pass - - if isinstance(address, bytes): - raise AddressValueError( - '%r does not appear to be an IPv4 or IPv6 address. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?' % address) - - raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % - address) - - -def ip_network(address, strict=True): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP network. Either IPv4 or - IPv6 networks may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Network or IPv6Network object. - - Raises: - ValueError: if the string passed isn't either a v4 or a v6 - address. Or if the network has host bits set. - - """ - try: - return IPv4Network(address, strict) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Network(address, strict) - except (AddressValueError, NetmaskValueError): - pass - - if isinstance(address, bytes): - raise AddressValueError( - '%r does not appear to be an IPv4 or IPv6 network. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?' % address) - - raise ValueError('%r does not appear to be an IPv4 or IPv6 network' % - address) - - -def ip_interface(address): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP address. Either IPv4 or - IPv6 addresses may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Interface or IPv6Interface object. - - Raises: - ValueError: if the string passed isn't either a v4 or a v6 - address. - - Notes: - The IPv?Interface classes describe an Address on a particular - Network, so they're basically a combination of both the Address - and Network classes. - - """ - try: - return IPv4Interface(address) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Interface(address) - except (AddressValueError, NetmaskValueError): - pass - - raise ValueError('%r does not appear to be an IPv4 or IPv6 interface' % - address) - - -def v4_int_to_packed(address): - """Represent an address as 4 packed bytes in network (big-endian) order. - - Args: - address: An integer representation of an IPv4 IP address. - - Returns: - The integer address packed as 4 bytes in network (big-endian) order. - - Raises: - ValueError: If the integer is negative or too large to be an - IPv4 IP address. - - """ - try: - return _compat_to_bytes(address, 4, 'big') - except (struct.error, OverflowError): - raise ValueError("Address negative or too large for IPv4") - - -def v6_int_to_packed(address): - """Represent an address as 16 packed bytes in network (big-endian) order. - - Args: - address: An integer representation of an IPv6 IP address. - - Returns: - The integer address packed as 16 bytes in network (big-endian) order. - - """ - try: - return _compat_to_bytes(address, 16, 'big') - except (struct.error, OverflowError): - raise ValueError("Address negative or too large for IPv6") - - -def _split_optional_netmask(address): - """Helper to split the netmask and raise AddressValueError if needed""" - addr = _compat_str(address).split('/') - if len(addr) > 2: - raise AddressValueError("Only one '/' permitted in %r" % address) - return addr - - -def _find_address_range(addresses): - """Find a sequence of sorted deduplicated IPv#Address. - - Args: - addresses: a list of IPv#Address objects. - - Yields: - A tuple containing the first and last IP addresses in the sequence. - - """ - it = iter(addresses) - first = last = next(it) # pylint: disable=stop-iteration-return - for ip in it: - if ip._ip != last._ip + 1: - yield first, last - first = ip - last = ip - yield first, last - - -def _count_righthand_zero_bits(number, bits): - """Count the number of zero bits on the right hand side. - - Args: - number: an integer. - bits: maximum number of bits to count. - - Returns: - The number of zero bits on the right hand side of the number. - - """ - if number == 0: - return bits - return min(bits, _compat_bit_length(~number & (number - 1))) - - -def summarize_address_range(first, last): - """Summarize a network range given the first and last IP addresses. - - Example: - >>> list(summarize_address_range(IPv4Address('192.0.2.0'), - ... IPv4Address('192.0.2.130'))) - ... #doctest: +NORMALIZE_WHITESPACE - [IPv4Network('192.0.2.0/25'), IPv4Network('192.0.2.128/31'), - IPv4Network('192.0.2.130/32')] - - Args: - first: the first IPv4Address or IPv6Address in the range. - last: the last IPv4Address or IPv6Address in the range. - - Returns: - An iterator of the summarized IPv(4|6) network objects. - - Raise: - TypeError: - If the first and last objects are not IP addresses. - If the first and last objects are not the same version. - ValueError: - If the last object is not greater than the first. - If the version of the first address is not 4 or 6. - - """ - if (not (isinstance(first, _BaseAddress) and - isinstance(last, _BaseAddress))): - raise TypeError('first and last must be IP addresses, not networks') - if first.version != last.version: - raise TypeError("%s and %s are not of the same version" % ( - first, last)) - if first > last: - raise ValueError('last IP address must be greater than first') - - if first.version == 4: - ip = IPv4Network - elif first.version == 6: - ip = IPv6Network - else: - raise ValueError('unknown IP version') - - ip_bits = first._max_prefixlen - first_int = first._ip - last_int = last._ip - while first_int <= last_int: - nbits = min(_count_righthand_zero_bits(first_int, ip_bits), - _compat_bit_length(last_int - first_int + 1) - 1) - net = ip((first_int, ip_bits - nbits)) - yield net - first_int += 1 << nbits - if first_int - 1 == ip._ALL_ONES: - break - - -def _collapse_addresses_internal(addresses): - """Loops through the addresses, collapsing concurrent netblocks. - - Example: - - ip1 = IPv4Network('192.0.2.0/26') - ip2 = IPv4Network('192.0.2.64/26') - ip3 = IPv4Network('192.0.2.128/26') - ip4 = IPv4Network('192.0.2.192/26') - - _collapse_addresses_internal([ip1, ip2, ip3, ip4]) -> - [IPv4Network('192.0.2.0/24')] - - This shouldn't be called directly; it is called via - collapse_addresses([]). - - Args: - addresses: A list of IPv4Network's or IPv6Network's - - Returns: - A list of IPv4Network's or IPv6Network's depending on what we were - passed. - - """ - # First merge - to_merge = list(addresses) - subnets = {} - while to_merge: - net = to_merge.pop() - supernet = net.supernet() - existing = subnets.get(supernet) - if existing is None: - subnets[supernet] = net - elif existing != net: - # Merge consecutive subnets - del subnets[supernet] - to_merge.append(supernet) - # Then iterate over resulting networks, skipping subsumed subnets - last = None - for net in sorted(subnets.values()): - if last is not None: - # Since they are sorted, - # last.network_address <= net.network_address is a given. - if last.broadcast_address >= net.broadcast_address: - continue - yield net - last = net - - -def collapse_addresses(addresses): - """Collapse a list of IP objects. - - Example: - collapse_addresses([IPv4Network('192.0.2.0/25'), - IPv4Network('192.0.2.128/25')]) -> - [IPv4Network('192.0.2.0/24')] - - Args: - addresses: An iterator of IPv4Network or IPv6Network objects. - - Returns: - An iterator of the collapsed IPv(4|6)Network objects. - - Raises: - TypeError: If passed a list of mixed version objects. - - """ - addrs = [] - ips = [] - nets = [] - - # split IP addresses and networks - for ip in addresses: - if isinstance(ip, _BaseAddress): - if ips and ips[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, ips[-1])) - ips.append(ip) - elif ip._prefixlen == ip._max_prefixlen: - if ips and ips[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, ips[-1])) - try: - ips.append(ip.ip) - except AttributeError: - ips.append(ip.network_address) - else: - if nets and nets[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, nets[-1])) - nets.append(ip) - - # sort and dedup - ips = sorted(set(ips)) - - # find consecutive address ranges in the sorted sequence and summarize them - if ips: - for first, last in _find_address_range(ips): - addrs.extend(summarize_address_range(first, last)) - - return _collapse_addresses_internal(addrs + nets) - - -def get_mixed_type_key(obj): - """Return a key suitable for sorting between networks and addresses. - - Address and Network objects are not sortable by default; they're - fundamentally different so the expression - - IPv4Address('192.0.2.0') <= IPv4Network('192.0.2.0/24') - - doesn't make any sense. There are some times however, where you may wish - to have ipaddress sort these for you anyway. If you need to do this, you - can use this function as the key= argument to sorted(). - - Args: - obj: either a Network or Address object. - Returns: - appropriate key. - - """ - if isinstance(obj, _BaseNetwork): - return obj._get_networks_key() - elif isinstance(obj, _BaseAddress): - return obj._get_address_key() - return NotImplemented - - -class _IPAddressBase(_TotalOrderingMixin): - - """The mother class.""" - - __slots__ = () - - @property - def exploded(self): - """Return the longhand version of the IP address as a string.""" - return self._explode_shorthand_ip_string() - - @property - def compressed(self): - """Return the shorthand version of the IP address as a string.""" - return _compat_str(self) - - @property - def reverse_pointer(self): - """The name of the reverse DNS pointer for the IP address, e.g.: - >>> ipaddress.ip_address("127.0.0.1").reverse_pointer - '1.0.0.127.in-addr.arpa' - >>> ipaddress.ip_address("2001:db8::1").reverse_pointer - '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa' - - """ - return self._reverse_pointer() - - @property - def version(self): - msg = '%200s has no version specified' % (type(self),) - raise NotImplementedError(msg) - - def _check_int_address(self, address): - if address < 0: - msg = "%d (< 0) is not permitted as an IPv%d address" - raise AddressValueError(msg % (address, self._version)) - if address > self._ALL_ONES: - msg = "%d (>= 2**%d) is not permitted as an IPv%d address" - raise AddressValueError(msg % (address, self._max_prefixlen, - self._version)) - - def _check_packed_address(self, address, expected_len): - address_len = len(address) - if address_len != expected_len: - msg = ( - '%r (len %d != %d) is not permitted as an IPv%d address. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?') - raise AddressValueError(msg % (address, address_len, - expected_len, self._version)) - - @classmethod - def _ip_int_from_prefix(cls, prefixlen): - """Turn the prefix length into a bitwise netmask - - Args: - prefixlen: An integer, the prefix length. - - Returns: - An integer. - - """ - return cls._ALL_ONES ^ (cls._ALL_ONES >> prefixlen) - - @classmethod - def _prefix_from_ip_int(cls, ip_int): - """Return prefix length from the bitwise netmask. - - Args: - ip_int: An integer, the netmask in expanded bitwise format - - Returns: - An integer, the prefix length. - - Raises: - ValueError: If the input intermingles zeroes & ones - """ - trailing_zeroes = _count_righthand_zero_bits(ip_int, - cls._max_prefixlen) - prefixlen = cls._max_prefixlen - trailing_zeroes - leading_ones = ip_int >> trailing_zeroes - all_ones = (1 << prefixlen) - 1 - if leading_ones != all_ones: - byteslen = cls._max_prefixlen // 8 - details = _compat_to_bytes(ip_int, byteslen, 'big') - msg = 'Netmask pattern %r mixes zeroes & ones' - raise ValueError(msg % details) - return prefixlen - - @classmethod - def _report_invalid_netmask(cls, netmask_str): - msg = '%r is not a valid netmask' % netmask_str - raise NetmaskValueError(msg) - - @classmethod - def _prefix_from_prefix_string(cls, prefixlen_str): - """Return prefix length from a numeric string - - Args: - prefixlen_str: The string to be converted - - Returns: - An integer, the prefix length. - - Raises: - NetmaskValueError: If the input is not a valid netmask - """ - # int allows a leading +/- as well as surrounding whitespace, - # so we ensure that isn't the case - if not _BaseV4._DECIMAL_DIGITS.issuperset(prefixlen_str): - cls._report_invalid_netmask(prefixlen_str) - try: - prefixlen = int(prefixlen_str) - except ValueError: - cls._report_invalid_netmask(prefixlen_str) - if not (0 <= prefixlen <= cls._max_prefixlen): - cls._report_invalid_netmask(prefixlen_str) - return prefixlen - - @classmethod - def _prefix_from_ip_string(cls, ip_str): - """Turn a netmask/hostmask string into a prefix length - - Args: - ip_str: The netmask/hostmask to be converted - - Returns: - An integer, the prefix length. - - Raises: - NetmaskValueError: If the input is not a valid netmask/hostmask - """ - # Parse the netmask/hostmask like an IP address. - try: - ip_int = cls._ip_int_from_string(ip_str) - except AddressValueError: - cls._report_invalid_netmask(ip_str) - - # Try matching a netmask (this would be /1*0*/ as a bitwise regexp). - # Note that the two ambiguous cases (all-ones and all-zeroes) are - # treated as netmasks. - try: - return cls._prefix_from_ip_int(ip_int) - except ValueError: - pass - - # Invert the bits, and try matching a /0+1+/ hostmask instead. - ip_int ^= cls._ALL_ONES - try: - return cls._prefix_from_ip_int(ip_int) - except ValueError: - cls._report_invalid_netmask(ip_str) - - def __reduce__(self): - return self.__class__, (_compat_str(self),) - - -class _BaseAddress(_IPAddressBase): - - """A generic IP object. - - This IP class contains the version independent methods which are - used by single IP addresses. - """ - - __slots__ = () - - def __int__(self): - return self._ip - - def __eq__(self, other): - try: - return (self._ip == other._ip and - self._version == other._version) - except AttributeError: - return NotImplemented - - def __lt__(self, other): - if not isinstance(other, _IPAddressBase): - return NotImplemented - if not isinstance(other, _BaseAddress): - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - if self._version != other._version: - raise TypeError('%s and %s are not of the same version' % ( - self, other)) - if self._ip != other._ip: - return self._ip < other._ip - return False - - # Shorthand for Integer addition and subtraction. This is not - # meant to ever support addition/subtraction of addresses. - def __add__(self, other): - if not isinstance(other, _compat_int_types): - return NotImplemented - return self.__class__(int(self) + other) - - def __sub__(self, other): - if not isinstance(other, _compat_int_types): - return NotImplemented - return self.__class__(int(self) - other) - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, _compat_str(self)) - - def __str__(self): - return _compat_str(self._string_from_ip_int(self._ip)) - - def __hash__(self): - return hash(hex(int(self._ip))) - - def _get_address_key(self): - return (self._version, self) - - def __reduce__(self): - return self.__class__, (self._ip,) - - -class _BaseNetwork(_IPAddressBase): - - """A generic IP network object. - - This IP class contains the version independent methods which are - used by networks. - - """ - def __init__(self, address): - self._cache = {} - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, _compat_str(self)) - - def __str__(self): - return '%s/%d' % (self.network_address, self.prefixlen) - - def hosts(self): - """Generate Iterator over usable hosts in a network. - - This is like __iter__ except it doesn't return the network - or broadcast addresses. - - """ - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network + 1, broadcast): - yield self._address_class(x) - - def __iter__(self): - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network, broadcast + 1): - yield self._address_class(x) - - def __getitem__(self, n): - network = int(self.network_address) - broadcast = int(self.broadcast_address) - if n >= 0: - if network + n > broadcast: - raise IndexError('address out of range') - return self._address_class(network + n) - else: - n += 1 - if broadcast + n < network: - raise IndexError('address out of range') - return self._address_class(broadcast + n) - - def __lt__(self, other): - if not isinstance(other, _IPAddressBase): - return NotImplemented - if not isinstance(other, _BaseNetwork): - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - if self._version != other._version: - raise TypeError('%s and %s are not of the same version' % ( - self, other)) - if self.network_address != other.network_address: - return self.network_address < other.network_address - if self.netmask != other.netmask: - return self.netmask < other.netmask - return False - - def __eq__(self, other): - try: - return (self._version == other._version and - self.network_address == other.network_address and - int(self.netmask) == int(other.netmask)) - except AttributeError: - return NotImplemented - - def __hash__(self): - return hash(int(self.network_address) ^ int(self.netmask)) - - def __contains__(self, other): - # always false if one is v4 and the other is v6. - if self._version != other._version: - return False - # dealing with another network. - if isinstance(other, _BaseNetwork): - return False - # dealing with another address - else: - # address - return (int(self.network_address) <= int(other._ip) <= - int(self.broadcast_address)) - - def overlaps(self, other): - """Tell if self is partly contained in other.""" - return self.network_address in other or ( - self.broadcast_address in other or ( - other.network_address in self or ( - other.broadcast_address in self))) - - @property - def broadcast_address(self): - x = self._cache.get('broadcast_address') - if x is None: - x = self._address_class(int(self.network_address) | - int(self.hostmask)) - self._cache['broadcast_address'] = x - return x - - @property - def hostmask(self): - x = self._cache.get('hostmask') - if x is None: - x = self._address_class(int(self.netmask) ^ self._ALL_ONES) - self._cache['hostmask'] = x - return x - - @property - def with_prefixlen(self): - return '%s/%d' % (self.network_address, self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self.network_address, self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self.network_address, self.hostmask) - - @property - def num_addresses(self): - """Number of hosts in the current subnet.""" - return int(self.broadcast_address) - int(self.network_address) + 1 - - @property - def _address_class(self): - # Returning bare address objects (rather than interfaces) allows for - # more consistent behaviour across the network address, broadcast - # address and individual host addresses. - msg = '%200s has no associated address class' % (type(self),) - raise NotImplementedError(msg) - - @property - def prefixlen(self): - return self._prefixlen - - def address_exclude(self, other): - """Remove an address from a larger block. - - For example: - - addr1 = ip_network('192.0.2.0/28') - addr2 = ip_network('192.0.2.1/32') - list(addr1.address_exclude(addr2)) = - [IPv4Network('192.0.2.0/32'), IPv4Network('192.0.2.2/31'), - IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.8/29')] - - or IPv6: - - addr1 = ip_network('2001:db8::1/32') - addr2 = ip_network('2001:db8::1/128') - list(addr1.address_exclude(addr2)) = - [ip_network('2001:db8::1/128'), - ip_network('2001:db8::2/127'), - ip_network('2001:db8::4/126'), - ip_network('2001:db8::8/125'), - ... - ip_network('2001:db8:8000::/33')] - - Args: - other: An IPv4Network or IPv6Network object of the same type. - - Returns: - An iterator of the IPv(4|6)Network objects which is self - minus other. - - Raises: - TypeError: If self and other are of differing address - versions, or if other is not a network object. - ValueError: If other is not completely contained by self. - - """ - if not self._version == other._version: - raise TypeError("%s and %s are not of the same version" % ( - self, other)) - - if not isinstance(other, _BaseNetwork): - raise TypeError("%s is not a network object" % other) - - if not other.subnet_of(self): - raise ValueError('%s not contained in %s' % (other, self)) - if other == self: - return - - # Make sure we're comparing the network of other. - other = other.__class__('%s/%s' % (other.network_address, - other.prefixlen)) - - s1, s2 = self.subnets() - while s1 != other and s2 != other: - if other.subnet_of(s1): - yield s2 - s1, s2 = s1.subnets() - elif other.subnet_of(s2): - yield s1 - s1, s2 = s2.subnets() - else: - # If we got here, there's a bug somewhere. - raise AssertionError('Error performing exclusion: ' - 's1: %s s2: %s other: %s' % - (s1, s2, other)) - if s1 == other: - yield s2 - elif s2 == other: - yield s1 - else: - # If we got here, there's a bug somewhere. - raise AssertionError('Error performing exclusion: ' - 's1: %s s2: %s other: %s' % - (s1, s2, other)) - - def compare_networks(self, other): - """Compare two IP objects. - - This is only concerned about the comparison of the integer - representation of the network addresses. This means that the - host bits aren't considered at all in this method. If you want - to compare host bits, you can easily enough do a - 'HostA._ip < HostB._ip' - - Args: - other: An IP object. - - Returns: - If the IP versions of self and other are the same, returns: - - -1 if self < other: - eg: IPv4Network('192.0.2.0/25') < IPv4Network('192.0.2.128/25') - IPv6Network('2001:db8::1000/124') < - IPv6Network('2001:db8::2000/124') - 0 if self == other - eg: IPv4Network('192.0.2.0/24') == IPv4Network('192.0.2.0/24') - IPv6Network('2001:db8::1000/124') == - IPv6Network('2001:db8::1000/124') - 1 if self > other - eg: IPv4Network('192.0.2.128/25') > IPv4Network('192.0.2.0/25') - IPv6Network('2001:db8::2000/124') > - IPv6Network('2001:db8::1000/124') - - Raises: - TypeError if the IP versions are different. - - """ - # does this need to raise a ValueError? - if self._version != other._version: - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - # self._version == other._version below here: - if self.network_address < other.network_address: - return -1 - if self.network_address > other.network_address: - return 1 - # self.network_address == other.network_address below here: - if self.netmask < other.netmask: - return -1 - if self.netmask > other.netmask: - return 1 - return 0 - - def _get_networks_key(self): - """Network-only key function. - - Returns an object that identifies this address' network and - netmask. This function is a suitable "key" argument for sorted() - and list.sort(). - - """ - return (self._version, self.network_address, self.netmask) - - def subnets(self, prefixlen_diff=1, new_prefix=None): - """The subnets which join to make the current subnet. - - In the case that self contains only one IP - (self._prefixlen == 32 for IPv4 or self._prefixlen == 128 - for IPv6), yield an iterator with just ourself. - - Args: - prefixlen_diff: An integer, the amount the prefix length - should be increased by. This should not be set if - new_prefix is also set. - new_prefix: The desired new prefix length. This must be a - larger number (smaller prefix) than the existing prefix. - This should not be set if prefixlen_diff is also set. - - Returns: - An iterator of IPv(4|6) objects. - - Raises: - ValueError: The prefixlen_diff is too small or too large. - OR - prefixlen_diff and new_prefix are both set or new_prefix - is a smaller number than the current prefix (smaller - number means a larger network) - - """ - if self._prefixlen == self._max_prefixlen: - yield self - return - - if new_prefix is not None: - if new_prefix < self._prefixlen: - raise ValueError('new prefix must be longer') - if prefixlen_diff != 1: - raise ValueError('cannot set prefixlen_diff and new_prefix') - prefixlen_diff = new_prefix - self._prefixlen - - if prefixlen_diff < 0: - raise ValueError('prefix length diff must be > 0') - new_prefixlen = self._prefixlen + prefixlen_diff - - if new_prefixlen > self._max_prefixlen: - raise ValueError( - 'prefix length diff %d is invalid for netblock %s' % ( - new_prefixlen, self)) - - start = int(self.network_address) - end = int(self.broadcast_address) + 1 - step = (int(self.hostmask) + 1) >> prefixlen_diff - for new_addr in _compat_range(start, end, step): - current = self.__class__((new_addr, new_prefixlen)) - yield current - - def supernet(self, prefixlen_diff=1, new_prefix=None): - """The supernet containing the current network. - - Args: - prefixlen_diff: An integer, the amount the prefix length of - the network should be decreased by. For example, given a - /24 network and a prefixlen_diff of 3, a supernet with a - /21 netmask is returned. - - Returns: - An IPv4 network object. - - Raises: - ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have - a negative prefix length. - OR - If prefixlen_diff and new_prefix are both set or new_prefix is a - larger number than the current prefix (larger number means a - smaller network) - - """ - if self._prefixlen == 0: - return self - - if new_prefix is not None: - if new_prefix > self._prefixlen: - raise ValueError('new prefix must be shorter') - if prefixlen_diff != 1: - raise ValueError('cannot set prefixlen_diff and new_prefix') - prefixlen_diff = self._prefixlen - new_prefix - - new_prefixlen = self.prefixlen - prefixlen_diff - if new_prefixlen < 0: - raise ValueError( - 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % - (self.prefixlen, prefixlen_diff)) - return self.__class__(( - int(self.network_address) & (int(self.netmask) << prefixlen_diff), - new_prefixlen)) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is a multicast address. - See RFC 2373 2.7 for details. - - """ - return (self.network_address.is_multicast and - self.broadcast_address.is_multicast) - - @staticmethod - def _is_subnet_of(a, b): - try: - # Always false if one is v4 and the other is v6. - if a._version != b._version: - raise TypeError("%s and %s are not of the same version" % (a, b)) - return (b.network_address <= a.network_address and - b.broadcast_address >= a.broadcast_address) - except AttributeError: - raise TypeError("Unable to test subnet containment " - "between %s and %s" % (a, b)) - - def subnet_of(self, other): - """Return True if this network is a subnet of other.""" - return self._is_subnet_of(self, other) - - def supernet_of(self, other): - """Return True if this network is a supernet of other.""" - return self._is_subnet_of(other, self) - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within one of the - reserved IPv6 Network ranges. - - """ - return (self.network_address.is_reserved and - self.broadcast_address.is_reserved) - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is reserved per RFC 4291. - - """ - return (self.network_address.is_link_local and - self.broadcast_address.is_link_local) - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv4-special-registry or iana-ipv6-special-registry. - - """ - return (self.network_address.is_private and - self.broadcast_address.is_private) - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, True if the address is not reserved per - iana-ipv4-special-registry or iana-ipv6-special-registry. - - """ - return not self.is_private - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 2373 2.5.2. - - """ - return (self.network_address.is_unspecified and - self.broadcast_address.is_unspecified) - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback address as defined in - RFC 2373 2.5.3. - - """ - return (self.network_address.is_loopback and - self.broadcast_address.is_loopback) - - -class _BaseV4(object): - - """Base IPv4 object. - - The following methods are used by IPv4 objects in both single IP - addresses and networks. - - """ - - __slots__ = () - _version = 4 - # Equivalent to 255.255.255.255 or 32 bits of 1's. - _ALL_ONES = (2 ** IPV4LENGTH) - 1 - _DECIMAL_DIGITS = frozenset('0123456789') - - # the valid octets for host and netmasks. only useful for IPv4. - _valid_mask_octets = frozenset([255, 254, 252, 248, 240, 224, 192, 128, 0]) - - _max_prefixlen = IPV4LENGTH - # There are only a handful of valid v4 netmasks, so we cache them all - # when constructed (see _make_netmask()). - _netmask_cache = {} - - def _explode_shorthand_ip_string(self): - return _compat_str(self) - - @classmethod - def _make_netmask(cls, arg): - """Make a (netmask, prefix_len) tuple from the given argument. - - Argument can be: - - an integer (the prefix length) - - a string representing the prefix length (e.g. "24") - - a string representing the prefix netmask (e.g. "255.255.255.0") - """ - if arg not in cls._netmask_cache: - if isinstance(arg, _compat_int_types): - prefixlen = arg - else: - try: - # Check for a netmask in prefix length form - prefixlen = cls._prefix_from_prefix_string(arg) - except NetmaskValueError: - # Check for a netmask or hostmask in dotted-quad form. - # This may raise NetmaskValueError. - prefixlen = cls._prefix_from_ip_string(arg) - netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) - cls._netmask_cache[arg] = netmask, prefixlen - return cls._netmask_cache[arg] - - @classmethod - def _ip_int_from_string(cls, ip_str): - """Turn the given IP string into an integer for comparison. - - Args: - ip_str: A string, the IP ip_str. - - Returns: - The IP ip_str as an integer. - - Raises: - AddressValueError: if ip_str isn't a valid IPv4 Address. - - """ - if not ip_str: - raise AddressValueError('Address cannot be empty') - - octets = ip_str.split('.') - if len(octets) != 4: - raise AddressValueError("Expected 4 octets in %r" % ip_str) - - try: - return _compat_int_from_byte_vals( - map(cls._parse_octet, octets), 'big') - except ValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - - @classmethod - def _parse_octet(cls, octet_str): - """Convert a decimal octet into an integer. - - Args: - octet_str: A string, the number to parse. - - Returns: - The octet as an integer. - - Raises: - ValueError: if the octet isn't strictly a decimal from [0..255]. - - """ - if not octet_str: - raise ValueError("Empty octet not permitted") - # Whitelist the characters, since int() allows a lot of bizarre stuff. - if not cls._DECIMAL_DIGITS.issuperset(octet_str): - msg = "Only decimal digits permitted in %r" - raise ValueError(msg % octet_str) - # We do the length check second, since the invalid character error - # is likely to be more informative for the user - if len(octet_str) > 3: - msg = "At most 3 characters permitted in %r" - raise ValueError(msg % octet_str) - # Convert to integer (we know digits are legal) - octet_int = int(octet_str, 10) - # Any octets that look like they *might* be written in octal, - # and which don't look exactly the same in both octal and - # decimal are rejected as ambiguous - if octet_int > 7 and octet_str[0] == '0': - msg = "Ambiguous (octal/decimal) value in %r not permitted" - raise ValueError(msg % octet_str) - if octet_int > 255: - raise ValueError("Octet %d (> 255) not permitted" % octet_int) - return octet_int - - @classmethod - def _string_from_ip_int(cls, ip_int): - """Turns a 32-bit integer into dotted decimal notation. - - Args: - ip_int: An integer, the IP address. - - Returns: - The IP address as a string in dotted decimal notation. - - """ - return '.'.join(_compat_str(struct.unpack(b'!B', b)[0] - if isinstance(b, bytes) - else b) - for b in _compat_to_bytes(ip_int, 4, 'big')) - - def _is_hostmask(self, ip_str): - """Test if the IP string is a hostmask (rather than a netmask). - - Args: - ip_str: A string, the potential hostmask. - - Returns: - A boolean, True if the IP string is a hostmask. - - """ - bits = ip_str.split('.') - try: - parts = [x for x in map(int, bits) if x in self._valid_mask_octets] - except ValueError: - return False - if len(parts) != len(bits): - return False - if parts[0] < parts[-1]: - return True - return False - - def _reverse_pointer(self): - """Return the reverse DNS pointer name for the IPv4 address. - - This implements the method described in RFC1035 3.5. - - """ - reverse_octets = _compat_str(self).split('.')[::-1] - return '.'.join(reverse_octets) + '.in-addr.arpa' - - @property - def max_prefixlen(self): - return self._max_prefixlen - - @property - def version(self): - return self._version - - -class IPv4Address(_BaseV4, _BaseAddress): - - """Represent and manipulate single IPv4 Addresses.""" - - __slots__ = ('_ip', '__weakref__') - - def __init__(self, address): - - """ - Args: - address: A string or integer representing the IP - - Additionally, an integer can be passed, so - IPv4Address('192.0.2.1') == IPv4Address(3221225985). - or, more generally - IPv4Address(int(IPv4Address('192.0.2.1'))) == - IPv4Address('192.0.2.1') - - Raises: - AddressValueError: If ipaddress isn't a valid IPv4 address. - - """ - # Efficient constructor from integer. - if isinstance(address, _compat_int_types): - self._check_int_address(address) - self._ip = address - return - - # Constructing from a packed address - if isinstance(address, bytes): - self._check_packed_address(address, 4) - bvs = _compat_bytes_to_byte_vals(address) - self._ip = _compat_int_from_byte_vals(bvs, 'big') - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP string. - addr_str = _compat_str(address) - if '/' in addr_str: - raise AddressValueError("Unexpected '/' in %r" % address) - self._ip = self._ip_int_from_string(addr_str) - - @property - def packed(self): - """The binary representation of this address.""" - return v4_int_to_packed(self._ip) - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within the - reserved IPv4 Network range. - - """ - return self in self._constants._reserved_network - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv4-special-registry. - - """ - return any(self in net for net in self._constants._private_networks) - - @property - def is_global(self): - return ( - self not in self._constants._public_network and - not self.is_private) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is multicast. - See RFC 3171 for details. - - """ - return self in self._constants._multicast_network - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 5735 3. - - """ - return self == self._constants._unspecified_address - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback per RFC 3330. - - """ - return self in self._constants._loopback_network - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is link-local per RFC 3927. - - """ - return self in self._constants._linklocal_network - - -class IPv4Interface(IPv4Address): - - def __init__(self, address): - if isinstance(address, (bytes, _compat_int_types)): - IPv4Address.__init__(self, address) - self.network = IPv4Network(self._ip) - self._prefixlen = self._max_prefixlen - return - - if isinstance(address, tuple): - IPv4Address.__init__(self, address[0]) - if len(address) > 1: - self._prefixlen = int(address[1]) - else: - self._prefixlen = self._max_prefixlen - - self.network = IPv4Network(address, strict=False) - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - return - - addr = _split_optional_netmask(address) - IPv4Address.__init__(self, addr[0]) - - self.network = IPv4Network(address, strict=False) - self._prefixlen = self.network._prefixlen - - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - - def __str__(self): - return '%s/%d' % (self._string_from_ip_int(self._ip), - self.network.prefixlen) - - def __eq__(self, other): - address_equal = IPv4Address.__eq__(self, other) - if not address_equal or address_equal is NotImplemented: - return address_equal - try: - return self.network == other.network - except AttributeError: - # An interface with an associated network is NOT the - # same as an unassociated address. That's why the hash - # takes the extra info into account. - return False - - def __lt__(self, other): - address_less = IPv4Address.__lt__(self, other) - if address_less is NotImplemented: - return NotImplemented - try: - return (self.network < other.network or - self.network == other.network and address_less) - except AttributeError: - # We *do* allow addresses and interfaces to be sorted. The - # unassociated address is considered less than all interfaces. - return False - - def __hash__(self): - return self._ip ^ self._prefixlen ^ int(self.network.network_address) - - __reduce__ = _IPAddressBase.__reduce__ - - @property - def ip(self): - return IPv4Address(self._ip) - - @property - def with_prefixlen(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.hostmask) - - -class IPv4Network(_BaseV4, _BaseNetwork): - - """This class represents and manipulates 32-bit IPv4 network + addresses.. - - Attributes: [examples for IPv4Network('192.0.2.0/27')] - .network_address: IPv4Address('192.0.2.0') - .hostmask: IPv4Address('0.0.0.31') - .broadcast_address: IPv4Address('192.0.2.32') - .netmask: IPv4Address('255.255.255.224') - .prefixlen: 27 - - """ - # Class to use when creating address objects - _address_class = IPv4Address - - def __init__(self, address, strict=True): - - """Instantiate a new IPv4 network object. - - Args: - address: A string or integer representing the IP [& network]. - '192.0.2.0/24' - '192.0.2.0/255.255.255.0' - '192.0.0.2/0.0.0.255' - are all functionally the same in IPv4. Similarly, - '192.0.2.1' - '192.0.2.1/255.255.255.255' - '192.0.2.1/32' - are also functionally equivalent. That is to say, failing to - provide a subnetmask will create an object with a mask of /32. - - If the mask (portion after the / in the argument) is given in - dotted quad form, it is treated as a netmask if it starts with a - non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it - starts with a zero field (e.g. 0.255.255.255 == /8), with the - single exception of an all-zero mask which is treated as a - netmask == /0. If no mask is given, a default of /32 is used. - - Additionally, an integer can be passed, so - IPv4Network('192.0.2.1') == IPv4Network(3221225985) - or, more generally - IPv4Interface(int(IPv4Interface('192.0.2.1'))) == - IPv4Interface('192.0.2.1') - - Raises: - AddressValueError: If ipaddress isn't a valid IPv4 address. - NetmaskValueError: If the netmask isn't valid for - an IPv4 address. - ValueError: If strict is True and a network address is not - supplied. - - """ - _BaseNetwork.__init__(self, address) - - # Constructing from a packed address or integer - if isinstance(address, (_compat_int_types, bytes)): - self.network_address = IPv4Address(address) - self.netmask, self._prefixlen = self._make_netmask( - self._max_prefixlen) - # fixme: address/network test here. - return - - if isinstance(address, tuple): - if len(address) > 1: - arg = address[1] - else: - # We weren't given an address[1] - arg = self._max_prefixlen - self.network_address = IPv4Address(address[0]) - self.netmask, self._prefixlen = self._make_netmask(arg) - packed = int(self.network_address) - if packed & int(self.netmask) != packed: - if strict: - raise ValueError('%s has host bits set' % self) - else: - self.network_address = IPv4Address(packed & - int(self.netmask)) - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP prefix string. - addr = _split_optional_netmask(address) - self.network_address = IPv4Address(self._ip_int_from_string(addr[0])) - - if len(addr) == 2: - arg = addr[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - - if strict: - if (IPv4Address(int(self.network_address) & int(self.netmask)) != - self.network_address): - raise ValueError('%s has host bits set' % self) - self.network_address = IPv4Address(int(self.network_address) & - int(self.netmask)) - - if self._prefixlen == (self._max_prefixlen - 1): - self.hosts = self.__iter__ - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, True if the address is not reserved per - iana-ipv4-special-registry. - - """ - return (not (self.network_address in IPv4Network('100.64.0.0/10') and - self.broadcast_address in IPv4Network('100.64.0.0/10')) and - not self.is_private) - - -class _IPv4Constants(object): - - _linklocal_network = IPv4Network('169.254.0.0/16') - - _loopback_network = IPv4Network('127.0.0.0/8') - - _multicast_network = IPv4Network('224.0.0.0/4') - - _public_network = IPv4Network('100.64.0.0/10') - - _private_networks = [ - IPv4Network('0.0.0.0/8'), - IPv4Network('10.0.0.0/8'), - IPv4Network('127.0.0.0/8'), - IPv4Network('169.254.0.0/16'), - IPv4Network('172.16.0.0/12'), - IPv4Network('192.0.0.0/29'), - IPv4Network('192.0.0.170/31'), - IPv4Network('192.0.2.0/24'), - IPv4Network('192.168.0.0/16'), - IPv4Network('198.18.0.0/15'), - IPv4Network('198.51.100.0/24'), - IPv4Network('203.0.113.0/24'), - IPv4Network('240.0.0.0/4'), - IPv4Network('255.255.255.255/32'), - ] - - _reserved_network = IPv4Network('240.0.0.0/4') - - _unspecified_address = IPv4Address('0.0.0.0') - - -IPv4Address._constants = _IPv4Constants - - -class _BaseV6(object): - - """Base IPv6 object. - - The following methods are used by IPv6 objects in both single IP - addresses and networks. - - """ - - __slots__ = () - _version = 6 - _ALL_ONES = (2 ** IPV6LENGTH) - 1 - _HEXTET_COUNT = 8 - _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef') - _max_prefixlen = IPV6LENGTH - - # There are only a bunch of valid v6 netmasks, so we cache them all - # when constructed (see _make_netmask()). - _netmask_cache = {} - - @classmethod - def _make_netmask(cls, arg): - """Make a (netmask, prefix_len) tuple from the given argument. - - Argument can be: - - an integer (the prefix length) - - a string representing the prefix length (e.g. "24") - - a string representing the prefix netmask (e.g. "255.255.255.0") - """ - if arg not in cls._netmask_cache: - if isinstance(arg, _compat_int_types): - prefixlen = arg - else: - prefixlen = cls._prefix_from_prefix_string(arg) - netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) - cls._netmask_cache[arg] = netmask, prefixlen - return cls._netmask_cache[arg] - - @classmethod - def _ip_int_from_string(cls, ip_str): - """Turn an IPv6 ip_str into an integer. - - Args: - ip_str: A string, the IPv6 ip_str. - - Returns: - An int, the IPv6 address - - Raises: - AddressValueError: if ip_str isn't a valid IPv6 Address. - - """ - if not ip_str: - raise AddressValueError('Address cannot be empty') - - parts = ip_str.split(':') - - # An IPv6 address needs at least 2 colons (3 parts). - _min_parts = 3 - if len(parts) < _min_parts: - msg = "At least %d parts expected in %r" % (_min_parts, ip_str) - raise AddressValueError(msg) - - # If the address has an IPv4-style suffix, convert it to hexadecimal. - if '.' in parts[-1]: - try: - ipv4_int = IPv4Address(parts.pop())._ip - except AddressValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) - parts.append('%x' % (ipv4_int & 0xFFFF)) - - # An IPv6 address can't have more than 8 colons (9 parts). - # The extra colon comes from using the "::" notation for a single - # leading or trailing zero part. - _max_parts = cls._HEXTET_COUNT + 1 - if len(parts) > _max_parts: - msg = "At most %d colons permitted in %r" % ( - _max_parts - 1, ip_str) - raise AddressValueError(msg) - - # Disregarding the endpoints, find '::' with nothing in between. - # This indicates that a run of zeroes has been skipped. - skip_index = None - for i in _compat_range(1, len(parts) - 1): - if not parts[i]: - if skip_index is not None: - # Can't have more than one '::' - msg = "At most one '::' permitted in %r" % ip_str - raise AddressValueError(msg) - skip_index = i - - # parts_hi is the number of parts to copy from above/before the '::' - # parts_lo is the number of parts to copy from below/after the '::' - if skip_index is not None: - # If we found a '::', then check if it also covers the endpoints. - parts_hi = skip_index - parts_lo = len(parts) - skip_index - 1 - if not parts[0]: - parts_hi -= 1 - if parts_hi: - msg = "Leading ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # ^: requires ^:: - if not parts[-1]: - parts_lo -= 1 - if parts_lo: - msg = "Trailing ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # :$ requires ::$ - parts_skipped = cls._HEXTET_COUNT - (parts_hi + parts_lo) - if parts_skipped < 1: - msg = "Expected at most %d other parts with '::' in %r" - raise AddressValueError(msg % (cls._HEXTET_COUNT - 1, ip_str)) - else: - # Otherwise, allocate the entire address to parts_hi. The - # endpoints could still be empty, but _parse_hextet() will check - # for that. - if len(parts) != cls._HEXTET_COUNT: - msg = "Exactly %d parts expected without '::' in %r" - raise AddressValueError(msg % (cls._HEXTET_COUNT, ip_str)) - if not parts[0]: - msg = "Leading ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # ^: requires ^:: - if not parts[-1]: - msg = "Trailing ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # :$ requires ::$ - parts_hi = len(parts) - parts_lo = 0 - parts_skipped = 0 - - try: - # Now, parse the hextets into a 128-bit integer. - ip_int = 0 - for i in range(parts_hi): - ip_int <<= 16 - ip_int |= cls._parse_hextet(parts[i]) - ip_int <<= 16 * parts_skipped - for i in range(-parts_lo, 0): - ip_int <<= 16 - ip_int |= cls._parse_hextet(parts[i]) - return ip_int - except ValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - - @classmethod - def _parse_hextet(cls, hextet_str): - """Convert an IPv6 hextet string into an integer. - - Args: - hextet_str: A string, the number to parse. - - Returns: - The hextet as an integer. - - Raises: - ValueError: if the input isn't strictly a hex number from - [0..FFFF]. - - """ - # Whitelist the characters, since int() allows a lot of bizarre stuff. - if not cls._HEX_DIGITS.issuperset(hextet_str): - raise ValueError("Only hex digits permitted in %r" % hextet_str) - # We do the length check second, since the invalid character error - # is likely to be more informative for the user - if len(hextet_str) > 4: - msg = "At most 4 characters permitted in %r" - raise ValueError(msg % hextet_str) - # Length check means we can skip checking the integer value - return int(hextet_str, 16) - - @classmethod - def _compress_hextets(cls, hextets): - """Compresses a list of hextets. - - Compresses a list of strings, replacing the longest continuous - sequence of "0" in the list with "" and adding empty strings at - the beginning or at the end of the string such that subsequently - calling ":".join(hextets) will produce the compressed version of - the IPv6 address. - - Args: - hextets: A list of strings, the hextets to compress. - - Returns: - A list of strings. - - """ - best_doublecolon_start = -1 - best_doublecolon_len = 0 - doublecolon_start = -1 - doublecolon_len = 0 - for index, hextet in enumerate(hextets): - if hextet == '0': - doublecolon_len += 1 - if doublecolon_start == -1: - # Start of a sequence of zeros. - doublecolon_start = index - if doublecolon_len > best_doublecolon_len: - # This is the longest sequence of zeros so far. - best_doublecolon_len = doublecolon_len - best_doublecolon_start = doublecolon_start - else: - doublecolon_len = 0 - doublecolon_start = -1 - - if best_doublecolon_len > 1: - best_doublecolon_end = (best_doublecolon_start + - best_doublecolon_len) - # For zeros at the end of the address. - if best_doublecolon_end == len(hextets): - hextets += [''] - hextets[best_doublecolon_start:best_doublecolon_end] = [''] - # For zeros at the beginning of the address. - if best_doublecolon_start == 0: - hextets = [''] + hextets - - return hextets - - @classmethod - def _string_from_ip_int(cls, ip_int=None): - """Turns a 128-bit integer into hexadecimal notation. - - Args: - ip_int: An integer, the IP address. - - Returns: - A string, the hexadecimal representation of the address. - - Raises: - ValueError: The address is bigger than 128 bits of all ones. - - """ - if ip_int is None: - ip_int = int(cls._ip) - - if ip_int > cls._ALL_ONES: - raise ValueError('IPv6 address is too large') - - hex_str = '%032x' % ip_int - hextets = ['%x' % int(hex_str[x:x + 4], 16) for x in range(0, 32, 4)] - - hextets = cls._compress_hextets(hextets) - return ':'.join(hextets) - - def _explode_shorthand_ip_string(self): - """Expand a shortened IPv6 address. - - Args: - ip_str: A string, the IPv6 address. - - Returns: - A string, the expanded IPv6 address. - - """ - if isinstance(self, IPv6Network): - ip_str = _compat_str(self.network_address) - elif isinstance(self, IPv6Interface): - ip_str = _compat_str(self.ip) - else: - ip_str = _compat_str(self) - - ip_int = self._ip_int_from_string(ip_str) - hex_str = '%032x' % ip_int - parts = [hex_str[x:x + 4] for x in range(0, 32, 4)] - if isinstance(self, (_BaseNetwork, IPv6Interface)): - return '%s/%d' % (':'.join(parts), self._prefixlen) - return ':'.join(parts) - - def _reverse_pointer(self): - """Return the reverse DNS pointer name for the IPv6 address. - - This implements the method described in RFC3596 2.5. - - """ - reverse_chars = self.exploded[::-1].replace(':', '') - return '.'.join(reverse_chars) + '.ip6.arpa' - - @property - def max_prefixlen(self): - return self._max_prefixlen - - @property - def version(self): - return self._version - - -class IPv6Address(_BaseV6, _BaseAddress): - - """Represent and manipulate single IPv6 Addresses.""" - - __slots__ = ('_ip', '__weakref__') - - def __init__(self, address): - """Instantiate a new IPv6 address object. - - Args: - address: A string or integer representing the IP - - Additionally, an integer can be passed, so - IPv6Address('2001:db8::') == - IPv6Address(42540766411282592856903984951653826560) - or, more generally - IPv6Address(int(IPv6Address('2001:db8::'))) == - IPv6Address('2001:db8::') - - Raises: - AddressValueError: If address isn't a valid IPv6 address. - - """ - # Efficient constructor from integer. - if isinstance(address, _compat_int_types): - self._check_int_address(address) - self._ip = address - return - - # Constructing from a packed address - if isinstance(address, bytes): - self._check_packed_address(address, 16) - bvs = _compat_bytes_to_byte_vals(address) - self._ip = _compat_int_from_byte_vals(bvs, 'big') - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP string. - addr_str = _compat_str(address) - if '/' in addr_str: - raise AddressValueError("Unexpected '/' in %r" % address) - self._ip = self._ip_int_from_string(addr_str) - - @property - def packed(self): - """The binary representation of this address.""" - return v6_int_to_packed(self._ip) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is a multicast address. - See RFC 2373 2.7 for details. - - """ - return self in self._constants._multicast_network - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within one of the - reserved IPv6 Network ranges. - - """ - return any(self in x for x in self._constants._reserved_networks) - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is reserved per RFC 4291. - - """ - return self in self._constants._linklocal_network - - @property - def is_site_local(self): - """Test if the address is reserved for site-local. - - Note that the site-local address space has been deprecated by RFC 3879. - Use is_private to test if this address is in the space of unique local - addresses as defined by RFC 4193. - - Returns: - A boolean, True if the address is reserved per RFC 3513 2.5.6. - - """ - return self in self._constants._sitelocal_network - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv6-special-registry. - - """ - return any(self in net for net in self._constants._private_networks) - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, true if the address is not reserved per - iana-ipv6-special-registry. - - """ - return not self.is_private - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 2373 2.5.2. - - """ - return self._ip == 0 - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback address as defined in - RFC 2373 2.5.3. - - """ - return self._ip == 1 - - @property - def ipv4_mapped(self): - """Return the IPv4 mapped address. - - Returns: - If the IPv6 address is a v4 mapped address, return the - IPv4 mapped address. Return None otherwise. - - """ - if (self._ip >> 32) != 0xFFFF: - return None - return IPv4Address(self._ip & 0xFFFFFFFF) - - @property - def teredo(self): - """Tuple of embedded teredo IPs. - - Returns: - Tuple of the (server, client) IPs or None if the address - doesn't appear to be a teredo address (doesn't start with - 2001::/32) - - """ - if (self._ip >> 96) != 0x20010000: - return None - return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), - IPv4Address(~self._ip & 0xFFFFFFFF)) - - @property - def sixtofour(self): - """Return the IPv4 6to4 embedded address. - - Returns: - The IPv4 6to4-embedded address if present or None if the - address doesn't appear to contain a 6to4 embedded address. - - """ - if (self._ip >> 112) != 0x2002: - return None - return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) - - -class IPv6Interface(IPv6Address): - - def __init__(self, address): - if isinstance(address, (bytes, _compat_int_types)): - IPv6Address.__init__(self, address) - self.network = IPv6Network(self._ip) - self._prefixlen = self._max_prefixlen - return - if isinstance(address, tuple): - IPv6Address.__init__(self, address[0]) - if len(address) > 1: - self._prefixlen = int(address[1]) - else: - self._prefixlen = self._max_prefixlen - self.network = IPv6Network(address, strict=False) - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - return - - addr = _split_optional_netmask(address) - IPv6Address.__init__(self, addr[0]) - self.network = IPv6Network(address, strict=False) - self.netmask = self.network.netmask - self._prefixlen = self.network._prefixlen - self.hostmask = self.network.hostmask - - def __str__(self): - return '%s/%d' % (self._string_from_ip_int(self._ip), - self.network.prefixlen) - - def __eq__(self, other): - address_equal = IPv6Address.__eq__(self, other) - if not address_equal or address_equal is NotImplemented: - return address_equal - try: - return self.network == other.network - except AttributeError: - # An interface with an associated network is NOT the - # same as an unassociated address. That's why the hash - # takes the extra info into account. - return False - - def __lt__(self, other): - address_less = IPv6Address.__lt__(self, other) - if address_less is NotImplemented: - return NotImplemented - try: - return (self.network < other.network or - self.network == other.network and address_less) - except AttributeError: - # We *do* allow addresses and interfaces to be sorted. The - # unassociated address is considered less than all interfaces. - return False - - def __hash__(self): - return self._ip ^ self._prefixlen ^ int(self.network.network_address) - - __reduce__ = _IPAddressBase.__reduce__ - - @property - def ip(self): - return IPv6Address(self._ip) - - @property - def with_prefixlen(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.hostmask) - - @property - def is_unspecified(self): - return self._ip == 0 and self.network.is_unspecified - - @property - def is_loopback(self): - return self._ip == 1 and self.network.is_loopback - - -class IPv6Network(_BaseV6, _BaseNetwork): - - """This class represents and manipulates 128-bit IPv6 networks. - - Attributes: [examples for IPv6('2001:db8::1000/124')] - .network_address: IPv6Address('2001:db8::1000') - .hostmask: IPv6Address('::f') - .broadcast_address: IPv6Address('2001:db8::100f') - .netmask: IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0') - .prefixlen: 124 - - """ - - # Class to use when creating address objects - _address_class = IPv6Address - - def __init__(self, address, strict=True): - """Instantiate a new IPv6 Network object. - - Args: - address: A string or integer representing the IPv6 network or the - IP and prefix/netmask. - '2001:db8::/128' - '2001:db8:0000:0000:0000:0000:0000:0000/128' - '2001:db8::' - are all functionally the same in IPv6. That is to say, - failing to provide a subnetmask will create an object with - a mask of /128. - - Additionally, an integer can be passed, so - IPv6Network('2001:db8::') == - IPv6Network(42540766411282592856903984951653826560) - or, more generally - IPv6Network(int(IPv6Network('2001:db8::'))) == - IPv6Network('2001:db8::') - - strict: A boolean. If true, ensure that we have been passed - A true network address, eg, 2001:db8::1000/124 and not an - IP address on a network, eg, 2001:db8::1/124. - - Raises: - AddressValueError: If address isn't a valid IPv6 address. - NetmaskValueError: If the netmask isn't valid for - an IPv6 address. - ValueError: If strict was True and a network address was not - supplied. - - """ - _BaseNetwork.__init__(self, address) - - # Efficient constructor from integer or packed address - if isinstance(address, (bytes, _compat_int_types)): - self.network_address = IPv6Address(address) - self.netmask, self._prefixlen = self._make_netmask( - self._max_prefixlen) - return - - if isinstance(address, tuple): - if len(address) > 1: - arg = address[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - self.network_address = IPv6Address(address[0]) - packed = int(self.network_address) - if packed & int(self.netmask) != packed: - if strict: - raise ValueError('%s has host bits set' % self) - else: - self.network_address = IPv6Address(packed & - int(self.netmask)) - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP prefix string. - addr = _split_optional_netmask(address) - - self.network_address = IPv6Address(self._ip_int_from_string(addr[0])) - - if len(addr) == 2: - arg = addr[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - - if strict: - if (IPv6Address(int(self.network_address) & int(self.netmask)) != - self.network_address): - raise ValueError('%s has host bits set' % self) - self.network_address = IPv6Address(int(self.network_address) & - int(self.netmask)) - - if self._prefixlen == (self._max_prefixlen - 1): - self.hosts = self.__iter__ - - def hosts(self): - """Generate Iterator over usable hosts in a network. - - This is like __iter__ except it doesn't return the - Subnet-Router anycast address. - - """ - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network + 1, broadcast + 1): - yield self._address_class(x) - - @property - def is_site_local(self): - """Test if the address is reserved for site-local. - - Note that the site-local address space has been deprecated by RFC 3879. - Use is_private to test if this address is in the space of unique local - addresses as defined by RFC 4193. - - Returns: - A boolean, True if the address is reserved per RFC 3513 2.5.6. - - """ - return (self.network_address.is_site_local and - self.broadcast_address.is_site_local) - - -class _IPv6Constants(object): - - _linklocal_network = IPv6Network('fe80::/10') - - _multicast_network = IPv6Network('ff00::/8') - - _private_networks = [ - IPv6Network('::1/128'), - IPv6Network('::/128'), - IPv6Network('::ffff:0:0/96'), - IPv6Network('100::/64'), - IPv6Network('2001::/23'), - IPv6Network('2001:2::/48'), - IPv6Network('2001:db8::/32'), - IPv6Network('2001:10::/28'), - IPv6Network('fc00::/7'), - IPv6Network('fe80::/10'), - ] - - _reserved_networks = [ - IPv6Network('::/8'), IPv6Network('100::/8'), - IPv6Network('200::/7'), IPv6Network('400::/6'), - IPv6Network('800::/5'), IPv6Network('1000::/4'), - IPv6Network('4000::/3'), IPv6Network('6000::/3'), - IPv6Network('8000::/3'), IPv6Network('A000::/3'), - IPv6Network('C000::/3'), IPv6Network('E000::/4'), - IPv6Network('F000::/5'), IPv6Network('F800::/6'), - IPv6Network('FE00::/9'), - ] - - _sitelocal_network = IPv6Network('fec0::/10') - - -IPv6Address._constants = _IPv6Constants 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 5901dc767d4..00000000000 --- 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 974d346098b..00000000000 --- 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 1ebf31d7bd8..00000000000 --- 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 84f3a4e9488..00000000000 --- 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''' - - - - - - - - - - - - - - - - - - - - - ''' - - -# 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 e76d31e9837..00000000000 --- 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, -# -# 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 3e338c82ba8..00000000000 --- 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, -# -# 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 8031738781a..00000000000 --- 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 ") - if not isinstance(comparable, dict): - if comparable is None: - comparable = dict() - else: - raise AssertionError("`comparable` must be of type ") - - 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 ") - if not isinstance(other, dict): - raise AssertionError("`other` must be of type ") - - 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 bd37f14931a..00000000000 --- 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 . -# -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 81a26bff44d..00000000000 --- 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) diff --git a/lib/ansible/modules/commands/telnet.py b/lib/ansible/modules/commands/telnet.py deleted file mode 100644 index 04078a081ed..00000000000 --- a/lib/ansible/modules/commands/telnet.py +++ /dev/null @@ -1,112 +0,0 @@ -# this is a virtual module that is entirely implemented server side -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' ---- -module: telnet -short_description: Executes a low-down and dirty telnet command -version_added: 2.4 -description: - - Executes a low-down and dirty telnet command, not going through the module subsystem. - - This is mostly to be used for enabling ssh on devices that only have telnet enabled by default. -options: - command: - description: - - List of commands to be executed in the telnet session. - required: True - aliases: ['commands'] - host: - description: - - The host/target on which to execute the command - required: False - default: remote_addr - user: - description: - - The user for login - required: False - default: remote_user - password: - description: - - The password for login - port: - description: - - Remote port to use - default: 23 - timeout: - description: - - timeout for remote operations - default: 120 - prompts: - description: - - List of prompts expected before sending next command - required: False - default: ['$'] - login_prompt: - description: - - Login or username prompt to expect - required: False - default: 'login: ' - password_prompt: - description: - - Login or username prompt to expect - required: False - default: 'Password: ' - pause: - description: - - Seconds to pause between each command issued - required: False - default: 1 - send_newline: - description: - - Sends a newline character upon successful connection to start the - terminal session. - required: False - default: False - type: bool - version_added: "2.7" -notes: - - The C(environment) keyword does not work with this task -author: - - Ansible Core Team -''' - -EXAMPLES = ''' -- name: send configuration commands to IOS - telnet: - user: cisco - password: cisco - login_prompt: "Username: " - prompts: - - "[>#]" - command: - - terminal length 0 - - configure terminal - - hostname ios01 - -- name: run show commands - telnet: - user: cisco - password: cisco - login_prompt: "Username: " - prompts: - - "[>#]" - command: - - terminal length 0 - - show version -''' - -RETURN = ''' -output: - description: output of each command is an element in this list - type: list - returned: always - sample: [ 'success', 'success', '', 'warning .. something' ] -''' diff --git a/lib/ansible/modules/network/cli/cli_command.py b/lib/ansible/modules/network/cli/cli_command.py deleted file mode 100644 index 9eecf54cef2..00000000000 --- a/lib/ansible/modules/network/cli/cli_command.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/python -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: cli_command -version_added: "2.7" -author: "Nathaniel Case (@Qalthos)" -short_description: Run a cli command on cli-based network devices -description: - - Sends a command to a network device and returns the result read from the device. -extends_documentation_fragment: network_agnostic -options: - command: - description: - - The command to send to the remote network device. The resulting output - from the command is returned, unless I(sendonly) is set. - required: true - prompt: - description: - - A single regex pattern or a sequence of patterns to evaluate the expected - prompt from I(command). - required: false - type: list - answer: - description: - - The answer to reply with if I(prompt) is matched. The value can be a single answer - or a list of answer for multiple prompts. In case the command execution results in - multiple prompts the sequence of the prompt and excepted answer should be in same order. - required: false - type: list - sendonly: - description: - - The boolean value, that when set to true will send I(command) to the - device but not wait for a result. - type: bool - default: false - required: false - newline: - description: - - The boolean value, that when set to false will send I(answer) to the - device without a trailing newline. - type: bool - default: true - required: false - version_added: 2.9 - check_all: - description: - - By default if any one of the prompts mentioned in C(prompt) option is matched it won't check - for other prompts. This boolean flag, that when set to I(True) will check for all the prompts - mentioned in C(prompt) option in the given order. If the option is set to I(True) all the prompts - should be received from remote host if not it will result in timeout. - type: bool - default: false -""" - -EXAMPLES = """ -- name: run show version on remote devices - cli_command: - command: show version - -- name: run command with json formatted output - cli_command: - command: show version | json - -- name: run command expecting user confirmation - cli_command: - command: commit replace - prompt: This commit will replace or remove the entire running configuration - answer: yes - -- name: run command expecting user confirmation - cli_command: - command: show interface summary - prompt: Press any key to continue - answer: y - newline: false - -- name: run config mode command and handle prompt/answer - cli_command: - command: "{{ item }}" - prompt: - - "Exit with uncommitted changes" - answer: 'y' - loop: - - configure - - set system syslog file test any any - - exit - -- name: multiple prompt, multiple answer (mandatory check for all prompts) - cli_command: - command: "copy sftp sftp://user@host//user/test.img" - check_all: True - prompt: - - "Confirm download operation" - - "Password" - - "Do you want to change that to the standby image" - answer: - - 'y' - - - - 'y' -""" - -RETURN = """ -stdout: - description: The response from the command - returned: when sendonly is false - type: str - sample: 'Version: VyOS 1.1.7[...]' - -json: - description: A dictionary representing a JSON-formatted response - returned: when the device response is valid JSON - type: dict - sample: | - { - "architecture": "i386", - "bootupTimestamp": 1532649700.56, - "modelName": "vEOS", - "version": "4.15.9M" - [...] - } -""" - -from ansible.module_utils._text import to_text -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import Connection, ConnectionError - - -def main(): - """entry point for module execution - """ - argument_spec = dict( - command=dict(type='str', required=True), - prompt=dict(type='list', required=False), - answer=dict(type='list', required=False), - newline=dict(type='bool', default=True, required=False), - sendonly=dict(type='bool', default=False, required=False), - check_all=dict(type='bool', default=False, required=False), - ) - required_together = [['prompt', 'answer']] - module = AnsibleModule(argument_spec=argument_spec, required_together=required_together, - supports_check_mode=True) - - if module.check_mode and not module.params['command'].startswith('show'): - module.fail_json( - msg='Only show commands are supported when using check_mode, not ' - 'executing %s' % module.params['command'] - ) - - warnings = list() - result = {'changed': False, 'warnings': warnings} - - connection = Connection(module._socket_path) - response = '' - try: - response = connection.get(**module.params) - except ConnectionError as exc: - module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) - - if not module.params['sendonly']: - try: - result['json'] = module.from_json(response) - except ValueError: - pass - - result.update({ - 'stdout': response, - }) - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/cli/cli_config.py b/lib/ansible/modules/network/cli/cli_config.py deleted file mode 100644 index c1752f1e6a6..00000000000 --- a/lib/ansible/modules/network/cli/cli_config.py +++ /dev/null @@ -1,408 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2018, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: cli_config -version_added: "2.7" -author: "Trishna Guha (@trishnaguha)" -notes: - - The commands will be returned only for platforms that do not support onbox diff. - The C(--diff) option with the playbook will return the difference in configuration for devices that has support for onbox diff -short_description: Push text based configuration to network devices over network_cli -description: - - This module provides platform agnostic way of pushing text based - configuration to network devices over network_cli connection plugin. -extends_documentation_fragment: network_agnostic -options: - config: - description: - - The config to be pushed to the network device. This argument - is mutually exclusive with C(rollback) and either one of the - option should be given as input. The config should have - indentation that the device uses. - type: 'str' - commit: - description: - - The C(commit) argument instructs the module to push the - configuration to the device. This is mapped to module check mode. - type: 'bool' - replace: - description: - - If the C(replace) argument is set to C(yes), it will replace - the entire running-config of the device with the C(config) - argument value. For devices that support replacing running - configuration from file on device like NXOS/JUNOS, the - C(replace) argument takes path to the file on the device - that will be used for replacing the entire running-config. - The value of C(config) option should be I(None) for such devices. - Nexus 9K devices only support replace. Use I(net_put) or - I(nxos_file_copy) in case of NXOS module to copy the flat file - to remote device and then use set the fullpath to this argument. - type: 'str' - backup: - description: - - This argument will cause the module to create a full backup of - the current running config from the remote device before any - changes are made. If the C(backup_options) value is not given, - the backup file is written to the C(backup) folder in the playbook - root directory or role root directory, if playbook is part of an - ansible role. If the directory does not exist, it is created. - type: bool - default: 'no' - version_added: "2.8" - rollback: - description: - - The C(rollback) argument instructs the module to rollback the - current configuration to the identifier specified in the - argument. If the specified rollback identifier does not - exist on the remote device, the module will fail. To rollback - to the most recent commit, set the C(rollback) argument to 0. - This option is mutually exclusive with C(config). - commit_comment: - description: - - The C(commit_comment) argument specifies a text string to be used - when committing the configuration. If the C(commit) argument - is set to False, this argument is silently ignored. This argument - is only valid for the platforms that support commit operation - with comment. - type: 'str' - defaults: - description: - - The I(defaults) argument will influence how the running-config - is collected from the device. When the value is set to true, - the command used to collect the running-config is append with - the all keyword. When the value is set to false, the command - is issued without the all keyword. - default: 'no' - type: 'bool' - multiline_delimiter: - description: - - This argument is used when pushing a multiline configuration - element to the device. It specifies the character to use as - the delimiting character. This only applies to the configuration - action. - type: 'str' - diff_replace: - description: - - Instructs the module on the way to perform the configuration - on the device. If the C(diff_replace) argument is set to I(line) - then the modified lines are pushed to the device in configuration - mode. If the argument is set to I(block) then the entire command - block is pushed to the device in configuration mode if any - line is not correct. Note that this parameter will be ignored if - the platform has onbox diff support. - choices: ['line', 'block', 'config'] - diff_match: - description: - - Instructs the module on the way to perform the matching of - the set of commands against the current device config. If C(diff_match) - is set to I(line), commands are matched line by line. If C(diff_match) - is set to I(strict), command lines are matched with respect to position. - If C(diff_match) is set to I(exact), command lines must be an equal match. - Finally, if C(diff_match) is set to I(none), the module will not attempt - to compare the source configuration with the running configuration on the - remote device. Note that this parameter will be ignored if the platform - has onbox diff support. - choices: ['line', 'strict', 'exact', 'none'] - diff_ignore_lines: - description: - - Use this argument to specify one or more lines that should be - ignored during the diff. This is used for lines in the configuration - that are automatically updated by the system. This argument takes - a list of regular expressions or exact line matches. - Note that this parameter will be ignored if the platform has onbox - diff support. - backup_options: - description: - - This is a dict object containing configurable options related to backup file path. - The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set - to I(no) this option will be silently ignored. - suboptions: - filename: - description: - - The filename to be used to store the backup configuration. If the filename - is not given it will be generated based on the hostname, current time and date - in format defined by _config.@ - dir_path: - description: - - This option provides the path ending with directory name in which the backup - configuration file will be stored. If the directory does not exist it will be first - created and the filename is either the value of C(filename) or default filename - as described in C(filename) options description. If the path value is not given - in that case a I(backup) directory will be created in the current working directory - and backup configuration will be copied in C(filename) within I(backup) directory. - type: path - type: dict - version_added: "2.8" -""" - -EXAMPLES = """ -- name: configure device with config - cli_config: - config: "{{ lookup('template', 'basic/config.j2') }}" - -- name: multiline config - cli_config: - config: | - hostname foo - feature nxapi - -- name: configure device with config with defaults enabled - cli_config: - config: "{{ lookup('template', 'basic/config.j2') }}" - defaults: yes - -- name: Use diff_match - cli_config: - config: "{{ lookup('file', 'interface_config') }}" - diff_match: none - -- name: nxos replace config - cli_config: - replace: 'bootflash:nxoscfg' - -- name: junos replace config - cli_config: - replace: '/var/home/ansible/junos01.cfg' - -- name: commit with comment - cli_config: - config: set system host-name foo - commit_comment: this is a test - -- name: configurable backup path - cli_config: - config: "{{ lookup('template', 'basic/config.j2') }}" - backup: yes - backup_options: - filename: backup.cfg - dir_path: /home/user -""" - -RETURN = """ -commands: - description: The set of commands that will be pushed to the remote device - returned: always - type: list - sample: ['interface Loopback999', 'no shutdown'] -backup_path: - description: The full path to the backup file - returned: when backup is yes - type: str - sample: /playbooks/ansible/backup/hostname_config.2016-07-16@22:28:34 -""" - -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import Connection -from ansible.module_utils._text import to_text - - -def validate_args(module, device_operations): - """validate param if it is supported on the platform - """ - feature_list = [ - 'replace', 'rollback', 'commit_comment', 'defaults', 'multiline_delimiter', - 'diff_replace', 'diff_match', 'diff_ignore_lines', - ] - - for feature in feature_list: - if module.params[feature]: - supports_feature = device_operations.get('supports_%s' % feature) - if supports_feature is None: - module.fail_json( - "This platform does not specify whether %s is supported or not. " - "Please report an issue against this platform's cliconf plugin." % feature - ) - elif not supports_feature: - module.fail_json(msg='Option %s is not supported on this platform' % feature) - - -def run(module, device_operations, connection, candidate, running, rollback_id): - result = {} - resp = {} - config_diff = [] - banner_diff = {} - - replace = module.params['replace'] - commit_comment = module.params['commit_comment'] - multiline_delimiter = module.params['multiline_delimiter'] - diff_replace = module.params['diff_replace'] - diff_match = module.params['diff_match'] - diff_ignore_lines = module.params['diff_ignore_lines'] - - commit = not module.check_mode - - if replace in ('yes', 'true', 'True'): - replace = True - elif replace in ('no', 'false', 'False'): - replace = False - - if replace is not None and replace not in [True, False] and candidate is not None: - module.fail_json(msg="Replace value '%s' is a configuration file path already" - " present on the device. Hence 'replace' and 'config' options" - " are mutually exclusive" % replace) - - if rollback_id is not None: - resp = connection.rollback(rollback_id, commit) - if 'diff' in resp: - result['changed'] = True - - elif device_operations.get('supports_onbox_diff'): - if diff_replace: - module.warn('diff_replace is ignored as the device supports onbox diff') - if diff_match: - module.warn('diff_mattch is ignored as the device supports onbox diff') - if diff_ignore_lines: - module.warn('diff_ignore_lines is ignored as the device supports onbox diff') - - if candidate and not isinstance(candidate, list): - candidate = candidate.strip('\n').splitlines() - - kwargs = {'candidate': candidate, 'commit': commit, 'replace': replace, - 'comment': commit_comment} - resp = connection.edit_config(**kwargs) - - if 'diff' in resp: - result['changed'] = True - - elif device_operations.get('supports_generate_diff'): - kwargs = {'candidate': candidate, 'running': running} - if diff_match: - kwargs.update({'diff_match': diff_match}) - if diff_replace: - kwargs.update({'diff_replace': diff_replace}) - if diff_ignore_lines: - kwargs.update({'diff_ignore_lines': diff_ignore_lines}) - - diff_response = connection.get_diff(**kwargs) - - config_diff = diff_response.get('config_diff') - banner_diff = diff_response.get('banner_diff') - - if config_diff: - if isinstance(config_diff, list): - candidate = config_diff - else: - candidate = config_diff.splitlines() - - kwargs = { - 'candidate': candidate, - 'commit': commit, - 'replace': replace, - 'comment': commit_comment - } - if commit: - connection.edit_config(**kwargs) - result['changed'] = True - result['commands'] = config_diff.split('\n') - - if banner_diff: - candidate = json.dumps(banner_diff) - - kwargs = {'candidate': candidate, 'commit': commit} - if multiline_delimiter: - kwargs.update({'multiline_delimiter': multiline_delimiter}) - if commit: - connection.edit_banner(**kwargs) - result['changed'] = True - - if module._diff: - if 'diff' in resp: - result['diff'] = {'prepared': resp['diff']} - else: - diff = '' - if config_diff: - if isinstance(config_diff, list): - diff += '\n'.join(config_diff) - else: - diff += config_diff - if banner_diff: - diff += json.dumps(banner_diff) - result['diff'] = {'prepared': diff} - - return result - - -def main(): - """main entry point for execution - """ - backup_spec = dict( - filename=dict(), - dir_path=dict(type='path') - ) - argument_spec = dict( - backup=dict(default=False, type='bool'), - backup_options=dict(type='dict', options=backup_spec), - config=dict(type='str'), - commit=dict(type='bool'), - replace=dict(type='str'), - rollback=dict(type='int'), - commit_comment=dict(type='str'), - defaults=dict(default=False, type='bool'), - multiline_delimiter=dict(type='str'), - diff_replace=dict(choices=['line', 'block', 'config']), - diff_match=dict(choices=['line', 'strict', 'exact', 'none']), - diff_ignore_lines=dict(type='list') - ) - - mutually_exclusive = [('config', 'rollback')] - required_one_of = [['backup', 'config', 'rollback']] - - module = AnsibleModule(argument_spec=argument_spec, - mutually_exclusive=mutually_exclusive, - required_one_of=required_one_of, - supports_check_mode=True) - - result = {'changed': False} - - connection = Connection(module._socket_path) - capabilities = module.from_json(connection.get_capabilities()) - - if capabilities: - device_operations = capabilities.get('device_operations', dict()) - validate_args(module, device_operations) - else: - device_operations = dict() - - if module.params['defaults']: - if 'get_default_flag' in capabilities.get('rpc'): - flags = connection.get_default_flag() - else: - flags = 'all' - else: - flags = [] - - candidate = module.params['config'] - candidate = to_text(candidate, errors='surrogate_then_replace') if candidate else None - running = connection.get_config(flags=flags) - rollback_id = module.params['rollback'] - - if module.params['backup']: - result['__backup__'] = running - - if candidate or rollback_id or module.params['replace']: - try: - result.update(run(module, device_operations, connection, candidate, running, rollback_id)) - except Exception as exc: - module.fail_json(msg=to_text(exc)) - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/files/net_get.py b/lib/ansible/modules/network/files/net_get.py deleted file mode 100644 index 74a5285f4b6..00000000000 --- a/lib/ansible/modules/network/files/net_get.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2018, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_get -version_added: "2.6" -author: "Deepak Agrawal (@dagrawal)" -short_description: Copy a file from a network device to Ansible Controller -description: - - This module provides functionality to copy file from network device to - ansible controller. -extends_documentation_fragment: network_agnostic -options: - src: - description: - - Specifies the source file. The path to the source file can either be - the full path on the network device or a relative path as per path - supported by destination network device. - required: true - protocol: - description: - - Protocol used to transfer file. - default: scp - choices: ['scp', 'sftp'] - dest: - description: - - Specifies the destination file. The path to the destination file can - either be the full path on the Ansible control host or a relative - path from the playbook or role root directory. - default: - - Same filename as specified in I(src). The path will be playbook root - or role root directory if playbook is part of a role. - -requirements: - - "scp" - -notes: - - Some devices need specific configurations to be enabled before scp can work - These configuration should be pre-configured before using this module - e.g ios - C(ip scp server enable). - - User privilege to do scp on network device should be pre-configured - e.g. ios - need user privilege 15 by default for allowing scp. - - Default destination of source file. -""" - -EXAMPLES = """ -- name: copy file from the network device to Ansible controller - net_get: - src: running_cfg_ios1.txt - -- name: copy file from ios to common location at /tmp - net_get: - src: running_cfg_sw1.txt - dest : /tmp/ios1.txt -""" - -RETURN = """ -""" diff --git a/lib/ansible/modules/network/files/net_put.py b/lib/ansible/modules/network/files/net_put.py deleted file mode 100644 index 5c52fcf60b0..00000000000 --- a/lib/ansible/modules/network/files/net_put.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2018, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_put -version_added: "2.6" -author: "Deepak Agrawal (@dagrawal)" -short_description: Copy a file from Ansible Controller to a network device -description: - - This module provides functionality to copy file from Ansible controller to - network devices. -extends_documentation_fragment: network_agnostic -options: - src: - description: - - Specifies the source file. The path to the source file can either be - the full path on the Ansible control host or a relative path from the - playbook or role root directory. - required: true - protocol: - description: - - Protocol used to transfer file. - default: scp - choices: ['scp', 'sftp'] - dest: - description: - - Specifies the destination file. The path to destination file can - either be the full path or relative path as supported by network_os. - default: - - Filename from src and at default directory of user shell on - network_os. - required: no - mode: - description: - - Set the file transfer mode. If mode is set to I(text) then I(src) - file will go through Jinja2 template engine to replace any vars if - present in the src file. If mode is set to I(binary) then file will be - copied as it is to destination device. - default: binary - choices: ['binary', 'text'] - version_added: "2.7" - -requirements: - - "scp" - -notes: - - Some devices need specific configurations to be enabled before scp can work - These configuration should be pre-configured before using this module - e.g ios - C(ip scp server enable). - - User privilege to do scp on network device should be pre-configured - e.g. ios - need user privilege 15 by default for allowing scp. - - Default destination of source file. -""" - -EXAMPLES = """ -- name: copy file from ansible controller to a network device - net_put: - src: running_cfg_ios1.txt - -- name: copy file at root dir of flash in slot 3 of sw1(ios) - net_put: - src: running_cfg_sw1.txt - protocol: sftp - dest : flash3:/running_cfg_sw1.txt -""" - -RETURN = """ -""" diff --git a/lib/ansible/modules/network/interface/_net_interface.py b/lib/ansible/modules/network/interface/_net_interface.py deleted file mode 100644 index 6ce3e3cd596..00000000000 --- a/lib/ansible/modules/network/interface/_net_interface.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_interface -version_added: "2.4" -author: "Ganesh Nalawade (@ganeshrn)" -short_description: Manage Interface on network devices -description: - - This module provides declarative management of Interfaces - on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_interfaces" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - name: - description: - - Name of the Interface. - required: true - description: - description: - - Description of Interface. - enabled: - description: - - Configure interface link status. - speed: - description: - - Interface link speed. - mtu: - description: - - Maximum size of transmit packet. - duplex: - description: - - Interface link status - default: auto - choices: ['full', 'half', 'auto'] - tx_rate: - description: - - Transmit rate in bits per second (bps). - - This is state check parameter only. - - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) - rx_rate: - description: - - Receiver rate in bits per second (bps). - - This is state check parameter only. - - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) - delay: - description: - - Time in seconds to wait before checking for the operational state on remote - device. This wait is applicable for operational state argument which are - I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). - default: 10 - aggregate: - description: List of Interfaces definitions. - purge: - description: - - Purge Interfaces not defined in the aggregate parameter. - This applies only for logical interface. - default: no - state: - description: - - State of the Interface configuration, C(up) indicates present and - operationally up and C(down) indicates present and operationally C(down) - default: present - choices: ['present', 'absent', 'up', 'down'] -""" - -EXAMPLES = """ -- name: configure interface - net_interface: - name: ge-0/0/1 - description: test-interface - -- name: remove interface - net_interface: - name: ge-0/0/1 - state: absent - -- name: make interface up - net_interface: - name: ge-0/0/1 - description: test-interface - enabled: True - -- name: make interface down - net_interface: - name: ge-0/0/1 - description: test-interface - enabled: False - -- name: Create interface using aggregate - net_interface: - aggregate: - - { name: ge-0/0/1, description: test-interface-1 } - - { name: ge-0/0/2, description: test-interface-2 } - speed: 1g - duplex: full - mtu: 512 - -- name: Delete interface using aggregate - net_interface: - aggregate: - - { name: ge-0/0/1 } - - { name: ge-0/0/2 } - state: absent - -- name: Check intent arguments - net_interface: - name: fxp0 - state: up - tx_rate: ge(0) - rx_rate: le(0) - -- name: Config + intent - net_interface: - name: fxp0 - enabled: False - state: down -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device. - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - interface 20 - - name test-interface -""" diff --git a/lib/ansible/modules/network/interface/_net_linkagg.py b/lib/ansible/modules/network/interface/_net_linkagg.py deleted file mode 100644 index ceeaec66759..00000000000 --- a/lib/ansible/modules/network/interface/_net_linkagg.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_linkagg -version_added: "2.4" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Manage link aggregation groups on network devices -description: - - This module provides declarative management of link aggregation groups - on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_lag_interfaces" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - name: - description: - - Name of the link aggregation group. - required: true - mode: - description: - - Mode of the link aggregation group. A value of C(on) will enable LACP. - C(active) configures the link to actively information about the state of the link, - or it can be configured in C(passive) mode ie. send link state information only when - received them from another link. - default: on - choices: ['on', 'active', 'passive'] - members: - description: - - List of members interfaces of the link aggregation group. The value can be - single interface or list of interfaces. - required: true - min_links: - description: - - Minimum members that should be up - before bringing up the link aggregation group. - aggregate: - description: List of link aggregation definitions. - purge: - description: - - Purge link aggregation groups not defined in the I(aggregate) parameter. - default: no - state: - description: - - State of the link aggregation group. - default: present - choices: ['present', 'absent', 'up', 'down'] -""" - -EXAMPLES = """ -- name: configure link aggregation group - net_linkagg: - name: bond0 - members: - - eth0 - - eth1 - -- name: remove configuration - net_linkagg: - name: bond0 - state: absent - -- name: Create aggregate of linkagg definitions - net_linkagg: - aggregate: - - { name: bond0, members: [eth1] } - - { name: bond1, members: [eth2] } - -- name: Remove aggregate of linkagg definitions - net_linkagg: - aggregate: - - name: bond0 - - name: bond1 - state: absent -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - set interfaces bonding bond0 - - set interfaces ethernet eth0 bond-group 'bond0' - - set interfaces ethernet eth1 bond-group 'bond0' -""" diff --git a/lib/ansible/modules/network/interface/_net_lldp_interface.py b/lib/ansible/modules/network/interface/_net_lldp_interface.py deleted file mode 100644 index c6b48a9b785..00000000000 --- a/lib/ansible/modules/network/interface/_net_lldp_interface.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - -DOCUMENTATION = """ ---- -module: net_lldp_interface -version_added: "2.4" -author: "Ganesh Nalawade (@ganeshrn)" -short_description: Manage LLDP interfaces configuration on network devices -description: - - This module provides declarative management of LLDP interfaces - configuration on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_lldp_interfaces" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - name: - description: - - Name of the interface LLDP should be configured on. - aggregate: - description: List of interfaces LLDP should be configured on. - purge: - description: - - Purge interfaces not defined in the aggregate parameter. - default: no - state: - description: - - State of the LLDP configuration. - default: present - choices: ['present', 'absent', 'enabled', 'disabled'] -""" - -EXAMPLES = """ -- name: Configure LLDP on specific interfaces - net_lldp_interface: - name: eth1 - state: present - -- name: Disable LLDP on specific interfaces - net_lldp_interface: - name: eth1 - state: disabled - -- name: Enable LLDP on specific interfaces - net_lldp_interface: - name: eth1 - state: enabled - -- name: Delete LLDP on specific interfaces - net_lldp_interface: - name: eth1 - state: absent - -- name: Create aggregate of LLDP interface configurations - net_lldp_interface: - aggregate: - - { name: eth1 } - - { name: eth2 } - state: present - -- name: Delete aggregate of LLDP interface configurations - net_lldp_interface: - aggregate: - - { name: eth1 } - - { name: eth2 } - state: absent -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - set service lldp eth1 disable -""" diff --git a/lib/ansible/modules/network/layer2/_net_l2_interface.py b/lib/ansible/modules/network/layer2/_net_l2_interface.py deleted file mode 100644 index 0b8e5e6101f..00000000000 --- a/lib/ansible/modules/network/layer2/_net_l2_interface.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - -DOCUMENTATION = """ ---- -module: net_l2_interface -version_added: "2.4" -author: "Ganesh Nalawade (@ganeshrn)" -short_description: Manage Layer-2 interface on network devices -description: - - This module provides declarative management of Layer-2 interface - on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_l2_interfaces" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - name: - description: - - Name of the interface excluding any logical unit number. - aggregate: - description: - - List of Layer-2 interface definitions. - mode: - description: - - Mode in which interface needs to be configured. - default: access - choices: ['access', 'trunk'] - access_vlan: - description: - - Configure given VLAN in access port. - trunk_vlans: - description: - - List of VLANs to be configured in trunk port. - native_vlan: - description: - - Native VLAN to be configured in trunk port. - trunk_allowed_vlans: - description: - - List of allowed VLAN's in a given trunk port. - state: - description: - - State of the Layer-2 Interface configuration. - default: present - choices: ['present', 'absent',] -""" - -EXAMPLES = """ -- name: configure Layer-2 interface - net_l2_interface: - name: gigabitethernet0/0/1 - mode: access - access_vlan: 30 - -- name: remove Layer-2 interface configuration - net_l2_interface: - name: gigabitethernet0/0/1 - state: absent -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - interface gigabitethernet0/0/1 - - switchport mode access - - switchport access vlan 30 -""" diff --git a/lib/ansible/modules/network/layer2/_net_vlan.py b/lib/ansible/modules/network/layer2/_net_vlan.py deleted file mode 100644 index ec32e6a8315..00000000000 --- a/lib/ansible/modules/network/layer2/_net_vlan.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_vlan -version_added: "2.4" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Manage VLANs on network devices -description: - - This module provides declarative management of VLANs - on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_vlans" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - name: - description: - - Name of the VLAN. - vlan_id: - description: - - ID of the VLAN. - interfaces: - description: - - List of interfaces the VLAN should be configured on. - aggregate: - description: List of VLANs definitions. - purge: - description: - - Purge VLANs not defined in the I(aggregate) parameter. - default: no - state: - description: - - State of the VLAN configuration. - default: present - choices: ['present', 'absent', 'active', 'suspend'] -""" - -EXAMPLES = """ -- name: configure VLAN ID and name - net_vlan: - vlan_id: 20 - name: test-vlan - -- name: remove configuration - net_vlan: - state: absent - -- name: configure VLAN state - net_vlan: - vlan_id: - state: suspend - -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - vlan 20 - - name test-vlan -""" diff --git a/lib/ansible/modules/network/layer3/_net_l3_interface.py b/lib/ansible/modules/network/layer3/_net_l3_interface.py deleted file mode 100644 index 8c13e8a7f21..00000000000 --- a/lib/ansible/modules/network/layer3/_net_l3_interface.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_l3_interface -version_added: "2.4" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Manage L3 interfaces on network devices -description: - - This module provides declarative management of L3 interfaces - on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_l3_interfaces" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - name: - description: - - Name of the L3 interface. - ipv4: - description: - - IPv4 of the L3 interface. - ipv6: - description: - - IPv6 of the L3 interface. - aggregate: - description: List of L3 interfaces definitions - purge: - description: - - Purge L3 interfaces not defined in the I(aggregate) parameter. - default: no - state: - description: - - State of the L3 interface configuration. - default: present - choices: ['present', 'absent'] -""" - -EXAMPLES = """ -- name: Set eth0 IPv4 address - net_l3_interface: - name: eth0 - ipv4: 192.168.0.1/24 - -- name: Remove eth0 IPv4 address - net_l3_interface: - name: eth0 - state: absent - -- name: Set IP addresses on aggregate - net_l3_interface: - aggregate: - - { name: eth1, ipv4: 192.168.2.10/24 } - - { name: eth2, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } - -- name: Remove IP addresses on aggregate - net_l3_interface: - aggregate: - - { name: eth1, ipv4: 192.168.2.10/24 } - - { name: eth2, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" } - state: absent -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - set interfaces ethernet eth0 address '192.168.0.1/24' -""" diff --git a/lib/ansible/modules/network/layer3/_net_vrf.py b/lib/ansible/modules/network/layer3/_net_vrf.py deleted file mode 100644 index 240f876f122..00000000000 --- a/lib/ansible/modules/network/layer3/_net_vrf.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_vrf -version_added: "2.4" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Manage VRFs on network devices -description: - - This module provides declarative management of VRFs - on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_vrf" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - name: - description: - - Name of the VRF. - interfaces: - description: - - List of interfaces the VRF should be configured on. - aggregate: - description: List of VRFs definitions - purge: - description: - - Purge VRFs not defined in the I(aggregate) parameter. - default: no - state: - description: - - State of the VRF configuration. - default: present - choices: ['present', 'absent'] -""" - -EXAMPLES = """ -- name: Create VRF named MANAGEMENT - net_vrf: - name: MANAGEMENT - -- name: remove VRF named MANAGEMENT - net_vrf: - name: MANAGEMENT - state: absent - -- name: Create aggregate of VRFs with purge - net_vrf: - aggregate: - - { name: test4, rd: "1:204" } - - { name: test5, rd: "1:205" } - state: present - purge: yes - -- name: Delete aggregate of VRFs - net_vrf: - aggregate: - - name: test2 - - name: test3 - - name: test4 - - name: test5 - state: absent -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - vrf definition MANAGEMENT -""" diff --git a/lib/ansible/modules/network/netconf/netconf_config.py b/lib/ansible/modules/network/netconf/netconf_config.py deleted file mode 100644 index ddadfd68bf1..00000000000 --- a/lib/ansible/modules/network/netconf/netconf_config.py +++ /dev/null @@ -1,479 +0,0 @@ -#!/usr/bin/python - -# (c) 2016, Leandro Lisboa Penz -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = ''' ---- -module: netconf_config -version_added: "2.2" -author: "Leandro Lisboa Penz (@lpenz)" -short_description: netconf device configuration -description: - - Netconf is a network management protocol developed and standardized by - the IETF. It is documented in RFC 6241. - - This module allows the user to send a configuration XML file to a netconf - device, and detects if there was a configuration change. -extends_documentation_fragment: - - netconf - - network_agnostic -options: - content: - description: - - The configuration data as defined by the device's data models, the value can be either in - xml string format or text format. The format of the configuration should be supported by remote - Netconf server - aliases: ['xml'] - target: - description: - Name of the configuration datastore to be edited. - - auto, uses candidate and fallback to running - - candidate, edit datastore and then commit - - running, edit datastore directly - default: auto - version_added: "2.4" - aliases: ['datastore'] - source_datastore: - description: - - Name of the configuration datastore to use as the source to copy the configuration - to the datastore mentioned by C(target) option. The values can be either I(running), I(candidate), - I(startup) or a remote URL - version_added: "2.7" - aliases: ['source'] - format: - description: - - The format of the configuration provided as value of C(content). Accepted values are I(xml) and I(text) and - the given configuration format should be supported by remote Netconf server. - default: xml - choices: ['xml', 'text'] - version_added: "2.7" - lock: - description: - - Instructs the module to explicitly lock the datastore specified as C(target). By setting the option - value I(always) is will explicitly lock the datastore mentioned in C(target) option. It the value - is I(never) it will not lock the C(target) datastore. The value I(if-supported) lock the C(target) - datastore only if it is supported by the remote Netconf server. - default: always - choices: ['never', 'always', 'if-supported'] - version_added: "2.7" - default_operation: - description: - - The default operation for rpc, valid values are I(merge), I(replace) and I(none). - If the default value is merge, the configuration data in the C(content) option is merged at the - corresponding level in the C(target) datastore. If the value is replace the data in the C(content) - option completely replaces the configuration in the C(target) datastore. If the value is none the C(target) - datastore is unaffected by the configuration in the config option, unless and until the incoming configuration - data uses the C(operation) operation to request a different operation. - choices: ['merge', 'replace', 'none'] - version_added: "2.7" - confirm: - description: - - This argument will configure a timeout value for the commit to be confirmed before it is automatically - rolled back. If the C(confirm_commit) argument is set to False, this argument is silently ignored. If the - value of this argument is set to 0, the commit is confirmed immediately. The remote host MUST - support :candidate and :confirmed-commit capability for this option to . - default: 0 - version_added: "2.7" - confirm_commit: - description: - - This argument will execute commit operation on remote device. It can be used to confirm a previous commit. - type: bool - default: 'no' - version_added: "2.7" - error_option: - description: - - This option controls the netconf server action after an error occurs while editing the configuration. - - If I(error_option=stop-on-error), abort the config edit on first error. - - If I(error_option=continue-on-error), continue to process configuration data on error. - The error is recorded and negative response is generated if any errors occur. - - If I(error_option=rollback-on-error), rollback to the original configuration if - any error occurs. - This requires the remote Netconf server to support the I(error_option=rollback-on-error) capability. - default: stop-on-error - choices: ['stop-on-error', 'continue-on-error', 'rollback-on-error'] - version_added: "2.7" - save: - description: - - The C(save) argument instructs the module to save the configuration in C(target) datastore to the - startup-config if changed and if :startup capability is supported by Netconf server. - default: false - version_added: "2.4" - type: bool - backup: - description: - - This argument will cause the module to create a full backup of - the current C(running-config) from the remote device before any - changes are made. If the C(backup_options) value is not given, - the backup file is written to the C(backup) folder in the playbook - root directory or role root directory, if playbook is part of an - ansible role. If the directory does not exist, it is created. - type: bool - default: 'no' - version_added: "2.7" - delete: - description: - - It instructs the module to delete the configuration from value mentioned in C(target) datastore. - type: bool - default: 'no' - version_added: "2.7" - commit: - description: - - This boolean flag controls if the configuration changes should be committed or not after editing the - candidate datastore. This option is supported only if remote Netconf server supports :candidate - capability. If the value is set to I(False) commit won't be issued after edit-config operation - and user needs to handle commit or discard-changes explicitly. - type: bool - default: True - version_added: "2.7" - validate: - description: - - This boolean flag if set validates the content of datastore given in C(target) option. - For this option to work remote Netconf server should support :validate capability. - type: bool - default: False - version_added: "2.7" - src: - description: - - Specifies the source path to the xml file that contains the configuration or configuration template - to load. The path to the source file can either be the full path on the Ansible control host or - a relative path from the playbook or role root directory. This argument is mutually exclusive with I(xml). - version_added: "2.4" - backup_options: - description: - - This is a dict object containing configurable options related to backup file path. - The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set - to I(no) this option will be silently ignored. - suboptions: - filename: - description: - - The filename to be used to store the backup configuration. If the filename - is not given it will be generated based on the hostname, current time and date - in format defined by _config.@ - dir_path: - description: - - This option provides the path ending with directory name in which the backup - configuration file will be stored. If the directory does not exist it will be first - created and the filename is either the value of C(filename) or default filename - as described in C(filename) options description. If the path value is not given - in that case a I(backup) directory will be created in the current working directory - and backup configuration will be copied in C(filename) within I(backup) directory. - type: path - type: dict - version_added: "2.8" - get_filter: - description: - - This argument specifies the XML string which acts as a filter to restrict the portions of - the data retrieved from the remote device when comparing the before and after state of the - device following calls to edit_config. When not specified, the entire configuration or - state data is returned for comparison depending on the value of C(source) option. The C(get_filter) - value can be either XML string or XPath, if the filter is in XPath format the NETCONF server - running on remote host should support xpath capability else it will result in an error. - version_added: "2.10" -requirements: - - "ncclient" -notes: - - This module requires the netconf system service be enabled on - the remote device being managed. - - This module supports devices with and without the candidate and - confirmed-commit capabilities. It will always use the safer feature. - - This module supports the use of connection=netconf -''' - -EXAMPLES = ''' -- name: use lookup filter to provide xml configuration - netconf_config: - content: "{{ lookup('file', './config.xml') }}" - -- name: set ntp server in the device - netconf_config: - content: | - - - - true - - ntp1 -
127.0.0.1
-
-
-
-
- -- name: wipe ntp configuration - netconf_config: - content: | - - - - false - - ntp1 - - - - - -- name: configure interface while providing different private key file path (for connection=netconf) - netconf_config: - backup: yes - register: backup_junos_location - vars: - ansible_private_key_file: /home/admin/.ssh/newprivatekeyfile - -- name: configurable backup path - netconf_config: - backup: yes - backup_options: - filename: backup.cfg - dir_path: /home/user -''' - -RETURN = ''' -server_capabilities: - description: list of capabilities of the server - returned: success - type: list - sample: ['urn:ietf:params:netconf:base:1.1','urn:ietf:params:netconf:capability:confirmed-commit:1.0','urn:ietf:params:netconf:capability:candidate:1.0'] -backup_path: - description: The full path to the backup file - returned: when backup is yes - type: str - sample: /playbooks/ansible/backup/config.2016-07-16@22:28:34 -diff: - description: If --diff option in enabled while running, the before and after configuration change are - returned as part of before and after key. - returned: when diff is enabled - type: dict - sample: - "after": "\n\n\n17.3R1.10...<--snip-->" - "before": "\n\n\n 17.3R1.10...<--snip-->" -''' - -from ansible.module_utils._text import to_text -from ansible.module_utils.basic import AnsibleModule, env_fallback -from ansible.module_utils.connection import Connection, ConnectionError -from ansible.module_utils.network.netconf.netconf import get_capabilities, get_config, sanitize_xml - -import sys -try: - from lxml.etree import tostring, fromstring, XMLSyntaxError -except ImportError: - from xml.etree.ElementTree import tostring, fromstring - if sys.version_info < (2, 7): - from xml.parsers.expat import ExpatError as XMLSyntaxError - else: - from xml.etree.ElementTree import ParseError as XMLSyntaxError - - -def get_filter_type(filter): - if not filter: - return None - else: - try: - fromstring(filter) - return 'subtree' - except XMLSyntaxError: - return 'xpath' - - -def main(): - """ main entry point for module execution - """ - backup_spec = dict( - filename=dict(), - dir_path=dict(type='path') - ) - argument_spec = dict( - content=dict(aliases=['xml']), - target=dict(choices=['auto', 'candidate', 'running'], default='auto', aliases=['datastore']), - source_datastore=dict(aliases=['source']), - format=dict(choices=['xml', 'text'], default='xml'), - lock=dict(choices=['never', 'always', 'if-supported'], default='always'), - default_operation=dict(choices=['merge', 'replace', 'none']), - confirm=dict(type='int', default=0), - confirm_commit=dict(type='bool', default=False), - error_option=dict(choices=['stop-on-error', 'continue-on-error', 'rollback-on-error'], default='stop-on-error'), - backup=dict(type='bool', default=False), - backup_options=dict(type='dict', options=backup_spec), - save=dict(type='bool', default=False), - delete=dict(type='bool', default=False), - commit=dict(type='bool', default=True), - validate=dict(type='bool', default=False), - get_filter=dict(), - ) - - # deprecated options - netconf_top_spec = { - 'src': dict(type='path', removed_in_version=2.11), - 'host': dict(removed_in_version=2.11), - 'port': dict(removed_in_version=2.11, type='int', default=830), - 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']), removed_in_version=2.11, no_log=True), - 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), removed_in_version=2.11, no_log=True), - 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), removed_in_version=2.11, type='path'), - 'hostkey_verify': dict(removed_in_version=2.11, type='bool', default=True), - 'look_for_keys': dict(removed_in_version=2.11, type='bool', default=True), - 'timeout': dict(removed_in_version=2.11, type='int', default=10), - } - argument_spec.update(netconf_top_spec) - - mutually_exclusive = [('content', 'src', 'source', 'delete', 'confirm_commit')] - required_one_of = [('content', 'src', 'source', 'delete', 'confirm_commit')] - - module = AnsibleModule(argument_spec=argument_spec, - required_one_of=required_one_of, - mutually_exclusive=mutually_exclusive, - supports_check_mode=True) - - if module.params['src']: - module.deprecate(msg="argument 'src' has been deprecated. Use file lookup plugin instead to read file contents.", - version="2.11") - - config = module.params['content'] or module.params['src'] - target = module.params['target'] - lock = module.params['lock'] - source = module.params['source_datastore'] - delete = module.params['delete'] - confirm_commit = module.params['confirm_commit'] - confirm = module.params['confirm'] - validate = module.params['validate'] - save = module.params['save'] - filter = module.params['get_filter'] - filter_type = get_filter_type(filter) - - conn = Connection(module._socket_path) - capabilities = get_capabilities(module) - operations = capabilities['device_operations'] - - supports_commit = operations.get('supports_commit', False) - supports_writable_running = operations.get('supports_writable_running', False) - supports_startup = operations.get('supports_startup', False) - - # identify target datastore - if target == 'candidate' and not supports_commit: - module.fail_json(msg=':candidate is not supported by this netconf server') - elif target == 'running' and not supports_writable_running: - module.fail_json(msg=':writable-running is not supported by this netconf server') - elif target == 'auto': - if supports_commit: - target = 'candidate' - elif supports_writable_running: - target = 'running' - else: - module.fail_json(msg='neither :candidate nor :writable-running are supported by this netconf server') - - # Netconf server capability validation against input options - if save and not supports_startup: - module.fail_json(msg='cannot copy <%s/> to , while :startup is not supported' % target) - - if confirm_commit and not operations.get('supports_confirm_commit', False): - module.fail_json(msg='confirm commit is not supported by Netconf server') - - if (confirm > 0) and not operations.get('supports_confirm_commit', False): - module.fail_json(msg='confirm commit is not supported by this netconf server, given confirm timeout: %d' % confirm) - - if validate and not operations.get('supports_validate', False): - module.fail_json(msg='validate is not supported by this netconf server') - - if filter_type == 'xpath' and not operations.get('supports_xpath', False): - module.fail_json(msg="filter value '%s' of type xpath is not supported on this device" % filter) - - filter_spec = (filter_type, filter) if filter_type else None - - if lock == 'never': - execute_lock = False - elif target in operations.get('lock_datastore', []): - # lock is requested (always/if-support) and supported => lets do it - execute_lock = True - else: - # lock is requested (always/if-supported) but not supported => issue warning - module.warn("lock operation on '%s' source is not supported on this device" % target) - execute_lock = (lock == 'always') - - result = {'changed': False, 'server_capabilities': capabilities.get('server_capabilities', [])} - before = None - after = None - locked = False - try: - if module.params['backup']: - response = get_config(module, target, filter_spec, lock=execute_lock) - before = to_text(tostring(response), errors='surrogate_then_replace').strip() - result['__backup__'] = before.strip() - if validate: - conn.validate(target) - if source: - if not module.check_mode: - conn.copy(source, target) - result['changed'] = True - elif delete: - if not module.check_mode: - conn.delete(target) - result['changed'] = True - elif confirm_commit: - if not module.check_mode: - conn.commit() - result['changed'] = True - elif config: - if module.check_mode and not supports_commit: - module.warn("check mode not supported as Netconf server doesn't support candidate capability") - result['changed'] = True - module.exit_json(**result) - - if execute_lock: - conn.lock(target=target) - locked = True - if before is None: - before = to_text(conn.get_config(source=target, filter=filter_spec), errors='surrogate_then_replace').strip() - - kwargs = { - 'config': config, - 'target': target, - 'default_operation': module.params['default_operation'], - 'error_option': module.params['error_option'], - 'format': module.params['format'], - } - - conn.edit_config(**kwargs) - - if supports_commit and module.params['commit']: - after = to_text(conn.get_config(source='candidate', filter=filter_spec), errors='surrogate_then_replace').strip() - if not module.check_mode: - confirm_timeout = confirm if confirm > 0 else None - confirmed_commit = True if confirm_timeout else False - conn.commit(confirmed=confirmed_commit, timeout=confirm_timeout) - else: - conn.discard_changes() - - if after is None: - after = to_text(conn.get_config(source='running', filter=filter_spec), errors='surrogate_then_replace').strip() - - sanitized_before = sanitize_xml(before) - sanitized_after = sanitize_xml(after) - if sanitized_before != sanitized_after: - result['changed'] = True - - if result['changed']: - if save and not module.check_mode: - conn.copy_config(target, 'startup') - if module._diff: - result['diff'] = {'before': sanitized_before, 'after': sanitized_after} - - except ConnectionError as e: - module.fail_json(msg=to_text(e, errors='surrogate_then_replace').strip()) - finally: - if locked: - conn.unlock(target=target) - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/netconf/netconf_get.py b/lib/ansible/modules/network/netconf/netconf_get.py deleted file mode 100644 index fcc8533c3b4..00000000000 --- a/lib/ansible/modules/network/netconf/netconf_get.py +++ /dev/null @@ -1,261 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2018, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: netconf_get -version_added: "2.6" -author: - - "Ganesh Nalawade (@ganeshrn)" - - "Sven Wisotzky (@wisotzky)" -short_description: Fetch configuration/state data from NETCONF enabled network devices. -description: - - NETCONF is a network management protocol developed and standardized by - the IETF. It is documented in RFC 6241. - - This module allows the user to fetch configuration and state data from NETCONF - enabled network devices. -extends_documentation_fragment: network_agnostic -options: - source: - description: - - This argument specifies the datastore from which configuration data should be fetched. - Valid values are I(running), I(candidate) and I(startup). If the C(source) value is not - set both configuration and state information are returned in response from running datastore. - choices: ['running', 'candidate', 'startup'] - filter: - description: - - This argument specifies the XML string which acts as a filter to restrict the portions of - the data to be are retrieved from the remote device. If this option is not specified entire - configuration or state data is returned in result depending on the value of C(source) - option. The C(filter) value can be either XML string or XPath, if the filter is in - XPath format the NETCONF server running on remote host should support xpath capability - else it will result in an error. - display: - description: - - Encoding scheme to use when serializing output from the device. The option I(json) will - serialize the output as JSON data. If the option value is I(json) it requires jxmlease - to be installed on control node. The option I(pretty) is similar to received XML response - but is using human readable format (spaces, new lines). The option value I(xml) is similar - to received XML response but removes all XML namespaces. - choices: ['json', 'pretty', 'xml'] - lock: - description: - - Instructs the module to explicitly lock the datastore specified as C(source). If no - I(source) is defined, the I(running) datastore will be locked. By setting the option - value I(always) is will explicitly lock the datastore mentioned in C(source) option. - By setting the option value I(never) it will not lock the C(source) datastore. The - value I(if-supported) allows better interworking with NETCONF servers, which do not - support the (un)lock operation for all supported datastores. - default: never - choices: ['never', 'always', 'if-supported'] -requirements: - - ncclient (>=v0.5.2) - - jxmlease - -notes: - - This module requires the NETCONF system service be enabled on - the remote device being managed. - - This module supports the use of connection=netconf -""" - -EXAMPLES = """ -- name: Get running configuration and state data - netconf_get: - -- name: Get configuration and state data from startup datastore - netconf_get: - source: startup - -- name: Get system configuration data from running datastore state (junos) - netconf_get: - source: running - filter: - -- name: Get configuration and state data in JSON format - netconf_get: - display: json - -- name: get schema list using subtree w/ namespaces - netconf_get: - display: json - filter: - lock: never - -- name: get schema list using xpath - netconf_get: - display: xml - filter: /netconf-state/schemas/schema - -- name: get interface configuration with filter (iosxr) - netconf_get: - display: pretty - filter: - lock: if-supported - -- name: Get system configuration data from running datastore state (junos) - netconf_get: - source: running - filter: - lock: if-supported - -- name: Get complete configuration data from running datastore (SROS) - netconf_get: - source: running - filter: - -- name: Get complete state data (SROS) - netconf_get: - filter: -""" - -RETURN = """ -stdout: - description: The raw XML string containing configuration or state data - received from the underlying ncclient library. - returned: always apart from low-level errors (such as action plugin) - type: str - sample: '...' -stdout_lines: - description: The value of stdout split into a list - returned: always apart from low-level errors (such as action plugin) - type: list - sample: ['...', '...'] -output: - description: Based on the value of display option will return either the set of - transformed XML to JSON format from the RPC response with type dict - or pretty XML string response (human-readable) or response with - namespace removed from XML string. - returned: when the display format is selected as JSON it is returned as dict type, if the - display format is xml or pretty pretty it is returned as a string apart from low-level - errors (such as action plugin). - type: complex - contains: - formatted_output: - - Contains formatted response received from remote host as per the value in display format. -""" -import sys - -try: - from lxml.etree import tostring, fromstring, XMLSyntaxError -except ImportError: - from xml.etree.ElementTree import tostring, fromstring - if sys.version_info < (2, 7): - from xml.parsers.expat import ExpatError as XMLSyntaxError - else: - from xml.etree.ElementTree import ParseError as XMLSyntaxError - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.netconf.netconf import get_capabilities, get_config, get -from ansible.module_utils.network.common.netconf import remove_namespaces -from ansible.module_utils._text import to_text - -try: - import jxmlease - HAS_JXMLEASE = True -except ImportError: - HAS_JXMLEASE = False - - -def get_filter_type(filter): - if not filter: - return None - else: - try: - fromstring(filter) - return 'subtree' - except XMLSyntaxError: - return 'xpath' - - -def main(): - """entry point for module execution - """ - argument_spec = dict( - source=dict(choices=['running', 'candidate', 'startup']), - filter=dict(), - display=dict(choices=['json', 'pretty', 'xml']), - lock=dict(default='never', choices=['never', 'always', 'if-supported']) - ) - - module = AnsibleModule(argument_spec=argument_spec, - supports_check_mode=True) - - capabilities = get_capabilities(module) - operations = capabilities['device_operations'] - - source = module.params['source'] - filter = module.params['filter'] - filter_type = get_filter_type(filter) - lock = module.params['lock'] - display = module.params['display'] - - if source == 'candidate' and not operations.get('supports_commit', False): - module.fail_json(msg='candidate source is not supported on this device') - - if source == 'startup' and not operations.get('supports_startup', False): - module.fail_json(msg='startup source is not supported on this device') - - if filter_type == 'xpath' and not operations.get('supports_xpath', False): - module.fail_json(msg="filter value '%s' of type xpath is not supported on this device" % filter) - - # If source is None, NETCONF operation is issued, reading config/state data - # from the running datastore. The python expression "(source or 'running')" results - # in the value of source (if not None) or the value 'running' (if source is None). - - if lock == 'never': - execute_lock = False - elif (source or 'running') in operations.get('lock_datastore', []): - # lock is requested (always/if-support) and supported => lets do it - execute_lock = True - else: - # lock is requested (always/if-supported) but not supported => issue warning - module.warn("lock operation on '%s' source is not supported on this device" % (source or 'running')) - execute_lock = (lock == 'always') - - if display == 'json' and not HAS_JXMLEASE: - module.fail_json(msg='jxmlease is required to display response in json format' - 'but does not appear to be installed. ' - 'It can be installed using `pip install jxmlease`') - - filter_spec = (filter_type, filter) if filter_type else None - - if source is not None: - response = get_config(module, source, filter_spec, execute_lock) - else: - response = get(module, filter_spec, execute_lock) - - xml_resp = to_text(tostring(response)) - output = None - - if display == 'xml': - output = remove_namespaces(xml_resp) - elif display == 'json': - try: - output = jxmlease.parse(xml_resp) - except Exception: - raise ValueError(xml_resp) - elif display == 'pretty': - output = to_text(tostring(response, pretty_print=True)) - - result = { - 'stdout': xml_resp, - 'output': output - } - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/netconf/netconf_rpc.py b/lib/ansible/modules/network/netconf/netconf_rpc.py deleted file mode 100644 index b84c5adef7a..00000000000 --- a/lib/ansible/modules/network/netconf/netconf_rpc.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2018, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: netconf_rpc -version_added: "2.6" -author: - - "Ganesh Nalawade (@ganeshrn)" - - "Sven Wisotzky (@wisotzky)" -short_description: Execute operations on NETCONF enabled network devices. -description: - - NETCONF is a network management protocol developed and standardized by - the IETF. It is documented in RFC 6241. - - This module allows the user to execute NETCONF RPC requests as defined - by IETF RFC standards as well as proprietary requests. -extends_documentation_fragment: network_agnostic -options: - rpc: - description: - - This argument specifies the request (name of the operation) to be executed on - the remote NETCONF enabled device. - xmlns: - description: - - NETCONF operations not defined in rfc6241 typically require the appropriate - XML namespace to be set. In the case the I(request) option is not already - provided in XML format, the namespace can be defined by the I(xmlns) - option. - content: - description: - - This argument specifies the optional request content (all RPC attributes). - The I(content) value can either be provided as XML formatted string or as - dictionary. - display: - description: - - Encoding scheme to use when serializing output from the device. The option I(json) will - serialize the output as JSON data. If the option value is I(json) it requires jxmlease - to be installed on control node. The option I(pretty) is similar to received XML response - but is using human readable format (spaces, new lines). The option value I(xml) is similar - to received XML response but removes all XML namespaces. - choices: ['json', 'pretty', 'xml'] -requirements: - - ncclient (>=v0.5.2) - - jxmlease - -notes: - - This module requires the NETCONF system service be enabled on the remote device - being managed. - - This module supports the use of connection=netconf - - To execute C(get-config), C(get) or C(edit-config) requests it is recommended - to use the Ansible I(netconf_get) and I(netconf_config) modules. -""" - -EXAMPLES = """ -- name: lock candidate - netconf_rpc: - rpc: lock - content: - target: - candidate: - -- name: unlock candidate - netconf_rpc: - rpc: unlock - xmlns: "urn:ietf:params:xml:ns:netconf:base:1.0" - content: "{'target': {'candidate': None}}" - -- name: discard changes - netconf_rpc: - rpc: discard-changes - -- name: get-schema - netconf_rpc: - rpc: get-schema - xmlns: urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring - content: - identifier: ietf-netconf - version: "2011-06-01" - -- name: copy running to startup - netconf_rpc: - rpc: copy-config - content: - source: - running: - target: - startup: - -- name: get schema list with JSON output - netconf_rpc: - rpc: get - content: | - - - - - - display: json - -- name: get schema using XML request - netconf_rpc: - rpc: "get-schema" - xmlns: "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring" - content: | - ietf-netconf-monitoring - 2010-10-04 - display: json -""" - -RETURN = """ -stdout: - description: The raw XML string containing configuration or state data - received from the underlying ncclient library. - returned: always apart from low-level errors (such as action plugin) - type: str - sample: '...' -stdout_lines: - description: The value of stdout split into a list - returned: always apart from low-level errors (such as action plugin) - type: list - sample: ['...', '...'] -output: - description: Based on the value of display option will return either the set of - transformed XML to JSON format from the RPC response with type dict - or pretty XML string response (human-readable) or response with - namespace removed from XML string. - returned: when the display format is selected as JSON it is returned as dict type, if the - display format is xml or pretty pretty it is returned as a string apart from low-level - errors (such as action plugin). - type: complex - contains: - formatted_output: - - Contains formatted response received from remote host as per the value in display format. -""" - -import ast - -try: - from lxml.etree import tostring -except ImportError: - from xml.etree.ElementTree import tostring - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.netconf.netconf import dispatch -from ansible.module_utils.network.common.netconf import remove_namespaces - -try: - import jxmlease - HAS_JXMLEASE = True -except ImportError: - HAS_JXMLEASE = False - - -def get_xml_request(module, request, xmlns, content): - if content is None: - if xmlns is None: - return '<%s/>' % request - else: - return '<%s xmlns="%s"/>' % (request, xmlns) - - if isinstance(content, str): - content = content.strip() - - if content.startswith('<') and content.endswith('>'): - # assumption content contains already XML payload - if xmlns is None: - return '<%s>%s' % (request, content, request) - else: - return '<%s xmlns="%s">%s' % (request, xmlns, content, request) - - try: - # trying if content contains dict - content = ast.literal_eval(content) - except Exception: - module.fail_json(msg='unsupported content value `%s`' % content) - - if isinstance(content, dict): - if not HAS_JXMLEASE: - module.fail_json(msg='jxmlease is required to convert RPC content to XML ' - 'but does not appear to be installed. ' - 'It can be installed using `pip install jxmlease`') - - payload = jxmlease.XMLDictNode(content).emit_xml(pretty=False, full_document=False) - if xmlns is None: - return '<%s>%s' % (request, payload, request) - else: - return '<%s xmlns="%s">%s' % (request, xmlns, payload, request) - - module.fail_json(msg='unsupported content data-type `%s`' % type(content).__name__) - - -def main(): - """entry point for module execution - """ - argument_spec = dict( - rpc=dict(type="str", required=True), - xmlns=dict(type="str"), - content=dict(), - display=dict(choices=['json', 'pretty', 'xml']) - ) - - module = AnsibleModule(argument_spec=argument_spec, - supports_check_mode=True) - - rpc = module.params['rpc'] - xmlns = module.params['xmlns'] - content = module.params['content'] - display = module.params['display'] - - if rpc is None: - module.fail_json(msg='argument `rpc` must not be None') - - rpc = rpc.strip() - if len(rpc) == 0: - module.fail_json(msg='argument `rpc` must not be empty') - - if rpc in ['close-session']: - # explicit close-session is not allowed, as this would make the next - # NETCONF operation to the same host fail - module.fail_json(msg='unsupported operation `%s`' % rpc) - - if display == 'json' and not HAS_JXMLEASE: - module.fail_json(msg='jxmlease is required to display response in json format' - 'but does not appear to be installed. ' - 'It can be installed using `pip install jxmlease`') - - xml_req = get_xml_request(module, rpc, xmlns, content) - response = dispatch(module, xml_req) - - xml_resp = tostring(response) - output = None - - if display == 'xml': - output = remove_namespaces(xml_resp) - elif display == 'json': - try: - output = jxmlease.parse(xml_resp) - except Exception: - raise ValueError(xml_resp) - elif display == 'pretty': - output = tostring(response, pretty_print=True) - - result = { - 'stdout': xml_resp, - 'output': output - } - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/protocol/_net_lldp.py b/lib/ansible/modules/network/protocol/_net_lldp.py deleted file mode 100644 index b3f3d690843..00000000000 --- a/lib/ansible/modules/network/protocol/_net_lldp.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_lldp -version_added: "2.4" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Manage LLDP service configuration on network devices -description: - - This module provides declarative management of LLDP service configuration - on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_lldp_global" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - state: - description: - - State of the LLDP service configuration. - default: present - choices: ['present', 'absent'] -""" - -EXAMPLES = """ -- name: Enable LLDP service - net_lldp: - state: present - -- name: Disable LLDP service - net_lldp: - state: absent -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - set service lldp -""" diff --git a/lib/ansible/modules/network/restconf/restconf_config.py b/lib/ansible/modules/network/restconf/restconf_config.py deleted file mode 100644 index dcd065caa1e..00000000000 --- a/lib/ansible/modules/network/restconf/restconf_config.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/python -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = ''' ---- -module: restconf_config -version_added: "2.8" -author: "Ganesh Nalawade (@ganeshrn)" -short_description: Handles create, update, read and delete of configuration data on RESTCONF enabled devices. -description: - - RESTCONF is a standard mechanisms to allow web applications to configure and manage - data. RESTCONF is a IETF standard and documented on RFC 8040. - - This module allows the user to configure data on RESTCONF enabled devices. -options: - path: - description: - - URI being used to execute API calls. - required: true - content: - description: - - The configuration data in format as specififed in C(format) option. Required unless C(method) is - I(delete). - method: - description: - - The RESTCONF method to manage the configuration change on device. The value I(post) is used to - create a data resource or invoke an operation resource, I(put) is used to replace the target - data resource, I(patch) is used to modify the target resource, and I(delete) is used to delete - the target resource. - required: false - default: post - choices: ['post', 'put', 'patch', 'delete'] - format: - description: - - The format of the configuration provided as value of C(content). Accepted values are I(xml) and I(json) and - the given configuration format should be supported by remote RESTCONF server. - default: json - choices: ['json', 'xml'] -''' - -EXAMPLES = ''' -- name: create l3vpn services - restconf_config: - path: /config/ietf-l3vpn-svc:l3vpn-svc/vpn-services - content: | - { - "vpn-service":[ - { - "vpn-id": "red_vpn2", - "customer-name": "blue", - "vpn-service-topology": "ietf-l3vpn-svc:any-to-any" - }, - { - "vpn-id": "blue_vpn1", - "customer-name": "red", - "vpn-service-topology": "ietf-l3vpn-svc:any-to-any" - } - ] - } -''' - -RETURN = ''' -candidate: - description: The configuration sent to the device. - returned: When the method is not delete - type: dict - sample: | - { - "vpn-service": [ - { - "customer-name": "red", - "vpn-id": "blue_vpn1", - "vpn-service-topology": "ietf-l3vpn-svc:any-to-any" - } - ] - } -running: - description: The current running configuration on the device. - returned: When the method is not delete - type: dict - sample: | - { - "vpn-service": [ - { - "vpn-id": "red_vpn2", - "customer-name": "blue", - "vpn-service-topology": "ietf-l3vpn-svc:any-to-any" - }, - { - "vpn-id": "blue_vpn1", - "customer-name": "red", - "vpn-service-topology": "ietf-l3vpn-svc:any-to-any" - } - ] - } - -''' - -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import ConnectionError -from ansible.module_utils.network.common.utils import dict_diff -from ansible.module_utils.network.restconf import restconf -from ansible.module_utils.six import string_types - - -def main(): - """entry point for module execution - """ - argument_spec = dict( - path=dict(required=True), - content=dict(), - method=dict(choices=['post', 'put', 'patch', 'delete'], default='post'), - format=dict(choices=['json', 'xml'], default='json'), - ) - required_if = [ - ['method', 'post', ['content']], - ['method', 'put', ['content']], - ['method', 'patch', ['content']], - ] - - module = AnsibleModule( - argument_spec=argument_spec, - required_if=required_if, - supports_check_mode=True - ) - - path = module.params['path'] - candidate = module.params['content'] - method = module.params['method'] - format = module.params['format'] - - if isinstance(candidate, string_types): - candidate = json.loads(candidate) - - warnings = list() - result = {'changed': False, 'warnings': warnings} - - running = None - commit = not module.check_mode - try: - running = restconf.get(module, path, output=format) - except ConnectionError as exc: - if exc.code == 404: - running = None - else: - module.fail_json(msg=to_text(exc), code=exc.code) - - try: - if method == 'delete': - if running: - if commit: - restconf.edit_config(module, path=path, method='DELETE') - result['changed'] = True - else: - warnings.append("delete not executed as resource '%s' does not exist" % path) - else: - if running: - if method == 'post': - module.fail_json(msg="resource '%s' already exist" % path, code=409) - diff = dict_diff(running, candidate) - result['candidate'] = candidate - result['running'] = running - else: - method = 'POST' - diff = candidate - - if diff: - if module._diff: - result['diff'] = {'prepared': diff, 'before': candidate, 'after': running} - - if commit: - restconf.edit_config(module, path=path, content=diff, method=method.upper(), format=format) - result['changed'] = True - - except ConnectionError as exc: - module.fail_json(msg=str(exc), code=exc.code) - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/restconf/restconf_get.py b/lib/ansible/modules/network/restconf/restconf_get.py deleted file mode 100644 index d61767fa64d..00000000000 --- a/lib/ansible/modules/network/restconf/restconf_get.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/python -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: restconf_get -version_added: "2.8" -author: "Ganesh Nalawade (@ganeshrn)" -short_description: Fetch configuration/state data from RESTCONF enabled devices. -description: - - RESTCONF is a standard mechanisms to allow web applications to access the - configuration data and state data developed and standardized by - the IETF. It is documented in RFC 8040. - - This module allows the user to fetch configuration and state data from RESTCONF - enabled devices. -options: - path: - description: - - URI being used to execute API calls. - required: true - content: - description: - - The C(content) is a query parameter that controls how descendant nodes of the - requested data nodes in C(path) will be processed in the reply. If value is - I(config) return only configuration descendant data nodes of value in C(path). - If value is I(nonconfig) return only non-configuration descendant data nodes - of value in C(path). If value is I(all) return all descendant data nodes of - value in C(path) - required: false - choices: ['config', 'nonconfig', 'all'] - output: - description: - - The output of response received. - required: false - default: json - choices: ['json', 'xml'] -""" - -EXAMPLES = """ -- name: get l3vpn services - restconf_get: - path: /config/ietf-l3vpn-svc:l3vpn-svc/vpn-services -""" - -RETURN = """ -response: - description: A dictionary representing a JSON-formatted response - returned: when the device response is valid JSON - type: dict - sample: | - { - "vpn-services": { - "vpn-service": [ - { - "customer-name": "red", - "vpn-id": "blue_vpn1", - "vpn-service-topology": "ietf-l3vpn-svc:any-to-any" - } - ] - } - } - -""" - -from ansible.module_utils._text import to_text -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import ConnectionError -from ansible.module_utils.network.restconf import restconf - - -def main(): - """entry point for module execution - """ - argument_spec = dict( - path=dict(required=True), - content=dict(choices=['config', 'nonconfig', 'all']), - output=dict(choices=['json', 'xml'], default='json'), - ) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True - ) - - result = {'changed': False} - - try: - response = restconf.get(module, **module.params) - except ConnectionError as exc: - module.fail_json(msg=to_text(exc), code=exc.code) - - result.update({ - 'response': response, - }) - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/routing/_net_static_route.py b/lib/ansible/modules/network/routing/_net_static_route.py deleted file mode 100644 index 66a6bd2956c..00000000000 --- a/lib/ansible/modules/network/routing/_net_static_route.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_static_route -version_added: "2.4" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Manage static IP routes on network appliances (routers, switches et. al.) -description: - - This module provides declarative management of static - IP routes on network appliances (routers, switches et. al.). -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_static_route" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic - -options: - prefix: - description: - - Network prefix of the static route. - required: true - mask: - description: - - Network prefix mask of the static route. - required: true - next_hop: - description: - - Next hop IP of the static route. - required: true - admin_distance: - description: - - Admin distance of the static route. - aggregate: - description: List of static route definitions - purge: - description: - - Purge static routes not defined in the I(aggregate) parameter. - default: no - state: - description: - - State of the static route configuration. - default: present - choices: ['present', 'absent'] -""" - -EXAMPLES = """ -- name: configure static route - net_static_route: - prefix: 192.168.2.0 - mask: 255.255.255.0 - next_hop: 10.0.0.1 - -- name: remove configuration - net_static_route: - prefix: 192.168.2.0 - mask: 255.255.255.0 - next_hop: 10.0.0.1 - state: absent - -- name: configure aggregates of static routes - net_static_route: - aggregate: - - { prefix: 192.168.2.0, mask: 255.255.255.0, next_hop: 10.0.0.1 } - - { prefix: 192.168.3.0, mask: 255.255.255.0, next_hop: 10.0.2.1 } - -- name: Remove static route collections - net_static_route: - aggregate: - - { prefix: 172.24.1.0/24, next_hop: 192.168.42.64 } - - { prefix: 172.24.3.0/24, next_hop: 192.168.42.64 } - state: absent -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always - type: list - sample: - - ip route 192.168.2.0/24 10.0.0.1 -""" diff --git a/lib/ansible/modules/network/system/_net_banner.py b/lib/ansible/modules/network/system/_net_banner.py deleted file mode 100644 index cd0330e5a8a..00000000000 --- a/lib/ansible/modules/network/system/_net_banner.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/python -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_banner -version_added: "2.4" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Manage multiline banners on network devices -description: - - This will configure both login and motd banners on network devices. - It allows playbooks to add or remove - banner text from the active running configuration. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_banner" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - banner: - description: - - Specifies which banner that should be - configured on the remote device. - required: true - choices: ['login', 'motd'] - text: - description: - - The banner text that should be - present in the remote device running configuration. This argument - accepts a multiline string, with no empty lines. Requires I(state=present). - state: - description: - - Specifies whether or not the configuration is - present in the current devices active running configuration. - default: present - choices: ['present', 'absent'] -""" - -EXAMPLES = """ -- name: configure the login banner - net_banner: - banner: login - text: | - this is my login banner - that contains a multiline - string - state: present - -- name: remove the motd banner - net_banner: - banner: motd - state: absent - -- name: Configure banner from file - net_banner: - banner: motd - text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}" - state: present - -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - banner login - - this is my login banner - - that contains a multiline - - string -""" diff --git a/lib/ansible/modules/network/system/_net_logging.py b/lib/ansible/modules/network/system/_net_logging.py deleted file mode 100644 index e78b89f85d1..00000000000 --- a/lib/ansible/modules/network/system/_net_logging.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_logging -version_added: "2.4" -author: "Ganesh Nalawade (@ganeshrn)" -short_description: Manage logging on network devices -description: - - This module provides declarative management of logging - on network devices. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_logging" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - dest: - description: - - Destination of the logs. - choices: ['console', 'host'] - name: - description: - - If value of C(dest) is I(host) it indicates file-name - the host name to be notified. - facility: - description: - - Set logging facility. - level: - description: - - Set logging severity levels. - aggregate: - description: List of logging definitions. - purge: - description: - - Purge logging not defined in the I(aggregate) parameter. - default: no - state: - description: - - State of the logging configuration. - default: present - choices: ['present', 'absent'] -""" - -EXAMPLES = """ -- name: configure console logging - net_logging: - dest: console - facility: any - level: critical - -- name: remove console logging configuration - net_logging: - dest: console - state: absent - -- name: configure host logging - net_logging: - dest: host - name: 192.0.2.1 - facility: kernel - level: critical - -- name: Configure file logging using aggregate - net_logging: - dest: file - aggregate: - - name: test-1 - facility: pfe - level: critical - - name: test-2 - facility: kernel - level: emergency -- name: Delete file logging using aggregate - net_logging: - dest: file - aggregate: - - name: test-1 - facility: pfe - level: critical - - name: test-2 - facility: kernel - level: emergency - state: absent -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - logging console critical -""" diff --git a/lib/ansible/modules/network/system/_net_system.py b/lib/ansible/modules/network/system/_net_system.py deleted file mode 100644 index dd53f75b783..00000000000 --- a/lib/ansible/modules/network/system/_net_system.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - - -DOCUMENTATION = """ ---- -module: net_system -version_added: "2.4" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Manage the system attributes on network devices -description: - - This module provides declarative management of node system attributes - on network devices. It provides an option to configure host system - parameters or remove those parameters from the device active - configuration. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_system" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - hostname: - description: - - Configure the device hostname parameter. This option takes an ASCII string value. - domain_name: - description: - - Configure the IP domain name - on the remote device to the provided value. Value - should be in the dotted name form and will be - appended to the C(hostname) to create a fully-qualified - domain name. - domain_search: - description: - - Provides the list of domain suffixes to - append to the hostname for the purpose of doing name resolution. - This argument accepts a name or list of names and will be reconciled - with the current active configuration on the running node. - lookup_source: - description: - - Provides one or more source - interfaces to use for performing DNS lookups. The interface - provided in C(lookup_source) must be a valid interface configured - on the device. - name_servers: - description: - - List of DNS name servers by IP address to use to perform name resolution - lookups. This argument accepts either a list of DNS servers See - examples. - state: - description: - - State of the configuration - values in the device's current active configuration. When set - to I(present), the values should be configured in the device active - configuration and when set to I(absent) the values should not be - in the device active configuration - default: present - choices: ['present', 'absent'] -""" - -EXAMPLES = """ -- name: configure hostname and domain name - net_system: - hostname: ios01 - domain_name: test.example.com - domain_search: - - ansible.com - - redhat.com - - cisco.com - -- name: domain search on single domain - net_system: - domain_search: ansible.com - -- name: remove configuration - net_system: - state: absent - -- name: configure DNS lookup sources - net_system: - lookup_source: MgmtEth0/0/CPU0/0 - -- name: configure name servers - net_system: - name_servers: - - 8.8.8.8 - - 8.8.4.4 -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always, except for the platforms that use Netconf transport to manage the device. - type: list - sample: - - hostname ios01 - - ip domain name test.example.com -""" diff --git a/lib/ansible/modules/network/system/_net_user.py b/lib/ansible/modules/network/system/_net_user.py deleted file mode 100644 index efc7ad89f71..00000000000 --- a/lib/ansible/modules/network/system/_net_user.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'network'} - -DOCUMENTATION = """ ---- -module: net_user -version_added: "2.4" -author: "Trishna Guha (@trishnaguha)" -short_description: Manage the aggregate of local users on network device -description: - - This module provides declarative management of the local usernames - configured on network devices. It allows playbooks to manage - either individual usernames or the aggregate of usernames in the - current running config. It also supports purging usernames from the - configuration that are not explicitly defined. -deprecated: - removed_in: "2.13" - alternative: Use platform-specific "[netos]_user" module - why: Updated modules released with more functionality -extends_documentation_fragment: network_agnostic -options: - aggregate: - description: - - The set of username objects to be configured on the remote - network device. The list entries can either be the username - or a hash of username and properties. This argument is mutually - exclusive with the C(name) argument. - name: - description: - - The username to be configured on the remote network device. - This argument accepts a string value and is mutually exclusive - with the C(aggregate) argument. - Please note that this option is not same as C(provider username). - configured_password: - description: - - The password to be configured on the remote network device. The - password needs to be provided in clear and it will be encrypted - on the device. - Please note that this option is not same as C(provider password). - update_password: - description: - - Since passwords are encrypted in the device running config, this - argument will instruct the module when to change the password. When - set to C(always), the password will always be updated in the device - and when set to C(on_create) the password will be updated only if - the username is created. - default: always - choices: ['on_create', 'always'] - privilege: - description: - - The C(privilege) argument configures the privilege level of the - user when logged into the system. This argument accepts integer - values in the range of 1 to 15. - role: - description: - - Configures the role for the username in the - device running configuration. The argument accepts a string value - defining the role name. This argument does not check if the role - has been configured on the device. - sshkey: - description: - - Specifies the SSH public key to configure - for the given username. This argument accepts a valid SSH key value. - nopassword: - description: - - Defines the username without assigning - a password. This will allow the user to login to the system - without being authenticated by a password. - type: bool - purge: - description: - - Instructs the module to consider the - resource definition absolute. It will remove any previously - configured usernames on the device with the exception of the - `admin` user (the current defined set of users). - type: bool - default: false - state: - description: - - Configures the state of the username definition - as it relates to the device operational configuration. When set - to I(present), the username(s) should be configured in the device active - configuration and when set to I(absent) the username(s) should not be - in the device active configuration - default: present - choices: ['present', 'absent'] -""" - -EXAMPLES = """ -- name: create a new user - net_user: - name: ansible - sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" - state: present - -- name: remove all users except admin - net_user: - purge: yes - -- name: set multiple users to privilege level 15 - net_user: - aggregate: - - { name: netop } - - { name: netend } - privilege: 15 - state: present - -- name: Change Password for User netop - net_user: - name: netop - configured_password: "{{ new_password }}" - update_password: always - state: present -""" - -RETURN = """ -commands: - description: The list of configuration mode commands to send to the device - returned: always - type: list - sample: - - username ansible secret password - - username admin secret admin -""" diff --git a/lib/ansible/modules/network/system/net_ping.py b/lib/ansible/modules/network/system/net_ping.py deleted file mode 100644 index a8c23cc9e6e..00000000000 --- a/lib/ansible/modules/network/system/net_ping.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = r''' ---- -module: net_ping -version_added: "2.4" -author: "Jacob McGill (@jmcgill298)" -short_description: Tests reachability using ping from a network device -description: - - Tests reachability using ping from network device to a remote destination. - - For Windows targets, use the M(win_ping) module instead. - - For targets running Python, use the M(ping) module instead. -extends_documentation_fragment: network_agnostic -options: - count: - description: - - Number of packets to send. - default: 5 - dest: - description: - - The IP Address or hostname (resolvable by switch) of the remote node. - required: true - source: - description: - - The source IP Address. - state: - description: - - Determines if the expected result is success or fail. - choices: [ absent, present ] - default: present - vrf: - description: - - The VRF to use for forwarding. - default: default -notes: - - For Windows targets, use the M(win_ping) module instead. - - For targets running Python, use the M(ping) module instead. -''' - - -EXAMPLES = r''' -- name: Test reachability to 10.10.10.10 using default vrf - net_ping: - dest: 10.10.10.10 - -- name: Test reachability to 10.20.20.20 using prod vrf - net_ping: - dest: 10.20.20.20 - vrf: prod - -- name: Test unreachability to 10.30.30.30 using default vrf - net_ping: - dest: 10.30.30.30 - state: absent - -- name: Test reachability to 10.40.40.40 using prod vrf and setting count and source - net_ping: - dest: 10.40.40.40 - source: loopback0 - vrf: prod - count: 20 -''' - -RETURN = r''' -commands: - description: Show the command sent. - returned: always - type: list - sample: ["ping vrf prod 10.40.40.40 count 20 source loopback0"] -packet_loss: - description: Percentage of packets lost. - returned: always - type: str - sample: "0%" -packets_rx: - description: Packets successfully received. - returned: always - type: int - sample: 20 -packets_tx: - description: Packets successfully transmitted. - returned: always - type: int - sample: 20 -rtt: - description: Show RTT stats. - returned: always - type: dict - sample: {"avg": 2, "max": 8, "min": 1} -''' diff --git a/lib/ansible/plugins/action/cli_command.py b/lib/ansible/plugins/action/cli_command.py deleted file mode 100644 index 15052e1b584..00000000000 --- a/lib/ansible/plugins/action/cli_command.py +++ /dev/null @@ -1,31 +0,0 @@ -# -# Copyright 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 . -# -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.normal import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - - def run(self, tmp=None, task_vars=None): - if self._play_context.connection.split('.')[-1] != 'network_cli': - return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} - - return super(ActionModule, self).run(task_vars=task_vars) diff --git a/lib/ansible/plugins/action/cli_config.py b/lib/ansible/plugins/action/cli_config.py deleted file mode 100644 index 0f2753625ec..00000000000 --- a/lib/ansible/plugins/action/cli_config.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# Copyright 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 . -# -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.network import ActionModule as ActionNetworkModule - - -class ActionModule(ActionNetworkModule): - - def run(self, tmp=None, task_vars=None): - del tmp # tmp no longer has any effect - - self._config_module = True - if self._play_context.connection.split('.')[-1] != 'network_cli': - return {'failed': True, 'msg': 'Connection type %s is not valid for cli_config module' % self._play_context.connection} - - return super(ActionModule, self).run(task_vars=task_vars) diff --git a/lib/ansible/plugins/action/net_banner.py b/lib/ansible/plugins/action/net_banner.py deleted file mode 100644 index d8617842d81..00000000000 --- a/lib/ansible/plugins/action/net_banner.py +++ /dev/null @@ -1,28 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - return result diff --git a/lib/ansible/plugins/action/net_base.py b/lib/ansible/plugins/action/net_base.py deleted file mode 100644 index edf8561530d..00000000000 --- a/lib/ansible/plugins/action/net_base.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright: (c) 2015, Ansible Inc, -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import copy - -from ansible.errors import AnsibleError -from ansible.plugins.action import ActionBase -from ansible.utils.display import Display - -display = Display() - - -class ActionModule(ActionBase): - - def run(self, tmp=None, task_vars=None): - del tmp # tmp no longer has any effect - - result = {} - play_context = copy.deepcopy(self._play_context) - play_context.network_os = self._get_network_os(task_vars) - new_task = self._task.copy() - - module = self._get_implementation_module(play_context.network_os, self._task.action) - if not module: - if self._task.args['fail_on_missing_module']: - result['failed'] = True - else: - result['failed'] = False - - result['msg'] = ('Could not find implementation module %s for %s' % - (self._task.action, play_context.network_os)) - return result - - new_task.action = module - - action = self._shared_loader_obj.action_loader.get(play_context.network_os, - task=new_task, - connection=self._connection, - play_context=play_context, - loader=self._loader, - templar=self._templar, - shared_loader_obj=self._shared_loader_obj) - display.vvvv('Running implementation module %s' % module) - return action.run(task_vars=task_vars) - - def _get_network_os(self, task_vars): - if 'network_os' in self._task.args and self._task.args['network_os']: - display.vvvv('Getting network OS from task argument') - network_os = self._task.args['network_os'] - elif self._play_context.network_os: - display.vvvv('Getting network OS from inventory') - network_os = self._play_context.network_os - elif 'network_os' in task_vars.get('ansible_facts', {}) and task_vars['ansible_facts']['network_os']: - display.vvvv('Getting network OS from fact') - network_os = task_vars['ansible_facts']['network_os'] - else: - raise AnsibleError('ansible_network_os must be specified on this host to use platform agnostic modules') - - return network_os - - def _get_implementation_module(self, network_os, platform_agnostic_module): - module_name = network_os.split('.')[-1] + '_' + platform_agnostic_module.partition('_')[2] - if '.' in network_os: - fqcn_module = '.'.join(network_os.split('.')[0:-1]) - implementation_module = fqcn_module + '.' + module_name - else: - implementation_module = module_name - - if implementation_module not in self._shared_loader_obj.module_loader: - implementation_module = None - - return implementation_module diff --git a/lib/ansible/plugins/action/net_get.py b/lib/ansible/plugins/action/net_get.py deleted file mode 100644 index 4f96f9d9615..00000000000 --- a/lib/ansible/plugins/action/net_get.py +++ /dev/null @@ -1,183 +0,0 @@ -# (c) 2018, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import os -import re -import uuid -import hashlib - -from ansible.errors import AnsibleError -from ansible.module_utils._text import to_text, to_bytes -from ansible.module_utils.connection import Connection, ConnectionError -from ansible.plugins.action import ActionBase -from ansible.module_utils.six.moves.urllib.parse import urlsplit -from ansible.utils.display import Display - -display = Display() - - -class ActionModule(ActionBase): - - def run(self, tmp=None, task_vars=None): - socket_path = None - self._get_network_os(task_vars) - persistent_connection = self._play_context.connection.split('.')[-1] - - result = super(ActionModule, self).run(task_vars=task_vars) - - if persistent_connection != 'network_cli': - # It is supported only with network_cli - result['failed'] = True - result['msg'] = ('connection type %s is not valid for net_get module,' - ' please use fully qualified name of network_cli connection type' % self._play_context.connection) - return result - - try: - src = self._task.args['src'] - except KeyError as exc: - return {'failed': True, 'msg': 'missing required argument: %s' % exc} - - # Get destination file if specified - dest = self._task.args.get('dest') - - if dest is None: - dest = self._get_default_dest(src) - else: - dest = self._handle_dest_path(dest) - - # Get proto - proto = self._task.args.get('protocol') - if proto is None: - proto = 'scp' - - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - sock_timeout = conn.get_option('persistent_command_timeout') - - try: - changed = self._handle_existing_file(conn, src, dest, proto, sock_timeout) - if changed is False: - result['changed'] = changed - result['destination'] = dest - return result - except Exception as exc: - result['msg'] = ('Warning: %s idempotency check failed. Check dest' % exc) - - try: - conn.get_file( - source=src, destination=dest, - proto=proto, timeout=sock_timeout - ) - except Exception as exc: - result['failed'] = True - result['msg'] = 'Exception received: %s' % exc - - result['changed'] = changed - result['destination'] = dest - return result - - def _handle_dest_path(self, dest): - working_path = self._get_working_path() - - if os.path.isabs(dest) or urlsplit('dest').scheme: - dst = dest - else: - dst = self._loader.path_dwim_relative(working_path, '', dest) - - return dst - - def _get_src_filename_from_path(self, src_path): - filename_list = re.split('/|:', src_path) - return filename_list[-1] - - def _get_default_dest(self, src_path): - dest_path = self._get_working_path() - src_fname = self._get_src_filename_from_path(src_path) - filename = '%s/%s' % (dest_path, src_fname) - return filename - - def _handle_existing_file(self, conn, source, dest, proto, timeout): - """ - Determines whether the source and destination file match. - - :return: False if source and dest both exist and have matching sha1 sums, True otherwise. - """ - if not os.path.exists(dest): - return True - - cwd = self._loader.get_basedir() - filename = str(uuid.uuid4()) - tmp_dest_file = os.path.join(cwd, filename) - try: - conn.get_file( - source=source, destination=tmp_dest_file, - proto=proto, timeout=timeout - ) - except ConnectionError as exc: - error = to_text(exc) - if error.endswith("No such file or directory"): - if os.path.exists(tmp_dest_file): - os.remove(tmp_dest_file) - return True - - try: - with open(tmp_dest_file, 'r') as f: - new_content = f.read() - with open(dest, 'r') as f: - old_content = f.read() - except (IOError, OSError): - os.remove(tmp_dest_file) - raise - - sha1 = hashlib.sha1() - old_content_b = to_bytes(old_content, errors='surrogate_or_strict') - sha1.update(old_content_b) - checksum_old = sha1.digest() - - sha1 = hashlib.sha1() - new_content_b = to_bytes(new_content, errors='surrogate_or_strict') - sha1.update(new_content_b) - checksum_new = sha1.digest() - os.remove(tmp_dest_file) - if checksum_old == checksum_new: - return False - return True - - def _get_working_path(self): - cwd = self._loader.get_basedir() - if self._task._role is not None: - cwd = self._task._role._role_path - return cwd - - def _get_network_os(self, task_vars): - if 'network_os' in self._task.args and self._task.args['network_os']: - display.vvvv('Getting network OS from task argument') - network_os = self._task.args['network_os'] - elif self._play_context.network_os: - display.vvvv('Getting network OS from inventory') - network_os = self._play_context.network_os - elif 'network_os' in task_vars.get('ansible_facts', {}) and task_vars['ansible_facts']['network_os']: - display.vvvv('Getting network OS from fact') - network_os = task_vars['ansible_facts']['network_os'] - else: - raise AnsibleError('ansible_network_os must be specified on this host') - - return network_os diff --git a/lib/ansible/plugins/action/net_interface.py b/lib/ansible/plugins/action/net_interface.py deleted file mode 100644 index 81f46f197a0..00000000000 --- a/lib/ansible/plugins/action/net_interface.py +++ /dev/null @@ -1,27 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - return result diff --git a/lib/ansible/plugins/action/net_l2_interface.py b/lib/ansible/plugins/action/net_l2_interface.py deleted file mode 100644 index 81f46f197a0..00000000000 --- a/lib/ansible/plugins/action/net_l2_interface.py +++ /dev/null @@ -1,27 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - return result diff --git a/lib/ansible/plugins/action/net_l3_interface.py b/lib/ansible/plugins/action/net_l3_interface.py deleted file mode 100644 index d8617842d81..00000000000 --- a/lib/ansible/plugins/action/net_l3_interface.py +++ /dev/null @@ -1,28 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - return result diff --git a/lib/ansible/plugins/action/net_linkagg.py b/lib/ansible/plugins/action/net_linkagg.py deleted file mode 100644 index 81f46f197a0..00000000000 --- a/lib/ansible/plugins/action/net_linkagg.py +++ /dev/null @@ -1,27 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - return result diff --git a/lib/ansible/plugins/action/net_lldp.py b/lib/ansible/plugins/action/net_lldp.py deleted file mode 100644 index d8617842d81..00000000000 --- a/lib/ansible/plugins/action/net_lldp.py +++ /dev/null @@ -1,28 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - return result diff --git a/lib/ansible/plugins/action/net_lldp_interface.py b/lib/ansible/plugins/action/net_lldp_interface.py deleted file mode 100644 index d8617842d81..00000000000 --- a/lib/ansible/plugins/action/net_lldp_interface.py +++ /dev/null @@ -1,28 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - return result diff --git a/lib/ansible/plugins/action/net_logging.py b/lib/ansible/plugins/action/net_logging.py deleted file mode 100644 index 81f46f197a0..00000000000 --- a/lib/ansible/plugins/action/net_logging.py +++ /dev/null @@ -1,27 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - return result diff --git a/lib/ansible/plugins/action/net_ping.py b/lib/ansible/plugins/action/net_ping.py deleted file mode 100644 index afcf49dede0..00000000000 --- a/lib/ansible/plugins/action/net_ping.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - return result diff --git a/lib/ansible/plugins/action/net_put.py b/lib/ansible/plugins/action/net_put.py deleted file mode 100644 index a1b83fc6c42..00000000000 --- a/lib/ansible/plugins/action/net_put.py +++ /dev/null @@ -1,212 +0,0 @@ -# (c) 2018, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import os -import uuid -import hashlib - -from ansible.errors import AnsibleError -from ansible.module_utils._text import to_text, to_bytes -from ansible.module_utils.connection import Connection, ConnectionError -from ansible.plugins.action import ActionBase -from ansible.module_utils.six.moves.urllib.parse import urlsplit -from ansible.utils.display import Display - -display = Display() - - -class ActionModule(ActionBase): - - def run(self, tmp=None, task_vars=None): - socket_path = None - network_os = self._get_network_os(task_vars).split('.')[-1] - persistent_connection = self._play_context.connection.split('.')[-1] - - result = super(ActionModule, self).run(task_vars=task_vars) - - if persistent_connection != 'network_cli': - # It is supported only with network_cli - result['failed'] = True - result['msg'] = ('connection type %s is not valid for net_put module,' - ' please use fully qualified name of network_cli connection type' % self._play_context.connection) - return result - - try: - src = self._task.args['src'] - except KeyError as exc: - return {'failed': True, 'msg': 'missing required argument: %s' % exc} - - src_file_path_name = src - - # Get destination file if specified - dest = self._task.args.get('dest') - - # Get proto - proto = self._task.args.get('protocol') - if proto is None: - proto = 'scp' - - # Get mode if set - mode = self._task.args.get('mode') - if mode is None: - mode = 'binary' - - if mode == 'text': - try: - self._handle_template(convert_data=False) - except ValueError as exc: - return dict(failed=True, msg=to_text(exc)) - - # Now src has resolved file write to disk in current diectory for scp - src = self._task.args.get('src') - filename = str(uuid.uuid4()) - cwd = self._loader.get_basedir() - output_file = os.path.join(cwd, filename) - try: - with open(output_file, 'wb') as f: - f.write(to_bytes(src, encoding='utf-8')) - except Exception: - os.remove(output_file) - raise - else: - try: - output_file = self._get_binary_src_file(src) - except ValueError as exc: - return dict(failed=True, msg=to_text(exc)) - - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - sock_timeout = conn.get_option('persistent_command_timeout') - - if dest is None: - dest = src_file_path_name - - try: - changed = self._handle_existing_file(conn, output_file, dest, proto, sock_timeout) - if changed is False: - result['changed'] = changed - result['destination'] = dest - return result - except Exception as exc: - result['msg'] = ('Warning: %s idempotency check failed. Check dest' % exc) - - try: - conn.copy_file( - source=output_file, destination=dest, - proto=proto, timeout=sock_timeout - ) - except Exception as exc: - if to_text(exc) == "No response from server": - if network_os == 'iosxr': - # IOSXR sometimes closes socket prematurely after completion - # of file transfer - result['msg'] = 'Warning: iosxr scp server pre close issue. Please check dest' - else: - result['failed'] = True - result['msg'] = 'Exception received: %s' % exc - - if mode == 'text': - # Cleanup tmp file expanded wih ansible vars - os.remove(output_file) - - result['changed'] = changed - result['destination'] = dest - return result - - def _handle_existing_file(self, conn, source, dest, proto, timeout): - """ - Determines whether the source and destination file match. - - :return: False if source and dest both exist and have matching sha1 sums, True otherwise. - """ - cwd = self._loader.get_basedir() - filename = str(uuid.uuid4()) - tmp_source_file = os.path.join(cwd, filename) - try: - conn.get_file( - source=dest, destination=tmp_source_file, - proto=proto, timeout=timeout - ) - except ConnectionError as exc: - error = to_text(exc) - if error.endswith("No such file or directory"): - if os.path.exists(tmp_source_file): - os.remove(tmp_source_file) - return True - - try: - with open(source, 'r') as f: - new_content = f.read() - with open(tmp_source_file, 'r') as f: - old_content = f.read() - except (IOError, OSError): - os.remove(tmp_source_file) - raise - - sha1 = hashlib.sha1() - old_content_b = to_bytes(old_content, errors='surrogate_or_strict') - sha1.update(old_content_b) - checksum_old = sha1.digest() - - sha1 = hashlib.sha1() - new_content_b = to_bytes(new_content, errors='surrogate_or_strict') - sha1.update(new_content_b) - checksum_new = sha1.digest() - os.remove(tmp_source_file) - if checksum_old == checksum_new: - return False - return True - - def _get_binary_src_file(self, src): - working_path = self._get_working_path() - - if os.path.isabs(src) or urlsplit('src').scheme: - source = src - else: - source = self._loader.path_dwim_relative(working_path, 'templates', src) - if not source: - source = self._loader.path_dwim_relative(working_path, src) - - if not os.path.exists(source): - raise ValueError('path specified in src not found') - - return source - - def _get_working_path(self): - cwd = self._loader.get_basedir() - if self._task._role is not None: - cwd = self._task._role._role_path - return cwd - - def _get_network_os(self, task_vars): - if 'network_os' in self._task.args and self._task.args['network_os']: - display.vvvv('Getting network OS from task argument') - network_os = self._task.args['network_os'] - elif self._play_context.network_os: - display.vvvv('Getting network OS from inventory') - network_os = self._play_context.network_os - elif 'network_os' in task_vars.get('ansible_facts', {}) and task_vars['ansible_facts']['network_os']: - display.vvvv('Getting network OS from fact') - network_os = task_vars['ansible_facts']['network_os'] - else: - raise AnsibleError('ansible_network_os must be specified on this host') - - return network_os diff --git a/lib/ansible/plugins/action/net_static_route.py b/lib/ansible/plugins/action/net_static_route.py deleted file mode 100644 index d8617842d81..00000000000 --- a/lib/ansible/plugins/action/net_static_route.py +++ /dev/null @@ -1,28 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - return result diff --git a/lib/ansible/plugins/action/net_system.py b/lib/ansible/plugins/action/net_system.py deleted file mode 100644 index 81f46f197a0..00000000000 --- a/lib/ansible/plugins/action/net_system.py +++ /dev/null @@ -1,27 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - return result diff --git a/lib/ansible/plugins/action/net_user.py b/lib/ansible/plugins/action/net_user.py deleted file mode 100644 index 81f46f197a0..00000000000 --- a/lib/ansible/plugins/action/net_user.py +++ /dev/null @@ -1,27 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - return result diff --git a/lib/ansible/plugins/action/net_vlan.py b/lib/ansible/plugins/action/net_vlan.py deleted file mode 100644 index d8617842d81..00000000000 --- a/lib/ansible/plugins/action/net_vlan.py +++ /dev/null @@ -1,28 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - return result diff --git a/lib/ansible/plugins/action/net_vrf.py b/lib/ansible/plugins/action/net_vrf.py deleted file mode 100644 index d8617842d81..00000000000 --- a/lib/ansible/plugins/action/net_vrf.py +++ /dev/null @@ -1,28 +0,0 @@ -# (c) 2017, Ansible 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 . -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.action.net_base import ActionModule as _ActionModule - - -class ActionModule(_ActionModule): - def run(self, tmp=None, task_vars=None): - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - return result diff --git a/lib/ansible/plugins/action/netconf.py b/lib/ansible/plugins/action/netconf.py deleted file mode 100644 index 2cd37661ba7..00000000000 --- a/lib/ansible/plugins/action/netconf.py +++ /dev/null @@ -1,90 +0,0 @@ -# -# Copyright 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 . -# -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import copy -import sys - -from ansible.plugins.action.network import ActionModule as ActionNetworkModule -from ansible.utils.display import Display - -display = Display() - - -class ActionModule(ActionNetworkModule): - - def run(self, tmp=None, task_vars=None): - del tmp # tmp no longer has any effect - - module_name = self._task.action.split('.')[-1] - self._config_module = True if module_name == 'netconf_config' else False - persistent_connection = self._play_context.connection.split('.')[-1] - warnings = [] - - if persistent_connection not in ['netconf', 'local'] and module_name == 'netconf_config': - return {'failed': True, 'msg': 'Connection type %s is not valid for netconf_config module. ' - 'Valid connection type is netconf or local (deprecated)' % self._play_context.connection} - elif persistent_connection not in ['netconf'] and module_name != 'netconf_config': - return {'failed': True, 'msg': 'Connection type %s is not valid for %s module. ' - 'Valid connection type is netconf.' % (self._play_context.connection, module_name)} - - if self._play_context.connection == 'local' and module_name == 'netconf_config': - args = self._task.args - pc = copy.deepcopy(self._play_context) - pc.connection = 'ansible.netcommon.netconf' - pc.port = int(args.get('port') or self._play_context.port or 830) - - pc.remote_user = args.get('username') or self._play_context.connection_user - pc.password = args.get('password') or self._play_context.password - pc.private_key_file = args.get('ssh_keyfile') or self._play_context.private_key_file - - connection = self._shared_loader_obj.connection_loader.get('ansible.netcommon.persistent', pc, sys.stdin, - task_uuid=self._task._uuid) - - # TODO: Remove below code after ansible minimal is cut out - if connection is None: - pc.connection = 'netconf' - connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid) - - display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr) - - timeout = args.get('timeout') - command_timeout = int(timeout) if timeout else connection.get_option('persistent_command_timeout') - connection.set_options(direct={'persistent_command_timeout': command_timeout, 'look_for_keys': args.get('look_for_keys'), - 'hostkey_verify': args.get('hostkey_verify'), - 'allow_agent': args.get('allow_agent')}) - - socket_path = connection.run() - display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) - if not socket_path: - return {'failed': True, - 'msg': 'unable to open shell. Please see: ' + - 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} - - task_vars['ansible_socket'] = socket_path - warnings.append(['connection local support for this module is deprecated and will be removed in version 2.14, use connection %s' % pc.connection]) - - result = super(ActionModule, self).run(task_vars=task_vars) - if warnings: - if 'warnings' in result: - result['warnings'].extend(warnings) - else: - result['warnings'] = warnings - return result diff --git a/lib/ansible/plugins/action/network.py b/lib/ansible/plugins/action/network.py deleted file mode 100644 index 24b1ca32c6a..00000000000 --- a/lib/ansible/plugins/action/network.py +++ /dev/null @@ -1,178 +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 . -# -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import os -import time -import re - -from ansible.errors import AnsibleError -from ansible.module_utils._text import to_text, to_bytes -from ansible.module_utils.six.moves.urllib.parse import urlsplit -from ansible.plugins.action.normal import ActionModule as _ActionModule -from ansible.utils.display import Display - -display = Display() - -PRIVATE_KEYS_RE = re.compile('__.+__') - - -class ActionModule(_ActionModule): - - def run(self, task_vars=None): - config_module = hasattr(self, '_config_module') and self._config_module - if config_module and self._task.args.get('src'): - try: - self._handle_src_option() - except AnsibleError as e: - return {'failed': True, 'msg': e.message, 'changed': False} - - result = super(ActionModule, self).run(task_vars=task_vars) - - if config_module and self._task.args.get('backup') and not result.get('failed'): - self._handle_backup_option(result, task_vars) - - return result - - def _handle_backup_option(self, result, task_vars): - - filename = None - backup_path = None - try: - content = result['__backup__'] - except KeyError: - raise AnsibleError('Failed while reading configuration backup') - - backup_options = self._task.args.get('backup_options') - if backup_options: - filename = backup_options.get('filename') - backup_path = backup_options.get('dir_path') - - if not backup_path: - cwd = self._get_working_path() - backup_path = os.path.join(cwd, 'backup') - if not filename: - tstamp = time.strftime("%Y-%m-%d@%H:%M:%S", time.localtime(time.time())) - filename = '%s_config.%s' % (task_vars['inventory_hostname'], tstamp) - - dest = os.path.join(backup_path, filename) - backup_path = os.path.expanduser(os.path.expandvars(to_bytes(backup_path, errors='surrogate_or_strict'))) - - if not os.path.exists(backup_path): - os.makedirs(backup_path) - - new_task = self._task.copy() - for item in self._task.args: - if not item.startswith('_'): - new_task.args.pop(item, None) - - new_task.args.update( - dict( - content=content, - dest=dest, - ), - ) - copy_action = self._shared_loader_obj.action_loader.get('copy', - task=new_task, - connection=self._connection, - play_context=self._play_context, - loader=self._loader, - templar=self._templar, - shared_loader_obj=self._shared_loader_obj) - copy_result = copy_action.run(task_vars=task_vars) - if copy_result.get('failed'): - result['failed'] = copy_result['failed'] - result['msg'] = copy_result.get('msg') - return - - result['backup_path'] = dest - if copy_result.get('changed', False): - result['changed'] = copy_result['changed'] - - if backup_options and backup_options.get('filename'): - result['date'] = time.strftime('%Y-%m-%d', time.gmtime(os.stat(result['backup_path']).st_ctime)) - result['time'] = time.strftime('%H:%M:%S', time.gmtime(os.stat(result['backup_path']).st_ctime)) - - else: - result['date'] = tstamp.split('@')[0] - result['time'] = tstamp.split('@')[1] - result['shortname'] = result['backup_path'][::-1].split('.', 1)[1][::-1] - result['filename'] = result['backup_path'].split('/')[-1] - - # strip out any keys that have two leading and two trailing - # underscore characters - for key in list(result.keys()): - if PRIVATE_KEYS_RE.match(key): - del result[key] - - def _get_working_path(self): - cwd = self._loader.get_basedir() - if self._task._role is not None: - cwd = self._task._role._role_path - return cwd - - def _handle_src_option(self, convert_data=True): - src = self._task.args.get('src') - working_path = self._get_working_path() - - if os.path.isabs(src) or urlsplit('src').scheme: - source = src - else: - source = self._loader.path_dwim_relative(working_path, 'templates', src) - if not source: - source = self._loader.path_dwim_relative(working_path, src) - - if not os.path.exists(source): - raise AnsibleError('path specified in src not found') - - try: - with open(source, 'r') as f: - template_data = to_text(f.read()) - except IOError as e: - raise AnsibleError("unable to load src file {0}, I/O error({1}): {2}".format(source, e.errno, e.strerror)) - - # Create a template search path in the following order: - # [working_path, self_role_path, dependent_role_paths, dirname(source)] - searchpath = [working_path] - if self._task._role is not None: - searchpath.append(self._task._role._role_path) - if hasattr(self._task, "_block:"): - dep_chain = self._task._block.get_dep_chain() - if dep_chain is not None: - for role in dep_chain: - searchpath.append(role._role_path) - searchpath.append(os.path.dirname(source)) - with self._templar.set_temporary_context(searchpath=searchpath): - self._task.args['src'] = self._templar.template(template_data, convert_data=convert_data) - - def _get_network_os(self, task_vars): - if 'network_os' in self._task.args and self._task.args['network_os']: - display.vvvv('Getting network OS from task argument') - network_os = self._task.args['network_os'] - elif self._play_context.network_os: - display.vvvv('Getting network OS from inventory') - network_os = self._play_context.network_os - elif 'network_os' in task_vars.get('ansible_facts', {}) and task_vars['ansible_facts']['network_os']: - display.vvvv('Getting network OS from fact') - network_os = task_vars['ansible_facts']['network_os'] - else: - raise AnsibleError('ansible_network_os must be specified on this host') - - return network_os diff --git a/lib/ansible/plugins/action/telnet.py b/lib/ansible/plugins/action/telnet.py deleted file mode 100644 index 3fd68eb7040..00000000000 --- a/lib/ansible/plugins/action/telnet.py +++ /dev/null @@ -1,96 +0,0 @@ -# (c) 2017, Ansible Project -# -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import telnetlib -from time import sleep - -from ansible.module_utils._text import to_native, to_bytes -from ansible.module_utils.six import text_type -from ansible.plugins.action import ActionBase -from ansible.utils.display import Display - -display = Display() - - -class ActionModule(ActionBase): - TRANSFERS_FILES = False - - def run(self, tmp=None, task_vars=None): - - if self._task.environment and any(self._task.environment): - self._display.warning('The telnet task does not support the environment keyword') - - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - if self._play_context.check_mode: - # in --check mode, always skip this module execution - result['skipped'] = True - result['msg'] = 'The telnet task does not support check mode' - else: - result['changed'] = True - result['failed'] = False - - host = self._task.args.get('host', self._play_context.remote_addr) - user = self._task.args.get('user', self._play_context.remote_user) - password = self._task.args.get('password', self._play_context.password) - - # FIXME, default to play_context? - port = self._task.args.get('port', '23') - timeout = self._task.args.get('timeout', 120) - pause = self._task.args.get('pause', 1) - - send_newline = self._task.args.get('send_newline', False) - - login_prompt = self._task.args.get('login_prompt', "login: ") - password_prompt = self._task.args.get('password_prompt', "Password: ") - prompts = self._task.args.get('prompts', ["\\$ "]) - commands = self._task.args.get('command') or self._task.args.get('commands') - - if isinstance(commands, text_type): - commands = commands.split(',') - - if isinstance(commands, list) and commands: - - tn = telnetlib.Telnet(host, port, timeout) - - output = [] - try: - if send_newline: - tn.write(b'\n') - - tn.read_until(to_bytes(login_prompt)) - tn.write(to_bytes(user + "\n")) - - if password: - tn.read_until(to_bytes(password_prompt)) - tn.write(to_bytes(password + "\n")) - - tn.expect(list(map(to_bytes, prompts))) - - for cmd in commands: - display.vvvvv('>>> %s' % cmd) - tn.write(to_bytes(cmd + "\n")) - index, match, out = tn.expect(list(map(to_bytes, prompts)), timeout=timeout) - display.vvvvv('<<< %s' % cmd) - output.append(out) - sleep(pause) - - tn.write(b"exit\n") - - except EOFError as e: - result['failed'] = True - result['msg'] = 'Telnet action failed: %s' % to_native(e) - finally: - if tn: - tn.close() - result['output'] = output - else: - result['failed'] = True - result['msg'] = 'Telnet requires a command to execute' - - return result diff --git a/lib/ansible/plugins/become/enable.py b/lib/ansible/plugins/become/enable.py deleted file mode 100644 index 3a2c1d5bdcb..00000000000 --- a/lib/ansible/plugins/become/enable.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2018, Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = """ - become: enable - short_description: Switch to elevated permissions on a network device - description: - - This become plugins allows elevated permissions on a remote network device. - author: ansible (@core) - version_added: "2.8" - options: - become_pass: - description: password - ini: - - section: enable_become_plugin - key: password - vars: - - name: ansible_become_password - - name: ansible_become_pass - - name: ansible_enable_pass - env: - - name: ANSIBLE_BECOME_PASS - - name: ANSIBLE_ENABLE_PASS - notes: - - enable is really implemented in the network connection handler and as such can only be used with network connections. - - This plugin ignores the 'become_exe' and 'become_user' settings as it uses an API and not an executable. -""" - -from ansible.plugins.become import BecomeBase - - -class BecomeModule(BecomeBase): - - name = 'enable' - - def build_become_command(self, cmd, shell): - # enable is implemented inside the network connection plugins - return cmd diff --git a/lib/ansible/plugins/connection/httpapi.py b/lib/ansible/plugins/connection/httpapi.py deleted file mode 100644 index ae240875fa0..00000000000 --- a/lib/ansible/plugins/connection/httpapi.py +++ /dev/null @@ -1,301 +0,0 @@ -# (c) 2018 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = """ ---- -author: Ansible Networking Team -connection: httpapi -short_description: Use httpapi to run command on network appliances -description: - - This connection plugin provides a connection to remote devices over a - HTTP(S)-based api. -version_added: "2.6" -options: - host: - description: - - Specifies the remote device FQDN or IP address to establish the HTTP(S) - connection to. - default: inventory_hostname - vars: - - name: ansible_host - port: - type: int - description: - - Specifies the port on the remote device that listens for connections - when establishing the HTTP(S) connection. - - When unspecified, will pick 80 or 443 based on the value of use_ssl. - ini: - - section: defaults - key: remote_port - env: - - name: ANSIBLE_REMOTE_PORT - vars: - - name: ansible_httpapi_port - network_os: - description: - - Configures the device platform network operating system. This value is - used to load the correct httpapi plugin to communicate with the remote - device - vars: - - name: ansible_network_os - remote_user: - description: - - The username used to authenticate to the remote device when the API - connection is first established. If the remote_user is not specified, - the connection will use the username of the logged in user. - - Can be configured from the CLI via the C(--user) or C(-u) options. - ini: - - section: defaults - key: remote_user - env: - - name: ANSIBLE_REMOTE_USER - vars: - - name: ansible_user - password: - description: - - Configures the user password used to authenticate to the remote device - when needed for the device API. - vars: - - name: ansible_password - - name: ansible_httpapi_pass - - name: ansible_httpapi_password - use_ssl: - type: boolean - description: - - Whether to connect using SSL (HTTPS) or not (HTTP). - default: False - vars: - - name: ansible_httpapi_use_ssl - validate_certs: - type: boolean - version_added: '2.7' - description: - - Whether to validate SSL certificates - default: True - vars: - - name: ansible_httpapi_validate_certs - use_proxy: - type: boolean - version_added: "2.9" - description: - - Whether to use https_proxy for requests. - default: True - vars: - - name: ansible_httpapi_use_proxy - become: - type: boolean - description: - - The become option will instruct the CLI session to attempt privilege - escalation on platforms that support it. Normally this means - transitioning from user mode to C(enable) mode in the CLI session. - If become is set to True and the remote device does not support - privilege escalation or the privilege has already been elevated, then - this option is silently ignored. - - Can be configured from the CLI via the C(--become) or C(-b) options. - default: False - ini: - - section: privilege_escalation - key: become - env: - - name: ANSIBLE_BECOME - vars: - - name: ansible_become - become_method: - description: - - This option allows the become method to be specified in for handling - privilege escalation. Typically the become_method value is set to - C(enable) but could be defined as other values. - default: sudo - ini: - - section: privilege_escalation - key: become_method - env: - - name: ANSIBLE_BECOME_METHOD - vars: - - name: ansible_become_method - persistent_connect_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait when trying to - initially establish a persistent connection. If this value expires - before the connection to the remote device is completed, the connection - will fail. - default: 30 - ini: - - section: persistent_connection - key: connect_timeout - env: - - name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT - vars: - - name: ansible_connect_timeout - persistent_command_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait for a command to - return from the remote device. If this timer is exceeded before the - command returns, the connection plugin will raise an exception and - close. - default: 30 - ini: - - section: persistent_connection - key: command_timeout - env: - - name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT - vars: - - name: ansible_command_timeout - persistent_log_messages: - type: boolean - description: - - This flag will enable logging the command executed and response received from - target device in the ansible log file. For this option to work 'log_path' ansible - configuration option is required to be set to a file path with write access. - - Be sure to fully understand the security implications of enabling this - option as it could create a security vulnerability by logging sensitive information in log file. - default: False - ini: - - section: persistent_connection - key: log_messages - env: - - name: ANSIBLE_PERSISTENT_LOG_MESSAGES - vars: - - name: ansible_persistent_log_messages -""" - -from io import BytesIO - -from ansible.errors import AnsibleConnectionFailure -from ansible.module_utils._text import to_bytes -from ansible.module_utils.six import PY3 -from ansible.module_utils.six.moves import cPickle -from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError -from ansible.module_utils.urls import open_url -from ansible.playbook.play_context import PlayContext -from ansible.plugins.loader import httpapi_loader -from ansible.plugins.connection import NetworkConnectionBase, ensure_connect - - -class Connection(NetworkConnectionBase): - '''Network API connection''' - - transport = 'httpapi' - has_pipelining = True - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - self._url = None - self._auth = None - - if self._network_os: - - self.httpapi = httpapi_loader.get(self._network_os, self) - if self.httpapi: - self._sub_plugin = {'type': 'httpapi', 'name': self.httpapi._load_name, 'obj': self.httpapi} - self.queue_message('vvvv', 'loaded API plugin %s from path %s for network_os %s' % - (self.httpapi._load_name, self.httpapi._original_path, self._network_os)) - else: - raise AnsibleConnectionFailure('unable to load API plugin for network_os %s' % self._network_os) - - else: - raise AnsibleConnectionFailure( - 'Unable to automatically determine host network os. Please ' - 'manually configure ansible_network_os value for this host' - ) - self.queue_message('log', 'network_os is set to %s' % self._network_os) - - def update_play_context(self, pc_data): - """Updates the play context information for the connection""" - pc_data = to_bytes(pc_data) - if PY3: - pc_data = cPickle.loads(pc_data, encoding='bytes') - else: - pc_data = cPickle.loads(pc_data) - play_context = PlayContext() - play_context.deserialize(pc_data) - - self.queue_message('vvvv', 'updating play_context for connection') - if self._play_context.become ^ play_context.become: - self.set_become(play_context) - if play_context.become is True: - self.queue_message('vvvv', 'authorizing connection') - else: - self.queue_message('vvvv', 'deauthorizing connection') - - self._play_context = play_context - - def _connect(self): - if not self.connected: - protocol = 'https' if self.get_option('use_ssl') else 'http' - host = self.get_option('host') - port = self.get_option('port') or (443 if protocol == 'https' else 80) - self._url = '%s://%s:%s' % (protocol, host, port) - - self.queue_message('vvv', "ESTABLISH HTTP(S) CONNECTFOR USER: %s TO %s" % - (self._play_context.remote_user, self._url)) - self.httpapi.set_become(self._play_context) - self._connected = True - - self.httpapi.login(self.get_option('remote_user'), self.get_option('password')) - - def close(self): - ''' - Close the active session to the device - ''' - # only close the connection if its connected. - if self._connected: - self.queue_message('vvvv', "closing http(s) connection to device") - self.logout() - - super(Connection, self).close() - - @ensure_connect - def send(self, path, data, **kwargs): - ''' - Sends the command to the device over api - ''' - url_kwargs = dict( - timeout=self.get_option('persistent_command_timeout'), - validate_certs=self.get_option('validate_certs'), - use_proxy=self.get_option("use_proxy"), - headers={}, - ) - url_kwargs.update(kwargs) - if self._auth: - # Avoid modifying passed-in headers - headers = dict(kwargs.get('headers', {})) - headers.update(self._auth) - url_kwargs['headers'] = headers - else: - url_kwargs['force_basic_auth'] = True - url_kwargs['url_username'] = self.get_option('remote_user') - url_kwargs['url_password'] = self.get_option('password') - - try: - url = self._url + path - self._log_messages("send url '%s' with data '%s' and kwargs '%s'" % (url, data, url_kwargs)) - response = open_url(url, data=data, **url_kwargs) - except HTTPError as exc: - is_handled = self.handle_httperror(exc) - if is_handled is True: - return self.send(path, data, **kwargs) - elif is_handled is False: - raise - else: - response = is_handled - except URLError as exc: - raise AnsibleConnectionFailure('Could not connect to {0}: {1}'.format(self._url + path, exc.reason)) - - response_buffer = BytesIO() - resp_data = response.read() - self._log_messages("received response: '%s'" % resp_data) - response_buffer.write(resp_data) - - # Try to assign a new auth token if one is given - self._auth = self.update_auth(response, response_buffer) or self._auth - - response_buffer.seek(0) - - return response, response_buffer diff --git a/lib/ansible/plugins/connection/napalm.py b/lib/ansible/plugins/connection/napalm.py deleted file mode 100644 index c7b13a7b5a1..00000000000 --- a/lib/ansible/plugins/connection/napalm.py +++ /dev/null @@ -1,195 +0,0 @@ -# (c) 2018 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - - -DOCUMENTATION = """ ---- -author: Ansible Networking Team -connection: napalm -short_description: Provides persistent connection using NAPALM -description: - - This connection plugin provides connectivity to network devices using - the NAPALM network device abstraction library. This library requires - certain features to be enabled on network devices depending on the - destination device operating system. The connection plugin requires - C(napalm) to be installed locally on the Ansible controller. -version_added: "2.8" -requirements: - - napalm -options: - host: - description: - - Specifies the remote device FQDN or IP address to establish the SSH - connection to. - default: inventory_hostname - vars: - - name: ansible_host - port: - type: int - description: - - Specifies the port on the remote device that listens for connections - when establishing the SSH connection. - default: 22 - ini: - - section: defaults - key: remote_port - env: - - name: ANSIBLE_REMOTE_PORT - vars: - - name: ansible_port - network_os: - description: - - Configures the device platform network operating system. This value is - used to load a napalm device abstraction. - vars: - - name: ansible_network_os - remote_user: - description: - - The username used to authenticate to the remote device when the SSH - connection is first established. If the remote_user is not specified, - the connection will use the username of the logged in user. - - Can be configured from the CLI via the C(--user) or C(-u) options. - ini: - - section: defaults - key: remote_user - env: - - name: ANSIBLE_REMOTE_USER - vars: - - name: ansible_user - password: - description: - - Configures the user password used to authenticate to the remote device - when first establishing the SSH connection. - vars: - - name: ansible_password - - name: ansible_ssh_pass - - name: ansible_ssh_password - private_key_file: - description: - - The private SSH key or certificate file used to authenticate to the - remote device when first establishing the SSH connection. - ini: - - section: defaults - key: private_key_file - env: - - name: ANSIBLE_PRIVATE_KEY_FILE - vars: - - name: ansible_private_key_file - timeout: - type: int - description: - - Sets the connection time, in seconds, for communicating with the - remote device. This timeout is used as the default timeout value for - commands when issuing a command to the network CLI. If the command - does not return in timeout seconds, an error is generated. - default: 120 - host_key_auto_add: - type: boolean - description: - - By default, Ansible will prompt the user before adding SSH keys to the - known hosts file. By enabling this option, unknown host keys will - automatically be added to the known hosts file. - - Be sure to fully understand the security implications of enabling this - option on production systems as it could create a security vulnerability. - default: False - ini: - - section: paramiko_connection - key: host_key_auto_add - env: - - name: ANSIBLE_HOST_KEY_AUTO_ADD - persistent_connect_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait when trying to - initially establish a persistent connection. If this value expires - before the connection to the remote device is completed, the connection - will fail. - default: 30 - ini: - - section: persistent_connection - key: connect_timeout - env: - - name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT - vars: - - name: ansible_connect_timeout - persistent_command_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait for a command to - return from the remote device. If this timer is exceeded before the - command returns, the connection plugin will raise an exception and - close. - default: 30 - ini: - - section: persistent_connection - key: command_timeout - env: - - name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT - vars: - - name: ansible_command_timeout -""" - -from ansible.errors import AnsibleConnectionFailure, AnsibleError -from ansible.plugins.connection import NetworkConnectionBase - -try: - from napalm import get_network_driver - from napalm.base import ModuleImportError - HAS_NAPALM = True -except ImportError: - HAS_NAPALM = False - - -class Connection(NetworkConnectionBase): - """Napalm connections""" - - transport = 'napalm' - has_pipelining = False - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - self.napalm = None - - def _connect(self): - if not HAS_NAPALM: - raise AnsibleError('The "napalm" python library is required to use the napalm connection type.\n') - - super(Connection, self)._connect() - - if not self.connected: - if not self._network_os: - raise AnsibleConnectionFailure( - 'Unable to automatically determine host network os. Please ' - 'manually configure ansible_network_os value for this host' - ) - self.queue_message('log', 'network_os is set to %s' % self._network_os) - - try: - driver = get_network_driver(self._network_os) - except ModuleImportError: - raise AnsibleConnectionFailure('Failed to import napalm driver for {0}'.format(self._network_os)) - - host = self.get_option('host') - self.napalm = driver( - hostname=host, - username=self.get_option('remote_user'), - password=self.get_option('password'), - timeout=self.get_option('persistent_command_timeout'), - ) - - self.napalm.open() - - self._sub_plugin = {'name': 'napalm', 'obj': self.napalm} - self.queue_message('vvvv', 'created napalm device for network_os %s' % self._network_os) - self._connected = True - - def close(self): - if self.napalm: - self.napalm.close() - self.napalm = None - - super(Connection, self).close() diff --git a/lib/ansible/plugins/connection/netconf.py b/lib/ansible/plugins/connection/netconf.py deleted file mode 100644 index 6fac6373229..00000000000 --- a/lib/ansible/plugins/connection/netconf.py +++ /dev/null @@ -1,337 +0,0 @@ -# (c) 2016 Red Hat Inc. -# (c) 2017 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = """ ---- -author: Ansible Networking Team -connection: netconf -short_description: Provides a persistent connection using the netconf protocol -description: - - This connection plugin provides a connection to remote devices over the - SSH NETCONF subsystem. This connection plugin is typically used by - network devices for sending and receiving RPC calls over NETCONF. - - Note this connection plugin requires ncclient to be installed on the - local Ansible controller. -version_added: "2.3" -requirements: - - ncclient -options: - host: - description: - - Specifies the remote device FQDN or IP address to establish the SSH - connection to. - default: inventory_hostname - vars: - - name: ansible_host - port: - type: int - description: - - Specifies the port on the remote device that listens for connections - when establishing the SSH connection. - default: 830 - ini: - - section: defaults - key: remote_port - env: - - name: ANSIBLE_REMOTE_PORT - vars: - - name: ansible_port - network_os: - description: - - Configures the device platform network operating system. This value is - used to load a device specific netconf plugin. If this option is not - configured (or set to C(auto)), then Ansible will attempt to guess the - correct network_os to use. - If it can not guess a network_os correctly it will use C(default). - vars: - - name: ansible_network_os - remote_user: - description: - - The username used to authenticate to the remote device when the SSH - connection is first established. If the remote_user is not specified, - the connection will use the username of the logged in user. - - Can be configured from the CLI via the C(--user) or C(-u) options. - ini: - - section: defaults - key: remote_user - env: - - name: ANSIBLE_REMOTE_USER - vars: - - name: ansible_user - password: - description: - - Configures the user password used to authenticate to the remote device - when first establishing the SSH connection. - vars: - - name: ansible_password - - name: ansible_ssh_pass - - name: ansible_ssh_password - - name: ansible_netconf_password - private_key_file: - description: - - The private SSH key or certificate file used to authenticate to the - remote device when first establishing the SSH connection. - ini: - - section: defaults - key: private_key_file - env: - - name: ANSIBLE_PRIVATE_KEY_FILE - vars: - - name: ansible_private_key_file - look_for_keys: - default: True - description: - - Enables looking for ssh keys in the usual locations for ssh keys (e.g. :file:`~/.ssh/id_*`). - env: - - name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS - ini: - - section: paramiko_connection - key: look_for_keys - type: boolean - host_key_checking: - description: 'Set this to "False" if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host' - type: boolean - default: True - env: - - name: ANSIBLE_HOST_KEY_CHECKING - - name: ANSIBLE_SSH_HOST_KEY_CHECKING - - name: ANSIBLE_NETCONF_HOST_KEY_CHECKING - ini: - - section: defaults - key: host_key_checking - - section: paramiko_connection - key: host_key_checking - vars: - - name: ansible_host_key_checking - - name: ansible_ssh_host_key_checking - - name: ansible_netconf_host_key_checking - persistent_connect_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait when trying to - initially establish a persistent connection. If this value expires - before the connection to the remote device is completed, the connection - will fail. - default: 30 - ini: - - section: persistent_connection - key: connect_timeout - env: - - name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT - vars: - - name: ansible_connect_timeout - persistent_command_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait for a command to - return from the remote device. If this timer is exceeded before the - command returns, the connection plugin will raise an exception and - close. - default: 30 - ini: - - section: persistent_connection - key: command_timeout - env: - - name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT - vars: - - name: ansible_command_timeout - netconf_ssh_config: - description: - - This variable is used to enable bastion/jump host with netconf connection. If set to - True the bastion/jump host ssh settings should be present in ~/.ssh/config file, - alternatively it can be set to custom ssh configuration file path to read the - bastion/jump host settings. - ini: - - section: netconf_connection - key: ssh_config - version_added: '2.7' - env: - - name: ANSIBLE_NETCONF_SSH_CONFIG - vars: - - name: ansible_netconf_ssh_config - version_added: '2.7' - persistent_log_messages: - type: boolean - description: - - This flag will enable logging the command executed and response received from - target device in the ansible log file. For this option to work 'log_path' ansible - configuration option is required to be set to a file path with write access. - - Be sure to fully understand the security implications of enabling this - option as it could create a security vulnerability by logging sensitive information in log file. - default: False - ini: - - section: persistent_connection - key: log_messages - env: - - name: ANSIBLE_PERSISTENT_LOG_MESSAGES - vars: - - name: ansible_persistent_log_messages -""" - -import os -import logging -import json - -from ansible.errors import AnsibleConnectionFailure, AnsibleError -from ansible.module_utils._text import to_bytes, to_native, to_text -from ansible.module_utils.basic import missing_required_lib -from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE, BOOLEANS_FALSE -from ansible.plugins.loader import netconf_loader -from ansible.plugins.connection import NetworkConnectionBase, ensure_connect - -try: - from ncclient import manager - from ncclient.operations import RPCError - from ncclient.transport.errors import SSHUnknownHostError - from ncclient.xml_ import to_ele, to_xml - HAS_NCCLIENT = True - NCCLIENT_IMP_ERR = None -except (ImportError, AttributeError) as err: # paramiko and gssapi are incompatible and raise AttributeError not ImportError - HAS_NCCLIENT = False - NCCLIENT_IMP_ERR = err - -logging.getLogger('ncclient').setLevel(logging.INFO) - - -class Connection(NetworkConnectionBase): - """NetConf connections""" - - transport = 'netconf' - has_pipelining = False - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - # If network_os is not specified then set the network os to auto - # This will be used to trigger the use of guess_network_os when connecting. - self._network_os = self._network_os or 'auto' - - self.netconf = netconf_loader.get(self._network_os, self) - if self.netconf: - self._sub_plugin = {'type': 'netconf', 'name': self.netconf._load_name, 'obj': self.netconf} - self.queue_message('vvvv', 'loaded netconf plugin %s from path %s for network_os %s' % - (self.netconf._load_name, self.netconf._original_path, self._network_os)) - else: - self.netconf = netconf_loader.get("default", self) - self._sub_plugin = {'type': 'netconf', 'name': 'default', 'obj': self.netconf} - self.queue_message('display', 'unable to load netconf plugin for network_os %s, falling back to default plugin' % self._network_os) - - self.queue_message('log', 'network_os is set to %s' % self._network_os) - self._manager = None - self.key_filename = None - self._ssh_config = None - - def exec_command(self, cmd, in_data=None, sudoable=True): - """Sends the request to the node and returns the reply - The method accepts two forms of request. The first form is as a byte - string that represents xml string be send over netconf session. - The second form is a json-rpc (2.0) byte string. - """ - if self._manager: - # to_ele operates on native strings - request = to_ele(to_native(cmd, errors='surrogate_or_strict')) - - if request is None: - return 'unable to parse request' - - try: - reply = self._manager.rpc(request) - except RPCError as exc: - error = self.internal_error(data=to_text(to_xml(exc.xml), errors='surrogate_or_strict')) - return json.dumps(error) - - return reply.data_xml - else: - return super(Connection, self).exec_command(cmd, in_data, sudoable) - - @property - @ensure_connect - def manager(self): - return self._manager - - def _connect(self): - if not HAS_NCCLIENT: - raise AnsibleError("%s: %s" % (missing_required_lib("ncclient"), to_native(NCCLIENT_IMP_ERR))) - - self.queue_message('log', 'ssh connection done, starting ncclient') - - allow_agent = True - if self._play_context.password is not None: - allow_agent = False - setattr(self._play_context, 'allow_agent', allow_agent) - - self.key_filename = self._play_context.private_key_file or self.get_option('private_key_file') - if self.key_filename: - self.key_filename = str(os.path.expanduser(self.key_filename)) - - self._ssh_config = self.get_option('netconf_ssh_config') - if self._ssh_config in BOOLEANS_TRUE: - self._ssh_config = True - elif self._ssh_config in BOOLEANS_FALSE: - self._ssh_config = None - - # Try to guess the network_os if the network_os is set to auto - if self._network_os == 'auto': - for cls in netconf_loader.all(class_only=True): - network_os = cls.guess_network_os(self) - if network_os: - self.queue_message('vvv', 'discovered network_os %s' % network_os) - self._network_os = network_os - - # If we have tried to detect the network_os but were unable to i.e. network_os is still 'auto' - # then use default as the network_os - - if self._network_os == 'auto': - # Network os not discovered. Set it to default - self.queue_message('vvv', 'Unable to discover network_os. Falling back to default.') - self._network_os = 'default' - try: - ncclient_device_handler = self.netconf.get_option('ncclient_device_handler') - except KeyError: - ncclient_device_handler = 'default' - self.queue_message('vvv', 'identified ncclient device handler: %s.' % ncclient_device_handler) - device_params = {'name': ncclient_device_handler} - - try: - port = self._play_context.port or 830 - self.queue_message('vvv', "ESTABLISH NETCONF SSH CONNECTION FOR USER: %s on PORT %s TO %s WITH SSH_CONFIG = %s" % - (self._play_context.remote_user, port, self._play_context.remote_addr, self._ssh_config)) - self._manager = manager.connect( - host=self._play_context.remote_addr, - port=port, - username=self._play_context.remote_user, - password=self._play_context.password, - key_filename=self.key_filename, - hostkey_verify=self.get_option('host_key_checking'), - look_for_keys=self.get_option('look_for_keys'), - device_params=device_params, - allow_agent=self._play_context.allow_agent, - timeout=self.get_option('persistent_connect_timeout'), - ssh_config=self._ssh_config - ) - - self._manager._timeout = self.get_option('persistent_command_timeout') - except SSHUnknownHostError as exc: - raise AnsibleConnectionFailure(to_native(exc)) - except ImportError: - raise AnsibleError("connection=netconf is not supported on {0}".format(self._network_os)) - - if not self._manager.connected: - return 1, b'', b'not connected' - - self.queue_message('log', 'ncclient manager object created successfully') - - self._connected = True - - super(Connection, self)._connect() - - return 0, to_bytes(self._manager.session_id, errors='surrogate_or_strict'), b'' - - def close(self): - if self._manager: - self._manager.close_session() - super(Connection, self).close() diff --git a/lib/ansible/plugins/connection/network_cli.py b/lib/ansible/plugins/connection/network_cli.py deleted file mode 100644 index 482e360a64b..00000000000 --- a/lib/ansible/plugins/connection/network_cli.py +++ /dev/null @@ -1,759 +0,0 @@ -# (c) 2016 Red Hat Inc. -# (c) 2017 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = """ ---- -author: Ansible Networking Team -connection: network_cli -short_description: Use network_cli to run command on network appliances -description: - - This connection plugin provides a connection to remote devices over the - SSH and implements a CLI shell. This connection plugin is typically used by - network devices for sending and receiving CLi commands to network devices. -version_added: "2.3" -options: - host: - description: - - Specifies the remote device FQDN or IP address to establish the SSH - connection to. - default: inventory_hostname - vars: - - name: ansible_host - port: - type: int - description: - - Specifies the port on the remote device that listens for connections - when establishing the SSH connection. - default: 22 - ini: - - section: defaults - key: remote_port - env: - - name: ANSIBLE_REMOTE_PORT - vars: - - name: ansible_port - network_os: - description: - - Configures the device platform network operating system. This value is - used to load the correct terminal and cliconf plugins to communicate - with the remote device. - vars: - - name: ansible_network_os - remote_user: - description: - - The username used to authenticate to the remote device when the SSH - connection is first established. If the remote_user is not specified, - the connection will use the username of the logged in user. - - Can be configured from the CLI via the C(--user) or C(-u) options. - ini: - - section: defaults - key: remote_user - env: - - name: ANSIBLE_REMOTE_USER - vars: - - name: ansible_user - password: - description: - - Configures the user password used to authenticate to the remote device - when first establishing the SSH connection. - vars: - - name: ansible_password - - name: ansible_ssh_pass - - name: ansible_ssh_password - private_key_file: - description: - - The private SSH key or certificate file used to authenticate to the - remote device when first establishing the SSH connection. - ini: - - section: defaults - key: private_key_file - env: - - name: ANSIBLE_PRIVATE_KEY_FILE - vars: - - name: ansible_private_key_file - become: - type: boolean - description: - - The become option will instruct the CLI session to attempt privilege - escalation on platforms that support it. Normally this means - transitioning from user mode to C(enable) mode in the CLI session. - If become is set to True and the remote device does not support - privilege escalation or the privilege has already been elevated, then - this option is silently ignored. - - Can be configured from the CLI via the C(--become) or C(-b) options. - default: False - ini: - - section: privilege_escalation - key: become - env: - - name: ANSIBLE_BECOME - vars: - - name: ansible_become - become_method: - description: - - This option allows the become method to be specified in for handling - privilege escalation. Typically the become_method value is set to - C(enable) but could be defined as other values. - default: sudo - ini: - - section: privilege_escalation - key: become_method - env: - - name: ANSIBLE_BECOME_METHOD - vars: - - name: ansible_become_method - host_key_auto_add: - type: boolean - description: - - By default, Ansible will prompt the user before adding SSH keys to the - known hosts file. Since persistent connections such as network_cli run - in background processes, the user will never be prompted. By enabling - this option, unknown host keys will automatically be added to the - known hosts file. - - Be sure to fully understand the security implications of enabling this - option on production systems as it could create a security vulnerability. - default: False - ini: - - section: paramiko_connection - key: host_key_auto_add - env: - - name: ANSIBLE_HOST_KEY_AUTO_ADD - persistent_connect_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait when trying to - initially establish a persistent connection. If this value expires - before the connection to the remote device is completed, the connection - will fail. - default: 30 - ini: - - section: persistent_connection - key: connect_timeout - env: - - name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT - vars: - - name: ansible_connect_timeout - persistent_command_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait for a command to - return from the remote device. If this timer is exceeded before the - command returns, the connection plugin will raise an exception and - close. - default: 30 - ini: - - section: persistent_connection - key: command_timeout - env: - - name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT - vars: - - name: ansible_command_timeout - persistent_buffer_read_timeout: - type: float - description: - - Configures, in seconds, the amount of time to wait for the data to be read - from Paramiko channel after the command prompt is matched. This timeout - value ensures that command prompt matched is correct and there is no more data - left to be received from remote host. - default: 0.1 - ini: - - section: persistent_connection - key: buffer_read_timeout - env: - - name: ANSIBLE_PERSISTENT_BUFFER_READ_TIMEOUT - vars: - - name: ansible_buffer_read_timeout - persistent_log_messages: - type: boolean - description: - - This flag will enable logging the command executed and response received from - target device in the ansible log file. For this option to work 'log_path' ansible - configuration option is required to be set to a file path with write access. - - Be sure to fully understand the security implications of enabling this - option as it could create a security vulnerability by logging sensitive information in log file. - default: False - ini: - - section: persistent_connection - key: log_messages - env: - - name: ANSIBLE_PERSISTENT_LOG_MESSAGES - vars: - - name: ansible_persistent_log_messages - terminal_stdout_re: - type: list - elements: dict - version_added: '2.9' - description: - - A single regex pattern or a sequence of patterns along with optional flags - to match the command prompt from the received response chunk. This option - accepts C(pattern) and C(flags) keys. The value of C(pattern) is a python - regex pattern to match the response and the value of C(flags) is the value - accepted by I(flags) argument of I(re.compile) python method to control - the way regex is matched with the response, for example I('re.I'). - vars: - - name: ansible_terminal_stdout_re - terminal_stderr_re: - type: list - elements: dict - version_added: '2.9' - description: - - This option provides the regex pattern and optional flags to match the - error string from the received response chunk. This option - accepts C(pattern) and C(flags) keys. The value of C(pattern) is a python - regex pattern to match the response and the value of C(flags) is the value - accepted by I(flags) argument of I(re.compile) python method to control - the way regex is matched with the response, for example I('re.I'). - vars: - - name: ansible_terminal_stderr_re - terminal_initial_prompt: - type: list - version_added: '2.9' - description: - - A single regex pattern or a sequence of patterns to evaluate the expected - prompt at the time of initial login to the remote host. - vars: - - name: ansible_terminal_initial_prompt - terminal_initial_answer: - type: list - version_added: '2.9' - description: - - The answer to reply with if the C(terminal_initial_prompt) is matched. The value can be a single answer - or a list of answers for multiple terminal_initial_prompt. In case the login menu has - multiple prompts the sequence of the prompt and excepted answer should be in same order and the value - of I(terminal_prompt_checkall) should be set to I(True) if all the values in C(terminal_initial_prompt) are - expected to be matched and set to I(False) if any one login prompt is to be matched. - vars: - - name: ansible_terminal_initial_answer - terminal_initial_prompt_checkall: - type: boolean - version_added: '2.9' - description: - - By default the value is set to I(False) and any one of the prompts mentioned in C(terminal_initial_prompt) - option is matched it won't check for other prompts. When set to I(True) it will check for all the prompts - mentioned in C(terminal_initial_prompt) option in the given order and all the prompts - should be received from remote host if not it will result in timeout. - default: False - vars: - - name: ansible_terminal_initial_prompt_checkall - terminal_inital_prompt_newline: - type: boolean - version_added: '2.9' - description: - - This boolean flag, that when set to I(True) will send newline in the response if any of values - in I(terminal_initial_prompt) is matched. - default: True - vars: - - name: ansible_terminal_initial_prompt_newline - network_cli_retries: - description: - - Number of attempts to connect to remote host. The delay time between the retires increases after - every attempt by power of 2 in seconds till either the maximum attempts are exhausted or any of the - C(persistent_command_timeout) or C(persistent_connect_timeout) timers are triggered. - default: 3 - version_added: '2.9' - type: integer - env: - - name: ANSIBLE_NETWORK_CLI_RETRIES - ini: - - section: persistent_connection - key: network_cli_retries - vars: - - name: ansible_network_cli_retries -""" - -from functools import wraps -import getpass -import json -import logging -import re -import os -import signal -import socket -import time -import traceback -from io import BytesIO - -from ansible.errors import AnsibleConnectionFailure -from ansible.module_utils.six import PY3 -from ansible.module_utils.six.moves import cPickle -from ansible.module_utils.network.common.utils import to_list -from ansible.module_utils._text import to_bytes, to_text -from ansible.playbook.play_context import PlayContext -from ansible.plugins.connection import NetworkConnectionBase -from ansible.plugins.loader import cliconf_loader, terminal_loader, connection_loader - - -def ensure_connect(func): - @wraps(func) - def wrapped(self, *args, **kwargs): - if not self._connected: - self._connect() - self.update_cli_prompt_context() - return func(self, *args, **kwargs) - return wrapped - - -class AnsibleCmdRespRecv(Exception): - pass - - -class Connection(NetworkConnectionBase): - ''' CLI (shell) SSH connections on Paramiko ''' - - transport = 'network_cli' - has_pipelining = True - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - self._ssh_shell = None - - self._matched_prompt = None - self._matched_cmd_prompt = None - self._matched_pattern = None - self._last_response = None - self._history = list() - self._command_response = None - self._last_recv_window = None - - self._terminal = None - self.cliconf = None - self._paramiko_conn = None - - # Managing prompt context - self._check_prompt = False - self._task_uuid = to_text(kwargs.get('task_uuid', '')) - - if self._play_context.verbosity > 3: - logging.getLogger('paramiko').setLevel(logging.DEBUG) - - if self._network_os: - self._terminal = terminal_loader.get(self._network_os, self) - if not self._terminal: - raise AnsibleConnectionFailure('network os %s is not supported' % self._network_os) - - self.cliconf = cliconf_loader.get(self._network_os, self) - if self.cliconf: - self._sub_plugin = {'type': 'cliconf', 'name': self.cliconf._load_name, 'obj': self.cliconf} - self.queue_message('vvvv', 'loaded cliconf plugin %s from path %s for network_os %s' % - (self.cliconf._load_name, self.cliconf._original_path, self._network_os)) - else: - self.queue_message('vvvv', 'unable to load cliconf for network_os %s' % self._network_os) - else: - raise AnsibleConnectionFailure( - 'Unable to automatically determine host network os. Please ' - 'manually configure ansible_network_os value for this host' - ) - self.queue_message('log', 'network_os is set to %s' % self._network_os) - - @property - def paramiko_conn(self): - if self._paramiko_conn is None: - self._paramiko_conn = connection_loader.get('paramiko', self._play_context, '/dev/null') - self._paramiko_conn.set_options(direct={'look_for_keys': not bool(self._play_context.password and not self._play_context.private_key_file)}) - return self._paramiko_conn - - def _get_log_channel(self): - name = "p=%s u=%s | " % (os.getpid(), getpass.getuser()) - name += "paramiko [%s]" % self._play_context.remote_addr - return name - - @ensure_connect - def get_prompt(self): - """Returns the current prompt from the device""" - return self._matched_prompt - - def exec_command(self, cmd, in_data=None, sudoable=True): - # this try..except block is just to handle the transition to supporting - # network_cli as a toplevel connection. Once connection=local is gone, - # this block can be removed as well and all calls passed directly to - # the local connection - if self._ssh_shell: - try: - cmd = json.loads(to_text(cmd, errors='surrogate_or_strict')) - kwargs = {'command': to_bytes(cmd['command'], errors='surrogate_or_strict')} - for key in ('prompt', 'answer', 'sendonly', 'newline', 'prompt_retry_check'): - if cmd.get(key) is True or cmd.get(key) is False: - kwargs[key] = cmd[key] - elif cmd.get(key) is not None: - kwargs[key] = to_bytes(cmd[key], errors='surrogate_or_strict') - return self.send(**kwargs) - except ValueError: - cmd = to_bytes(cmd, errors='surrogate_or_strict') - return self.send(command=cmd) - - else: - return super(Connection, self).exec_command(cmd, in_data, sudoable) - - def update_play_context(self, pc_data): - """Updates the play context information for the connection""" - pc_data = to_bytes(pc_data) - if PY3: - pc_data = cPickle.loads(pc_data, encoding='bytes') - else: - pc_data = cPickle.loads(pc_data) - play_context = PlayContext() - play_context.deserialize(pc_data) - - self.queue_message('vvvv', 'updating play_context for connection') - if self._play_context.become ^ play_context.become: - if play_context.become is True: - auth_pass = play_context.become_pass - self._terminal.on_become(passwd=auth_pass) - self.queue_message('vvvv', 'authorizing connection') - else: - self._terminal.on_unbecome() - self.queue_message('vvvv', 'deauthorizing connection') - - self._play_context = play_context - - if hasattr(self, 'reset_history'): - self.reset_history() - if hasattr(self, 'disable_response_logging'): - self.disable_response_logging() - - def set_check_prompt(self, task_uuid): - self._check_prompt = task_uuid - - def update_cli_prompt_context(self): - # set cli prompt context at the start of new task run only - if self._check_prompt and self._task_uuid != self._check_prompt: - self._task_uuid, self._check_prompt = self._check_prompt, False - self.set_cli_prompt_context() - - def _connect(self): - ''' - Connects to the remote device and starts the terminal - ''' - if not self.connected: - self.paramiko_conn._set_log_channel(self._get_log_channel()) - self.paramiko_conn.force_persistence = self.force_persistence - - command_timeout = self.get_option('persistent_command_timeout') - max_pause = min([self.get_option('persistent_connect_timeout'), command_timeout]) - retries = self.get_option('network_cli_retries') - total_pause = 0 - - for attempt in range(retries + 1): - try: - ssh = self.paramiko_conn._connect() - break - except Exception as e: - pause = 2 ** (attempt + 1) - if attempt == retries or total_pause >= max_pause: - raise AnsibleConnectionFailure(to_text(e, errors='surrogate_or_strict')) - else: - msg = (u"network_cli_retry: attempt: %d, caught exception(%s), " - u"pausing for %d seconds" % (attempt + 1, to_text(e, errors='surrogate_or_strict'), pause)) - - self.queue_message('vv', msg) - time.sleep(pause) - total_pause += pause - continue - - self.queue_message('vvvv', 'ssh connection done, setting terminal') - self._connected = True - - self._ssh_shell = ssh.ssh.invoke_shell() - self._ssh_shell.settimeout(command_timeout) - - self.queue_message('vvvv', 'loaded terminal plugin for network_os %s' % self._network_os) - - terminal_initial_prompt = self.get_option('terminal_initial_prompt') or self._terminal.terminal_initial_prompt - terminal_initial_answer = self.get_option('terminal_initial_answer') or self._terminal.terminal_initial_answer - newline = self.get_option('terminal_inital_prompt_newline') or self._terminal.terminal_inital_prompt_newline - check_all = self.get_option('terminal_initial_prompt_checkall') or False - - self.receive(prompts=terminal_initial_prompt, answer=terminal_initial_answer, newline=newline, check_all=check_all) - - if self._play_context.become: - self.queue_message('vvvv', 'firing event: on_become') - auth_pass = self._play_context.become_pass - self._terminal.on_become(passwd=auth_pass) - - self.queue_message('vvvv', 'firing event: on_open_shell()') - self._terminal.on_open_shell() - - self.queue_message('vvvv', 'ssh connection has completed successfully') - - return self - - def close(self): - ''' - Close the active connection to the device - ''' - # only close the connection if its connected. - if self._connected: - self.queue_message('debug', "closing ssh connection to device") - if self._ssh_shell: - self.queue_message('debug', "firing event: on_close_shell()") - self._terminal.on_close_shell() - self._ssh_shell.close() - self._ssh_shell = None - self.queue_message('debug', "cli session is now closed") - - self.paramiko_conn.close() - self._paramiko_conn = None - self.queue_message('debug', "ssh connection has been closed successfully") - super(Connection, self).close() - - def receive(self, command=None, prompts=None, answer=None, newline=True, prompt_retry_check=False, check_all=False): - ''' - Handles receiving of output from command - ''' - self._matched_prompt = None - self._matched_cmd_prompt = None - recv = BytesIO() - handled = False - command_prompt_matched = False - matched_prompt_window = window_count = 0 - - # set terminal regex values for command prompt and errors in response - self._terminal_stderr_re = self._get_terminal_std_re('terminal_stderr_re') - self._terminal_stdout_re = self._get_terminal_std_re('terminal_stdout_re') - - cache_socket_timeout = self._ssh_shell.gettimeout() - command_timeout = self.get_option('persistent_command_timeout') - self._validate_timeout_value(command_timeout, "persistent_command_timeout") - if cache_socket_timeout != command_timeout: - self._ssh_shell.settimeout(command_timeout) - - buffer_read_timeout = self.get_option('persistent_buffer_read_timeout') - self._validate_timeout_value(buffer_read_timeout, "persistent_buffer_read_timeout") - - self._log_messages("command: %s" % command) - while True: - if command_prompt_matched: - try: - signal.signal(signal.SIGALRM, self._handle_buffer_read_timeout) - signal.setitimer(signal.ITIMER_REAL, buffer_read_timeout) - data = self._ssh_shell.recv(256) - signal.alarm(0) - self._log_messages("response-%s: %s" % (window_count + 1, data)) - # if data is still received on channel it indicates the prompt string - # is wrongly matched in between response chunks, continue to read - # remaining response. - command_prompt_matched = False - - # restart command_timeout timer - signal.signal(signal.SIGALRM, self._handle_command_timeout) - signal.alarm(command_timeout) - - except AnsibleCmdRespRecv: - # reset socket timeout to global timeout - self._ssh_shell.settimeout(cache_socket_timeout) - return self._command_response - else: - data = self._ssh_shell.recv(256) - self._log_messages("response-%s: %s" % (window_count + 1, data)) - # when a channel stream is closed, received data will be empty - if not data: - break - - recv.write(data) - offset = recv.tell() - 256 if recv.tell() > 256 else 0 - recv.seek(offset) - - window = self._strip(recv.read()) - self._last_recv_window = window - window_count += 1 - - if prompts and not handled: - handled = self._handle_prompt(window, prompts, answer, newline, False, check_all) - matched_prompt_window = window_count - elif prompts and handled and prompt_retry_check and matched_prompt_window + 1 == window_count: - # check again even when handled, if same prompt repeats in next window - # (like in the case of a wrong enable password, etc) indicates - # value of answer is wrong, report this as error. - if self._handle_prompt(window, prompts, answer, newline, prompt_retry_check, check_all): - raise AnsibleConnectionFailure("For matched prompt '%s', answer is not valid" % self._matched_cmd_prompt) - - if self._find_prompt(window): - self._last_response = recv.getvalue() - resp = self._strip(self._last_response) - self._command_response = self._sanitize(resp, command) - if buffer_read_timeout == 0.0: - # reset socket timeout to global timeout - self._ssh_shell.settimeout(cache_socket_timeout) - return self._command_response - else: - command_prompt_matched = True - - @ensure_connect - def send(self, command, prompt=None, answer=None, newline=True, sendonly=False, prompt_retry_check=False, check_all=False): - ''' - Sends the command to the device in the opened shell - ''' - if check_all: - prompt_len = len(to_list(prompt)) - answer_len = len(to_list(answer)) - if prompt_len != answer_len: - raise AnsibleConnectionFailure("Number of prompts (%s) is not same as that of answers (%s)" % (prompt_len, answer_len)) - try: - cmd = b'%s\r' % command - self._history.append(cmd) - self._ssh_shell.sendall(cmd) - self._log_messages('send command: %s' % cmd) - if sendonly: - return - response = self.receive(command, prompt, answer, newline, prompt_retry_check, check_all) - return to_text(response, errors='surrogate_then_replace') - except (socket.timeout, AttributeError): - self.queue_message('error', traceback.format_exc()) - raise AnsibleConnectionFailure("timeout value %s seconds reached while trying to send command: %s" - % (self._ssh_shell.gettimeout(), command.strip())) - - def _handle_buffer_read_timeout(self, signum, frame): - self.queue_message('vvvv', "Response received, triggered 'persistent_buffer_read_timeout' timer of %s seconds" % - self.get_option('persistent_buffer_read_timeout')) - raise AnsibleCmdRespRecv() - - def _handle_command_timeout(self, signum, frame): - msg = 'command timeout triggered, timeout value is %s secs.\nSee the timeout setting options in the Network Debug and Troubleshooting Guide.'\ - % self.get_option('persistent_command_timeout') - self.queue_message('log', msg) - raise AnsibleConnectionFailure(msg) - - def _strip(self, data): - ''' - Removes ANSI codes from device response - ''' - for regex in self._terminal.ansi_re: - data = regex.sub(b'', data) - return data - - def _handle_prompt(self, resp, prompts, answer, newline, prompt_retry_check=False, check_all=False): - ''' - Matches the command prompt and responds - - :arg resp: Byte string containing the raw response from the remote - :arg prompts: Sequence of byte strings that we consider prompts for input - :arg answer: Sequence of Byte string to send back to the remote if we find a prompt. - A carriage return is automatically appended to this string. - :param prompt_retry_check: Bool value for trying to detect more prompts - :param check_all: Bool value to indicate if all the values in prompt sequence should be matched or any one of - given prompt. - :returns: True if a prompt was found in ``resp``. If check_all is True - will True only after all the prompt in the prompts list are matched. False otherwise. - ''' - single_prompt = False - if not isinstance(prompts, list): - prompts = [prompts] - single_prompt = True - if not isinstance(answer, list): - answer = [answer] - prompts_regex = [re.compile(to_bytes(r), re.I) for r in prompts] - for index, regex in enumerate(prompts_regex): - match = regex.search(resp) - if match: - self._matched_cmd_prompt = match.group() - self._log_messages("matched command prompt: %s" % self._matched_cmd_prompt) - - # if prompt_retry_check is enabled to check if same prompt is - # repeated don't send answer again. - if not prompt_retry_check: - prompt_answer = answer[index] if len(answer) > index else answer[0] - self._ssh_shell.sendall(b'%s' % prompt_answer) - if newline: - self._ssh_shell.sendall(b'\r') - prompt_answer += b'\r' - self._log_messages("matched command prompt answer: %s" % prompt_answer) - if check_all and prompts and not single_prompt: - prompts.pop(0) - answer.pop(0) - return False - return True - return False - - def _sanitize(self, resp, command=None): - ''' - Removes elements from the response before returning to the caller - ''' - cleaned = [] - for line in resp.splitlines(): - if command and line.strip() == command.strip(): - continue - - for prompt in self._matched_prompt.strip().splitlines(): - if prompt.strip() in line: - break - else: - cleaned.append(line) - return b'\n'.join(cleaned).strip() - - def _find_prompt(self, response): - '''Searches the buffered response for a matching command prompt - ''' - errored_response = None - is_error_message = False - - for regex in self._terminal_stderr_re: - if regex.search(response): - is_error_message = True - - # Check if error response ends with command prompt if not - # receive it buffered prompt - for regex in self._terminal_stdout_re: - match = regex.search(response) - if match: - errored_response = response - self._matched_pattern = regex.pattern - self._matched_prompt = match.group() - self._log_messages("matched error regex '%s' from response '%s'" % (self._matched_pattern, errored_response)) - break - - if not is_error_message: - for regex in self._terminal_stdout_re: - match = regex.search(response) - if match: - self._matched_pattern = regex.pattern - self._matched_prompt = match.group() - self._log_messages("matched cli prompt '%s' with regex '%s' from response '%s'" % (self._matched_prompt, self._matched_pattern, response)) - if not errored_response: - return True - - if errored_response: - raise AnsibleConnectionFailure(errored_response) - - return False - - def _validate_timeout_value(self, timeout, timer_name): - if timeout < 0: - raise AnsibleConnectionFailure("'%s' timer value '%s' is invalid, value should be greater than or equal to zero." % (timer_name, timeout)) - - def transport_test(self, connect_timeout): - """This method enables wait_for_connection to work. - - As it is used by wait_for_connection, it is called by that module's action plugin, - which is on the controller process, which means that nothing done on this instance - should impact the actual persistent connection... this check is for informational - purposes only and should be properly cleaned up. - """ - - # Force a fresh connect if for some reason we have connected before. - self.close() - self._connect() - self.close() - - def _get_terminal_std_re(self, option): - terminal_std_option = self.get_option(option) - terminal_std_re = [] - - if terminal_std_option: - for item in terminal_std_option: - if "pattern" not in item: - raise AnsibleConnectionFailure("'pattern' is a required key for option '%s'," - " received option value is %s" % (option, item)) - pattern = br"%s" % to_bytes(item['pattern']) - flag = item.get('flags', 0) - if flag: - flag = getattr(re, flag.split('.')[1]) - terminal_std_re.append(re.compile(pattern, flag)) - else: - # To maintain backward compatibility - terminal_std_re = getattr(self._terminal, option) - - return terminal_std_re diff --git a/lib/ansible/plugins/connection/persistent.py b/lib/ansible/plugins/connection/persistent.py deleted file mode 100644 index 626d9dd433f..00000000000 --- a/lib/ansible/plugins/connection/persistent.py +++ /dev/null @@ -1,82 +0,0 @@ -# 2017 Red Hat Inc. -# (c) 2017 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = """ -author: Ansible Core Team -connection: persistent -short_description: Use a persistent unix socket for connection -description: - - This is a helper plugin to allow making other connections persistent. -version_added: "2.3" -options: - persistent_command_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait for a command to - return from the remote device. If this timer is exceeded before the - command returns, the connection plugin will raise an exception and - close - default: 10 - ini: - - section: persistent_connection - key: command_timeout - env: - - name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT - vars: - - name: ansible_command_timeout -""" -from ansible.executor.task_executor import start_connection -from ansible.plugins.connection import ConnectionBase -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection as SocketConnection -from ansible.utils.display import Display - -display = Display() - - -class Connection(ConnectionBase): - ''' Local based connections ''' - - transport = 'persistent' - has_pipelining = False - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - self._task_uuid = to_text(kwargs.get('task_uuid', '')) - - def _connect(self): - self._connected = True - return self - - def exec_command(self, cmd, in_data=None, sudoable=True): - display.vvvv('exec_command(), socket_path=%s' % self.socket_path, host=self._play_context.remote_addr) - connection = SocketConnection(self.socket_path) - out = connection.exec_command(cmd, in_data=in_data, sudoable=sudoable) - return 0, out, '' - - def put_file(self, in_path, out_path): - pass - - def fetch_file(self, in_path, out_path): - pass - - def close(self): - self._connected = False - - def run(self): - """Returns the path of the persistent connection socket. - - Attempts to ensure (within playcontext.timeout seconds) that the - socket path exists. If the path exists (or the timeout has expired), - returns the socket path. - """ - display.vvvv('starting connection from persistent connection plugin', host=self._play_context.remote_addr) - variables = {'ansible_command_timeout': self.get_option('persistent_command_timeout')} - socket_path = start_connection(self._play_context, variables, self._task_uuid) - display.vvvv('local domain socket path is %s' % socket_path, host=self._play_context.remote_addr) - setattr(self, '_socket_path', socket_path) - return socket_path diff --git a/lib/ansible/plugins/doc_fragments/netconf.py b/lib/ansible/plugins/doc_fragments/netconf.py deleted file mode 100644 index f2b90201e46..00000000000 --- a/lib/ansible/plugins/doc_fragments/netconf.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- - -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - - -class ModuleDocFragment(object): - - # Standard files documentation fragment - DOCUMENTATION = r''' -options: - host: - description: - - Specifies the DNS host name or address for connecting to the remote - device over the specified transport. The value of host is used as - the destination address for the transport. - type: str - required: true - port: - description: - - Specifies the port to use when building the connection to the remote - device. The port value will default to port 830. - type: int - default: 830 - username: - description: - - Configures the username to use to authenticate the connection to - the remote device. This value is used to authenticate - the SSH session. If the value is not specified in the task, the - value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead. - type: str - password: - description: - - Specifies the password to use to authenticate the connection to - the remote device. This value is used to authenticate - the SSH session. If the value is not specified in the task, the - value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead. - type: str - timeout: - description: - - Specifies the timeout in seconds for communicating with the network device - for either connecting or sending commands. If the timeout is - exceeded before the operation is completed, the module will error. - type: int - default: 10 - ssh_keyfile: - description: - - Specifies the SSH key to use to authenticate the connection to - the remote device. This value is the path to the key - used to authenticate the SSH session. If the value is not specified in - the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE) - will be used instead. - type: path - hostkey_verify: - description: - - If set to C(yes), the ssh host key of the device must match a ssh key present on - the host if set to C(no), the ssh host key of the device is not checked. - type: bool - default: yes - look_for_keys: - description: - - Enables looking in the usual locations for the ssh keys (e.g. :file:`~/.ssh/id_*`) - type: bool - default: yes -notes: - - For information on using netconf see the :ref:`Platform Options guide using Netconf` - - For more information on using Ansible to manage network devices see the :ref:`Ansible Network Guide ` -''' diff --git a/lib/ansible/plugins/doc_fragments/network_agnostic.py b/lib/ansible/plugins/doc_fragments/network_agnostic.py deleted file mode 100644 index 043aceb8e06..00000000000 --- a/lib/ansible/plugins/doc_fragments/network_agnostic.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: (c) 2019 Ansible, Inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - - -class ModuleDocFragment(object): - - # Standard files documentation fragment - DOCUMENTATION = r''' -options: {} -notes: - - This module is supported on C(ansible_network_os) network platforms. See - the :ref:`Network Platform Options ` for details. -''' diff --git a/lib/ansible/plugins/filter/ipaddr.py b/lib/ansible/plugins/filter/ipaddr.py deleted file mode 100644 index bcc7abdc957..00000000000 --- a/lib/ansible/plugins/filter/ipaddr.py +++ /dev/null @@ -1,1142 +0,0 @@ -# (c) 2014, Maciej Delmanowski -# -# 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from functools import partial -import types - -try: - import netaddr -except ImportError: - # in this case, we'll make the filters return error messages (see bottom) - netaddr = None -else: - class mac_linux(netaddr.mac_unix): - pass - mac_linux.word_fmt = '%.2x' - -from ansible import errors - - -# ---- IP address and network query helpers ---- -def _empty_ipaddr_query(v, vtype): - # We don't have any query to process, so just check what type the user - # expects, and return the IP address in a correct format - if v: - if vtype == 'address': - return str(v.ip) - elif vtype == 'network': - return str(v) - - -def _first_last(v): - if v.size == 2: - first_usable = int(netaddr.IPAddress(v.first)) - last_usable = int(netaddr.IPAddress(v.last)) - return first_usable, last_usable - elif v.size > 1: - first_usable = int(netaddr.IPAddress(v.first + 1)) - last_usable = int(netaddr.IPAddress(v.last - 1)) - return first_usable, last_usable - - -def _6to4_query(v, vtype, value): - if v.version == 4: - - if v.size == 1: - ipconv = str(v.ip) - elif v.size > 1: - if v.ip != v.network: - ipconv = str(v.ip) - else: - ipconv = False - - if ipaddr(ipconv, 'public'): - numbers = list(map(int, ipconv.split('.'))) - - try: - return '2002:{:02x}{:02x}:{:02x}{:02x}::1/48'.format(*numbers) - except Exception: - return False - - elif v.version == 6: - if vtype == 'address': - if ipaddr(str(v), '2002::/16'): - return value - elif vtype == 'network': - if v.ip != v.network: - if ipaddr(str(v.ip), '2002::/16'): - return value - else: - return False - - -def _ip_query(v): - if v.size == 1: - return str(v.ip) - if v.size > 1: - # /31 networks in netaddr have no broadcast address - if v.ip != v.network or not v.broadcast: - return str(v.ip) - - -def _gateway_query(v): - if v.size > 1: - if v.ip != v.network: - return str(v.ip) + '/' + str(v.prefixlen) - - -def _address_prefix_query(v): - if v.size > 1: - if v.ip != v.network: - return str(v.ip) + '/' + str(v.prefixlen) - - -def _bool_ipaddr_query(v): - if v: - return True - - -def _broadcast_query(v): - if v.size > 2: - return str(v.broadcast) - - -def _cidr_query(v): - return str(v) - - -def _cidr_lookup_query(v, iplist, value): - try: - if v in iplist: - return value - except Exception: - return False - - -def _first_usable_query(v, vtype): - if vtype == 'address': - "Does it make sense to raise an error" - raise errors.AnsibleFilterError('Not a network address') - elif vtype == 'network': - if v.size == 2: - return str(netaddr.IPAddress(int(v.network))) - elif v.size > 1: - return str(netaddr.IPAddress(int(v.network) + 1)) - - -def _host_query(v): - if v.size == 1: - return str(v) - elif v.size > 1: - if v.ip != v.network: - return str(v.ip) + '/' + str(v.prefixlen) - - -def _hostmask_query(v): - return str(v.hostmask) - - -def _int_query(v, vtype): - if vtype == 'address': - return int(v.ip) - elif vtype == 'network': - return str(int(v.ip)) + '/' + str(int(v.prefixlen)) - - -def _ip_prefix_query(v): - if v.size == 2: - return str(v.ip) + '/' + str(v.prefixlen) - elif v.size > 1: - if v.ip != v.network: - return str(v.ip) + '/' + str(v.prefixlen) - - -def _ip_netmask_query(v): - if v.size == 2: - return str(v.ip) + ' ' + str(v.netmask) - elif v.size > 1: - if v.ip != v.network: - return str(v.ip) + ' ' + str(v.netmask) - - -''' -def _ip_wildcard_query(v): - if v.size == 2: - return str(v.ip) + ' ' + str(v.hostmask) - elif v.size > 1: - if v.ip != v.network: - return str(v.ip) + ' ' + str(v.hostmask) -''' - - -def _ipv4_query(v, value): - if v.version == 6: - try: - return str(v.ipv4()) - except Exception: - return False - else: - return value - - -def _ipv6_query(v, value): - if v.version == 4: - return str(v.ipv6()) - else: - return value - - -def _last_usable_query(v, vtype): - if vtype == 'address': - "Does it make sense to raise an error" - raise errors.AnsibleFilterError('Not a network address') - elif vtype == 'network': - if v.size > 1: - first_usable, last_usable = _first_last(v) - return str(netaddr.IPAddress(last_usable)) - - -def _link_local_query(v, value): - v_ip = netaddr.IPAddress(str(v.ip)) - if v.version == 4: - if ipaddr(str(v_ip), '169.254.0.0/24'): - return value - - elif v.version == 6: - if ipaddr(str(v_ip), 'fe80::/10'): - return value - - -def _loopback_query(v, value): - v_ip = netaddr.IPAddress(str(v.ip)) - if v_ip.is_loopback(): - return value - - -def _multicast_query(v, value): - if v.is_multicast(): - return value - - -def _net_query(v): - if v.size > 1: - if v.ip == v.network: - return str(v.network) + '/' + str(v.prefixlen) - - -def _netmask_query(v): - return str(v.netmask) - - -def _network_query(v): - '''Return the network of a given IP or subnet''' - return str(v.network) - - -def _network_id_query(v): - '''Return the network of a given IP or subnet''' - return str(v.network) - - -def _network_netmask_query(v): - return str(v.network) + ' ' + str(v.netmask) - - -def _network_wildcard_query(v): - return str(v.network) + ' ' + str(v.hostmask) - - -def _next_usable_query(v, vtype): - if vtype == 'address': - "Does it make sense to raise an error" - raise errors.AnsibleFilterError('Not a network address') - elif vtype == 'network': - if v.size > 1: - first_usable, last_usable = _first_last(v) - next_ip = int(netaddr.IPAddress(int(v.ip) + 1)) - if next_ip >= first_usable and next_ip <= last_usable: - return str(netaddr.IPAddress(int(v.ip) + 1)) - - -def _peer_query(v, vtype): - if vtype == 'address': - raise errors.AnsibleFilterError("Not a network address") - elif vtype == 'network': - if v.size == 2: - return str(netaddr.IPAddress(int(v.ip) ^ 1)) - if v.size == 4: - if int(v.ip) % 4 == 0: - raise errors.AnsibleFilterError("Network address of /30 has no peer") - if int(v.ip) % 4 == 3: - raise errors.AnsibleFilterError("Broadcast address of /30 has no peer") - return str(netaddr.IPAddress(int(v.ip) ^ 3)) - raise errors.AnsibleFilterError("Not a point-to-point network") - - -def _prefix_query(v): - return int(v.prefixlen) - - -def _previous_usable_query(v, vtype): - if vtype == 'address': - "Does it make sense to raise an error" - raise errors.AnsibleFilterError('Not a network address') - elif vtype == 'network': - if v.size > 1: - first_usable, last_usable = _first_last(v) - previous_ip = int(netaddr.IPAddress(int(v.ip) - 1)) - if previous_ip >= first_usable and previous_ip <= last_usable: - return str(netaddr.IPAddress(int(v.ip) - 1)) - - -def _private_query(v, value): - if v.is_private(): - return value - - -def _public_query(v, value): - v_ip = netaddr.IPAddress(str(v.ip)) - if (v_ip.is_unicast() and not v_ip.is_private() and - not v_ip.is_loopback() and not v_ip.is_netmask() and - not v_ip.is_hostmask()): - return value - - -def _range_usable_query(v, vtype): - if vtype == 'address': - "Does it make sense to raise an error" - raise errors.AnsibleFilterError('Not a network address') - elif vtype == 'network': - if v.size > 1: - first_usable, last_usable = _first_last(v) - first_usable = str(netaddr.IPAddress(first_usable)) - last_usable = str(netaddr.IPAddress(last_usable)) - return "{0}-{1}".format(first_usable, last_usable) - - -def _revdns_query(v): - v_ip = netaddr.IPAddress(str(v.ip)) - return v_ip.reverse_dns - - -def _size_query(v): - return v.size - - -def _size_usable_query(v): - if v.size == 1: - return 0 - elif v.size == 2: - return 2 - return v.size - 2 - - -def _subnet_query(v): - return str(v.cidr) - - -def _type_query(v): - if v.size == 1: - return 'address' - if v.size > 1: - if v.ip != v.network: - return 'address' - else: - return 'network' - - -def _unicast_query(v, value): - if v.is_unicast(): - return value - - -def _version_query(v): - return v.version - - -def _wrap_query(v, vtype, value): - if v.version == 6: - if vtype == 'address': - return '[' + str(v.ip) + ']' - elif vtype == 'network': - return '[' + str(v.ip) + ']/' + str(v.prefixlen) - else: - return value - - -# ---- HWaddr query helpers ---- -def _bare_query(v): - v.dialect = netaddr.mac_bare - return str(v) - - -def _bool_hwaddr_query(v): - if v: - return True - - -def _int_hwaddr_query(v): - return int(v) - - -def _cisco_query(v): - v.dialect = netaddr.mac_cisco - return str(v) - - -def _empty_hwaddr_query(v, value): - if v: - return value - - -def _linux_query(v): - v.dialect = mac_linux - return str(v) - - -def _postgresql_query(v): - v.dialect = netaddr.mac_pgsql - return str(v) - - -def _unix_query(v): - v.dialect = netaddr.mac_unix - return str(v) - - -def _win_query(v): - v.dialect = netaddr.mac_eui48 - return str(v) - - -# ---- IP address and network filters ---- - -# Returns a minified list of subnets or a single subnet that spans all of -# the inputs. -def cidr_merge(value, action='merge'): - if not hasattr(value, '__iter__'): - raise errors.AnsibleFilterError('cidr_merge: expected iterable, got ' + repr(value)) - - if action == 'merge': - try: - return [str(ip) for ip in netaddr.cidr_merge(value)] - except Exception as e: - raise errors.AnsibleFilterError('cidr_merge: error in netaddr:\n%s' % e) - - elif action == 'span': - # spanning_cidr needs at least two values - if len(value) == 0: - return None - elif len(value) == 1: - try: - return str(netaddr.IPNetwork(value[0])) - except Exception as e: - raise errors.AnsibleFilterError('cidr_merge: error in netaddr:\n%s' % e) - else: - try: - return str(netaddr.spanning_cidr(value)) - except Exception as e: - raise errors.AnsibleFilterError('cidr_merge: error in netaddr:\n%s' % e) - - else: - raise errors.AnsibleFilterError("cidr_merge: invalid action '%s'" % action) - - -def ipaddr(value, query='', version=False, alias='ipaddr'): - ''' Check if string is an IP address or network and filter it ''' - - query_func_extra_args = { - '': ('vtype',), - '6to4': ('vtype', 'value'), - 'cidr_lookup': ('iplist', 'value'), - 'first_usable': ('vtype',), - 'int': ('vtype',), - 'ipv4': ('value',), - 'ipv6': ('value',), - 'last_usable': ('vtype',), - 'link-local': ('value',), - 'loopback': ('value',), - 'lo': ('value',), - 'multicast': ('value',), - 'next_usable': ('vtype',), - 'peer': ('vtype',), - 'previous_usable': ('vtype',), - 'private': ('value',), - 'public': ('value',), - 'unicast': ('value',), - 'range_usable': ('vtype',), - 'wrap': ('vtype', 'value'), - } - - query_func_map = { - '': _empty_ipaddr_query, - '6to4': _6to4_query, - 'address': _ip_query, - 'address/prefix': _address_prefix_query, # deprecate - 'bool': _bool_ipaddr_query, - 'broadcast': _broadcast_query, - 'cidr': _cidr_query, - 'cidr_lookup': _cidr_lookup_query, - 'first_usable': _first_usable_query, - 'gateway': _gateway_query, # deprecate - 'gw': _gateway_query, # deprecate - 'host': _host_query, - 'host/prefix': _address_prefix_query, # deprecate - 'hostmask': _hostmask_query, - 'hostnet': _gateway_query, # deprecate - 'int': _int_query, - 'ip': _ip_query, - 'ip/prefix': _ip_prefix_query, - 'ip_netmask': _ip_netmask_query, - # 'ip_wildcard': _ip_wildcard_query, built then could not think of use case - 'ipv4': _ipv4_query, - 'ipv6': _ipv6_query, - 'last_usable': _last_usable_query, - 'link-local': _link_local_query, - 'lo': _loopback_query, - 'loopback': _loopback_query, - 'multicast': _multicast_query, - 'net': _net_query, - 'next_usable': _next_usable_query, - 'netmask': _netmask_query, - 'network': _network_query, - 'network_id': _network_id_query, - 'network/prefix': _subnet_query, - 'network_netmask': _network_netmask_query, - 'network_wildcard': _network_wildcard_query, - 'peer': _peer_query, - 'prefix': _prefix_query, - 'previous_usable': _previous_usable_query, - 'private': _private_query, - 'public': _public_query, - 'range_usable': _range_usable_query, - 'revdns': _revdns_query, - 'router': _gateway_query, # deprecate - 'size': _size_query, - 'size_usable': _size_usable_query, - 'subnet': _subnet_query, - 'type': _type_query, - 'unicast': _unicast_query, - 'v4': _ipv4_query, - 'v6': _ipv6_query, - 'version': _version_query, - 'wildcard': _hostmask_query, - 'wrap': _wrap_query, - } - - vtype = None - - if not value: - return False - - elif value is True: - return False - - # Check if value is a list and parse each element - elif isinstance(value, (list, tuple, types.GeneratorType)): - - _ret = [] - for element in value: - if ipaddr(element, str(query), version): - _ret.append(ipaddr(element, str(query), version)) - - if _ret: - return _ret - else: - return list() - - # Check if value is a number and convert it to an IP address - elif str(value).isdigit(): - - # We don't know what IP version to assume, so let's check IPv4 first, - # then IPv6 - try: - if ((not version) or (version and version == 4)): - v = netaddr.IPNetwork('0.0.0.0/0') - v.value = int(value) - v.prefixlen = 32 - elif version and version == 6: - v = netaddr.IPNetwork('::/0') - v.value = int(value) - v.prefixlen = 128 - - # IPv4 didn't work the first time, so it definitely has to be IPv6 - except Exception: - try: - v = netaddr.IPNetwork('::/0') - v.value = int(value) - v.prefixlen = 128 - - # The value is too big for IPv6. Are you a nanobot? - except Exception: - return False - - # We got an IP address, let's mark it as such - value = str(v) - vtype = 'address' - - # value has not been recognized, check if it's a valid IP string - else: - try: - v = netaddr.IPNetwork(value) - - # value is a valid IP string, check if user specified - # CIDR prefix or just an IP address, this will indicate default - # output format - try: - address, prefix = value.split('/') - vtype = 'network' - except Exception: - vtype = 'address' - - # value hasn't been recognized, maybe it's a numerical CIDR? - except Exception: - try: - address, prefix = value.split('/') - address.isdigit() - address = int(address) - prefix.isdigit() - prefix = int(prefix) - - # It's not numerical CIDR, give up - except Exception: - return False - - # It is something, so let's try and build a CIDR from the parts - try: - v = netaddr.IPNetwork('0.0.0.0/0') - v.value = address - v.prefixlen = prefix - - # It's not a valid IPv4 CIDR - except Exception: - try: - v = netaddr.IPNetwork('::/0') - v.value = address - v.prefixlen = prefix - - # It's not a valid IPv6 CIDR. Give up. - except Exception: - return False - - # We have a valid CIDR, so let's write it in correct format - value = str(v) - vtype = 'network' - - # We have a query string but it's not in the known query types. Check if - # that string is a valid subnet, if so, we can check later if given IP - # address/network is inside that specific subnet - try: - # ?? 6to4 and link-local were True here before. Should they still? - if query and (query not in query_func_map or query == 'cidr_lookup') and not str(query).isdigit() and ipaddr(query, 'network'): - iplist = netaddr.IPSet([netaddr.IPNetwork(query)]) - query = 'cidr_lookup' - except Exception: - pass - - # This code checks if value maches the IP version the user wants, ie. if - # it's any version ("ipaddr()"), IPv4 ("ipv4()") or IPv6 ("ipv6()") - # If version does not match, return False - if version and v.version != version: - return False - - extras = [] - for arg in query_func_extra_args.get(query, tuple()): - extras.append(locals()[arg]) - try: - return query_func_map[query](v, *extras) - except KeyError: - try: - float(query) - if v.size == 1: - if vtype == 'address': - return str(v.ip) - elif vtype == 'network': - return str(v) - - elif v.size > 1: - try: - return str(v[query]) + '/' + str(v.prefixlen) - except Exception: - return False - - else: - return value - - except Exception: - raise errors.AnsibleFilterError(alias + ': unknown filter type: %s' % query) - - return False - - -def ipmath(value, amount): - try: - if '/' in value: - ip = netaddr.IPNetwork(value).ip - else: - ip = netaddr.IPAddress(value) - except (netaddr.AddrFormatError, ValueError): - msg = 'You must pass a valid IP address; {0} is invalid'.format(value) - raise errors.AnsibleFilterError(msg) - - if not isinstance(amount, int): - msg = ( - 'You must pass an integer for arithmetic; ' - '{0} is not a valid integer' - ).format(amount) - raise errors.AnsibleFilterError(msg) - - return str(ip + amount) - - -def ipwrap(value, query=''): - try: - if isinstance(value, (list, tuple, types.GeneratorType)): - _ret = [] - for element in value: - if ipaddr(element, query, version=False, alias='ipwrap'): - _ret.append(ipaddr(element, 'wrap')) - else: - _ret.append(element) - - return _ret - else: - _ret = ipaddr(value, query, version=False, alias='ipwrap') - if _ret: - return ipaddr(_ret, 'wrap') - else: - return value - - except Exception: - return value - - -def ipv4(value, query=''): - return ipaddr(value, query, version=4, alias='ipv4') - - -def ipv6(value, query=''): - return ipaddr(value, query, version=6, alias='ipv6') - - -# Split given subnet into smaller subnets or find out the biggest subnet of -# a given IP address with given CIDR prefix -# Usage: -# -# - address or address/prefix | ipsubnet -# returns CIDR subnet of a given input -# -# - address/prefix | ipsubnet(cidr) -# returns number of possible subnets for given CIDR prefix -# -# - address/prefix | ipsubnet(cidr, index) -# returns new subnet with given CIDR prefix -# -# - address | ipsubnet(cidr) -# returns biggest subnet with given CIDR prefix that address belongs to -# -# - address | ipsubnet(cidr, index) -# returns next indexed subnet which contains given address -# -# - address/prefix | ipsubnet(subnet/prefix) -# return the index of the subnet in the subnet -def ipsubnet(value, query='', index='x'): - ''' Manipulate IPv4/IPv6 subnets ''' - - try: - vtype = ipaddr(value, 'type') - if vtype == 'address': - v = ipaddr(value, 'cidr') - elif vtype == 'network': - v = ipaddr(value, 'subnet') - - value = netaddr.IPNetwork(v) - except Exception: - return False - query_string = str(query) - if not query: - return str(value) - - elif query_string.isdigit(): - vsize = ipaddr(v, 'size') - query = int(query) - - try: - float(index) - index = int(index) - - if vsize > 1: - try: - return str(list(value.subnet(query))[index]) - except Exception: - return False - - elif vsize == 1: - try: - return str(value.supernet(query)[index]) - except Exception: - return False - - except Exception: - if vsize > 1: - try: - return str(len(list(value.subnet(query)))) - except Exception: - return False - - elif vsize == 1: - try: - return str(value.supernet(query)[0]) - except Exception: - return False - - elif query_string: - vtype = ipaddr(query, 'type') - if vtype == 'address': - v = ipaddr(query, 'cidr') - elif vtype == 'network': - v = ipaddr(query, 'subnet') - else: - msg = 'You must pass a valid subnet or IP address; {0} is invalid'.format(query_string) - raise errors.AnsibleFilterError(msg) - query = netaddr.IPNetwork(v) - for i, subnet in enumerate(query.subnet(value.prefixlen), 1): - if subnet == value: - return str(i) - msg = '{0} is not in the subnet {1}'.format(value.cidr, query.cidr) - raise errors.AnsibleFilterError(msg) - return False - - -# Returns the nth host within a network described by value. -# Usage: -# -# - address or address/prefix | nthhost(nth) -# returns the nth host within the given network -def nthhost(value, query=''): - ''' Get the nth host within a given network ''' - try: - vtype = ipaddr(value, 'type') - if vtype == 'address': - v = ipaddr(value, 'cidr') - elif vtype == 'network': - v = ipaddr(value, 'subnet') - - value = netaddr.IPNetwork(v) - except Exception: - return False - - if not query: - return False - - try: - nth = int(query) - if value.size > nth: - return value[nth] - - except ValueError: - return False - - return False - - -# Returns the next nth usable ip within a network described by value. -def next_nth_usable(value, offset): - try: - vtype = ipaddr(value, 'type') - if vtype == 'address': - v = ipaddr(value, 'cidr') - elif vtype == 'network': - v = ipaddr(value, 'subnet') - - v = netaddr.IPNetwork(v) - except Exception: - return False - - if type(offset) != int: - raise errors.AnsibleFilterError('Must pass in an integer') - if v.size > 1: - first_usable, last_usable = _first_last(v) - nth_ip = int(netaddr.IPAddress(int(v.ip) + offset)) - if nth_ip >= first_usable and nth_ip <= last_usable: - return str(netaddr.IPAddress(int(v.ip) + offset)) - - -# Returns the previous nth usable ip within a network described by value. -def previous_nth_usable(value, offset): - try: - vtype = ipaddr(value, 'type') - if vtype == 'address': - v = ipaddr(value, 'cidr') - elif vtype == 'network': - v = ipaddr(value, 'subnet') - - v = netaddr.IPNetwork(v) - except Exception: - return False - - if type(offset) != int: - raise errors.AnsibleFilterError('Must pass in an integer') - if v.size > 1: - first_usable, last_usable = _first_last(v) - nth_ip = int(netaddr.IPAddress(int(v.ip) - offset)) - if nth_ip >= first_usable and nth_ip <= last_usable: - return str(netaddr.IPAddress(int(v.ip) - offset)) - - -def _range_checker(ip_check, first, last): - ''' - Tests whether an ip address is within the bounds of the first and last address. - - :param ip_check: The ip to test if it is within first and last. - :param first: The first IP in the range to test against. - :param last: The last IP in the range to test against. - - :return: bool - ''' - if ip_check >= first and ip_check <= last: - return True - else: - return False - - -def _address_normalizer(value): - ''' - Used to validate an address or network type and return it in a consistent format. - This is being used for future use cases not currently available such as an address range. - - :param value: The string representation of an address or network. - - :return: The address or network in the normalized form. - ''' - try: - vtype = ipaddr(value, 'type') - if vtype == 'address' or vtype == "network": - v = ipaddr(value, 'subnet') - except Exception: - return False - - return v - - -def network_in_usable(value, test): - ''' - Checks whether 'test' is a useable address or addresses in 'value' - - :param: value: The string representation of an address or network to test against. - :param test: The string representation of an address or network to validate if it is within the range of 'value'. - - :return: bool - ''' - # normalize value and test variables into an ipaddr - v = _address_normalizer(value) - w = _address_normalizer(test) - - # get first and last addresses as integers to compare value and test; or cathes value when case is /32 - v_first = ipaddr(ipaddr(v, 'first_usable') or ipaddr(v, 'address'), 'int') - v_last = ipaddr(ipaddr(v, 'last_usable') or ipaddr(v, 'address'), 'int') - w_first = ipaddr(ipaddr(w, 'network') or ipaddr(w, 'address'), 'int') - w_last = ipaddr(ipaddr(w, 'broadcast') or ipaddr(w, 'address'), 'int') - - if _range_checker(w_first, v_first, v_last) and _range_checker(w_last, v_first, v_last): - return True - else: - return False - - -def network_in_network(value, test): - ''' - Checks whether the 'test' address or addresses are in 'value', including broadcast and network - - :param: value: The network address or range to test against. - :param test: The address or network to validate if it is within the range of 'value'. - - :return: bool - ''' - # normalize value and test variables into an ipaddr - v = _address_normalizer(value) - w = _address_normalizer(test) - - # get first and last addresses as integers to compare value and test; or cathes value when case is /32 - v_first = ipaddr(ipaddr(v, 'network') or ipaddr(v, 'address'), 'int') - v_last = ipaddr(ipaddr(v, 'broadcast') or ipaddr(v, 'address'), 'int') - w_first = ipaddr(ipaddr(w, 'network') or ipaddr(w, 'address'), 'int') - w_last = ipaddr(ipaddr(w, 'broadcast') or ipaddr(w, 'address'), 'int') - - if _range_checker(w_first, v_first, v_last) and _range_checker(w_last, v_first, v_last): - return True - else: - return False - - -def reduce_on_network(value, network): - ''' - Reduces a list of addresses to only the addresses that match a given network. - - :param: value: The list of addresses to filter on. - :param: network: The network to validate against. - - :return: The reduced list of addresses. - ''' - # normalize network variable into an ipaddr - n = _address_normalizer(network) - - # get first and last addresses as integers to compare value and test; or cathes value when case is /32 - n_first = ipaddr(ipaddr(n, 'network') or ipaddr(n, 'address'), 'int') - n_last = ipaddr(ipaddr(n, 'broadcast') or ipaddr(n, 'address'), 'int') - - # create an empty list to fill and return - r = [] - - for address in value: - # normalize address variables into an ipaddr - a = _address_normalizer(address) - - # get first and last addresses as integers to compare value and test; or cathes value when case is /32 - a_first = ipaddr(ipaddr(a, 'network') or ipaddr(a, 'address'), 'int') - a_last = ipaddr(ipaddr(a, 'broadcast') or ipaddr(a, 'address'), 'int') - - if _range_checker(a_first, n_first, n_last) and _range_checker(a_last, n_first, n_last): - r.append(address) - - return r - - -# Returns the SLAAC address within a network for a given HW/MAC address. -# Usage: -# -# - prefix | slaac(mac) -def slaac(value, query=''): - ''' Get the SLAAC address within given network ''' - try: - vtype = ipaddr(value, 'type') - if vtype == 'address': - v = ipaddr(value, 'cidr') - elif vtype == 'network': - v = ipaddr(value, 'subnet') - - if ipaddr(value, 'version') != 6: - return False - - value = netaddr.IPNetwork(v) - except Exception: - return False - - if not query: - return False - - try: - mac = hwaddr(query, alias='slaac') - - eui = netaddr.EUI(mac) - except Exception: - return False - - return eui.ipv6(value.network) - - -# ---- HWaddr / MAC address filters ---- -def hwaddr(value, query='', alias='hwaddr'): - ''' Check if string is a HW/MAC address and filter it ''' - - query_func_extra_args = { - '': ('value',), - } - - query_func_map = { - '': _empty_hwaddr_query, - 'bare': _bare_query, - 'bool': _bool_hwaddr_query, - 'int': _int_hwaddr_query, - 'cisco': _cisco_query, - 'eui48': _win_query, - 'linux': _linux_query, - 'pgsql': _postgresql_query, - 'postgresql': _postgresql_query, - 'psql': _postgresql_query, - 'unix': _unix_query, - 'win': _win_query, - } - - try: - v = netaddr.EUI(value) - except Exception: - if query and query != 'bool': - raise errors.AnsibleFilterError(alias + ': not a hardware address: %s' % value) - - extras = [] - for arg in query_func_extra_args.get(query, tuple()): - extras.append(locals()[arg]) - try: - return query_func_map[query](v, *extras) - except KeyError: - raise errors.AnsibleFilterError(alias + ': unknown filter type: %s' % query) - - return False - - -def macaddr(value, query=''): - return hwaddr(value, query, alias='macaddr') - - -def _need_netaddr(f_name, *args, **kwargs): - raise errors.AnsibleFilterError("The %s filter requires python's netaddr be " - "installed on the ansible controller" % f_name) - - -def ip4_hex(arg, delimiter=''): - ''' Convert an IPv4 address to Hexadecimal notation ''' - numbers = list(map(int, arg.split('.'))) - return '{0:02x}{sep}{1:02x}{sep}{2:02x}{sep}{3:02x}'.format(*numbers, sep=delimiter) - - -# ---- Ansible filters ---- -class FilterModule(object): - ''' IP address and network manipulation filters ''' - filter_map = { - # IP addresses and networks - 'cidr_merge': cidr_merge, - 'ipaddr': ipaddr, - 'ipmath': ipmath, - 'ipwrap': ipwrap, - 'ip4_hex': ip4_hex, - 'ipv4': ipv4, - 'ipv6': ipv6, - 'ipsubnet': ipsubnet, - 'next_nth_usable': next_nth_usable, - 'network_in_network': network_in_network, - 'network_in_usable': network_in_usable, - 'reduce_on_network': reduce_on_network, - 'nthhost': nthhost, - 'previous_nth_usable': previous_nth_usable, - 'slaac': slaac, - - # MAC / HW addresses - 'hwaddr': hwaddr, - 'macaddr': macaddr - } - - def filters(self): - if netaddr: - return self.filter_map - else: - # Need to install python's netaddr for these filters to work - return dict((f, partial(_need_netaddr, f)) for f in self.filter_map) diff --git a/lib/ansible/plugins/filter/network.py b/lib/ansible/plugins/filter/network.py deleted file mode 100644 index bf7711d0fa9..00000000000 --- a/lib/ansible/plugins/filter/network.py +++ /dev/null @@ -1,481 +0,0 @@ -# -# {c) 2017 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import re -import os -import traceback -import string - -from xml.etree.ElementTree import fromstring - -from ansible.module_utils._text import to_native, to_text -from ansible.module_utils.network.common.utils import Template -from ansible.module_utils.six import iteritems, string_types -from ansible.module_utils.common._collections_compat import Mapping -from ansible.errors import AnsibleError, AnsibleFilterError -from ansible.utils.display import Display -from ansible.utils.encrypt import passlib_or_crypt, random_password - -try: - import yaml - HAS_YAML = True -except ImportError: - HAS_YAML = False - -try: - import textfsm - HAS_TEXTFSM = True -except ImportError: - HAS_TEXTFSM = False - -display = Display() - - -def re_matchall(regex, value): - objects = list() - for match in re.findall(regex.pattern, value, re.M): - obj = {} - if regex.groupindex: - for name, index in iteritems(regex.groupindex): - if len(regex.groupindex) == 1: - obj[name] = match - else: - obj[name] = match[index - 1] - objects.append(obj) - return objects - - -def re_search(regex, value): - obj = {} - match = regex.search(value, re.M) - if match: - items = list(match.groups()) - if regex.groupindex: - for name, index in iteritems(regex.groupindex): - obj[name] = items[index - 1] - return obj - - -def parse_cli(output, tmpl): - if not isinstance(output, string_types): - raise AnsibleError("parse_cli input should be a string, but was given a input of %s" % (type(output))) - - if not os.path.exists(tmpl): - raise AnsibleError('unable to locate parse_cli template: %s' % tmpl) - - try: - template = Template() - except ImportError as exc: - raise AnsibleError(to_native(exc)) - - with open(tmpl) as tmpl_fh: - tmpl_content = tmpl_fh.read() - - spec = yaml.safe_load(tmpl_content) - obj = {} - - for name, attrs in iteritems(spec['keys']): - value = attrs['value'] - - try: - variables = spec.get('vars', {}) - value = template(value, variables) - except Exception: - pass - - if 'start_block' in attrs and 'end_block' in attrs: - start_block = re.compile(attrs['start_block']) - end_block = re.compile(attrs['end_block']) - - blocks = list() - lines = None - block_started = False - - for line in output.split('\n'): - match_start = start_block.match(line) - match_end = end_block.match(line) - - if match_start: - lines = list() - lines.append(line) - block_started = True - - elif match_end: - if lines: - lines.append(line) - blocks.append('\n'.join(lines)) - block_started = False - - elif block_started: - if lines: - lines.append(line) - - regex_items = [re.compile(r) for r in attrs['items']] - objects = list() - - for block in blocks: - if isinstance(value, Mapping) and 'key' not in value: - items = list() - for regex in regex_items: - match = regex.search(block) - if match: - item_values = match.groupdict() - item_values['match'] = list(match.groups()) - items.append(item_values) - else: - items.append(None) - - obj = {} - for k, v in iteritems(value): - try: - obj[k] = template(v, {'item': items}, fail_on_undefined=False) - except Exception: - obj[k] = None - objects.append(obj) - - elif isinstance(value, Mapping): - items = list() - for regex in regex_items: - match = regex.search(block) - if match: - item_values = match.groupdict() - item_values['match'] = list(match.groups()) - items.append(item_values) - else: - items.append(None) - - key = template(value['key'], {'item': items}) - values = dict([(k, template(v, {'item': items})) for k, v in iteritems(value['values'])]) - objects.append({key: values}) - - return objects - - elif 'items' in attrs: - regexp = re.compile(attrs['items']) - when = attrs.get('when') - conditional = "{%% if %s %%}True{%% else %%}False{%% endif %%}" % when - - if isinstance(value, Mapping) and 'key' not in value: - values = list() - - for item in re_matchall(regexp, output): - entry = {} - - for item_key, item_value in iteritems(value): - entry[item_key] = template(item_value, {'item': item}) - - if when: - if template(conditional, {'item': entry}): - values.append(entry) - else: - values.append(entry) - - obj[name] = values - - elif isinstance(value, Mapping): - values = dict() - - for item in re_matchall(regexp, output): - entry = {} - - for item_key, item_value in iteritems(value['values']): - entry[item_key] = template(item_value, {'item': item}) - - key = template(value['key'], {'item': item}) - - if when: - if template(conditional, {'item': {'key': key, 'value': entry}}): - values[key] = entry - else: - values[key] = entry - - obj[name] = values - - else: - item = re_search(regexp, output) - obj[name] = template(value, {'item': item}) - - else: - obj[name] = value - - return obj - - -def parse_cli_textfsm(value, template): - if not HAS_TEXTFSM: - raise AnsibleError('parse_cli_textfsm filter requires TextFSM library to be installed') - - if not isinstance(value, string_types): - raise AnsibleError("parse_cli_textfsm input should be a string, but was given a input of %s" % (type(value))) - - if not os.path.exists(template): - raise AnsibleError('unable to locate parse_cli_textfsm template: %s' % template) - - try: - template = open(template) - except IOError as exc: - raise AnsibleError(to_native(exc)) - - re_table = textfsm.TextFSM(template) - fsm_results = re_table.ParseText(value) - - results = list() - for item in fsm_results: - results.append(dict(zip(re_table.header, item))) - - return results - - -def _extract_param(template, root, attrs, value): - - key = None - when = attrs.get('when') - conditional = "{%% if %s %%}True{%% else %%}False{%% endif %%}" % when - param_to_xpath_map = attrs['items'] - - if isinstance(value, Mapping): - key = value.get('key', None) - if key: - value = value['values'] - - entries = dict() if key else list() - - for element in root.findall(attrs['top']): - entry = dict() - item_dict = dict() - for param, param_xpath in iteritems(param_to_xpath_map): - fields = None - try: - fields = element.findall(param_xpath) - except Exception: - display.warning("Failed to evaluate value of '%s' with XPath '%s'.\nUnexpected error: %s." % (param, param_xpath, traceback.format_exc())) - - tags = param_xpath.split('/') - - # check if xpath ends with attribute. - # If yes set attribute key/value dict to param value in case attribute matches - # else if it is a normal xpath assign matched element text value. - if len(tags) and tags[-1].endswith(']'): - if fields: - if len(fields) > 1: - item_dict[param] = [field.attrib for field in fields] - else: - item_dict[param] = fields[0].attrib - else: - item_dict[param] = {} - else: - if fields: - if len(fields) > 1: - item_dict[param] = [field.text for field in fields] - else: - item_dict[param] = fields[0].text - else: - item_dict[param] = None - - if isinstance(value, Mapping): - for item_key, item_value in iteritems(value): - entry[item_key] = template(item_value, {'item': item_dict}) - else: - entry = template(value, {'item': item_dict}) - - if key: - expanded_key = template(key, {'item': item_dict}) - if when: - if template(conditional, {'item': {'key': expanded_key, 'value': entry}}): - entries[expanded_key] = entry - else: - entries[expanded_key] = entry - else: - if when: - if template(conditional, {'item': entry}): - entries.append(entry) - else: - entries.append(entry) - - return entries - - -def parse_xml(output, tmpl): - if not os.path.exists(tmpl): - raise AnsibleError('unable to locate parse_xml template: %s' % tmpl) - - if not isinstance(output, string_types): - raise AnsibleError('parse_xml works on string input, but given input of : %s' % type(output)) - - root = fromstring(output) - try: - template = Template() - except ImportError as exc: - raise AnsibleError(to_native(exc)) - - with open(tmpl) as tmpl_fh: - tmpl_content = tmpl_fh.read() - - spec = yaml.safe_load(tmpl_content) - obj = {} - - for name, attrs in iteritems(spec['keys']): - value = attrs['value'] - - try: - variables = spec.get('vars', {}) - value = template(value, variables) - except Exception: - pass - - if 'items' in attrs: - obj[name] = _extract_param(template, root, attrs, value) - else: - obj[name] = value - - return obj - - -def type5_pw(password, salt=None): - if not isinstance(password, string_types): - raise AnsibleFilterError("type5_pw password input should be a string, but was given a input of %s" % (type(password).__name__)) - - salt_chars = u''.join(( - to_text(string.ascii_letters), - to_text(string.digits), - u'./' - )) - if salt is not None and not isinstance(salt, string_types): - raise AnsibleFilterError("type5_pw salt input should be a string, but was given a input of %s" % (type(salt).__name__)) - elif not salt: - salt = random_password(length=4, chars=salt_chars) - elif not set(salt) <= set(salt_chars): - raise AnsibleFilterError("type5_pw salt used inproper characters, must be one of %s" % (salt_chars)) - - encrypted_password = passlib_or_crypt(password, "md5_crypt", salt=salt) - - return encrypted_password - - -def hash_salt(password): - - split_password = password.split("$") - if len(split_password) != 4: - raise AnsibleFilterError('Could not parse salt out password correctly from {0}'.format(password)) - else: - return split_password[2] - - -def comp_type5(unencrypted_password, encrypted_password, return_original=False): - - salt = hash_salt(encrypted_password) - if type5_pw(unencrypted_password, salt) == encrypted_password: - if return_original is True: - return encrypted_password - else: - return True - return False - - -def vlan_parser(vlan_list, first_line_len=48, other_line_len=44): - - ''' - Input: Unsorted list of vlan integers - Output: Sorted string list of integers according to IOS-like vlan list rules - - 1. Vlans are listed in ascending order - 2. Runs of 3 or more consecutive vlans are listed with a dash - 3. The first line of the list can be first_line_len characters long - 4. Subsequent list lines can be other_line_len characters - ''' - - # Sort and remove duplicates - sorted_list = sorted(set(vlan_list)) - - if sorted_list[0] < 1 or sorted_list[-1] > 4094: - raise AnsibleFilterError('Valid VLAN range is 1-4094') - - parse_list = [] - idx = 0 - while idx < len(sorted_list): - start = idx - end = start - while end < len(sorted_list) - 1: - if sorted_list[end + 1] - sorted_list[end] == 1: - end += 1 - else: - break - - if start == end: - # Single VLAN - parse_list.append(str(sorted_list[idx])) - elif start + 1 == end: - # Run of 2 VLANs - parse_list.append(str(sorted_list[start])) - parse_list.append(str(sorted_list[end])) - else: - # Run of 3 or more VLANs - parse_list.append(str(sorted_list[start]) + '-' + str(sorted_list[end])) - idx = end + 1 - - line_count = 0 - result = [''] - for vlans in parse_list: - # First line (" switchport trunk allowed vlan ") - if line_count == 0: - if len(result[line_count] + vlans) > first_line_len: - result.append('') - line_count += 1 - result[line_count] += vlans + ',' - else: - result[line_count] += vlans + ',' - - # Subsequent lines (" switchport trunk allowed vlan add ") - else: - if len(result[line_count] + vlans) > other_line_len: - result.append('') - line_count += 1 - result[line_count] += vlans + ',' - else: - result[line_count] += vlans + ',' - - # Remove trailing orphan commas - for idx in range(0, len(result)): - result[idx] = result[idx].rstrip(',') - - # Sometimes text wraps to next line, but there are no remaining VLANs - if '' in result: - result.remove('') - - return result - - -class FilterModule(object): - """Filters for working with output from network devices""" - - filter_map = { - 'parse_cli': parse_cli, - 'parse_cli_textfsm': parse_cli_textfsm, - 'parse_xml': parse_xml, - 'type5_pw': type5_pw, - 'hash_salt': hash_salt, - 'comp_type5': comp_type5, - 'vlan_parser': vlan_parser - } - - def filters(self): - return self.filter_map diff --git a/lib/ansible/plugins/httpapi/restconf.py b/lib/ansible/plugins/httpapi/restconf.py deleted file mode 100644 index 7d121ff1bd1..00000000000 --- a/lib/ansible/plugins/httpapi/restconf.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright (c) 2018 Cisco and/or its affiliates. -# -# 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 . -# - -from __future__ import (absolute_import, division, print_function) - -__metaclass__ = type - -DOCUMENTATION = """ ---- -author: Ansible Networking Team -httpapi: restconf -short_description: HttpApi Plugin for devices supporting Restconf API -description: - - This HttpApi plugin provides methods to connect to Restconf API - endpoints. -version_added: "2.8" -options: - root_path: - type: str - description: - - Specifies the location of the Restconf root. - default: '/restconf' - vars: - - name: ansible_httpapi_restconf_root -""" - -import json - -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import ConnectionError -from ansible.module_utils.six.moves.urllib.error import HTTPError -from ansible.plugins.httpapi import HttpApiBase - - -CONTENT_TYPE = 'application/yang-data+json' - - -class HttpApi(HttpApiBase): - def send_request(self, data, **message_kwargs): - if data: - data = json.dumps(data) - - path = '/'.join([self.get_option('root_path').rstrip('/'), message_kwargs.get('path', '').lstrip('/')]) - - headers = { - 'Content-Type': message_kwargs.get('content_type') or CONTENT_TYPE, - 'Accept': message_kwargs.get('accept') or CONTENT_TYPE, - } - response, response_data = self.connection.send(path, data, headers=headers, method=message_kwargs.get('method')) - - return handle_response(response, response_data) - - -def handle_response(response, response_data): - try: - response_data = json.loads(response_data.read()) - except ValueError: - response_data = response_data.read() - - if isinstance(response, HTTPError): - if response_data: - if 'errors' in response_data: - errors = response_data['errors']['error'] - error_text = '\n'.join((error['error-message'] for error in errors)) - else: - error_text = response_data - - raise ConnectionError(error_text, code=response.code) - raise ConnectionError(to_text(response), code=response.code) - - return response_data diff --git a/lib/ansible/plugins/netconf/default.py b/lib/ansible/plugins/netconf/default.py deleted file mode 100644 index 3e4f57c24b0..00000000000 --- a/lib/ansible/plugins/netconf/default.py +++ /dev/null @@ -1,68 +0,0 @@ -# -# (c) 2017 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 . -# -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = """ ---- -author: Ansible Networking Team -netconf: default -short_description: Use default netconf plugin to run standard netconf commands as per RFC -description: - - This default plugin provides low level abstraction apis for - sending and receiving netconf commands as per Netconf RFC specification. -version_added: "2.9" -options: - ncclient_device_handler: - type: str - default: default - description: - - Specifies the ncclient device handler name for network os that support default netconf - implementation as per Netconf RFC specification. To identify the ncclient device handler - name refer ncclient library documentation. -""" -import json - -from ansible.module_utils._text import to_text -from ansible.plugins.netconf import NetconfBase - - -class Netconf(NetconfBase): - - def get_text(self, ele, tag): - try: - return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip() - except AttributeError: - pass - - def get_device_info(self): - device_info = dict() - device_info['network_os'] = 'default' - return device_info - - def get_capabilities(self): - result = dict() - result['rpc'] = self.get_base_rpc() - result['network_api'] = 'netconf' - result['device_info'] = self.get_device_info() - result['server_capabilities'] = [c for c in self.m.server_capabilities] - result['client_capabilities'] = [c for c in self.m.client_capabilities] - result['session_id'] = self.m.session_id - result['device_operations'] = self.get_device_operations(result['server_capabilities']) - return json.dumps(result) diff --git a/test/integration/targets/filter_ipaddr/aliases b/test/integration/targets/filter_ipaddr/aliases deleted file mode 100644 index 1603f4351b6..00000000000 --- a/test/integration/targets/filter_ipaddr/aliases +++ /dev/null @@ -1,3 +0,0 @@ -shippable/posix/group2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller -skip/aix diff --git a/test/integration/targets/filter_ipaddr/runme.sh b/test/integration/targets/filter_ipaddr/runme.sh deleted file mode 100755 index d498a5fc67f..00000000000 --- a/test/integration/targets/filter_ipaddr/runme.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -source virtualenv.sh - -# Requirements have to be installed prior to running ansible-playbook -# because plugins and requirements are loaded before the task runs - -pip install netaddr - -ANSIBLE_ROLES_PATH=../ ansible-playbook runme.yml "$@" diff --git a/test/integration/targets/filter_ipaddr/runme.yml b/test/integration/targets/filter_ipaddr/runme.yml deleted file mode 100644 index d2c5bda7320..00000000000 --- a/test/integration/targets/filter_ipaddr/runme.yml +++ /dev/null @@ -1,3 +0,0 @@ -- hosts: localhost - roles: - - { role: filter_ipaddr } diff --git a/test/integration/targets/filter_ipaddr/tasks/main.yml b/test/integration/targets/filter_ipaddr/tasks/main.yml deleted file mode 100644 index c0fde7ced83..00000000000 --- a/test/integration/targets/filter_ipaddr/tasks/main.yml +++ /dev/null @@ -1,23 +0,0 @@ -- name: Test ipaddr filter - assert: - that: - - "'192.168.0.1/32' | ipaddr('netmask') == '255.255.255.255'" - - "'192.168.0.1/24' | ipaddr('netmask') == '255.255.255.0'" - - "not '192.168.0.1/31' | ipaddr('broadcast')" - - "'192.168.0.1/24' | ipaddr('broadcast') == '192.168.0.255'" - - "'192.168.0.1/24' | ipaddr('prefix') == 24" - - "'192.168.0.1/24' | ipaddr('address') == '192.168.0.1'" - - "'192.168.0.1/24' | ipaddr('network') == '192.168.0.0'" - - "'fe80::dead:beef/64' | ipaddr('broadcast') == 'fe80::ffff:ffff:ffff:ffff'" - - "'::1/120' | ipaddr('netmask') == 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00'" - - "{{ subnets | ipaddr(1) }} == ['10.1.1.1/24', '10.1.2.1/24']" - - "{{ subnets | ipaddr('1') }} == ['10.1.1.1/24', '10.1.2.1/24']" - - "{{ subnets | ipaddr(-1) }} == ['10.1.1.255/24', '10.1.2.255/24']" - - "{{ subnets | ipaddr('-1') }} == ['10.1.1.255/24', '10.1.2.255/24']" - - "'{{ prefix | ipaddr(1) }}' == '10.1.1.1/24'" - - "'{{ prefix | ipaddr('1') }}' == '10.1.1.1/24'" - - "'{{ prefix | ipaddr('network') }}' == '10.1.1.0'" - - "'{{ prefix | ipaddr('-1') }}' == '10.1.1.255/24'" - vars: - subnets: ['10.1.1.0/24', '10.1.2.0/24'] - prefix: '10.1.1.0/24' diff --git a/test/integration/targets/netconf_config/defaults/main.yaml b/test/integration/targets/netconf_config/defaults/main.yaml deleted file mode 100644 index 5f709c5aac1..00000000000 --- a/test/integration/targets/netconf_config/defaults/main.yaml +++ /dev/null @@ -1,2 +0,0 @@ ---- -testcase: "*" diff --git a/test/integration/targets/netconf_config/meta/main.yml b/test/integration/targets/netconf_config/meta/main.yml deleted file mode 100644 index 3403f48112f..00000000000 --- a/test/integration/targets/netconf_config/meta/main.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -dependencies: - - { role: prepare_junos_tests, when: ansible_network_os == 'junos' } - - { role: prepare_iosxr_tests, when: ansible_network_os == 'iosxr' } diff --git a/test/integration/targets/netconf_config/tasks/iosxr.yaml b/test/integration/targets/netconf_config/tasks/iosxr.yaml deleted file mode 100644 index 4f36f4c54d7..00000000000 --- a/test/integration/targets/netconf_config/tasks/iosxr.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: collect all netconf test cases - find: - paths: "{{ role_path }}/tests/iosxr" - patterns: "{{ testcase }}.yaml" - register: test_cases - connection: local - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test case (connection=netconf) - include: "{{ test_case_to_run }}" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_config/tasks/junos.yaml b/test/integration/targets/netconf_config/tasks/junos.yaml deleted file mode 100644 index 86c56f83a5e..00000000000 --- a/test/integration/targets/netconf_config/tasks/junos.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: collect all netconf test cases - find: - paths: "{{ role_path }}/tests/junos" - patterns: "{{ testcase }}.yaml" - register: test_cases - connection: local - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test case (connection=netconf) - include: "{{ test_case_to_run }} ansible_connection=netconf" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_config/tasks/main.yaml b/test/integration/targets/netconf_config/tasks/main.yaml deleted file mode 100644 index 4d8eb94cd5a..00000000000 --- a/test/integration/targets/netconf_config/tasks/main.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -- { include: junos.yaml, when: ansible_network_os == 'junos', tags: ['netconf'] } -- { include: iosxr.yaml, when: ansible_network_os == 'iosxr', tags: ['netconf'] } diff --git a/test/integration/targets/netconf_config/tests/iosxr/basic.yaml b/test/integration/targets/netconf_config/tests/iosxr/basic.yaml deleted file mode 100644 index c2cd38974de..00000000000 --- a/test/integration/targets/netconf_config/tests/iosxr/basic.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -- debug: msg="START netconf_config iosxr/basic.yaml on connection={{ ansible_connection }}" - -- name: save config test - netconf_config: - backup: yes - register: result - connection: netconf - -- assert: - that: - - "'backup_path' in result" - -- debug: msg="END netconf_config iosxr/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/integration/targets/netconf_config/tests/junos/basic.yaml b/test/integration/targets/netconf_config/tests/junos/basic.yaml deleted file mode 100644 index c9d4d169db6..00000000000 --- a/test/integration/targets/netconf_config/tests/junos/basic.yaml +++ /dev/null @@ -1,75 +0,0 @@ ---- -- debug: msg="START netconf_config junos/basic.yaml on connection={{ ansible_connection }}" - -- include_vars: "{{playbook_dir }}/targets/netconf_config/tests/junos/fixtures/config.yml" - -- name: syslog file config- setup - junos_config: - lines: - - delete system syslog file test_netconf_config - -- name: configure syslog file - netconf_config: - content: "{{ syslog_config }}" - register: result - -- assert: - that: - - "result.changed == true" - - "'test_netconf_config' in result.diff.after" - -- name: configure syslog file (idempotent) - netconf_config: - content: "{{ syslog_config }}" - register: result - -- assert: - that: - - "result.changed == false" - -- name: replace default operation fail - netconf_config: - content: "{{ syslog_config_replace }}" - default_operation: 'replace' - register: result - ignore_errors: yes - -- assert: - that: - - "result.failed == true" - - "'Missing mandatory statement' in result.msg" - -- name: replace syslog config with operation key in content - netconf_config: - content: "{{ syslog_config_replace }}" - register: result - -- assert: - that: - - "result.changed == true" - -- name: test backup - netconf_config: - content: "{{ syslog_config }}" - backup: True - register: result - -- assert: - that: - - "'backup_path' in result" - -- name: syslog file config- teardown - junos_config: - lines: - - delete system syslog file test_netconf_config - -- name: save config - netconf_config: - backup: yes - register: result - -- assert: - that: - - "'backup_path' in result" - -- debug: msg="END netconf_config junos/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/integration/targets/netconf_config/tests/junos/fixtures/config.yml b/test/integration/targets/netconf_config/tests/junos/fixtures/config.yml deleted file mode 100644 index 86de0f9da5c..00000000000 --- a/test/integration/targets/netconf_config/tests/junos/fixtures/config.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -syslog_config: | - - - - - - test_netconf_config - - any - - - - kernel - - - - - - - - -syslog_config_replace: | - - - - - - test_netconf_config - - any - - - - - - - diff --git a/test/integration/targets/netconf_get/defaults/main.yaml b/test/integration/targets/netconf_get/defaults/main.yaml deleted file mode 100644 index 5f709c5aac1..00000000000 --- a/test/integration/targets/netconf_get/defaults/main.yaml +++ /dev/null @@ -1,2 +0,0 @@ ---- -testcase: "*" diff --git a/test/integration/targets/netconf_get/meta/main.yml b/test/integration/targets/netconf_get/meta/main.yml deleted file mode 100644 index 0fb2b09f0ef..00000000000 --- a/test/integration/targets/netconf_get/meta/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -dependencies: - - { role: prepare_junos_tests, when: ansible_network_os == 'junos' } - - { role: prepare_iosxr_tests, when: ansible_network_os == 'iosxr' } - - { role: prepare_sros_tests, when: ansible_network_os == 'sros' } diff --git a/test/integration/targets/netconf_get/tasks/iosxr.yaml b/test/integration/targets/netconf_get/tasks/iosxr.yaml deleted file mode 100644 index 4f36f4c54d7..00000000000 --- a/test/integration/targets/netconf_get/tasks/iosxr.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: collect all netconf test cases - find: - paths: "{{ role_path }}/tests/iosxr" - patterns: "{{ testcase }}.yaml" - register: test_cases - connection: local - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test case (connection=netconf) - include: "{{ test_case_to_run }}" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_get/tasks/junos.yaml b/test/integration/targets/netconf_get/tasks/junos.yaml deleted file mode 100644 index 86c56f83a5e..00000000000 --- a/test/integration/targets/netconf_get/tasks/junos.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: collect all netconf test cases - find: - paths: "{{ role_path }}/tests/junos" - patterns: "{{ testcase }}.yaml" - register: test_cases - connection: local - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test case (connection=netconf) - include: "{{ test_case_to_run }} ansible_connection=netconf" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_get/tasks/main.yaml b/test/integration/targets/netconf_get/tasks/main.yaml deleted file mode 100644 index a34a2fecd60..00000000000 --- a/test/integration/targets/netconf_get/tasks/main.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -- { include: junos.yaml, when: ansible_network_os == 'junos', tags: ['netconf'] } -- { include: iosxr.yaml, when: ansible_network_os == 'iosxr', tags: ['netconf'] } -- { include: sros.yaml, when: ansible_network_os == 'sros', tags: ['netconf'] } diff --git a/test/integration/targets/netconf_get/tasks/sros.yaml b/test/integration/targets/netconf_get/tasks/sros.yaml deleted file mode 100644 index bc8728b82ed..00000000000 --- a/test/integration/targets/netconf_get/tasks/sros.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: collect all netconf test cases - find: - paths: "{{ role_path }}/tests/sros" - patterns: "{{ testcase }}.yaml" - register: test_cases - connection: local - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test case (connection=netconf) - include: "{{ test_case_to_run }} ansible_connection=netconf" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_get/tests/iosxr/basic.yaml b/test/integration/targets/netconf_get/tests/iosxr/basic.yaml deleted file mode 100644 index 0a6066d4fa8..00000000000 --- a/test/integration/targets/netconf_get/tests/iosxr/basic.yaml +++ /dev/null @@ -1,163 +0,0 @@ ---- -- debug: msg="START netconf_get iosxr/basic.yaml on connection={{ ansible_connection }}" - -- name: setup interface - iosxr_config: - commands: - - description this is test interface Loopback999 - - no shutdown - parents: - - interface Loopback999 - match: none - connection: network_cli - -- name: get running interface confiugration with filter - netconf_get: - source: running - filter: - register: result - connection: netconf - -- assert: - that: - - "'this is test interface Loopback999' in result.stdout" - - "'' not in result.stdout" - -- name: test lock=never, get-config, running interface confiugration with filter without lock - netconf_get: - source: running - lock: never - filter: - register: result - connection: netconf - -- assert: - that: - - "'this is test interface Loopback999' in result.stdout" - - "'' not in result.stdout" - -- name: test lock=if-supported, get-config, running interface confiugration with filter without lock - netconf_get: - source: running - lock: if-supported - filter: - register: result - connection: netconf - -- assert: - that: - - "'this is test interface Loopback999' in result.stdout" - - "'' not in result.stdout" - -- name: Failure scenario, get-config information with lock - netconf_get: - source: running - lock: always - register: result - ignore_errors: True - connection: netconf - -- assert: - that: - - "'running' in result.msg" - -- name: Failure scenario, fetch config from startup - netconf_get: - source: startup - register: result - ignore_errors: True - connection: netconf - -- assert: - that: - - "'startup source is not supported' in result.msg" - -- name: test get, information from running datastore without lock - netconf_get: - lock: never - filter: - register: result - connection: netconf - -- assert: - that: - - "'this is test interface Loopback999' in result.stdout" - -- name: test get, information from running datastore with lock if supported - netconf_get: - lock: if-supported - filter: - register: result - connection: netconf - -- assert: - that: - - "'this is test interface Loopback999' in result.stdout" - -- name: Failure scenario, get information from running with lock - netconf_get: - lock: always - register: result - ignore_errors: True - connection: netconf - -- assert: - that: - - "'running' in result.msg" - -- name: get configuration and state data in json format - netconf_get: - source: running - display: json - register: result - connection: netconf - -- assert: - that: - - "{{ result['output']['data']['aaa'] is defined}}" - -- name: get configuration data in xml pretty format - netconf_get: - source: running - display: pretty - register: result - connection: netconf - -- assert: - that: - - "{{ result['output'] is defined}}" - -- name: get configuration data in xml with namespace stripped - netconf_get: - source: running - display: xml - register: result - connection: netconf - -- assert: - that: - - "{{ result['output'] is defined}}" - - "{{ 'xmlns' not in result.output }}" - -- name: Failure scenario, unsupported filter - netconf_get: - filter: configuration/state - register: result - ignore_errors: True - connection: netconf - -- assert: - that: - - "'filter value \\'configuration/state\\' of type xpath is not supported' in result.msg" - -- name: setup - teardown - iosxr_config: - commands: - - no description - - shutdown - parents: - - interface Loopback999 - match: none - connection: network_cli - -- debug: msg="END netconf_get iosxr/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/integration/targets/netconf_get/tests/junos/basic.yaml b/test/integration/targets/netconf_get/tests/junos/basic.yaml deleted file mode 100644 index 3eee81e9209..00000000000 --- a/test/integration/targets/netconf_get/tests/junos/basic.yaml +++ /dev/null @@ -1,126 +0,0 @@ ---- -- debug: msg="START netconf_get junos/basic.yaml on connection={{ ansible_connection }}" - -- name: Configure syslog file - setup - junos_config: - lines: - - set system syslog file test1 any any - register: result - -- name: Get system configuration data from running datastore state - netconf_get: - source: running - filter: - register: result - -- assert: - that: - - "'test1' in result.stdout" - - "'any' in result.stdout" - - "'' in result.stdout" - - "'' not in result.stdout" - - "'' not in result.stdout" - -- name: Failure scenario, fetch config from startup - netconf_get: - source: startup - register: result - ignore_errors: True - -- assert: - that: - - "'startup source is not supported' in result.msg" - -- name: Failure scenario, fetch config from running with lock - netconf_get: - lock: always - source: running - register: result - ignore_errors: True - -- assert: - that: - - "'syntax error' in result.msg" - -- name: Get system configuration data from running datastore state and lock if-supported - netconf_get: - source: running - filter: - lock: if-supported - register: result - -- assert: - that: - - "'test1' in result.stdout" - - "'any' in result.stdout" - - "'' in result.stdout" - - "'' not in result.stdout" - - "'' not in result.stdout" - -- name: get configuration and state data in json format - netconf_get: - source: running - display: json - register: result - -- assert: - that: - - "{{ result['output']['rpc-reply']['data']['configuration'] is defined}}" - -- name: get configuration and state data in xml pretty format - netconf_get: - source: running - display: pretty - register: result - -- assert: - that: - - "{{ result['output'] is defined}}" - -- name: get configuration data in xml with namespace stripped - netconf_get: - source: running - display: xml - register: result - -- assert: - that: - - "{{ result['output'] is defined}}" - - "{{ 'xmlns' not in result.output }}" - -- name: get configuration and state data without datastore lock - netconf_get: - lock: never - register: result - -- assert: - that: - - "'' in result.stdout" - - "'' in result.stdout" - -- name: get configuration and state data and lock data-store if supported - netconf_get: - lock: if-supported - register: result - -- assert: - that: - - "'' in result.stdout" - - "'' in result.stdout" - -- name: Failure scenario, unsupported filter - netconf_get: - filter: configuration/state - register: result - ignore_errors: True - -- assert: - that: - - "'filter value \\'configuration/state\\' of type xpath is not supported' in result.msg" - -- name: Configure syslog file - teardown - junos_config: - lines: - - delete system syslog file test1 any any - -- debug: msg="END netconf_get junos/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/integration/targets/netconf_get/tests/sros/basic.yaml b/test/integration/targets/netconf_get/tests/sros/basic.yaml deleted file mode 100644 index 1a68ff32437..00000000000 --- a/test/integration/targets/netconf_get/tests/sros/basic.yaml +++ /dev/null @@ -1,56 +0,0 @@ ---- -- debug: msg="START netconf_get sros/basic.yaml on connection={{ ansible_connection }}" - -- name: Get complete configuration data (SROS) - netconf_get: - filter: - register: result - -- assert: - that: - - "'urn:nokia.com:sros:ns:yang:sr:conf' in result.stdout" - - "'urn:nokia.com:sros:ns:yang:sr:state' not in result.stdout" - -- name: Get complete state data (SROS) - netconf_get: - filter: - register: result - -- assert: - that: - - "'urn:nokia.com:sros:ns:yang:sr:state' in result.stdout" - - "'urn:nokia.com:sros:ns:yang:sr:conf' not in result.stdout" - -- name: Get service configuration data from candidate datastore (SROS) - netconf_get: - source: candidate - filter: - display: json - register: result - -- assert: - that: - - "'' in result.stdout" - -- name: Get system configuration data from running datastore (SROS) - netconf_get: - source: running - filter: - register: result - -- assert: - that: - - "'' in result.stdout" - -- name: Get complete configuration and state data (SROS) - netconf_get: - register: result - -- assert: - that: - - "'' in result.stdout" - - "'' in result.stdout" - - "'urn:nokia.com:sros:ns:yang:sr:conf' in result.stdout" - - "'urn:nokia.com:sros:ns:yang:sr:state' in result.stdout" - -- debug: msg="END netconf_get sros/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/integration/targets/netconf_rpc/defaults/main.yaml b/test/integration/targets/netconf_rpc/defaults/main.yaml deleted file mode 100644 index 5f709c5aac1..00000000000 --- a/test/integration/targets/netconf_rpc/defaults/main.yaml +++ /dev/null @@ -1,2 +0,0 @@ ---- -testcase: "*" diff --git a/test/integration/targets/netconf_rpc/meta/main.yml b/test/integration/targets/netconf_rpc/meta/main.yml deleted file mode 100644 index 3403f48112f..00000000000 --- a/test/integration/targets/netconf_rpc/meta/main.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -dependencies: - - { role: prepare_junos_tests, when: ansible_network_os == 'junos' } - - { role: prepare_iosxr_tests, when: ansible_network_os == 'iosxr' } diff --git a/test/integration/targets/netconf_rpc/tasks/iosxr.yaml b/test/integration/targets/netconf_rpc/tasks/iosxr.yaml deleted file mode 100644 index 78949855312..00000000000 --- a/test/integration/targets/netconf_rpc/tasks/iosxr.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: collect all netconf test cases - find: - paths: "{{ role_path }}/tests/iosxr" - patterns: "{{ testcase }}.yaml" - register: test_cases - connection: local - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test case (connection=netconf) - include: "{{ test_case_to_run }} ansible_connection=netconf" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_rpc/tasks/junos.yaml b/test/integration/targets/netconf_rpc/tasks/junos.yaml deleted file mode 100644 index 86c56f83a5e..00000000000 --- a/test/integration/targets/netconf_rpc/tasks/junos.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: collect all netconf test cases - find: - paths: "{{ role_path }}/tests/junos" - patterns: "{{ testcase }}.yaml" - register: test_cases - connection: local - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test case (connection=netconf) - include: "{{ test_case_to_run }} ansible_connection=netconf" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_rpc/tasks/main.yaml b/test/integration/targets/netconf_rpc/tasks/main.yaml deleted file mode 100644 index a34a2fecd60..00000000000 --- a/test/integration/targets/netconf_rpc/tasks/main.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -- { include: junos.yaml, when: ansible_network_os == 'junos', tags: ['netconf'] } -- { include: iosxr.yaml, when: ansible_network_os == 'iosxr', tags: ['netconf'] } -- { include: sros.yaml, when: ansible_network_os == 'sros', tags: ['netconf'] } diff --git a/test/integration/targets/netconf_rpc/tasks/sros.yaml b/test/integration/targets/netconf_rpc/tasks/sros.yaml deleted file mode 100644 index bc8728b82ed..00000000000 --- a/test/integration/targets/netconf_rpc/tasks/sros.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: collect all netconf test cases - find: - paths: "{{ role_path }}/tests/sros" - patterns: "{{ testcase }}.yaml" - register: test_cases - connection: local - -- name: set test_items - set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" - -- name: run test case (connection=netconf) - include: "{{ test_case_to_run }} ansible_connection=netconf" - with_items: "{{ test_items }}" - loop_control: - loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_rpc/tests/iosxr/basic.yaml b/test/integration/targets/netconf_rpc/tests/iosxr/basic.yaml deleted file mode 100644 index 992d051692e..00000000000 --- a/test/integration/targets/netconf_rpc/tests/iosxr/basic.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- debug: msg="START netconf_rpc iosxr/basic.yaml on connection={{ ansible_connection }}" - -- name: discard changes - netconf_rpc: - rpc: discard-changes - -- debug: msg="END netconf_rpc iosxr/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/integration/targets/netconf_rpc/tests/junos/basic.yaml b/test/integration/targets/netconf_rpc/tests/junos/basic.yaml deleted file mode 100644 index 956a1e424d9..00000000000 --- a/test/integration/targets/netconf_rpc/tests/junos/basic.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- debug: msg="START netconf_rpc junos/basic.yaml on connection={{ ansible_connection }}" - -- name: discard changes - netconf_rpc: - rpc: discard-changes - -- debug: msg="END netconf_rpc junos/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/integration/targets/netconf_rpc/tests/sros/basic.yaml b/test/integration/targets/netconf_rpc/tests/sros/basic.yaml deleted file mode 100644 index f7e58a3651e..00000000000 --- a/test/integration/targets/netconf_rpc/tests/sros/basic.yaml +++ /dev/null @@ -1,188 +0,0 @@ ---- -- debug: msg="START netconf_rpc sros/basic.yaml on connection={{ ansible_connection }}" - -- name: lock candidate (content is dict) - netconf_rpc: - rpc: lock - content: - target: - candidate: - register: result - connection: netconf - -- name: discard changes (w/o content) - netconf_rpc: - rpc: discard-changes - display: xml - register: result - connection: netconf - -- name: unlock candidate (content is dict as json) - netconf_rpc: - rpc: unlock - xmlns: "urn:ietf:params:xml:ns:netconf:base:1.0" - content: "{'target': {'candidate': None}}" - display: json - register: result - connection: netconf - -- assert: - that: - - "{{ result['output']['rpc-reply'] is defined}}" - - "{{ result['output']['rpc-reply']['ok'] is defined}}" - -- name: validate candidate (content is single line of XML) - netconf_rpc: - rpc: validate - content: "" - display: json - register: result - connection: netconf - -- assert: - that: - - "{{ result['output']['rpc-reply'] is defined}}" - - "{{ result['output']['rpc-reply']['ok'] is defined}}" - -- name: copy running to startup - netconf_rpc: - rpc: copy-config - content: - source: - running: - target: - startup: - register: result - connection: netconf - -- name: get schema list (content is multiple lines of XML) - netconf_rpc: - rpc: get - content: | - - - - - - display: json - register: result - connection: netconf - -- assert: - that: - - "{{ result['output']['data'] is defined}}" - - "{{ result['output']['data']['netconf-state'] is defined}}" - - "{{ result['output']['data']['netconf-state']['schemas'] is defined}}" - - "{{ result['output']['data']['netconf-state']['schemas']['schema'] is defined}}" - -# The following two test-cases have been validated against a pre-release implementation. -# To make this playbook work with the regular Nokia SROS 16.0 release, those test-cases -# have been commented out. As soon the operation is supported by SROS -# those test-cases shall be included. - -#- name: get-schema -# netconf_rpc: -# rpc: get-schema -# xmlns: urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring -# content: -# identifier: ietf-netconf -# version: "2011-06-01" -# register: result -# connection: netconf - -#- name: get schema using XML request -# netconf_rpc: -# rpc: "get-schema" -# xmlns: "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring" -# content: | -# ietf-netconf-monitoring -# 2010-10-04 -# display: pretty -# register: result -# connection: netconf - -- name: Failure scenario, unsupported content (xpath value) - netconf_rpc: - rpc: get - content: schemas/schema[identifier=ietf-netconf-monitoring] - register: result - connection: netconf - ignore_errors: True - -- assert: - that: - - "'unsupported content value' in result.msg" - -- name: Failure scenario, unsupported content type (list) - netconf_rpc: - rpc: get - content: - - value1 - - value2 - register: result - connection: netconf - ignore_errors: True - -- assert: - that: - - "'unsupported content data-type' in result.msg" - -- name: Failure scenario, RPC is close-session - netconf_rpc: - rpc: close-session - register: result - connection: netconf - ignore_errors: True - -- assert: - that: - - "'unsupported operation' in result.msg" - -- name: Failure scenario, attribute rpc missing - netconf_rpc: - display: json - register: result - connection: netconf - ignore_errors: True - -- assert: - that: - - "'missing required arguments' in result.msg" - -- name: Failure scenario, attribute rpc is None - netconf_rpc: - rpc: - display: json - register: result - connection: netconf - ignore_errors: True - -- assert: - that: - - "'must not be None' in result.msg" - -- name: Failure scenario, attribute rpc is zero-length string - netconf_rpc: - rpc: "" - display: json - register: result - connection: netconf - ignore_errors: True - -- assert: - that: - - "'must not be empty' in result.msg" - -- name: Failure scenario, attribute rpc only contains white-spaces - netconf_rpc: - rpc: " " - display: json - register: result - connection: netconf - ignore_errors: True - -- assert: - that: - - "'must not be empty' in result.msg" - -- debug: msg="END netconf_rpc sros/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 3448786dddc..7f5b28797a0 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -68,10 +68,6 @@ lib/ansible/module_utils/azure_rm_common_rest.py metaclass-boilerplate lib/ansible/module_utils/basic.py metaclass-boilerplate lib/ansible/module_utils/common/network.py future-import-boilerplate lib/ansible/module_utils/common/network.py metaclass-boilerplate -lib/ansible/module_utils/compat/ipaddress.py future-import-boilerplate -lib/ansible/module_utils/compat/ipaddress.py metaclass-boilerplate -lib/ansible/module_utils/compat/ipaddress.py no-assert -lib/ansible/module_utils/compat/ipaddress.py no-unicode-literals lib/ansible/module_utils/connection.py future-import-boilerplate lib/ansible/module_utils/connection.py metaclass-boilerplate lib/ansible/module_utils/distro/__init__.py empty-init # breaks namespacing, bundled, do not override @@ -102,20 +98,6 @@ lib/ansible/module_utils/netapp_module.py metaclass-boilerplate lib/ansible/module_utils/network/asa/asa.py future-import-boilerplate lib/ansible/module_utils/network/asa/asa.py metaclass-boilerplate lib/ansible/module_utils/network/checkpoint/checkpoint.py metaclass-boilerplate -lib/ansible/module_utils/network/common/cfg/base.py future-import-boilerplate -lib/ansible/module_utils/network/common/cfg/base.py metaclass-boilerplate -lib/ansible/module_utils/network/common/config.py future-import-boilerplate -lib/ansible/module_utils/network/common/config.py metaclass-boilerplate -lib/ansible/module_utils/network/common/facts/facts.py future-import-boilerplate -lib/ansible/module_utils/network/common/facts/facts.py metaclass-boilerplate -lib/ansible/module_utils/network/common/netconf.py future-import-boilerplate -lib/ansible/module_utils/network/common/netconf.py metaclass-boilerplate -lib/ansible/module_utils/network/common/network.py future-import-boilerplate -lib/ansible/module_utils/network/common/network.py metaclass-boilerplate -lib/ansible/module_utils/network/common/parsing.py future-import-boilerplate -lib/ansible/module_utils/network/common/parsing.py metaclass-boilerplate -lib/ansible/module_utils/network/common/utils.py future-import-boilerplate -lib/ansible/module_utils/network/common/utils.py metaclass-boilerplate lib/ansible/module_utils/network/dellos10/dellos10.py future-import-boilerplate lib/ansible/module_utils/network/dellos10/dellos10.py metaclass-boilerplate lib/ansible/module_utils/network/dellos6/dellos6.py future-import-boilerplate @@ -176,8 +158,6 @@ lib/ansible/module_utils/network/junos/junos.py future-import-boilerplate lib/ansible/module_utils/network/junos/junos.py metaclass-boilerplate lib/ansible/module_utils/network/meraki/meraki.py future-import-boilerplate lib/ansible/module_utils/network/meraki/meraki.py metaclass-boilerplate -lib/ansible/module_utils/network/netconf/netconf.py future-import-boilerplate -lib/ansible/module_utils/network/netconf/netconf.py metaclass-boilerplate lib/ansible/module_utils/network/nxos/argspec/facts/facts.py future-import-boilerplate lib/ansible/module_utils/network/nxos/argspec/facts/facts.py metaclass-boilerplate lib/ansible/module_utils/network/nxos/facts/facts.py future-import-boilerplate @@ -188,8 +168,6 @@ lib/ansible/module_utils/network/nxos/nxos.py future-import-boilerplate lib/ansible/module_utils/network/nxos/nxos.py metaclass-boilerplate lib/ansible/module_utils/network/nxos/utils/utils.py future-import-boilerplate lib/ansible/module_utils/network/nxos/utils/utils.py metaclass-boilerplate -lib/ansible/module_utils/network/restconf/restconf.py future-import-boilerplate -lib/ansible/module_utils/network/restconf/restconf.py metaclass-boilerplate lib/ansible/module_utils/network/skydive/api.py future-import-boilerplate lib/ansible/module_utils/network/skydive/api.py metaclass-boilerplate lib/ansible/module_utils/network/vyos/vyos.py future-import-boilerplate @@ -1766,11 +1744,6 @@ lib/ansible/modules/network/check_point/cp_mgmt_vpn_community_star.py validate-m lib/ansible/modules/network/check_point/cp_mgmt_vpn_community_star_facts.py validate-modules:parameter-list-no-elements lib/ansible/modules/network/check_point/cp_mgmt_wildcard.py validate-modules:parameter-list-no-elements lib/ansible/modules/network/check_point/cp_mgmt_wildcard_facts.py validate-modules:parameter-list-no-elements -lib/ansible/modules/network/cli/cli_command.py validate-modules:parameter-list-no-elements -lib/ansible/modules/network/cli/cli_command.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/network/cli/cli_config.py validate-modules:doc-missing-type -lib/ansible/modules/network/cli/cli_config.py validate-modules:parameter-list-no-elements -lib/ansible/modules/network/cli/cli_config.py validate-modules:parameter-type-not-in-doc lib/ansible/modules/network/dellos10/dellos10_command.py validate-modules:doc-default-does-not-match-spec lib/ansible/modules/network/dellos10/dellos10_command.py validate-modules:doc-missing-type lib/ansible/modules/network/dellos10/dellos10_command.py validate-modules:doc-required-mismatch @@ -2825,18 +2798,6 @@ lib/ansible/modules/network/meraki/meraki_vlan.py validate-modules:doc-elements- lib/ansible/modules/network/meraki/meraki_vlan.py validate-modules:missing-suboption-docs lib/ansible/modules/network/meraki/meraki_vlan.py validate-modules:parameter-type-not-in-doc lib/ansible/modules/network/meraki/meraki_vlan.py validate-modules:undocumented-parameter -lib/ansible/modules/network/netconf/netconf_config.py validate-modules:doc-choices-do-not-match-spec -lib/ansible/modules/network/netconf/netconf_config.py validate-modules:doc-missing-type -lib/ansible/modules/network/netconf/netconf_config.py validate-modules:doc-required-mismatch -lib/ansible/modules/network/netconf/netconf_config.py validate-modules:mutually_exclusive-unknown -lib/ansible/modules/network/netconf/netconf_config.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/network/netconf/netconf_config.py validate-modules:required_one_of-unknown -lib/ansible/modules/network/netconf/netconf_get.py validate-modules:doc-missing-type -lib/ansible/modules/network/netconf/netconf_get.py validate-modules:return-syntax-error -lib/ansible/modules/network/netconf/netconf_rpc.py validate-modules:doc-missing-type -lib/ansible/modules/network/netconf/netconf_rpc.py validate-modules:doc-required-mismatch -lib/ansible/modules/network/netconf/netconf_rpc.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/network/netconf/netconf_rpc.py validate-modules:return-syntax-error lib/ansible/modules/network/nxos/_nxos_interface.py validate-modules:doc-choices-do-not-match-spec lib/ansible/modules/network/nxos/_nxos_interface.py validate-modules:doc-default-does-not-match-spec lib/ansible/modules/network/nxos/_nxos_interface.py validate-modules:doc-default-incompatible-type @@ -3359,8 +3320,6 @@ lib/ansible/modules/network/ovs/openvswitch_db.py validate-modules:doc-missing-t lib/ansible/modules/network/ovs/openvswitch_db.py validate-modules:parameter-type-not-in-doc lib/ansible/modules/network/ovs/openvswitch_port.py validate-modules:doc-missing-type lib/ansible/modules/network/ovs/openvswitch_port.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/network/restconf/restconf_config.py validate-modules:doc-missing-type -lib/ansible/modules/network/restconf/restconf_get.py validate-modules:doc-missing-type lib/ansible/modules/network/skydive/skydive_capture.py validate-modules:doc-missing-type lib/ansible/modules/network/skydive/skydive_capture.py validate-modules:doc-required-mismatch lib/ansible/modules/network/skydive/skydive_capture.py validate-modules:invalid-ansiblemodule-schema @@ -3975,9 +3934,6 @@ lib/ansible/plugins/action/eos.py action-plugin-docs # base class for deprecated lib/ansible/plugins/action/ios.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` lib/ansible/plugins/action/iosxr.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` lib/ansible/plugins/action/junos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -lib/ansible/plugins/action/net_base.py action-plugin-docs # base class for other net_* action plugins which have a matching module -lib/ansible/plugins/action/netconf.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -lib/ansible/plugins/action/network.py action-plugin-docs # base class for network action plugins lib/ansible/plugins/action/normal.py action-plugin-docs # default action plugin for modules without a dedicated action plugin lib/ansible/plugins/action/nxos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` lib/ansible/plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` @@ -4026,10 +3982,6 @@ lib/ansible/plugins/doc_fragments/meraki.py future-import-boilerplate lib/ansible/plugins/doc_fragments/meraki.py metaclass-boilerplate lib/ansible/plugins/doc_fragments/netapp.py future-import-boilerplate lib/ansible/plugins/doc_fragments/netapp.py metaclass-boilerplate -lib/ansible/plugins/doc_fragments/netconf.py future-import-boilerplate -lib/ansible/plugins/doc_fragments/netconf.py metaclass-boilerplate -lib/ansible/plugins/doc_fragments/network_agnostic.py future-import-boilerplate -lib/ansible/plugins/doc_fragments/network_agnostic.py metaclass-boilerplate lib/ansible/plugins/doc_fragments/nxos.py future-import-boilerplate lib/ansible/plugins/doc_fragments/nxos.py metaclass-boilerplate lib/ansible/plugins/doc_fragments/openstack.py future-import-boilerplate diff --git a/test/units/module_utils/network/common/test_parsing.py b/test/units/module_utils/network/common/test_parsing.py deleted file mode 100644 index 34dd0db30be..00000000000 --- a/test/units/module_utils/network/common/test_parsing.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# -# (c) 2017 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 . - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -from units.compat import unittest -from ansible.module_utils.network.common.parsing import Conditional - -test_results = ['result_1', 'result_2', 'result_3'] -c1 = Conditional('result[1] == result_2') -c2 = Conditional('result[2] not == result_2') -c3 = Conditional('result[0] neq not result_1') - - -class TestNotKeyword(unittest.TestCase): - def test_negate_instance_variable_assignment(self): - assert c1.negate is False and c2.negate is True - - def test_key_value_instance_variable_assignment(self): - c1_assignments = c1.key == 'result[1]' and c1.value == 'result_2' - c2_assignments = c2.key == 'result[2]' and c2.value == 'result_2' - assert c1_assignments and c2_assignments - - def test_conditionals_w_not_keyword(self): - assert c1(test_results) and c2(test_results) and c3(test_results) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/units/module_utils/network/common/test_utils.py b/test/units/module_utils/network/common/test_utils.py deleted file mode 100644 index f34834ce5f0..00000000000 --- a/test/units/module_utils/network/common/test_utils.py +++ /dev/null @@ -1,213 +0,0 @@ -# -*- coding: utf-8 -*- -# -# (c) 2017 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 . - -# Make coding more python3-ish -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -import pytest - -from ansible.module_utils.network.common.utils import to_list, sort_list -from ansible.module_utils.network.common.utils import dict_diff, dict_merge -from ansible.module_utils.network.common.utils import conditional, Template -from ansible.module_utils.common.network import ( - to_masklen, to_netmask, to_subnet, to_ipv6_network, to_ipv6_subnet, is_masklen, is_netmask -) - - -def test_to_list(): - for scalar in ('string', 1, True, False, None): - assert isinstance(to_list(scalar), list) - - for container in ([1, 2, 3], {'one': 1}): - assert isinstance(to_list(container), list) - - test_list = [1, 2, 3] - assert id(test_list) != id(to_list(test_list)) - - -def test_sort(): - data = [3, 1, 2] - assert [1, 2, 3] == sort_list(data) - - string_data = '123' - assert string_data == sort_list(string_data) - - -def test_dict_diff(): - base = dict(obj2=dict(), b1=True, b2=False, b3=False, - one=1, two=2, three=3, obj1=dict(key1=1, key2=2), - l1=[1, 3], l2=[1, 2, 3], l4=[4], - nested=dict(n1=dict(n2=2))) - - other = dict(b1=True, b2=False, b3=True, b4=True, - one=1, three=4, four=4, obj1=dict(key1=2), - l1=[2, 1], l2=[3, 2, 1], l3=[1], - nested=dict(n1=dict(n2=2, n3=3))) - - result = dict_diff(base, other) - - # string assertions - assert 'one' not in result - assert 'two' not in result - assert result['three'] == 4 - assert result['four'] == 4 - - # dict assertions - assert 'obj1' in result - assert 'key1' in result['obj1'] - assert 'key2' not in result['obj1'] - - # list assertions - assert result['l1'] == [2, 1] - assert 'l2' not in result - assert result['l3'] == [1] - assert 'l4' not in result - - # nested assertions - assert 'obj1' in result - assert result['obj1']['key1'] == 2 - assert 'key2' not in result['obj1'] - - # bool assertions - assert 'b1' not in result - assert 'b2' not in result - assert result['b3'] - assert result['b4'] - - -def test_dict_merge(): - base = dict(obj2=dict(), b1=True, b2=False, b3=False, - one=1, two=2, three=3, obj1=dict(key1=1, key2=2), - l1=[1, 3], l2=[1, 2, 3], l4=[4], - nested=dict(n1=dict(n2=2))) - - other = dict(b1=True, b2=False, b3=True, b4=True, - one=1, three=4, four=4, obj1=dict(key1=2), - l1=[2, 1], l2=[3, 2, 1], l3=[1], - nested=dict(n1=dict(n2=2, n3=3))) - - result = dict_merge(base, other) - - # string assertions - assert 'one' in result - assert 'two' in result - assert result['three'] == 4 - assert result['four'] == 4 - - # dict assertions - assert 'obj1' in result - assert 'key1' in result['obj1'] - assert 'key2' in result['obj1'] - - # list assertions - assert result['l1'] == [1, 2, 3] - assert 'l2' in result - assert result['l3'] == [1] - assert 'l4' in result - - # nested assertions - assert 'obj1' in result - assert result['obj1']['key1'] == 2 - assert 'key2' in result['obj1'] - - # bool assertions - assert 'b1' in result - assert 'b2' in result - assert result['b3'] - assert result['b4'] - - -def test_conditional(): - assert conditional(10, 10) - assert conditional('10', '10') - assert conditional('foo', 'foo') - assert conditional(True, True) - assert conditional(False, False) - assert conditional(None, None) - assert conditional("ge(1)", 1) - assert conditional("gt(1)", 2) - assert conditional("le(2)", 2) - assert conditional("lt(3)", 2) - assert conditional("eq(1)", 1) - assert conditional("neq(0)", 1) - assert conditional("min(1)", 1) - assert conditional("max(1)", 1) - assert conditional("exactly(1)", 1) - - -def test_template(): - tmpl = Template() - assert 'foo' == tmpl('{{ test }}', {'test': 'foo'}) - - -def test_to_masklen(): - assert 24 == to_masklen('255.255.255.0') - - -def test_to_masklen_invalid(): - with pytest.raises(ValueError): - to_masklen('255') - - -def test_to_netmask(): - assert '255.0.0.0' == to_netmask(8) - assert '255.0.0.0' == to_netmask('8') - - -def test_to_netmask_invalid(): - with pytest.raises(ValueError): - to_netmask(128) - - -def test_to_subnet(): - result = to_subnet('192.168.1.1', 24) - assert '192.168.1.0/24' == result - - result = to_subnet('192.168.1.1', 24, dotted_notation=True) - assert '192.168.1.0 255.255.255.0' == result - - -def test_to_subnet_invalid(): - with pytest.raises(ValueError): - to_subnet('foo', 'bar') - - -def test_is_masklen(): - assert is_masklen(32) - assert not is_masklen(33) - assert not is_masklen('foo') - - -def test_is_netmask(): - assert is_netmask('255.255.255.255') - assert not is_netmask(24) - assert not is_netmask('foo') - - -def test_to_ipv6_network(): - assert '2001:db8::' == to_ipv6_network('2001:db8::') - assert '2001:0db8:85a3::' == to_ipv6_network('2001:0db8:85a3:0000:0000:8a2e:0370:7334') - assert '2001:0db8:85a3::' == to_ipv6_network('2001:0db8:85a3:0:0:8a2e:0370:7334') - - -def test_to_ipv6_subnet(): - assert '2001:db8::' == to_ipv6_subnet('2001:db8::') - assert '2001:0db8:85a3:4242::' == to_ipv6_subnet('2001:0db8:85a3:4242:0000:8a2e:0370:7334') - assert '2001:0db8:85a3:4242::' == to_ipv6_subnet('2001:0db8:85a3:4242:0:8a2e:0370:7334') diff --git a/test/units/modules/network/cli/cli_module.py b/test/units/modules/network/cli/cli_module.py deleted file mode 100644 index e92bc8ffc0f..00000000000 --- a/test/units/modules/network/cli/cli_module.py +++ /dev/null @@ -1,88 +0,0 @@ -# (c) 2016 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import json -import os - -from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase - - -fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') -fixture_data = {} - - -def load_fixture(name): - path = os.path.join(fixture_path, name) - - if path in fixture_data: - return fixture_data[path] - - with open(path) as fixture: - data = fixture.read() - - try: - data = json.loads(data) - except ValueError: - pass - - fixture_data[path] = data - return data - - -class TestCliModule(ModuleTestCase): - - def execute_module(self, failed=False, changed=False, commands=None, sort=True): - - self.load_fixtures(commands) - - if failed: - result = self.failed() - self.assertTrue(result['failed'], result) - else: - result = self.changed(changed) - self.assertEqual(result['changed'], changed, result) - - if commands is not None: - if sort: - self.assertEqual(sorted(commands), sorted(result['commands']), result['commands']) - else: - self.assertEqual(commands, result['commands'], result['commands']) - - return result - - def failed(self): - with self.assertRaises(AnsibleFailJson) as exc: - self.module.main() - - result = exc.exception.args[0] - self.assertTrue(result['failed'], result) - return result - - def changed(self, changed=False): - with self.assertRaises(AnsibleExitJson) as exc: - self.module.main() - - result = exc.exception.args[0] - self.assertEqual(result['changed'], changed, result) - return result - - def load_fixtures(self, commands=None): - pass diff --git a/test/units/modules/network/cli/test_cli_config.py b/test/units/modules/network/cli/test_cli_config.py deleted file mode 100644 index 3e21fadc72e..00000000000 --- a/test/units/modules/network/cli/test_cli_config.py +++ /dev/null @@ -1,55 +0,0 @@ -# (c) 2016 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from units.compat.mock import patch, MagicMock -from ansible.modules.network.cli import cli_config -from units.modules.utils import set_module_args -from .cli_module import TestCliModule - - -class TestCliConfigModule(TestCliModule): - - module = cli_config - - def setUp(self): - super(TestCliConfigModule, self).setUp() - - self.mock_connection = patch('ansible.modules.network.cli.cli_config.Connection') - self.get_connection = self.mock_connection.start() - - self.conn = self.get_connection() - - def tearDown(self): - super(TestCliConfigModule, self).tearDown() - - self.mock_connection.stop() - - @patch('ansible.modules.network.cli.cli_config.run') - def test_cli_config_backup_returns__backup__(self, run_mock): - self.conn.get_capabilities = MagicMock(return_value='{}') - - args = dict(backup=True) - set_module_args(args) - - run_mock.return_value = {} - - result = self.execute_module() - self.assertIn('__backup__', result) diff --git a/test/units/plugins/connection/test_httpapi.py b/test/units/plugins/connection/test_httpapi.py deleted file mode 100644 index efdc7c95a12..00000000000 --- a/test/units/plugins/connection/test_httpapi.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# (c) 2020 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from io import StringIO -import pytest - -from units.compat import unittest -from ansible.plugins.connection import httpapi -from ansible.playbook.play_context import PlayContext - - -class TestHttpApiConnectionClass(unittest.TestCase): - - def test_httpapi_connection_module(self): - play_context = PlayContext() - play_context.prompt = ( - '[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: ' - ) - play_context.network_os = 'eos' - in_stream = StringIO() - - self.assertIsInstance(httpapi.Connection(play_context, in_stream), httpapi.Connection) diff --git a/test/units/plugins/connection/test_netconf.py b/test/units/plugins/connection/test_netconf.py deleted file mode 100644 index bf8eee03110..00000000000 --- a/test/units/plugins/connection/test_netconf.py +++ /dev/null @@ -1,121 +0,0 @@ -# -# (c) 2016 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from io import StringIO -import sys -import pytest - -from units.compat import unittest -from units.compat.mock import patch, MagicMock, PropertyMock -from ansible.playbook.play_context import PlayContext - -pytest.importorskip("ncclient") - -PY3 = sys.version_info[0] == 3 - -builtin_import = __import__ - -mock_ncclient = MagicMock(name='ncclient') - - -def import_mock(name, *args): - if name.startswith('ncclient'): - return mock_ncclient - return builtin_import(name, *args) - - -if PY3: - with patch('builtins.__import__', side_effect=import_mock): - from ansible.plugins.connection import netconf - from ansible.plugins.loader import connection_loader -else: - with patch('__builtin__.__import__', side_effect=import_mock): - from ansible.plugins.connection import netconf - from ansible.plugins.loader import connection_loader - - -class TestNetconfConnectionClass(unittest.TestCase): - - def test_netconf_connection_module(self): - play_context = PlayContext() - play_context.prompt = ( - '[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: ' - ) - in_stream = StringIO() - - self.assertIsInstance(netconf.Connection(play_context, in_stream), netconf.Connection) - - def test_netconf_init(self): - pc = PlayContext() - conn = connection_loader.get('netconf', pc, '/dev/null') - - self.assertEqual('auto', conn._network_os) - self.assertIsNone(conn._manager) - self.assertFalse(conn._connected) - - @patch("ansible.plugins.connection.netconf.netconf_loader") - def test_netconf__connect(self, mock_netconf_loader): - pc = PlayContext() - conn = connection_loader.get('netconf', pc, '/dev/null') - - mock_manager = MagicMock() - mock_manager.session_id = '123456789' - netconf.manager.connect = MagicMock(return_value=mock_manager) - - rc, out, err = conn._connect() - - self.assertEqual(0, rc) - self.assertEqual(b'123456789', out) - self.assertEqual(b'', err) - self.assertTrue(conn._connected) - - def test_netconf_exec_command(self): - pc = PlayContext() - conn = connection_loader.get('netconf', pc, '/dev/null') - - conn._connected = True - - mock_reply = MagicMock(name='reply') - type(mock_reply).data_xml = PropertyMock(return_value='') - - mock_manager = MagicMock(name='self._manager') - mock_manager.rpc.return_value = mock_reply - conn._manager = mock_manager - - out = conn.exec_command('') - - self.assertEqual('', out) - - def test_netconf_exec_command_invalid_request(self): - pc = PlayContext() - conn = connection_loader.get('netconf', pc, '/dev/null') - - conn._connected = True - - mock_manager = MagicMock(name='self._manager') - conn._manager = mock_manager - - netconf.to_ele.return_value = None - - out = conn.exec_command('test string') - - self.assertEqual('unable to parse request', out) diff --git a/test/units/plugins/connection/test_network_cli.py b/test/units/plugins/connection/test_network_cli.py deleted file mode 100644 index f675561ae98..00000000000 --- a/test/units/plugins/connection/test_network_cli.py +++ /dev/null @@ -1,152 +0,0 @@ -# -# (c) 2016 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from io import StringIO -import re -import json - -from units.compat import unittest -from units.compat.mock import patch, MagicMock - -from ansible.module_utils._text import to_text -from ansible.errors import AnsibleConnectionFailure -from ansible.playbook.play_context import PlayContext -from ansible.plugins.connection import network_cli -from ansible.plugins.loader import connection_loader - - -class TestConnectionClass(unittest.TestCase): - - def test_network_cli_connection_module(self): - play_context = PlayContext() - play_context.prompt = ( - '[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: ' - ) - play_context.network_os = 'eos' - in_stream = StringIO() - - self.assertIsInstance(network_cli.Connection(play_context, in_stream), network_cli.Connection) - - def test_network_cli__invalid_os(self): - pc = PlayContext() - pc.network_os = 'does not exist' - - self.assertRaises(AnsibleConnectionFailure, connection_loader.get, 'network_cli', pc, '/dev/null') - - def test_network_cli__no_os(self): - pc = PlayContext() - pc.network_os = None - - self.assertRaises(AnsibleConnectionFailure, connection_loader.get, 'network_cli', pc, '/dev/null') - - @patch("ansible.plugins.connection.network_cli.terminal_loader") - @patch("ansible.plugins.connection.paramiko_ssh.Connection._connect") - def test_network_cli__connect(self, mocked_super, mocked_terminal_loader): - pc = PlayContext() - pc.network_os = 'ios' - conn = connection_loader.get('network_cli', pc, '/dev/null') - - conn.ssh = MagicMock() - conn.receive = MagicMock() - - conn._connect() - self.assertTrue(conn._terminal.on_open_shell.called) - self.assertFalse(conn._terminal.on_become.called) - - conn._play_context.become = True - conn._play_context.become_method = 'enable' - conn._play_context.become_pass = 'password' - conn._connected = False - - conn._connect() - conn._terminal.on_become.assert_called_with(passwd='password') - - @patch("ansible.plugins.connection.paramiko_ssh.Connection.close") - def test_network_cli_close(self, mocked_super): - pc = PlayContext() - pc.network_os = 'ios' - conn = connection_loader.get('network_cli', pc, '/dev/null') - - terminal = MagicMock(supports_multiplexing=False) - conn._terminal = terminal - conn._ssh_shell = MagicMock() - conn._paramiko_conn = MagicMock() - conn._connected = True - - conn.close() - self.assertTrue(terminal.on_close_shell.called) - self.assertIsNone(conn._ssh_shell) - self.assertIsNone(conn._paramiko_conn) - - @patch("ansible.plugins.connection.paramiko_ssh.Connection._connect") - def test_network_cli_exec_command(self, mocked_super): - pc = PlayContext() - pc.network_os = 'ios' - conn = connection_loader.get('network_cli', pc, '/dev/null') - - mock_send = MagicMock(return_value=b'command response') - conn.send = mock_send - conn._ssh_shell = MagicMock() - - # test sending a single command and converting to dict - out = conn.exec_command('command') - self.assertEqual(out, b'command response') - mock_send.assert_called_with(command=b'command') - - # test sending a json string - out = conn.exec_command(json.dumps({'command': 'command'})) - self.assertEqual(out, b'command response') - mock_send.assert_called_with(command=b'command') - - @patch("ansible.plugins.connection.network_cli.Connection._get_terminal_std_re") - @patch("ansible.plugins.connection.network_cli.Connection._connect") - def test_network_cli_send(self, mocked_connect, mocked_terminal_re): - - pc = PlayContext() - pc.network_os = 'ios' - conn = connection_loader.get('network_cli', pc, '/dev/null') - - mock__terminal = MagicMock() - mocked_terminal_re.side_effect = [[re.compile(b'^ERROR')], [re.compile(b'device#')]] - conn._terminal = mock__terminal - - mock__shell = MagicMock() - conn._ssh_shell = mock__shell - - response = b"""device#command - command response - - device# - """ - - mock__shell.recv.side_effect = [response, None] - conn.send(b'command') - - mock__shell.sendall.assert_called_with(b'command\r') - self.assertEqual(to_text(conn._command_response), 'command response') - - mock__shell.reset_mock() - mock__shell.recv.side_effect = [b"ERROR: error message device#"] - mocked_terminal_re.side_effect = [[re.compile(b'^ERROR')], [re.compile(b'device#')]] - with self.assertRaises(AnsibleConnectionFailure) as exc: - conn.send(b'command') - self.assertEqual(str(exc.exception), 'ERROR: error message device#') diff --git a/test/units/plugins/filter/fixtures/network/show_vlans_xml_output.txt b/test/units/plugins/filter/fixtures/network/show_vlans_xml_output.txt deleted file mode 100644 index b426dcca81d..00000000000 --- a/test/units/plugins/filter/fixtures/network/show_vlans_xml_output.txt +++ /dev/null @@ -1,34 +0,0 @@ - - - - - test-1 - 100 - - - test-2 - - - test-3 - 300 - test vlan-3 - - em3.0 - - - - test-4 - test vlan-4 - 400 - - - test-5 - test vlan-5 - 500 - - em5.0 - - - - - diff --git a/test/units/plugins/filter/fixtures/network/show_vlans_xml_single_value_spec.yml b/test/units/plugins/filter/fixtures/network/show_vlans_xml_single_value_spec.yml deleted file mode 100644 index c320eb10297..00000000000 --- a/test/units/plugins/filter/fixtures/network/show_vlans_xml_single_value_spec.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -vars: - vlan: "{{ item.name }}" - -keys: - vlans: - type: list - value: "{{ vlan }}" - top: configuration/vlans/vlan - items: - name: name diff --git a/test/units/plugins/filter/fixtures/network/show_vlans_xml_spec.yml b/test/units/plugins/filter/fixtures/network/show_vlans_xml_spec.yml deleted file mode 100644 index 555e3547d0c..00000000000 --- a/test/units/plugins/filter/fixtures/network/show_vlans_xml_spec.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -vars: - vlan: - vlan_id: "{{ item.vlan_id }}" - name: "{{ item.name }}" - desc: "{{ item.desc }}" - interface: "{{ item.intf }}" - enabled: "{{ item.state.get('inactive') != 'inactive' }}" - state: "{% if item.state.get('inactive') == 'inactive'%}inactive{% else %}active{% endif %}" - -keys: - vlans: - type: list - value: "{{ vlan }}" - top: configuration/vlans/vlan - items: - vlan_id: vlan-id - name: name - desc: description - intf: interface/name - state: ".[@inactive='inactive']" diff --git a/test/units/plugins/filter/fixtures/network/show_vlans_xml_with_condition_spec.yml b/test/units/plugins/filter/fixtures/network/show_vlans_xml_with_condition_spec.yml deleted file mode 100644 index d1d3fe225e3..00000000000 --- a/test/units/plugins/filter/fixtures/network/show_vlans_xml_with_condition_spec.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -vars: - vlan: - vlan_id: "{{ item.vlan_id }}" - name: "{{ item.name }}" - desc: "{{ item.desc }}" - interface: "{{ item.intf }}" - enabled: "{{ item.state.get('inactive') != 'inactive' }}" - state: "{% if item.state.get('inactive') == 'inactive'%}inactive{% else %}active{% endif %}" - -keys: - vlans: - type: list - value: "{{ vlan }}" - top: configuration/vlans/vlan - items: - vlan_id: vlan-id - name: name - desc: description - intf: interface/name - state: ".[@inactive='inactive']" - when: item.name == 'test-5' diff --git a/test/units/plugins/filter/fixtures/network/show_vlans_xml_with_key_spec.yml b/test/units/plugins/filter/fixtures/network/show_vlans_xml_with_key_spec.yml deleted file mode 100644 index 341498a9ad7..00000000000 --- a/test/units/plugins/filter/fixtures/network/show_vlans_xml_with_key_spec.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -vars: - vlan: - key: "{{ item.name }}" - values: - vlan_id: "{{ item.vlan_id }}" - name: "{{ item.name }}" - desc: "{{ item.desc }}" - interface: "{{ item.intf }}" - enabled: "{{ item.state.get('inactive') != 'inactive' }}" - state: "{% if item.state.get('inactive') == 'inactive'%}inactive{% else %}active{% endif %}" - -keys: - vlans: - type: list - value: "{{ vlan }}" - top: configuration/vlans/vlan - items: - vlan_id: vlan-id - name: name - desc: description - intf: interface/name - state: ".[@inactive='inactive']" diff --git a/test/units/plugins/filter/test_ipaddr.py b/test/units/plugins/filter/test_ipaddr.py deleted file mode 100644 index bdc024b06e4..00000000000 --- a/test/units/plugins/filter/test_ipaddr.py +++ /dev/null @@ -1,577 +0,0 @@ -# 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import sys -import pytest - -from units.compat import unittest -from ansible.errors import AnsibleFilterError -from ansible.plugins.filter.ipaddr import (ipaddr, next_nth_usable, ipsubnet, - previous_nth_usable, network_in_usable, network_in_network, - cidr_merge, ipmath) -netaddr = pytest.importorskip('netaddr') - - -class TestIpFilter(unittest.TestCase): - def test_netmask(self): - address = '1.1.1.1/24' - self.assertEqual(ipaddr(address, 'netmask'), '255.255.255.0') - address = '1.1.1.1/25' - self.assertEqual(ipaddr(address, 'netmask'), '255.255.255.128') - address = '1.12.1.34/32' - self.assertEqual(ipaddr(address, 'netmask'), '255.255.255.255') - - def test_network(self): - address = '1.12.1.34/32' - self.assertEqual(ipaddr(address, 'network'), '1.12.1.34') - address = '1.12.1.34/255.255.255.255' - self.assertEqual(ipaddr(address, 'network'), '1.12.1.34') - address = '1.12.1.34' - self.assertEqual(ipaddr(address, 'network'), '1.12.1.34') - address = '1.12.1.35/31' - self.assertEqual(ipaddr(address, 'network'), '1.12.1.34') - address = '1.12.1.34/24' - self.assertEqual(ipaddr(address, 'network'), '1.12.1.0') - - def test_broadcast(self): - address = '1.12.1.34/24' - self.assertEqual(ipaddr(address, 'broadcast'), '1.12.1.255') - address = '1.12.1.34/16' - self.assertEqual(ipaddr(address, 'broadcast'), '1.12.255.255') - address = '1.12.1.34/27' - self.assertEqual(ipaddr(address, 'broadcast'), '1.12.1.63') - address = '1.12.1.34/32' - self.assertEqual(ipaddr(address, 'broadcast'), None) - address = '1.12.1.35/31' - self.assertEqual(ipaddr(address, 'broadcast'), None) - - def test_first_usable(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'first_usable'), '1.12.1.1') - address = '1.12.1.36/24' - self.assertEqual(ipaddr(address, 'first_usable'), '1.12.1.1') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'first_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'first_usable'), '1.12.1.33') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'first_usable'), '1.12.1.33') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'first_usable'), '1.12.1.36') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'first_usable'), '1.12.1.36') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'first_usable'), None) - - def test_last_usable(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'last_usable'), '1.12.1.254') - address = '1.12.1.36/24' - self.assertEqual(ipaddr(address, 'last_usable'), '1.12.1.254') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'last_usable'), '1.12.1.46') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'last_usable'), '1.12.1.46') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'last_usable'), '1.12.1.37') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'last_usable'), '1.12.1.37') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'last_usable'), None) - - def test_wildcard(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'wildcard'), '0.0.0.255') - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'wildcard'), '0.0.0.127') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'wildcard'), '0.0.0.15') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'wildcard'), '0.0.0.15') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'wildcard'), '0.0.0.1') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'wildcard'), '0.0.0.1') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'wildcard'), '0.0.0.0') - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'wildcard'), '0.0.0.255') - - def test_size_usable(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'size_usable'), 254) - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'size_usable'), 126) - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'size_usable'), 14) - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'size_usable'), 14) - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'size_usable'), 2) - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'size_usable'), 2) - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'size_usable'), 0) - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'size_usable'), 254) - - def test_range_usable(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'range_usable'), '1.12.1.1-1.12.1.254') - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'range_usable'), '1.12.1.1-1.12.1.126') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'range_usable'), '1.12.1.33-1.12.1.46') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'range_usable'), '1.12.1.33-1.12.1.46') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'range_usable'), '1.12.1.36-1.12.1.37') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'range_usable'), '1.12.1.36-1.12.1.37') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'range_usable'), None) - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'range_usable'), '1.12.1.1-1.12.1.254') - - def test_address_prefix(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'address/prefix'), None) - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'address/prefix'), None) - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'address/prefix'), '1.12.1.36/28') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'address/prefix'), '1.12.1.36/28') - # address = '1.12.1.36/31' - # self.assertEqual(ipaddr(address, 'address/prefix'), '1.12.1.36/31') - unfixable? - # address = '1.12.1.37/31' - # self.assertEqual(ipaddr(address, 'address/prefix'), '1.12.1.37/31') - unfixable? - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'address/prefix'), None) - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'address/prefix'), '1.12.1.254/24') - - def test_ip_prefix(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'ip/prefix'), None) - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'ip/prefix'), None) - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'ip/prefix'), '1.12.1.36/28') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'ip/prefix'), '1.12.1.36/28') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'ip/prefix'), '1.12.1.36/31') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'ip/prefix'), '1.12.1.37/31') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'ip/prefix'), None) - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'ip/prefix'), '1.12.1.254/24') - - def test_ip_netmask(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'ip_netmask'), None) - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'ip_netmask'), None) - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'ip_netmask'), '1.12.1.36 255.255.255.240') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'ip_netmask'), '1.12.1.36 255.255.255.240') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'ip_netmask'), '1.12.1.36 255.255.255.254') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'ip_netmask'), '1.12.1.37 255.255.255.254') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'ip_netmask'), None) - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'ip_netmask'), '1.12.1.254 255.255.255.0') - - ''' - def test_ip_wildcard(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'ip_wildcard'), None) - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'ip_wildcard'), None) - #address = '1.12.1.34' - #self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'ip_wildcard'), '1.12.1.36 0.0.0.15') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'ip_wildcard'), '1.12.1.36 0.0.0.15') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'ip_wildcard'), '1.12.1.36 0.0.0.1') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'ip_wildcard'), '1.12.1.37 0.0.0.1') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'ip_wildcard'), None) - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'ip_wildcard'), '1.12.1.254 0.0.0.255') - ''' - def test_network_id(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'network_id'), '1.12.1.0') - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'network_id'), '1.12.1.0') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'network_id'), '1.12.1.32') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'network_id'), '1.12.1.32') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'network_id'), '1.12.1.36') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'network_id'), '1.12.1.36') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'network_id'), '1.12.1.36') - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'network_id'), '1.12.1.0') - - def test_network_prefix(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'network/prefix'), '1.12.1.0/24') - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'network/prefix'), '1.12.1.0/25') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'network/prefix'), '1.12.1.32/28') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'network/prefix'), '1.12.1.32/28') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'network/prefix'), '1.12.1.36/31') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'network/prefix'), '1.12.1.36/31') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'network/prefix'), '1.12.1.36/32') - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'network/prefix'), '1.12.1.0/24') - - def test_network_netmask(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'network_netmask'), '1.12.1.0 255.255.255.0') - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'network_netmask'), '1.12.1.0 255.255.255.128') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'network_netmask'), '1.12.1.32 255.255.255.240') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'network_netmask'), '1.12.1.32 255.255.255.240') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'network_netmask'), '1.12.1.36 255.255.255.254') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'network_netmask'), '1.12.1.36 255.255.255.254') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'network_netmask'), '1.12.1.36 255.255.255.255') - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'network_netmask'), '1.12.1.0 255.255.255.0') - - def test_network_wildcard(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'network_wildcard'), '1.12.1.0 0.0.0.255') - address = '1.12.1.0/25' - self.assertEqual(ipaddr(address, 'network_wildcard'), '1.12.1.0 0.0.0.127') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'network_wildcard'), '1.12.1.32 0.0.0.15') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'network_wildcard'), '1.12.1.32 0.0.0.15') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'network_wildcard'), '1.12.1.36 0.0.0.1') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'network_wildcard'), '1.12.1.36 0.0.0.1') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'network_wildcard'), '1.12.1.36 0.0.0.0') - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'network_wildcard'), '1.12.1.0 0.0.0.255') - - def test_next_usable(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'next_usable'), '1.12.1.1') - address = '1.12.1.36/24' - self.assertEqual(ipaddr(address, 'next_usable'), '1.12.1.37') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'next_usable'), '1.12.1.37') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'next_usable'), '1.12.1.37') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'next_usable'), '1.12.1.37') - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'next_usable'), None) - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'next_usable'), None) - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'next_usable'), None) - - def test_peer(self): - address = '1.12.1.0/31' - self.assertEqual(ipaddr(address, 'peer'), '1.12.1.1') - address = '1.12.1.1/31' - self.assertEqual(ipaddr(address, 'peer'), '1.12.1.0') - address = '1.12.1.1/30' - self.assertEqual(ipaddr(address, 'peer'), '1.12.1.2') - address = '1.12.1.2/30' - self.assertEqual(ipaddr(address, 'peer'), '1.12.1.1') - with self.assertRaises(AnsibleFilterError): - address = '1.12.1.34' - ipaddr(address, 'peer') - with self.assertRaises(AnsibleFilterError): - address = '1.12.1.33/29' - ipaddr(address, 'peer') - with self.assertRaises(AnsibleFilterError): - address = '1.12.1.32/30' - ipaddr(address, 'peer') - with self.assertRaises(AnsibleFilterError): - address = '1.12.1.35/30' - ipaddr(address, 'peer') - with self.assertRaises(AnsibleFilterError): - address = '1.12.1.34/32' - ipaddr(address, 'peer') - - def test_previous_usable(self): - address = '1.12.1.0/24' - self.assertEqual(ipaddr(address, 'previous_usable'), None) - address = '1.12.1.36/24' - self.assertEqual(ipaddr(address, 'previous_usable'), '1.12.1.35') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(ipaddr(address, 'previous_usable'), '1.12.1.35') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(ipaddr(address, 'previous_usable'), '1.12.1.35') - address = '1.12.1.36/31' - self.assertEqual(ipaddr(address, 'previous_usable'), None) - address = '1.12.1.37/31' - self.assertEqual(ipaddr(address, 'previous_usable'), '1.12.1.36') - address = '1.12.1.36/32' - self.assertEqual(ipaddr(address, 'previous_usable'), None) - address = '1.12.1.254/24' - self.assertEqual(ipaddr(address, 'previous_usable'), '1.12.1.253') - - def test_next_nth_usable(self): - address = '1.12.1.0/24' - self.assertEqual(next_nth_usable(address, 5), '1.12.1.5') - address = '1.12.1.36/24' - self.assertEqual(next_nth_usable(address, 10), '1.12.1.46') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(next_nth_usable(address, 4), '1.12.1.40') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(next_nth_usable(address, 4), '1.12.1.40') - address = '1.12.1.36/31' - self.assertEqual(next_nth_usable(address, 1), '1.12.1.37') - address = '1.12.1.37/31' - self.assertEqual(next_nth_usable(address, 1), None) - address = '1.12.1.36/32' - self.assertEqual(next_nth_usable(address, 1), None) - address = '1.12.1.254/24' - self.assertEqual(next_nth_usable(address, 2), None) - - def test_previous_nth_usable(self): - address = '1.12.1.0/24' - self.assertEqual(previous_nth_usable(address, 5), None) - address = '1.12.1.36/24' - self.assertEqual(previous_nth_usable(address, 10), '1.12.1.26') - # address = '1.12.1.34' - # self.assertFalse(ipaddr(address, 'last_usable'), 'Not a network address') - address = '1.12.1.36/28' - self.assertEqual(previous_nth_usable(address, 2), '1.12.1.34') - address = '1.12.1.36/255.255.255.240' - self.assertEqual(previous_nth_usable(address, 2), '1.12.1.34') - address = '1.12.1.36/31' - self.assertEqual(previous_nth_usable(address, 1), None) - address = '1.12.1.37/31' - self.assertEqual(previous_nth_usable(address, 1), '1.12.1.36') - address = '1.12.1.36/32' - self.assertEqual(previous_nth_usable(address, 1), None) - address = '1.12.1.254/24' - self.assertEqual(previous_nth_usable(address, 2), '1.12.1.252') - - def test_network_in_usable(self): - subnet = '1.12.1.0/24' - address = '1.12.1.10' - self.assertEqual(network_in_usable(subnet, address), True) - subnet = '1.12.1.0/24' - address = '1.12.0.10' - self.assertEqual(network_in_usable(subnet, address), False) - subnet = '1.12.1.32/28' - address = '1.12.1.36' - self.assertEqual(network_in_usable(subnet, address), True) - subnet = '1.12.1.32/28' - address = '1.12.1.36/31' - self.assertEqual(network_in_usable(subnet, address), True) - subnet = '1.12.1.32/28' - address = '1.12.1.48/31' - self.assertEqual(network_in_usable(subnet, address), False) - subnet = '1.12.1.32/255.255.255.240' - address = '1.12.1.31' - self.assertEqual(network_in_usable(subnet, address), False) - subnet = '1.12.1.36/31' - address = '1.12.1.36' - self.assertEqual(network_in_usable(subnet, address), True) - subnet = '1.12.1.37/31' - address = '1.12.1.35' - self.assertEqual(network_in_usable(subnet, address), False) - subnet = '1.12.1.36/32' - address = '1.12.1.36' - self.assertEqual(network_in_usable(subnet, address), True) - subnet = '1.12.1.0/24' - address = '1.12.2.0' - self.assertEqual(network_in_usable(subnet, address), False) - - def test_network_in_network(self): - subnet = '1.12.1.0/24' - address = '1.12.1.0' - self.assertEqual(network_in_network(subnet, address), True) - subnet = '1.12.1.0/24' - address = '1.12.0.10' - self.assertEqual(network_in_network(subnet, address), False) - subnet = '1.12.1.32/28' - address = '1.12.1.32/28' - self.assertEqual(network_in_network(subnet, address), True) - subnet = '1.12.1.32/28' - address = '1.12.1.47' - self.assertEqual(network_in_network(subnet, address), True) - subnet = '1.12.1.32/28' - address = '1.12.1.48/31' - self.assertEqual(network_in_network(subnet, address), False) - subnet = '1.12.1.32/255.255.255.240' - address = '1.12.1.31' - self.assertEqual(network_in_network(subnet, address), False) - subnet = '1.12.1.36/31' - address = '1.12.1.36' - self.assertEqual(network_in_network(subnet, address), True) - subnet = '1.12.1.37/31' - address = '1.12.1.35' - self.assertEqual(network_in_network(subnet, address), False) - subnet = '1.12.1.36/32' - address = '1.12.1.36' - self.assertEqual(network_in_network(subnet, address), True) - subnet = '1.12.1.0/24' - address = '1.12.2.0' - self.assertEqual(network_in_network(subnet, address), False) - - def test_cidr_merge(self): - self.assertEqual(cidr_merge([]), []) - self.assertEqual(cidr_merge([], 'span'), None) - subnets = ['1.12.1.0/24'] - self.assertEqual(cidr_merge(subnets), subnets) - self.assertEqual(cidr_merge(subnets, 'span'), subnets[0]) - subnets = ['1.12.1.0/25', '1.12.1.128/25'] - self.assertEqual(cidr_merge(subnets), ['1.12.1.0/24']) - self.assertEqual(cidr_merge(subnets, 'span'), '1.12.1.0/24') - subnets = ['1.12.1.0/25', '1.12.1.128/25', '1.12.2.0/24'] - self.assertEqual(cidr_merge(subnets), ['1.12.1.0/24', '1.12.2.0/24']) - self.assertEqual(cidr_merge(subnets, 'span'), '1.12.0.0/22') - subnets = ['1.12.1.1', '1.12.1.255'] - self.assertEqual(cidr_merge(subnets), ['1.12.1.1/32', '1.12.1.255/32']) - self.assertEqual(cidr_merge(subnets, 'span'), '1.12.1.0/24') - - def test_ipmath(self): - self.assertEqual(ipmath('192.168.1.5', 5), '192.168.1.10') - self.assertEqual(ipmath('192.168.1.5', -5), '192.168.1.0') - self.assertEqual(ipmath('192.168.0.5', -10), '192.167.255.251') - - self.assertEqual(ipmath('192.168.1.1/24', 5), '192.168.1.6') - self.assertEqual(ipmath('192.168.1.6/24', -5), '192.168.1.1') - self.assertEqual(ipmath('192.168.2.6/24', -10), '192.168.1.252') - - self.assertEqual(ipmath('2001::1', 8), '2001::9') - self.assertEqual(ipmath('2001::1', 9), '2001::a') - self.assertEqual(ipmath('2001::1', 10), '2001::b') - self.assertEqual(ipmath('2001::5', -3), '2001::2') - self.assertEqual( - ipmath('2001::5', -10), - '2000:ffff:ffff:ffff:ffff:ffff:ffff:fffb' - ) - - expected = 'You must pass a valid IP address; invalid_ip is invalid' - with self.assertRaises(AnsibleFilterError) as exc: - ipmath('invalid_ip', 8) - self.assertEqual(exc.exception.message, expected) - - expected = ( - 'You must pass an integer for arithmetic; ' - 'some_number is not a valid integer' - ) - with self.assertRaises(AnsibleFilterError) as exc: - ipmath('1.2.3.4', 'some_number') - self.assertEqual(exc.exception.message, expected) - - def test_ipsubnet(self): - test_cases = ( - (('1.1.1.1/24', '30'), '64'), - (('1.1.1.1/25', '24'), '0'), - (('1.12.1.34/32', '1.12.1.34/24'), '35'), - (('192.168.50.0/24', '192.168.0.0/16'), '51'), - (('192.168.144.5', '192.168.0.0/16'), '36870'), - (('192.168.144.5', '192.168.144.5/24'), '6'), - (('192.168.144.5/32', '192.168.144.0/24'), '6'), - (('192.168.144.16/30', '192.168.144.0/24'), '5'), - (('192.168.144.5', ), '192.168.144.5/32'), - (('192.168.0.0/16', ), '192.168.0.0/16'), - (('192.168.144.5', ), '192.168.144.5/32'), - (('192.168.0.0/16', '20'), '16'), - (('192.168.0.0/16', '20', '0'), '192.168.0.0/20'), - (('192.168.0.0/16', '20', '-1'), '192.168.240.0/20'), - (('192.168.0.0/16', '20', '5'), '192.168.80.0/20'), - (('192.168.0.0/16', '20', '-5'), '192.168.176.0/20'), - (('192.168.144.5', '20'), '192.168.144.0/20'), - (('192.168.144.5', '18', '0'), '192.168.128.0/18'), - (('192.168.144.5', '18', '-1'), '192.168.144.4/31'), - (('192.168.144.5', '18', '5'), '192.168.144.0/23'), - (('192.168.144.5', '18', '-5'), '192.168.144.0/27'), - (('span', 'test', 'error'), False), - (('test', ), False), - (('192.168.144.5', '500000', '-5'), False), - (('192.168.144.5', '18', '500000'), False), - (('200000', '18', '-5'), '0.3.13.64/27'), - ) - for args, res in test_cases: - self._test_ipsubnet(args, res) - - def _test_ipsubnet(self, ipsubnet_args, expected_result): - if ipsubnet_args == ('1.1.1.1/25', '24') and expected_result == '0' and sys.version_info >= (3, 7): - return # fails in netaddr on Python 3.7+ - - self.assertEqual(ipsubnet(*ipsubnet_args), expected_result) - - with self.assertRaisesRegexp(AnsibleFilterError, 'You must pass a valid subnet or IP address; invalid_subnet is invalid'): - ipsubnet('192.168.144.5', 'invalid_subnet') - - with self.assertRaisesRegexp(AnsibleFilterError, '192.168.144.0/30 is not in the subnet 192.168.144.4/30'): - ipsubnet('192.168.144.1/30', '192.168.144.5/30') diff --git a/test/units/plugins/filter/test_network.py b/test/units/plugins/filter/test_network.py deleted file mode 100644 index 04e328a754d..00000000000 --- a/test/units/plugins/filter/test_network.py +++ /dev/null @@ -1,182 +0,0 @@ -# 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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import os -import sys - -from units.compat import unittest -from ansible.plugins.filter.network import parse_xml, type5_pw, hash_salt, comp_type5, vlan_parser - -fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'network') - -with open(os.path.join(fixture_path, 'show_vlans_xml_output.txt')) as f: - output_xml = f.read() - - -class TestNetworkParseFilter(unittest.TestCase): - - @unittest.skipIf(sys.version_info[:2] == (2, 6), 'XPath expression not supported in this version') - def test_parse_xml_to_list_of_dict(self): - spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_spec.yml') - parsed = parse_xml(output_xml, spec_file_path) - expected = {'vlans': [{'name': 'test-1', 'enabled': True, 'state': 'active', 'interface': None, 'vlan_id': 100, 'desc': None}, - {'name': 'test-2', 'enabled': True, 'state': 'active', 'interface': None, 'vlan_id': None, 'desc': None}, - {'name': 'test-3', 'enabled': True, 'state': 'active', 'interface': 'em3.0', 'vlan_id': 300, 'desc': 'test vlan-3'}, - {'name': 'test-4', 'enabled': False, 'state': 'inactive', 'interface': None, 'vlan_id': 400, 'desc': 'test vlan-4'}, - {'name': 'test-5', 'enabled': False, 'state': 'inactive', 'interface': 'em5.0', 'vlan_id': 500, 'desc': 'test vlan-5'}]} - self.assertEqual(parsed, expected) - - @unittest.skipIf(sys.version_info[:2] == (2, 6), 'XPath expression not supported in this version') - def test_parse_xml_to_dict(self): - spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_with_key_spec.yml') - parsed = parse_xml(output_xml, spec_file_path) - expected = {'vlans': {'test-4': {'name': 'test-4', 'enabled': False, 'state': 'inactive', 'interface': None, 'vlan_id': 400, 'desc': 'test vlan-4'}, - 'test-3': {'name': 'test-3', 'enabled': True, 'state': 'active', 'interface': 'em3.0', 'vlan_id': 300, 'desc': 'test vlan-3'}, - 'test-1': {'name': 'test-1', 'enabled': True, 'state': 'active', 'interface': None, 'vlan_id': 100, 'desc': None}, - 'test-5': {'name': 'test-5', 'enabled': False, 'state': 'inactive', 'interface': 'em5.0', 'vlan_id': 500, 'desc': 'test vlan-5'}, - 'test-2': {'name': 'test-2', 'enabled': True, 'state': 'active', 'interface': None, 'vlan_id': None, 'desc': None}} - } - self.assertEqual(parsed, expected) - - @unittest.skipIf(sys.version_info[:2] == (2, 6), 'XPath expression not supported in this version') - def test_parse_xml_with_condition_spec(self): - spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_with_condition_spec.yml') - parsed = parse_xml(output_xml, spec_file_path) - expected = {'vlans': [{'name': 'test-5', 'enabled': False, 'state': 'inactive', 'interface': 'em5.0', 'vlan_id': 500, 'desc': 'test vlan-5'}]} - self.assertEqual(parsed, expected) - - def test_parse_xml_with_single_value_spec(self): - spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_single_value_spec.yml') - parsed = parse_xml(output_xml, spec_file_path) - expected = {'vlans': ['test-1', 'test-2', 'test-3', 'test-4', 'test-5']} - self.assertEqual(parsed, expected) - - def test_parse_xml_validate_input(self): - spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_spec.yml') - output = 10 - - with self.assertRaises(Exception) as e: - parse_xml(output_xml, 'junk_path') - self.assertEqual("unable to locate parse_xml template: junk_path", str(e.exception)) - - with self.assertRaises(Exception) as e: - parse_xml(output, spec_file_path) - self.assertEqual("parse_xml works on string input, but given input of : %s" % type(output), str(e.exception)) - - -class TestNetworkType5(unittest.TestCase): - - def test_defined_salt_success(self): - password = 'cisco' - salt = 'nTc1' - expected = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.' - parsed = type5_pw(password, salt) - self.assertEqual(parsed, expected) - - def test_undefined_salt_success(self): - password = 'cisco' - parsed = type5_pw(password) - self.assertEqual(len(parsed), 30) - - def test_wrong_data_type(self): - - with self.assertRaises(Exception) as e: - type5_pw([]) - self.assertEqual("type5_pw password input should be a string, but was given a input of list", str(e.exception)) - - with self.assertRaises(Exception) as e: - type5_pw({}) - self.assertEqual("type5_pw password input should be a string, but was given a input of dict", str(e.exception)) - - with self.assertRaises(Exception) as e: - type5_pw('pass', []) - self.assertEqual("type5_pw salt input should be a string, but was given a input of list", str(e.exception)) - - with self.assertRaises(Exception) as e: - type5_pw('pass', {}) - self.assertEqual("type5_pw salt input should be a string, but was given a input of dict", str(e.exception)) - - def test_bad_salt_char(self): - - with self.assertRaises(Exception) as e: - type5_pw('password', '*()') - self.assertEqual("type5_pw salt used inproper characters, must be one of " - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./", str(e.exception)) - - with self.assertRaises(Exception) as e: - type5_pw('password', 'asd$') - self.assertEqual("type5_pw salt used inproper characters, must be one of " - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./", str(e.exception)) - - -class TestHashSalt(unittest.TestCase): - - def test_retrieve_salt(self): - password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.' - parsed = hash_salt(password) - self.assertEqual(parsed, 'nTc1') - - password = '$2y$14$wHhBmAgOMZEld9iJtV.' - parsed = hash_salt(password) - self.assertEqual(parsed, '14') - - def test_unparseable_salt(self): - password = '$nTc1$Z28sUTcWfXlvVe2x.3XAa.' - with self.assertRaises(Exception) as e: - hash_salt(password) - self.assertEqual("Could not parse salt out password correctly from $nTc1$Z28sUTcWfXlvVe2x.3XAa.", str(e.exception)) - - -class TestCompareType5(unittest.TestCase): - - def test_compare_type5_boolean(self): - unencrypted_password = 'cisco' - encrypted_password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.' - parsed = comp_type5(unencrypted_password, encrypted_password) - self.assertEqual(parsed, True) - - def test_compare_type5_string(self): - unencrypted_password = 'cisco' - encrypted_password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.' - parsed = comp_type5(unencrypted_password, encrypted_password, True) - self.assertEqual(parsed, '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.') - - def test_compare_type5_fail(self): - unencrypted_password = 'invalid_password' - encrypted_password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.' - parsed = comp_type5(unencrypted_password, encrypted_password) - self.assertEqual(parsed, False) - - -class TestVlanParser(unittest.TestCase): - - def test_compression(self): - raw_list = [1, 2, 3] - parsed_list = ['1-3'] - self.assertEqual(vlan_parser(raw_list), parsed_list) - - def test_single_line(self): - raw_list = [100, 1688, 3002, 3003, 3004, 3005, 3102, 3103, 3104, 3105, 3802, 3900, 3998, 3999] - parsed_list = ['100,1688,3002-3005,3102-3105,3802,3900,3998,3999'] - self.assertEqual(vlan_parser(raw_list), parsed_list) - - def test_multi_line(self): - raw_list = [100, 1688, 3002, 3004, 3005, 3050, 3102, 3104, 3105, 3151, 3802, 3900, 3998, 3999] - parsed_list = ['100,1688,3002,3004,3005,3050,3102,3104,3105,3151', '3802,3900,3998,3999'] - self.assertEqual(vlan_parser(raw_list), parsed_list)