Repository: ansible/ansible

Issue:
ios_facts broken when persistent_connections is disabled
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
ios_facts

##### ANSIBLE VERSION
```
ansible 2.3.0 (test_ios 812608c49b) last updated 2017/02/09 11:15:34 (GMT +100)

```

##### CONFIGURATION

##### OS / ENVIRONMENT

##### SUMMARY
ios_facts fails withnetwork_cli when persistent_connections is disabled.
If I enable persistent_connections it passes

##### STEPS TO REPRODUCE
```yaml
- name: test getting all facts
  ios_facts:
    gather_subset:
      - all
  register: result
```

##### EXPECTED RESULTS

##### ACTUAL RESULTS
time ANSIBLE_DEBUG=True ANSIBLE_USE_PERSISTENT_CONNECTIONS=False ANSIBLE_ROLES_PATH=targets  ansible-playbook -vvvvv  -i  inventory.network-jobarker ios.yaml  --limit ios -e "limit_to=ios_facts" -u cisco -k --tags network_cli

```
TASK [ios_facts : test getting all facts] ********************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible/test/integration/targets/ios_facts/tests/network_cli/all_facts.yaml:5
 20483 1486646441.98275: sending task start callback
 20483 1486646441.98278: entering _queue_task() for ios01/ios_facts
 20483 1486646441.98281: Creating lock for ios_facts
 20483 1486646441.98573: worker is 1 (out of 1 available)
 20483 1486646441.98663: exiting _queue_task() for ios01/ios_facts
 20483 1486646441.98715: done queuing things up, now waiting for results queue to drain
 20483 1486646441.98721: waiting for pending results...
 20536 1486646441.99037: running TaskExecutor() for ios01/TASK: ios_facts : test getting all facts
 20536 1486646441.99172: in run()
 20536 1486646441.99212: calling self._execute()
 20536 1486646441.99883: Loading FilterModule 'core' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/filter/core.py
 20536 1486646442.02356: Loading FilterModule 'ipaddr' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/filter/ipaddr.py
 20536 1486646442.02404: Loading FilterModule 'json_query' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/filter/json_query.py
 20536 1486646442.02427: Loading FilterModule 'mathstuff' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/filter/mathstuff.py
 20536 1486646442.02519: Loading TestModule 'core' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/test/core.py
 20536 1486646442.02534: Loading TestModule 'files' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/test/files.py
 20536 1486646442.02546: Loading TestModule 'mathstuff' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/test/mathstuff.py
 20536 1486646442.08269: Loading Connection 'network_cli' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/connection/network_cli.py
 20536 1486646442.08383: Loading ShellModule 'csh' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/shell/csh.py
 20536 1486646442.08414: Loading ShellModule 'fish' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/shell/fish.py
 20536 1486646442.08455: Loading ShellModule 'powershell' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/shell/powershell.py
 20536 1486646442.08472: Loading ShellModule 'sh' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/shell/sh.py
 20536 1486646442.08497: Loading ShellModule 'sh' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/shell/sh.py (found_in_cache=True, class_only=False)
 20536 1486646442.08509: in VariableManager get_vars()
 20536 1486646442.08545: done with get_vars()
 20536 1486646442.08559: Loading Connection 'network_cli' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/connection/network_cli.py (found_in_cache=True, class_only=True)
 20536 1486646442.08599: Loading ActionModule 'network' from /home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/action/network.py
 20536 1486646442.08608: starting attempt loop
 20536 1486646442.08612: running the handler
<ios01> closing connection
 20536 1486646442.09082: done running TaskExecutor() for ios01/TASK: ios_facts : test getting all facts
 20536 1486646442.09087: sending task result
 20536 1486646442.09130: done sending task result
 20536 1486646442.09140: WORKER PROCESS EXITING
 20483 1486646442.09335: in VariableManager get_vars()
 20483 1486646442.09553: done with get_vars()
 20483 1486646442.09591: marking ios01 as failed
 20483 1486646442.09609: marking host ios01 failed, current state: HOST STATE: block=4, task=2, rescue=0, always=0, run_state=ITERATING_TASKS, fail_state=FAILED_NONE, pending_setup=False, tasks child state? (HOST STATE: block=0, task=4, rescue=0, always=0, run_state=ITERATING_TASKS, fail_state=FAILED_NONE, pending_setup=False, tasks child state? (HOST STATE: block=0, task=2, rescue=0, always=0, run_state=ITERATING_TASKS, fail_state=FAILED_NONE, pending_setup=False, tasks child state? (None), rescue child state? (None), always child state? (None), did rescue? False, did start at task? False), rescue child state? (None), always child state? (None), did rescue? False, did start at task? False), rescue child state? (None), always child state? (None), did rescue? False, did start at task? False
 20483 1486646442.09619: ^ failed state is now: HOST STATE: block=4, task=2, rescue=0, always=0, run_state=ITERATING_TASKS, fail_state=FAILED_NONE, pending_setup=False, tasks child state? (HOST STATE: block=0, task=4, rescue=0, always=0, run_state=ITERATING_TASKS, fail_state=FAILED_NONE, pending_setup=False, tasks child state? (HOST STATE: block=0, task=2, rescue=0, always=0, run_state=ITERATING_COMPLETE, fail_state=FAILED_TASKS, pending_setup=False, tasks child state? (None), rescue child state? (None), always child state? (None), did rescue? False, did start at task? False), rescue child state? (None), always child state? (None), did rescue? False, did start at task? False), rescue child state? (None), always child state? (None), did rescue? False, did start at task? False
 20483 1486646442.09627: getting the next task for host ios01
 20483 1486646442.09636: done getting next task for host ios01
 20483 1486646442.09641:  ^ task is: None
 20483 1486646442.09648:  ^ state is: HOST STATE: block=4, task=2, rescue=0, always=0, run_state=ITERATING_COMPLETE, fail_state=FAILED_TASKS, pending_setup=False, tasks child state? (None), rescue child state? (None), always child state? (None), did rescue? False, did start at task? False
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/executor/task_executor.py", line 126, in run
    res = self._execute()
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/executor/task_executor.py", line 504, in _execute
    result = self._handler.run(task_vars=variables)
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/action/network.py", line 43, in run
    module.main()
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/modules/network/ios/ios_facts.py", line 495, in main
    inst.populate()
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/modules/network/ios/ios_facts.py", line 238, in populate
    super(Hardware, self).populate()
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/modules/network/ios/ios_facts.py", line 185, in populate
    self.responses = run_commands(self.module, self.COMMANDS, check_rc=False)
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/modules/network/ios/ios_facts.py", line 165, in invoke
    return func(*args, **kwargs)
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/module_utils/ios.py", line 51, in run_commands
    rc, out, err = module.exec_command(cmd)
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/module_utils/local.py", line 59, in exec_command
    rc, out, err = self.connection.exec_command(args)
  File "/home/johnb/git/ansible-inc/ansible/lib/ansible/plugins/connection/network_cli.py", line 235, in exec_command
    if obj['command'] == 'close_shell()':
TypeError: string indices must be integers

fatal: [ios01]: FAILED! => {
    "failed": true, 
    "msg": "Unexpected failure during module execution.", 
    "stdout": ""
}
 20483 1486646442.09842: no more pending results, returning what we have

```



Discussion:
cc @privateip @Qalthos
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/module_utils/ios.py b/lib/ansible/module_utils/ios.py
--- a/lib/ansible/module_utils/ios.py
+++ b/lib/ansible/module_utils/ios.py
@@ -26,7 +26,7 @@
 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #
 from ansible.module_utils.basic import env_fallback
-from ansible.module_utils.network_common import to_list
+from ansible.module_utils.network_common import to_list, ComplexList
 from ansible.module_utils.connection import exec_command
 
 _DEVICE_CONFIGS = {}
@@ -65,9 +65,19 @@ def get_config(module, flags=[]):
         _DEVICE_CONFIGS[cmd] = cfg
         return cfg
 
+def to_commands(commands):
+    transform = ComplexList(dict(
+        command=dict(key=True),
+        prompt=dict(),
+        response=dict()
+    ))
+    return transform(commands)
+
+
 def run_commands(module, commands, check_rc=True):
     responses = list()
-    for cmd in to_list(commands):
+    commands = to_commands(to_list(commands))
+    for cmd in commands:
         cmd = module.jsonify(cmd)
         rc, out, err = exec_command(module, cmd)
         if check_rc and rc != 0:
diff --git a/lib/ansible/modules/network/ios/ios_facts.py b/lib/ansible/modules/network/ios/ios_facts.py
--- a/lib/ansible/modules/network/ios/ios_facts.py
+++ b/lib/ansible/modules/network/ios/ios_facts.py
@@ -208,8 +208,8 @@ def parse_serialnum(self, data):
 class Hardware(FactsBase):
 
     COMMANDS = [
-        'dir | include Directory',
-        'show memory statistics | include Processor'
+        'dir',
+        'show memory statistics'
     ]
 
     def populate(self):
diff --git a/lib/ansible/plugins/action/ios.py b/lib/ansible/plugins/action/ios.py
--- a/lib/ansible/plugins/action/ios.py
+++ b/lib/ansible/plugins/action/ios.py
@@ -41,7 +41,8 @@ def run(self, tmp=None, task_vars=None):
 
         pc = copy.deepcopy(self._play_context)
         pc.connection = 'network_cli'
-        pc.port = provider['port'] or self._play_context.port
+        pc.network_os = 'ios'
+        pc.port = provider['port'] or self._play_context.port or 22
         pc.remote_user = provider['username'] or self._play_context.connection_user
         pc.password = provider['password'] or self._play_context.password
         pc.become = provider['authorize'] or False


Test patch: