From e68671e1ca4f364d03abf02826a00b7ae3c696af Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 24 Mar 2021 15:17:01 +0530 Subject: [PATCH 01/31] Add filter plugin for xml_to_json --- plugins/filter/xml_to_json.py | 306 ++++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 plugins/filter/xml_to_json.py diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/xml_to_json.py new file mode 100644 index 00000000..9dd61c29 --- /dev/null +++ b/plugins/filter/xml_to_json.py @@ -0,0 +1,306 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +""" +The index_of filter plugin +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: xml_to_json + author: Ashwini Mhatre (@amhatre) + version_added: "1.0.0" + short_description: convert given xml string to json + description: + - This plugin converts the xml string to json. + - Using the parameters below- C(data|ansible.utils.xml_to_json) + options: + data: + description: + - The input xml string . + - This option represents the xml value that is passed to the filter plugin in pipe format. + - For example C(config_data|ansible.utils.xml_to_json), in this case C(config_data) represents this option. + type: str + required: True + notes: +""" + +EXAMPLES = r""" + +#### Simple examples + +- name: Define xml data + ansible.builtin.set_fact: + xml: + - 1 + - 2 + - 3 + +- name: Find the index of 2 + ansible.builtin.set_fact: + indices: "{{ data|ansible.utils.index_of('eq', 2) }}" + +# TASK [Find the index of 2] ************************************************* +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: '1' + + +- name: Find the index of 2, ensure list is returned + ansible.builtin.set_fact: + indices: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" + +# TASK [Find the index of 2, ensure list is returned] ************************ +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: +# - 1 + + +- name: Find the index of 3 using the long format + ansible.builtin.set_fact: + indices: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" + vars: + value: 3 + +# TASK [Find the index of 3 using the long format] *************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: +# - 2 + + +- name: Find numbers greater than 1, using loop + debug: + msg: "{{ data[item] }} is {{ test }} than {{ value }}" + loop: "{{ data|ansible.utils.index_of(test, value) }}" + vars: + test: '>' + value: 1 + +# TASK [Find numbers great than 1, using loop] ******************************* +# ok: [sw01] => (item=1) => +# msg: 2 is > than 1 +# ok: [sw01] => (item=2) => +# msg: 3 is > than 1 + + +#### Working with lists of dictionaries + +- name: Define a list with hostname and type + ansible.builtin.set_fact: + data: + - name: sw01.example.lan + type: switch + - name: rtr01.example.lan + type: router + - name: fw01.example.corp + type: firewall + - name: fw02.example.corp + type: firewall + +- name: Find the index of all firewalls using the type key + ansible.builtin.set_fact: + firewalls: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" + +# TASK [Find the index of all firewalls using the type key] ****************** +# ok: [nxos101] => changed=false +# ansible_facts: +# firewalls: +# - 2 +# - 3 + +- name: Find the index of all firewalls, use in a loop + debug: + msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." + loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" + vars: + device_type: firewall + +# TASK [Find the index of all firewalls, use in a loop, as a filter] ********* +# ok: [nxos101] => (item=2) => +# msg: The type of firewall at index 2 has name fw01.example.corp. +# ok: [nxos101] => (item=3) => +# msg: The type of firewall at index 3 has name fw02.example.corp. + +- name: Find the index of all devices with a .corp name + debug: + msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" + loop: "{{ data|ansible.utils.index_of('regex', expression, 'name') }}" + vars: + expression: '\.corp$' # ends with .corp + +# TASK [Find the index of all devices with a .corp name] ********************* +# ok: [nxos101] => (item=2) => +# msg: The device named fw01.example.corp is a firewall +# ok: [nxos101] => (item=3) => +# msg: The device named fw02.example.corp is a firewall + + +#### Working with complex structures from resource modules + +- name: Retrieve the current L3 interface configuration + cisco.nxos.nxos_l3_interfaces: + state: gathered + register: current_l3 + +# TASK [Retrieve the current L3 interface configuration] ********************* +# ok: [sw01] => changed=false +# gathered: +# - name: Ethernet1/1 +# - name: Ethernet1/2 +# <...> +# - name: Ethernet1/128 +# - ipv4: +# - address: 192.168.101.14/24 +# name: mgmt0 + +- name: Find the indices interfaces with a 192.168.101.xx ip address + ansible.builtin.set_fact: + found: "{{ found + entry }}" + with_indexed_items: "{{ current_l3.gathered }}" + vars: + found: [] + ip: '192.168.101.' + address: "{{ item.1.ipv4|d([])|ansible.utils.index_of('search', ip, 'address', wantlist=True) }}" + entry: + - interface_idx: "{{ item.0 }}" + address_idxs: "{{ address }}" + when: address + +# TASK [debug] *************************************************************** +# ok: [sw01] => +# found: +# - address_idxs: +# - 0 +# interface_idx: '128' + +- name: Show all interfaces and their address + debug: + msg: "{{ interface.name }} has ip {{ address }}" + loop: "{{ found|subelements('address_idxs') }}" + vars: + interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" + address: "{{ interface.ipv4[item.1].address }}" + +# TASK [Show all interfaces and their address] ******************************* +# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => +# msg: mgmt0 has ip 192.168.101.14/24 + + +#### Working with deeply nested data + +- name: Define interface configuration facts + ansible.builtin.set_fact: + data: + interfaces: + interface: + - config: + description: configured by Ansible - 1 + enabled: True + loopback-mode: False + mtu: 1024 + name: loopback0000 + type: eth + name: loopback0000 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 1 + enabled: True + index: 5 + index: 5 + - config: + description: subinterface configured by Ansible - 2 + enabled: False + index: 2 + index: 2 + - config: + description: configured by Ansible - 2 + enabled: False + loopback-mode: False + mtu: 2048 + name: loopback1111 + type: virt + name: loopback1111 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 3 + enabled: True + index: 10 + index: 10 + - config: + description: subinterface configured by Ansible - 4 + enabled: False + index: 3 + index: 3 + + +- name: Find the description of loopback111, subinterface index 10 + debug: + msg: |- + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface[subint_idx|int] + .config.description }} + vars: + # the values to search for + int_name: loopback1111 + sub_index: 10 + # retrieve the index in each nested list + int_idx: | + {{ data.interfaces.interface| + ansible.utils.index_of('eq', int_name, 'name') }} + subint_idx: | + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface| + ansible.utils.index_of('eq', sub_index, 'index') }} + +# TASK [Find the description of loopback111, subinterface index 10] ************ +# ok: [sw01] => +# msg: subinterface configured by Ansible - 3 + +""" + +from ansible.errors import AnsibleFilterError +from jinja2.filters import environmentfilter +from ansible_collections.ansible.utils.plugins.module_utils.common.xml_to_json import ( + xml_to_json, +) +from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( + AnsibleArgSpecValidator, +) + + +@environmentfilter +def _xml_to_json(*args, **kwargs): + """Convert the given data from xml to json.""" + + keys = [ + "environment", + "data", + ] + data = dict(zip(keys, args)) + data.update(kwargs) + environment = data.pop("environment") + aav = AnsibleArgSpecValidator( + data=data, schema=DOCUMENTATION, name="xml_to_json" + ) + valid, errors, updated_data = aav.validate() + if not valid: + raise AnsibleFilterError(errors) + updated_data["tests"] = environment.tests + return xml_to_json(**updated_data) + + +class FilterModule(object): + """ xml_to_json """ + + def filters(self): + """a mapping of filter names to functions""" + return {"xml_to_json": _xml_to_json} \ No newline at end of file From 64826c1a45cdfdc685c68cd657abed8b17ec3143 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Thu, 25 Mar 2021 21:59:35 +0530 Subject: [PATCH 02/31] Add xml_to_json and json_to_xml filter plugin --- plugins/filter/json_to_xml.py | 303 +++++++++++++++++++++ plugins/filter/xml_to_json.py | 2 +- plugins/module_utils/common/json_to_xml.py | 123 +++++++++ plugins/module_utils/common/xml_to_json.py | 120 ++++++++ 4 files changed, 547 insertions(+), 1 deletion(-) create mode 100644 plugins/filter/json_to_xml.py create mode 100644 plugins/module_utils/common/json_to_xml.py create mode 100644 plugins/module_utils/common/xml_to_json.py diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py new file mode 100644 index 00000000..d75e6df9 --- /dev/null +++ b/plugins/filter/json_to_xml.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +""" +The index_of filter plugin +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: json_to_xml + author: Ashwini Mhatre (@amhatre) + version_added: "1.0.0" + short_description: convert given json string to xml + description: + - This plugin converts the xml string to json. + - Using the parameters below- C(data|ansible.utils.json_to_xml) + options: + data: + description: + - The input json string . + - This option represents the json value that is passed to the filter plugin in pipe format. + - For example C(config_data|ansible.utils.json_to_xml), in this case C(config_data) represents this option. + type: str + required: True +""" + +EXAMPLES = r""" + +#### Simple examples + +- name: Define xml data + ansible.builtin.set_fact: + xml: + + +- name: Find the index of 2 + ansible.builtin.set_fact: + indices: "{{ data|ansible.utils.index_of('eq', 2) }}" + +# TASK [Find the index of 2] ************************************************* +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: '1' + + +- name: Find the index of 2, ensure list is returned + ansible.builtin.set_fact: + indices: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" + +# TASK [Find the index of 2, ensure list is returned] ************************ +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: +# - 1 + + +- name: Find the index of 3 using the long format + ansible.builtin.set_fact: + indices: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" + vars: + value: 3 + +# TASK [Find the index of 3 using the long format] *************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: +# - 2 + + +- name: Find numbers greater than 1, using loop + debug: + msg: "{{ data[item] }} is {{ test }} than {{ value }}" + loop: "{{ data|ansible.utils.index_of(test, value) }}" + vars: + test: '>' + value: 1 + +# TASK [Find numbers great than 1, using loop] ******************************* +# ok: [sw01] => (item=1) => +# msg: 2 is > than 1 +# ok: [sw01] => (item=2) => +# msg: 3 is > than 1 + + +#### Working with lists of dictionaries + +- name: Define a list with hostname and type + ansible.builtin.set_fact: + data: + - name: sw01.example.lan + type: switch + - name: rtr01.example.lan + type: router + - name: fw01.example.corp + type: firewall + - name: fw02.example.corp + type: firewall + +- name: Find the index of all firewalls using the type key + ansible.builtin.set_fact: + firewalls: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" + +# TASK [Find the index of all firewalls using the type key] ****************** +# ok: [nxos101] => changed=false +# ansible_facts: +# firewalls: +# - 2 +# - 3 + +- name: Find the index of all firewalls, use in a loop + debug: + msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." + loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" + vars: + device_type: firewall + +# TASK [Find the index of all firewalls, use in a loop, as a filter] ********* +# ok: [nxos101] => (item=2) => +# msg: The type of firewall at index 2 has name fw01.example.corp. +# ok: [nxos101] => (item=3) => +# msg: The type of firewall at index 3 has name fw02.example.corp. + +- name: Find the index of all devices with a .corp name + debug: + msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" + loop: "{{ data|ansible.utils.index_of('regex', expression, 'name') }}" + vars: + expression: '\.corp$' # ends with .corp + +# TASK [Find the index of all devices with a .corp name] ********************* +# ok: [nxos101] => (item=2) => +# msg: The device named fw01.example.corp is a firewall +# ok: [nxos101] => (item=3) => +# msg: The device named fw02.example.corp is a firewall + + +#### Working with complex structures from resource modules + +- name: Retrieve the current L3 interface configuration + cisco.nxos.nxos_l3_interfaces: + state: gathered + register: current_l3 + +# TASK [Retrieve the current L3 interface configuration] ********************* +# ok: [sw01] => changed=false +# gathered: +# - name: Ethernet1/1 +# - name: Ethernet1/2 +# <...> +# - name: Ethernet1/128 +# - ipv4: +# - address: 192.168.101.14/24 +# name: mgmt0 + +- name: Find the indices interfaces with a 192.168.101.xx ip address + ansible.builtin.set_fact: + found: "{{ found + entry }}" + with_indexed_items: "{{ current_l3.gathered }}" + vars: + found: [] + ip: '192.168.101.' + address: "{{ item.1.ipv4|d([])|ansible.utils.index_of('search', ip, 'address', wantlist=True) }}" + entry: + - interface_idx: "{{ item.0 }}" + address_idxs: "{{ address }}" + when: address + +# TASK [debug] *************************************************************** +# ok: [sw01] => +# found: +# - address_idxs: +# - 0 +# interface_idx: '128' + +- name: Show all interfaces and their address + debug: + msg: "{{ interface.name }} has ip {{ address }}" + loop: "{{ found|subelements('address_idxs') }}" + vars: + interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" + address: "{{ interface.ipv4[item.1].address }}" + +# TASK [Show all interfaces and their address] ******************************* +# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => +# msg: mgmt0 has ip 192.168.101.14/24 + + +#### Working with deeply nested data + +- name: Define interface configuration facts + ansible.builtin.set_fact: + data: + interfaces: + interface: + - config: + description: configured by Ansible - 1 + enabled: True + loopback-mode: False + mtu: 1024 + name: loopback0000 + type: eth + name: loopback0000 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 1 + enabled: True + index: 5 + index: 5 + - config: + description: subinterface configured by Ansible - 2 + enabled: False + index: 2 + index: 2 + - config: + description: configured by Ansible - 2 + enabled: False + loopback-mode: False + mtu: 2048 + name: loopback1111 + type: virt + name: loopback1111 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 3 + enabled: True + index: 10 + index: 10 + - config: + description: subinterface configured by Ansible - 4 + enabled: False + index: 3 + index: 3 + + +- name: Find the description of loopback111, subinterface index 10 + debug: + msg: |- + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface[subint_idx|int] + .config.description }} + vars: + # the values to search for + int_name: loopback1111 + sub_index: 10 + # retrieve the index in each nested list + int_idx: | + {{ data.interfaces.interface| + ansible.utils.index_of('eq', int_name, 'name') }} + subint_idx: | + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface| + ansible.utils.index_of('eq', sub_index, 'index') }} + +# TASK [Find the description of loopback111, subinterface index 10] ************ +# ok: [sw01] => +# msg: subinterface configured by Ansible - 3 + +""" + +from ansible.errors import AnsibleFilterError +from jinja2.filters import environmentfilter +from ansible_collections.ansible.utils.plugins.module_utils.common.json_to_xml import ( + json_to_xml, +) +from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( + AnsibleArgSpecValidator, +) + + +@environmentfilter +def _json_to_xml(*args, **kwargs): + """Convert the given data from xml to json.""" + + keys = [ + "environment", + "data", + ] + data = dict(zip(keys, args)) + data.update(kwargs) + environment = data.pop("environment") + aav = AnsibleArgSpecValidator( + data=data, schema=DOCUMENTATION, name="json_to_xml" + ) + valid, errors, updated_data = aav.validate() + if not valid: + raise AnsibleFilterError(errors) + updated_data["tests"] = environment.tests + return json_to_xml(**updated_data) + + +class FilterModule(object): + """ json_to_xml """ + + def filters(self): + """a mapping of filter names to functions""" + return {"json_to_xml": _json_to_xml} \ No newline at end of file diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/xml_to_json.py index 9dd61c29..f8e79a9e 100644 --- a/plugins/filter/xml_to_json.py +++ b/plugins/filter/xml_to_json.py @@ -27,7 +27,6 @@ - For example C(config_data|ansible.utils.xml_to_json), in this case C(config_data) represents this option. type: str required: True - notes: """ EXAMPLES = r""" @@ -302,5 +301,6 @@ class FilterModule(object): """ xml_to_json """ def filters(self): + """a mapping of filter names to functions""" return {"xml_to_json": _xml_to_json} \ No newline at end of file diff --git a/plugins/module_utils/common/json_to_xml.py b/plugins/module_utils/common/json_to_xml.py new file mode 100644 index 00000000..6ac56a85 --- /dev/null +++ b/plugins/module_utils/common/json_to_xml.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +""" +The index_of plugin common code +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import xmltodict +import ast +from ansible.module_utils.six import string_types, integer_types +from ansible.module_utils._text import to_native + +# Note, this file can only be used on the control node +# where ansible is installed +# limit imports to filter and lookup plugins +try: + from ansible.errors import AnsibleError +except ImportError: + pass + + +def _raise_error(msg): + """Raise an error message, prepend with filter name + + :param msg: The message + :type msg: str + :raises: AnsibleError + """ + error = "Error when using plugin 'json_to_xml': {msg}".format(msg=msg) + raise AnsibleError(error) + + + + + +def _run_test(entry, test, right, tests): + """Run a test + + :param test: The test to run + :type test: a lambda from the qual_map + :param entry: The x for the lambda + :type entry: str int or bool + :param right: The y for the lamba + :type right: str int bool or list + :return: If the test passed + :rtype: book + """ + msg = ( + "Error encountered when testing value " + "'{entry}' (type={entry_type}) against " + "'{right}' (type={right_type}) with '{test}'. " + ).format( + entry=entry, + entry_type=type(_to_well_known_type(entry)).__name__, + right=right, + right_type=type(_to_well_known_type(entry)).__name__, + test=test, + ) + + if test.startswith("!"): + invert = True + test = test.lstrip("!") + if test == "=": + test = "==" + elif test.startswith("not "): + invert = True + test = test.lstrip("not ") + else: + invert = False + + if not isinstance(right, list) and test == "in": + right = [right] + + j2_test = tests.get(test) + if not j2_test: + msg = "{msg} Error was: the test '{test}' was not found.".format( + msg=msg, test=test + ) + _raise_error(msg) + else: + try: + if right is None: + result = j2_test(entry) + else: + result = j2_test(entry, right) + except Exception as exc: + msg = "{msg} Error was: {error}".format( + msg=msg, error=to_native(exc) + ) + _raise_error(msg) + + if invert: + result = not result + return result + +def validate_json(data): + """ + validate input xml + """ + return data + +def json_to_xml( + data, + tests=None, +): + """Convert data which is in json to xml" + + :param data: The data passed in (data|json_to_xml(...)) + :type data: xml + """ + valid_xml = validate_json(data) + if valid_xml: + data = ast.literal_eval(data) + res = xmltodict.unparse(data, pretty=True) + else: + _raise_error("Input Xml is not valid") + return res diff --git a/plugins/module_utils/common/xml_to_json.py b/plugins/module_utils/common/xml_to_json.py new file mode 100644 index 00000000..e0fe29fb --- /dev/null +++ b/plugins/module_utils/common/xml_to_json.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +""" +The index_of plugin common code +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import xmltodict +import json +from ansible.module_utils.six import string_types, integer_types +from ansible.module_utils._text import to_native + +# Note, this file can only be used on the control node +# where ansible is installed +# limit imports to filter and lookup plugins +try: + from ansible.errors import AnsibleError +except ImportError: + pass + + +def _raise_error(msg): + """Raise an error message, prepend with filter name + :param msg: The message + :type msg: str + :raises: AnsibleError + """ + error = "Error when using plugin 'xml_to_json': {msg}".format(msg=msg) + raise AnsibleError(error) + + + + + +def _run_test(entry, test, right, tests): + """Run a test + + :param test: The test to run + :type test: a lambda from the qual_map + :param entry: The x for the lambda + :type entry: str int or bool + :param right: The y for the lamba + :type right: str int bool or list + :return: If the test passed + :rtype: book + """ + msg = ( + "Error encountered when testing value " + "'{entry}' (type={entry_type}) against " + "'{right}' (type={right_type}) with '{test}'. " + ).format( + entry=entry, + entry_type=type(_to_well_known_type(entry)).__name__, + right=right, + right_type=type(_to_well_known_type(entry)).__name__, + test=test, + ) + + if test.startswith("!"): + invert = True + test = test.lstrip("!") + if test == "=": + test = "==" + elif test.startswith("not "): + invert = True + test = test.lstrip("not ") + else: + invert = False + + if not isinstance(right, list) and test == "in": + right = [right] + + j2_test = tests.get(test) + if not j2_test: + msg = "{msg} Error was: the test '{test}' was not found.".format( + msg=msg, test=test + ) + _raise_error(msg) + else: + try: + if right is None: + result = j2_test(entry) + else: + result = j2_test(entry, right) + except Exception as exc: + msg = "{msg} Error was: {error}".format( + msg=msg, error=to_native(exc) + ) + _raise_error(msg) + + if invert: + result = not result + return result + +def validate_xml(data): + """ + validate input xml + """ + return data + +def xml_to_json( + data, + tests=None, +): + """Convert data which is in xml to json" + :param data: The data passed in (data|xml_to_json(...)) + :type data: xml + """ + valid_xml = validate_xml(data) + if valid_xml: + res = json.dumps(xmltodict.parse(data)) + else: + _raise_error("Input Xml is not valid") + return res From f7d3a816e769922f817ac7143808d15d9a8365e2 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 15:30:58 +0530 Subject: [PATCH 03/31] add valiadte input function --- plugins/filter/json_to_xml.py | 258 +++----------------- plugins/filter/xml_to_json.py | 264 +++------------------ plugins/module_utils/common/json_to_xml.py | 15 +- plugins/module_utils/common/utils.py | 124 ++++++++++ plugins/module_utils/common/xml_to_json.py | 15 +- 5 files changed, 200 insertions(+), 476 deletions(-) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index d75e6df9..430b9615 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -33,234 +33,40 @@ #### Simple examples -- name: Define xml data - ansible.builtin.set_fact: - xml: - +- name: Define json data + ansible.builtin.set_fact: + data: { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } + - debug: + msg: "{{ data|ansible.utils.json_to_xml }}" + +TASK [Define json data ] ************************************************************************* +task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 +ok: [localhost] => { + "ansible_facts": { + "data": { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } + }, + "changed": false +} + +TASK [debug] *********************************************************************************************************** +task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 +Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +ok: [localhost] => { + "msg": "\n\n\t\n" +} -- name: Find the index of 2 - ansible.builtin.set_fact: - indices: "{{ data|ansible.utils.index_of('eq', 2) }}" -# TASK [Find the index of 2] ************************************************* -# ok: [nxos101] => changed=false -# ansible_facts: -# indices: '1' - - -- name: Find the index of 2, ensure list is returned - ansible.builtin.set_fact: - indices: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" - -# TASK [Find the index of 2, ensure list is returned] ************************ -# ok: [nxos101] => changed=false -# ansible_facts: -# indices: -# - 1 - - -- name: Find the index of 3 using the long format - ansible.builtin.set_fact: - indices: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" - vars: - value: 3 - -# TASK [Find the index of 3 using the long format] *************************** -# ok: [nxos101] => changed=false -# ansible_facts: -# indices: -# - 2 - - -- name: Find numbers greater than 1, using loop - debug: - msg: "{{ data[item] }} is {{ test }} than {{ value }}" - loop: "{{ data|ansible.utils.index_of(test, value) }}" - vars: - test: '>' - value: 1 - -# TASK [Find numbers great than 1, using loop] ******************************* -# ok: [sw01] => (item=1) => -# msg: 2 is > than 1 -# ok: [sw01] => (item=2) => -# msg: 3 is > than 1 - - -#### Working with lists of dictionaries - -- name: Define a list with hostname and type - ansible.builtin.set_fact: - data: - - name: sw01.example.lan - type: switch - - name: rtr01.example.lan - type: router - - name: fw01.example.corp - type: firewall - - name: fw02.example.corp - type: firewall - -- name: Find the index of all firewalls using the type key - ansible.builtin.set_fact: - firewalls: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" - -# TASK [Find the index of all firewalls using the type key] ****************** -# ok: [nxos101] => changed=false -# ansible_facts: -# firewalls: -# - 2 -# - 3 - -- name: Find the index of all firewalls, use in a loop - debug: - msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." - loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" - vars: - device_type: firewall - -# TASK [Find the index of all firewalls, use in a loop, as a filter] ********* -# ok: [nxos101] => (item=2) => -# msg: The type of firewall at index 2 has name fw01.example.corp. -# ok: [nxos101] => (item=3) => -# msg: The type of firewall at index 3 has name fw02.example.corp. - -- name: Find the index of all devices with a .corp name - debug: - msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" - loop: "{{ data|ansible.utils.index_of('regex', expression, 'name') }}" - vars: - expression: '\.corp$' # ends with .corp - -# TASK [Find the index of all devices with a .corp name] ********************* -# ok: [nxos101] => (item=2) => -# msg: The device named fw01.example.corp is a firewall -# ok: [nxos101] => (item=3) => -# msg: The device named fw02.example.corp is a firewall - - -#### Working with complex structures from resource modules - -- name: Retrieve the current L3 interface configuration - cisco.nxos.nxos_l3_interfaces: - state: gathered - register: current_l3 - -# TASK [Retrieve the current L3 interface configuration] ********************* -# ok: [sw01] => changed=false -# gathered: -# - name: Ethernet1/1 -# - name: Ethernet1/2 -# <...> -# - name: Ethernet1/128 -# - ipv4: -# - address: 192.168.101.14/24 -# name: mgmt0 - -- name: Find the indices interfaces with a 192.168.101.xx ip address - ansible.builtin.set_fact: - found: "{{ found + entry }}" - with_indexed_items: "{{ current_l3.gathered }}" - vars: - found: [] - ip: '192.168.101.' - address: "{{ item.1.ipv4|d([])|ansible.utils.index_of('search', ip, 'address', wantlist=True) }}" - entry: - - interface_idx: "{{ item.0 }}" - address_idxs: "{{ address }}" - when: address - -# TASK [debug] *************************************************************** -# ok: [sw01] => -# found: -# - address_idxs: -# - 0 -# interface_idx: '128' - -- name: Show all interfaces and their address - debug: - msg: "{{ interface.name }} has ip {{ address }}" - loop: "{{ found|subelements('address_idxs') }}" - vars: - interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" - address: "{{ interface.ipv4[item.1].address }}" - -# TASK [Show all interfaces and their address] ******************************* -# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => -# msg: mgmt0 has ip 192.168.101.14/24 - - -#### Working with deeply nested data - -- name: Define interface configuration facts - ansible.builtin.set_fact: - data: - interfaces: - interface: - - config: - description: configured by Ansible - 1 - enabled: True - loopback-mode: False - mtu: 1024 - name: loopback0000 - type: eth - name: loopback0000 - subinterfaces: - subinterface: - - config: - description: subinterface configured by Ansible - 1 - enabled: True - index: 5 - index: 5 - - config: - description: subinterface configured by Ansible - 2 - enabled: False - index: 2 - index: 2 - - config: - description: configured by Ansible - 2 - enabled: False - loopback-mode: False - mtu: 2048 - name: loopback1111 - type: virt - name: loopback1111 - subinterfaces: - subinterface: - - config: - description: subinterface configured by Ansible - 3 - enabled: True - index: 10 - index: 10 - - config: - description: subinterface configured by Ansible - 4 - enabled: False - index: 3 - index: 3 - - -- name: Find the description of loopback111, subinterface index 10 - debug: - msg: |- - {{ data.interfaces.interface[int_idx|int] - .subinterfaces.subinterface[subint_idx|int] - .config.description }} - vars: - # the values to search for - int_name: loopback1111 - sub_index: 10 - # retrieve the index in each nested list - int_idx: | - {{ data.interfaces.interface| - ansible.utils.index_of('eq', int_name, 'name') }} - subint_idx: | - {{ data.interfaces.interface[int_idx|int] - .subinterfaces.subinterface| - ansible.utils.index_of('eq', sub_index, 'index') }} - -# TASK [Find the description of loopback111, subinterface index 10] ************ -# ok: [sw01] => -# msg: subinterface configured by Ansible - 3 """ diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/xml_to_json.py index f8e79a9e..ee0df536 100644 --- a/plugins/filter/xml_to_json.py +++ b/plugins/filter/xml_to_json.py @@ -33,237 +33,39 @@ #### Simple examples -- name: Define xml data - ansible.builtin.set_fact: - xml: - - 1 - - 2 - - 3 - -- name: Find the index of 2 - ansible.builtin.set_fact: - indices: "{{ data|ansible.utils.index_of('eq', 2) }}" - -# TASK [Find the index of 2] ************************************************* -# ok: [nxos101] => changed=false -# ansible_facts: -# indices: '1' - - -- name: Find the index of 2, ensure list is returned - ansible.builtin.set_fact: - indices: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" - -# TASK [Find the index of 2, ensure list is returned] ************************ -# ok: [nxos101] => changed=false -# ansible_facts: -# indices: -# - 1 - - -- name: Find the index of 3 using the long format - ansible.builtin.set_fact: - indices: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" - vars: - value: 3 - -# TASK [Find the index of 3 using the long format] *************************** -# ok: [nxos101] => changed=false -# ansible_facts: -# indices: -# - 2 - - -- name: Find numbers greater than 1, using loop - debug: - msg: "{{ data[item] }} is {{ test }} than {{ value }}" - loop: "{{ data|ansible.utils.index_of(test, value) }}" - vars: - test: '>' - value: 1 - -# TASK [Find numbers great than 1, using loop] ******************************* -# ok: [sw01] => (item=1) => -# msg: 2 is > than 1 -# ok: [sw01] => (item=2) => -# msg: 3 is > than 1 - - -#### Working with lists of dictionaries - -- name: Define a list with hostname and type - ansible.builtin.set_fact: - data: - - name: sw01.example.lan - type: switch - - name: rtr01.example.lan - type: router - - name: fw01.example.corp - type: firewall - - name: fw02.example.corp - type: firewall - -- name: Find the index of all firewalls using the type key - ansible.builtin.set_fact: - firewalls: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" - -# TASK [Find the index of all firewalls using the type key] ****************** -# ok: [nxos101] => changed=false -# ansible_facts: -# firewalls: -# - 2 -# - 3 - -- name: Find the index of all firewalls, use in a loop - debug: - msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." - loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" - vars: - device_type: firewall - -# TASK [Find the index of all firewalls, use in a loop, as a filter] ********* -# ok: [nxos101] => (item=2) => -# msg: The type of firewall at index 2 has name fw01.example.corp. -# ok: [nxos101] => (item=3) => -# msg: The type of firewall at index 3 has name fw02.example.corp. - -- name: Find the index of all devices with a .corp name - debug: - msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" - loop: "{{ data|ansible.utils.index_of('regex', expression, 'name') }}" - vars: - expression: '\.corp$' # ends with .corp - -# TASK [Find the index of all devices with a .corp name] ********************* -# ok: [nxos101] => (item=2) => -# msg: The device named fw01.example.corp is a firewall -# ok: [nxos101] => (item=3) => -# msg: The device named fw02.example.corp is a firewall - - -#### Working with complex structures from resource modules - -- name: Retrieve the current L3 interface configuration - cisco.nxos.nxos_l3_interfaces: - state: gathered - register: current_l3 - -# TASK [Retrieve the current L3 interface configuration] ********************* -# ok: [sw01] => changed=false -# gathered: -# - name: Ethernet1/1 -# - name: Ethernet1/2 -# <...> -# - name: Ethernet1/128 -# - ipv4: -# - address: 192.168.101.14/24 -# name: mgmt0 - -- name: Find the indices interfaces with a 192.168.101.xx ip address - ansible.builtin.set_fact: - found: "{{ found + entry }}" - with_indexed_items: "{{ current_l3.gathered }}" - vars: - found: [] - ip: '192.168.101.' - address: "{{ item.1.ipv4|d([])|ansible.utils.index_of('search', ip, 'address', wantlist=True) }}" - entry: - - interface_idx: "{{ item.0 }}" - address_idxs: "{{ address }}" - when: address - -# TASK [debug] *************************************************************** -# ok: [sw01] => -# found: -# - address_idxs: -# - 0 -# interface_idx: '128' - -- name: Show all interfaces and their address - debug: - msg: "{{ interface.name }} has ip {{ address }}" - loop: "{{ found|subelements('address_idxs') }}" - vars: - interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" - address: "{{ interface.ipv4[item.1].address }}" - -# TASK [Show all interfaces and their address] ******************************* -# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => -# msg: mgmt0 has ip 192.168.101.14/24 - - -#### Working with deeply nested data - -- name: Define interface configuration facts - ansible.builtin.set_fact: - data: - interfaces: - interface: - - config: - description: configured by Ansible - 1 - enabled: True - loopback-mode: False - mtu: 1024 - name: loopback0000 - type: eth - name: loopback0000 - subinterfaces: - subinterface: - - config: - description: subinterface configured by Ansible - 1 - enabled: True - index: 5 - index: 5 - - config: - description: subinterface configured by Ansible - 2 - enabled: False - index: 2 - index: 2 - - config: - description: configured by Ansible - 2 - enabled: False - loopback-mode: False - mtu: 2048 - name: loopback1111 - type: virt - name: loopback1111 - subinterfaces: - subinterface: - - config: - description: subinterface configured by Ansible - 3 - enabled: True - index: 10 - index: 10 - - config: - description: subinterface configured by Ansible - 4 - enabled: False - index: 3 - index: 3 - - -- name: Find the description of loopback111, subinterface index 10 - debug: - msg: |- - {{ data.interfaces.interface[int_idx|int] - .subinterfaces.subinterface[subint_idx|int] - .config.description }} - vars: - # the values to search for - int_name: loopback1111 - sub_index: 10 - # retrieve the index in each nested list - int_idx: | - {{ data.interfaces.interface| - ansible.utils.index_of('eq', int_name, 'name') }} - subint_idx: | - {{ data.interfaces.interface[int_idx|int] - .subinterfaces.subinterface| - ansible.utils.index_of('eq', sub_index, 'index') }} - -# TASK [Find the description of loopback111, subinterface index 10] ************ -# ok: [sw01] => -# msg: subinterface configured by Ansible - 3 - +tasks: + - name: convert given xml to json + ansible.builtin.set_fact: + data: " + + " + + - debug: + msg: "{{ data|ansible.utils.xml_to_json }}" + +##TASK###### +TASK [convert given xml to json] ***************************************************************************************************** +task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 +ok: [localhost] => { + "ansible_facts": { + "data": " " + }, + "changed": false +} + +TASK [debug] ************************************************************************************************************************* +task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 +Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +ok: [localhost] => { + "msg": { + "netconf-state": { + "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", + "schemas": { + "schema": null + } + } + } +} """ from ansible.errors import AnsibleFilterError diff --git a/plugins/module_utils/common/json_to_xml.py b/plugins/module_utils/common/json_to_xml.py index 6ac56a85..ba7acb72 100644 --- a/plugins/module_utils/common/json_to_xml.py +++ b/plugins/module_utils/common/json_to_xml.py @@ -13,8 +13,10 @@ import xmltodict import ast -from ansible.module_utils.six import string_types, integer_types from ansible.module_utils._text import to_native +from ansible_collections.ansible.utils.plugins.module_utils.common.utils import ( + validate_data, +) # Note, this file can only be used on the control node # where ansible is installed @@ -99,11 +101,6 @@ def _run_test(entry, test, right, tests): result = not result return result -def validate_json(data): - """ - validate input xml - """ - return data def json_to_xml( data, @@ -114,10 +111,10 @@ def json_to_xml( :param data: The data passed in (data|json_to_xml(...)) :type data: xml """ - valid_xml = validate_json(data) - if valid_xml: + filter_type = validate_data(data, 'json') + if filter_type == 'json': data = ast.literal_eval(data) res = xmltodict.unparse(data, pretty=True) else: - _raise_error("Input Xml is not valid") + _raise_error("Input json is not valid") return res diff --git a/plugins/module_utils/common/utils.py b/plugins/module_utils/common/utils.py index 681bf778..6f596183 100644 --- a/plugins/module_utils/common/utils.py +++ b/plugins/module_utils/common/utils.py @@ -7,10 +7,37 @@ __metaclass__ = type +import sys +import json from copy import deepcopy from ansible.module_utils.common._collections_compat import Mapping from ansible.module_utils.six import iteritems +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_native + +try: + HAS_LXML = True + from lxml.etree import fromstring, XMLSyntaxError + from lxml import etree + +except ImportError: + HAS_LXML = False + from xml.etree.ElementTree import fromstring + + if sys.version_info < (2, 7): + from xml.parsers.expat import ExpatError as XMLSyntaxError + else: + from xml.etree.ElementTree import ParseError as XMLSyntaxError + + +try: + import xmltodict + + HAS_XMLTODICT = True +except ImportError: + HAS_XMLTODICT = False def sort_list(val): @@ -110,3 +137,100 @@ def to_list(val): return [val] else: return list() + +def validate_data(data, fmt=None): + """ + This function validates the data for given format (fmt). + If the fmt is None it tires to guess the data format. + Currently support data format checks are + 1) xml + 2) json + :param data: The data which should be validated and normalised. + :param fmt: This is an optional argument which indicated the format + of the data. Valid values are "xml" and "json". If the value + is None the format of the data will be guessed and returned in the output. + :return: + * If the format identified is XML it returns the data format type + which is "xml" in this case. + + * If the format identified is JSON it returns data format type + which is "json" in this case. + + """ + if data is None: + return None, None + + if isinstance(data, string_types): + data = data.strip() + if (data.startswith("<") and data.endswith(">")) or fmt == "xml": + try: + result = fromstring(data) + if fmt and fmt != "xml": + raise Exception( + "Invalid format '%s'. Expected format is 'xml' for data '%s'" + % (fmt, data) + ) + return "xml" + except XMLSyntaxError as exc: + if fmt == "xml": + raise Exception( + "'%s' XML validation failed with error '%s'" + % ( + data, + to_native(exc, errors="surrogate_then_replace"), + ) + ) + pass + except Exception as exc: + error = "'%s' recognized as XML but was not valid." % data + raise Exception( + error + to_native(exc, errors="surrogate_then_replace") + ) + elif (data.startswith("{") and data.endswith("}")) or fmt == "json": + try: + if fmt and fmt != "json": + raise Exception( + "Invalid format '%s'. Expected format is 'json' for data '%s'" + % (fmt, data) + ) + return "json" + except ( + TypeError, + getattr(json.decoder, "JSONDecodeError", ValueError), + ) as exc: + if fmt == "json": + raise Exception( + "'%s' JSON validation failed with error '%s'" + % ( + data, + to_native(exc, errors="surrogate_then_replace"), + ) + ) + except Exception as exc: + error = "'%s' recognized as JSON but was not valid." % data + raise Exception( + error + to_native(exc, errors="surrogate_then_replace") + ) + elif isinstance(data, dict): + if fmt and fmt != "json": + raise Exception( + "Invalid format '%s'. Expected format is 'json' for data '%s'" + % (fmt, data) + ) + + try: + result = json.loads(json.dumps(data)) + return "json" + except ( + TypeError, + getattr(json.decoder, "JSONDecodeError", ValueError), + ) as exc: + raise Exception( + "'%s' JSON validation failed with error '%s'" + % (data, to_native(exc, errors="surrogate_then_replace")) + ) + except Exception as exc: + error = "'%s' recognized as JSON but was not valid." % data + raise Exception( + error + to_native(exc, errors="surrogate_then_replace") + ) diff --git a/plugins/module_utils/common/xml_to_json.py b/plugins/module_utils/common/xml_to_json.py index e0fe29fb..1b9ecc34 100644 --- a/plugins/module_utils/common/xml_to_json.py +++ b/plugins/module_utils/common/xml_to_json.py @@ -13,9 +13,10 @@ import xmltodict import json -from ansible.module_utils.six import string_types, integer_types from ansible.module_utils._text import to_native - +from ansible_collections.ansible.utils.plugins.module_utils.common.utils import ( + validate_data, +) # Note, this file can only be used on the control node # where ansible is installed # limit imports to filter and lookup plugins @@ -98,12 +99,6 @@ def _run_test(entry, test, right, tests): result = not result return result -def validate_xml(data): - """ - validate input xml - """ - return data - def xml_to_json( data, tests=None, @@ -112,8 +107,8 @@ def xml_to_json( :param data: The data passed in (data|xml_to_json(...)) :type data: xml """ - valid_xml = validate_xml(data) - if valid_xml: + filter_type = validate_data(data, 'xml') + if filter_type == 'xml': res = json.dumps(xmltodict.parse(data)) else: _raise_error("Input Xml is not valid") From 7863d4e6ac80dcd549733dc6ea11a45935a42707 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 15:48:46 +0530 Subject: [PATCH 04/31] Add changelog fragments --- changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml diff --git a/changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml b/changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml new file mode 100644 index 00000000..0139428a --- /dev/null +++ b/changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Add Xml_to_json and json_to_xml fiter plugin (https://github.com/ansible-collections/ansible.utils/pull/56). From a702805081773f70d781fe249fdc9ea3b1f391fd Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 16:00:23 +0530 Subject: [PATCH 05/31] Remove unwanted code --- plugins/filter/json_to_xml.py | 1 - plugins/filter/xml_to_json.py | 1 - plugins/module_utils/common/json_to_xml.py | 69 +--------------------- plugins/module_utils/common/xml_to_json.py | 68 +-------------------- 4 files changed, 2 insertions(+), 137 deletions(-) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index 430b9615..a2e88d6f 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -97,7 +97,6 @@ def _json_to_xml(*args, **kwargs): valid, errors, updated_data = aav.validate() if not valid: raise AnsibleFilterError(errors) - updated_data["tests"] = environment.tests return json_to_xml(**updated_data) diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/xml_to_json.py index ee0df536..6c6ae639 100644 --- a/plugins/filter/xml_to_json.py +++ b/plugins/filter/xml_to_json.py @@ -95,7 +95,6 @@ def _xml_to_json(*args, **kwargs): valid, errors, updated_data = aav.validate() if not valid: raise AnsibleFilterError(errors) - updated_data["tests"] = environment.tests return xml_to_json(**updated_data) diff --git a/plugins/module_utils/common/json_to_xml.py b/plugins/module_utils/common/json_to_xml.py index ba7acb72..65b718de 100644 --- a/plugins/module_utils/common/json_to_xml.py +++ b/plugins/module_utils/common/json_to_xml.py @@ -13,7 +13,6 @@ import xmltodict import ast -from ansible.module_utils._text import to_native from ansible_collections.ansible.utils.plugins.module_utils.common.utils import ( validate_data, ) @@ -37,74 +36,8 @@ def _raise_error(msg): error = "Error when using plugin 'json_to_xml': {msg}".format(msg=msg) raise AnsibleError(error) - - - - -def _run_test(entry, test, right, tests): - """Run a test - - :param test: The test to run - :type test: a lambda from the qual_map - :param entry: The x for the lambda - :type entry: str int or bool - :param right: The y for the lamba - :type right: str int bool or list - :return: If the test passed - :rtype: book - """ - msg = ( - "Error encountered when testing value " - "'{entry}' (type={entry_type}) against " - "'{right}' (type={right_type}) with '{test}'. " - ).format( - entry=entry, - entry_type=type(_to_well_known_type(entry)).__name__, - right=right, - right_type=type(_to_well_known_type(entry)).__name__, - test=test, - ) - - if test.startswith("!"): - invert = True - test = test.lstrip("!") - if test == "=": - test = "==" - elif test.startswith("not "): - invert = True - test = test.lstrip("not ") - else: - invert = False - - if not isinstance(right, list) and test == "in": - right = [right] - - j2_test = tests.get(test) - if not j2_test: - msg = "{msg} Error was: the test '{test}' was not found.".format( - msg=msg, test=test - ) - _raise_error(msg) - else: - try: - if right is None: - result = j2_test(entry) - else: - result = j2_test(entry, right) - except Exception as exc: - msg = "{msg} Error was: {error}".format( - msg=msg, error=to_native(exc) - ) - _raise_error(msg) - - if invert: - result = not result - return result - - def json_to_xml( - data, - tests=None, + data ): """Convert data which is in json to xml" diff --git a/plugins/module_utils/common/xml_to_json.py b/plugins/module_utils/common/xml_to_json.py index 1b9ecc34..abbdf32a 100644 --- a/plugins/module_utils/common/xml_to_json.py +++ b/plugins/module_utils/common/xml_to_json.py @@ -13,7 +13,6 @@ import xmltodict import json -from ansible.module_utils._text import to_native from ansible_collections.ansible.utils.plugins.module_utils.common.utils import ( validate_data, ) @@ -35,73 +34,8 @@ def _raise_error(msg): error = "Error when using plugin 'xml_to_json': {msg}".format(msg=msg) raise AnsibleError(error) - - - - -def _run_test(entry, test, right, tests): - """Run a test - - :param test: The test to run - :type test: a lambda from the qual_map - :param entry: The x for the lambda - :type entry: str int or bool - :param right: The y for the lamba - :type right: str int bool or list - :return: If the test passed - :rtype: book - """ - msg = ( - "Error encountered when testing value " - "'{entry}' (type={entry_type}) against " - "'{right}' (type={right_type}) with '{test}'. " - ).format( - entry=entry, - entry_type=type(_to_well_known_type(entry)).__name__, - right=right, - right_type=type(_to_well_known_type(entry)).__name__, - test=test, - ) - - if test.startswith("!"): - invert = True - test = test.lstrip("!") - if test == "=": - test = "==" - elif test.startswith("not "): - invert = True - test = test.lstrip("not ") - else: - invert = False - - if not isinstance(right, list) and test == "in": - right = [right] - - j2_test = tests.get(test) - if not j2_test: - msg = "{msg} Error was: the test '{test}' was not found.".format( - msg=msg, test=test - ) - _raise_error(msg) - else: - try: - if right is None: - result = j2_test(entry) - else: - result = j2_test(entry, right) - except Exception as exc: - msg = "{msg} Error was: {error}".format( - msg=msg, error=to_native(exc) - ) - _raise_error(msg) - - if invert: - result = not result - return result - def xml_to_json( - data, - tests=None, + data ): """Convert data which is in xml to json" :param data: The data passed in (data|xml_to_json(...)) From 43b87efbcde25f9de99b7904ccfb511bfbb07f51 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 16:19:36 +0530 Subject: [PATCH 06/31] Fix tox linters --- plugins/filter/json_to_xml.py | 2 +- plugins/filter/xml_to_json.py | 2 +- plugins/module_utils/common/json_to_xml.py | 9 ++++----- plugins/module_utils/common/xml_to_json.py | 10 +++++----- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index a2e88d6f..0ff05b93 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -105,4 +105,4 @@ class FilterModule(object): def filters(self): """a mapping of filter names to functions""" - return {"json_to_xml": _json_to_xml} \ No newline at end of file + return {"json_to_xml": _json_to_xml} diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/xml_to_json.py index 6c6ae639..9b6eb1e6 100644 --- a/plugins/filter/xml_to_json.py +++ b/plugins/filter/xml_to_json.py @@ -104,4 +104,4 @@ class FilterModule(object): def filters(self): """a mapping of filter names to functions""" - return {"xml_to_json": _xml_to_json} \ No newline at end of file + return {"xml_to_json": _xml_to_json} diff --git a/plugins/module_utils/common/json_to_xml.py b/plugins/module_utils/common/json_to_xml.py index 65b718de..a006f23b 100644 --- a/plugins/module_utils/common/json_to_xml.py +++ b/plugins/module_utils/common/json_to_xml.py @@ -36,16 +36,15 @@ def _raise_error(msg): error = "Error when using plugin 'json_to_xml': {msg}".format(msg=msg) raise AnsibleError(error) -def json_to_xml( - data -): + +def json_to_xml(data): """Convert data which is in json to xml" :param data: The data passed in (data|json_to_xml(...)) :type data: xml """ - filter_type = validate_data(data, 'json') - if filter_type == 'json': + filter_type = validate_data(data, "json") + if filter_type == "json": data = ast.literal_eval(data) res = xmltodict.unparse(data, pretty=True) else: diff --git a/plugins/module_utils/common/xml_to_json.py b/plugins/module_utils/common/xml_to_json.py index abbdf32a..d28028fe 100644 --- a/plugins/module_utils/common/xml_to_json.py +++ b/plugins/module_utils/common/xml_to_json.py @@ -16,6 +16,7 @@ from ansible_collections.ansible.utils.plugins.module_utils.common.utils import ( validate_data, ) + # Note, this file can only be used on the control node # where ansible is installed # limit imports to filter and lookup plugins @@ -34,15 +35,14 @@ def _raise_error(msg): error = "Error when using plugin 'xml_to_json': {msg}".format(msg=msg) raise AnsibleError(error) -def xml_to_json( - data -): + +def xml_to_json(data): """Convert data which is in xml to json" :param data: The data passed in (data|xml_to_json(...)) :type data: xml """ - filter_type = validate_data(data, 'xml') - if filter_type == 'xml': + filter_type = validate_data(data, "xml") + if filter_type == "xml": res = json.dumps(xmltodict.parse(data)) else: _raise_error("Input Xml is not valid") From 5e0d600cbf3a9242f8a7ddf4ab729fa45a8def27 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 16:36:50 +0530 Subject: [PATCH 07/31] Add toxlinters --- plugins/module_utils/common/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/module_utils/common/utils.py b/plugins/module_utils/common/utils.py index 6f596183..1ecfca6c 100644 --- a/plugins/module_utils/common/utils.py +++ b/plugins/module_utils/common/utils.py @@ -138,6 +138,7 @@ def to_list(val): else: return list() + def validate_data(data, fmt=None): """ This function validates the data for given format (fmt). From f4825b79f3a586cfbaf12b070bd4271a85997bb9 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 20:23:12 +0530 Subject: [PATCH 08/31] Address review comments --- plugins/filter/json_to_xml.py | 17 +-- plugins/filter/xml_to_json.py | 18 +-- plugins/module_utils/common/utils.py | 127 +----------------- .../common => plugin_utils}/json_to_xml.py | 25 ++-- .../common => plugin_utils}/xml_to_json.py | 24 ++-- 5 files changed, 32 insertions(+), 179 deletions(-) rename plugins/{module_utils/common => plugin_utils}/json_to_xml.py (64%) rename plugins/{module_utils/common => plugin_utils}/xml_to_json.py (63%) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index 0ff05b93..9a662874 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -1,11 +1,13 @@ +# # -*- coding: utf-8 -*- -# Copyright 2020 Red Hat +# Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# """ -The index_of filter plugin +The json_to_xml filter plugin """ from __future__ import absolute_import, division, print_function @@ -14,7 +16,7 @@ DOCUMENTATION = """ name: json_to_xml author: Ashwini Mhatre (@amhatre) - version_added: "1.0.0" + version_added: "2.0.2" short_description: convert given json string to xml description: - This plugin converts the xml string to json. @@ -72,7 +74,7 @@ from ansible.errors import AnsibleFilterError from jinja2.filters import environmentfilter -from ansible_collections.ansible.utils.plugins.module_utils.common.json_to_xml import ( +from ansible_collections.ansible.utils.plugins.plugin_utils.json_to_xml import ( json_to_xml, ) from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( @@ -84,13 +86,8 @@ def _json_to_xml(*args, **kwargs): """Convert the given data from xml to json.""" - keys = [ - "environment", - "data", - ] - data = dict(zip(keys, args)) + data = {"data": args[1]} data.update(kwargs) - environment = data.pop("environment") aav = AnsibleArgSpecValidator( data=data, schema=DOCUMENTATION, name="json_to_xml" ) diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/xml_to_json.py index 9b6eb1e6..c8dd58e6 100644 --- a/plugins/filter/xml_to_json.py +++ b/plugins/filter/xml_to_json.py @@ -1,11 +1,12 @@ +# # -*- coding: utf-8 -*- -# Copyright 2020 Red Hat +# Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - +# """ -The index_of filter plugin +The xml_to_json filter plugin """ from __future__ import absolute_import, division, print_function @@ -14,7 +15,7 @@ DOCUMENTATION = """ name: xml_to_json author: Ashwini Mhatre (@amhatre) - version_added: "1.0.0" + version_added: "2.0.2" short_description: convert given xml string to json description: - This plugin converts the xml string to json. @@ -70,7 +71,7 @@ from ansible.errors import AnsibleFilterError from jinja2.filters import environmentfilter -from ansible_collections.ansible.utils.plugins.module_utils.common.xml_to_json import ( +from ansible_collections.ansible.utils.plugins.plugin_utils.xml_to_json import ( xml_to_json, ) from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( @@ -82,13 +83,8 @@ def _xml_to_json(*args, **kwargs): """Convert the given data from xml to json.""" - keys = [ - "environment", - "data", - ] - data = dict(zip(keys, args)) + data = {"data": args[1]} data.update(kwargs) - environment = data.pop("environment") aav = AnsibleArgSpecValidator( data=data, schema=DOCUMENTATION, name="xml_to_json" ) diff --git a/plugins/module_utils/common/utils.py b/plugins/module_utils/common/utils.py index 1ecfca6c..565895e0 100644 --- a/plugins/module_utils/common/utils.py +++ b/plugins/module_utils/common/utils.py @@ -7,37 +7,10 @@ __metaclass__ = type -import sys -import json from copy import deepcopy from ansible.module_utils.common._collections_compat import Mapping from ansible.module_utils.six import iteritems -from ansible.module_utils.basic import missing_required_lib -from ansible.module_utils.six import string_types -from ansible.module_utils._text import to_native - -try: - HAS_LXML = True - from lxml.etree import fromstring, XMLSyntaxError - from lxml import etree - -except ImportError: - HAS_LXML = False - from xml.etree.ElementTree import fromstring - - if sys.version_info < (2, 7): - from xml.parsers.expat import ExpatError as XMLSyntaxError - else: - from xml.etree.ElementTree import ParseError as XMLSyntaxError - - -try: - import xmltodict - - HAS_XMLTODICT = True -except ImportError: - HAS_XMLTODICT = False def sort_list(val): @@ -136,102 +109,4 @@ def to_list(val): elif val is not None: return [val] else: - return list() - - -def validate_data(data, fmt=None): - """ - This function validates the data for given format (fmt). - If the fmt is None it tires to guess the data format. - Currently support data format checks are - 1) xml - 2) json - :param data: The data which should be validated and normalised. - :param fmt: This is an optional argument which indicated the format - of the data. Valid values are "xml" and "json". If the value - is None the format of the data will be guessed and returned in the output. - :return: - * If the format identified is XML it returns the data format type - which is "xml" in this case. - - * If the format identified is JSON it returns data format type - which is "json" in this case. - - """ - if data is None: - return None, None - - if isinstance(data, string_types): - data = data.strip() - if (data.startswith("<") and data.endswith(">")) or fmt == "xml": - try: - result = fromstring(data) - if fmt and fmt != "xml": - raise Exception( - "Invalid format '%s'. Expected format is 'xml' for data '%s'" - % (fmt, data) - ) - return "xml" - except XMLSyntaxError as exc: - if fmt == "xml": - raise Exception( - "'%s' XML validation failed with error '%s'" - % ( - data, - to_native(exc, errors="surrogate_then_replace"), - ) - ) - pass - except Exception as exc: - error = "'%s' recognized as XML but was not valid." % data - raise Exception( - error + to_native(exc, errors="surrogate_then_replace") - ) - elif (data.startswith("{") and data.endswith("}")) or fmt == "json": - try: - if fmt and fmt != "json": - raise Exception( - "Invalid format '%s'. Expected format is 'json' for data '%s'" - % (fmt, data) - ) - return "json" - except ( - TypeError, - getattr(json.decoder, "JSONDecodeError", ValueError), - ) as exc: - if fmt == "json": - raise Exception( - "'%s' JSON validation failed with error '%s'" - % ( - data, - to_native(exc, errors="surrogate_then_replace"), - ) - ) - except Exception as exc: - error = "'%s' recognized as JSON but was not valid." % data - raise Exception( - error + to_native(exc, errors="surrogate_then_replace") - ) - elif isinstance(data, dict): - if fmt and fmt != "json": - raise Exception( - "Invalid format '%s'. Expected format is 'json' for data '%s'" - % (fmt, data) - ) - - try: - result = json.loads(json.dumps(data)) - return "json" - except ( - TypeError, - getattr(json.decoder, "JSONDecodeError", ValueError), - ) as exc: - raise Exception( - "'%s' JSON validation failed with error '%s'" - % (data, to_native(exc, errors="surrogate_then_replace")) - ) - except Exception as exc: - error = "'%s' recognized as JSON but was not valid." % data - raise Exception( - error + to_native(exc, errors="surrogate_then_replace") - ) + return list() \ No newline at end of file diff --git a/plugins/module_utils/common/json_to_xml.py b/plugins/plugin_utils/json_to_xml.py similarity index 64% rename from plugins/module_utils/common/json_to_xml.py rename to plugins/plugin_utils/json_to_xml.py index a006f23b..ede96df6 100644 --- a/plugins/module_utils/common/json_to_xml.py +++ b/plugins/plugin_utils/json_to_xml.py @@ -1,11 +1,12 @@ +# # -*- coding: utf-8 -*- -# Copyright 2020 Red Hat +# Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - +# """ -The index_of plugin common code +The json_to_xml plugin """ from __future__ import absolute_import, division, print_function @@ -13,17 +14,8 @@ import xmltodict import ast -from ansible_collections.ansible.utils.plugins.module_utils.common.utils import ( - validate_data, -) +from ansible.errors import AnsibleError -# Note, this file can only be used on the control node -# where ansible is installed -# limit imports to filter and lookup plugins -try: - from ansible.errors import AnsibleError -except ImportError: - pass def _raise_error(msg): @@ -43,10 +35,11 @@ def json_to_xml(data): :param data: The data passed in (data|json_to_xml(...)) :type data: xml """ - filter_type = validate_data(data, "json") - if filter_type == "json": + + try: data = ast.literal_eval(data) + import epdb;epdb.serve() res = xmltodict.unparse(data, pretty=True) - else: + except Exception: _raise_error("Input json is not valid") return res diff --git a/plugins/module_utils/common/xml_to_json.py b/plugins/plugin_utils/xml_to_json.py similarity index 63% rename from plugins/module_utils/common/xml_to_json.py rename to plugins/plugin_utils/xml_to_json.py index d28028fe..7c199d29 100644 --- a/plugins/module_utils/common/xml_to_json.py +++ b/plugins/plugin_utils/xml_to_json.py @@ -1,11 +1,12 @@ +# # -*- coding: utf-8 -*- -# Copyright 2020 Red Hat +# Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - +# """ -The index_of plugin common code +The xml_to_json plugin code """ from __future__ import absolute_import, division, print_function @@ -13,17 +14,8 @@ import xmltodict import json -from ansible_collections.ansible.utils.plugins.module_utils.common.utils import ( - validate_data, -) +from ansible.errors import AnsibleError -# Note, this file can only be used on the control node -# where ansible is installed -# limit imports to filter and lookup plugins -try: - from ansible.errors import AnsibleError -except ImportError: - pass def _raise_error(msg): @@ -41,9 +33,9 @@ def xml_to_json(data): :param data: The data passed in (data|xml_to_json(...)) :type data: xml """ - filter_type = validate_data(data, "xml") - if filter_type == "xml": + + try: res = json.dumps(xmltodict.parse(data)) - else: + except Exception: _raise_error("Input Xml is not valid") return res From 1ca534dd83985f4b2d1030170e3dcb7c8a7e7ebf Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 20:45:43 +0530 Subject: [PATCH 09/31] fix linter --- plugins/filter/json_to_xml.py | 2 +- plugins/module_utils/common/utils.py | 2 +- plugins/plugin_utils/json_to_xml.py | 2 -- plugins/plugin_utils/xml_to_json.py | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index 9a662874..a9f9b6fa 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -35,7 +35,7 @@ #### Simple examples -- name: Define json data +- name: Define json data ansible.builtin.set_fact: data: { "interface-configurations": { diff --git a/plugins/module_utils/common/utils.py b/plugins/module_utils/common/utils.py index 565895e0..681bf778 100644 --- a/plugins/module_utils/common/utils.py +++ b/plugins/module_utils/common/utils.py @@ -109,4 +109,4 @@ def to_list(val): elif val is not None: return [val] else: - return list() \ No newline at end of file + return list() diff --git a/plugins/plugin_utils/json_to_xml.py b/plugins/plugin_utils/json_to_xml.py index ede96df6..73ebaa49 100644 --- a/plugins/plugin_utils/json_to_xml.py +++ b/plugins/plugin_utils/json_to_xml.py @@ -17,7 +17,6 @@ from ansible.errors import AnsibleError - def _raise_error(msg): """Raise an error message, prepend with filter name @@ -38,7 +37,6 @@ def json_to_xml(data): try: data = ast.literal_eval(data) - import epdb;epdb.serve() res = xmltodict.unparse(data, pretty=True) except Exception: _raise_error("Input json is not valid") diff --git a/plugins/plugin_utils/xml_to_json.py b/plugins/plugin_utils/xml_to_json.py index 7c199d29..6acd1f3c 100644 --- a/plugins/plugin_utils/xml_to_json.py +++ b/plugins/plugin_utils/xml_to_json.py @@ -17,7 +17,6 @@ from ansible.errors import AnsibleError - def _raise_error(msg): """Raise an error message, prepend with filter name :param msg: The message From e722126939385aac6e277c6824678d7d9376b748 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 20:47:24 +0530 Subject: [PATCH 10/31] Address review comments --- plugins/filter/json_to_xml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index a9f9b6fa..2ab34e3c 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -19,7 +19,7 @@ version_added: "2.0.2" short_description: convert given json string to xml description: - - This plugin converts the xml string to json. + - This plugin converts the json string to xml. - Using the parameters below- C(data|ansible.utils.json_to_xml) options: data: From 604c113d6307e0a5bf211055d4fc52f8c36e38fd Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Fri, 26 Mar 2021 22:19:08 +0530 Subject: [PATCH 11/31] Address review comments --- plugins/filter/json_to_xml.py | 47 +++++++++++++++++++++++++++-- plugins/filter/xml_to_json.py | 46 ++++++++++++++++++++++++++-- plugins/plugin_utils/json_to_xml.py | 20 ++++++------ plugins/plugin_utils/xml_to_json.py | 18 ++++++----- 4 files changed, 109 insertions(+), 22 deletions(-) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index 2ab34e3c..e9ec2f8f 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -29,11 +29,16 @@ - For example C(config_data|ansible.utils.json_to_xml), in this case C(config_data) represents this option. type: str required: True + engine: + description: + - Conversion library to use within the filter plugin. + type: str + default: xmltodict """ EXAMPLES = r""" -#### Simple examples +#### Simple examples with out any engine. plugin will use default value as xmltodict - name: Define json data ansible.builtin.set_fact: @@ -68,7 +73,40 @@ Cisco-IOS-XR-ifmgr-cfg\">\n\t\n" } +#### example2 with engine=xmltodict +- name: Define json data + ansible.builtin.set_fact: + data: { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } + - debug: + msg: "{{ data|ansible.utils.json_to_xml('xmltodict') }}" + +TASK [Define json data ] ************************************************************************* +task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 +ok: [localhost] => { + "ansible_facts": { + "data": { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } + }, + "changed": false +} + +TASK [debug] *********************************************************************************************************** +task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 +Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +ok: [localhost] => { + "msg": "\n\n\t\n" +} """ @@ -84,9 +122,12 @@ @environmentfilter def _json_to_xml(*args, **kwargs): - """Convert the given data from xml to json.""" + """Convert the given data from json to xml.""" + import epdb - data = {"data": args[1]} + epdb.serve() + keys = ["data", "engine"] + data = dict(zip(keys, args[1:])) data.update(kwargs) aav = AnsibleArgSpecValidator( data=data, schema=DOCUMENTATION, name="json_to_xml" diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/xml_to_json.py index c8dd58e6..d0040f45 100644 --- a/plugins/filter/xml_to_json.py +++ b/plugins/filter/xml_to_json.py @@ -28,11 +28,16 @@ - For example C(config_data|ansible.utils.xml_to_json), in this case C(config_data) represents this option. type: str required: True + engine: + description: + - Conversion library to use within the filter plugin. + type: str + default: xmltodict """ EXAMPLES = r""" -#### Simple examples +#### Simple examples with out any engine. plugin will use default value as xmltodict tasks: - name: convert given xml to json @@ -54,6 +59,42 @@ "changed": false } +TASK [debug] ************************************************************************************************************************* +task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 +Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +ok: [localhost] => { + "msg": { + "netconf-state": { + "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", + "schemas": { + "schema": null + } + } + } +} + +#### example2 with engine=xmltodict + +tasks: + - name: convert given xml to json + ansible.builtin.set_fact: + data: " + + " + + - debug: + msg: "{{ data|ansible.utils.xml_to_json('xmltodict') }}" + +##TASK###### +TASK [convert given xml to json] ***************************************************************************************************** +task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 +ok: [localhost] => { + "ansible_facts": { + "data": " " + }, + "changed": false +} + TASK [debug] ************************************************************************************************************************* task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils @@ -83,7 +124,8 @@ def _xml_to_json(*args, **kwargs): """Convert the given data from xml to json.""" - data = {"data": args[1]} + keys = ["data", "engine"] + data = dict(zip(keys, args[1:])) data.update(kwargs) aav = AnsibleArgSpecValidator( data=data, schema=DOCUMENTATION, name="xml_to_json" diff --git a/plugins/plugin_utils/json_to_xml.py b/plugins/plugin_utils/json_to_xml.py index 73ebaa49..785a0425 100644 --- a/plugins/plugin_utils/json_to_xml.py +++ b/plugins/plugin_utils/json_to_xml.py @@ -12,7 +12,6 @@ __metaclass__ = type -import xmltodict import ast from ansible.errors import AnsibleError @@ -28,16 +27,19 @@ def _raise_error(msg): raise AnsibleError(error) -def json_to_xml(data): +def json_to_xml(data, engine): """Convert data which is in json to xml" :param data: The data passed in (data|json_to_xml(...)) :type data: xml + :param engine: Conversion library default=xmltodict """ - - try: - data = ast.literal_eval(data) - res = xmltodict.unparse(data, pretty=True) - except Exception: - _raise_error("Input json is not valid") - return res + if engine == "xmltodict": + import xmltodict + + try: + data = ast.literal_eval(data) + res = xmltodict.unparse(data, pretty=True) + except Exception: + _raise_error("Input json is not valid") + return res diff --git a/plugins/plugin_utils/xml_to_json.py b/plugins/plugin_utils/xml_to_json.py index 6acd1f3c..4cba38e8 100644 --- a/plugins/plugin_utils/xml_to_json.py +++ b/plugins/plugin_utils/xml_to_json.py @@ -12,7 +12,6 @@ __metaclass__ = type -import xmltodict import json from ansible.errors import AnsibleError @@ -27,14 +26,17 @@ def _raise_error(msg): raise AnsibleError(error) -def xml_to_json(data): +def xml_to_json(data, engine): """Convert data which is in xml to json" :param data: The data passed in (data|xml_to_json(...)) :type data: xml + :param engine: Conversion library default=xml_to_dict """ - - try: - res = json.dumps(xmltodict.parse(data)) - except Exception: - _raise_error("Input Xml is not valid") - return res + if engine == "xmltodict": + import xmltodict + + try: + res = json.dumps(xmltodict.parse(data)) + except Exception: + _raise_error("Input Xml is not valid") + return res From 8baebc6c28b3c5ffa16cef0c64a3a1e73e0b88da Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Mon, 29 Mar 2021 12:44:49 +0530 Subject: [PATCH 12/31] fix syntax errors --- plugins/filter/json_to_xml.py | 85 +++++++++++++++++---------------- plugins/filter/xml_to_json.py | 88 +++++++++++++++++------------------ 2 files changed, 86 insertions(+), 87 deletions(-) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index e9ec2f8f..e651df5d 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -41,7 +41,7 @@ #### Simple examples with out any engine. plugin will use default value as xmltodict - name: Define json data - ansible.builtin.set_fact: + ansible.builtin.set_fact: data: { "interface-configurations": { "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", @@ -51,27 +51,27 @@ - debug: msg: "{{ data|ansible.utils.json_to_xml }}" -TASK [Define json data ] ************************************************************************* -task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 -ok: [localhost] => { - "ansible_facts": { - "data": { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } - }, - "changed": false -} - -TASK [debug] *********************************************************************************************************** -task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 -Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils -ok: [localhost] => { - "msg": "\n\n\t\n" -} +# TASK [Define json data ] ************************************************************************* +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 +# ok: [localhost] => { +# "ansible_facts": { +# "data": { +# "interface-configurations": { +# "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", +# "interface-configuration": null +# } +# } +# }, +# "changed": false +# } +# +# TASK [debug] *********************************************************************************************************** +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 +# Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +# ok: [localhost] => { +# "msg": "\n\n\t\n" +# } #### example2 with engine=xmltodict @@ -86,27 +86,26 @@ - debug: msg: "{{ data|ansible.utils.json_to_xml('xmltodict') }}" -TASK [Define json data ] ************************************************************************* -task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 -ok: [localhost] => { - "ansible_facts": { - "data": { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } - }, - "changed": false -} - -TASK [debug] *********************************************************************************************************** -task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 -Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils -ok: [localhost] => { - "msg": "\n\n\t\n" -} +# TASK [Define json data ] ************************************************************************* +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 +# ok: [localhost] => { +# "ansible_facts": { +# "data": { +# "interface-configurations": { +# "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", +# "interface-configuration": null +# } +# } +# }, +# "changed": false +# } +# TASK [debug] *********************************************************************************************************** +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 +# Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +# ok: [localhost] => { +# "msg": "\n\n\t\n" +# } """ diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/xml_to_json.py index d0040f45..43f6693c 100644 --- a/plugins/filter/xml_to_json.py +++ b/plugins/filter/xml_to_json.py @@ -50,28 +50,28 @@ msg: "{{ data|ansible.utils.xml_to_json }}" ##TASK###### -TASK [convert given xml to json] ***************************************************************************************************** -task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 -ok: [localhost] => { - "ansible_facts": { - "data": " " - }, - "changed": false -} - -TASK [debug] ************************************************************************************************************************* -task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 -Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils -ok: [localhost] => { - "msg": { - "netconf-state": { - "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", - "schemas": { - "schema": null - } - } - } -} +# TASK [convert given xml to json] ***************************************************************************************************** +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 +# ok: [localhost] => { +# "ansible_facts": { +# "data": " " +# }, +# "changed": false +# } +# +# TASK [debug] ************************************************************************************************************************* +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 +# Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +# ok: [localhost] => { +# "msg": { +# "netconf-state": { +# "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", +# "schemas": { +# "schema": null +# } +# } +# } +# } #### example2 with engine=xmltodict @@ -86,28 +86,28 @@ msg: "{{ data|ansible.utils.xml_to_json('xmltodict') }}" ##TASK###### -TASK [convert given xml to json] ***************************************************************************************************** -task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 -ok: [localhost] => { - "ansible_facts": { - "data": " " - }, - "changed": false -} - -TASK [debug] ************************************************************************************************************************* -task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 -Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils -ok: [localhost] => { - "msg": { - "netconf-state": { - "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", - "schemas": { - "schema": null - } - } - } -} +# TASK [convert given xml to json] ***************************************************************************************************** +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 +# ok: [localhost] => { +# "ansible_facts": { +# "data": " " +# }, +# "changed": false +# } +# +# TASK [debug] ************************************************************************************************************************* +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 +# Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +# ok: [localhost] => { +# "msg": { +# "netconf-state": { +# "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", +# "schemas": { +# "schema": null +# } +# } +# } +# } """ from ansible.errors import AnsibleFilterError From 4e00e83d9e820b607197f96867966d9241cda578 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Tue, 30 Mar 2021 13:46:08 +0530 Subject: [PATCH 13/31] Add uts for xml_tojson --- tests/unit/plugins/filter/test_xml_to_json.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/unit/plugins/filter/test_xml_to_json.py diff --git a/tests/unit/plugins/filter/test_xml_to_json.py b/tests/unit/plugins/filter/test_xml_to_json.py new file mode 100644 index 00000000..ce44f0e4 --- /dev/null +++ b/tests/unit/plugins/filter/test_xml_to_json.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import unittest + +from ansible.errors import AnsibleFilterError +from ansible_collections.ansible.utils.plugins.filter.xml_to_json import xml_to_json + +DATA = { + "engine": "xmltodict" +} + +VALID_DATA = "" \ + "" + +class TestXmlToJson(unittest.TestCase): + def setUp(self): + pass + + def test_invalid_argspec(self): + """Check passing invalid argspec""" + + # missing required arguments + args = [DATA] + kwargs = {} + with self.assertRaises(AnsibleFilterError) as error: + xml_to_json(*args, **kwargs) + self.assertIn( + "Missing either 'data' or 'criteria' value in filter input, refer 'ansible.utils.xml_to_json' filter", + str(error.exception), + ) + + # missing required arguments + with self.assertRaises(AnsibleFilterError) as error: + xml_to_json([DATA]) + self.assertIn( + "Missing either 'data' or 'engine' ", str(error.exception) + ) + + + def test_valid_data(self): + """Check passing valid data as per criteria""" + + args = [VALID_DATA, "xmltodict"] + result = xml_to_json(*args) + self.assertEqual(result, []) From 055a162174157b0537834dcef3b77675b7005bc2 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Tue, 30 Mar 2021 20:18:02 +0530 Subject: [PATCH 14/31] Add Uts for xml_to_json and json_to_xml --- tests/unit/module_utils/test_json_to_xml.py | 50 +++++++++++++++++++ .../test_xml_to_json.py | 32 +++++------- 2 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 tests/unit/module_utils/test_json_to_xml.py rename tests/unit/{plugins/filter => module_utils}/test_xml_to_json.py (61%) diff --git a/tests/unit/module_utils/test_json_to_xml.py b/tests/unit/module_utils/test_json_to_xml.py new file mode 100644 index 00000000..efe34446 --- /dev/null +++ b/tests/unit/module_utils/test_json_to_xml.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import unittest +from ansible.errors import AnsibleError +from ansible_collections.ansible.utils.plugins.filter.json_to_xml import json_to_xml + +INVALID_DATA = "" + +VALID_DATA = """{ + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + } + }""" + +OUTPUT = """ +""" + + +class TestXmlToJson(unittest.TestCase): + def setUp(self): + pass + + def test_invalid_data(self): + """Check passing invalid argspec""" + + # missing required arguments + args = [INVALID_DATA, "xmltodict"] + kwargs = {} + with self.assertRaises(AnsibleError) as error: + json_to_xml(*args, **kwargs) + print(str(error.exception)) + self.assertIn( + "Error when using plugin 'json_to_xml': Input json is not valid", + str(error.exception), + ) + + def test_valid_data(self): + """Check passing valid data as per criteria""" + self.maxDiff = None + args = [VALID_DATA, "xmltodict"] + result = json_to_xml(*args) + print(result) + self.assertEqual(result, OUTPUT) diff --git a/tests/unit/plugins/filter/test_xml_to_json.py b/tests/unit/module_utils/test_xml_to_json.py similarity index 61% rename from tests/unit/plugins/filter/test_xml_to_json.py rename to tests/unit/module_utils/test_xml_to_json.py index ce44f0e4..fa1fd03d 100644 --- a/tests/unit/plugins/filter/test_xml_to_json.py +++ b/tests/unit/module_utils/test_xml_to_json.py @@ -8,45 +8,39 @@ __metaclass__ = type import unittest - -from ansible.errors import AnsibleFilterError +from ansible.errors import AnsibleError from ansible_collections.ansible.utils.plugins.filter.xml_to_json import xml_to_json -DATA = { - "engine": "xmltodict" -} +INVALID_DATA = "" VALID_DATA = "" \ "" + + class TestXmlToJson(unittest.TestCase): def setUp(self): pass - def test_invalid_argspec(self): + def test_invalid_data(self): """Check passing invalid argspec""" # missing required arguments - args = [DATA] + args = [INVALID_DATA, "xmltodict"] kwargs = {} - with self.assertRaises(AnsibleFilterError) as error: + with self.assertRaises(AnsibleError) as error: xml_to_json(*args, **kwargs) + print(str(error.exception)) self.assertIn( - "Missing either 'data' or 'criteria' value in filter input, refer 'ansible.utils.xml_to_json' filter", + "Error when using plugin 'xml_to_json': Input Xml is not valid", str(error.exception), ) - # missing required arguments - with self.assertRaises(AnsibleFilterError) as error: - xml_to_json([DATA]) - self.assertIn( - "Missing either 'data' or 'engine' ", str(error.exception) - ) - - def test_valid_data(self): """Check passing valid data as per criteria""" - + self.maxDiff = None + output = """{"netconf-state": {"@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", + "schemas": {"schema": null}}}""" args = [VALID_DATA, "xmltodict"] result = xml_to_json(*args) - self.assertEqual(result, []) + self.assertEqual(result, output) From 5e3d79f7ee373417e436ea2060a188becda5df8b Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Tue, 30 Mar 2021 22:27:38 +0530 Subject: [PATCH 15/31] Add integration testcases for xml_to_json and json_to_xml plugin --- plugins/filter/json_to_xml.py | 3 -- .../json_to_xml/tasks/include/simple.yaml | 35 +++++++++++++++++ .../targets/json_to_xml/tasks/main.yaml | 13 +++++++ .../xml_to_json/tasks/include/simple.yaml | 39 +++++++++++++++++++ .../targets/xml_to_json/tasks/main.yaml | 13 +++++++ 5 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 tests/integration/targets/json_to_xml/tasks/include/simple.yaml create mode 100644 tests/integration/targets/json_to_xml/tasks/main.yaml create mode 100644 tests/integration/targets/xml_to_json/tasks/include/simple.yaml create mode 100644 tests/integration/targets/xml_to_json/tasks/main.yaml diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index e651df5d..718425a9 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -122,9 +122,6 @@ @environmentfilter def _json_to_xml(*args, **kwargs): """Convert the given data from json to xml.""" - import epdb - - epdb.serve() keys = ["data", "engine"] data = dict(zip(keys, args[1:])) data.update(kwargs) diff --git a/tests/integration/targets/json_to_xml/tasks/include/simple.yaml b/tests/integration/targets/json_to_xml/tasks/include/simple.yaml new file mode 100644 index 00000000..27963492 --- /dev/null +++ b/tests/integration/targets/json_to_xml/tasks/include/simple.yaml @@ -0,0 +1,35 @@ +--- +- name: Setup xml and expected json + ansible.builtin.set_fact: + data: { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "interface-configuration": null + } + } + output: "\n\n\t\n" + +- debug: + msg: "{{ data|ansible.utils.json_to_xml() }}" + +- name: Integration tests with and without default engine as xmltodict and + assert: + that: "{{ output == item.test }}" + loop: + - test: "{{ data|ansible.utils.json_to_xml() }}" + - test: "{{ data|ansible.utils.json_to_xml('xmltodict') }}" + +- name: Setup invalid xml as input to ansible.utils.xml_to_json. + ansible.builtin.set_fact: + data: "" + +- name: validate input xml + ansible.builtin.set_fact: + _result: "{{ data|ansible.utils.json_to_xml() }}" + ignore_errors: true + register: result + +- assert: + that: "{{ msg in result.msg }}" + vars: + msg: "Error when using plugin 'json_to_xml': Input json is not valid" \ No newline at end of file diff --git a/tests/integration/targets/json_to_xml/tasks/main.yaml b/tests/integration/targets/json_to_xml/tasks/main.yaml new file mode 100644 index 00000000..4274d750 --- /dev/null +++ b/tests/integration/targets/json_to_xml/tasks/main.yaml @@ -0,0 +1,13 @@ +--- +- name: Recursively find all test files + find: + file_type: file + paths: "{{ role_path }}/tasks/include" + recurse: true + use_regex: true + patterns: + - '^(?!_).+$' + register: found + +- include: "{{ item.path }}" + loop: "{{ found.files }}" diff --git a/tests/integration/targets/xml_to_json/tasks/include/simple.yaml b/tests/integration/targets/xml_to_json/tasks/include/simple.yaml new file mode 100644 index 00000000..4ab440ea --- /dev/null +++ b/tests/integration/targets/xml_to_json/tasks/include/simple.yaml @@ -0,0 +1,39 @@ +--- +- name: Setup xml and expected json + ansible.builtin.set_fact: + data: " + + " + output: { + "netconf-state": { + "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", + "schemas": { + "schema": null + } + } + } + +- debug: + msg: "{{ data|ansible.utils.xml_to_json() }}" + +- name: Integration tests with and without default engine as xmltodict and + assert: + that: "{{ output == item.test }}" + loop: + - test: "{{ data|ansible.utils.xml_to_json() }}" + - test: "{{ data|ansible.utils.xml_to_json('xmltodict') }}" + +- name: Setup invalid xml as input to ansible.utils.xml_to_json. + ansible.builtin.set_fact: + data: "" + +- name: validate input xml + ansible.builtin.set_fact: + _result: "{{ data|ansible.utils.xml_to_json() }}" + ignore_errors: true + register: result + +- assert: + that: "{{ msg in result.msg }}" + vars: + msg: "Error when using plugin 'xml_to_json': Input Xml is not valid" diff --git a/tests/integration/targets/xml_to_json/tasks/main.yaml b/tests/integration/targets/xml_to_json/tasks/main.yaml new file mode 100644 index 00000000..4274d750 --- /dev/null +++ b/tests/integration/targets/xml_to_json/tasks/main.yaml @@ -0,0 +1,13 @@ +--- +- name: Recursively find all test files + find: + file_type: file + paths: "{{ role_path }}/tasks/include" + recurse: true + use_regex: true + patterns: + - '^(?!_).+$' + register: found + +- include: "{{ item.path }}" + loop: "{{ found.files }}" From a68ea8927f37659656737c28d09c9299588a3cce Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 12:32:18 +0530 Subject: [PATCH 16/31] fix linters --- tests/unit/module_utils/test_json_to_xml.py | 6 +- tests/unit/module_utils/test_xml_to_json.py | 13 ++- tests/unit/plugins/action/test_cli_parse.py | 100 +++++++------------- 3 files changed, 48 insertions(+), 71 deletions(-) diff --git a/tests/unit/module_utils/test_json_to_xml.py b/tests/unit/module_utils/test_json_to_xml.py index efe34446..3ab12679 100644 --- a/tests/unit/module_utils/test_json_to_xml.py +++ b/tests/unit/module_utils/test_json_to_xml.py @@ -9,9 +9,11 @@ import unittest from ansible.errors import AnsibleError -from ansible_collections.ansible.utils.plugins.filter.json_to_xml import json_to_xml +from ansible_collections.ansible.utils.plugins.filter.json_to_xml import ( + json_to_xml, +) -INVALID_DATA = "" +INVALID_DATA = '' VALID_DATA = """{ "interface-configurations": { diff --git a/tests/unit/module_utils/test_xml_to_json.py b/tests/unit/module_utils/test_xml_to_json.py index fa1fd03d..c61b9d2a 100644 --- a/tests/unit/module_utils/test_xml_to_json.py +++ b/tests/unit/module_utils/test_xml_to_json.py @@ -9,13 +9,16 @@ import unittest from ansible.errors import AnsibleError -from ansible_collections.ansible.utils.plugins.filter.xml_to_json import xml_to_json +from ansible_collections.ansible.utils.plugins.filter.xml_to_json import ( + xml_to_json, +) -INVALID_DATA = "" - -VALID_DATA = "" \ - "" +INVALID_DATA = '' +VALID_DATA = ( + '' + "" +) class TestXmlToJson(unittest.TestCase): diff --git a/tests/unit/plugins/action/test_cli_parse.py b/tests/unit/plugins/action/test_cli_parse.py index eb5771f0..16c6cc37 100644 --- a/tests/unit/plugins/action/test_cli_parse.py +++ b/tests/unit/plugins/action/test_cli_parse.py @@ -58,7 +58,7 @@ def setUp(self): @staticmethod def _load_fixture(filename): - """ Load a fixture from the filesystem + """Load a fixture from the filesystem :param filename: The name of the file to load :type filename: str @@ -72,23 +72,20 @@ def _load_fixture(filename): return fhand.read() def test_fn_debug(self): - """ Confirm debug doesn't fail and return None - """ + """Confirm debug doesn't fail and return None""" msg = "some message" result = self._plugin._debug(msg) self.assertEqual(result, None) def test_fn_ail_json(self): - """ Confirm fail json replaces basic.py in msg - """ + """Confirm fail json replaces basic.py in msg""" msg = "text (basic.py)" with self.assertRaises(Exception) as error: self._plugin._fail_json(msg) self.assertEqual("text cli_parse", str(error.exception)) def test_fn_check_argspec_pass(self): - """ Confirm a valid argspec passes - """ + """Confirm a valid argspec passes""" kwargs = { "text": "text", "parser": { @@ -102,8 +99,7 @@ def test_fn_check_argspec_pass(self): self.assertEqual(valid, True) def test_fn_check_argspec_fail_no_test_or_command(self): - """ Confirm failed argpsec w/o text or command - """ + """Confirm failed argpsec w/o text or command""" kwargs = { "parser": { "name": "ansible.utils.textfsm", @@ -122,8 +118,7 @@ def test_fn_check_argspec_fail_no_test_or_command(self): ) def test_fn_check_argspec_fail_no_parser_name(self): - """ Confirm failed argspec no parser name - """ + """Confirm failed argspec no parser name""" kwargs = {"text": "anything", "parser": {"command": "show version"}} valid, result, updated_params = check_argspec( DOCUMENTATION, @@ -137,8 +132,7 @@ def test_fn_check_argspec_fail_no_parser_name(self): ) def test_fn_extended_check_argspec_parser_name_not_coll(self): - """ Confirm failed argpsec parser not collection format - """ + """Confirm failed argpsec parser not collection format""" self._plugin._task.args = { "text": "anything", "parser": { @@ -151,7 +145,7 @@ def test_fn_extended_check_argspec_parser_name_not_coll(self): self.assertIn("including collection", self._plugin._result["msg"]) def test_fn_extended_check_argspec_missing_tpath_or_command(self): - """ Confirm failed argpsec missing template_path + """Confirm failed argpsec missing template_path or command when text provided """ self._plugin._task.args = { @@ -165,8 +159,7 @@ def test_fn_extended_check_argspec_missing_tpath_or_command(self): ) def test_fn_load_parser_pass(self): - """ Confirm each each of the parsers loads from the filesystem - """ + """Confirm each each of the parsers loads from the filesystem""" parser_names = ["json", "textfsm", "ttp", "xml"] for parser_name in parser_names: self._plugin._task.args = { @@ -179,8 +172,7 @@ def test_fn_load_parser_pass(self): self.assertTrue(callable(parser.parse)) def test_fn_load_parser_fail(self): - """ Confirm missing parser fails gracefully - """ + """Confirm missing parser fails gracefully""" self._plugin._task.args = { "text": "anything", "parser": {"name": "a.b.c"}, @@ -191,7 +183,7 @@ def test_fn_load_parser_fail(self): self.assertIn("No module named", self._plugin._result["msg"]) def test_fn_set_parser_command_missing(self): - """ Confirm parser/command is set if missing + """Confirm parser/command is set if missing and command provided """ self._plugin._task.args = { @@ -204,8 +196,7 @@ def test_fn_set_parser_command_missing(self): ) def test_fn_set_parser_command_present(self): - """ Confirm parser/command is not changed if provided - """ + """Confirm parser/command is not changed if provided""" self._plugin._task.args = { "command": "anything", "parser": {"command": "something", "name": "a.b.c"}, @@ -216,15 +207,13 @@ def test_fn_set_parser_command_present(self): ) def test_fn_set_parser_command_absent(self): - """ Confirm parser/command is not added - """ + """Confirm parser/command is not added""" self._plugin._task.args = {"parser": {}} self._plugin._set_parser_command() self.assertNotIn("command", self._plugin._task.args["parser"]) def test_fn_set_text_present(self): - """ Check task args text is set to stdout - """ + """Check task args text is set to stdout""" expected = "output" self._plugin._result["stdout"] = expected self._plugin._task.args = {} @@ -232,16 +221,14 @@ def test_fn_set_text_present(self): self.assertEqual(self._plugin._task.args["text"], expected) def test_fn_set_text_absent(self): - """ Check task args text is set to stdout - """ + """Check task args text is set to stdout""" self._plugin._result["stdout"] = None self._plugin._task.args = {} self._plugin._set_text() self.assertNotIn("text", self._plugin._task.args) def test_fn_os_from_task_vars(self): - """ Confirm os is set based on task vars - """ + """Confirm os is set based on task vars""" checks = [ ("ansible_network_os", "cisco.nxos.nxos", "nxos"), ("ansible_network_os", "NXOS", "nxos"), @@ -254,7 +241,7 @@ def test_fn_os_from_task_vars(self): self.assertEqual(result, check[2]) def test_fn_update_template_path_not_exist(self): - """ Check the creation of the template_path if + """Check the creation of the template_path if it doesn't exist in the user provided data """ self._plugin._task.args = { @@ -269,7 +256,7 @@ def test_fn_update_template_path_not_exist(self): ) def test_fn_update_template_path_not_exist_os(self): - """ Check the creation of the template_path if + """Check the creation of the template_path if it doesn't exist in the user provided data name based on os provided in task """ @@ -284,7 +271,7 @@ def test_fn_update_template_path_not_exist_os(self): ) def test_fn_update_template_path_mock_find_needle(self): - """ Check the creation of the template_path + """Check the creation of the template_path mock the find needle fn so the template doesn't need to be in the default template folder """ @@ -302,8 +289,7 @@ def test_fn_update_template_path_mock_find_needle(self): ) def test_fn_get_template_contents_pass(self): - """ Check the retrieval of the template contents - """ + """Check the retrieval of the template contents""" temp = tempfile.NamedTemporaryFile() contents = "abcdef" with open(temp.name, "w") as fileh: @@ -314,8 +300,7 @@ def test_fn_get_template_contents_pass(self): self.assertEqual(result, contents) def test_fn_get_template_contents_missing(self): - """ Check the retrieval of the template contents - """ + """Check the retrieval of the template contents""" self._plugin._task.args = {"parser": {"template_path": "non-exist"}} with self.assertRaises(Exception) as error: self._plugin._get_template_contents() @@ -324,15 +309,13 @@ def test_fn_get_template_contents_missing(self): ) def test_fn_get_template_contents_not_specified(self): - """ Check the none when template_path not specified - """ + """Check the none when template_path not specified""" self._plugin._task.args = {"parser": {}} result = self._plugin._get_template_contents() self.assertIsNone(result) def test_fn_prune_result_pass(self): - """ Test the removal of stdout and stdout_lines from the _result - """ + """Test the removal of stdout and stdout_lines from the _result""" self._plugin._result["stdout"] = "abc" self._plugin._result["stdout_lines"] = "abc" self._plugin._prune_result() @@ -340,15 +323,13 @@ def test_fn_prune_result_pass(self): self.assertNotIn("stdout_lines", self._plugin._result) def test_fn_prune_result_not_exist(self): - """ Test the removal of stdout and stdout_lines from the _result - """ + """Test the removal of stdout and stdout_lines from the _result""" self._plugin._prune_result() self.assertNotIn("stdout", self._plugin._result) self.assertNotIn("stdout_lines", self._plugin._result) def test_fn_run_command_lx_rc0(self): - """ Check run command for non network - """ + """Check run command for non network""" response = "abc" self._plugin._connection.socket_path = None self._plugin._low_level_execute_command = MagicMock() @@ -363,8 +344,7 @@ def test_fn_run_command_lx_rc0(self): self.assertEqual(self._plugin._result["stdout_lines"], response) def test_fn_run_command_lx_rc1(self): - """ Check run command for non network - """ + """Check run command for non network""" response = "abc" self._plugin._connection.socket_path = None self._plugin._low_level_execute_command = MagicMock() @@ -381,8 +361,7 @@ def test_fn_run_command_lx_rc1(self): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_command_network(self, mock_rpc): - """ Check run command for network - """ + """Check run command for network""" expected = "abc" mock_rpc.return_value = expected self._plugin._connection.socket_path = ( @@ -394,16 +373,14 @@ def test_fn_run_command_network(self, mock_rpc): self.assertEqual(self._plugin._result["stdout_lines"], [expected]) def test_fn_run_command_not_specified(self): - """ Check run command for network - """ + """Check run command for network""" self._plugin._task.args = {"command": None} result = self._plugin._run_command() self.assertIsNone(result) @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_pass_w_fact(self, mock_rpc): - """ Check full module run with valid params - """ + """Check full module run with valid params""" mock_out = self._load_fixture("nxos_show_version.txt") mock_rpc.return_value = mock_out self._plugin._connection.socket_path = ( @@ -431,8 +408,7 @@ def test_fn_run_pass_w_fact(self, mock_rpc): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_pass_wo_fact(self, mock_rpc): - """ Check full module run with valid params - """ + """Check full module run with valid params""" mock_out = self._load_fixture("nxos_show_version.txt") mock_rpc.return_value = mock_out self._plugin._connection.socket_path = ( @@ -456,8 +432,7 @@ def test_fn_run_pass_wo_fact(self, mock_rpc): self.assertNotIn("ansible_facts", result) def test_fn_run_fail_argspec(self): - """ Check full module run with invalid params - """ + """Check full module run with invalid params""" self._plugin._task.args = { "text": "anything", "parser": { @@ -470,8 +445,7 @@ def test_fn_run_fail_argspec(self): self.assertIn("including collection", self._plugin._result["msg"]) def test_fn_run_fail_command(self): - """ Confirm clean fail with rc 1 - """ + """Confirm clean fail with rc 1""" self._plugin._connection.socket_path = None self._plugin._low_level_execute_command = MagicMock() self._plugin._low_level_execute_command.return_value = { @@ -495,8 +469,7 @@ def test_fn_run_fail_command(self): self.assertEqual(result, expected) def test_fn_run_fail_missing_parser(self): - """Confirm clean fail with missing parser - """ + """Confirm clean fail with missing parser""" self._plugin._task.args = {"text": None, "parser": {"name": "a.b.c"}} task_vars = {"inventory_hostname": "mockdevice"} result = self._plugin.run(task_vars=task_vars) @@ -505,7 +478,7 @@ def test_fn_run_fail_missing_parser(self): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_pass_missing_parser_constants(self, mock_rpc): - """ Check full module run using parser w/o + """Check full module run using parser w/o DEFAULT_TEMPLATE_EXTENSION or PROVIDE_TEMPLATE_CONTENTS defined in the parser """ @@ -542,7 +515,7 @@ def parse(self, *_args, **kwargs): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_pass_missing_parser_in_parser(self, mock_rpc): - """ Check full module run using parser w/o + """Check full module run using parser w/o a parser function defined in the parser defined in the parser """ @@ -577,8 +550,7 @@ class CliParser(CliParserBase): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_net_device_error(self, mock_rpc): - """ Check full module run mock error from network device - """ + """Check full module run mock error from network device""" msg = "I was mocked" mock_rpc.side_effect = AnsibleConnectionError(msg) self._plugin._connection.socket_path = ( From 5a8d78d988477d06199accdd5abfa0c82613670e Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 13:52:35 +0530 Subject: [PATCH 17/31] Fix linters --- plugins/filter/json_to_xml.py | 2 +- tests/unit/module_utils/test_xml_to_json.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/json_to_xml.py index 718425a9..428cc668 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/json_to_xml.py @@ -98,7 +98,7 @@ # } # }, # "changed": false -# } +# } # TASK [debug] *********************************************************************************************************** # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils diff --git a/tests/unit/module_utils/test_xml_to_json.py b/tests/unit/module_utils/test_xml_to_json.py index c61b9d2a..c230136a 100644 --- a/tests/unit/module_utils/test_xml_to_json.py +++ b/tests/unit/module_utils/test_xml_to_json.py @@ -42,7 +42,7 @@ def test_invalid_data(self): def test_valid_data(self): """Check passing valid data as per criteria""" self.maxDiff = None - output = """{"netconf-state": {"@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", + output = """{"netconf-state": {"@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", "schemas": {"schema": null}}}""" args = [VALID_DATA, "xmltodict"] result = xml_to_json(*args) From 93769853597d21adcdacc019dea133d9ef32d95f Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 15:40:51 +0530 Subject: [PATCH 18/31] Address review comments --- .../filter/{xml_to_json.py => from_xml.py} | 32 +++++++++---------- plugins/filter/{json_to_xml.py => to_xml.py} | 22 ++++++------- .../{xml_to_json.py => from_xml.py} | 18 +++++++---- .../{json_to_xml.py => to_xml.py} | 19 +++++++---- .../tasks/include/simple.yaml | 12 +++---- .../{json_to_xml => from_xml}/tasks/main.yaml | 0 .../tasks/include/simple.yaml | 12 +++---- .../{xml_to_json => to_xml}/tasks/main.yaml | 0 .../filter/test_from_xml.py} | 19 +++++------ .../filter/test_to_xml.py} | 12 +++---- 10 files changed, 80 insertions(+), 66 deletions(-) rename plugins/filter/{xml_to_json.py => from_xml.py} (83%) rename plugins/filter/{json_to_xml.py => to_xml.py} (87%) rename plugins/plugin_utils/{xml_to_json.py => from_xml.py} (68%) rename plugins/plugin_utils/{json_to_xml.py => to_xml.py} (70%) rename tests/integration/targets/{xml_to_json => from_xml}/tasks/include/simple.yaml (69%) rename tests/integration/targets/{json_to_xml => from_xml}/tasks/main.yaml (100%) rename tests/integration/targets/{json_to_xml => to_xml}/tasks/include/simple.yaml (70%) rename tests/integration/targets/{xml_to_json => to_xml}/tasks/main.yaml (100%) rename tests/unit/{module_utils/test_xml_to_json.py => plugins/filter/test_from_xml.py} (69%) rename tests/unit/{module_utils/test_json_to_xml.py => plugins/filter/test_to_xml.py} (81%) diff --git a/plugins/filter/xml_to_json.py b/plugins/filter/from_xml.py similarity index 83% rename from plugins/filter/xml_to_json.py rename to plugins/filter/from_xml.py index 43f6693c..0d079221 100644 --- a/plugins/filter/xml_to_json.py +++ b/plugins/filter/from_xml.py @@ -6,26 +6,26 @@ # """ -The xml_to_json filter plugin +The from_xml filter plugin """ from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ - name: xml_to_json + name: from_xml author: Ashwini Mhatre (@amhatre) version_added: "2.0.2" - short_description: convert given xml string to json + short_description: convert given xml string to native python dictionary. description: - - This plugin converts the xml string to json. - - Using the parameters below- C(data|ansible.utils.xml_to_json) + - This plugin converts the xml string to native python dictionary. + - Using the parameters below- C(data|ansible.utils.from_xml) options: data: description: - The input xml string . - This option represents the xml value that is passed to the filter plugin in pipe format. - - For example C(config_data|ansible.utils.xml_to_json), in this case C(config_data) represents this option. + - For example C(config_data|ansible.utils.from_xml), in this case C(config_data) represents this option. type: str required: True engine: @@ -40,14 +40,14 @@ #### Simple examples with out any engine. plugin will use default value as xmltodict tasks: - - name: convert given xml to json + - name: convert given xml to native python dictionary ansible.builtin.set_fact: data: " " - debug: - msg: "{{ data|ansible.utils.xml_to_json }}" + msg: "{{ data|ansible.utils.from_xml }}" ##TASK###### # TASK [convert given xml to json] ***************************************************************************************************** @@ -83,7 +83,7 @@ " - debug: - msg: "{{ data|ansible.utils.xml_to_json('xmltodict') }}" + msg: "{{ data|ansible.utils.from_xml('xmltodict') }}" ##TASK###### # TASK [convert given xml to json] ***************************************************************************************************** @@ -112,8 +112,8 @@ from ansible.errors import AnsibleFilterError from jinja2.filters import environmentfilter -from ansible_collections.ansible.utils.plugins.plugin_utils.xml_to_json import ( - xml_to_json, +from ansible_collections.ansible.utils.plugins.plugin_utils.from_xml import ( + from_xml, ) from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( AnsibleArgSpecValidator, @@ -121,25 +121,25 @@ @environmentfilter -def _xml_to_json(*args, **kwargs): +def _from_xml(*args, **kwargs): """Convert the given data from xml to json.""" keys = ["data", "engine"] data = dict(zip(keys, args[1:])) data.update(kwargs) aav = AnsibleArgSpecValidator( - data=data, schema=DOCUMENTATION, name="xml_to_json" + data=data, schema=DOCUMENTATION, name="from_xml" ) valid, errors, updated_data = aav.validate() if not valid: raise AnsibleFilterError(errors) - return xml_to_json(**updated_data) + return from_xml(**updated_data) class FilterModule(object): - """ xml_to_json """ + """ from_xml """ def filters(self): """a mapping of filter names to functions""" - return {"xml_to_json": _xml_to_json} + return {"from_xml": _from_xml} diff --git a/plugins/filter/json_to_xml.py b/plugins/filter/to_xml.py similarity index 87% rename from plugins/filter/json_to_xml.py rename to plugins/filter/to_xml.py index 428cc668..b93ac12a 100644 --- a/plugins/filter/json_to_xml.py +++ b/plugins/filter/to_xml.py @@ -20,13 +20,13 @@ short_description: convert given json string to xml description: - This plugin converts the json string to xml. - - Using the parameters below- C(data|ansible.utils.json_to_xml) + - Using the parameters below- C(data|ansible.utils.to_xml) options: data: description: - The input json string . - This option represents the json value that is passed to the filter plugin in pipe format. - - For example C(config_data|ansible.utils.json_to_xml), in this case C(config_data) represents this option. + - For example C(config_data|ansible.utils.to_xml), in this case C(config_data) represents this option. type: str required: True engine: @@ -49,7 +49,7 @@ } } - debug: - msg: "{{ data|ansible.utils.json_to_xml }}" + msg: "{{ data|ansible.utils.to_xml }}" # TASK [Define json data ] ************************************************************************* # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 @@ -84,7 +84,7 @@ } } - debug: - msg: "{{ data|ansible.utils.json_to_xml('xmltodict') }}" + msg: "{{ data|ansible.utils.to_xml('xmltodict') }}" # TASK [Define json data ] ************************************************************************* # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 @@ -111,8 +111,8 @@ from ansible.errors import AnsibleFilterError from jinja2.filters import environmentfilter -from ansible_collections.ansible.utils.plugins.plugin_utils.json_to_xml import ( - json_to_xml, +from ansible_collections.ansible.utils.plugins.plugin_utils.to_xml import ( + to_xml, ) from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( AnsibleArgSpecValidator, @@ -120,23 +120,23 @@ @environmentfilter -def _json_to_xml(*args, **kwargs): +def _to_xml(*args, **kwargs): """Convert the given data from json to xml.""" keys = ["data", "engine"] data = dict(zip(keys, args[1:])) data.update(kwargs) aav = AnsibleArgSpecValidator( - data=data, schema=DOCUMENTATION, name="json_to_xml" + data=data, schema=DOCUMENTATION, name="to_xml" ) valid, errors, updated_data = aav.validate() if not valid: raise AnsibleFilterError(errors) - return json_to_xml(**updated_data) + return to_xml(**updated_data) class FilterModule(object): - """ json_to_xml """ + """ to_xml """ def filters(self): """a mapping of filter names to functions""" - return {"json_to_xml": _json_to_xml} + return {"to_xml": _to_xml} diff --git a/plugins/plugin_utils/xml_to_json.py b/plugins/plugin_utils/from_xml.py similarity index 68% rename from plugins/plugin_utils/xml_to_json.py rename to plugins/plugin_utils/from_xml.py index 4cba38e8..73567117 100644 --- a/plugins/plugin_utils/xml_to_json.py +++ b/plugins/plugin_utils/from_xml.py @@ -6,7 +6,7 @@ # """ -The xml_to_json plugin code +The from_xml plugin code """ from __future__ import absolute_import, division, print_function @@ -15,6 +15,12 @@ import json from ansible.errors import AnsibleError +try: + import xmltodict + + HAS_XMLTODICT = True +except ImportError: + HAS_XMLTODICT = False def _raise_error(msg): """Raise an error message, prepend with filter name @@ -22,19 +28,19 @@ def _raise_error(msg): :type msg: str :raises: AnsibleError """ - error = "Error when using plugin 'xml_to_json': {msg}".format(msg=msg) + error = "Error when using plugin 'from_xml': {msg}".format(msg=msg) raise AnsibleError(error) -def xml_to_json(data, engine): +def from_xml(data, engine): """Convert data which is in xml to json" - :param data: The data passed in (data|xml_to_json(...)) + :param data: The data passed in (data|from_xml(...)) :type data: xml :param engine: Conversion library default=xml_to_dict """ if engine == "xmltodict": - import xmltodict - + if not HAS_XMLTODICT: + _raise_error("Missing required library xmltodict") try: res = json.dumps(xmltodict.parse(data)) except Exception: diff --git a/plugins/plugin_utils/json_to_xml.py b/plugins/plugin_utils/to_xml.py similarity index 70% rename from plugins/plugin_utils/json_to_xml.py rename to plugins/plugin_utils/to_xml.py index 785a0425..207d4ba3 100644 --- a/plugins/plugin_utils/json_to_xml.py +++ b/plugins/plugin_utils/to_xml.py @@ -6,7 +6,7 @@ # """ -The json_to_xml plugin +The to_xml plugin """ from __future__ import absolute_import, division, print_function @@ -15,6 +15,13 @@ import ast from ansible.errors import AnsibleError +try: + import xmltodict + + HAS_XMLTODICT = True +except ImportError: + HAS_XMLTODICT = False + def _raise_error(msg): """Raise an error message, prepend with filter name @@ -23,20 +30,20 @@ def _raise_error(msg): :type msg: str :raises: AnsibleError """ - error = "Error when using plugin 'json_to_xml': {msg}".format(msg=msg) + error = "Error when using plugin 'to_xml': {msg}".format(msg=msg) raise AnsibleError(error) -def json_to_xml(data, engine): +def to_xml(data, engine): """Convert data which is in json to xml" - :param data: The data passed in (data|json_to_xml(...)) + :param data: The data passed in (data|to_xml(...)) :type data: xml :param engine: Conversion library default=xmltodict """ if engine == "xmltodict": - import xmltodict - + if not HAS_XMLTODICT: + _raise_error("Missing required library xmltodict") try: data = ast.literal_eval(data) res = xmltodict.unparse(data, pretty=True) diff --git a/tests/integration/targets/xml_to_json/tasks/include/simple.yaml b/tests/integration/targets/from_xml/tasks/include/simple.yaml similarity index 69% rename from tests/integration/targets/xml_to_json/tasks/include/simple.yaml rename to tests/integration/targets/from_xml/tasks/include/simple.yaml index 4ab440ea..c7135eee 100644 --- a/tests/integration/targets/xml_to_json/tasks/include/simple.yaml +++ b/tests/integration/targets/from_xml/tasks/include/simple.yaml @@ -14,26 +14,26 @@ } - debug: - msg: "{{ data|ansible.utils.xml_to_json() }}" + msg: "{{ data|ansible.utils.from_xml() }}" - name: Integration tests with and without default engine as xmltodict and assert: that: "{{ output == item.test }}" loop: - - test: "{{ data|ansible.utils.xml_to_json() }}" - - test: "{{ data|ansible.utils.xml_to_json('xmltodict') }}" + - test: "{{ data|ansible.utils.from_xml() }}" + - test: "{{ data|ansible.utils.from_xml('xmltodict') }}" -- name: Setup invalid xml as input to ansible.utils.xml_to_json. +- name: Setup invalid xml as input to ansible.utils.from_xml. ansible.builtin.set_fact: data: "" - name: validate input xml ansible.builtin.set_fact: - _result: "{{ data|ansible.utils.xml_to_json() }}" + _result: "{{ data|ansible.utils.from_xml() }}" ignore_errors: true register: result - assert: that: "{{ msg in result.msg }}" vars: - msg: "Error when using plugin 'xml_to_json': Input Xml is not valid" + msg: "Error when using plugin 'from_xml': Input Xml is not valid" diff --git a/tests/integration/targets/json_to_xml/tasks/main.yaml b/tests/integration/targets/from_xml/tasks/main.yaml similarity index 100% rename from tests/integration/targets/json_to_xml/tasks/main.yaml rename to tests/integration/targets/from_xml/tasks/main.yaml diff --git a/tests/integration/targets/json_to_xml/tasks/include/simple.yaml b/tests/integration/targets/to_xml/tasks/include/simple.yaml similarity index 70% rename from tests/integration/targets/json_to_xml/tasks/include/simple.yaml rename to tests/integration/targets/to_xml/tasks/include/simple.yaml index 27963492..19223a16 100644 --- a/tests/integration/targets/json_to_xml/tasks/include/simple.yaml +++ b/tests/integration/targets/to_xml/tasks/include/simple.yaml @@ -10,26 +10,26 @@ output: "\n\n\t\n" - debug: - msg: "{{ data|ansible.utils.json_to_xml() }}" + msg: "{{ data|ansible.utils.to_xml() }}" - name: Integration tests with and without default engine as xmltodict and assert: that: "{{ output == item.test }}" loop: - - test: "{{ data|ansible.utils.json_to_xml() }}" - - test: "{{ data|ansible.utils.json_to_xml('xmltodict') }}" + - test: "{{ data|ansible.utils.to_xml() }}" + - test: "{{ data|ansible.utils.to_xml('xmltodict') }}" -- name: Setup invalid xml as input to ansible.utils.xml_to_json. +- name: Setup invalid xml as input to ansible.utils.from_xml ansible.builtin.set_fact: data: "" - name: validate input xml ansible.builtin.set_fact: - _result: "{{ data|ansible.utils.json_to_xml() }}" + _result: "{{ data|ansible.utils.to_xml() }}" ignore_errors: true register: result - assert: that: "{{ msg in result.msg }}" vars: - msg: "Error when using plugin 'json_to_xml': Input json is not valid" \ No newline at end of file + msg: "Error when using plugin 'to_xml': Input json is not valid" \ No newline at end of file diff --git a/tests/integration/targets/xml_to_json/tasks/main.yaml b/tests/integration/targets/to_xml/tasks/main.yaml similarity index 100% rename from tests/integration/targets/xml_to_json/tasks/main.yaml rename to tests/integration/targets/to_xml/tasks/main.yaml diff --git a/tests/unit/module_utils/test_xml_to_json.py b/tests/unit/plugins/filter/test_from_xml.py similarity index 69% rename from tests/unit/module_utils/test_xml_to_json.py rename to tests/unit/plugins/filter/test_from_xml.py index c230136a..565e8670 100644 --- a/tests/unit/module_utils/test_xml_to_json.py +++ b/tests/unit/plugins/filter/test_from_xml.py @@ -9,8 +9,8 @@ import unittest from ansible.errors import AnsibleError -from ansible_collections.ansible.utils.plugins.filter.xml_to_json import ( - xml_to_json, +from ansible_collections.ansible.utils.plugins.filter.from_xml import ( + from_xml, ) INVALID_DATA = '' @@ -20,8 +20,10 @@ "" ) +OUTPUT = """{"netconf-state": \ +{"@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", "schemas": {"schema": null}}}""" -class TestXmlToJson(unittest.TestCase): +class TestFromXml(unittest.TestCase): def setUp(self): pass @@ -32,18 +34,17 @@ def test_invalid_data(self): args = [INVALID_DATA, "xmltodict"] kwargs = {} with self.assertRaises(AnsibleError) as error: - xml_to_json(*args, **kwargs) + from_xml(*args, **kwargs) print(str(error.exception)) self.assertIn( - "Error when using plugin 'xml_to_json': Input Xml is not valid", + "Error when using plugin 'from_xml': Input Xml is not valid", str(error.exception), ) def test_valid_data(self): """Check passing valid data as per criteria""" self.maxDiff = None - output = """{"netconf-state": {"@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", - "schemas": {"schema": null}}}""" args = [VALID_DATA, "xmltodict"] - result = xml_to_json(*args) - self.assertEqual(result, output) + result = from_xml(*args) + print(result) + self.assertEqual(result, OUTPUT) diff --git a/tests/unit/module_utils/test_json_to_xml.py b/tests/unit/plugins/filter/test_to_xml.py similarity index 81% rename from tests/unit/module_utils/test_json_to_xml.py rename to tests/unit/plugins/filter/test_to_xml.py index 3ab12679..05726a50 100644 --- a/tests/unit/module_utils/test_json_to_xml.py +++ b/tests/unit/plugins/filter/test_to_xml.py @@ -9,8 +9,8 @@ import unittest from ansible.errors import AnsibleError -from ansible_collections.ansible.utils.plugins.filter.json_to_xml import ( - json_to_xml, +from ansible_collections.ansible.utils.plugins.filter.to_xml import ( + to_xml, ) INVALID_DATA = '' @@ -25,7 +25,7 @@ """ -class TestXmlToJson(unittest.TestCase): +class TestToXml(unittest.TestCase): def setUp(self): pass @@ -36,10 +36,10 @@ def test_invalid_data(self): args = [INVALID_DATA, "xmltodict"] kwargs = {} with self.assertRaises(AnsibleError) as error: - json_to_xml(*args, **kwargs) + to_xml(*args, **kwargs) print(str(error.exception)) self.assertIn( - "Error when using plugin 'json_to_xml': Input json is not valid", + "Error when using plugin 'to_xml': Input json is not valid", str(error.exception), ) @@ -47,6 +47,6 @@ def test_valid_data(self): """Check passing valid data as per criteria""" self.maxDiff = None args = [VALID_DATA, "xmltodict"] - result = json_to_xml(*args) + result = to_xml(*args) print(result) self.assertEqual(result, OUTPUT) From ba0263b74be9de4bf1a9390100e56404188a0e14 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 15:44:28 +0530 Subject: [PATCH 19/31] fix linters --- plugins/plugin_utils/from_xml.py | 1 + tests/unit/plugins/filter/test_from_xml.py | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/plugin_utils/from_xml.py b/plugins/plugin_utils/from_xml.py index 73567117..d33f96b8 100644 --- a/plugins/plugin_utils/from_xml.py +++ b/plugins/plugin_utils/from_xml.py @@ -22,6 +22,7 @@ except ImportError: HAS_XMLTODICT = False + def _raise_error(msg): """Raise an error message, prepend with filter name :param msg: The message diff --git a/tests/unit/plugins/filter/test_from_xml.py b/tests/unit/plugins/filter/test_from_xml.py index 565e8670..e2c50e78 100644 --- a/tests/unit/plugins/filter/test_from_xml.py +++ b/tests/unit/plugins/filter/test_from_xml.py @@ -23,6 +23,7 @@ OUTPUT = """{"netconf-state": \ {"@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", "schemas": {"schema": null}}}""" + class TestFromXml(unittest.TestCase): def setUp(self): pass From 92489532412857527218432ab2b1ee8e783fd55a Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 15:50:23 +0530 Subject: [PATCH 20/31] address review comments --- tests/unit/plugins/filter/test_from_xml.py | 4 +--- tests/unit/plugins/filter/test_to_xml.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit/plugins/filter/test_from_xml.py b/tests/unit/plugins/filter/test_from_xml.py index e2c50e78..0ff51fed 100644 --- a/tests/unit/plugins/filter/test_from_xml.py +++ b/tests/unit/plugins/filter/test_from_xml.py @@ -9,9 +9,7 @@ import unittest from ansible.errors import AnsibleError -from ansible_collections.ansible.utils.plugins.filter.from_xml import ( - from_xml, -) +from ansible_collections.ansible.utils.plugins.filter.from_xml import from_xml INVALID_DATA = '' diff --git a/tests/unit/plugins/filter/test_to_xml.py b/tests/unit/plugins/filter/test_to_xml.py index 05726a50..91851a7c 100644 --- a/tests/unit/plugins/filter/test_to_xml.py +++ b/tests/unit/plugins/filter/test_to_xml.py @@ -9,9 +9,7 @@ import unittest from ansible.errors import AnsibleError -from ansible_collections.ansible.utils.plugins.filter.to_xml import ( - to_xml, -) +from ansible_collections.ansible.utils.plugins.filter.to_xml import to_xml INVALID_DATA = '' From 440760efc14fedc4cdf827bedc535163699dfd3f Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 15:55:35 +0530 Subject: [PATCH 21/31] address review comments --- changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml | 2 +- plugins/filter/to_xml.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml b/changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml index 0139428a..556fde81 100644 --- a/changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml +++ b/changelogs/fragments/add-xmltojson-and-jsontoxml-filter.yaml @@ -1,3 +1,3 @@ --- minor_changes: - - Add Xml_to_json and json_to_xml fiter plugin (https://github.com/ansible-collections/ansible.utils/pull/56). + - Add from_xml and to_xml fiter plugin (https://github.com/ansible-collections/ansible.utils/pull/56). diff --git a/plugins/filter/to_xml.py b/plugins/filter/to_xml.py index b93ac12a..dc40a17b 100644 --- a/plugins/filter/to_xml.py +++ b/plugins/filter/to_xml.py @@ -7,14 +7,14 @@ """ -The json_to_xml filter plugin +The to_xml filter plugin """ from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ - name: json_to_xml + name: to_xml author: Ashwini Mhatre (@amhatre) version_added: "2.0.2" short_description: convert given json string to xml From daa4f308b24ae30f6fd8e68aae21465ad075ee1e Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 15:58:38 +0530 Subject: [PATCH 22/31] revert unwanted changes tttlease enter the commit message for your changes. Lines starting --- tests/unit/plugins/action/test_cli_parse.py | 102 +++++++++++++------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/tests/unit/plugins/action/test_cli_parse.py b/tests/unit/plugins/action/test_cli_parse.py index 16c6cc37..a575f872 100644 --- a/tests/unit/plugins/action/test_cli_parse.py +++ b/tests/unit/plugins/action/test_cli_parse.py @@ -58,7 +58,7 @@ def setUp(self): @staticmethod def _load_fixture(filename): - """Load a fixture from the filesystem + """ Load a fixture from the filesystem :param filename: The name of the file to load :type filename: str @@ -72,20 +72,23 @@ def _load_fixture(filename): return fhand.read() def test_fn_debug(self): - """Confirm debug doesn't fail and return None""" + """ Confirm debug doesn't fail and return None + """ msg = "some message" result = self._plugin._debug(msg) self.assertEqual(result, None) def test_fn_ail_json(self): - """Confirm fail json replaces basic.py in msg""" + """ Confirm fail json replaces basic.py in msg + """ msg = "text (basic.py)" with self.assertRaises(Exception) as error: self._plugin._fail_json(msg) self.assertEqual("text cli_parse", str(error.exception)) def test_fn_check_argspec_pass(self): - """Confirm a valid argspec passes""" + """ Confirm a valid argspec passes + """ kwargs = { "text": "text", "parser": { @@ -99,7 +102,8 @@ def test_fn_check_argspec_pass(self): self.assertEqual(valid, True) def test_fn_check_argspec_fail_no_test_or_command(self): - """Confirm failed argpsec w/o text or command""" + """ Confirm failed argpsec w/o text or command + """ kwargs = { "parser": { "name": "ansible.utils.textfsm", @@ -118,7 +122,8 @@ def test_fn_check_argspec_fail_no_test_or_command(self): ) def test_fn_check_argspec_fail_no_parser_name(self): - """Confirm failed argspec no parser name""" + """ Confirm failed argspec no parser name + """ kwargs = {"text": "anything", "parser": {"command": "show version"}} valid, result, updated_params = check_argspec( DOCUMENTATION, @@ -132,7 +137,8 @@ def test_fn_check_argspec_fail_no_parser_name(self): ) def test_fn_extended_check_argspec_parser_name_not_coll(self): - """Confirm failed argpsec parser not collection format""" + """ Confirm failed argpsec parser not collection format + """ self._plugin._task.args = { "text": "anything", "parser": { @@ -145,7 +151,7 @@ def test_fn_extended_check_argspec_parser_name_not_coll(self): self.assertIn("including collection", self._plugin._result["msg"]) def test_fn_extended_check_argspec_missing_tpath_or_command(self): - """Confirm failed argpsec missing template_path + """ Confirm failed argpsec missing template_path or command when text provided """ self._plugin._task.args = { @@ -159,7 +165,8 @@ def test_fn_extended_check_argspec_missing_tpath_or_command(self): ) def test_fn_load_parser_pass(self): - """Confirm each each of the parsers loads from the filesystem""" + """ Confirm each each of the parsers loads from the filesystem + """ parser_names = ["json", "textfsm", "ttp", "xml"] for parser_name in parser_names: self._plugin._task.args = { @@ -172,7 +179,8 @@ def test_fn_load_parser_pass(self): self.assertTrue(callable(parser.parse)) def test_fn_load_parser_fail(self): - """Confirm missing parser fails gracefully""" + """ Confirm missing parser fails gracefully + """ self._plugin._task.args = { "text": "anything", "parser": {"name": "a.b.c"}, @@ -183,7 +191,7 @@ def test_fn_load_parser_fail(self): self.assertIn("No module named", self._plugin._result["msg"]) def test_fn_set_parser_command_missing(self): - """Confirm parser/command is set if missing + """ Confirm parser/command is set if missing and command provided """ self._plugin._task.args = { @@ -196,7 +204,8 @@ def test_fn_set_parser_command_missing(self): ) def test_fn_set_parser_command_present(self): - """Confirm parser/command is not changed if provided""" + """ Confirm parser/command is not changed if provided + """ self._plugin._task.args = { "command": "anything", "parser": {"command": "something", "name": "a.b.c"}, @@ -207,13 +216,15 @@ def test_fn_set_parser_command_present(self): ) def test_fn_set_parser_command_absent(self): - """Confirm parser/command is not added""" + """ Confirm parser/command is not added + """ self._plugin._task.args = {"parser": {}} self._plugin._set_parser_command() self.assertNotIn("command", self._plugin._task.args["parser"]) def test_fn_set_text_present(self): - """Check task args text is set to stdout""" + """ Check task args text is set to stdout + """ expected = "output" self._plugin._result["stdout"] = expected self._plugin._task.args = {} @@ -221,14 +232,16 @@ def test_fn_set_text_present(self): self.assertEqual(self._plugin._task.args["text"], expected) def test_fn_set_text_absent(self): - """Check task args text is set to stdout""" + """ Check task args text is set to stdout + """ self._plugin._result["stdout"] = None self._plugin._task.args = {} self._plugin._set_text() self.assertNotIn("text", self._plugin._task.args) def test_fn_os_from_task_vars(self): - """Confirm os is set based on task vars""" + """ Confirm os is set based on task vars + """ checks = [ ("ansible_network_os", "cisco.nxos.nxos", "nxos"), ("ansible_network_os", "NXOS", "nxos"), @@ -241,7 +254,7 @@ def test_fn_os_from_task_vars(self): self.assertEqual(result, check[2]) def test_fn_update_template_path_not_exist(self): - """Check the creation of the template_path if + """ Check the creation of the template_path if it doesn't exist in the user provided data """ self._plugin._task.args = { @@ -256,7 +269,7 @@ def test_fn_update_template_path_not_exist(self): ) def test_fn_update_template_path_not_exist_os(self): - """Check the creation of the template_path if + """ Check the creation of the template_path if it doesn't exist in the user provided data name based on os provided in task """ @@ -271,7 +284,7 @@ def test_fn_update_template_path_not_exist_os(self): ) def test_fn_update_template_path_mock_find_needle(self): - """Check the creation of the template_path + """ Check the creation of the template_path mock the find needle fn so the template doesn't need to be in the default template folder """ @@ -289,7 +302,8 @@ def test_fn_update_template_path_mock_find_needle(self): ) def test_fn_get_template_contents_pass(self): - """Check the retrieval of the template contents""" + """ Check the retrieval of the template contents + """ temp = tempfile.NamedTemporaryFile() contents = "abcdef" with open(temp.name, "w") as fileh: @@ -300,7 +314,8 @@ def test_fn_get_template_contents_pass(self): self.assertEqual(result, contents) def test_fn_get_template_contents_missing(self): - """Check the retrieval of the template contents""" + """ Check the retrieval of the template contents + """ self._plugin._task.args = {"parser": {"template_path": "non-exist"}} with self.assertRaises(Exception) as error: self._plugin._get_template_contents() @@ -309,13 +324,15 @@ def test_fn_get_template_contents_missing(self): ) def test_fn_get_template_contents_not_specified(self): - """Check the none when template_path not specified""" + """ Check the none when template_path not specified + """ self._plugin._task.args = {"parser": {}} result = self._plugin._get_template_contents() self.assertIsNone(result) def test_fn_prune_result_pass(self): - """Test the removal of stdout and stdout_lines from the _result""" + """ Test the removal of stdout and stdout_lines from the _result + """ self._plugin._result["stdout"] = "abc" self._plugin._result["stdout_lines"] = "abc" self._plugin._prune_result() @@ -323,13 +340,15 @@ def test_fn_prune_result_pass(self): self.assertNotIn("stdout_lines", self._plugin._result) def test_fn_prune_result_not_exist(self): - """Test the removal of stdout and stdout_lines from the _result""" + """ Test the removal of stdout and stdout_lines from the _result + """ self._plugin._prune_result() self.assertNotIn("stdout", self._plugin._result) self.assertNotIn("stdout_lines", self._plugin._result) def test_fn_run_command_lx_rc0(self): - """Check run command for non network""" + """ Check run command for non network + """ response = "abc" self._plugin._connection.socket_path = None self._plugin._low_level_execute_command = MagicMock() @@ -344,7 +363,8 @@ def test_fn_run_command_lx_rc0(self): self.assertEqual(self._plugin._result["stdout_lines"], response) def test_fn_run_command_lx_rc1(self): - """Check run command for non network""" + """ Check run command for non network + """ response = "abc" self._plugin._connection.socket_path = None self._plugin._low_level_execute_command = MagicMock() @@ -361,7 +381,8 @@ def test_fn_run_command_lx_rc1(self): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_command_network(self, mock_rpc): - """Check run command for network""" + """ Check run command for network + """ expected = "abc" mock_rpc.return_value = expected self._plugin._connection.socket_path = ( @@ -373,14 +394,16 @@ def test_fn_run_command_network(self, mock_rpc): self.assertEqual(self._plugin._result["stdout_lines"], [expected]) def test_fn_run_command_not_specified(self): - """Check run command for network""" + """ Check run command for network + """ self._plugin._task.args = {"command": None} result = self._plugin._run_command() self.assertIsNone(result) @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_pass_w_fact(self, mock_rpc): - """Check full module run with valid params""" + """ Check full module run with valid params + """ mock_out = self._load_fixture("nxos_show_version.txt") mock_rpc.return_value = mock_out self._plugin._connection.socket_path = ( @@ -408,7 +431,8 @@ def test_fn_run_pass_w_fact(self, mock_rpc): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_pass_wo_fact(self, mock_rpc): - """Check full module run with valid params""" + """ Check full module run with valid params + """ mock_out = self._load_fixture("nxos_show_version.txt") mock_rpc.return_value = mock_out self._plugin._connection.socket_path = ( @@ -432,7 +456,8 @@ def test_fn_run_pass_wo_fact(self, mock_rpc): self.assertNotIn("ansible_facts", result) def test_fn_run_fail_argspec(self): - """Check full module run with invalid params""" + """ Check full module run with invalid params + """ self._plugin._task.args = { "text": "anything", "parser": { @@ -445,7 +470,8 @@ def test_fn_run_fail_argspec(self): self.assertIn("including collection", self._plugin._result["msg"]) def test_fn_run_fail_command(self): - """Confirm clean fail with rc 1""" + """ Confirm clean fail with rc 1 + """ self._plugin._connection.socket_path = None self._plugin._low_level_execute_command = MagicMock() self._plugin._low_level_execute_command.return_value = { @@ -469,7 +495,8 @@ def test_fn_run_fail_command(self): self.assertEqual(result, expected) def test_fn_run_fail_missing_parser(self): - """Confirm clean fail with missing parser""" + """Confirm clean fail with missing parser + """ self._plugin._task.args = {"text": None, "parser": {"name": "a.b.c"}} task_vars = {"inventory_hostname": "mockdevice"} result = self._plugin.run(task_vars=task_vars) @@ -478,7 +505,7 @@ def test_fn_run_fail_missing_parser(self): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_pass_missing_parser_constants(self, mock_rpc): - """Check full module run using parser w/o + """ Check full module run using parser w/o DEFAULT_TEMPLATE_EXTENSION or PROVIDE_TEMPLATE_CONTENTS defined in the parser """ @@ -515,7 +542,7 @@ def parse(self, *_args, **kwargs): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_pass_missing_parser_in_parser(self, mock_rpc): - """Check full module run using parser w/o + """ Check full module run using parser w/o a parser function defined in the parser defined in the parser """ @@ -550,7 +577,8 @@ class CliParser(CliParserBase): @patch("ansible.module_utils.connection.Connection.__rpc__") def test_fn_run_net_device_error(self, mock_rpc): - """Check full module run mock error from network device""" + """ Check full module run mock error from network device + """ msg = "I was mocked" mock_rpc.side_effect = AnsibleConnectionError(msg) self._plugin._connection.socket_path = ( @@ -563,4 +591,4 @@ def test_fn_run_net_device_error(self, mock_rpc): task_vars = {"inventory_hostname": "mockdevice"} result = self._plugin.run(task_vars=task_vars) self.assertEqual(result["failed"], True) - self.assertEqual([msg], result["msg"]) + self.assertEqual([msg], result["msg"]) \ No newline at end of file From d845d766155781ceb0710bd762b354effe4478df Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 16:00:18 +0530 Subject: [PATCH 23/31] revert unwanted changes t Please enter the commit message for your changes. Lines starting --- tests/unit/plugins/action/test_cli_parse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/plugins/action/test_cli_parse.py b/tests/unit/plugins/action/test_cli_parse.py index a575f872..eb5771f0 100644 --- a/tests/unit/plugins/action/test_cli_parse.py +++ b/tests/unit/plugins/action/test_cli_parse.py @@ -591,4 +591,4 @@ def test_fn_run_net_device_error(self, mock_rpc): task_vars = {"inventory_hostname": "mockdevice"} result = self._plugin.run(task_vars=task_vars) self.assertEqual(result["failed"], True) - self.assertEqual([msg], result["msg"]) \ No newline at end of file + self.assertEqual([msg], result["msg"]) From fc28e99200ebce0f3678f2c475fa8300013f6b65 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Wed, 31 Mar 2021 19:04:34 +0530 Subject: [PATCH 24/31] Address review comments --- plugins/filter/to_xml.py | 12 ++++++------ plugins/plugin_utils/from_xml.py | 4 ++-- plugins/plugin_utils/to_xml.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/filter/to_xml.py b/plugins/filter/to_xml.py index dc40a17b..3859f482 100644 --- a/plugins/filter/to_xml.py +++ b/plugins/filter/to_xml.py @@ -48,8 +48,8 @@ "interface-configuration": null } } - - debug: - msg: "{{ data|ansible.utils.to_xml }}" +- debug: + msg: "{{ data|ansible.utils.to_xml }}" # TASK [Define json data ] ************************************************************************* # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 @@ -76,15 +76,15 @@ #### example2 with engine=xmltodict - name: Define json data - ansible.builtin.set_fact: - data: { + ansible.builtin.set_fact: + data: { "interface-configurations": { "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", "interface-configuration": null } } - - debug: - msg: "{{ data|ansible.utils.to_xml('xmltodict') }}" +- debug: + msg: "{{ data|ansible.utils.to_xml('xmltodict') }}" # TASK [Define json data ] ************************************************************************* # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 diff --git a/plugins/plugin_utils/from_xml.py b/plugins/plugin_utils/from_xml.py index d33f96b8..0e6b1076 100644 --- a/plugins/plugin_utils/from_xml.py +++ b/plugins/plugin_utils/from_xml.py @@ -13,7 +13,7 @@ __metaclass__ = type import json -from ansible.errors import AnsibleError +from ansible.errors import AnsibleFilterError try: import xmltodict @@ -30,7 +30,7 @@ def _raise_error(msg): :raises: AnsibleError """ error = "Error when using plugin 'from_xml': {msg}".format(msg=msg) - raise AnsibleError(error) + raise AnsibleFilterError(error) def from_xml(data, engine): diff --git a/plugins/plugin_utils/to_xml.py b/plugins/plugin_utils/to_xml.py index 207d4ba3..42f9a4db 100644 --- a/plugins/plugin_utils/to_xml.py +++ b/plugins/plugin_utils/to_xml.py @@ -13,7 +13,7 @@ __metaclass__ = type import ast -from ansible.errors import AnsibleError +from ansible.errors import AnsibleFilterError try: import xmltodict @@ -31,7 +31,7 @@ def _raise_error(msg): :raises: AnsibleError """ error = "Error when using plugin 'to_xml': {msg}".format(msg=msg) - raise AnsibleError(error) + raise AnsibleFilterError(error) def to_xml(data, engine): From 655f327d1bedcda19022c54ae5fb6f5e63332945 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Thu, 1 Apr 2021 15:01:40 +0530 Subject: [PATCH 25/31] Address review comments --- plugins/filter/to_xml.py | 22 ++++++++----------- plugins/plugin_utils/from_xml.py | 3 +++ plugins/plugin_utils/to_xml.py | 4 +++- .../targets/to_xml/tasks/include/simple.yaml | 20 ++++++++--------- tests/unit/plugins/filter/test_to_xml.py | 10 ++++----- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/plugins/filter/to_xml.py b/plugins/filter/to_xml.py index 3859f482..f21acf34 100644 --- a/plugins/filter/to_xml.py +++ b/plugins/filter/to_xml.py @@ -27,7 +27,7 @@ - The input json string . - This option represents the json value that is passed to the filter plugin in pipe format. - For example C(config_data|ansible.utils.to_xml), in this case C(config_data) represents this option. - type: str + type: dict required: True engine: description: @@ -42,12 +42,10 @@ - name: Define json data ansible.builtin.set_fact: - data: { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } + data: + "interface-configurations": + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" + "interface-configuration": - debug: msg: "{{ data|ansible.utils.to_xml }}" @@ -77,12 +75,10 @@ - name: Define json data ansible.builtin.set_fact: - data: { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } + data: + "interface-configurations": + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" + "interface-configuration": - debug: msg: "{{ data|ansible.utils.to_xml('xmltodict') }}" diff --git a/plugins/plugin_utils/from_xml.py b/plugins/plugin_utils/from_xml.py index 0e6b1076..c8578b43 100644 --- a/plugins/plugin_utils/from_xml.py +++ b/plugins/plugin_utils/from_xml.py @@ -47,3 +47,6 @@ def from_xml(data, engine): except Exception: _raise_error("Input Xml is not valid") return res + else: + error = "engine: {engine} is not supported ".format(engine=engine) + _raise_error(error) diff --git a/plugins/plugin_utils/to_xml.py b/plugins/plugin_utils/to_xml.py index 42f9a4db..2f967713 100644 --- a/plugins/plugin_utils/to_xml.py +++ b/plugins/plugin_utils/to_xml.py @@ -45,8 +45,10 @@ def to_xml(data, engine): if not HAS_XMLTODICT: _raise_error("Missing required library xmltodict") try: - data = ast.literal_eval(data) res = xmltodict.unparse(data, pretty=True) except Exception: _raise_error("Input json is not valid") return res + else: + error = "engine: {engine} is not supported ".format(engine=engine) + _raise_error(error) diff --git a/tests/integration/targets/to_xml/tasks/include/simple.yaml b/tests/integration/targets/to_xml/tasks/include/simple.yaml index 19223a16..1f7a412b 100644 --- a/tests/integration/targets/to_xml/tasks/include/simple.yaml +++ b/tests/integration/targets/to_xml/tasks/include/simple.yaml @@ -1,12 +1,10 @@ --- - name: Setup xml and expected json ansible.builtin.set_fact: - data: { - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - "interface-configuration": null - } - } + data: + "interface-configurations": + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" + "interface-configuration": output: "\n\n\t\n" - debug: @@ -19,17 +17,19 @@ - test: "{{ data|ansible.utils.to_xml() }}" - test: "{{ data|ansible.utils.to_xml('xmltodict') }}" -- name: Setup invalid xml as input to ansible.utils.from_xml +- name: test for supported engine for to_xml filter ansible.builtin.set_fact: - data: "" + data: + "interface-configurations": + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" - name: validate input xml ansible.builtin.set_fact: - _result: "{{ data|ansible.utils.to_xml() }}" + _result: "{{ data|ansible.utils.to_xml('dicttoxml') }}" ignore_errors: true register: result - assert: that: "{{ msg in result.msg }}" vars: - msg: "Error when using plugin 'to_xml': Input json is not valid" \ No newline at end of file + msg: "Error when using plugin 'to_xml': engine: dicttoxml is not supported" \ No newline at end of file diff --git a/tests/unit/plugins/filter/test_to_xml.py b/tests/unit/plugins/filter/test_to_xml.py index 91851a7c..a684c063 100644 --- a/tests/unit/plugins/filter/test_to_xml.py +++ b/tests/unit/plugins/filter/test_to_xml.py @@ -13,11 +13,11 @@ INVALID_DATA = '' -VALID_DATA = """{ - "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", - } - }""" +VALID_DATA = { + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + } +} OUTPUT = """ """ From 43a49ec96e870c137c3a102dfddf492296116de1 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Thu, 1 Apr 2021 15:06:50 +0530 Subject: [PATCH 26/31] Address review comments --- plugins/filter/to_xml.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/filter/to_xml.py b/plugins/filter/to_xml.py index f21acf34..126a7446 100644 --- a/plugins/filter/to_xml.py +++ b/plugins/filter/to_xml.py @@ -42,7 +42,7 @@ - name: Define json data ansible.builtin.set_fact: - data: + data: "interface-configurations": "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" "interface-configuration": @@ -75,7 +75,7 @@ - name: Define json data ansible.builtin.set_fact: - data: + data: "interface-configurations": "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" "interface-configuration": From 12b04eb71fc8a4b3289c90ec47e6d687930683dc Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Thu, 1 Apr 2021 15:19:34 +0530 Subject: [PATCH 27/31] fix linters --- tests/unit/plugins/filter/test_to_xml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/plugins/filter/test_to_xml.py b/tests/unit/plugins/filter/test_to_xml.py index a684c063..48cf7e88 100644 --- a/tests/unit/plugins/filter/test_to_xml.py +++ b/tests/unit/plugins/filter/test_to_xml.py @@ -15,7 +15,7 @@ VALID_DATA = { "interface-configurations": { - "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" } } From df52ffdb8d64e876ddd7bd9b18a485a5cda168b8 Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Thu, 1 Apr 2021 18:28:47 +0530 Subject: [PATCH 28/31] fix sanity --- plugins/plugin_utils/to_xml.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/plugin_utils/to_xml.py b/plugins/plugin_utils/to_xml.py index 2f967713..4c42827a 100644 --- a/plugins/plugin_utils/to_xml.py +++ b/plugins/plugin_utils/to_xml.py @@ -12,7 +12,6 @@ __metaclass__ = type -import ast from ansible.errors import AnsibleFilterError try: From ddfa8e365e9c8406ef746939159c1cf6352bee2a Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Thu, 1 Apr 2021 20:11:43 +0530 Subject: [PATCH 29/31] Add docs --- README.md | 4 +- docs/ansible.utils.from_xml_filter.rst | 173 +++++++++++++++++++++++++ docs/ansible.utils.to_xml_filter.rst | 166 ++++++++++++++++++++++++ 3 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 docs/ansible.utils.from_xml_filter.rst create mode 100644 docs/ansible.utils.to_xml_filter.rst diff --git a/README.md b/README.md index 80494ede..893f6659 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The Ansible ``ansible.utils`` collection includes a variety of plugins that aid ## Ansible version compatibility -This collection has been tested against following Ansible versions: **>=2.9.10,<2.11**. +This collection has been tested against following Ansible versions: **>=2.9.10**. Plugins and modules within a collection may be tested with only specific Ansible versions. A collection may contain metadata that identifies these versions. @@ -21,9 +21,11 @@ PEP440 is the schema used to describe the versions of Ansible. ### Filter plugins Name | Description --- | --- +[ansible.utils.from_xml](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.from_xml_filter.rst)|convert given xml string to native python dictionary. [ansible.utils.get_path](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.get_path_filter.rst)|Retrieve the value in a variable using a path [ansible.utils.index_of](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.index_of_filter.rst)|Find the indices of items in a list matching some criteria [ansible.utils.to_paths](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_paths_filter.rst)|Flatten a complex object into a dictionary of paths and values +[ansible.utils.to_xml](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_xml_filter.rst)|convert given json string to xml [ansible.utils.validate](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.validate_filter.rst)|Validate data with provided criteria ### Lookup plugins diff --git a/docs/ansible.utils.from_xml_filter.rst b/docs/ansible.utils.from_xml_filter.rst new file mode 100644 index 00000000..1a565ad4 --- /dev/null +++ b/docs/ansible.utils.from_xml_filter.rst @@ -0,0 +1,173 @@ +.. _ansible.utils.from_xml_filter: + + +********************** +ansible.utils.from_xml +********************** + +**convert given xml string to native python dictionary.** + + +Version added: 2.0.2 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This plugin converts the xml string to native python dictionary. +- Using the parameters below- ``data|ansible.utils.from_xml`` + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ data + +
+ string + / required +
+
+ + +
The input xml string .
+
This option represents the xml value that is passed to the filter plugin in pipe format.
+
For example config_data|ansible.utils.from_xml, in this case config_data represents this option.
+
+
+ engine + +
+ string +
+
+ Default:
"xmltodict"
+
+ +
Conversion library to use within the filter plugin.
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + #### Simple examples with out any engine. plugin will use default value as xmltodict + + tasks: + - name: convert given xml to native python dictionary + ansible.builtin.set_fact: + data: " + + " + + - debug: + msg: "{{ data|ansible.utils.from_xml }}" + + ##TASK###### + # TASK [convert given xml to json] ***************************************************************************************************** + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 + # ok: [localhost] => { + # "ansible_facts": { + # "data": " " + # }, + # "changed": false + # } + # + # TASK [debug] ************************************************************************************************************************* + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 + # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils + # ok: [localhost] => { + # "msg": { + # "netconf-state": { + # "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", + # "schemas": { + # "schema": null + # } + # } + # } + # } + + #### example2 with engine=xmltodict + + tasks: + - name: convert given xml to json + ansible.builtin.set_fact: + data: " + + " + + - debug: + msg: "{{ data|ansible.utils.from_xml('xmltodict') }}" + + ##TASK###### + # TASK [convert given xml to json] ***************************************************************************************************** + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 + # ok: [localhost] => { + # "ansible_facts": { + # "data": " " + # }, + # "changed": false + # } + # + # TASK [debug] ************************************************************************************************************************* + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:13 + # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils + # ok: [localhost] => { + # "msg": { + # "netconf-state": { + # "@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", + # "schemas": { + # "schema": null + # } + # } + # } + # } + + + + +Status +------ + + +Authors +~~~~~~~ + +- Ashwini Mhatre (@amhatre) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/docs/ansible.utils.to_xml_filter.rst b/docs/ansible.utils.to_xml_filter.rst new file mode 100644 index 00000000..46bda19e --- /dev/null +++ b/docs/ansible.utils.to_xml_filter.rst @@ -0,0 +1,166 @@ +.. _ansible.utils.to_xml_filter: + + +******************** +ansible.utils.to_xml +******************** + +**convert given json string to xml** + + +Version added: 2.0.2 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This plugin converts the json string to xml. +- Using the parameters below- ``data|ansible.utils.to_xml`` + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ data + +
+ dictionary + / required +
+
+ + +
The input json string .
+
This option represents the json value that is passed to the filter plugin in pipe format.
+
For example config_data|ansible.utils.to_xml, in this case config_data represents this option.
+
+
+ engine + +
+ string +
+
+ Default:
"xmltodict"
+
+ +
Conversion library to use within the filter plugin.
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + #### Simple examples with out any engine. plugin will use default value as xmltodict + + - name: Define json data + ansible.builtin.set_fact: + data: + "interface-configurations": + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" + "interface-configuration": + - debug: + msg: "{{ data|ansible.utils.to_xml }}" + + # TASK [Define json data ] ************************************************************************* + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 + # ok: [localhost] => { + # "ansible_facts": { + # "data": { + # "interface-configurations": { + # "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + # "interface-configuration": null + # } + # } + # }, + # "changed": false + # } + # + # TASK [debug] *********************************************************************************************************** + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 + # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils + # ok: [localhost] => { + # "msg": "\n\n\t\n" + # } + + #### example2 with engine=xmltodict + + - name: Define json data + ansible.builtin.set_fact: + data: + "interface-configurations": + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" + "interface-configuration": + - debug: + msg: "{{ data|ansible.utils.to_xml('xmltodict') }}" + + # TASK [Define json data ] ************************************************************************* + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 + # ok: [localhost] => { + # "ansible_facts": { + # "data": { + # "interface-configurations": { + # "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + # "interface-configuration": null + # } + # } + # }, + # "changed": false + # } + # TASK [debug] *********************************************************************************************************** + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 + # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils + # ok: [localhost] => { + # "msg": "\n\n\t\n" + # } + + + + +Status +------ + + +Authors +~~~~~~~ + +- Ashwini Mhatre (@amhatre) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. From d3694f7ab89f73f651988683593f125d1930a66b Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Thu, 8 Apr 2021 10:08:24 +0530 Subject: [PATCH 30/31] Address review comments --- docs/ansible.utils.from_xml_filter.rst | 16 ++++++++-------- docs/ansible.utils.to_xml_filter.rst | 16 ++++++++-------- plugins/filter/from_xml.py | 16 ++++++++-------- plugins/filter/to_xml.py | 16 ++++++++-------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/ansible.utils.from_xml_filter.rst b/docs/ansible.utils.from_xml_filter.rst index 1a565ad4..d1d43d45 100644 --- a/docs/ansible.utils.from_xml_filter.rst +++ b/docs/ansible.utils.from_xml_filter.rst @@ -5,7 +5,7 @@ ansible.utils.from_xml ********************** -**convert given xml string to native python dictionary.** +**Convert given XML string to native python dictionary.** Version added: 2.0.2 @@ -17,7 +17,7 @@ Version added: 2.0.2 Synopsis -------- -- This plugin converts the xml string to native python dictionary. +- This plugin converts the XML string to a native python dictionary. - Using the parameters below- ``data|ansible.utils.from_xml`` @@ -50,8 +50,8 @@ Parameters -
The input xml string .
-
This option represents the xml value that is passed to the filter plugin in pipe format.
+
The input XML string.
+
This option represents the XML value that is passed to the filter plugin in pipe format.
For example config_data|ansible.utils.from_xml, in this case config_data represents this option.
@@ -87,7 +87,7 @@ Examples #### Simple examples with out any engine. plugin will use default value as xmltodict tasks: - - name: convert given xml to native python dictionary + - name: convert given XML to native python dictionary ansible.builtin.set_fact: data: " @@ -97,7 +97,7 @@ Examples msg: "{{ data|ansible.utils.from_xml }}" ##TASK###### - # TASK [convert given xml to json] ***************************************************************************************************** + # TASK [convert given XML to native python dictionary] ***************************************************************************************************** # task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 # ok: [localhost] => { # "ansible_facts": { @@ -123,7 +123,7 @@ Examples #### example2 with engine=xmltodict tasks: - - name: convert given xml to json + - name: convert given XML to native python dictionary ansible.builtin.set_fact: data: " @@ -133,7 +133,7 @@ Examples msg: "{{ data|ansible.utils.from_xml('xmltodict') }}" ##TASK###### - # TASK [convert given xml to json] ***************************************************************************************************** + # TASK [convert given XML to native python dictionary] ***************************************************************************************************** # task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 # ok: [localhost] => { # "ansible_facts": { diff --git a/docs/ansible.utils.to_xml_filter.rst b/docs/ansible.utils.to_xml_filter.rst index 46bda19e..40d3490e 100644 --- a/docs/ansible.utils.to_xml_filter.rst +++ b/docs/ansible.utils.to_xml_filter.rst @@ -5,7 +5,7 @@ ansible.utils.to_xml ******************** -**convert given json string to xml** +**Convert given JSON string to XML** Version added: 2.0.2 @@ -17,7 +17,7 @@ Version added: 2.0.2 Synopsis -------- -- This plugin converts the json string to xml. +- This plugin converts the JSON string to XML. - Using the parameters below- ``data|ansible.utils.to_xml`` @@ -50,8 +50,8 @@ Parameters -
The input json string .
-
This option represents the json value that is passed to the filter plugin in pipe format.
+
The input JSON string .
+
This option represents the JSON value that is passed to the filter plugin in pipe format.
For example config_data|ansible.utils.to_xml, in this case config_data represents this option.
@@ -86,7 +86,7 @@ Examples #### Simple examples with out any engine. plugin will use default value as xmltodict - - name: Define json data + - name: Define JSON data ansible.builtin.set_fact: data: "interface-configurations": @@ -95,7 +95,7 @@ Examples - debug: msg: "{{ data|ansible.utils.to_xml }}" - # TASK [Define json data ] ************************************************************************* + # TASK [Define JSON data ] ************************************************************************* # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 # ok: [localhost] => { # "ansible_facts": { @@ -119,7 +119,7 @@ Examples #### example2 with engine=xmltodict - - name: Define json data + - name: Define JSON data ansible.builtin.set_fact: data: "interface-configurations": @@ -128,7 +128,7 @@ Examples - debug: msg: "{{ data|ansible.utils.to_xml('xmltodict') }}" - # TASK [Define json data ] ************************************************************************* + # TASK [Define JSON data ] ************************************************************************* # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 # ok: [localhost] => { # "ansible_facts": { diff --git a/plugins/filter/from_xml.py b/plugins/filter/from_xml.py index 0d079221..d2f8b701 100644 --- a/plugins/filter/from_xml.py +++ b/plugins/filter/from_xml.py @@ -16,15 +16,15 @@ name: from_xml author: Ashwini Mhatre (@amhatre) version_added: "2.0.2" - short_description: convert given xml string to native python dictionary. + short_description: Convert given XML string to native python dictionary. description: - - This plugin converts the xml string to native python dictionary. + - This plugin converts the XML string to a native python dictionary. - Using the parameters below- C(data|ansible.utils.from_xml) options: data: description: - - The input xml string . - - This option represents the xml value that is passed to the filter plugin in pipe format. + - The input XML string. + - This option represents the XML value that is passed to the filter plugin in pipe format. - For example C(config_data|ansible.utils.from_xml), in this case C(config_data) represents this option. type: str required: True @@ -40,7 +40,7 @@ #### Simple examples with out any engine. plugin will use default value as xmltodict tasks: - - name: convert given xml to native python dictionary + - name: convert given XML to native python dictionary ansible.builtin.set_fact: data: " @@ -50,7 +50,7 @@ msg: "{{ data|ansible.utils.from_xml }}" ##TASK###### -# TASK [convert given xml to json] ***************************************************************************************************** +# TASK [convert given XML to native python dictionary] ***************************************************************************************************** # task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 # ok: [localhost] => { # "ansible_facts": { @@ -76,7 +76,7 @@ #### example2 with engine=xmltodict tasks: - - name: convert given xml to json + - name: convert given XML to native python dictionary ansible.builtin.set_fact: data: " @@ -86,7 +86,7 @@ msg: "{{ data|ansible.utils.from_xml('xmltodict') }}" ##TASK###### -# TASK [convert given xml to json] ***************************************************************************************************** +# TASK [convert given XML to native python dictionary] ***************************************************************************************************** # task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5 # ok: [localhost] => { # "ansible_facts": { diff --git a/plugins/filter/to_xml.py b/plugins/filter/to_xml.py index 126a7446..5e27aef7 100644 --- a/plugins/filter/to_xml.py +++ b/plugins/filter/to_xml.py @@ -17,15 +17,15 @@ name: to_xml author: Ashwini Mhatre (@amhatre) version_added: "2.0.2" - short_description: convert given json string to xml + short_description: Convert given JSON string to XML description: - - This plugin converts the json string to xml. + - This plugin converts the JSON string to XML. - Using the parameters below- C(data|ansible.utils.to_xml) options: data: description: - - The input json string . - - This option represents the json value that is passed to the filter plugin in pipe format. + - The input JSON string . + - This option represents the JSON value that is passed to the filter plugin in pipe format. - For example C(config_data|ansible.utils.to_xml), in this case C(config_data) represents this option. type: dict required: True @@ -40,7 +40,7 @@ #### Simple examples with out any engine. plugin will use default value as xmltodict -- name: Define json data +- name: Define JSON data ansible.builtin.set_fact: data: "interface-configurations": @@ -49,7 +49,7 @@ - debug: msg: "{{ data|ansible.utils.to_xml }}" -# TASK [Define json data ] ************************************************************************* +# TASK [Define JSON data ] ************************************************************************* # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 # ok: [localhost] => { # "ansible_facts": { @@ -73,7 +73,7 @@ #### example2 with engine=xmltodict -- name: Define json data +- name: Define JSON data ansible.builtin.set_fact: data: "interface-configurations": @@ -82,7 +82,7 @@ - debug: msg: "{{ data|ansible.utils.to_xml('xmltodict') }}" -# TASK [Define json data ] ************************************************************************* +# TASK [Define JSON data ] ************************************************************************* # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 # ok: [localhost] => { # "ansible_facts": { From f395e39ee6681ce27fe1fb95450b1983680041de Mon Sep 17 00:00:00 2001 From: ashwini-mhatre Date: Thu, 8 Apr 2021 10:10:40 +0530 Subject: [PATCH 31/31] Address review comments --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 893f6659..40e00620 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The Ansible ``ansible.utils`` collection includes a variety of plugins that aid ## Ansible version compatibility -This collection has been tested against following Ansible versions: **>=2.9.10**. +This collection has been tested against following Ansible versions: **>=2.9.10,<2.12**. Plugins and modules within a collection may be tested with only specific Ansible versions. A collection may contain metadata that identifies these versions. @@ -21,11 +21,11 @@ PEP440 is the schema used to describe the versions of Ansible. ### Filter plugins Name | Description --- | --- -[ansible.utils.from_xml](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.from_xml_filter.rst)|convert given xml string to native python dictionary. +[ansible.utils.from_xml](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.from_xml_filter.rst)|Convert given XML string to native python dictionary. [ansible.utils.get_path](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.get_path_filter.rst)|Retrieve the value in a variable using a path [ansible.utils.index_of](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.index_of_filter.rst)|Find the indices of items in a list matching some criteria [ansible.utils.to_paths](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_paths_filter.rst)|Flatten a complex object into a dictionary of paths and values -[ansible.utils.to_xml](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_xml_filter.rst)|convert given json string to xml +[ansible.utils.to_xml](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_xml_filter.rst)|Convert given JSON string to XML [ansible.utils.validate](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.validate_filter.rst)|Validate data with provided criteria ### Lookup plugins