mirror of https://github.com/ansible/ansible.git
Add new filter to parse xml output for network use cases (#31562)
* Add new filter to parse xml output for network use cases Fixes #31026 * Add parse_xml filter * Add documentation for parse_xml filter * Edited for clarity. * Fix review comment and add unit tests * Fix unit test CI failure * Fix CI issues * Fix unit test failures * Fix review comments * More copy edits.pull/33122/head
parent
50c9f91060
commit
0ddf092ae3
@ -0,0 +1,34 @@
|
|||||||
|
<rpc-reply>
|
||||||
|
<configuration>
|
||||||
|
<vlans>
|
||||||
|
<vlan>
|
||||||
|
<name>test-1</name>
|
||||||
|
<vlan-id>100</vlan-id>
|
||||||
|
</vlan>
|
||||||
|
<vlan>
|
||||||
|
<name>test-2</name>
|
||||||
|
</vlan>
|
||||||
|
<vlan>
|
||||||
|
<name>test-3</name>
|
||||||
|
<vlan-id>300</vlan-id>
|
||||||
|
<description>test vlan-3</description>
|
||||||
|
<interface>
|
||||||
|
<name>em3.0</name>
|
||||||
|
</interface>
|
||||||
|
</vlan>
|
||||||
|
<vlan inactive="inactive">
|
||||||
|
<name>test-4</name>
|
||||||
|
<description>test vlan-4</description>
|
||||||
|
<vlan-id>400</vlan-id>
|
||||||
|
</vlan>
|
||||||
|
<vlan inactive="inactive">
|
||||||
|
<name>test-5</name>
|
||||||
|
<description>test vlan-5</description>
|
||||||
|
<vlan-id>500</vlan-id>
|
||||||
|
<interface>
|
||||||
|
<name>em5.0</name>
|
||||||
|
</interface>
|
||||||
|
</vlan>
|
||||||
|
</vlans>
|
||||||
|
</configuration>
|
||||||
|
</rpc-reply>
|
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
vars:
|
||||||
|
vlan: "{{ item.name }}"
|
||||||
|
|
||||||
|
keys:
|
||||||
|
vlans:
|
||||||
|
type: list
|
||||||
|
value: "{{ vlan }}"
|
||||||
|
top: configuration/vlans/vlan
|
||||||
|
items:
|
||||||
|
name: name
|
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
vars:
|
||||||
|
vlan:
|
||||||
|
vlan_id: "{{ item.vlan_id }}"
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
desc: "{{ item.desc }}"
|
||||||
|
interface: "{{ item.intf }}"
|
||||||
|
enabled: "{{ item.state.get('inactive') != 'inactive' }}"
|
||||||
|
state: "{% if item.state.get('inactive') == 'inactive'%}inactive{% else %}active{% endif %}"
|
||||||
|
|
||||||
|
keys:
|
||||||
|
vlans:
|
||||||
|
type: list
|
||||||
|
value: "{{ vlan }}"
|
||||||
|
top: configuration/vlans/vlan
|
||||||
|
items:
|
||||||
|
vlan_id: vlan-id
|
||||||
|
name: name
|
||||||
|
desc: description
|
||||||
|
intf: interface/name
|
||||||
|
state: ".[@inactive='inactive']"
|
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
vars:
|
||||||
|
vlan:
|
||||||
|
vlan_id: "{{ item.vlan_id }}"
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
desc: "{{ item.desc }}"
|
||||||
|
interface: "{{ item.intf }}"
|
||||||
|
enabled: "{{ item.state.get('inactive') != 'inactive' }}"
|
||||||
|
state: "{% if item.state.get('inactive') == 'inactive'%}inactive{% else %}active{% endif %}"
|
||||||
|
|
||||||
|
keys:
|
||||||
|
vlans:
|
||||||
|
type: list
|
||||||
|
value: "{{ vlan }}"
|
||||||
|
top: configuration/vlans/vlan
|
||||||
|
items:
|
||||||
|
vlan_id: vlan-id
|
||||||
|
name: name
|
||||||
|
desc: description
|
||||||
|
intf: interface/name
|
||||||
|
state: ".[@inactive='inactive']"
|
||||||
|
when: item.name == 'test-5'
|
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
vars:
|
||||||
|
vlan:
|
||||||
|
key: "{{ item.name }}"
|
||||||
|
values:
|
||||||
|
vlan_id: "{{ item.vlan_id }}"
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
desc: "{{ item.desc }}"
|
||||||
|
interface: "{{ item.intf }}"
|
||||||
|
enabled: "{{ item.state.get('inactive') != 'inactive' }}"
|
||||||
|
state: "{% if item.state.get('inactive') == 'inactive'%}inactive{% else %}active{% endif %}"
|
||||||
|
|
||||||
|
keys:
|
||||||
|
vlans:
|
||||||
|
type: list
|
||||||
|
value: "{{ vlan }}"
|
||||||
|
top: configuration/vlans/vlan
|
||||||
|
items:
|
||||||
|
vlan_id: vlan-id
|
||||||
|
name: name
|
||||||
|
desc: description
|
||||||
|
intf: interface/name
|
||||||
|
state: ".[@inactive='inactive']"
|
@ -0,0 +1,80 @@
|
|||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ansible.compat.tests import unittest
|
||||||
|
from ansible.plugins.filter.network import parse_xml
|
||||||
|
|
||||||
|
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'network')
|
||||||
|
|
||||||
|
with open(os.path.join(fixture_path, 'show_vlans_xml_output.txt')) as f:
|
||||||
|
output_xml = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkParseFilter(unittest.TestCase):
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.version_info[:2] == (2, 6), 'XPath expression not supported in this version')
|
||||||
|
def test_parse_xml_to_list_of_dict(self):
|
||||||
|
spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_spec.yml')
|
||||||
|
parsed = parse_xml(output_xml, spec_file_path)
|
||||||
|
expected = {'vlans': [{'name': 'test-1', 'enabled': True, 'state': 'active', 'interface': None, 'vlan_id': 100, 'desc': None},
|
||||||
|
{'name': 'test-2', 'enabled': True, 'state': 'active', 'interface': None, 'vlan_id': None, 'desc': None},
|
||||||
|
{'name': 'test-3', 'enabled': True, 'state': 'active', 'interface': 'em3.0', 'vlan_id': 300, 'desc': 'test vlan-3'},
|
||||||
|
{'name': 'test-4', 'enabled': False, 'state': 'inactive', 'interface': None, 'vlan_id': 400, 'desc': 'test vlan-4'},
|
||||||
|
{'name': 'test-5', 'enabled': False, 'state': 'inactive', 'interface': 'em5.0', 'vlan_id': 500, 'desc': 'test vlan-5'}]}
|
||||||
|
self.assertEqual(parsed, expected)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.version_info[:2] == (2, 6), 'XPath expression not supported in this version')
|
||||||
|
def test_parse_xml_to_dict(self):
|
||||||
|
spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_with_key_spec.yml')
|
||||||
|
parsed = parse_xml(output_xml, spec_file_path)
|
||||||
|
expected = {'vlans': {'test-4': {'name': 'test-4', 'enabled': False, 'state': 'inactive', 'interface': None, 'vlan_id': 400, 'desc': 'test vlan-4'},
|
||||||
|
'test-3': {'name': 'test-3', 'enabled': True, 'state': 'active', 'interface': 'em3.0', 'vlan_id': 300, 'desc': 'test vlan-3'},
|
||||||
|
'test-1': {'name': 'test-1', 'enabled': True, 'state': 'active', 'interface': None, 'vlan_id': 100, 'desc': None},
|
||||||
|
'test-5': {'name': 'test-5', 'enabled': False, 'state': 'inactive', 'interface': 'em5.0', 'vlan_id': 500, 'desc': 'test vlan-5'},
|
||||||
|
'test-2': {'name': 'test-2', 'enabled': True, 'state': 'active', 'interface': None, 'vlan_id': None, 'desc': None}}
|
||||||
|
}
|
||||||
|
self.assertEqual(parsed, expected)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.version_info[:2] == (2, 6), 'XPath expression not supported in this version')
|
||||||
|
def test_parse_xml_with_condition_spec(self):
|
||||||
|
spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_with_condition_spec.yml')
|
||||||
|
parsed = parse_xml(output_xml, spec_file_path)
|
||||||
|
expected = {'vlans': [{'name': 'test-5', 'enabled': False, 'state': 'inactive', 'interface': 'em5.0', 'vlan_id': 500, 'desc': 'test vlan-5'}]}
|
||||||
|
self.assertEqual(parsed, expected)
|
||||||
|
|
||||||
|
def test_parse_xml_with_single_value_spec(self):
|
||||||
|
spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_single_value_spec.yml')
|
||||||
|
parsed = parse_xml(output_xml, spec_file_path)
|
||||||
|
expected = {'vlans': ['test-1', 'test-2', 'test-3', 'test-4', 'test-5']}
|
||||||
|
self.assertEqual(parsed, expected)
|
||||||
|
|
||||||
|
def test_parse_xml_validate_input(self):
|
||||||
|
spec_file_path = os.path.join(fixture_path, 'show_vlans_xml_spec.yml')
|
||||||
|
output = 10
|
||||||
|
|
||||||
|
with self.assertRaises(Exception) as e:
|
||||||
|
parse_xml(output_xml, 'junk_path')
|
||||||
|
self.assertEqual("unable to locate parse_cli template: junk_path", str(e.exception))
|
||||||
|
|
||||||
|
with self.assertRaises(Exception) as e:
|
||||||
|
parse_xml(output, spec_file_path)
|
||||||
|
self.assertEqual("parse_xml works on string input, but given input of : %s" % type(output), str(e.exception))
|
Loading…
Reference in New Issue