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
This commit is contained in:
Ganesh Nalawade 2019-11-03 04:10:30 +05:30 committed by GitHub
parent 73526b9d65
commit c27e47327f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 209 additions and 239 deletions

View file

@ -305,8 +305,9 @@ def main():
pc_data = to_text(init_data) pc_data = to_text(init_data)
try: try:
conn.update_play_context(pc_data) conn.update_play_context(pc_data)
conn.set_cli_prompt_context()
except Exception as exc: 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 # not fatal e.g. netconf
if isinstance(exc, ConnectionError) and getattr(exc, 'code', None) == -32601: if isinstance(exc, ConnectionError) and getattr(exc, 'code', None) == -32601:
pass pass

View file

@ -23,8 +23,6 @@ import sys
import copy import copy
from ansible import constants as C 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.aireos.aireos import aireos_provider_spec from ansible.module_utils.network.aireos.aireos import aireos_provider_spec
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
@ -69,14 +67,6 @@ class ActionModule(ActionNetworkModule):
'msg': 'unable to open shell. Please see: ' + 'msg': 'unable to open shell. Please see: ' +
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} '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 task_vars['ansible_socket'] = socket_path
if self._play_context.become_method == 'enable': if self._play_context.become_method == 'enable':

View file

@ -23,8 +23,6 @@ import sys
import copy import copy
from ansible import constants as C 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.aruba.aruba import aruba_provider_spec from ansible.module_utils.network.aruba.aruba import aruba_provider_spec
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
@ -70,14 +68,6 @@ class ActionModule(ActionNetworkModule):
'msg': 'unable to open shell. Please see: ' + 'msg': 'unable to open shell. Please see: ' +
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} '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 task_vars['ansible_socket'] = socket_path
if self._play_context.become_method == 'enable': if self._play_context.become_method == 'enable':

View file

