Refactor CLI prompt mode check for network plugins (#63945) (#69210)

* 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

(cherry picked from commit c27e47327f)

* Fix cli context check for network_cli connection (#64697)

* Fix cli context check for network_cli connection

Fixes #64575

*  Check cli context for network_cli connection
   at the start of new task run only.

* Pass task_uuid around to identify start of new task run

* Handle for local connection

(cherry picked from commit ee3f8d28a4)

* Add test for reboot & wait_for_connection on EOS & IOS (#63014)

* Add test for reboot & wait_for_connection

* Add test for ios

* Collection-proof block test

* Add junos test

* Don't try to evaluate cli context unless using the connection

* Prevent infinite recursion

* Update change log

(cherry picked from commit e19b94f43b)
(cherry picked from commit ee3f8d28a4)
(cherry picked from commit e19b94f43b)

devel:
https://github.com/ansible/ansible/pull/63945
https://github.com/ansible/ansible/pull/64697
https://github.com/ansible/ansible/pull/63014

Co-authored-by: Nathaniel Case <ncase@redhat.com>
pull/69271/head
Ganesh Nalawade 5 years ago committed by GitHub
parent fbf93db4a8
commit e40b96ed88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,6 @@
minor_changes:
- Move cli prompt check logic from action to cliconf plugin (https://github.com/ansible/ansible/pull/63945)
- Add test for reboot & wait_for_connection on EOS & IOS (https://github.com/ansible/ansible/pull/63014)
bugfixes:
- Fix cli context check for network_cli connection (https://github.com/ansible/ansible/pull/64697)

@ -71,10 +71,11 @@ class ConnectionProcess(object):
The connection process wraps around a Connection object that manages
the connection to a remote device that persists over the playbook
'''
def __init__(self, fd, play_context, socket_path, original_path, ansible_playbook_pid=None):
def __init__(self, fd, play_context, socket_path, original_path, task_uuid=None, ansible_playbook_pid=None):
self.play_context = play_context
self.socket_path = socket_path
self.original_path = original_path
self._task_uuid = task_uuid
self.fd = fd
self.exception = None
@ -98,7 +99,7 @@ class ConnectionProcess(object):
if self.play_context.private_key_file and self.play_context.private_key_file[0] not in '~/':
self.play_context.private_key_file = os.path.join(self.original_path, self.play_context.private_key_file)
self.connection = connection_loader.get(self.play_context.connection, self.play_context, '/dev/null',
ansible_playbook_pid=self._ansible_playbook_pid)
task_uuid=self._task_uuid, ansible_playbook_pid=self._ansible_playbook_pid)
self.connection.set_options(var_options=variables)
self.connection._socket_path = self.socket_path
@ -257,8 +258,8 @@ def main():
if rc == 0:
ssh = connection_loader.get('ssh', class_only=True)
ansible_playbook_pid = sys.argv[1]
task_uuid = sys.argv[2]
cp = ssh._create_control_path(play_context.remote_addr, play_context.port, play_context.remote_user, play_context.connection, ansible_playbook_pid)
# create the persistent connection dir if need be and create the paths
# which we will be using later
tmp_path = unfrackpath(C.PERSISTENT_CONTROL_PATH_DIR)
@ -278,7 +279,7 @@ def main():
try:
os.close(r)
wfd = os.fdopen(w, 'w')
process = ConnectionProcess(wfd, play_context, socket_path, original_path, ansible_playbook_pid)
process = ConnectionProcess(wfd, play_context, socket_path, original_path, task_uuid, ansible_playbook_pid)
process.start(variables)
except Exception:
messages.append(('error', traceback.format_exc()))
@ -305,8 +306,9 @@ def main():
pc_data = to_text(init_data)
try:
conn.update_play_context(pc_data)
conn.set_check_prompt(task_uuid)
except Exception as exc:
# Only network_cli has update_play context, so missing this is
# Only network_cli has update_play context and set_check_prompt, so missing this is
# not fatal e.g. netconf
if isinstance(exc, ConnectionError) and getattr(exc, 'code', None) == -32601:
pass

@ -929,7 +929,7 @@ class TaskExecutor:
display.vvvv('using connection plugin %s' % connection.transport, host=self._play_context.remote_addr)
options = self._get_persistent_connection_options(connection, variables, templar)
socket_path = start_connection(self._play_context, options)
socket_path = start_connection(self._play_context, options, self._task._uuid)
display.vvvv('local domain socket path is %s' % socket_path, host=self._play_context.remote_addr)
setattr(connection, '_socket_path', socket_path)

@ -23,8 +23,6 @@ import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.aireos.aireos import aireos_provider_spec
from ansible.module_utils.network.common.utils import load_provider
@ -60,7 +58,7 @@ class ActionModule(ActionNetworkModule):
command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
@ -70,14 +68,6 @@ class ActionModule(ActionNetworkModule):
'msg': 'unable to open shell. Please see: ' +
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
# make sure we are in the right cli context which should be
# enable mode and not config module
conn = Connection(socket_path)
out = conn.get_prompt()
if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
task_vars['ansible_socket'] = socket_path
if self._play_context.become_method == 'enable':

@ -23,8 +23,6 @@ import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.aruba.aruba import aruba_provider_spec
from ansible.module_utils.network.common.utils import load_provider
@ -61,7 +59,7 @@ class ActionModule(ActionNetworkModule):
command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
@ -71,14 +69,6 @@ class ActionModule(ActionNetworkModule):
'msg': 'unable to open shell. Please see: ' +
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
# make sure we are in the right cli context which should be
# enable mode and not config module
conn = Connection(socket_path)
out = conn.get_prompt()
if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
task_vars['ansible_socket'] = socket_path
if self._play_context.become_method == 'enable':

@ -56,7 +56,7 @@ class ActionModule(ActionNetworkModule):
pc.become_method = 'enable'
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()

@ -71,7 +71,7 @@ class ActionModule(ActionNetworkModule):
command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()

@ -67,7 +67,7 @@ class ActionModule(_ActionModule):
command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()

@ -10,8 +10,6 @@ import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.cloudengine.ce import ce_provider_spec
from ansible.module_utils.network.common.utils import load_provider
@ -62,7 +60,7 @@ class ActionModule(ActionNetworkModule):
if module_name in ['ce_netconf'] or module_name not in CLI_SUPPORTED_MODULES:
pc.connection = 'netconf'
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
@ -87,22 +85,5 @@ class ActionModule(ActionNetworkModule):
return {'failed': True, 'msg': "Connection type '%s' is not valid for '%s' module."
% (self._play_context.connection, self._task.action)}
if (self._play_context.connection == 'local' and transport == 'cli' and self._task.action in CLI_SUPPORTED_MODULES) \
or self._play_context.connection == 'network_cli':
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
prompt = to_text(out, errors='surrogate_then_replace').strip()
while prompt.endswith(']'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
if prompt.startswith('[*'):
conn.exec_command('clear configuration candidate')
conn.exec_command('return')
out = conn.get_prompt()
prompt = to_text(out, errors='surrogate_then_replace').strip()
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -24,8 +24,6 @@ from ansible import constants as C
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.cnos.cnos import cnos_provider_spec
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.connection import Connection
from ansible.module_utils._text import to_text
from ansible.utils.display import Display
display = Display()
@ -36,7 +34,6 @@ class ActionModule(ActionNetworkModule):
def run(self, tmp=None, task_vars=None):
del tmp # tmp no longer has any effect
socket_path = None
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'cnos_config' else False
@ -56,7 +53,7 @@ class ActionModule(ActionNetworkModule):
pc.become_method = 'enable'
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
@ -68,18 +65,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path
# make sure we are in the right cli context which should be
# enable mode and not config module or exec mode
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('In Config mode, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
else:
conn.send_command('enable')
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -25,8 +25,6 @@ import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.dellos10.dellos10 import dellos10_provider_spec
@ -42,7 +40,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'dellos10_config' else False
socket_path = None
persistent_connection = self._play_context.connection.split('.')[-1]
if persistent_connection == 'network_cli':
@ -67,7 +64,7 @@ class ActionModule(ActionNetworkModule):
pc.become_pass = provider['auth_pass']
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
@ -79,17 +76,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
out = conn.get_prompt()
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -25,8 +25,6 @@ import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.dellos6.dellos6 import dellos6_provider_spec
from ansible.module_utils.network.common.utils import load_provider
@ -67,7 +65,7 @@ class ActionModule(ActionNetworkModule):
pc.become_pass = provider['auth_pass']
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
@ -79,17 +77,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
out = conn.get_prompt()
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -25,8 +25,6 @@ import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.dellos9.dellos9 import dellos9_provider_spec
@ -67,7 +65,7 @@ class ActionModule(ActionNetworkModule):
pc.become_pass = provider['auth_pass']
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
@ -79,17 +77,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
out = conn.get_prompt()
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -24,8 +24,6 @@ from ansible import constants as C
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.enos.enos import enos_provider_spec
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.connection import Connection
from ansible.module_utils._text import to_text
from ansible.utils.display import Display
display = Display()
@ -38,7 +36,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'enos_config' else False
socket_path = None
if self._play_context.connection == 'local':
provider = load_provider(enos_provider_spec, self._task.args)
@ -56,7 +53,7 @@ class ActionModule(ActionNetworkModule):
pc.become_method = 'enable'
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()
@ -68,18 +65,5 @@ class ActionModule(ActionNetworkModule):
task_vars['ansible_socket'] = socket_path
# make sure we are in the right cli context which should be
# enable mode and not config module or exec mode
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('In Config mode, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
else:
conn.send_command('enable')
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -23,8 +23,6 @@ import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.module_utils.network.eos.eos import eos_provider_spec
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider
@ -40,7 +38,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'eos_config' else False
socket_path = None
persistent_connection = self._play_context.connection.split('.')[-1]
if persistent_connection in ('network_cli', 'httpapi'):
@ -72,7 +69,7 @@ class ActionModule(ActionNetworkModule):
pc.become_pass = provider['auth_pass']
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
connection.set_options(direct={'persistent_command_timeout': command_timeout})
@ -91,19 +88,6 @@ class ActionModule(ActionNetworkModule):
else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
if (self._play_context.connection == 'local' and transport == 'cli') or self._play_context.connection == 'network_cli':
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
while '(config' in to_text(out, errors='surrogate_then_replace').strip():
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('abort')
out = conn.get_prompt()
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -19,12 +19,9 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
import sys
import copy
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.ios.ios import ios_provider_spec
@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'ios_config' else False
socket_path = None
persistent_connection = self._play_context.connection.split('.')[-1]
if persistent_connection == 'network_cli':
@ -64,7 +60,7 @@ class ActionModule(ActionNetworkModule):
pc.become_pass = provider['auth_pass']
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
connection.set_options(direct={'persistent_command_timeout': command_timeout})
@ -80,19 +76,5 @@ class ActionModule(ActionNetworkModule):
else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
try:
out = conn.get_prompt()
if re.search(r'config.*\)#', to_text(out, errors='surrogate_then_replace').strip()):
display.vvvv('wrong context, sending end to device', self._play_context.remote_addr)
conn.send_command('end')
except ConnectionError as exc:
return {'failed': True, 'msg': to_text(exc)}
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -22,9 +22,6 @@ __metaclass__ = type
import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.module_utils.network.iosxr.iosxr import iosxr_provider_spec
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider
@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'iosxr_config' else False
socket_path = None
force_cli = module_name in ('iosxr_netconf', 'iosxr_config', 'iosxr_command', 'iosxr_facts')
persistent_connection = self._play_context.connection.split('.')[-1]
@ -63,7 +59,7 @@ class ActionModule(ActionNetworkModule):
pc.password = provider['password'] or self._play_context.password
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
connection.set_options(direct={'persistent_command_timeout': command_timeout})
@ -87,18 +83,5 @@ class ActionModule(ActionNetworkModule):
else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
# make sure we are in the right cli context which should be
# enable mode and not config module
if (self._play_context.connection == 'local' and pc.connection == 'network_cli') or self._play_context.connection == 'network_cli':
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('abort')
out = conn.get_prompt()
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -22,9 +22,6 @@ __metaclass__ = type
import sys
import copy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.ironware.ironware import ironware_provider_spec
@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'ironware_config' else False
socket_path = None
persistent_connection = self._play_context.connection.split('.')[-1]
if persistent_connection == 'network_cli':
@ -64,7 +60,7 @@ class ActionModule(ActionNetworkModule):
pc.become_pass = provider['auth_pass']
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
connection.set_options(direct={'persistent_command_timeout': command_timeout})
@ -80,20 +76,5 @@ class ActionModule(ActionNetworkModule):
else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
try:
out = conn.get_prompt()
while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
out = conn.get_prompt()
except ConnectionError as exc:
return {'failed': True, 'msg': to_text(exc)}
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -22,8 +22,6 @@ __metaclass__ = type
import sys
import copy
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.junos.junos import junos_provider_spec
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
@ -41,7 +39,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'junos_config' else False
socket_path = None
persistent_connection = self._play_context.connection.split('.')[-1]
if self._play_context.connection == 'local':
@ -67,7 +64,7 @@ class ActionModule(ActionNetworkModule):
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
connection.set_options(direct={'persistent_command_timeout': command_timeout})
@ -95,18 +92,5 @@ class ActionModule(ActionNetworkModule):
"Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html"
% (self._play_context.connection, module_name)}
if (self._play_context.connection == 'local' and pc.connection == 'network_cli') or self._play_context.connection == 'network_cli':
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
while to_text(out, errors='surrogate_then_replace').strip().endswith('#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
out = conn.get_prompt()
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -55,7 +55,7 @@ class ActionModule(ActionNetworkModule):
pc.private_key_file = args.get('ssh_keyfile') or self._play_context.private_key_file
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
timeout = args.get('timeout')
command_timeout = int(timeout) if timeout else connection.get_option('persistent_command_timeout')

@ -24,8 +24,6 @@ import re
import sys
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.nxos.nxos import nxos_provider_spec
@ -57,8 +55,6 @@ class ActionModule(ActionNetworkModule):
self._task.args['username'] = self._play_context.connection_user
if module_name == 'nxos_install_os':
persistent_command_timeout = 0
persistent_connect_timeout = 0
connection = self._connection
if connection.transport == 'local':
persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
@ -106,7 +102,7 @@ class ActionModule(ActionNetworkModule):
pc.become_pass = provider['auth_pass']
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
connection.set_options(direct={'persistent_command_timeout': command_timeout})
@ -125,21 +121,6 @@ class ActionModule(ActionNetworkModule):
else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
if (self._play_context.connection == 'local' and transport == 'cli') or self._play_context.connection == 'network_cli':
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
# Match prompts ending in )# except those with (maint-mode)#
config_prompt = re.compile(r'^.*\((?!maint-mode).*\)#$')
out = conn.get_prompt()
while config_prompt.match(to_text(out, errors='surrogate_then_replace').strip()):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit')
out = conn.get_prompt()
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -59,7 +59,7 @@ class ActionModule(ActionNetworkModule):
command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
connection.set_options(direct={'persistent_command_timeout': command_timeout})
socket_path = connection.run()

@ -22,10 +22,7 @@ __metaclass__ = type
import sys
import copy
from ansible import constants as C
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.vyos.vyos import vyos_provider_spec
from ansible.utils.display import Display
@ -40,7 +37,6 @@ class ActionModule(ActionNetworkModule):
module_name = self._task.action.split('.')[-1]
self._config_module = True if module_name == 'vyos_config' else False
socket_path = None
persistent_connection = self._play_context.connection.split('.')[-1]
if persistent_connection == 'network_cli':
@ -60,7 +56,7 @@ class ActionModule(ActionNetworkModule):
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
connection.set_options(direct={'persistent_command_timeout': command_timeout})
@ -76,16 +72,5 @@ class ActionModule(ActionNetworkModule):
else:
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
if to_text(out, errors='surrogate_then_replace').strip().endswith('#'):
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
conn.send_command('exit discard')
result = super(ActionModule, self).run(task_vars=task_vars)
return result

@ -443,3 +443,31 @@ class CliconfBase(AnsiblePlugin):
if replace and not operations.get('supports_replace', False):
raise ValueError("configuration replace is not supported")
def set_cli_prompt_context(self):
"""
Ensure the command prompt on device is in right mode
:return: None
"""
pass
def _update_cli_prompt_context(self, config_context=None, exit_command='exit'):
"""
Update the cli prompt context to ensure it is in operational mode
:param config_context: It is string value to identify if the current cli prompt ends with config mode prompt
:param exit_command: Command to execute to exit the config mode
:return: None
"""
out = self._connection.get_prompt()
if out is None:
raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received'
u' response window: %s' % self._connection._last_recv_window)
while True:
out = to_text(out, errors='surrogate_then_replace').strip()
if config_context and out.endswith(config_context):
self._connection.queue_message('vvvv', 'wrong context, sending exit to device')
self.send_command(exit_command)
out = self._connection.get_prompt()
else:
break

@ -34,6 +34,7 @@ import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -85,3 +86,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context=')#')

@ -86,3 +86,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context=')#')

@ -34,6 +34,7 @@ import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -98,3 +99,24 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
out = self._connection.get_prompt()
if out is None:
raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received'
u' response window: %s' % self._connection._last_recv_window)
prompt = to_text(out, errors='surrogate_then_replace').strip()
while prompt.endswith(']'):
self._connection.queue_message('vvvv', 'wrong context, sending return to device')
if prompt.startswith('[*'):
self._connection.exec_command('clear configuration candidate')
self._connection.exec_command('return')
out = self._connection.get_prompt()
prompt = to_text(out, errors='surrogate_then_replace').strip()

@ -29,7 +29,7 @@ version_added: 2.6
import re
import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils.common._collections_compat import Mapping
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.network.common.utils import to_list
@ -116,3 +116,21 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
out = self._connection.get_prompt()
if out is None:
raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received'
u' response window: %s' % self._connection._last_recv_window)
if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
self._connection.queue_message('vvvv', 'In Config mode, sending exit to device')
self._connection.send_command('exit')
else:
self._connection.send_command('enable')

@ -87,3 +87,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context=')#')

@ -87,3 +87,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context=')#')

@ -87,3 +87,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context=')#')

@ -31,7 +31,8 @@ import json
from itertools import chain
from ansible.module_utils._text import to_bytes, to_text
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -83,3 +84,21 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
out = self._connection.get_prompt()
if out is None:
raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received'
u' response window: %s' % self._connection._last_recv_window)
if to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
self._connection.queue_message('vvvv', 'In Config mode, sending exit to device')
self._connection.send_command('exit')
else:
self._connection.send_command('enable')

@ -286,6 +286,14 @@ class Cliconf(CliconfBase):
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context='(config', exit_command='abort')
def _get_command_with_output(self, command, output):
options_values = self.get_option_values()
if output not in options_values['output']:

@ -34,8 +34,6 @@ import re
import time
import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text
from ansible.module_utils.common._collections_compat import Mapping
@ -346,6 +344,22 @@ class Cliconf(CliconfBase):
else:
return 'full'
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
out = self._connection.get_prompt()
if out is None:
raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received'
u' response window: %s' % self._connection._last_recv_window)
if re.search(r'config.*\)#', to_text(out, errors='surrogate_then_replace').strip()):
self._connection.queue_message('vvvv', 'wrong context, sending end to device')
self._connection.send_command('end')
def _extract_banners(self, config):
banners = {}
banner_cmds = re.findall(r'^banner (\w+)', config, re.M)

@ -266,3 +266,11 @@ class Cliconf(CliconfBase):
result['device_operations'] = self.get_device_operations()
result.update(self.get_option_values())
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context=')#', exit_command='abort')

@ -86,3 +86,11 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context=')#')

@ -246,6 +246,14 @@ class Cliconf(CliconfBase):
result.update(self.get_option_values())
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context='#')
def _get_command_with_output(self, command, output):
options_values = self.get_option_values()
if output not in options_values['output']:

@ -258,6 +258,24 @@ class Cliconf(CliconfBase):
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli context
:return: None
"""
if self._connection.connected:
out = self._connection.get_prompt()
if out is None:
raise AnsibleConnectionFailure(message=u'cli prompt is not identified from the last received'
u' response window: %s' % self._connection._last_recv_window)
# Match prompts ending in )# except those with (maint-mode)#
config_prompt = re.compile(r'^.*\((?!maint-mode).*\)#$')
while config_prompt.match(to_text(out, errors='surrogate_then_replace').strip()):
self._connection.queue_message('vvvv', 'wrong context, sending exit to device')
self._connection.send_command('exit')
out = self._connection.get_prompt()
def _get_command_with_output(self, command, output):
options_values = self.get_option_values()
if output not in options_values['output']:

@ -266,3 +266,11 @@ class Cliconf(CliconfBase):
result['device_operations'] = self.get_device_operations()
result.update(self.get_option_values())
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context='#', exit_command='exit discard')

@ -273,6 +273,7 @@ options:
- name: ansible_network_cli_retries
"""
from functools import wraps
import getpass
import json
import logging
@ -290,10 +291,20 @@ from ansible.module_utils.six.moves import cPickle
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils._text import to_bytes, to_text
from ansible.playbook.play_context import PlayContext
from ansible.plugins.connection import NetworkConnectionBase, ensure_connect
from ansible.plugins.connection import NetworkConnectionBase
from ansible.plugins.loader import cliconf_loader, terminal_loader, connection_loader
def ensure_connect(func):
@wraps(func)
def wrapped(self, *args, **kwargs):
if not self._connected:
self._connect()
self.update_cli_prompt_context()
return func(self, *args, **kwargs)
return wrapped
class AnsibleCmdRespRecv(Exception):
pass
@ -315,10 +326,15 @@ class Connection(NetworkConnectionBase):
self._last_response = None
self._history = list()
self._command_response = None
self._last_recv_window = None
self._terminal = None
self.cliconf = None
self.paramiko_conn = None
self._paramiko_conn = None
# Managing prompt context
self._check_prompt = False
self._task_uuid = to_text(kwargs.get('task_uuid', ''))
if self._play_context.verbosity > 3:
logging.getLogger('paramiko').setLevel(logging.DEBUG)
@ -401,6 +417,15 @@ class Connection(NetworkConnectionBase):
if hasattr(self, 'disable_response_logging'):
self.disable_response_logging()
def set_check_prompt(self, task_uuid):
self._check_prompt = task_uuid
def update_cli_prompt_context(self):
# set cli prompt context at the start of new task run only
if self._check_prompt and self._task_uuid != self._check_prompt:
self._task_uuid, self._check_prompt = self._check_prompt, False
self.set_cli_prompt_context()
def _connect(self):
'''
Connects to the remote device and starts the terminal
@ -537,6 +562,7 @@ class Connection(NetworkConnectionBase):
recv.seek(offset)
window = self._strip(recv.read())
self._last_recv_window = window
window_count += 1
if prompts and not handled:

@ -31,6 +31,7 @@ options:
"""
from ansible.executor.task_executor import start_connection
from ansible.plugins.connection import ConnectionBase
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection as SocketConnection
from ansible.utils.display import Display
@ -43,6 +44,10 @@ class Connection(ConnectionBase):
transport = 'persistent'
has_pipelining = False
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
self._task_uuid = to_text(kwargs.get('task_uuid', ''))
def _connect(self):
self._connected = True
return self
@ -71,7 +76,7 @@ class Connection(ConnectionBase):
"""
display.vvvv('starting connection from persistent connection plugin', host=self._play_context.remote_addr)
variables = {'ansible_command_timeout': self.get_option('persistent_command_timeout')}
socket_path = start_connection(self._play_context, variables)
socket_path = start_connection(self._play_context, variables, self._task_uuid)
display.vvvv('local domain socket path is %s' % socket_path, host=self._play_context.remote_addr)
setattr(self, '_socket_path', socket_path)
return socket_path

@ -0,0 +1,20 @@
---
- block:
- cli_command:
command: reload power
prompt:
- "yes/no/cancel/diff]"
- "confirm]"
answer:
- "no"
- ""
check_all: yes
become: yes
- wait_for_connection:
delay: 20
sleep: 10
- cli_command:
command: show version
when: ansible_connection.endswith("network_cli")

@ -0,0 +1,19 @@
---
- block:
- cli_command:
command: reload
prompt:
- "yes/no"
- "confirm"
answer:
- "no"
- "y"
check_all: yes
- wait_for_connection:
delay: 20
sleep: 10
- cli_command:
command: show version
when: ansible_connection.endswith("network_cli")

@ -29,6 +29,36 @@
- assert:
that:
- "result.changed == false"
- cli_command:
command: "{{item}}"
prompt:
- "New password"
- "Retype new password"
answer:
- "Test1234"
- "Test1234"
check_all: True
loop:
- "configure"
- "rollback"
- "set system login user ansible_test class operator authentication plain-text-password"
- "commit"
register: result
ignore_errors: True
- assert:
that:
- "'failed' not in result"
- junos_netconf:
register: result
ignore_errors: True
- assert:
that:
- "result.failed == false"
when: ansible_connection == 'network_cli'
- debug: msg="END cli/cli_command.yaml on connection={{ ansible_connection }}"

@ -0,0 +1,15 @@
- name: collect cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
connection: local
register: test_cases
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test case (connection=network_cli)
include: "{{ test_case_to_run }} ansible_connection=network_cli"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -1,2 +1,3 @@
---
- { include: cli.yaml, tags: ['cli'] }
- { include: netconf.yaml, tags: ['netconf'] }

@ -0,0 +1,14 @@
---
- cli_command:
command: request system reboot
prompt:
- Reboot the system?
answer:
- y
- wait_for_connection:
delay: 20
sleep: 10
- cli_command:
command: show version
Loading…
Cancel
Save