ios_facts module will no longer error on missing command (#5491)

The module will error if it tries to use a cli command that is not available
on a given platform.  This fix will address that problem.  If the cli
command is not available, then the command is silently discarded and the
facts that the command output is based on is not returned.  Any failed
commands are provided in the module return under the failed_commands
key.  This fix also updates the Examples docstring to make it consistent
with other ios_* modules

fixes #5444
fixes #5372
pull/18777/head
Peter Sprygada 8 years ago committed by Matt Clay
parent df70a58d72
commit 8b70d17f61

@ -42,19 +42,31 @@ options:
""" """
EXAMPLES = """ EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
vars:
cli:
host: "{{ inventory_hostname }}"
username: cisco
password: cisco
transport: cli
# Collect all facts from the device # Collect all facts from the device
- ios_facts: - ios_facts:
gather_subset: all gather_subset: all
provider: "{{ cli }}"
# Collect only the config and default facts # Collect only the config and default facts
- ios_facts: - ios_facts:
gather_subset: gather_subset:
- config - config
provider: "{{ cli }}"
# Do not collect hardware facts # Do not collect hardware facts
- ios_facts: - ios_facts:
gather_subset: gather_subset:
- "!hardware" - "!hardware"
provider: "{{ cli }}"
""" """
RETURN = """ RETURN = """
@ -127,44 +139,35 @@ import re
import itertools import itertools
import ansible.module_utils.ios import ansible.module_utils.ios
from ansible.module_utils.netcli import CommandRunner, AddCommandError
from ansible.module_utils.network import NetworkModule from ansible.module_utils.network import NetworkModule
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
from ansible.module_utils.six.moves import zip from ansible.module_utils.six.moves import zip
def add_command(runner, command):
try:
runner.add_command(command)
except AddCommandError:
# AddCommandError is raised for any issue adding a command to
# the runner. Silently ignore the exception in this case
pass
class FactsBase(object): class FactsBase(object):
def __init__(self, runner): def __init__(self, module):
self.runner = runner self.module = module
self.facts = dict() self.facts = dict()
self.failed_commands = list()
self.commands() def run(self, cmd):
try:
return self.module.cli(cmd)[0]
except:
self.failed_commands.append(cmd)
def commands(self):
raise NotImplementedError
class Default(FactsBase): class Default(FactsBase):
def commands(self):
add_command(self.runner, 'show version')
def populate(self): def populate(self):
data = self.runner.get_command('show version') data = self.run('show version')
if data:
self.facts['version'] = self.parse_version(data) self.facts['version'] = self.parse_version(data)
self.facts['serialnum'] = self.parse_serialnum(data) self.facts['serialnum'] = self.parse_serialnum(data)
self.facts['model'] = self.parse_model(data) self.facts['model'] = self.parse_model(data)
self.facts['image'] = self.parse_image(data) self.facts['image'] = self.parse_image(data)
self.facts['hostname'] = self.parse_hostname(data) self.facts['hostname'] = self.parse_hostname(data)
def parse_version(self, data): def parse_version(self, data):
match = re.search(r'Version (\S+),', data) match = re.search(r'Version (\S+),', data)
@ -194,20 +197,17 @@ class Default(FactsBase):
class Hardware(FactsBase): class Hardware(FactsBase):
def commands(self):
add_command(self.runner, 'dir | include Directory')
add_command(self.runner, 'show version')
add_command(self.runner, 'show memory statistics | include Processor')
def populate(self): def populate(self):
data = self.runner.get_command('dir | include Directory') data = self.run('dir | include Directory')
self.facts['filesystems'] = self.parse_filesystems(data) if data:
self.facts['filesystems'] = self.parse_filesystems(data)
data = self.runner.get_command('show memory statistics | include Processor') data = self.run('show memory statistics | include Processor')
match = re.findall(r'\s(\d+)\s', data) if data:
if match: match = re.findall(r'\s(\d+)\s', data)
self.facts['memtotal_mb'] = int(match[0]) / 1024 if match:
self.facts['memfree_mb'] = int(match[1]) / 1024 self.facts['memtotal_mb'] = int(match[0]) / 1024
self.facts['memfree_mb'] = int(match[1]) / 1024
def parse_filesystems(self, data): def parse_filesystems(self, data):
return re.findall(r'^Directory of (\S+)/', data, re.M) return re.findall(r'^Directory of (\S+)/', data, re.M)
@ -215,37 +215,33 @@ class Hardware(FactsBase):
class Config(FactsBase): class Config(FactsBase):
def commands(self):
add_command(self.runner, 'show running-config')
def populate(self): def populate(self):
self.facts['config'] = self.runner.get_command('show running-config') data = self.run('show running-config')
if data:
self.facts['config'] = data
class Interfaces(FactsBase): class Interfaces(FactsBase):
def commands(self):
add_command(self.runner, 'show interfaces')
add_command(self.runner, 'show ipv6 interface')
add_command(self.runner, 'show lldp')
add_command(self.runner, 'show lldp neighbors detail')
def populate(self): def populate(self):
self.facts['all_ipv4_addresses'] = list() self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list() self.facts['all_ipv6_addresses'] = list()
data = self.runner.get_command('show interfaces') data = self.run('show interfaces')
interfaces = self.parse_interfaces(data) if data:
self.facts['interfaces'] = self.populate_interfaces(interfaces) interfaces = self.parse_interfaces(data)
self.facts['interfaces'] = self.populate_interfaces(interfaces)
data = self.runner.get_command('show ipv6 interface') data = self.run('show ipv6 interface')
if len(data) > 0: if data:
data = self.parse_interfaces(data) data = self.parse_interfaces(data)
self.populate_ipv6_interfaces(data) self.populate_ipv6_interfaces(data)
if 'LLDP is not enabled' not in self.runner.get_command('show lldp'): data = self.run('show lldp')
neighbors = self.runner.get_command('show lldp neighbors detail') if 'LLDP is not enabled' not in data:
self.facts['neighbors'] = self.parse_neighbors(neighbors) neighbors = self.run('show lldp neighbors detail')
if neighbors:
self.facts['neighbors'] = self.parse_neighbors(neighbors)
def populate_interfaces(self, interfaces): def populate_interfaces(self, interfaces):
facts = dict() facts = dict()
@ -434,27 +430,27 @@ def main():
facts = dict() facts = dict()
facts['gather_subset'] = list(runable_subsets) facts['gather_subset'] = list(runable_subsets)
runner = CommandRunner(module)
instances = list() instances = list()
for key in runable_subsets: for key in runable_subsets:
instances.append(FACT_SUBSETS[key](runner)) instances.append(FACT_SUBSETS[key](module))
runner.run() failed_commands = list()
try: try:
for inst in instances: for inst in instances:
inst.populate() inst.populate()
failed_commands.extend(inst.failed_commands)
facts.update(inst.facts) facts.update(inst.facts)
except Exception: except Exception:
module.exit_json(out=module.from_json(runner.items)) exc = get_exception()
module.fail_json(msg=str(exc))
ansible_facts = dict() ansible_facts = dict()
for key, value in iteritems(facts): for key, value in iteritems(facts):
key = 'ansible_net_%s' % key key = 'ansible_net_%s' % key
ansible_facts[key] = value ansible_facts[key] = value
module.exit_json(ansible_facts=ansible_facts) module.exit_json(ansible_facts=ansible_facts, failed_commands=failed_commands)
if __name__ == '__main__': if __name__ == '__main__':

Loading…
Cancel
Save