Repository: ansible/ansible

Issue:
Problem with nxos_facts on ansible 2.3
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report
 - Feature Idea
 - Documentation Report
bug report
##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
nxos_facts
##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0 (devel ebc2716be7)
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
centOs 6.9
##### SUMMARY
<!--- Explain the problem briefly -->
So for some reasons when I have invalid input on command next step of the module behaviour generation of the error and process iterrupted. Commands which have not "invalid input" works fine, but no output about them in module.
##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
---

- name: Collect NXOS facts
  hosts: test-routers
  gather_facts: false
  connection: local

  tasks:

    - name: NX-OS Facts
      nxos_facts:
        gather_subset: all
#        gather_subset:
#             - "!interfaces"
        provider: "{{ cli }}"

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
I want the output from good commands
Expect output of good commands about nx-os facts
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
[17:10]root@dnetapp01:/etc/network/scripts/databases/playbook_ansible$ ansible-playbook 1_nxos_facts.yml -vvv
Using /etc/ansible/ansible.cfg as config file
SSH password:

PLAYBOOK: 1_nxos_facts.yml **********************************************************************************************************************************************************
1 plays in 1_nxos_facts.yml

PLAY [Collect NXOS facts] ***********************************************************************************************************************************************************
META: ran handlers

TASK [NX-OS Facts] ******************************************************************************************************************************************************************
task path: /etc/network/scripts/databases/playbook_ansible/1_nxos_facts.yml:10
Using module file /etc/network/scripts/ansible/lib/ansible/modules/network/nxos/nxos_facts.py
<10.255.252.2> ESTABLISH LOCAL CONNECTION FOR USER: root
<10.255.252.2> EXEC /bin/sh -c 'echo ~ && sleep 0'
<10.255.252.2> EXEC /bin/sh -c '( umask 77 && mkdir -p "echo /root/.ansible/tmp/ansible-tmp-1488208234.33-150228641643642" && echo ansible-tmp-1488208234.33-150228641643642="e cho /root/.ansible/tmp/ansible-tmp-1488208234.33-150228641643642" ) && sleep 0'
<10.255.252.2> PUT /tmp/tmppgpQyw TO /root/.ansible/tmp/ansible-tmp-1488208234.33-150228641643642/nxos_facts.py
<10.255.252.2> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1488208234.33-150228641643642/ /root/.ansible/tmp/ansible-tmp-1488208234.33-150228641643642/nxos_facts.py && sleep 0'
<10.255.252.2> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1488208234.33-150228641643642/nxos_facts.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1488208234.33-1502 28641643642/" > /dev/null 2>&1 && sleep 0'
fatal: [10.255.252.2]: FAILED! => {
"changed": false,
"failed": true,
"invocation": {
"module_args": {
"gather_subset": [
"all"
],
"host": "10.255.252.2",
"password": "xxx",
"port": null,
"provider": {
"auth_pass": "xxx",
"authorize": true,
"host": "10.255.252.2",
"password": "xxx",
"port": null,
"ssh_keyfile": null,
"timeout": null,
"transport": "cli",
"use_ssl": null,
"username": "xxx",
"validate_certs": null
},
"ssh_keyfile": null,
"timeout": null,
"transport": "cli",
"use_ssl": null,
"username": "xxx",
"validate_certs": null
}
},
"msg": "show system resources | json\r\r\n ^\r\n% Invalid command at '^' marker.\r\n\rn7k-1-vdc-agg# "
}
to retry, use: --limit @/etc/network/scripts/databases/playbook_ansible/1_nxos_facts.retry

PLAY RECAP **************************************************************************************************************************************************************************
10.255.252.2 : ok=0 changed=0 unreachable=0 failed=1


```



Discussion:
cc @jedelman8 @GGabriele @privateip @gundalow @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/nxos.py b/lib/ansible/module_utils/nxos.py
--- a/lib/ansible/module_utils/nxos.py
+++ b/lib/ansible/module_utils/nxos.py
@@ -255,13 +255,16 @@ def send_request(self, commands, output='text', check_status=True):
             except ValueError:
                 self._module.fail_json(msg='unable to parse response')
 
-            if check_status:
-                output = response['ins_api']['outputs']['output']
-                for item in to_list(output):
-                    if item['code'] != '200':
-                        self._error(output=output, **item)
-                    else:
-                        result.append(item['body'])
+            output = response['ins_api']['outputs']['output']
+            for item in to_list(output):
+                if check_status and item['code'] != '200':
+                    self._error(output=output, **item)
+                elif 'body' in item:
+                    result.append(item['body'])
+                #else:
+                    # error in command but since check_status is disabled
+                    # silently drop it.
+                    #result.append(item['msg'])
 
         return result
 
diff --git a/lib/ansible/modules/network/nxos/nxos_facts.py b/lib/ansible/modules/network/nxos/nxos_facts.py
--- a/lib/ansible/modules/network/nxos/nxos_facts.py
+++ b/lib/ansible/modules/network/nxos/nxos_facts.py
@@ -180,7 +180,7 @@
 """
 import re
 
-from ansible.module_utils.nxos import run_commands
+from ansible.module_utils.nxos import run_commands, get_config
 from ansible.module_utils.nxos import nxos_argument_spec, check_args
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.six import iteritems
@@ -188,15 +188,24 @@
 
 class FactsBase(object):
 
-    COMMANDS = frozenset()
-
     def __init__(self, module):
         self.module = module
+        self.warnings = list()
         self.facts = dict()
-        self.responses = None
 
     def populate(self):
-        self.responses = run_commands(self.module, list(self.COMMANDS))
+        pass
+
+    def run(self, command, output=None):
+        command_string = command
+        if output:
+            command = {'command': command, 'output': output}
+        resp = run_commands(self.module, command, check_rc=False)
+        try:
+            return resp[0]
+        except IndexError:
+            self.warnings.append('command %s returned to data, facts will not be populated' % command_string)
+            return None
 
     def transform_dict(self, data, keymap):
         transform = dict()
@@ -210,6 +219,7 @@ def transform_iterable(self, iterable, keymap):
             yield self.transform_dict(item, keymap)
 
 
+
 class Default(FactsBase):
 
     VERSION_MAP = frozenset([
@@ -220,38 +230,30 @@ class Default(FactsBase):
         ('host_name', 'hostname')
     ])
 
-    COMMANDS = ['show version | json']
-
     def populate(self):
-        super(Default, self).populate()
-        data = self.responses[0]
-        self.facts.update(self.transform_dict(data, self.VERSION_MAP))
+        data = self.run('show version', 'json')
+        if data:
+            self.facts.update(self.transform_dict(data, self.VERSION_MAP))
 
 class Config(FactsBase):
 
-    COMMANDS = ['show running-config']
-
     def populate(self):
         super(Config, self).populate()
-        data = self.responses[0]
-        self.facts['config'] = data
+        self.facts['config'] = get_config(self.module)
 
 
 class Hardware(FactsBase):
 
-    COMMANDS = [
-        {'command': 'dir', 'output': 'text'},
-        'show system resources | json'
-    ]
-
     def populate(self):
-        super(Hardware, self).populate()
-        data = self.responses[0]
-        self.facts['filesystems'] = self.parse_filesystems(data)
+        cmd = {'command': 'dir', 'output': 'text'},
+        data = self.run('dir', 'text')
+        if data:
+            self.facts['filesystems'] = self.parse_filesystems(data)
 
-        data = self.responses[1]
-        self.facts['memtotal_mb'] = int(data['memory_usage_total']) / 1024
-        self.facts['memfree_mb'] = int(data['memory_usage_free']) / 1024
+        data = self.run('show system resources', 'json')
+        if data:
+            self.facts['memtotal_mb'] = int(data['memory_usage_total']) / 1024
+            self.facts['memfree_mb'] = int(data['memory_usage_free']) / 1024
 
     def parse_filesystems(self, data):
         return re.findall(r'^Usage for (\S+)//', data, re.M)
@@ -285,15 +287,16 @@ def populate(self):
         self.facts['all_ipv4_addresses'] = list()
         self.facts['all_ipv6_addresses'] = list()
 
-        data = run_commands(self.module, ['show interface | json'])[0]
-        self.facts['interfaces'] = self.populate_interfaces(data)
+        data = self.run('show interface', 'json')
+        if data:
+            self.facts['interfaces'] = self.populate_interfaces(data)
 
-        out = run_commands(self.module, ['show ipv6 interface | json'])
-        if out[0]:
+        data = self.run('show ipv6 inteface', 'json')
+        if data:
             self.parse_ipv6_interfaces(out)
 
-        out = run_commands(self.module, ['show lldp neighbors'], check_rc=False)
-        if out and out[0]:
+        data = self.run('show lldp neighbors')
+        if data:
             self.facts['neighbors'] = self.populate_neighbors(out)
 
     def populate_interfaces(self, data):
@@ -371,32 +374,27 @@ class Legacy(FactsBase):
         ('total_capa', 'total_capacity')
     ])
 
-    COMMANDS = [
-        'show version | json',
-        'show module | json',
-        'show environment | json',
-        'show interface | json',
-        'show vlan brief | json'
-    ]
-
-
     def populate(self):
-        super(Legacy, self).populate()
-        data = self.responses[0]
-        self.facts.update(self.transform_dict(data, self.VERSION_MAP))
+        data = self.run('show version', 'json')
+        if data:
+            self.facts.update(self.transform_dict(data, self.VERSION_MAP))
 
-        data = self.responses[3]
-        self.facts['_interfaces_list'] = self.parse_interfaces(data)
+        data = self.run('show interface', 'json')
+        if data:
+            self.facts['_interfaces_list'] = self.parse_interfaces(data)
 
-        data = self.responses[4]
-        self.facts['_vlan_list'] = self.parse_vlans(data)
+        data = self.run('show vlan brief', 'json')
+        if data:
+            self.facts['_vlan_list'] = self.parse_vlans(data)
 
-        data = self.responses[1]
-        self.facts['_module'] = self.parse_module(data)
+        data = self.run('show module', 'json')
+        if data:
+            self.facts['_module'] = self.parse_module(data)
 
-        data = self.responses[2]
-        self.facts['_fan_info'] = self.parse_fan_info(data)
-        self.facts['_power_supply_info'] = self.parse_power_supply_info(data)
+        data = self.run('show environment', 'json')
+        if data:
+            self.facts['_fan_info'] = self.parse_fan_info(data)
+            self.facts['_power_supply_info'] = self.parse_power_supply_info(data)
 
     def parse_interfaces(self, data):
         objects = list()
@@ -497,6 +495,7 @@ def main():
     for inst in instances:
         inst.populate()
         facts.update(inst.facts)
+        warnings.extend(inst.warnings)
 
     ansible_facts = dict()
     for key, value in iteritems(facts):


Test patch: