From d26dfa16c76991d15c8bfb4d7dcfa107373b937a Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Mon, 18 May 2020 17:42:17 +0200 Subject: [PATCH] facts: Allow multiple entries per iface (#6) * facts: Allow multiple entries per iface There can be multiple addresses per interface, as well as multiple neighbors. This changes the format of "ansible_net_neighbors" to list instead of dict, because the old format could not store multiple neighbors per interface. Also, this fixes a crash when the ipv6 module is not loaded, because the error "bad command name" was being parsed with interface=None * facts: Fix tests * facts: Add changelog fragment --- plugins/modules/facts.py | 78 +++++++----------------- tests/unit/plugins/modules/test_facts.py | 8 +-- 2 files changed, 27 insertions(+), 59 deletions(-) diff --git a/plugins/modules/facts.py b/plugins/modules/facts.py index 2417133..0341ef6 100644 --- a/plugins/modules/facts.py +++ b/plugins/modules/facts.py @@ -248,7 +248,7 @@ class Interfaces(FactsBase): self.facts['interfaces'] = dict() self.facts['all_ipv4_addresses'] = list() self.facts['all_ipv6_addresses'] = list() - self.facts['neighbors'] = dict() + self.facts['neighbors'] = list() data = self.responses[0] if data: @@ -257,41 +257,31 @@ class Interfaces(FactsBase): data = self.responses[1] if data: - data = self.parse_addresses(data) - self.populate_ipv4_interfaces(data) + data = self.parse_detail(data) + self.populate_addresses(data, 'ipv4') data = self.responses[2] if data: - data = self.parse_addresses(data) - self.populate_ipv6_interfaces(data) + data = self.parse_detail(data) + self.populate_addresses(data, 'ipv6') data = self.responses[3] if data: - self.facts['neighbors'] = self.parse_neighbors(data) + self.facts['neighbors'] = list(self.parse_detail(data)) def populate_interfaces(self, data): for key, value in iteritems(data): self.facts['interfaces'][key] = value - def populate_ipv4_interfaces(self, data): - for key, value in iteritems(data): - if 'ipv4' not in self.facts['interfaces'][key]: - self.facts['interfaces'][key]['ipv4'] = list() + def populate_addresses(self, data, family): + for value in data: + key = value['interface'] + if family not in self.facts['interfaces'][key]: + self.facts['interfaces'][key][family] = list() addr, subnet = value['address'].split("/") - ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) - self.add_ip_address(addr.strip(), 'ipv4') - self.facts['interfaces'][key]['ipv4'].append(ipv4) - - def populate_ipv6_interfaces(self, data): - for key, value in iteritems(data): - if key is None: - break - if 'ipv6' not in self.facts['interfaces'][key]: - self.facts['interfaces'][key]['ipv6'] = list() - addr, subnet = value['address'].split("/") - ipv6 = dict(address=addr.strip(), subnet=subnet.strip()) - self.add_ip_address(addr.strip(), 'ipv6') - self.facts['interfaces'][key]['ipv6'].append(ipv6) + ip = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), family) + self.facts['interfaces'][key][family].append(ip) def add_ip_address(self, address, family): if family == 'ipv4': @@ -314,41 +304,19 @@ class Interfaces(FactsBase): facts = dict() data = self.preprocess(data) for line in data: - name = self.parse_name(line) - facts[name] = dict() - for (key, value) in re.findall(self.DETAIL_RE, line): - facts[name][key] = value + parsed = dict(re.findall(self.DETAIL_RE, line)) + if "name" not in parsed: + continue + facts[parsed["name"]] = dict(re.findall(self.DETAIL_RE, line)) return facts - def parse_addresses(self, data): - facts = dict() + def parse_detail(self, data): data = self.preprocess(data) for line in data: - name = self.parse_interface(line) - facts[name] = dict() - for (key, value) in re.findall(self.DETAIL_RE, line): - facts[name][key] = value - return facts - - def parse_neighbors(self, data): - facts = dict() - data = self.preprocess(data) - for line in data: - name = self.parse_interface(line) - facts[name] = dict() - for (key, value) in re.findall(self.DETAIL_RE, line): - facts[name][key] = value - return facts - - def parse_name(self, data): - match = re.search(r'name=\"([\w\d\-]+)\"', data, re.M) - if match: - return match.group(1) - - def parse_interface(self, data): - match = re.search(r'interface=([\w\d\-]+)', data, re.M) - if match: - return match.group(1) + parsed = dict(re.findall(self.DETAIL_RE, line)) + if "interface" not in parsed: + continue + yield parsed FACT_SUBSETS = dict( diff --git a/tests/unit/plugins/modules/test_facts.py b/tests/unit/plugins/modules/test_facts.py index 00f0ee4..d8dcf9c 100644 --- a/tests/unit/plugins/modules/test_facts.py +++ b/tests/unit/plugins/modules/test_facts.py @@ -92,7 +92,7 @@ class TestRouterosFactsModule(TestRouterosModule): set_module_args(dict(gather_subset='interfaces')) result = self.execute_module() self.assertIn( - result['ansible_facts']['ansible_net_all_ipv4_addresses'][0], ['10.37.129.3', '10.37.0.0'] + result['ansible_facts']['ansible_net_all_ipv4_addresses'][0], ['10.37.129.3', '10.37.0.0', '192.168.88.1'] ) self.assertEqual( result['ansible_facts']['ansible_net_all_ipv6_addresses'], ['fe80::21c:42ff:fe36:5290'] @@ -105,7 +105,7 @@ class TestRouterosFactsModule(TestRouterosModule): len(result['ansible_facts']['ansible_net_interfaces'].keys()), 11 ) self.assertEqual( - len(result['ansible_facts']['ansible_net_neighbors'].keys()), 4 + len(result['ansible_facts']['ansible_net_neighbors']), 4 ) def test_facts_interfaces_no_ipv6(self): @@ -113,7 +113,7 @@ class TestRouterosFactsModule(TestRouterosModule): 'facts/ipv6_address_print_detail_without-paging_no-ipv6' ) interfaces = self.module.Interfaces(module=self.module) - addresses = interfaces.parse_addresses(data=fixture) - result = interfaces.populate_ipv6_interfaces(data=addresses) + addresses = interfaces.parse_detail(data=fixture) + result = interfaces.populate_addresses(data=addresses, family='ipv6') self.assertEqual(result, None)