@ -10,8 +10,6 @@ import sys
import copy import copy
from ansible import constants as C 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.cloudengine.ce import ce_provider_spec from ansible.module_utils.network.cloudengine.ce import ce_provider_spec
from ansible.module_utils.network.common.utils import load_provider 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." return {'failed': True, 'msg': "Connection type '%s' is not valid for '%s' module."
% (self._play_context.connection, self._task.action)} % (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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -24,8 +24,6 @@ from ansible import constants as C
from ansible.plugins.action.network import ActionModule as ActionNetworkModule 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.cnos.cnos import cnos_provider_spec
from ansible.module_utils.network.common.utils import load_provider 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 from ansible.utils.display import Display
display = Display() display = Display()
@ -37,7 +35,6 @@ class ActionModule(ActionNetworkModule):
del tmp # tmp no longer has any effect del tmp # tmp no longer has any effect
self._config_module = True if self._task.action == 'cnos_config' else False self._config_module = True if self._task.action == 'cnos_config' else False
socket_path = None
if self._play_context.connection == 'local': if self._play_context.connection == 'local':
provider = load_provider(cnos_provider_spec, self._task.args) provider = load_provider(cnos_provider_spec, self._task.args)
@ -67,18 +64,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -25,8 +25,6 @@ import sys
import copy import copy
from ansible import constants as C 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.dellos10.dellos10 import dellos10_provider_spec 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 del tmp # tmp no longer has any effect
self._config_module = True if self._task.action == 'dellos10_config' else False self._config_module = True if self._task.action == 'dellos10_config' else False
socket_path = None
if self._play_context.connection == 'network_cli': if self._play_context.connection == 'network_cli':
provider = self._task.args.get('provider', {}) provider = self._task.args.get('provider', {})
@ -77,17 +74,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -25,8 +25,6 @@ import sys
import copy import copy
from ansible import constants as C 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.dellos6.dellos6 import dellos6_provider_spec from ansible.module_utils.network.dellos6.dellos6 import dellos6_provider_spec
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
@ -77,17 +75,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -25,8 +25,6 @@ import sys
import copy import copy
from ansible import constants as C 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.dellos9.dellos9 import dellos9_provider_spec from ansible.module_utils.network.dellos9.dellos9 import dellos9_provider_spec
@ -77,17 +75,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -24,8 +24,6 @@ from ansible import constants as C
from ansible.plugins.action.network import ActionModule as ActionNetworkModule 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.enos.enos import enos_provider_spec
from ansible.module_utils.network.common.utils import load_provider 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 from ansible.utils.display import Display
display = Display() display = Display()
@ -37,7 +35,7 @@ class ActionModule(ActionNetworkModule):
del tmp # tmp no longer has any effect del tmp # tmp no longer has any effect
self._config_module = True if self._task.action == 'enos_config' else False self._config_module = True if self._task.action == 'enos_config' else False
socket_path = None
if self._play_context.connection == 'local': if self._play_context.connection == 'local':
provider = load_provider(enos_provider_spec, self._task.args) provider = load_provider(enos_provider_spec, self._task.args)
pc = copy.deepcopy(self._play_context) pc = copy.deepcopy(self._play_context)
@ -66,18 +64,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -23,8 +23,6 @@ import sys
import copy import copy
from ansible import constants as C 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.module_utils.network.eos.eos import eos_provider_spec
from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
@ -40,7 +38,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1] module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'eos_config' else False self._config_module = True if module_name == 'eos_config' else False
socket_path = None
if self._play_context.connection in ('network_cli', 'httpapi'): if self._play_context.connection in ('network_cli', 'httpapi'):
provider = self._task.args.get('provider', {}) provider = self._task.args.get('provider', {})
@ -90,19 +87,6 @@ class ActionModule(ActionNetworkModule):
else: else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -19,12 +19,9 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import re
import sys import sys
import copy 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.ios.ios import ios_provider_spec 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] module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'ios_config' else False self._config_module = True if module_name == 'ios_config' else False
socket_path = None
if self._play_context.connection == 'network_cli': if self._play_context.connection == 'network_cli':
provider = self._task.args.get('provider', {}) provider = self._task.args.get('provider', {})
@ -79,19 +75,5 @@ class ActionModule(ActionNetworkModule):
else: else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -22,9 +22,6 @@ __metaclass__ = type
import sys import sys
import copy 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.module_utils.network.iosxr.iosxr import iosxr_provider_spec
from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1] module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'iosxr_config' else False 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') force_cli = module_name in ('iosxr_netconf', 'iosxr_config', 'iosxr_command', 'iosxr_facts')
if self._play_context.connection == 'local': if self._play_context.connection == 'local':
@ -86,18 +82,5 @@ class ActionModule(ActionNetworkModule):
else: else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -22,9 +22,6 @@ __metaclass__ = type
import sys import sys
import copy 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.ironware.ironware import ironware_provider_spec 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 del tmp # tmp no longer has any effect
self._config_module = True if self._task.action == 'ironware_config' else False self._config_module = True if self._task.action == 'ironware_config' else False
socket_path = None
if self._play_context.connection == 'network_cli': if self._play_context.connection == 'network_cli':
provider = self._task.args.get('provider', {}) provider = self._task.args.get('provider', {})
@ -78,20 +74,5 @@ class ActionModule(ActionNetworkModule):
else: else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -22,8 +22,6 @@ __metaclass__ = type
import sys import sys
import copy 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.common.utils import load_provider
from ansible.module_utils.network.junos.junos import junos_provider_spec from ansible.module_utils.network.junos.junos import junos_provider_spec
from ansible.plugins.action.network import ActionModule as ActionNetworkModule from ansible.plugins.action.network import ActionModule as ActionNetworkModule
@ -41,7 +39,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1] module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'junos_config' else False self._config_module = True if module_name == 'junos_config' else False
socket_path = None
if self._play_context.connection == 'local': if self._play_context.connection == 'local':
provider = load_provider(junos_provider_spec, self._task.args) 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" "Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html"
% (self._play_context.connection, module_name)} % (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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -24,8 +24,6 @@ import re
import sys import sys
from ansible import constants as C 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.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.nxos.nxos import nxos_provider_spec 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 self._task.args['username'] = self._play_context.connection_user
if module_name == 'nxos_install_os': if module_name == 'nxos_install_os':
persistent_command_timeout = 0
persistent_connect_timeout = 0
connection = self._connection connection = self._connection
if connection.transport == 'local': if connection.transport == 'local':
persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
@ -124,21 +120,6 @@ class ActionModule(ActionNetworkModule):
else: else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -22,10 +22,7 @@ __metaclass__ = type
import sys import sys
import copy import copy
from ansible import constants as C
from ansible.plugins.action.network import ActionModule as ActionNetworkModule 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.common.utils import load_provider
from ansible.module_utils.network.vyos.vyos import vyos_provider_spec from ansible.module_utils.network.vyos.vyos import vyos_provider_spec
from ansible.utils.display import Display from ansible.utils.display import Display
@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1] module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'vyos_config' else False self._config_module = True if module_name == 'vyos_config' else False
socket_path = None
if self._play_context.connection == 'network_cli': if self._play_context.connection == 'network_cli':
provider = self._task.args.get('provider', {}) provider = self._task.args.get('provider', {})
@ -75,16 +71,5 @@ class ActionModule(ActionNetworkModule):
else: else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection} 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) result = super(ActionModule, self).run(task_vars=task_vars)
return result return result

View file

@ -447,3 +447,31 @@ class CliconfBase(AnsiblePlugin):
if replace and not operations.get('supports_replace', False): if replace and not operations.get('supports_replace', False):
raise ValueError("configuration replace is not supported") 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

View file

@ -34,6 +34,7 @@ import json
from itertools import chain from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -85,3 +86,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self): def get_capabilities(self):
result = super(Cliconf, self).get_capabilities() result = super(Cliconf, self).get_capabilities()
return json.dumps(result) 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=')#')

View file

@ -86,3 +86,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self): def get_capabilities(self):
result = super(Cliconf, self).get_capabilities() result = super(Cliconf, self).get_capabilities()
return json.dumps(result) 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=')#')

View file

@ -34,6 +34,7 @@ import json
from itertools import chain from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -98,3 +99,24 @@ class Cliconf(CliconfBase):
def get_capabilities(self): def get_capabilities(self):
result = super(Cliconf, self).get_capabilities() result = super(Cliconf, self).get_capabilities()
return json.dumps(result) 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()

View file

@ -29,7 +29,7 @@ version_added: 2.6
import re import re
import json import json
from itertools import chain from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils.common._collections_compat import Mapping from ansible.module_utils.common._collections_compat import Mapping
from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.network.common.utils import to_list
@ -116,3 +116,21 @@ class Cliconf(CliconfBase):
def get_capabilities(self): def get_capabilities(self):
result = super(Cliconf, self).get_capabilities() result = super(Cliconf, self).get_capabilities()
return json.dumps(result) 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')

View file

@ -113,3 +113,11 @@ class Cliconf(CliconfBase):
responses.append(out) responses.append(out)
return responses 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=')#')

View file

@ -113,3 +113,11 @@ class Cliconf(CliconfBase):
responses.append(out) responses.append(out)
return responses 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=')#')

View file

@ -113,3 +113,11 @@ class Cliconf(CliconfBase):
responses.append(out) responses.append(out)
return responses 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=')#')

View file

@ -31,7 +31,8 @@ import json
from itertools import chain 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.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -83,3 +84,21 @@ class Cliconf(CliconfBase):
def get_capabilities(self): def get_capabilities(self):
result = super(Cliconf, self).get_capabilities() result = super(Cliconf, self).get_capabilities()
return json.dumps(result) 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')

View file

@ -286,6 +286,14 @@ class Cliconf(CliconfBase):
return json.dumps(result) 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): def _get_command_with_output(self, command, output):
options_values = self.get_option_values() options_values = self.get_option_values()
if output not in options_values['output']: if output not in options_values['output']:

View file

@ -34,8 +34,6 @@ import re
import time import time
import json import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.module_utils.common._collections_compat import Mapping from ansible.module_utils.common._collections_compat import Mapping
@ -333,6 +331,22 @@ class Cliconf(CliconfBase):
else: else:
return 'full' 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): def _extract_banners(self, config):
banners = {} banners = {}
banner_cmds = re.findall(r'^banner (\w+)', config, re.M) banner_cmds = re.findall(r'^banner (\w+)', config, re.M)

View file

@ -266,3 +266,11 @@ class Cliconf(CliconfBase):
result['device_operations'] = self.get_device_operations() result['device_operations'] = self.get_device_operations()
result.update(self.get_option_values()) result.update(self.get_option_values())
return json.dumps(result) 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')

View file

@ -86,3 +86,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self): def get_capabilities(self):
result = super(Cliconf, self).get_capabilities() result = super(Cliconf, self).get_capabilities()
return json.dumps(result) 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=')#')

View file

@ -246,6 +246,14 @@ class Cliconf(CliconfBase):
result.update(self.get_option_values()) result.update(self.get_option_values())
return json.dumps(result) 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): def _get_command_with_output(self, command, output):
options_values = self.get_option_values() options_values = self.get_option_values()
if output not in options_values['output']: if output not in options_values['output']:

View file

@ -258,6 +258,24 @@ class Cliconf(CliconfBase):
return json.dumps(result) 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): def _get_command_with_output(self, command, output):
options_values = self.get_option_values() options_values = self.get_option_values()
if output not in options_values['output']: if output not in options_values['output']:

View file

@ -266,3 +266,11 @@ class Cliconf(CliconfBase):
result['device_operations'] = self.get_device_operations() result['device_operations'] = self.get_device_operations()
result.update(self.get_option_values()) result.update(self.get_option_values())
return json.dumps(result) 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')

View file

@ -314,6 +314,7 @@ class Connection(NetworkConnectionBase):
self._last_response = None self._last_response = None
self._history = list() self._history = list()
self._command_response = None self._command_response = None
self._last_recv_window = None
self._terminal = None self._terminal = None
self.cliconf = None self.cliconf = None
@ -541,6 +542,7 @@ class Connection(NetworkConnectionBase):
recv.seek(offset) recv.seek(offset)
window = self._strip(recv.read()) window = self._strip(recv.read())
self._last_recv_window = window
window_count += 1 window_count += 1
if prompts and not handled: if prompts and not handled: