Repository: ansible/ansible
Pull request: Refactor CLI prompt mode check for network plugins

##### SUMMARY
<!--- Describe the change below, including rationale and design decisions -->
*  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 initialize the persistent connection with the remote device
   only when it is required.
<!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue -->

Fixes #55202

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- Feature Pull Request

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
cli/scripts/ansible_connection_cli_stub.py
module_utils/network/common/cfg/base.py
module_utils/network/junos/config/interfaces/interfaces.py
module_utils/network/nxos/argspec/interfaces/interfaces.py
module_utils/network/nxos/config/interfaces/interfaces.py
plugins/action/aireos.py
plugins/action/aruba.py
plugins/action/asa.py
plugins/action/ce.py
plugins/action/cnos.py
plugins/action/dellos10.py
plugins/action/dellos6.py
plugins/action/dellos9.py
plugins/action/enos.py
plugins/action/eos.py
plugins/action/ios.py
plugins/action/iosxr.py
plugins/action/ironware.py
plugins/action/junos.py
plugins/action/nxos.py
plugins/action/vyos.py
plugins/cliconf/__init__.py
plugins/cliconf/aireos.py
plugins/cliconf/aruba.py
plugins/cliconf/ce.py
plugins/cliconf/cnos.py
plugins/cliconf/dellos10.py
plugins/cliconf/dellos6.py
plugins/cliconf/dellos9.py
plugins/cliconf/enos.py
plugins/cliconf/eos.py
plugins/cliconf/ios.py
plugins/cliconf/iosxr.py
plugins/cliconf/ironware.py
plugins/cliconf/junos.py
plugins/cliconf/nxos.py
##### ADDITIONAL INFORMATION
<!--- Include additional information to help people understand the change here -->
<!--- A step-by-step reproduction of the problem is helpful if there is no related issue -->

<!--- Paste verbatim command output below, e.g. before and after your change -->
```paste below

```


Problem statement:
cli_{config, command} modules don't inspect current cli context
##### SUMMARY
- Unlike platform specific modules, cli_command and cli_config modules don't use `action/{{ network_os }}.py` to inspect the current prompt and handle cli context switching from configuration mode to priv exec mode when required.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
cli_command.py
cli_config.py

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
devel
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```
HOST_KEY_CHECKING(/Users/nchakrab/collections/ansible.cfg) = False
PERSISTENT_COMMAND_TIMEOUT(/Users/nchakrab/collections/ansible.cfg) = 60
PERSISTENT_CONNECT_RETRY_TIMEOUT(/Users/nchakrab/collections/ansible.cfg) = 60
PERSISTENT_CONNECT_TIMEOUT(/Users/nchakrab/collections/ansible.cfg) = 60
```

##### OS / ENVIRONMENT
- Affects all the platforms that has a cliconf api (?)


##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
tasks:
  - cli_command:
      command: configure
    
  - cli_command:
      command: show version

  - cli_config:
      config: "{{ lines }}"
    vars:
      lines: hostname an-ios-02
```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->
After the first task is run, the prompt should be inspected and reset to enable mode.

##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes -->
```paste below
The full traceback is:
WARNING: The below traceback may *not* be related to the actual failure.
  File "/var/folders/84/qwfwbspx3hjfvxvq2hf2rk500000gn/T/ansible_cli_command_payload_ccqixE/__main__.py", line 151, in main
    response = connection.get(**module.params)
  File "/var/folders/84/qwfwbspx3hjfvxvq2hf2rk500000gn/T/ansible_cli_command_payload_ccqixE/ansible_cli_command_payload.zip/ansible/module_utils/connection.py", line 186, in __rpc__
    raise ConnectionError(to_text(msg, errors='surrogate_then_replace'), code=code)

fatal: [iosxr-02]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "answer": null,
            "check_all": false,
            "command": "show version",
            "prompt": null,
            "sendonly": false
        }
    },
    "msg": "show version\r\n\r                                  ^\r\n% Invalid input detected at '^' marker.\r\nRP/0/0/CPU0:iosxr-02(config)#"
}
```

Hints from issue discussion:
Files identified in the description:
* [lib/ansible/modules/network/cli/cli_command.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/network/cli/cli_command.py)
* [lib/ansible/modules/network/cli/cli_config.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/network/cli/cli_config.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @qalthos @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@NilashishC, just so you are aware we have a dedicated Working Group for network.
You can find other people interested in this in `#ansible-network` on Freenode IRC
For more information about communities, meetings and agendas see https://github.com/ansible/community

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: community_workgroups --->

Commit message: 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.

Patch to review:
diff --git a/lib/ansible/cli/scripts/ansible_connection_cli_stub.py b/lib/ansible/cli/scripts/ansible_connection_cli_stub.py
index 1f04039329b642..20bfc6b3a8b934 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 36fc0a7134a066..38e897ae00c267 100644
--- a/lib/ansible/plugins/action/aireos.py
+++ b/lib/ansible/plugins/action/aireos.py
@@ -23,8 +23,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
                     '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 9c246529aace24..193fdac91f54c4 100644
--- a/lib/ansible/plugins/action/aruba.py
+++ b/lib/ansible/plugins/action/aruba.py
@@ -23,8 +23,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
                     '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 303ea53bbfb73d..c5877d22d70c1e 100644
--- a/lib/ansible/plugins/action/ce.py
+++ b/lib/ansible/plugins/action/ce.py
@@ -10,8 +10,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
                 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 66dc14c8cf2c15..064fb3a9439f7d 100644
--- a/lib/ansible/plugins/action/cnos.py
+++ b/lib/ansible/plugins/action/cnos.py
@@ -24,8 +24,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
         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 @@ def run(self, tmp=None, task_vars=None):
 
             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 70bf263f7e67c1..d3a8480b17033b 100644
--- a/lib/ansible/plugins/action/dellos10.py
+++ b/lib/ansible/plugins/action/dellos10.py
@@ -25,8 +25,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
         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 @@ def run(self, tmp=None, task_vars=None):
 
             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 e8194aade1548b..9efcb6493d55df 100644
--- a/lib/ansible/plugins/action/dellos6.py
+++ b/lib/ansible/plugins/action/dellos6.py
@@ -25,8 +25,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
 
             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 20b82cf31f3276..318f0d8789263f 100644
--- a/lib/ansible/plugins/action/dellos9.py
+++ b/lib/ansible/plugins/action/dellos9.py
@@ -25,8 +25,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
 
             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 25db538af526a4..4eb650f6f4dd92 100644
--- a/lib/ansible/plugins/action/enos.py
+++ b/lib/ansible/plugins/action/enos.py
@@ -24,8 +24,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
         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 @@ def run(self, tmp=None, task_vars=None):
 
             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 dcaacd80ee9dff..9a4d41efae5250 100644
--- a/lib/ansible/plugins/action/eos.py
+++ b/lib/ansible/plugins/action/eos.py
@@ -23,8 +23,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
 
         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 @@ def run(self, tmp=None, task_vars=None):
         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 8cabaa6e92444f..9d2a630559ae80 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 @@ def run(self, tmp=None, task_vars=None):
 
         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 @@ def run(self, tmp=None, task_vars=None):
         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 c523630097c427..61950309ca86e3 100644
--- a/lib/ansible/plugins/action/iosxr.py
+++ b/lib/ansible/plugins/action/iosxr.py
@@ -22,9 +22,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.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 @@ def run(self, tmp=None, task_vars=None):
 
         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 @@ def run(self, tmp=None, task_vars=None):
         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 bedaa0ac527f8c..0ef8898d2cf45a 100644
--- a/lib/ansible/plugins/action/ironware.py
+++ b/lib/ansible/plugins/action/ironware.py
@@ -22,9 +22,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, 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 @@ def run(self, tmp=None, task_vars=None):
         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 @@ def run(self, tmp=None, task_vars=None):
         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 adf89e264d8088..7a81854bbf170e 100644
--- a/lib/ansible/plugins/action/junos.py
+++ b/lib/ansible/plugins/action/junos.py
@@ -22,8 +22,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
 
         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 @@ def run(self, tmp=None, task_vars=None):
                                                "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 62ce01ea1a416b..5d9763da83cd0b 100644
--- a/lib/ansible/plugins/action/nxos.py
+++ b/lib/ansible/plugins/action/nxos.py
@@ -24,8 +24,6 @@
 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 @@ def run(self, tmp=None, task_vars=None):
                 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 @@ def run(self, tmp=None, task_vars=None):
         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 f4cf1425334652..e0083d98fe2547 100644
--- a/lib/ansible/plugins/action/vyos.py
+++ b/lib/ansible/plugins/action/vyos.py
@@ -22,10 +22,7 @@
 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 @@ def run(self, tmp=None, task_vars=None):
 
         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 @@ def run(self, tmp=None, task_vars=None):
         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 b4dd9e57853abe..21f955ae09e2e7 100644
--- a/lib/ansible/plugins/cliconf/__init__.py
+++ b/lib/ansible/plugins/cliconf/__init__.py
@@ -19,6 +19,8 @@
 from __future__ import (absolute_import, division, print_function)
 __metaclass__ = type
 
+import re
+
 from abc import abstractmethod
 from functools import wraps
 
@@ -447,3 +449,31 @@ def check_edit_config_capability(self, operations, candidate=None, commit=True,
 
         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 8e84da26e7ca1f..4890cd1e74e2bf 100644
--- a/lib/ansible/plugins/cliconf/aireos.py
+++ b/lib/ansible/plugins/cliconf/aireos.py
@@ -34,6 +34,7 @@
 
 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,12 @@ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, c
     def get_capabilities(self):
         result = super(Cliconf, self).get_capabilities()
         return json.dumps(result)
+
+    @property
+    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 cb5c4219ac0282..a1fea0b88545ab 100644
--- a/lib/ansible/plugins/cliconf/aruba.py
+++ b/lib/ansible/plugins/cliconf/aruba.py
@@ -86,3 +86,12 @@ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, c
     def get_capabilities(self):
         result = super(Cliconf, self).get_capabilities()
         return json.dumps(result)
+
+    @property
+    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 859cfd6704de29..16f61d3f0145a7 100644
--- a/lib/ansible/plugins/cliconf/ce.py
+++ b/lib/ansible/plugins/cliconf/ce.py
@@ -34,6 +34,7 @@
 
 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,25 @@ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, c
     def get_capabilities(self):
         result = super(Cliconf, self).get_capabilities()
         return json.dumps(result)
+
+    @property
+    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 exit 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 254c743b471cb4..cb139e76f8794f 100644
--- a/lib/ansible/plugins/cliconf/cnos.py
+++ b/lib/ansible/plugins/cliconf/cnos.py
@@ -29,7 +29,7 @@
 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,22 @@ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, c
     def get_capabilities(self):
         result = super(Cliconf, self).get_capabilities()
         return json.dumps(result)
+
+    @property
+    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 40bee342f3eac2..9f54a434ebe207 100644
--- a/lib/ansible/plugins/cliconf/dellos10.py
+++ b/lib/ansible/plugins/cliconf/dellos10.py
@@ -113,3 +113,12 @@ def run_commands(self, commands=None, check_rc=True):
             responses.append(out)
 
         return responses
+
+    @property
+    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 bae7caeb78e203..96c877a594857a 100644
--- a/lib/ansible/plugins/cliconf/dellos6.py
+++ b/lib/ansible/plugins/cliconf/dellos6.py
@@ -113,3 +113,12 @@ def run_commands(self, commands=None, check_rc=True):
             responses.append(out)
 
         return responses
+
+    @property
+    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 d0a48d0ef81233..ddb239f555fa1d 100644
--- a/lib/ansible/plugins/cliconf/dellos9.py
+++ b/lib/ansible/plugins/cliconf/dellos9.py
@@ -113,3 +113,12 @@ def run_commands(self, commands=None, check_rc=True):
             responses.append(out)
 
         return responses
+
+    @property
+    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 0cf348a70c415a..6e29be831aab9a 100644
--- a/lib/ansible/plugins/cliconf/enos.py
+++ b/lib/ansible/plugins/cliconf/enos.py
@@ -31,7 +31,8 @@
 
 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,22 @@ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, c
     def get_capabilities(self):
         result = super(Cliconf, self).get_capabilities()
         return json.dumps(result)
+
+    @property
+    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 fb30ac8e091d39..d7649575ae275c 100644
--- a/lib/ansible/plugins/cliconf/eos.py
+++ b/lib/ansible/plugins/cliconf/eos.py
@@ -286,6 +286,15 @@ def get_capabilities(self):
 
         return json.dumps(result)
 
+    @property
+    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 f0b0760e8cb605..4d9ad904c5df72 100644
--- a/lib/ansible/plugins/cliconf/ios.py
+++ b/lib/ansible/plugins/cliconf/ios.py
@@ -34,8 +34,6 @@
 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,23 @@ def get_defaults_flag(self):
         else:
             return 'full'
 
+    @property
+    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 16ce9d8e5b3267..301af12ca20a98 100644
--- a/lib/ansible/plugins/cliconf/iosxr.py
+++ b/lib/ansible/plugins/cliconf/iosxr.py
@@ -266,3 +266,12 @@ def get_capabilities(self):
         result['device_operations'] = self.get_device_operations()
         result.update(self.get_option_values())
         return json.dumps(result)
+
+    @property
+    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 21fd1fb3af66c1..4446799e535aff 100644
--- a/lib/ansible/plugins/cliconf/ironware.py
+++ b/lib/ansible/plugins/cliconf/ironware.py
@@ -86,3 +86,12 @@ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, c
     def get_capabilities(self):
         result = super(Cliconf, self).get_capabilities()
         return json.dumps(result)
+
+    @property
+    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 35d264f99a0434..2a41cbe1716dea 100644
--- a/lib/ansible/plugins/cliconf/junos.py
+++ b/lib/ansible/plugins/cliconf/junos.py
@@ -246,6 +246,15 @@ def get_capabilities(self):
         result.update(self.get_option_values())
         return json.dumps(result)
 
+    @property
+    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 2c0eaafa488a70..32e8d89a2db67e 100644
--- a/lib/ansible/plugins/cliconf/nxos.py
+++ b/lib/ansible/plugins/cliconf/nxos.py
@@ -258,6 +258,25 @@ def get_capabilities(self):
 
         return json.dumps(result)
 
+    @property
+    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 d5c8efa7adafa8..adf73c2064a0ac 100644
--- a/lib/ansible/plugins/cliconf/vyos.py
+++ b/lib/ansible/plugins/cliconf/vyos.py
@@ -266,3 +266,12 @@ def get_capabilities(self):
         result['device_operations'] = self.get_device_operations()
         result.update(self.get_option_values())
         return json.dumps(result)
+
+    @property
+    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 a189b8da08dbf0..f0eedfebf7b5c0 100644
--- a/lib/ansible/plugins/connection/network_cli.py
+++ b/lib/ansible/plugins/connection/network_cli.py
@@ -314,6 +314,7 @@ def __init__(self, play_context, new_stdin, *args, **kwargs):
         self._last_response = None
         self._history = list()
         self._command_response = None
+        self._last_recv_window = None
 
         self._terminal = None
         self.cliconf = None
@@ -540,6 +541,7 @@ def receive(self, command=None, prompts=None, answer=None, newline=True, prompt_
             recv.seek(offset)
 
             window = self._strip(recv.read())
+            self._last_recv_window = window
             window_count += 1
 
             if prompts and not handled:


Code review comments:
diff_hunk: @@ -85,3 +86,12 @@ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, c
     def get_capabilities(self):
         result = super(Cliconf, self).get_capabilities()
         return json.dumps(result)
+
+    @property
path: lib/ansible/plugins/cliconf/aireos.py
text: @user2:
Why are all of these `@user1`s? They don't return anything, so I don't see what the use of that is.

@author:
Fixed

diff_hunk: @@ -19,6 +19,8 @@
 from __future__ import (absolute_import, division, print_function)
 __metaclass__ = type
 
+import re
path: lib/ansible/plugins/cliconf/__init__.py
text: @user1:
Am I missing something obvious? I don't see `re` being used here.

diff_hunk: @@ -98,3 +99,25 @@ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, c
     def get_capabilities(self):
         result = super(Cliconf, self).get_capabilities()
         return json.dumps(result)
+
+    @property
+    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 exit to device')
path: lib/ansible/plugins/cliconf/ce.py
text: @user1:
```suggestion
                self._connection.queue_message('vvvv', 'wrong context, sending return to device')
```

@author:
Fixed