IOS-XR NetConf and Cliconf plugin work (#33332)
* - Netconf plugin addition for iosxr - Utilities refactoring to support netconf and cliconf - iosx_banner refactoring for netconf and cliconf - Integration testcases changes to accomodate above changes * Fix sanity failures, shippable errors and review comments * fix pep8 issue * changes run_command method to send specific command args * - Review comment fixes - iosxr_command changes to remove ComplexDict based command_spec * - Move namespaces removal method from utils to netconf plugin * Minor refactoring in utils and change in deprecation message * rewrite build_xml logic and import changes for new utils dir structure * - Review comment changes and minor changes to documentation * * refactor common code and docs updates
This commit is contained in:
parent
4b6061ce3e
commit
2bc4c4f156
42 changed files with 1090 additions and 247 deletions
|
@ -26,12 +26,44 @@
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
# 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.
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#
|
#
|
||||||
from ansible.module_utils._text import to_text
|
import json
|
||||||
from ansible.module_utils.basic import env_fallback, return_values
|
from difflib import Differ
|
||||||
from ansible.module_utils.network.common.utils import to_list, ComplexList
|
from copy import deepcopy
|
||||||
from ansible.module_utils.connection import exec_command
|
|
||||||
|
from ansible.module_utils._text import to_text, to_bytes
|
||||||
|
from ansible.module_utils.basic import env_fallback
|
||||||
|
from ansible.module_utils.network.common.utils import to_list
|
||||||
|
from ansible.module_utils.connection import Connection
|
||||||
|
from ansible.module_utils.network.common.netconf import NetconfConnection
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ncclient.xml_ import to_xml
|
||||||
|
HAS_NCCLIENT = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_NCCLIENT = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from lxml import etree
|
||||||
|
HAS_XML = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_XML = False
|
||||||
|
|
||||||
_DEVICE_CONFIGS = {}
|
_DEVICE_CONFIGS = {}
|
||||||
|
_EDIT_OPS = frozenset(['merge', 'create', 'replace', 'delete'])
|
||||||
|
|
||||||
|
BASE_1_0 = "{urn:ietf:params:xml:ns:netconf:base:1.0}"
|
||||||
|
|
||||||
|
NS_DICT = {
|
||||||
|
'BASE_NSMAP': {"xc": "urn:ietf:params:xml:ns:netconf:base:1.0"},
|
||||||
|
'BANNERS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-infra-infra-cfg"},
|
||||||
|
'INTERFACES_NSMAP': {None: "http://openconfig.net/yang/interfaces"},
|
||||||
|
'INSTALL_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-installmgr-admin-oper"},
|
||||||
|
'HOST-NAMES_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-shellutil-cfg"},
|
||||||
|
'M:TYPE_NSMAP': {"idx": "urn:ietf:params:xml:ns:yang:iana-if-type"},
|
||||||
|
'ETHERNET_NSMAP': {None: "http://openconfig.net/yang/interfaces/ethernet"},
|
||||||
|
'CETHERNET_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-drivers-media-eth-cfg"},
|
||||||
|
'INTERFACE-CONFIGURATIONS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"}
|
||||||
|
}
|
||||||
|
|
||||||
iosxr_provider_spec = {
|
iosxr_provider_spec = {
|
||||||
'host': dict(),
|
'host': dict(),
|
||||||
|
@ -40,10 +72,19 @@ iosxr_provider_spec = {
|
||||||
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
|
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
|
||||||
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
|
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
|
||||||
'timeout': dict(type='int'),
|
'timeout': dict(type='int'),
|
||||||
|
'transport': dict(),
|
||||||
}
|
}
|
||||||
|
|
||||||
iosxr_argument_spec = {
|
iosxr_argument_spec = {
|
||||||
'provider': dict(type='dict', options=iosxr_provider_spec)
|
'provider': dict(type='dict', options=iosxr_provider_spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
command_spec = {
|
||||||
|
'command': dict(),
|
||||||
|
'prompt': dict(default=None),
|
||||||
|
'answer': dict(default=None)
|
||||||
|
}
|
||||||
|
|
||||||
iosxr_top_spec = {
|
iosxr_top_spec = {
|
||||||
'host': dict(removed_in_version=2.9),
|
'host': dict(removed_in_version=2.9),
|
||||||
'port': dict(removed_in_version=2.9, type='int'),
|
'port': dict(removed_in_version=2.9, type='int'),
|
||||||
|
@ -59,91 +100,317 @@ def get_provider_argspec():
|
||||||
return iosxr_provider_spec
|
return iosxr_provider_spec
|
||||||
|
|
||||||
|
|
||||||
def check_args(module, warnings):
|
def get_connection(module):
|
||||||
pass
|
if hasattr(module, 'connection'):
|
||||||
|
return module.connection
|
||||||
|
|
||||||
|
capabilities = get_device_capabilities(module)
|
||||||
|
network_api = capabilities.get('network_api')
|
||||||
|
if network_api == 'cliconf':
|
||||||
|
module.connection = Connection(module._socket_path)
|
||||||
|
elif network_api == 'netconf':
|
||||||
|
module.connection = NetconfConnection(module._socket_path)
|
||||||
|
else:
|
||||||
|
module.fail_json(msg='Invalid connection type {!s}'.format(network_api))
|
||||||
|
|
||||||
|
return module.connection
|
||||||
|
|
||||||
|
|
||||||
def get_config(module, flags=None):
|
def get_device_capabilities(module):
|
||||||
flags = [] if flags is None else flags
|
if hasattr(module, 'capabilities'):
|
||||||
|
return module.capabilities
|
||||||
|
|
||||||
cmd = 'show running-config '
|
capabilities = Connection(module._socket_path).get_capabilities()
|
||||||
cmd += ' '.join(flags)
|
module.capabilities = json.loads(capabilities)
|
||||||
cmd = cmd.strip()
|
|
||||||
|
|
||||||
try:
|
return module.capabilities
|
||||||
return _DEVICE_CONFIGS[cmd]
|
|
||||||
except KeyError:
|
|
||||||
rc, out, err = exec_command(module, cmd)
|
def build_xml_subtree(container_ele, xmap, param=None, opcode=None):
|
||||||
if rc != 0:
|
sub_root = container_ele
|
||||||
module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_or_strict'))
|
meta_subtree = list()
|
||||||
cfg = to_text(out, errors='surrogate_or_strict').strip()
|
|
||||||
_DEVICE_CONFIGS[cmd] = cfg
|
for key, meta in xmap.items():
|
||||||
|
|
||||||
|
candidates = meta.get('xpath', "").split("/")
|
||||||
|
|
||||||
|
if container_ele.tag == candidates[-2]:
|
||||||
|
parent = container_ele
|
||||||
|
elif sub_root.tag == candidates[-2]:
|
||||||
|
parent = sub_root
|
||||||
|
else:
|
||||||
|
parent = sub_root.find(".//" + meta.get('xpath', "").split(sub_root.tag + '/', 1)[1].rsplit('/', 1)[0])
|
||||||
|
|
||||||
|
if ((opcode in ('delete', 'merge') and meta.get('operation', 'unknown') == 'edit') or
|
||||||
|
meta.get('operation', None) is None):
|
||||||
|
|
||||||
|
if meta.get('tag', False):
|
||||||
|
if parent.tag == container_ele.tag:
|
||||||
|
if meta.get('ns', None) is True:
|
||||||
|
child = etree.Element(candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
|
||||||
|
else:
|
||||||
|
child = etree.Element(candidates[-1])
|
||||||
|
meta_subtree.append(child)
|
||||||
|
sub_root = child
|
||||||
|
else:
|
||||||
|
if meta.get('ns', None) is True:
|
||||||
|
child = etree.SubElement(parent, candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
|
||||||
|
else:
|
||||||
|
child = etree.SubElement(parent, candidates[-1])
|
||||||
|
|
||||||
|
if meta.get('attrib', None) and opcode in ('delete', 'merge'):
|
||||||
|
child.set(BASE_1_0 + meta.get('attrib'), opcode)
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
text = None
|
||||||
|
param_key = key.split(":")
|
||||||
|
if param_key[0] == 'a':
|
||||||
|
if param.get(param_key[1], None):
|
||||||
|
text = param.get(param_key[1])
|
||||||
|
elif param_key[0] == 'm':
|
||||||
|
if meta.get('value', None):
|
||||||
|
text = meta.get('value')
|
||||||
|
|
||||||
|
if text:
|
||||||
|
if meta.get('ns', None) is True:
|
||||||
|
child = etree.SubElement(parent, candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
|
||||||
|
else:
|
||||||
|
child = etree.SubElement(parent, candidates[-1])
|
||||||
|
child.text = text
|
||||||
|
|
||||||
|
if len(meta_subtree) > 1:
|
||||||
|
for item in meta_subtree:
|
||||||
|
container_ele.append(item)
|
||||||
|
|
||||||
|
return sub_root
|
||||||
|
|
||||||
|
|
||||||
|
def build_xml(container, xmap=None, params=None, opcode=None):
|
||||||
|
|
||||||
|
'''
|
||||||
|
Builds netconf xml rpc document from meta-data
|
||||||
|
|
||||||
|
Args:
|
||||||
|
container: the YANG container within the namespace
|
||||||
|
xmap: meta-data map to build xml tree
|
||||||
|
params: Input params that feed xml tree values
|
||||||
|
opcode: operation to be performed (merge, delete etc.)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Module inputs:
|
||||||
|
banner_params = [{'banner':'motd', 'text':'Ansible banner example', 'state':'present'}]
|
||||||
|
|
||||||
|
Meta-data definition:
|
||||||
|
bannermap = collections.OrderedDict()
|
||||||
|
bannermap.update([
|
||||||
|
('banner', {'xpath' : 'banners/banner', 'tag' : True, 'attrib' : "operation"}),
|
||||||
|
('a:banner', {'xpath' : 'banner/banner-name'}),
|
||||||
|
('a:text', {'xpath' : 'banner/banner-text', 'operation' : 'edit'})
|
||||||
|
])
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
key: exact match to the key in arg_spec for a parameter
|
||||||
|
(prefixes --> a: value fetched from arg_spec, m: value fetched from meta-data)
|
||||||
|
xpath: xpath of the element (based on YANG model)
|
||||||
|
tag: True if no text on the element
|
||||||
|
attrib: attribute to be embedded in the element (e.g. xc:operation="merge")
|
||||||
|
operation: if edit --> includes the element in edit_config() query else ignores for get() queries
|
||||||
|
value: if key is prefixed with "m:", value is required in meta-data
|
||||||
|
|
||||||
|
Output:
|
||||||
|
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<banners xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-infra-cfg">
|
||||||
|
<banner xc:operation="merge">
|
||||||
|
<banner-name>motd</banner-name>
|
||||||
|
<banner-text>Ansible banner example</banner-text>
|
||||||
|
</banner>
|
||||||
|
</banners>
|
||||||
|
</config>
|
||||||
|
:returns: xml rpc document as a string
|
||||||
|
'''
|
||||||
|
|
||||||
|
if opcode == 'filter':
|
||||||
|
root = etree.Element("filter", type="subtree")
|
||||||
|
elif opcode in ('delete', 'merge'):
|
||||||
|
root = etree.Element("config", nsmap=NS_DICT['BASE_NSMAP'])
|
||||||
|
|
||||||
|
container_ele = etree.SubElement(root, container, nsmap=NS_DICT[container.upper() + "_NSMAP"])
|
||||||
|
|
||||||
|
if xmap:
|
||||||
|
if not params:
|
||||||
|
build_xml_subtree(container_ele, xmap)
|
||||||
|
else:
|
||||||
|
subtree_list = list()
|
||||||
|
|
||||||
|
for param in to_list(params):
|
||||||
|
subtree_list.append(build_xml_subtree(container_ele, xmap, param, opcode=opcode))
|
||||||
|
|
||||||
|
for item in subtree_list:
|
||||||
|
container_ele.append(item)
|
||||||
|
|
||||||
|
return etree.tostring(root)
|
||||||
|
|
||||||
|
|
||||||
|
def etree_find(root, node):
|
||||||
|
element = etree.fromstring(root).find('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
|
||||||
|
if element is not None:
|
||||||
|
return element
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def etree_findall(root, node):
|
||||||
|
element = etree.fromstring(root).findall('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
|
||||||
|
if element is not None:
|
||||||
|
return element
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_cliconf(module):
|
||||||
|
capabilities = get_device_capabilities(module)
|
||||||
|
network_api = capabilities.get('network_api')
|
||||||
|
if network_api not in ('cliconf', 'netconf'):
|
||||||
|
module.fail_json(msg=('unsupported network_api: {!s}'.format(network_api)))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if network_api == 'cliconf':
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_netconf(module):
|
||||||
|
capabilities = get_device_capabilities(module)
|
||||||
|
network_api = capabilities.get('network_api')
|
||||||
|
if network_api not in ('cliconf', 'netconf'):
|
||||||
|
module.fail_json(msg=('unsupported network_api: {!s}'.format(network_api)))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if network_api == 'netconf':
|
||||||
|
if not HAS_NCCLIENT:
|
||||||
|
module.fail_json(msg=('ncclient is not installed'))
|
||||||
|
if not HAS_XML:
|
||||||
|
module.fail_json(msg=('lxml is not installed'))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_diff(module, running=None, candidate=None):
|
||||||
|
conn = get_connection(module)
|
||||||
|
|
||||||
|
if is_cliconf(module):
|
||||||
|
return conn.get('show commit changes diff')
|
||||||
|
elif is_netconf(module):
|
||||||
|
if running and candidate:
|
||||||
|
running_data = running.split("\n", 1)[1].rsplit("\n", 1)[0]
|
||||||
|
candidate_data = candidate.split("\n", 1)[1].rsplit("\n", 1)[0]
|
||||||
|
if running_data != candidate_data:
|
||||||
|
d = Differ()
|
||||||
|
diff = list(d.compare(running_data.splitlines(), candidate_data.splitlines()))
|
||||||
|
return '\n'.join(diff).strip()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def discard_config(module):
|
||||||
|
conn = get_connection(module)
|
||||||
|
conn.discard_changes()
|
||||||
|
|
||||||
|
|
||||||
|
def commit_config(module, comment=None, confirmed=False, confirm_timeout=None, persist=False, check=False):
|
||||||
|
conn = get_connection(module)
|
||||||
|
reply = None
|
||||||
|
|
||||||
|
if check:
|
||||||
|
reply = conn.validate()
|
||||||
|
else:
|
||||||
|
if is_netconf(module):
|
||||||
|
reply = conn.commit(confirmed=confirmed, timeout=confirm_timeout, persist=persist)
|
||||||
|
elif is_cliconf(module):
|
||||||
|
reply = conn.commit(comment=comment)
|
||||||
|
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
def get_config(module, source='running', config_filter=None):
|
||||||
|
global _DEVICE_CONFIGS
|
||||||
|
|
||||||
|
conn = get_connection(module)
|
||||||
|
|
||||||
|
if config_filter is not None:
|
||||||
|
key = (source + ' ' + ' '.join(config_filter)).strip().rstrip()
|
||||||
|
else:
|
||||||
|
key = source
|
||||||
|
config = _DEVICE_CONFIGS.get(key)
|
||||||
|
if config:
|
||||||
|
return config
|
||||||
|
else:
|
||||||
|
out = conn.get_config(source=source, filter=config_filter)
|
||||||
|
if is_netconf(module):
|
||||||
|
out = to_xml(conn.get_config(source=source, filter=config_filter))
|
||||||
|
|
||||||
|
cfg = to_bytes(out, errors='surrogate_then_replace').strip()
|
||||||
|
_DEVICE_CONFIGS.update({key: cfg})
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
def to_commands(module, commands):
|
def load_config(module, command_filter, warnings, replace=False, admin=False, commit=False, comment=None):
|
||||||
spec = {
|
conn = get_connection(module)
|
||||||
'command': dict(key=True),
|
|
||||||
'prompt': dict(),
|
|
||||||
'answer': dict()
|
|
||||||
}
|
|
||||||
transform = ComplexList(spec, module)
|
|
||||||
return transform(commands)
|
|
||||||
|
|
||||||
|
if is_netconf(module):
|
||||||
|
# FIXME: check for platform behaviour and restore this
|
||||||
|
# ret = conn.lock(target = 'candidate')
|
||||||
|
# ret = conn.discard_changes()
|
||||||
|
try:
|
||||||
|
ret = conn.edit_config(command_filter)
|
||||||
|
finally:
|
||||||
|
# ret = conn.unlock(target = 'candidate')
|
||||||
|
pass
|
||||||
|
|
||||||
def run_commands(module, commands, check_rc=True):
|
return ret
|
||||||
responses = list()
|
|
||||||
commands = to_commands(module, to_list(commands))
|
|
||||||
for cmd in to_list(commands):
|
|
||||||
cmd = module.jsonify(cmd)
|
|
||||||
rc, out, err = exec_command(module, cmd)
|
|
||||||
if check_rc and rc != 0:
|
|
||||||
module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), rc=rc)
|
|
||||||
responses.append(to_text(out, errors='surrogate_or_strict'))
|
|
||||||
return responses
|
|
||||||
|
|
||||||
|
elif is_cliconf(module):
|
||||||
def load_config(module, commands, warnings, commit=False, replace=False, comment=None, admin=False):
|
# to keep the pre-cliconf behaviour, make a copy, avoid adding commands to input list
|
||||||
cmd = 'configure terminal'
|
cmd_filter = deepcopy(command_filter)
|
||||||
if admin:
|
cmd_filter.insert(0, 'configure terminal')
|
||||||
cmd = 'admin ' + cmd
|
if admin:
|
||||||
|
cmd_filter.insert(0, 'admin')
|
||||||
rc, out, err = exec_command(module, cmd)
|
conn.edit_config(cmd_filter)
|
||||||
if rc != 0:
|
diff = get_config_diff(module)
|
||||||
module.fail_json(msg='unable to enter configuration mode', err=to_text(err, errors='surrogate_or_strict'))
|
|
||||||
|
|
||||||
failed = False
|
|
||||||
for command in to_list(commands):
|
|
||||||
if command == 'end':
|
|
||||||
continue
|
|
||||||
|
|
||||||
rc, out, err = exec_command(module, command)
|
|
||||||
if rc != 0:
|
|
||||||
failed = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if failed:
|
|
||||||
exec_command(module, 'abort')
|
|
||||||
module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), commands=commands, rc=rc)
|
|
||||||
|
|
||||||
rc, diff, err = exec_command(module, 'show commit changes diff')
|
|
||||||
if rc != 0:
|
|
||||||
# If we failed, maybe we are in an old version so
|
|
||||||
# we run show configuration instead
|
|
||||||
rc, diff, err = exec_command(module, 'show configuration')
|
|
||||||
if module._diff:
|
if module._diff:
|
||||||
warnings.append('device platform does not support config diff')
|
if diff:
|
||||||
|
module['diff'] = to_text(diff, errors='surrogate_or_strict')
|
||||||
|
if commit:
|
||||||
|
commit_config(module, comment=comment)
|
||||||
|
conn.edit_config('end')
|
||||||
|
else:
|
||||||
|
conn.discard_changes()
|
||||||
|
|
||||||
if commit:
|
return diff
|
||||||
cmd = 'commit'
|
|
||||||
if comment:
|
|
||||||
cmd += ' comment {0}'.format(comment)
|
|
||||||
else:
|
|
||||||
cmd = 'abort'
|
|
||||||
|
|
||||||
rc, out, err = exec_command(module, cmd)
|
|
||||||
if rc != 0:
|
|
||||||
exec_command(module, 'abort')
|
|
||||||
module.fail_json(msg=err, commands=commands, rc=rc)
|
|
||||||
|
|
||||||
return to_text(diff, errors='surrogate_or_strict')
|
def run_command(module, commands):
|
||||||
|
conn = get_connection(module)
|
||||||
|
responses = list()
|
||||||
|
for cmd in to_list(commands):
|
||||||
|
try:
|
||||||
|
cmd = json.loads(cmd)
|
||||||
|
command = cmd['command']
|
||||||
|
prompt = cmd['prompt']
|
||||||
|
answer = cmd['answer']
|
||||||
|
except:
|
||||||
|
command = cmd
|
||||||
|
prompt = None
|
||||||
|
answer = None
|
||||||
|
|
||||||
|
out = conn.get(command, prompt, answer)
|
||||||
|
|
||||||
|
try:
|
||||||
|
responses.append(to_text(out, errors='surrogate_or_strict'))
|
||||||
|
except UnicodeError:
|
||||||
|
module.fail_json(msg=u'failed to decode output from {0}:{1}'.format(cmd, to_text(out)))
|
||||||
|
return responses
|
||||||
|
|
|
@ -16,32 +16,32 @@ DOCUMENTATION = """
|
||||||
---
|
---
|
||||||
module: iosxr_banner
|
module: iosxr_banner
|
||||||
version_added: "2.4"
|
version_added: "2.4"
|
||||||
author: "Trishna Guha (@trishnaguha)"
|
author:
|
||||||
|
- Trishna Guha (@trishnaguha)
|
||||||
|
- Kedar Kekan (@kedarX)
|
||||||
short_description: Manage multiline banners on Cisco IOS XR devices
|
short_description: Manage multiline banners on Cisco IOS XR devices
|
||||||
description:
|
description:
|
||||||
- This will configure both exec and motd banners on remote devices
|
- This module will configure both exec and motd banners on remote device
|
||||||
running Cisco IOS XR. It allows playbooks to add or remote
|
running Cisco IOS XR. It allows playbooks to add or remove
|
||||||
banner text from the active running configuration.
|
banner text from the running configuration.
|
||||||
|
extends_documentation_fragment: iosxr
|
||||||
notes:
|
notes:
|
||||||
- Tested against IOS XR 6.1.2
|
- Tested against IOS XRv 6.1.2
|
||||||
options:
|
options:
|
||||||
banner:
|
banner:
|
||||||
description:
|
description:
|
||||||
- Specifies which banner that should be
|
- Specifies the type of banner to configure on remote device.
|
||||||
configured on the remote device.
|
|
||||||
required: true
|
required: true
|
||||||
default: null
|
default: null
|
||||||
choices: ['login', 'motd']
|
choices: ['login', 'motd']
|
||||||
text:
|
text:
|
||||||
description:
|
description:
|
||||||
- The banner text that should be
|
- Banner text to be configured. Accepts multiline string,
|
||||||
present in the remote device running configuration. This argument
|
without empty lines. Requires I(state=present).
|
||||||
accepts a multiline string, with no empty lines. Requires I(state=present).
|
|
||||||
default: null
|
default: null
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Specifies whether or not the configuration is present in the current
|
- Existential state of the configuration on the device.
|
||||||
devices active running configuration.
|
|
||||||
default: present
|
default: present
|
||||||
choices: ['present', 'absent']
|
choices: ['present', 'absent']
|
||||||
"""
|
"""
|
||||||
|
@ -79,60 +79,130 @@ commands:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import collections
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
|
from ansible.module_utils.network.iosxr.iosxr import get_config_diff, commit_config
|
||||||
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, discard_config
|
||||||
|
from ansible.module_utils.network.iosxr.iosxr import build_xml, is_cliconf, is_netconf
|
||||||
|
from ansible.module_utils.network.iosxr.iosxr import etree_find, etree_findall
|
||||||
|
|
||||||
|
|
||||||
def map_obj_to_commands(updates, module):
|
class ConfigBase(object):
|
||||||
commands = list()
|
def __init__(self, module):
|
||||||
want, have = updates
|
self._module = module
|
||||||
state = module.params['state']
|
self._result = {'changed': False, 'warnings': []}
|
||||||
|
self._want = {}
|
||||||
|
self._have = {}
|
||||||
|
|
||||||
if state == 'absent':
|
def map_params_to_obj(self):
|
||||||
if have.get('state') != 'absent' and ('text' in have.keys() and have['text']):
|
text = self._module.params['text']
|
||||||
commands.append('no banner %s' % module.params['banner'])
|
if text:
|
||||||
|
text = "{!r}".format(str(text).strip())
|
||||||
elif state == 'present':
|
self._want.update({
|
||||||
if (want['text'] and
|
'banner': self._module.params['banner'],
|
||||||
want['text'].encode().decode('unicode_escape').strip("'") != have.get('text')):
|
'text': text,
|
||||||
banner_cmd = 'banner %s ' % module.params['banner']
|
'state': self._module.params['state']
|
||||||
banner_cmd += want['text'].strip()
|
})
|
||||||
commands.append(banner_cmd)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
|
|
||||||
def map_config_to_obj(module):
|
class CliConfiguration(ConfigBase):
|
||||||
flags = 'banner %s' % module.params['banner']
|
def __init__(self, module):
|
||||||
output = get_config(module, flags=[flags])
|
super(CliConfiguration, self).__init__(module)
|
||||||
|
|
||||||
match = re.search(r'banner (\S+) (.*)', output, re.DOTALL)
|
def map_obj_to_commands(self):
|
||||||
if match:
|
commands = list()
|
||||||
text = match.group(2).strip("'")
|
state = self._module.params['state']
|
||||||
else:
|
if state == 'absent':
|
||||||
text = None
|
if self._have.get('state') != 'absent' and ('text' in self._have.keys() and self._have['text']):
|
||||||
|
commands.append('no banner {!s}'.format(self._module.params['banner']))
|
||||||
|
elif state == 'present':
|
||||||
|
if (self._want['text'] and
|
||||||
|
self._want['text'].encode().decode('unicode_escape').strip("'") != self._have.get('text')):
|
||||||
|
banner_cmd = 'banner {!s} '.format(self._module.params['banner'])
|
||||||
|
banner_cmd += self._want['text'].strip()
|
||||||
|
commands.append(banner_cmd)
|
||||||
|
self._result['commands'] = commands
|
||||||
|
if commands:
|
||||||
|
if not self._module.check_mode:
|
||||||
|
load_config(self._module, commands, self._result['warnings'], commit=True)
|
||||||
|
self._result['changed'] = True
|
||||||
|
|
||||||
obj = {'banner': module.params['banner'], 'state': 'absent'}
|
def map_config_to_obj(self):
|
||||||
|
cli_filter = 'banner {!s}'.format(self._module.params['banner'])
|
||||||
|
output = get_config(self._module, config_filter=cli_filter)
|
||||||
|
match = re.search(r'banner (\S+) (.*)', output, re.DOTALL)
|
||||||
|
if match:
|
||||||
|
text = match.group(2).strip("'")
|
||||||
|
else:
|
||||||
|
text = None
|
||||||
|
obj = {'banner': self._module.params['banner'], 'state': 'absent'}
|
||||||
|
if output:
|
||||||
|
obj['text'] = text
|
||||||
|
obj['state'] = 'present'
|
||||||
|
self._have.update(obj)
|
||||||
|
|
||||||
if output:
|
def run(self):
|
||||||
obj['text'] = text
|
self.map_params_to_obj()
|
||||||
obj['state'] = 'present'
|
self.map_config_to_obj()
|
||||||
|
self.map_obj_to_commands()
|
||||||
|
|
||||||
return obj
|
return self._result
|
||||||
|
|
||||||
|
|
||||||
def map_params_to_obj(module):
|
class NCConfiguration(ConfigBase):
|
||||||
text = module.params['text']
|
def __init__(self, module):
|
||||||
if text:
|
super(NCConfiguration, self).__init__(module)
|
||||||
text = "%r" % (str(text).strip())
|
self._banners_meta = collections.OrderedDict()
|
||||||
|
self._banners_meta.update([
|
||||||
|
('banner', {'xpath': 'banners/banner', 'tag': True, 'attrib': "operation"}),
|
||||||
|
('a:banner', {'xpath': 'banner/banner-name'}),
|
||||||
|
('a:text', {'xpath': 'banner/banner-text', 'operation': 'edit'})
|
||||||
|
])
|
||||||
|
|
||||||
return {
|
def map_obj_to_commands(self):
|
||||||
'banner': module.params['banner'],
|
state = self._module.params['state']
|
||||||
'text': text,
|
_get_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode="filter")
|
||||||
'state': module.params['state']
|
|
||||||
}
|
running = get_config(self._module, source='running', config_filter=_get_filter)
|
||||||
|
|
||||||
|
banner_name = None
|
||||||
|
banner_text = None
|
||||||
|
if etree_find(running, 'banner-text') is not None:
|
||||||
|
banner_name = etree_find(running, 'banner-name').text
|
||||||
|
banner_text = etree_find(running, 'banner-text').text
|
||||||
|
|
||||||
|
opcode = None
|
||||||
|
if state == 'absent' and banner_name == self._module.params['banner'] and len(banner_text):
|
||||||
|
opcode = "delete"
|
||||||
|
elif state == 'present':
|
||||||
|
opcode = 'merge'
|
||||||
|
|
||||||
|
self._result['commands'] = []
|
||||||
|
if opcode:
|
||||||
|
_edit_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode=opcode)
|
||||||
|
|
||||||
|
if _edit_filter is not None:
|
||||||
|
if not self._module.check_mode:
|
||||||
|
load_config(self._module, _edit_filter, self._result['warnings'])
|
||||||
|
candidate = get_config(self._module, source='candidate', config_filter=_get_filter)
|
||||||
|
|
||||||
|
diff = get_config_diff(self._module, running, candidate)
|
||||||
|
if diff:
|
||||||
|
commit_config(self._module)
|
||||||
|
self._result['changed'] = True
|
||||||
|
self._result['commands'] = _edit_filter
|
||||||
|
if self._module._diff:
|
||||||
|
self._result['diff'] = {'prepared': diff}
|
||||||
|
else:
|
||||||
|
discard_config(self._module)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.map_params_to_obj()
|
||||||
|
self.map_obj_to_commands()
|
||||||
|
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -152,23 +222,13 @@ def main():
|
||||||
required_if=required_if,
|
required_if=required_if,
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
warnings = list()
|
if is_cliconf(module):
|
||||||
check_args(module, warnings)
|
module.deprecate("cli support for 'iosxr_banner' is deprecated. Use transport netconf instead', version='4 releases from v2.5")
|
||||||
|
config_object = CliConfiguration(module)
|
||||||
result = {'changed': False}
|
elif is_netconf(module):
|
||||||
result['warnings'] = warnings
|
config_object = NCConfiguration(module)
|
||||||
|
|
||||||
want = map_params_to_obj(module)
|
|
||||||
have = map_config_to_obj(module)
|
|
||||||
|
|
||||||
commands = map_obj_to_commands((want, have), module)
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
if not module.check_mode:
|
|
||||||
load_config(module, commands, result['warnings'], commit=True)
|
|
||||||
result['changed'] = True
|
|
||||||
|
|
||||||
|
result = config_object.run()
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@ tasks:
|
||||||
commands:
|
commands:
|
||||||
- show version
|
- show version
|
||||||
- show interfaces
|
- show interfaces
|
||||||
|
- [{ command: example command that prompts, prompt: expected prompt, answer: yes}]
|
||||||
|
|
||||||
- name: run multiple commands and evaluate the output
|
- name: run multiple commands and evaluate the output
|
||||||
iosxr_command:
|
iosxr_command:
|
||||||
|
@ -125,9 +126,9 @@ failed_conditions:
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.network.iosxr.iosxr import run_commands, iosxr_argument_spec, check_args
|
from ansible.module_utils.network.iosxr.iosxr import run_command, iosxr_argument_spec
|
||||||
|
from ansible.module_utils.network.iosxr.iosxr import command_spec
|
||||||
from ansible.module_utils.network.common.parsing import Conditional
|
from ansible.module_utils.network.common.parsing import Conditional
|
||||||
from ansible.module_utils.network.common.utils import ComplexList
|
|
||||||
from ansible.module_utils.six import string_types
|
from ansible.module_utils.six import string_types
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
|
|
||||||
|
@ -140,27 +141,27 @@ def to_lines(stdout):
|
||||||
|
|
||||||
|
|
||||||
def parse_commands(module, warnings):
|
def parse_commands(module, warnings):
|
||||||
command = ComplexList(dict(
|
commands = module.params['commands']
|
||||||
command=dict(key=True),
|
|
||||||
prompt=dict(),
|
|
||||||
answer=dict()
|
|
||||||
), module)
|
|
||||||
commands = command(module.params['commands'])
|
|
||||||
|
|
||||||
for item in list(commands):
|
for item in list(commands):
|
||||||
if module.check_mode and not item['command'].startswith('show'):
|
try:
|
||||||
|
command = item['command']
|
||||||
|
except Exception:
|
||||||
|
command = item
|
||||||
|
if module.check_mode and not command.startswith('show'):
|
||||||
warnings.append(
|
warnings.append(
|
||||||
'only show commands are supported when using check mode, not '
|
'only show commands are supported when using check mode, not '
|
||||||
'executing `%s`' % item['command']
|
'executing `%s`' % command
|
||||||
)
|
)
|
||||||
commands.remove(item)
|
commands.remove(item)
|
||||||
elif item['command'].startswith('conf'):
|
elif command.startswith('conf'):
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg='iosxr_command does not support running config mode '
|
msg='iosxr_command does not support running config mode '
|
||||||
'commands. Please use iosxr_config instead'
|
'commands. Please use iosxr_config instead'
|
||||||
)
|
)
|
||||||
|
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
spec = dict(
|
spec = dict(
|
||||||
commands=dict(type='list', required=True),
|
commands=dict(type='list', required=True),
|
||||||
|
@ -174,11 +175,12 @@ def main():
|
||||||
|
|
||||||
spec.update(iosxr_argument_spec)
|
spec.update(iosxr_argument_spec)
|
||||||
|
|
||||||
|
spec.update(command_spec)
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=spec,
|
module = AnsibleModule(argument_spec=spec,
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
warnings = list()
|
warnings = list()
|
||||||
check_args(module, warnings)
|
|
||||||
|
|
||||||
commands = parse_commands(module, warnings)
|
commands = parse_commands(module, warnings)
|
||||||
|
|
||||||
|
@ -190,7 +192,7 @@ def main():
|
||||||
match = module.params['match']
|
match = module.params['match']
|
||||||
|
|
||||||
while retries > 0:
|
while retries > 0:
|
||||||
responses = run_commands(module, commands)
|
responses = run_command(module, commands)
|
||||||
|
|
||||||
for item in list(conditionals):
|
for item in list(conditionals):
|
||||||
if item(responses):
|
if item(responses):
|
||||||
|
@ -210,7 +212,6 @@ def main():
|
||||||
msg = 'One or more conditional statements have not be satisfied'
|
msg = 'One or more conditional statements have not be satisfied'
|
||||||
module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
||||||
|
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
'changed': False,
|
'changed': False,
|
||||||
'stdout': responses,
|
'stdout': responses,
|
||||||
|
|
|
@ -180,9 +180,8 @@ backup_path:
|
||||||
sample: /playbooks/ansible/backup/iosxr01.2016-07-16@22:28:34
|
sample: /playbooks/ansible/backup/iosxr01.2016-07-16@22:28:34
|
||||||
"""
|
"""
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.network.iosxr.iosxr import load_config,get_config
|
from ansible.module_utils.network.iosxr.iosxr import load_config, get_config
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
||||||
from ansible.module_utils.network.iosxr.iosxr import check_args as iosxr_check_args
|
|
||||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,7 +189,6 @@ DEFAULT_COMMIT_COMMENT = 'configured by iosxr_config'
|
||||||
|
|
||||||
|
|
||||||
def check_args(module, warnings):
|
def check_args(module, warnings):
|
||||||
iosxr_check_args(module, warnings)
|
|
||||||
if module.params['comment']:
|
if module.params['comment']:
|
||||||
if len(module.params['comment']) > 60:
|
if len(module.params['comment']) > 60:
|
||||||
module.fail_json(msg='comment argument cannot be more than 60 characters')
|
module.fail_json(msg='comment argument cannot be more than 60 characters')
|
||||||
|
@ -216,6 +214,7 @@ def get_candidate(module):
|
||||||
candidate.add(module.params['lines'], parents=parents)
|
candidate.add(module.params['lines'], parents=parents)
|
||||||
return candidate
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
def run(module, result):
|
def run(module, result):
|
||||||
match = module.params['match']
|
match = module.params['match']
|
||||||
replace = module.params['replace']
|
replace = module.params['replace']
|
||||||
|
@ -231,7 +230,7 @@ def run(module, result):
|
||||||
contents = get_running_config(module)
|
contents = get_running_config(module)
|
||||||
configobj = NetworkConfig(contents=contents, indent=1)
|
configobj = NetworkConfig(contents=contents, indent=1)
|
||||||
commands = candidate.difference(configobj, path=path, match=match,
|
commands = candidate.difference(configobj, path=path, match=match,
|
||||||
replace=replace)
|
replace=replace)
|
||||||
else:
|
else:
|
||||||
commands = candidate.items
|
commands = candidate.items
|
||||||
|
|
||||||
|
@ -253,6 +252,7 @@ def run(module, result):
|
||||||
result['diff'] = dict(prepared=diff)
|
result['diff'] = dict(prepared=diff)
|
||||||
result['changed'] = True
|
result['changed'] = True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""main entry point for module execution
|
"""main entry point for module execution
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -115,7 +115,7 @@ ansible_net_neighbors:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args, run_commands
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, run_command
|
||||||
from ansible.module_utils.six import iteritems
|
from ansible.module_utils.six import iteritems
|
||||||
from ansible.module_utils.six.moves import zip
|
from ansible.module_utils.six.moves import zip
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@ class Hardware(FactsBase):
|
||||||
results['dir /all'])
|
results['dir /all'])
|
||||||
|
|
||||||
match = re.search(r'Physical Memory: (\d+)M total \((\d+)',
|
match = re.search(r'Physical Memory: (\d+)M total \((\d+)',
|
||||||
results['show memory summary'])
|
results['show memory summary'])
|
||||||
if match:
|
if match:
|
||||||
self.facts['memtotal_mb'] = match.group(1)
|
self.facts['memtotal_mb'] = match.group(1)
|
||||||
self.facts['memfree_mb'] = match.group(2)
|
self.facts['memfree_mb'] = match.group(2)
|
||||||
|
@ -188,7 +188,7 @@ class Interfaces(FactsBase):
|
||||||
|
|
||||||
def commands(self):
|
def commands(self):
|
||||||
return(['show interfaces', 'show ipv6 interface',
|
return(['show interfaces', 'show ipv6 interface',
|
||||||
'show lldp', 'show lldp neighbors detail'])
|
'show lldp', 'show lldp neighbors detail'])
|
||||||
|
|
||||||
def populate(self, results):
|
def populate(self, results):
|
||||||
self.facts['all_ipv4_addresses'] = list()
|
self.facts['all_ipv4_addresses'] = list()
|
||||||
|
@ -360,7 +360,6 @@ def main():
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
warnings = list()
|
warnings = list()
|
||||||
check_args(module, warnings)
|
|
||||||
|
|
||||||
gather_subset = module.params['gather_subset']
|
gather_subset = module.params['gather_subset']
|
||||||
|
|
||||||
|
@ -405,7 +404,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
for inst in instances:
|
for inst in instances:
|
||||||
commands = inst.commands()
|
commands = inst.commands()
|
||||||
responses = run_commands(module, commands)
|
responses = run_command(module, commands)
|
||||||
results = dict(zip(commands, responses))
|
results = dict(zip(commands, responses))
|
||||||
inst.populate(results)
|
inst.populate(results)
|
||||||
facts.update(inst.facts)
|
facts.update(inst.facts)
|
||||||
|
|
|
@ -22,6 +22,7 @@ short_description: Manage Interface on Cisco IOS XR network devices
|
||||||
description:
|
description:
|
||||||
- This module provides declarative management of Interfaces
|
- This module provides declarative management of Interfaces
|
||||||
on Cisco IOS XR network devices.
|
on Cisco IOS XR network devices.
|
||||||
|
extends_documentation_fragment: iosxr
|
||||||
notes:
|
notes:
|
||||||
- Tested against IOS XR 6.1.2
|
- Tested against IOS XR 6.1.2
|
||||||
options:
|
options:
|
||||||
|
@ -142,7 +143,7 @@ from ansible.module_utils._text import to_text
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.connection import exec_command
|
from ansible.module_utils.connection import exec_command
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
||||||
from ansible.module_utils.network.common.utils import conditional, remove_default_spec
|
from ansible.module_utils.network.common.utils import conditional, remove_default_spec
|
||||||
|
|
||||||
|
|
||||||
|
@ -228,7 +229,7 @@ def map_params_to_obj(module):
|
||||||
|
|
||||||
|
|
||||||
def map_config_to_obj(module):
|
def map_config_to_obj(module):
|
||||||
data = get_config(module, flags=['interface'])
|
data = get_config(module, config_filter='interface')
|
||||||
interfaces = data.strip().rstrip('!').split('!')
|
interfaces = data.strip().rstrip('!').split('!')
|
||||||
|
|
||||||
if not interfaces:
|
if not interfaces:
|
||||||
|
@ -387,7 +388,6 @@ def main():
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
warnings = list()
|
warnings = list()
|
||||||
check_args(module, warnings)
|
|
||||||
|
|
||||||
result = {'changed': False}
|
result = {'changed': False}
|
||||||
|
|
||||||
|
@ -402,7 +402,6 @@ def main():
|
||||||
if commands:
|
if commands:
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
load_config(module, commands, result['warnings'], commit=True)
|
load_config(module, commands, result['warnings'], commit=True)
|
||||||
exec_command(module, 'exit')
|
|
||||||
result['changed'] = True
|
result['changed'] = True
|
||||||
|
|
||||||
failed_conditions = check_declarative_intent_params(module, want, result)
|
failed_conditions = check_declarative_intent_params(module, want, result)
|
||||||
|
|
|
@ -21,6 +21,7 @@ short_description: Manage logging on network devices
|
||||||
description:
|
description:
|
||||||
- This module provides declarative management of logging
|
- This module provides declarative management of logging
|
||||||
on Cisco IOS XR devices.
|
on Cisco IOS XR devices.
|
||||||
|
extends_documentation_fragment: iosxr
|
||||||
notes:
|
notes:
|
||||||
- Tested against IOS XR 6.1.2
|
- Tested against IOS XR 6.1.2
|
||||||
options:
|
options:
|
||||||
|
@ -114,7 +115,7 @@ from copy import deepcopy
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
||||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||||
|
|
||||||
|
|
||||||
|
@ -237,7 +238,7 @@ def map_config_to_obj(module):
|
||||||
obj = []
|
obj = []
|
||||||
dest_group = ('console', 'hostnameprefix', 'monitor', 'buffered', 'on')
|
dest_group = ('console', 'hostnameprefix', 'monitor', 'buffered', 'on')
|
||||||
|
|
||||||
data = get_config(module, flags=['logging'])
|
data = get_config(module, config_filter='logging')
|
||||||
lines = data.split("\n")
|
lines = data.split("\n")
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
|
@ -349,7 +350,6 @@ def main():
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
warnings = list()
|
warnings = list()
|
||||||
check_args(module, warnings)
|
|
||||||
|
|
||||||
result = {'changed': False}
|
result = {'changed': False}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ options:
|
||||||
description:
|
description:
|
||||||
- netconf vrf name
|
- netconf vrf name
|
||||||
required: false
|
required: false
|
||||||
default: none
|
default: default
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Specifies the state of the C(iosxr_netconf) resource on
|
- Specifies the state of the C(iosxr_netconf) resource on
|
||||||
|
@ -75,7 +75,7 @@ import re
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.connection import exec_command
|
from ansible.module_utils.connection import exec_command
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
||||||
from ansible.module_utils.six import iteritems
|
from ansible.module_utils.six import iteritems
|
||||||
|
|
||||||
|
@ -90,16 +90,12 @@ def map_obj_to_commands(updates, module):
|
||||||
if have['state'] == 'present':
|
if have['state'] == 'present':
|
||||||
commands.append('no netconf-yang agent ssh')
|
commands.append('no netconf-yang agent ssh')
|
||||||
|
|
||||||
if 'netconf_port' in have:
|
if 'netconf_port' in have:
|
||||||
commands.append('no ssh server netconf port %s' % have['netconf_port'])
|
commands.append('no ssh server netconf port %s' % have['netconf_port'])
|
||||||
|
|
||||||
if want['netconf_vrf']:
|
if have['netconf_vrf']:
|
||||||
for vrf in have['netconf_vrf']:
|
for vrf in have['netconf_vrf']:
|
||||||
if vrf == want['netconf_vrf']:
|
commands.append('no ssh server netconf vrf %s' % vrf)
|
||||||
commands.append('no ssh server netconf vrf %s' % vrf)
|
|
||||||
else:
|
|
||||||
for vrf in have['netconf_vrf']:
|
|
||||||
commands.append('no ssh server netconf vrf %s' % vrf)
|
|
||||||
else:
|
else:
|
||||||
if have['state'] == 'absent':
|
if have['state'] == 'absent':
|
||||||
commands.append('netconf-yang agent ssh')
|
commands.append('netconf-yang agent ssh')
|
||||||
|
@ -131,9 +127,9 @@ def parse_port(config):
|
||||||
def map_config_to_obj(module):
|
def map_config_to_obj(module):
|
||||||
obj = {'state': 'absent'}
|
obj = {'state': 'absent'}
|
||||||
|
|
||||||
netconf_config = get_config(module, flags=['netconf-yang agent'])
|
netconf_config = get_config(module, config_filter='netconf-yang agent')
|
||||||
|
|
||||||
ssh_config = get_config(module, flags=['ssh server'])
|
ssh_config = get_config(module, config_filter='ssh server')
|
||||||
ssh_config = [config_line for config_line in (line.strip() for line in ssh_config.splitlines()) if config_line]
|
ssh_config = [config_line for config_line in (line.strip() for line in ssh_config.splitlines()) if config_line]
|
||||||
obj['netconf_vrf'] = []
|
obj['netconf_vrf'] = []
|
||||||
for config in ssh_config:
|
for config in ssh_config:
|
||||||
|
@ -141,7 +137,7 @@ def map_config_to_obj(module):
|
||||||
obj.update({'netconf_port': parse_port(config)})
|
obj.update({'netconf_port': parse_port(config)})
|
||||||
if 'netconf vrf' in config:
|
if 'netconf vrf' in config:
|
||||||
obj['netconf_vrf'].append(parse_vrf(config))
|
obj['netconf_vrf'].append(parse_vrf(config))
|
||||||
if 'ssh' in netconf_config or 'netconf_port' in obj or obj['netconf_vrf']:
|
if 'ssh' in netconf_config and ('netconf_port' in obj or obj['netconf_vrf']):
|
||||||
obj.update({'state': 'present'})
|
obj.update({'state': 'present'})
|
||||||
|
|
||||||
if 'ssh' in netconf_config and 'netconf_port' not in obj:
|
if 'ssh' in netconf_config and 'netconf_port' not in obj:
|
||||||
|
@ -176,7 +172,7 @@ def main():
|
||||||
"""
|
"""
|
||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
netconf_port=dict(type='int', default=830, aliases=['listens_on']),
|
netconf_port=dict(type='int', default=830, aliases=['listens_on']),
|
||||||
netconf_vrf=dict(aliases=['vrf']),
|
netconf_vrf=dict(aliases=['vrf'], default='default'),
|
||||||
state=dict(default='present', choices=['present', 'absent']),
|
state=dict(default='present', choices=['present', 'absent']),
|
||||||
)
|
)
|
||||||
argument_spec.update(iosxr_argument_spec)
|
argument_spec.update(iosxr_argument_spec)
|
||||||
|
@ -185,7 +181,6 @@ def main():
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
warnings = list()
|
warnings = list()
|
||||||
check_args(module, warnings)
|
|
||||||
|
|
||||||
result = {'changed': False, 'warnings': warnings}
|
result = {'changed': False, 'warnings': warnings}
|
||||||
|
|
||||||
|
@ -197,10 +192,6 @@ def main():
|
||||||
if commands:
|
if commands:
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
diff = load_config(module, commands, result['warnings'], commit=True)
|
diff = load_config(module, commands, result['warnings'], commit=True)
|
||||||
if diff:
|
|
||||||
if module._diff:
|
|
||||||
result['diff'] = {'prepared': diff}
|
|
||||||
exec_command(module, 'exit')
|
|
||||||
result['changed'] = True
|
result['changed'] = True
|
||||||
|
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
|
@ -108,18 +108,21 @@ import re
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
||||||
|
|
||||||
|
|
||||||
def diff_list(want, have):
|
def diff_list(want, have):
|
||||||
adds = set(want).difference(have)
|
adds = set(want).difference(have)
|
||||||
removes = set(have).difference(want)
|
removes = set(have).difference(want)
|
||||||
return (adds, removes)
|
return (adds, removes)
|
||||||
|
|
||||||
|
|
||||||
def map_obj_to_commands(want, have, module):
|
def map_obj_to_commands(want, have, module):
|
||||||
commands = list()
|
commands = list()
|
||||||
state = module.params['state']
|
state = module.params['state']
|
||||||
|
|
||||||
needs_update = lambda x: want.get(x) and (want.get(x) != have.get(x))
|
def needs_update(x):
|
||||||
|
return want.get(x) and (want.get(x) != have.get(x))
|
||||||
|
|
||||||
if state == 'absent':
|
if state == 'absent':
|
||||||
if have['hostname'] != 'ios':
|
if have['hostname'] != 'ios':
|
||||||
|
@ -167,20 +170,24 @@ def map_obj_to_commands(want, have, module):
|
||||||
|
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
|
|
||||||
def parse_hostname(config):
|
def parse_hostname(config):
|
||||||
match = re.search(r'^hostname (\S+)', config, re.M)
|
match = re.search(r'^hostname (\S+)', config, re.M)
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
|
|
||||||
|
|
||||||
def parse_domain_name(config):
|
def parse_domain_name(config):
|
||||||
match = re.search(r'^domain name (\S+)', config, re.M)
|
match = re.search(r'^domain name (\S+)', config, re.M)
|
||||||
if match:
|
if match:
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
|
|
||||||
|
|
||||||
def parse_lookup_source(config):
|
def parse_lookup_source(config):
|
||||||
match = re.search(r'^domain lookup source-interface (\S+)', config, re.M)
|
match = re.search(r'^domain lookup source-interface (\S+)', config, re.M)
|
||||||
if match:
|
if match:
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
|
|
||||||
|
|
||||||
def map_config_to_obj(module):
|
def map_config_to_obj(module):
|
||||||
config = get_config(module)
|
config = get_config(module)
|
||||||
return {
|
return {
|
||||||
|
@ -192,6 +199,7 @@ def map_config_to_obj(module):
|
||||||
'name_servers': re.findall(r'^domain name-server (\S+)', config, re.M)
|
'name_servers': re.findall(r'^domain name-server (\S+)', config, re.M)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def map_params_to_obj(module):
|
def map_params_to_obj(module):
|
||||||
return {
|
return {
|
||||||
'hostname': module.params['hostname'],
|
'hostname': module.params['hostname'],
|
||||||
|
@ -202,6 +210,7 @@ def map_params_to_obj(module):
|
||||||
'name_servers': module.params['name_servers']
|
'name_servers': module.params['name_servers']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
""" Main entry point for Ansible module execution
|
""" Main entry point for Ansible module execution
|
||||||
"""
|
"""
|
||||||
|
@ -223,7 +232,6 @@ def main():
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
warnings = list()
|
warnings = list()
|
||||||
check_args(module, warnings)
|
|
||||||
|
|
||||||
result = {'changed': False, 'warnings': warnings}
|
result = {'changed': False, 'warnings': warnings}
|
||||||
|
|
||||||
|
@ -240,5 +248,6 @@ def main():
|
||||||
|
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -26,6 +26,7 @@ description:
|
||||||
either individual usernames or the aggregate of usernames in the
|
either individual usernames or the aggregate of usernames in the
|
||||||
current running config. It also supports purging usernames from the
|
current running config. It also supports purging usernames from the
|
||||||
configuration that are not explicitly defined.
|
configuration that are not explicitly defined.
|
||||||
|
extends_documentation_fragment: iosxr
|
||||||
notes:
|
notes:
|
||||||
- Tested against IOS XR 6.1.2
|
- Tested against IOS XR 6.1.2
|
||||||
options:
|
options:
|
||||||
|
@ -166,7 +167,7 @@ from copy import deepcopy
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
from ansible.module_utils.network.common.utils import remove_default_spec
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
@ -228,7 +229,7 @@ def map_obj_to_commands(updates, module):
|
||||||
|
|
||||||
|
|
||||||
def map_config_to_obj(module):
|
def map_config_to_obj(module):
|
||||||
data = get_config(module, flags=['username'])
|
data = get_config(module, config_filter='username')
|
||||||
users = data.strip().rstrip('!').split('!')
|
users = data.strip().rstrip('!').split('!')
|
||||||
|
|
||||||
if not users:
|
if not users:
|
||||||
|
@ -453,8 +454,6 @@ def main():
|
||||||
'To set a user password use "configured_password" instead.'
|
'To set a user password use "configured_password" instead.'
|
||||||
)
|
)
|
||||||
|
|
||||||
check_args(module, warnings)
|
|
||||||
|
|
||||||
result = {'changed': False}
|
result = {'changed': False}
|
||||||
|
|
||||||
want = map_params_to_obj(module)
|
want = map_params_to_obj(module)
|
||||||
|
|
|
@ -48,7 +48,17 @@ class ActionModule(_ActionModule):
|
||||||
elif self._play_context.connection == 'local':
|
elif self._play_context.connection == 'local':
|
||||||
provider = load_provider(iosxr_provider_spec, self._task.args)
|
provider = load_provider(iosxr_provider_spec, self._task.args)
|
||||||
pc = copy.deepcopy(self._play_context)
|
pc = copy.deepcopy(self._play_context)
|
||||||
pc.connection = 'network_cli'
|
if self._task.action in ['iosxr_netconf', 'iosxr_config', 'iosxr_command'] or \
|
||||||
|
(provider['transport'] == 'cli' and (self._task.action == 'iosxr_banner' or
|
||||||
|
self._task.action == 'iosxr_facts' or self._task.action == 'iosxr_logging' or
|
||||||
|
self._task.action == 'iosxr_system' or self._task.action == 'iosxr_user' or
|
||||||
|
self._task.action == 'iosxr_interface')):
|
||||||
|
pc.connection = 'network_cli'
|
||||||
|
pc.port = int(provider['port'] or self._play_context.port or 22)
|
||||||
|
else:
|
||||||
|
pc.connection = 'netconf'
|
||||||
|
pc.port = int(provider['port'] or self._play_context.port or 830)
|
||||||
|
|
||||||
pc.network_os = 'iosxr'
|
pc.network_os = 'iosxr'
|
||||||
pc.remote_addr = provider['host'] or self._play_context.remote_addr
|
pc.remote_addr = provider['host'] or self._play_context.remote_addr
|
||||||
pc.port = int(provider['port'] or self._play_context.port or 22)
|
pc.port = int(provider['port'] or self._play_context.port or 22)
|
||||||
|
@ -70,15 +80,16 @@ class ActionModule(_ActionModule):
|
||||||
|
|
||||||
# make sure we are in the right cli context which should be
|
# make sure we are in the right cli context which should be
|
||||||
# enable mode and not config module
|
# enable mode and not config module
|
||||||
if socket_path is None:
|
if pc.connection == 'network_cli':
|
||||||
socket_path = self._connection.socket_path
|
if socket_path is None:
|
||||||
|
socket_path = self._connection.socket_path
|
||||||
|
|
||||||
conn = Connection(socket_path)
|
conn = Connection(socket_path)
|
||||||
out = conn.get_prompt()
|
|
||||||
while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
|
|
||||||
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
|
|
||||||
conn.send_command('abort')
|
|
||||||
out = conn.get_prompt()
|
out = conn.get_prompt()
|
||||||
|
while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
|
||||||
|
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
|
||||||
|
conn.send_command('abort')
|
||||||
|
out = conn.get_prompt()
|
||||||
|
|
||||||
result = super(ActionModule, self).run(tmp, task_vars)
|
result = super(ActionModule, self).run(tmp, task_vars)
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -56,14 +56,19 @@ class Cliconf(CliconfBase):
|
||||||
|
|
||||||
return device_info
|
return device_info
|
||||||
|
|
||||||
def get_config(self, source='running'):
|
def get_config(self, source='running', filter=None):
|
||||||
lookup = {'running': 'running-config'}
|
lookup = {'running': 'running-config'}
|
||||||
if source not in lookup:
|
if source not in lookup:
|
||||||
return self.invalid_params("fetching configuration from %s is not supported" % source)
|
return self.invalid_params("fetching configuration from %s is not supported" % source)
|
||||||
return self.send_command(to_bytes(b'show %s' % lookup[source], errors='surrogate_or_strict'))
|
if filter:
|
||||||
|
cmd = to_bytes(b'show {0} {1}'.format(lookup[source], filter), errors='surrogate_or_strict')
|
||||||
|
else:
|
||||||
|
cmd = to_bytes(b'show {0}'.format(lookup[source]), errors='surrogate_or_strict')
|
||||||
|
|
||||||
|
return self.send_command(cmd)
|
||||||
|
|
||||||
def edit_config(self, command):
|
def edit_config(self, command):
|
||||||
for cmd in chain([b'configure'], to_list(command), [b'end']):
|
for cmd in chain(to_list(command)):
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
|
|
||||||
def get(self, command, prompt=None, answer=None, sendonly=False):
|
def get(self, command, prompt=None, answer=None, sendonly=False):
|
||||||
|
|
|
@ -54,10 +54,9 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
problems.
|
problems.
|
||||||
|
|
||||||
List of supported rpc's:
|
List of supported rpc's:
|
||||||
|
:get: Retrieves running configuration and device state information
|
||||||
:get_config: Retrieves the specified configuration from the device
|
:get_config: Retrieves the specified configuration from the device
|
||||||
:edit_config: Loads the specified commands into the remote device
|
:edit_config: Loads the specified commands into the remote device
|
||||||
:get: Execute specified command on remote device
|
|
||||||
:get_capabilities: Retrieves device information and supported rpc methods
|
|
||||||
:commit: Load configuration from candidate to running
|
:commit: Load configuration from candidate to running
|
||||||
:discard_changes: Discard changes to candidate datastore
|
:discard_changes: Discard changes to candidate datastore
|
||||||
:validate: Validate the contents of the specified configuration.
|
:validate: Validate the contents of the specified configuration.
|
||||||
|
@ -65,6 +64,9 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
:unlock: Release a configuration lock, previously obtained with the lock operation.
|
:unlock: Release a configuration lock, previously obtained with the lock operation.
|
||||||
:copy_config: create or replace an entire configuration datastore with the contents of another complete
|
:copy_config: create or replace an entire configuration datastore with the contents of another complete
|
||||||
configuration datastore.
|
configuration datastore.
|
||||||
|
:get-schema: Retrieves the required schema from the device
|
||||||
|
:get_capabilities: Retrieves device information and supported rpc methods
|
||||||
|
|
||||||
For JUNOS:
|
For JUNOS:
|
||||||
:execute_rpc: RPC to be execute on remote device
|
:execute_rpc: RPC to be execute on remote device
|
||||||
:load_configuration: Loads given configuration on device
|
:load_configuration: Loads given configuration on device
|
||||||
|
@ -100,7 +102,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
:source: name of the configuration datastore being queried
|
:source: name of the configuration datastore being queried
|
||||||
:filter: specifies the portion of the configuration to retrieve
|
:filter: specifies the portion of the configuration to retrieve
|
||||||
(by default entire configuration is retrieved)"""
|
(by default entire configuration is retrieved)"""
|
||||||
return self.m.get_config(*args, **kwargs).data_xml
|
pass
|
||||||
|
|
||||||
@ensure_connected
|
@ensure_connected
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
|
@ -108,7 +110,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
*filter* specifies the portion of the configuration to retrieve
|
*filter* specifies the portion of the configuration to retrieve
|
||||||
(by default entire configuration is retrieved)
|
(by default entire configuration is retrieved)
|
||||||
"""
|
"""
|
||||||
return self.m.get(*args, **kwargs).data_xml
|
pass
|
||||||
|
|
||||||
@ensure_connected
|
@ensure_connected
|
||||||
def edit_config(self, *args, **kwargs):
|
def edit_config(self, *args, **kwargs):
|
||||||
|
@ -122,10 +124,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
:error_option: if specified must be one of { `"stop-on-error"`, `"continue-on-error"`, `"rollback-on-error"` }
|
:error_option: if specified must be one of { `"stop-on-error"`, `"continue-on-error"`, `"rollback-on-error"` }
|
||||||
The `"rollback-on-error"` *error_option* depends on the `:rollback-on-error` capability.
|
The `"rollback-on-error"` *error_option* depends on the `:rollback-on-error` capability.
|
||||||
"""
|
"""
|
||||||
try:
|
pass
|
||||||
return self.m.edit_config(*args, **kwargs).data_xml
|
|
||||||
except RPCError as exc:
|
|
||||||
raise Exception(to_xml(exc.xml))
|
|
||||||
|
|
||||||
@ensure_connected
|
@ensure_connected
|
||||||
def validate(self, *args, **kwargs):
|
def validate(self, *args, **kwargs):
|
||||||
|
@ -133,7 +132,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
:source: is the name of the configuration datastore being validated or `config`
|
:source: is the name of the configuration datastore being validated or `config`
|
||||||
element containing the configuration subtree to be validated
|
element containing the configuration subtree to be validated
|
||||||
"""
|
"""
|
||||||
return self.m.validate(*args, **kwargs).data_xml
|
pass
|
||||||
|
|
||||||
@ensure_connected
|
@ensure_connected
|
||||||
def copy_config(self, *args, **kwargs):
|
def copy_config(self, *args, **kwargs):
|
||||||
|
@ -162,7 +161,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
def discard_changes(self, *args, **kwargs):
|
def discard_changes(self, *args, **kwargs):
|
||||||
"""Revert the candidate configuration to the currently running configuration.
|
"""Revert the candidate configuration to the currently running configuration.
|
||||||
Any uncommitted changes are discarded."""
|
Any uncommitted changes are discarded."""
|
||||||
return self.m.discard_changes(*args, **kwargs).data_xml
|
pass
|
||||||
|
|
||||||
@ensure_connected
|
@ensure_connected
|
||||||
def commit(self, *args, **kwargs):
|
def commit(self, *args, **kwargs):
|
||||||
|
@ -176,10 +175,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
:confirmed: whether this is a confirmed commit
|
:confirmed: whether this is a confirmed commit
|
||||||
:timeout: specifies the confirm timeout in seconds
|
:timeout: specifies the confirm timeout in seconds
|
||||||
"""
|
"""
|
||||||
try:
|
pass
|
||||||
return self.m.commit(*args, **kwargs).data_xml
|
|
||||||
except RPCError as exc:
|
|
||||||
raise Exception(to_xml(exc.xml))
|
|
||||||
|
|
||||||
@ensure_connected
|
@ensure_connected
|
||||||
def validate(self, *args, **kwargs):
|
def validate(self, *args, **kwargs):
|
||||||
|
@ -187,8 +183,18 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
:source: name of configuration data store"""
|
:source: name of configuration data store"""
|
||||||
return self.m.validate(*args, **kwargs).data_xml
|
return self.m.validate(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def get_schema(self, *args, **kwargs):
|
||||||
|
"""Retrieves the required schema from the device
|
||||||
|
"""
|
||||||
|
return self.m.get_schema(*args, **kwargs)
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def locked(self, *args, **kwargs):
|
||||||
|
return self.m.locked(*args, **kwargs)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_capabilities(self, commands):
|
def get_capabilities(self):
|
||||||
"""Retrieves device information and supported
|
"""Retrieves device information and supported
|
||||||
rpc methods by device platform and return result
|
rpc methods by device platform and return result
|
||||||
as a string
|
as a string
|
||||||
|
@ -213,3 +219,5 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
|
||||||
def fetch_file(self, source, destination):
|
def fetch_file(self, source, destination):
|
||||||
"""Fetch file over scp from remote device"""
|
"""Fetch file over scp from remote device"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# TODO Restore .data_xml, when ncclient supports it for all platforms
|
||||||
|
|
209
lib/ansible/plugins/netconf/iosxr.py
Normal file
209
lib/ansible/plugins/netconf/iosxr.py
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
# (c) 2017 Kedar Kekan (kkekan@redhat.com)
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import collections
|
||||||
|
from io import BytesIO
|
||||||
|
from ansible.module_utils.six import StringIO
|
||||||
|
|
||||||
|
from ansible import constants as C
|
||||||
|
from ansible.module_utils.network.iosxr.iosxr import build_xml
|
||||||
|
from ansible.errors import AnsibleConnectionFailure, AnsibleError
|
||||||
|
from ansible.plugins.netconf import NetconfBase
|
||||||
|
from ansible.plugins.netconf import ensure_connected
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ncclient import manager
|
||||||
|
from ncclient.operations import RPCError
|
||||||
|
from ncclient.transport.errors import SSHUnknownHostError
|
||||||
|
from ncclient.xml_ import to_ele, to_xml, new_ele
|
||||||
|
except ImportError:
|
||||||
|
raise AnsibleError("ncclient is not installed")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from lxml import etree
|
||||||
|
except ImportError:
|
||||||
|
raise AnsibleError("lxml is not installed")
|
||||||
|
|
||||||
|
|
||||||
|
def transform_reply():
|
||||||
|
reply = '''<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||||
|
<xsl:output method="xml" indent="no"/>
|
||||||
|
|
||||||
|
<xsl:template match="/|comment()|processing-instruction()">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates/>
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="*">
|
||||||
|
<xsl:element name="{local-name()}">
|
||||||
|
<xsl:apply-templates select="@*|node()"/>
|
||||||
|
</xsl:element>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="@*">
|
||||||
|
<xsl:attribute name="{local-name()}">
|
||||||
|
<xsl:value-of select="."/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
||||||
|
'''
|
||||||
|
if sys.version < '3':
|
||||||
|
return reply
|
||||||
|
else:
|
||||||
|
print("utf8")
|
||||||
|
return reply.encode('UTF-8')
|
||||||
|
|
||||||
|
|
||||||
|
# Note: Workaround for ncclient 0.5.3
|
||||||
|
def remove_namespaces(rpc_reply):
|
||||||
|
xslt = transform_reply()
|
||||||
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
|
xslt_doc = etree.parse(BytesIO(xslt), parser)
|
||||||
|
transform = etree.XSLT(xslt_doc)
|
||||||
|
|
||||||
|
return etree.fromstring(str(transform(etree.parse(StringIO(str(rpc_reply))))))
|
||||||
|
|
||||||
|
|
||||||
|
class Netconf(NetconfBase):
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
device_info['network_os'] = 'iosxr'
|
||||||
|
install_meta = collections.OrderedDict()
|
||||||
|
install_meta.update([
|
||||||
|
('boot-variables', {'xpath': 'install/boot-variables', 'tag': True}),
|
||||||
|
('boot-variable', {'xpath': 'install/boot-variables/boot-variable', 'tag': True, 'lead': True}),
|
||||||
|
('software', {'xpath': 'install/software', 'tag': True}),
|
||||||
|
('alias-devices', {'xpath': 'install/software/alias-devices', 'tag': True}),
|
||||||
|
('alias-device', {'xpath': 'install/software/alias-devices/alias-device', 'tag': True}),
|
||||||
|
('m:device-name', {'xpath': 'install/software/alias-devices/alias-device/device-name', 'value': 'disk0:'}),
|
||||||
|
])
|
||||||
|
|
||||||
|
install_filter = build_xml('install', install_meta, opcode='filter')
|
||||||
|
|
||||||
|
reply = self.get(install_filter)
|
||||||
|
ele_boot_variable = etree.fromstring(reply).find('.//boot-variable/boot-variable')
|
||||||
|
if ele_boot_variable:
|
||||||
|
device_info['network_os_image'] = re.split('[:|,]', ele_boot_variable.text)[1]
|
||||||
|
ele_package_name = etree.fromstring(reply).find('.//package-name')
|
||||||
|
if ele_package_name:
|
||||||
|
device_info['network_os_package'] = ele_package_name.text
|
||||||
|
device_info['network_os_version'] = re.split('-', ele_package_name.text)[-1]
|
||||||
|
|
||||||
|
hostname_filter = build_xml('host-names', opcode='filter')
|
||||||
|
|
||||||
|
reply = self.get(hostname_filter)
|
||||||
|
device_info['network_os_hostname'] = etree.fromstring(reply).find('.//host-name').text
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = dict()
|
||||||
|
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes', 'validate', 'lock', 'unlock', 'get-schema']
|
||||||
|
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
|
||||||
|
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def guess_network_os(obj):
|
||||||
|
|
||||||
|
try:
|
||||||
|
m = manager.connect(
|
||||||
|
host=obj._play_context.remote_addr,
|
||||||
|
port=obj._play_context.port or 830,
|
||||||
|
username=obj._play_context.remote_user,
|
||||||
|
password=obj._play_context.password,
|
||||||
|
key_filename=str(obj.key_filename),
|
||||||
|
hostkey_verify=C.HOST_KEY_CHECKING,
|
||||||
|
look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS,
|
||||||
|
allow_agent=obj.allow_agent,
|
||||||
|
timeout=obj._play_context.timeout
|
||||||
|
)
|
||||||
|
except SSHUnknownHostError as exc:
|
||||||
|
raise AnsibleConnectionFailure(str(exc))
|
||||||
|
|
||||||
|
guessed_os = None
|
||||||
|
for c in m.server_capabilities:
|
||||||
|
if re.search('IOS-XR', c):
|
||||||
|
guessed_os = 'iosxr'
|
||||||
|
break
|
||||||
|
|
||||||
|
m.close_session()
|
||||||
|
return guessed_os
|
||||||
|
|
||||||
|
# TODO: change .xml to .data_xml, when ncclient supports data_xml on all platforms
|
||||||
|
@ensure_connected
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
response = self.m.get(*args, **kwargs)
|
||||||
|
return to_xml(remove_namespaces(response))
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def get_config(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
response = self.m.get_config(*args, **kwargs)
|
||||||
|
return to_xml(remove_namespaces(response))
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def edit_config(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
response = self.m.edit_config(*args, **kwargs)
|
||||||
|
return to_xml(remove_namespaces(response))
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def commit(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
response = self.m.commit(*args, **kwargs)
|
||||||
|
return to_xml(remove_namespaces(response))
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def validate(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
response = self.m.validate(*args, **kwargs)
|
||||||
|
return to_xml(remove_namespaces(response))
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def discard_changes(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
response = self.m.discard_changes(*args, **kwargs)
|
||||||
|
return to_xml(remove_namespaces(response))
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
|
@ -152,3 +152,39 @@ class Netconf(NetconfBase):
|
||||||
def halt(self):
|
def halt(self):
|
||||||
"""reboot the device"""
|
"""reboot the device"""
|
||||||
return self.m.halt().data_xml
|
return self.m.halt().data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return self.m.get(*args, **kwargs).data_xml
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def get_config(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return self.m.get_config(*args, **kwargs).data_xml
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def edit_config(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
self.m.edit_config(*args, **kwargs).data_xml
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def commit(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return self.m.commit(*args, **kwargs).data_xml
|
||||||
|
except RPCError as exc:
|
||||||
|
raise Exception(to_xml(exc.xml))
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def validate(self, *args, **kwargs):
|
||||||
|
return self.m.validate(*args, **kwargs).data_xml
|
||||||
|
|
||||||
|
@ensure_connected
|
||||||
|
def discard_changes(self, *args, **kwargs):
|
||||||
|
return self.m.discard_changes(*args, **kwargs).data_xml
|
||||||
|
|
|
@ -64,6 +64,9 @@ options:
|
||||||
key used to authenticate the SSH session. If the value is not specified
|
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)
|
in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE)
|
||||||
will be used instead.
|
will be used instead.
|
||||||
|
requirements:
|
||||||
|
- "ncclient >= 0.5.3 when using netconf"
|
||||||
|
- "lxml >= 4.1.1 when using netconf"
|
||||||
notes:
|
notes:
|
||||||
- For more information on using Ansible to manage Cisco devices see U(https://www.ansible.com/ansible-cisco).
|
- For more information on using Ansible to manage Cisco devices see U(https://www.ansible.com/ansible-cisco).
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
---
|
---
|
||||||
- { include: cli.yaml, tags: ['cli'] }
|
- { include: cli.yaml, tags: ['cli'] }
|
||||||
|
- { include: netconf.yaml, tags: ['netconf'] }
|
||||||
|
|
16
test/integration/targets/iosxr_banner/tasks/netconf.yaml
Normal file
16
test/integration/targets/iosxr_banner/tasks/netconf.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
- name: collect all netconf test cases
|
||||||
|
find:
|
||||||
|
paths: "{{ role_path }}/tests/netconf"
|
||||||
|
patterns: "{{ testcase }}.yaml"
|
||||||
|
register: test_cases
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: set test_items
|
||||||
|
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||||
|
|
||||||
|
- name: run test case
|
||||||
|
include: "{{ test_case_to_run }}"
|
||||||
|
with_items: "{{ test_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: test_case_to_run
|
|
@ -2,6 +2,7 @@
|
||||||
- name: setup - remove login
|
- name: setup - remove login
|
||||||
iosxr_banner:
|
iosxr_banner:
|
||||||
banner: login
|
banner: login
|
||||||
|
provider: "{{ cli }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: Set login
|
- name: Set login
|
||||||
|
@ -11,6 +12,7 @@
|
||||||
this is my login banner
|
this is my login banner
|
||||||
that has a multiline
|
that has a multiline
|
||||||
string
|
string
|
||||||
|
provider: "{{ cli }}"
|
||||||
state: present
|
state: present
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
@ -30,6 +32,7 @@
|
||||||
this is my login banner
|
this is my login banner
|
||||||
that has a multiline
|
that has a multiline
|
||||||
string
|
string
|
||||||
|
provider: "{{ cli }}"
|
||||||
state: present
|
state: present
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
iosxr_banner:
|
iosxr_banner:
|
||||||
banner: motd
|
banner: motd
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: Set motd
|
- name: Set motd
|
||||||
iosxr_banner:
|
iosxr_banner:
|
||||||
|
@ -11,6 +12,7 @@
|
||||||
this is my motd banner
|
this is my motd banner
|
||||||
that has a multiline
|
that has a multiline
|
||||||
string
|
string
|
||||||
|
provider: "{{ cli }}"
|
||||||
state: present
|
state: present
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
@ -30,6 +32,7 @@
|
||||||
this is my motd banner
|
this is my motd banner
|
||||||
that has a multiline
|
that has a multiline
|
||||||
string
|
string
|
||||||
|
provider: "{{ cli }}"
|
||||||
state: present
|
state: present
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
text: |
|
text: |
|
||||||
Junk login banner
|
Junk login banner
|
||||||
over multiple lines
|
over multiple lines
|
||||||
|
provider: "{{ cli }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: remove login
|
- name: remove login
|
||||||
iosxr_banner:
|
iosxr_banner:
|
||||||
banner: login
|
banner: login
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- debug:
|
- debug:
|
||||||
|
@ -25,6 +27,7 @@
|
||||||
iosxr_banner:
|
iosxr_banner:
|
||||||
banner: login
|
banner: login
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
- name: Enable Netconf service
|
||||||
|
iosxr_netconf:
|
||||||
|
netconf_port: 830
|
||||||
|
netconf_vrf: 'default'
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: setup - remove login
|
||||||
|
iosxr_banner:
|
||||||
|
banner: login
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Set login
|
||||||
|
iosxr_banner:
|
||||||
|
banner: login
|
||||||
|
text: |
|
||||||
|
this is my login banner
|
||||||
|
that has a multiline
|
||||||
|
string
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "{{ result }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'this is my login banner' in result.commands"
|
||||||
|
- "'that has a multiline' in result.commands"
|
||||||
|
|
||||||
|
- name: Set login again (idempotent)
|
||||||
|
iosxr_banner:
|
||||||
|
banner: login
|
||||||
|
text: |
|
||||||
|
this is my login banner
|
||||||
|
that has a multiline
|
||||||
|
string
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.commands | length == 0"
|
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
- name: Enable Netconf service
|
||||||
|
iosxr_netconf:
|
||||||
|
netconf_port: 830
|
||||||
|
netconf_vrf: 'default'
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: setup - remove motd
|
||||||
|
iosxr_banner:
|
||||||
|
banner: motd
|
||||||
|
state: absent
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
|
||||||
|
- name: Set motd
|
||||||
|
iosxr_banner:
|
||||||
|
banner: motd
|
||||||
|
text: |
|
||||||
|
this is my motd banner
|
||||||
|
that has a multiline
|
||||||
|
string
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "{{ result }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'this is my motd banner' in result.commands"
|
||||||
|
- "'that has a multiline' in result.commands"
|
||||||
|
|
||||||
|
- name: Set motd again (idempotent)
|
||||||
|
iosxr_banner:
|
||||||
|
banner: motd
|
||||||
|
text: |
|
||||||
|
this is my motd banner
|
||||||
|
that has a multiline
|
||||||
|
string
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.commands | length == 0"
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
- name: Enable Netconf service
|
||||||
|
iosxr_netconf:
|
||||||
|
netconf_port: 830
|
||||||
|
netconf_vrf: 'default'
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Setup
|
||||||
|
iosxr_banner:
|
||||||
|
banner: login
|
||||||
|
text: |
|
||||||
|
Junk login banner
|
||||||
|
over multiple lines
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: remove login
|
||||||
|
iosxr_banner:
|
||||||
|
banner: login
|
||||||
|
state: absent
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "{{ result }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'xc:operation=\"delete\"' in result.commands"
|
||||||
|
|
||||||
|
- name: remove login (idempotent)
|
||||||
|
iosxr_banner:
|
||||||
|
banner: login
|
||||||
|
state: absent
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.commands | length == 0"
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
- name: run invalid command
|
- name: run invalid command
|
||||||
iosxr_command:
|
iosxr_command:
|
||||||
commands: ['show foo']
|
commands: [{command: 'show foo', prompt: 'fooprompt', answer: 'yes'}]
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
iosxr_command:
|
iosxr_command:
|
||||||
commands:
|
commands:
|
||||||
- show version
|
- show version
|
||||||
- show foo
|
- [{command: 'show foo', prompt: 'fooprompt', answer: 'yes'}]
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
iosxr_facts:
|
iosxr_facts:
|
||||||
gather_subset:
|
gather_subset:
|
||||||
- all
|
- all
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
- name: test getting default facts
|
- name: test getting default facts
|
||||||
iosxr_facts:
|
iosxr_facts:
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
iosxr_facts:
|
iosxr_facts:
|
||||||
gather_subset:
|
gather_subset:
|
||||||
- "foobar"
|
- "foobar"
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
gather_subset:
|
gather_subset:
|
||||||
- "!hardware"
|
- "!hardware"
|
||||||
- "hardware"
|
- "hardware"
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
iosxr_facts:
|
iosxr_facts:
|
||||||
gather_subset:
|
gather_subset:
|
||||||
- "!hardware"
|
- "!hardware"
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
iosxr_interface:
|
iosxr_interface:
|
||||||
name: GigabitEthernet0/0/0/2
|
name: GigabitEthernet0/0/0/2
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
name: GigabitEthernet0/0/0/2
|
name: GigabitEthernet0/0/0/2
|
||||||
description: test-interface-initial
|
description: test-interface-initial
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -25,6 +27,7 @@
|
||||||
name: GigabitEthernet0/0/0/2
|
name: GigabitEthernet0/0/0/2
|
||||||
description: test-interface-initial
|
description: test-interface-initial
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -39,6 +42,7 @@
|
||||||
duplex: half
|
duplex: half
|
||||||
mtu: 512
|
mtu: 512
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -57,6 +61,7 @@
|
||||||
duplex: full
|
duplex: full
|
||||||
mtu: 256
|
mtu: 256
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -75,6 +80,7 @@
|
||||||
duplex: full
|
duplex: full
|
||||||
mtu: 256
|
mtu: 256
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
|
@ -84,6 +90,7 @@
|
||||||
iosxr_interface:
|
iosxr_interface:
|
||||||
name: GigabitEthernet0/0/0/2
|
name: GigabitEthernet0/0/0/2
|
||||||
enabled: False
|
enabled: False
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -95,6 +102,7 @@
|
||||||
iosxr_interface:
|
iosxr_interface:
|
||||||
name: GigabitEthernet0/0/0/2
|
name: GigabitEthernet0/0/0/2
|
||||||
enabled: True
|
enabled: True
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -107,6 +115,7 @@
|
||||||
name: GigabitEthernet0/0/0/3
|
name: GigabitEthernet0/0/0/3
|
||||||
description: test-interface-initial
|
description: test-interface-initial
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -120,6 +129,7 @@
|
||||||
- name: GigabitEthernet0/0/0/3
|
- name: GigabitEthernet0/0/0/3
|
||||||
- name: GigabitEthernet0/0/0/2
|
- name: GigabitEthernet0/0/0/2
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: Add interface aggregate
|
- name: Add interface aggregate
|
||||||
iosxr_interface:
|
iosxr_interface:
|
||||||
|
@ -129,6 +139,7 @@
|
||||||
speed: 100
|
speed: 100
|
||||||
duplex: full
|
duplex: full
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -152,6 +163,7 @@
|
||||||
speed: 100
|
speed: 100
|
||||||
duplex: full
|
duplex: full
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -165,6 +177,7 @@
|
||||||
- name: GigabitEthernet0/0/0/2
|
- name: GigabitEthernet0/0/0/2
|
||||||
enabled: False
|
enabled: False
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -180,6 +193,7 @@
|
||||||
- name: GigabitEthernet0/0/0/2
|
- name: GigabitEthernet0/0/0/2
|
||||||
enabled: True
|
enabled: True
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -194,6 +208,7 @@
|
||||||
- name: GigabitEthernet0/0/0/4
|
- name: GigabitEthernet0/0/0/4
|
||||||
- name: GigabitEthernet0/0/0/5
|
- name: GigabitEthernet0/0/0/5
|
||||||
description: test-interface-initial
|
description: test-interface-initial
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Create interface aggregate
|
- name: Create interface aggregate
|
||||||
|
@ -204,6 +219,7 @@
|
||||||
- name: GigabitEthernet0/0/0/5
|
- name: GigabitEthernet0/0/0/5
|
||||||
description: test_interface_2
|
description: test_interface_2
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -218,6 +234,7 @@
|
||||||
- name: GigabitEthernet0/0/0/4
|
- name: GigabitEthernet0/0/0/4
|
||||||
- name: GigabitEthernet0/0/0/5
|
- name: GigabitEthernet0/0/0/5
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -232,6 +249,7 @@
|
||||||
- name: GigabitEthernet0/0/0/4
|
- name: GigabitEthernet0/0/0/4
|
||||||
- name: GigabitEthernet0/0/0/5
|
- name: GigabitEthernet0/0/0/5
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
description: test_interface_1
|
description: test_interface_1
|
||||||
enabled: True
|
enabled: True
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Check intent arguments
|
- name: Check intent arguments
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
name: GigabitEthernet0/0/0/1
|
name: GigabitEthernet0/0/0/1
|
||||||
state: up
|
state: up
|
||||||
delay: 20
|
delay: 20
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -24,6 +26,7 @@
|
||||||
iosxr_interface:
|
iosxr_interface:
|
||||||
name: GigabitEthernet0/0/0/1
|
name: GigabitEthernet0/0/0/1
|
||||||
state: down
|
state: down
|
||||||
|
provider: "{{ cli }}"
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
@ -38,6 +41,7 @@
|
||||||
enabled: False
|
enabled: False
|
||||||
state: down
|
state: down
|
||||||
delay: 20
|
delay: 20
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -49,6 +53,7 @@
|
||||||
name: GigabitEthernet0/0/0/1
|
name: GigabitEthernet0/0/0/1
|
||||||
enabled: False
|
enabled: False
|
||||||
state: up
|
state: up
|
||||||
|
provider: "{{ cli }}"
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
@ -64,6 +69,7 @@
|
||||||
enabled: True
|
enabled: True
|
||||||
state: up
|
state: up
|
||||||
delay: 20
|
delay: 20
|
||||||
|
provider: "{{ cli }}"
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
dest: hostnameprefix
|
dest: hostnameprefix
|
||||||
name: 172.16.0.1
|
name: 172.16.0.1
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: Remove console logging
|
- name: Remove console logging
|
||||||
iosxr_logging:
|
iosxr_logging:
|
||||||
dest: console
|
dest: console
|
||||||
level: warning
|
level: warning
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Remove buffer
|
- name: Remove buffer
|
||||||
|
@ -18,6 +20,7 @@
|
||||||
dest: buffered
|
dest: buffered
|
||||||
size: 4800000
|
size: 4800000
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
# Start tests
|
# Start tests
|
||||||
|
@ -26,6 +29,7 @@
|
||||||
dest: hostnameprefix
|
dest: hostnameprefix
|
||||||
name: 172.16.0.1
|
name: 172.16.0.1
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -39,6 +43,7 @@
|
||||||
dest: hostnameprefix
|
dest: hostnameprefix
|
||||||
name: 172.16.0.1
|
name: 172.16.0.1
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -50,6 +55,7 @@
|
||||||
dest: hostnameprefix
|
dest: hostnameprefix
|
||||||
name: 172.16.0.1
|
name: 172.16.0.1
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -62,6 +68,7 @@
|
||||||
dest: hostnameprefix
|
dest: hostnameprefix
|
||||||
name: 172.16.0.1
|
name: 172.16.0.1
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -73,6 +80,7 @@
|
||||||
dest: console
|
dest: console
|
||||||
level: warning
|
level: warning
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -84,6 +92,7 @@
|
||||||
iosxr_logging:
|
iosxr_logging:
|
||||||
dest: buffered
|
dest: buffered
|
||||||
size: 4800000
|
size: 4800000
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -96,6 +105,7 @@
|
||||||
aggregate:
|
aggregate:
|
||||||
- { dest: console, level: notifications }
|
- { dest: console, level: notifications }
|
||||||
- { dest: buffered, size: 4700000 }
|
- { dest: buffered, size: 4700000 }
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -110,6 +120,7 @@
|
||||||
- { dest: console, level: notifications }
|
- { dest: console, level: notifications }
|
||||||
- { dest: buffered, size: 4700000 }
|
- { dest: buffered, size: 4700000 }
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -7,12 +7,14 @@
|
||||||
- no ip domain-list ansible.com
|
- no ip domain-list ansible.com
|
||||||
- no ip domain-list redhat.com
|
- no ip domain-list redhat.com
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: configure domain_search
|
- name: configure domain_search
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
domain_search:
|
domain_search:
|
||||||
- ansible.com
|
- ansible.com
|
||||||
- redhat.com
|
- redhat.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -26,6 +28,7 @@
|
||||||
domain_search:
|
domain_search:
|
||||||
- ansible.com
|
- ansible.com
|
||||||
- redhat.com
|
- redhat.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -36,6 +39,7 @@
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
domain_search:
|
domain_search:
|
||||||
- ansible.com
|
- ansible.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -47,6 +51,7 @@
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
domain_search:
|
domain_search:
|
||||||
- ansible.com
|
- ansible.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -58,6 +63,7 @@
|
||||||
domain_search:
|
domain_search:
|
||||||
- ansible.com
|
- ansible.com
|
||||||
- redhat.com
|
- redhat.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -70,6 +76,7 @@
|
||||||
domain_search:
|
domain_search:
|
||||||
- ansible.com
|
- ansible.com
|
||||||
- redhat.com
|
- redhat.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -81,6 +88,7 @@
|
||||||
domain_search:
|
domain_search:
|
||||||
- ansible.com
|
- ansible.com
|
||||||
- eng.ansible.com
|
- eng.ansible.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -95,6 +103,7 @@
|
||||||
domain_search:
|
domain_search:
|
||||||
- ansible.com
|
- ansible.com
|
||||||
- eng.ansible.com
|
- eng.ansible.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -108,5 +117,6 @@
|
||||||
- no domain list redhat.com
|
- no domain list redhat.com
|
||||||
- no domain list eng.ansible.com
|
- no domain list eng.ansible.com
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- debug: msg="END cli/set_domain_search.yaml"
|
- debug: msg="END cli/set_domain_search.yaml"
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
iosxr_config:
|
iosxr_config:
|
||||||
lines: no domain name
|
lines: no domain name
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: configure domain_name
|
- name: configure domain_name
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
domain_name: eng.ansible.com
|
domain_name: eng.ansible.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -18,6 +20,7 @@
|
||||||
- name: verify domain_name
|
- name: verify domain_name
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
domain_name: eng.ansible.com
|
domain_name: eng.ansible.com
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -28,5 +31,6 @@
|
||||||
iosxr_config:
|
iosxr_config:
|
||||||
lines: no domain name
|
lines: no domain name
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- debug: msg="END cli/set_domain_name.yaml"
|
- debug: msg="END cli/set_domain_name.yaml"
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
iosxr_config:
|
iosxr_config:
|
||||||
lines: hostname switch
|
lines: hostname switch
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: configure hostname
|
- name: configure hostname
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
hostname: foo
|
hostname: foo
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -18,6 +20,7 @@
|
||||||
- name: verify hostname
|
- name: verify hostname
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
hostname: foo
|
hostname: foo
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -28,5 +31,6 @@
|
||||||
iosxr_config:
|
iosxr_config:
|
||||||
lines: "hostname {{ inventory_hostname }}"
|
lines: "hostname {{ inventory_hostname }}"
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- debug: msg="END cli/set_hostname.yaml"
|
- debug: msg="END cli/set_hostname.yaml"
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
- no domain lookup source-interface Loopback10
|
- no domain lookup source-interface Loopback10
|
||||||
# - vrf ansible
|
# - vrf ansible
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: configure lookup_source
|
- name: configure lookup_source
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
lookup_source: Loopback10
|
lookup_source: Loopback10
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -21,6 +23,7 @@
|
||||||
- name: verify lookup_source
|
- name: verify lookup_source
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
lookup_source: Loopback10
|
lookup_source: Loopback10
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -58,5 +61,6 @@
|
||||||
- no domain lookup source-interface Loopback10
|
- no domain lookup source-interface Loopback10
|
||||||
- no vrf ansible
|
- no vrf ansible
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- debug: msg="END cli/set_lookup_source.yaml"
|
- debug: msg="END cli/set_lookup_source.yaml"
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
- no ip name-server 2.2.2.2
|
- no ip name-server 2.2.2.2
|
||||||
- no ip name-server 3.3.3.3
|
- no ip name-server 3.3.3.3
|
||||||
match: none
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: configure name_servers
|
- name: configure name_servers
|
||||||
iosxr_system:
|
iosxr_system:
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
- 1.1.1.1
|
- 1.1.1.1
|
||||||
- 2.2.2.2
|
- 2.2.2.2
|
||||||
- 3.3.3.3
|
- 3.3.3.3
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -31,6 +33,7 @@
|
||||||
- 1.1.1.1
|
- 1.1.1.1
|
||||||
- 2.2.2.2
|
- 2.2.2.2
|
||||||
- 3.3.3.3
|
- 3.3.3.3
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -69,6 +72,7 @@
|
||||||
name_servers:
|
name_servers:
|
||||||
- 1.1.1.1
|
- 1.1.1.1
|
||||||
- 2.2.2.2
|
- 2.2.2.2
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
name: auth_user
|
name: auth_user
|
||||||
state: present
|
state: present
|
||||||
configured_password: pass123
|
configured_password: pass123
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: test login
|
- name: test login
|
||||||
expect:
|
expect:
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
name: auth_user
|
name: auth_user
|
||||||
state: present
|
state: present
|
||||||
public_key_contents: "{{ lookup('file', \"{{ role_path }}/files/public.pub\") }}"
|
public_key_contents: "{{ lookup('file', \"{{ role_path }}/files/public.pub\") }}"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: test login with private key
|
- name: test login with private key
|
||||||
expect:
|
expect:
|
||||||
|
@ -40,6 +42,7 @@
|
||||||
- name: remove user and key
|
- name: remove user and key
|
||||||
iosxr_user:
|
iosxr_user:
|
||||||
name: auth_user
|
name: auth_user
|
||||||
|
provider: "{{ cli }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: test login with private key (should fail, no user)
|
- name: test login with private key (should fail, no user)
|
||||||
|
@ -55,6 +58,7 @@
|
||||||
name: auth_user
|
name: auth_user
|
||||||
state: present
|
state: present
|
||||||
public_key: "{{ role_path }}/files/public.pub"
|
public_key: "{{ role_path }}/files/public.pub"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: test login with private key
|
- name: test login with private key
|
||||||
expect:
|
expect:
|
||||||
|
@ -68,6 +72,7 @@
|
||||||
name: auth_user
|
name: auth_user
|
||||||
state: present
|
state: present
|
||||||
public_key_contents: "{{ lookup('file', \"{{ role_path }}/files/public2.pub\") }}"
|
public_key_contents: "{{ lookup('file', \"{{ role_path }}/files/public2.pub\") }}"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
# FIXME: pexpect fails with OSError: [Errno 5] Input/output error
|
# FIXME: pexpect fails with OSError: [Errno 5] Input/output error
|
||||||
- name: test login with invalid private key (should fail)
|
- name: test login with invalid private key (should fail)
|
||||||
|
@ -88,4 +93,5 @@
|
||||||
iosxr_user:
|
iosxr_user:
|
||||||
name: auth_user
|
name: auth_user
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
- no username ansibletest1
|
- no username ansibletest1
|
||||||
- no username ansibletest2
|
- no username ansibletest2
|
||||||
- no username ansibletest3
|
- no username ansibletest3
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
- name: Create user (SetUp)
|
- name: Create user (SetUp)
|
||||||
iosxr_user:
|
iosxr_user:
|
||||||
name: ansibletest1
|
name: ansibletest1
|
||||||
configured_password: test
|
configured_password: test
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -25,6 +27,7 @@
|
||||||
configured_password: test
|
configured_password: test
|
||||||
update_password: always
|
update_password: always
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -39,6 +42,7 @@
|
||||||
configured_password: test
|
configured_password: test
|
||||||
update_password: on_create
|
update_password: on_create
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -53,6 +57,7 @@
|
||||||
update_password: on_create
|
update_password: on_create
|
||||||
group: sysadmin
|
group: sysadmin
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -68,6 +73,7 @@
|
||||||
update_password: on_create
|
update_password: on_create
|
||||||
group: sysadmin
|
group: sysadmin
|
||||||
state: present
|
state: present
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -83,6 +89,7 @@
|
||||||
configured_password: test
|
configured_password: test
|
||||||
state: present
|
state: present
|
||||||
group: sysadmin
|
group: sysadmin
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -103,6 +110,7 @@
|
||||||
configured_password: test
|
configured_password: test
|
||||||
state: present
|
state: present
|
||||||
group: sysadmin
|
group: sysadmin
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -122,6 +130,7 @@
|
||||||
update_password: on_create
|
update_password: on_create
|
||||||
state: present
|
state: present
|
||||||
group: sysadmin
|
group: sysadmin
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -136,6 +145,7 @@
|
||||||
- name: ansibletest2
|
- name: ansibletest2
|
||||||
- name: ansibletest3
|
- name: ansibletest3
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -150,6 +160,7 @@
|
||||||
- name: ansibletest2
|
- name: ansibletest2
|
||||||
- name: ansibletest3
|
- name: ansibletest3
|
||||||
state: absent
|
state: absent
|
||||||
|
provider: "{{ cli }}"
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -182,10 +182,6 @@ lib/ansible/modules/network/ios/ios_command.py
|
||||||
lib/ansible/modules/network/ios/ios_facts.py
|
lib/ansible/modules/network/ios/ios_facts.py
|
||||||
lib/ansible/modules/network/ios/ios_system.py
|
lib/ansible/modules/network/ios/ios_system.py
|
||||||
lib/ansible/modules/network/ios/ios_vrf.py
|
lib/ansible/modules/network/ios/ios_vrf.py
|
||||||
lib/ansible/modules/network/iosxr/iosxr_command.py
|
|
||||||
lib/ansible/modules/network/iosxr/iosxr_config.py
|
|
||||||
lib/ansible/modules/network/iosxr/iosxr_facts.py
|
|
||||||
lib/ansible/modules/network/iosxr/iosxr_system.py
|
|
||||||
lib/ansible/modules/network/netvisor/pn_cluster.py
|
lib/ansible/modules/network/netvisor/pn_cluster.py
|
||||||
lib/ansible/modules/network/netvisor/pn_ospfarea.py
|
lib/ansible/modules/network/netvisor/pn_ospfarea.py
|
||||||
lib/ansible/modules/network/netvisor/pn_vlag.py
|
lib/ansible/modules/network/netvisor/pn_vlag.py
|
||||||
|
|
|
@ -32,13 +32,13 @@ class TestIosxrCommandModule(TestIosxrModule):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestIosxrCommandModule, self).setUp()
|
super(TestIosxrCommandModule, self).setUp()
|
||||||
|
|
||||||
self.mock_run_commands = patch('ansible.modules.network.iosxr.iosxr_command.run_commands')
|
self.mock_run_command = patch('ansible.modules.network.iosxr.iosxr_command.run_command')
|
||||||
self.run_commands = self.mock_run_commands.start()
|
self.run_command = self.mock_run_command.start()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(TestIosxrCommandModule, self).tearDown()
|
super(TestIosxrCommandModule, self).tearDown()
|
||||||
|
|
||||||
self.mock_run_commands.stop()
|
self.mock_run_command.stop()
|
||||||
|
|
||||||
def load_fixtures(self, commands=None):
|
def load_fixtures(self, commands=None):
|
||||||
|
|
||||||
|
@ -49,13 +49,13 @@ class TestIosxrCommandModule(TestIosxrModule):
|
||||||
for item in commands:
|
for item in commands:
|
||||||
try:
|
try:
|
||||||
command = item['command']
|
command = item['command']
|
||||||
except ValueError:
|
except Exception:
|
||||||
command = item
|
command = item
|
||||||
filename = str(command).replace(' ', '_')
|
filename = str(command).replace(' ', '_')
|
||||||
output.append(load_fixture(filename))
|
output.append(load_fixture(filename))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
self.run_commands.side_effect = load_from_file
|
self.run_command.side_effect = load_from_file
|
||||||
|
|
||||||
def test_iosxr_command_simple(self):
|
def test_iosxr_command_simple(self):
|
||||||
set_module_args(dict(commands=['show version']))
|
set_module_args(dict(commands=['show version']))
|
||||||
|
@ -78,13 +78,13 @@ class TestIosxrCommandModule(TestIosxrModule):
|
||||||
wait_for = 'result[0] contains "test string"'
|
wait_for = 'result[0] contains "test string"'
|
||||||
set_module_args(dict(commands=['show version'], wait_for=wait_for))
|
set_module_args(dict(commands=['show version'], wait_for=wait_for))
|
||||||
self.execute_module(failed=True)
|
self.execute_module(failed=True)
|
||||||
self.assertEqual(self.run_commands.call_count, 10)
|
self.assertEqual(self.run_command.call_count, 10)
|
||||||
|
|
||||||
def test_iosxr_command_retries(self):
|
def test_iosxr_command_retries(self):
|
||||||
wait_for = 'result[0] contains "test string"'
|
wait_for = 'result[0] contains "test string"'
|
||||||
set_module_args(dict(commands=['show version'], wait_for=wait_for, retries=2))
|
set_module_args(dict(commands=['show version'], wait_for=wait_for, retries=2))
|
||||||
self.execute_module(failed=True)
|
self.execute_module(failed=True)
|
||||||
self.assertEqual(self.run_commands.call_count, 2)
|
self.assertEqual(self.run_command.call_count, 2)
|
||||||
|
|
||||||
def test_iosxr_command_match_any(self):
|
def test_iosxr_command_match_any(self):
|
||||||
wait_for = ['result[0] contains "Cisco IOS"',
|
wait_for = ['result[0] contains "Cisco IOS"',
|
||||||
|
|
|
@ -34,14 +34,14 @@ class TestIosxrFacts(TestIosxrModule):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestIosxrFacts, self).setUp()
|
super(TestIosxrFacts, self).setUp()
|
||||||
|
|
||||||
self.mock_run_commands = patch(
|
self.mock_run_command = patch(
|
||||||
'ansible.modules.network.iosxr.iosxr_facts.run_commands')
|
'ansible.modules.network.iosxr.iosxr_facts.run_command')
|
||||||
self.run_commands = self.mock_run_commands.start()
|
self.run_command = self.mock_run_command.start()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(TestIosxrFacts, self).tearDown()
|
super(TestIosxrFacts, self).tearDown()
|
||||||
|
|
||||||
self.mock_run_commands.stop()
|
self.mock_run_command.stop()
|
||||||
|
|
||||||
def load_fixtures(self, commands=None):
|
def load_fixtures(self, commands=None):
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class TestIosxrFacts(TestIosxrModule):
|
||||||
output.append(load_fixture(filename))
|
output.append(load_fixture(filename))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
self.run_commands.side_effect = load_from_file
|
self.run_command.side_effect = load_from_file
|
||||||
|
|
||||||
def test_iosxr_facts_gather_subset_default(self):
|
def test_iosxr_facts_gather_subset_default(self):
|
||||||
set_module_args(dict())
|
set_module_args(dict())
|
||||||
|
|
Loading…
Reference in a new issue