From c27e47327f902dddf0b93767e387cacdfc12e11f Mon Sep 17 00:00:00 2001 From: Ganesh Nalawade Date: Sun, 3 Nov 2019 04:10:30 +0530 Subject: [PATCH] Refactor CLI prompt mode check for network plugins (#63945) * Refactor CLI prompt mode check for network plugins * Move the CLI prompt mode check logic from action plugin to the controller side with the cliconf plugins. * This refactor also allows the network modules to initialise the persistent connection with remote device only when it is required. * Fix review comments --- .../scripts/ansible_connection_cli_stub.py | 3 +- lib/ansible/plugins/action/aireos.py | 10 ------- lib/ansible/plugins/action/aruba.py | 10 ------- lib/ansible/plugins/action/ce.py | 19 ------------- lib/ansible/plugins/action/cnos.py | 16 ----------- lib/ansible/plugins/action/dellos10.py | 15 ---------- lib/ansible/plugins/action/dellos6.py | 14 ---------- lib/ansible/plugins/action/dellos9.py | 14 ---------- lib/ansible/plugins/action/enos.py | 17 +---------- lib/ansible/plugins/action/eos.py | 16 ----------- lib/ansible/plugins/action/ios.py | 18 ------------ lib/ansible/plugins/action/iosxr.py | 17 ----------- lib/ansible/plugins/action/ironware.py | 19 ------------- lib/ansible/plugins/action/junos.py | 16 ----------- lib/ansible/plugins/action/nxos.py | 19 ------------- lib/ansible/plugins/action/vyos.py | 15 ---------- lib/ansible/plugins/cliconf/__init__.py | 28 +++++++++++++++++++ lib/ansible/plugins/cliconf/aireos.py | 9 ++++++ lib/ansible/plugins/cliconf/aruba.py | 8 ++++++ lib/ansible/plugins/cliconf/ce.py | 22 +++++++++++++++ lib/ansible/plugins/cliconf/cnos.py | 20 ++++++++++++- lib/ansible/plugins/cliconf/dellos10.py | 8 ++++++ lib/ansible/plugins/cliconf/dellos6.py | 8 ++++++ lib/ansible/plugins/cliconf/dellos9.py | 8 ++++++ lib/ansible/plugins/cliconf/enos.py | 21 +++++++++++++- lib/ansible/plugins/cliconf/eos.py | 8 ++++++ lib/ansible/plugins/cliconf/ios.py | 18 ++++++++++-- lib/ansible/plugins/cliconf/iosxr.py | 8 ++++++ lib/ansible/plugins/cliconf/ironware.py | 8 ++++++ lib/ansible/plugins/cliconf/junos.py | 8 ++++++ lib/ansible/plugins/cliconf/nxos.py | 18 ++++++++++++ lib/ansible/plugins/cliconf/vyos.py | 8 ++++++ lib/ansible/plugins/connection/network_cli.py | 2 ++ 33 files changed, 209 insertions(+), 239 deletions(-) diff --git a/lib/ansible/cli/scripts/ansible_connection_cli_stub.py b/lib/ansible/cli/scripts/ansible_connection_cli_stub.py index 1f04039329b..20bfc6b3a8b 100755 --- a/lib/ansible/cli/scripts/ansible_connection_cli_stub.py +++ b/lib/ansible/cli/scripts/ansible_connection_cli_stub.py @@ -305,8 +305,9 @@ def main(): pc_data = to_text(init_data) try: conn.update_play_context(pc_data) + conn.set_cli_prompt_context() except Exception as exc: - # Only network_cli has update_play context, so missing this is + # Only network_cli has update_play context and set_cli_prompt_context, so missing this is # not fatal e.g. netconf if isinstance(exc, ConnectionError) and getattr(exc, 'code', None) == -32601: pass diff --git a/lib/ansible/plugins/action/aireos.py b/lib/ansible/plugins/action/aireos.py index 36fc0a7134a..38e897ae00c 100644 --- a/lib/ansible/plugins/action/aireos.py +++ b/lib/ansible/plugins/action/aireos.py @@ -23,8 +23,6 @@ import sys import copy from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.aireos.aireos import aireos_provider_spec from ansible.module_utils.network.common.utils import load_provider @@ -69,14 +67,6 @@ class ActionModule(ActionNetworkModule): 'msg': 'unable to open shell. Please see: ' + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} - # make sure we are in the right cli context which should be - # enable mode and not config module - conn = Connection(socket_path) - out = conn.get_prompt() - if 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('exit') - task_vars['ansible_socket'] = socket_path if self._play_context.become_method == 'enable': diff --git a/lib/ansible/plugins/action/aruba.py b/lib/ansible/plugins/action/aruba.py index 9c246529aac..193fdac91f5 100644 --- a/lib/ansible/plugins/action/aruba.py +++ b/lib/ansible/plugins/action/aruba.py @@ -23,8 +23,6 @@ import sys import copy from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.aruba.aruba import aruba_provider_spec from ansible.module_utils.network.common.utils import load_provider @@ -70,14 +68,6 @@ class ActionModule(ActionNetworkModule): 'msg': 'unable to open shell. Please see: ' + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} - # make sure we are in the right cli context which should be - # enable mode and not config module - conn = Connection(socket_path) - out = conn.get_prompt() - if 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('exit') - task_vars['ansible_socket'] = socket_path if self._play_context.become_method == 'enable': diff --git a/lib/ansible/plugins/action/ce.py b/lib/ansible/plugins/action/ce.py index 303ea53bbfb..c5877d22d70 100644 --- a/lib/ansible/plugins/action/ce.py +++ b/lib/ansible/plugins/action/ce.py @@ -10,8 +10,6 @@ import sys import copy from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.cloudengine.ce import ce_provider_spec from ansible.module_utils.network.common.utils import load_provider @@ -85,22 +83,5 @@ class ActionModule(ActionNetworkModule): return {'failed': True, 'msg': "Connection type '%s' is not valid for '%s' module." % (self._play_context.connection, self._task.action)} - if (self._play_context.connection == 'local' and transport == 'cli' and self._task.action in CLI_SUPPORTED_MODULES) \ - or self._play_context.connection == 'network_cli': - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._connection.socket_path - conn = Connection(socket_path) - out = conn.get_prompt() - prompt = to_text(out, errors='surrogate_then_replace').strip() - while prompt.endswith(']'): - display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr) - if prompt.startswith('[*'): - conn.exec_command('clear configuration candidate') - conn.exec_command('return') - out = conn.get_prompt() - prompt = to_text(out, errors='surrogate_then_replace').strip() - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/cnos.py b/lib/ansible/plugins/action/cnos.py index 66dc14c8cf2..064fb3a9439 100644 --- a/lib/ansible/plugins/action/cnos.py +++ b/lib/ansible/plugins/action/cnos.py @@ -24,8 +24,6 @@ from ansible import constants as C from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.cnos.cnos import cnos_provider_spec from ansible.module_utils.network.common.utils import load_provider -from ansible.module_utils.connection import Connection -from ansible.module_utils._text import to_text from ansible.utils.display import Display display = Display() @@ -37,7 +35,6 @@ class ActionModule(ActionNetworkModule): del tmp # tmp no longer has any effect self._config_module = True if self._task.action == 'cnos_config' else False - socket_path = None if self._play_context.connection == 'local': provider = load_provider(cnos_provider_spec, self._task.args) @@ -67,18 +64,5 @@ class ActionModule(ActionNetworkModule): task_vars['ansible_socket'] = socket_path - # make sure we are in the right cli context which should be - # enable mode and not config module or exec mode - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - out = conn.get_prompt() - if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'): - display.vvvv('In Config mode, sending exit to device', self._play_context.remote_addr) - conn.send_command('exit') - else: - conn.send_command('enable') - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/dellos10.py b/lib/ansible/plugins/action/dellos10.py index 70bf263f7e6..d3a8480b170 100644 --- a/lib/ansible/plugins/action/dellos10.py +++ b/lib/ansible/plugins/action/dellos10.py @@ -25,8 +25,6 @@ import sys import copy from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.dellos10.dellos10 import dellos10_provider_spec @@ -41,7 +39,6 @@ class ActionModule(ActionNetworkModule): del tmp # tmp no longer has any effect self._config_module = True if self._task.action == 'dellos10_config' else False - socket_path = None if self._play_context.connection == 'network_cli': provider = self._task.args.get('provider', {}) @@ -77,17 +74,5 @@ class ActionModule(ActionNetworkModule): task_vars['ansible_socket'] = socket_path - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._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('exit') - out = conn.get_prompt() - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/dellos6.py b/lib/ansible/plugins/action/dellos6.py index e8194aade15..9efcb6493d5 100644 --- a/lib/ansible/plugins/action/dellos6.py +++ b/lib/ansible/plugins/action/dellos6.py @@ -25,8 +25,6 @@ import sys import copy from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.dellos6.dellos6 import dellos6_provider_spec from ansible.module_utils.network.common.utils import load_provider @@ -77,17 +75,5 @@ class ActionModule(ActionNetworkModule): task_vars['ansible_socket'] = socket_path - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._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('exit') - out = conn.get_prompt() - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/dellos9.py b/lib/ansible/plugins/action/dellos9.py index 20b82cf31f3..318f0d87892 100644 --- a/lib/ansible/plugins/action/dellos9.py +++ b/lib/ansible/plugins/action/dellos9.py @@ -25,8 +25,6 @@ import sys import copy from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.dellos9.dellos9 import dellos9_provider_spec @@ -77,17 +75,5 @@ class ActionModule(ActionNetworkModule): task_vars['ansible_socket'] = socket_path - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._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('exit') - out = conn.get_prompt() - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/enos.py b/lib/ansible/plugins/action/enos.py index 25db538af52..4eb650f6f4d 100644 --- a/lib/ansible/plugins/action/enos.py +++ b/lib/ansible/plugins/action/enos.py @@ -24,8 +24,6 @@ from ansible import constants as C from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.enos.enos import enos_provider_spec from ansible.module_utils.network.common.utils import load_provider -from ansible.module_utils.connection import Connection -from ansible.module_utils._text import to_text from ansible.utils.display import Display display = Display() @@ -37,7 +35,7 @@ class ActionModule(ActionNetworkModule): del tmp # tmp no longer has any effect self._config_module = True if self._task.action == 'enos_config' else False - socket_path = None + if self._play_context.connection == 'local': provider = load_provider(enos_provider_spec, self._task.args) pc = copy.deepcopy(self._play_context) @@ -66,18 +64,5 @@ class ActionModule(ActionNetworkModule): task_vars['ansible_socket'] = socket_path - # make sure we are in the right cli context which should be - # enable mode and not config module or exec mode - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - out = conn.get_prompt() - if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'): - display.vvvv('In Config mode, sending exit to device', self._play_context.remote_addr) - conn.send_command('exit') - else: - conn.send_command('enable') - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/eos.py b/lib/ansible/plugins/action/eos.py index dcaacd80ee9..9a4d41efae5 100644 --- a/lib/ansible/plugins/action/eos.py +++ b/lib/ansible/plugins/action/eos.py @@ -23,8 +23,6 @@ import sys import copy from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.module_utils.network.eos.eos import eos_provider_spec from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.common.utils import load_provider @@ -40,7 +38,6 @@ class ActionModule(ActionNetworkModule): module_name = self._task.action.split('.')[-1] self._config_module = True if module_name == 'eos_config' else False - socket_path = None if self._play_context.connection in ('network_cli', 'httpapi'): provider = self._task.args.get('provider', {}) @@ -90,19 +87,6 @@ class ActionModule(ActionNetworkModule): else: return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} - if (self._play_context.connection == 'local' and transport == 'cli') or self._play_context.connection == 'network_cli': - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - out = conn.get_prompt() - while '(config' in to_text(out, errors='surrogate_then_replace').strip(): - 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(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/ios.py b/lib/ansible/plugins/action/ios.py index 8cabaa6e924..9d2a630559a 100644 --- a/lib/ansible/plugins/action/ios.py +++ b/lib/ansible/plugins/action/ios.py @@ -19,12 +19,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import re import sys import copy -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection, ConnectionError from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.ios.ios import ios_provider_spec @@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule): module_name = self._task.action.split('.')[-1] self._config_module = True if module_name == 'ios_config' else False - socket_path = None if self._play_context.connection == 'network_cli': provider = self._task.args.get('provider', {}) @@ -79,19 +75,5 @@ class ActionModule(ActionNetworkModule): else: return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - try: - out = conn.get_prompt() - if re.search(r'config.*\)#', to_text(out, errors='surrogate_then_replace').strip()): - display.vvvv('wrong context, sending end to device', self._play_context.remote_addr) - conn.send_command('end') - except ConnectionError as exc: - return {'failed': True, 'msg': to_text(exc)} - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/iosxr.py b/lib/ansible/plugins/action/iosxr.py index c523630097c..61950309ca8 100644 --- a/lib/ansible/plugins/action/iosxr.py +++ b/lib/ansible/plugins/action/iosxr.py @@ -22,9 +22,6 @@ __metaclass__ = type import sys import copy -from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.module_utils.network.iosxr.iosxr import iosxr_provider_spec from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.common.utils import load_provider @@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule): module_name = self._task.action.split('.')[-1] self._config_module = True if module_name == 'iosxr_config' else False - socket_path = None force_cli = module_name in ('iosxr_netconf', 'iosxr_config', 'iosxr_command', 'iosxr_facts') if self._play_context.connection == 'local': @@ -86,18 +82,5 @@ class ActionModule(ActionNetworkModule): else: return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} - # make sure we are in the right cli context which should be - # enable mode and not config module - if (self._play_context.connection == 'local' and pc.connection == 'network_cli') or self._play_context.connection == 'network_cli': - if socket_path is None: - socket_path = self._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() - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/ironware.py b/lib/ansible/plugins/action/ironware.py index bedaa0ac527..0ef8898d2cf 100644 --- a/lib/ansible/plugins/action/ironware.py +++ b/lib/ansible/plugins/action/ironware.py @@ -22,9 +22,6 @@ __metaclass__ = type import sys import copy -from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection, ConnectionError from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.ironware.ironware import ironware_provider_spec @@ -39,7 +36,6 @@ class ActionModule(ActionNetworkModule): del tmp # tmp no longer has any effect self._config_module = True if self._task.action == 'ironware_config' else False - socket_path = None if self._play_context.connection == 'network_cli': provider = self._task.args.get('provider', {}) @@ -78,20 +74,5 @@ class ActionModule(ActionNetworkModule): else: return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - try: - 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('exit') - out = conn.get_prompt() - except ConnectionError as exc: - return {'failed': True, 'msg': to_text(exc)} - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/junos.py b/lib/ansible/plugins/action/junos.py index adf89e264d8..7a81854bbf1 100644 --- a/lib/ansible/plugins/action/junos.py +++ b/lib/ansible/plugins/action/junos.py @@ -22,8 +22,6 @@ __metaclass__ = type import sys import copy -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.junos.junos import junos_provider_spec from ansible.plugins.action.network import ActionModule as ActionNetworkModule @@ -41,7 +39,6 @@ class ActionModule(ActionNetworkModule): module_name = self._task.action.split('.')[-1] self._config_module = True if module_name == 'junos_config' else False - socket_path = None if self._play_context.connection == 'local': provider = load_provider(junos_provider_spec, self._task.args) @@ -94,18 +91,5 @@ class ActionModule(ActionNetworkModule): "Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html" % (self._play_context.connection, module_name)} - if (self._play_context.connection == 'local' and pc.connection == 'network_cli') or self._play_context.connection == 'network_cli': - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._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('exit') - out = conn.get_prompt() - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/nxos.py b/lib/ansible/plugins/action/nxos.py index 62ce01ea1a4..5d9763da83c 100644 --- a/lib/ansible/plugins/action/nxos.py +++ b/lib/ansible/plugins/action/nxos.py @@ -24,8 +24,6 @@ import re import sys from ansible import constants as C -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.nxos.nxos import nxos_provider_spec @@ -56,8 +54,6 @@ class ActionModule(ActionNetworkModule): self._task.args['username'] = self._play_context.connection_user if module_name == 'nxos_install_os': - persistent_command_timeout = 0 - persistent_connect_timeout = 0 connection = self._connection if connection.transport == 'local': persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT @@ -124,21 +120,6 @@ class ActionModule(ActionNetworkModule): else: return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} - if (self._play_context.connection == 'local' and transport == 'cli') or self._play_context.connection == 'network_cli': - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - # Match prompts ending in )# except those with (maint-mode)# - config_prompt = re.compile(r'^.*\((?!maint-mode).*\)#$') - out = conn.get_prompt() - while config_prompt.match(to_text(out, errors='surrogate_then_replace').strip()): - display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr) - conn.send_command('exit') - out = conn.get_prompt() - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/action/vyos.py b/lib/ansible/plugins/action/vyos.py index f4cf1425334..e0083d98fe2 100644 --- a/lib/ansible/plugins/action/vyos.py +++ b/lib/ansible/plugins/action/vyos.py @@ -22,10 +22,7 @@ __metaclass__ = type import sys import copy -from ansible import constants as C from ansible.plugins.action.network import ActionModule as ActionNetworkModule -from ansible.module_utils._text import to_text -from ansible.module_utils.connection import Connection from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.vyos.vyos import vyos_provider_spec from ansible.utils.display import Display @@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule): module_name = self._task.action.split('.')[-1] self._config_module = True if module_name == 'vyos_config' else False - socket_path = None if self._play_context.connection == 'network_cli': provider = self._task.args.get('provider', {}) @@ -75,16 +71,5 @@ class ActionModule(ActionNetworkModule): else: return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} - # make sure we are in the right cli context which should be - # enable mode and not config module - if socket_path is None: - socket_path = self._connection.socket_path - - conn = Connection(socket_path) - out = conn.get_prompt() - if 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('exit discard') - result = super(ActionModule, self).run(task_vars=task_vars) return result diff --git a/lib/ansible/plugins/cliconf/__init__.py b/lib/ansible/plugins/cliconf/__init__.py index b4dd9e57853..be2df78daf6 100644 --- a/lib/ansible/plugins/cliconf/__init__.py +++ b/lib/ansible/plugins/cliconf/__init__.py @@ -447,3 +447,31 @@ class CliconfBase(AnsiblePlugin): if replace and not operations.get('supports_replace', False): raise ValueError("configuration replace is not supported") + + def set_cli_prompt_context(self): + """ + Ensure the command prompt on device is in right mode + :return: None + """ + pass + + def _update_cli_prompt_context(self, config_context=None, exit_command='exit'): + """ + Update the cli prompt context to ensure it is in operational mode + :param config_context: It is string value to identify if the current cli prompt ends with config mode prompt + :param exit_command: Command to execute to exit the config mode + :return: None + """ + out = self._connection.get_prompt() + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + + while True: + out = to_text(out, errors='surrogate_then_replace').strip() + if config_context and out.endswith(config_context): + self._connection.queue_message('vvvv', 'wrong context, sending exit to device') + self.send_command(exit_command) + out = self._connection.get_prompt() + else: + break diff --git a/lib/ansible/plugins/cliconf/aireos.py b/lib/ansible/plugins/cliconf/aireos.py index 8e84da26e7c..8ce71f89d33 100644 --- a/lib/ansible/plugins/cliconf/aireos.py +++ b/lib/ansible/plugins/cliconf/aireos.py @@ -34,6 +34,7 @@ import json from itertools import chain +from ansible.errors import AnsibleConnectionFailure from ansible.module_utils._text import to_text from ansible.module_utils.network.common.utils import to_list from ansible.plugins.cliconf import CliconfBase, enable_mode @@ -85,3 +86,11 @@ class Cliconf(CliconfBase): def get_capabilities(self): result = super(Cliconf, self).get_capabilities() return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/lib/ansible/plugins/cliconf/aruba.py b/lib/ansible/plugins/cliconf/aruba.py index cb5c4219ac0..8dd146575dd 100644 --- a/lib/ansible/plugins/cliconf/aruba.py +++ b/lib/ansible/plugins/cliconf/aruba.py @@ -86,3 +86,11 @@ class Cliconf(CliconfBase): def get_capabilities(self): result = super(Cliconf, self).get_capabilities() return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/lib/ansible/plugins/cliconf/ce.py b/lib/ansible/plugins/cliconf/ce.py index 859cfd6704d..ee7072d018b 100644 --- a/lib/ansible/plugins/cliconf/ce.py +++ b/lib/ansible/plugins/cliconf/ce.py @@ -34,6 +34,7 @@ import json from itertools import chain +from ansible.errors import AnsibleConnectionFailure from ansible.module_utils._text import to_text from ansible.module_utils.network.common.utils import to_list from ansible.plugins.cliconf import CliconfBase, enable_mode @@ -98,3 +99,24 @@ class Cliconf(CliconfBase): def get_capabilities(self): result = super(Cliconf, self).get_capabilities() return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + + prompt = to_text(out, errors='surrogate_then_replace').strip() + while prompt.endswith(']'): + self._connection.queue_message('vvvv', 'wrong context, sending return to device') + if prompt.startswith('[*'): + self._connection.exec_command('clear configuration candidate') + self._connection.exec_command('return') + out = self._connection.get_prompt() + prompt = to_text(out, errors='surrogate_then_replace').strip() diff --git a/lib/ansible/plugins/cliconf/cnos.py b/lib/ansible/plugins/cliconf/cnos.py index 254c743b471..0a852f0b395 100644 --- a/lib/ansible/plugins/cliconf/cnos.py +++ b/lib/ansible/plugins/cliconf/cnos.py @@ -29,7 +29,7 @@ version_added: 2.6 import re import json -from itertools import chain +from ansible.errors import AnsibleConnectionFailure from ansible.module_utils.common._collections_compat import Mapping from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils.network.common.utils import to_list @@ -116,3 +116,21 @@ class Cliconf(CliconfBase): def get_capabilities(self): result = super(Cliconf, self).get_capabilities() return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + + if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'): + self._connection.queue_message('vvvv', 'In Config mode, sending exit to device') + self._connection.send_command('exit') + else: + self._connection.send_command('enable') diff --git a/lib/ansible/plugins/cliconf/dellos10.py b/lib/ansible/plugins/cliconf/dellos10.py index 40bee342f3e..85ea17ab4d9 100644 --- a/lib/ansible/plugins/cliconf/dellos10.py +++ b/lib/ansible/plugins/cliconf/dellos10.py @@ -113,3 +113,11 @@ class Cliconf(CliconfBase): responses.append(out) return responses + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/lib/ansible/plugins/cliconf/dellos6.py b/lib/ansible/plugins/cliconf/dellos6.py index bae7caeb78e..6dba49b1432 100644 --- a/lib/ansible/plugins/cliconf/dellos6.py +++ b/lib/ansible/plugins/cliconf/dellos6.py @@ -113,3 +113,11 @@ class Cliconf(CliconfBase): responses.append(out) return responses + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/lib/ansible/plugins/cliconf/dellos9.py b/lib/ansible/plugins/cliconf/dellos9.py index d0a48d0ef81..1d758d0784f 100644 --- a/lib/ansible/plugins/cliconf/dellos9.py +++ b/lib/ansible/plugins/cliconf/dellos9.py @@ -113,3 +113,11 @@ class Cliconf(CliconfBase): responses.append(out) return responses + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/lib/ansible/plugins/cliconf/enos.py b/lib/ansible/plugins/cliconf/enos.py index 0cf348a70c4..6a94ff7232f 100644 --- a/lib/ansible/plugins/cliconf/enos.py +++ b/lib/ansible/plugins/cliconf/enos.py @@ -31,7 +31,8 @@ import json from itertools import chain -from ansible.module_utils._text import to_bytes, to_text +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text from ansible.module_utils.network.common.utils import to_list from ansible.plugins.cliconf import CliconfBase, enable_mode @@ -83,3 +84,21 @@ class Cliconf(CliconfBase): def get_capabilities(self): result = super(Cliconf, self).get_capabilities() return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + + if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'): + self._connection.queue_message('vvvv', 'In Config mode, sending exit to device') + self._connection.send_command('exit') + else: + self._connection.send_command('enable') diff --git a/lib/ansible/plugins/cliconf/eos.py b/lib/ansible/plugins/cliconf/eos.py index fb30ac8e091..9141e1d7f36 100644 --- a/lib/ansible/plugins/cliconf/eos.py +++ b/lib/ansible/plugins/cliconf/eos.py @@ -286,6 +286,14 @@ class Cliconf(CliconfBase): return json.dumps(result) + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context='(config', exit_command='abort') + def _get_command_with_output(self, command, output): options_values = self.get_option_values() if output not in options_values['output']: diff --git a/lib/ansible/plugins/cliconf/ios.py b/lib/ansible/plugins/cliconf/ios.py index f0b0760e8cb..6b6242c54c2 100644 --- a/lib/ansible/plugins/cliconf/ios.py +++ b/lib/ansible/plugins/cliconf/ios.py @@ -34,8 +34,6 @@ import re import time import json -from itertools import chain - from ansible.errors import AnsibleConnectionFailure from ansible.module_utils._text import to_text from ansible.module_utils.common._collections_compat import Mapping @@ -333,6 +331,22 @@ class Cliconf(CliconfBase): else: return 'full' + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + + if re.search(r'config.*\)#', to_text(out, errors='surrogate_then_replace').strip()): + self._connection.queue_message('vvvv', 'wrong context, sending end to device') + self._connection.send_command('end') + def _extract_banners(self, config): banners = {} banner_cmds = re.findall(r'^banner (\w+)', config, re.M) diff --git a/lib/ansible/plugins/cliconf/iosxr.py b/lib/ansible/plugins/cliconf/iosxr.py index 16ce9d8e5b3..d4524f88e47 100644 --- a/lib/ansible/plugins/cliconf/iosxr.py +++ b/lib/ansible/plugins/cliconf/iosxr.py @@ -266,3 +266,11 @@ class Cliconf(CliconfBase): result['device_operations'] = self.get_device_operations() result.update(self.get_option_values()) return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#', exit_command='abort') diff --git a/lib/ansible/plugins/cliconf/ironware.py b/lib/ansible/plugins/cliconf/ironware.py index 21fd1fb3af6..1b526f2231f 100644 --- a/lib/ansible/plugins/cliconf/ironware.py +++ b/lib/ansible/plugins/cliconf/ironware.py @@ -86,3 +86,11 @@ class Cliconf(CliconfBase): def get_capabilities(self): result = super(Cliconf, self).get_capabilities() return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/lib/ansible/plugins/cliconf/junos.py b/lib/ansible/plugins/cliconf/junos.py index 35d264f99a0..d29b6f5fa4f 100644 --- a/lib/ansible/plugins/cliconf/junos.py +++ b/lib/ansible/plugins/cliconf/junos.py @@ -246,6 +246,14 @@ class Cliconf(CliconfBase): result.update(self.get_option_values()) return json.dumps(result) + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context='#') + def _get_command_with_output(self, command, output): options_values = self.get_option_values() if output not in options_values['output']: diff --git a/lib/ansible/plugins/cliconf/nxos.py b/lib/ansible/plugins/cliconf/nxos.py index 2c0eaafa488..207b824f8da 100644 --- a/lib/ansible/plugins/cliconf/nxos.py +++ b/lib/ansible/plugins/cliconf/nxos.py @@ -258,6 +258,24 @@ class Cliconf(CliconfBase): return json.dumps(result) + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli context + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + if out is None: + raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received' + u' response window: %s' % self._connection._last_recv_window) + # Match prompts ending in )# except those with (maint-mode)# + config_prompt = re.compile(r'^.*\((?!maint-mode).*\)#$') + + while config_prompt.match(to_text(out, errors='surrogate_then_replace').strip()): + self._connection.queue_message('vvvv', 'wrong context, sending exit to device') + self._connection.send_command('exit') + out = self._connection.get_prompt() + def _get_command_with_output(self, command, output): options_values = self.get_option_values() if output not in options_values['output']: diff --git a/lib/ansible/plugins/cliconf/vyos.py b/lib/ansible/plugins/cliconf/vyos.py index d5c8efa7ada..f0621508355 100644 --- a/lib/ansible/plugins/cliconf/vyos.py +++ b/lib/ansible/plugins/cliconf/vyos.py @@ -266,3 +266,11 @@ class Cliconf(CliconfBase): result['device_operations'] = self.get_device_operations() result.update(self.get_option_values()) return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context='#', exit_command='exit discard') diff --git a/lib/ansible/plugins/connection/network_cli.py b/lib/ansible/plugins/connection/network_cli.py index d188da3293f..c80c7084f8f 100644 --- a/lib/ansible/plugins/connection/network_cli.py +++ b/lib/ansible/plugins/connection/network_cli.py @@ -314,6 +314,7 @@ class Connection(NetworkConnectionBase): self._last_response = None self._history = list() self._command_response = None + self._last_recv_window = None self._terminal = None self.cliconf = None @@ -541,6 +542,7 @@ class Connection(NetworkConnectionBase): recv.seek(offset) window = self._strip(recv.read()) + self._last_recv_window = window window_count += 1 if prompts and not handled: