Add to_xml and from_xml filter plugin (#56)
Add to_xml and from_xml filter plugin Reviewed-by: https://github.com/apps/ansible-zuulpull/67/head
parent
c04eecdfc2
commit
43e77543d9
|
@ -21,9 +21,11 @@ PEP440 is the schema used to describe the versions of Ansible.
|
||||||
### Filter plugins
|
### Filter plugins
|
||||||
Name | Description
|
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.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.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_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
|
[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
|
### Lookup plugins
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
minor_changes:
|
||||||
|
- Add from_xml and to_xml fiter plugin (https://github.com/ansible-collections/ansible.utils/pull/56).
|
|
@ -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 a native python dictionary.
|
||||||
|
- Using the parameters below- ``data|ansible.utils.from_xml``
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<table border=0 cellpadding=0 class="documentation-table">
|
||||||
|
<tr>
|
||||||
|
<th colspan="1">Parameter</th>
|
||||||
|
<th>Choices/<font color="blue">Defaults</font></th>
|
||||||
|
<th>Configuration</th>
|
||||||
|
<th width="100%">Comments</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>data</b>
|
||||||
|
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||||
|
<div style="font-size: small">
|
||||||
|
<span style="color: purple">string</span>
|
||||||
|
/ <span style="color: red">required</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>The input XML string.</div>
|
||||||
|
<div>This option represents the XML value that is passed to the filter plugin in pipe format.</div>
|
||||||
|
<div>For example <code>config_data|ansible.utils.from_xml</code>, in this case <code>config_data</code> represents this option.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>engine</b>
|
||||||
|
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||||
|
<div style="font-size: small">
|
||||||
|
<span style="color: purple">string</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<b>Default:</b><br/><div style="color: blue">"xmltodict"</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>Conversion library to use within the filter plugin.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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: "
|
||||||
|
<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state>
|
||||||
|
"
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "{{ data|ansible.utils.from_xml }}"
|
||||||
|
|
||||||
|
##TASK######
|
||||||
|
# TASK [convert given XML to native python dictionary] *****************************************************************************************************
|
||||||
|
# task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5
|
||||||
|
# ok: [localhost] => {
|
||||||
|
# "ansible_facts": {
|
||||||
|
# "data": " <netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state> "
|
||||||
|
# },
|
||||||
|
# "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 native python dictionary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data: "
|
||||||
|
<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state>
|
||||||
|
"
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "{{ data|ansible.utils.from_xml('xmltodict') }}"
|
||||||
|
|
||||||
|
##TASK######
|
||||||
|
# TASK [convert given XML to native python dictionary] *****************************************************************************************************
|
||||||
|
# task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5
|
||||||
|
# ok: [localhost] => {
|
||||||
|
# "ansible_facts": {
|
||||||
|
# "data": " <netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state> "
|
||||||
|
# },
|
||||||
|
# "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.
|
|
@ -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
|
||||||
|
|
||||||
|
<table border=0 cellpadding=0 class="documentation-table">
|
||||||
|
<tr>
|
||||||
|
<th colspan="1">Parameter</th>
|
||||||
|
<th>Choices/<font color="blue">Defaults</font></th>
|
||||||
|
<th>Configuration</th>
|
||||||
|
<th width="100%">Comments</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>data</b>
|
||||||
|
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||||
|
<div style="font-size: small">
|
||||||
|
<span style="color: purple">dictionary</span>
|
||||||
|
/ <span style="color: red">required</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>The input JSON string .</div>
|
||||||
|
<div>This option represents the JSON value that is passed to the filter plugin in pipe format.</div>
|
||||||
|
<div>For example <code>config_data|ansible.utils.to_xml</code>, in this case <code>config_data</code> represents this option.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>engine</b>
|
||||||
|
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||||
|
<div style="font-size: small">
|
||||||
|
<span style="color: purple">string</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<b>Default:</b><br/><div style="color: blue">"xmltodict"</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>Conversion library to use within the filter plugin.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<interface-configurations xmlns=\"http://cisco.com/ns/yang/
|
||||||
|
# Cisco-IOS-XR-ifmgr-cfg\">\n\t<interface-configuration></interface-configuration>\n</interface-configurations>"
|
||||||
|
# }
|
||||||
|
|
||||||
|
#### 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": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<interface-configurations xmlns=\"http://cisco.com/ns/yang/
|
||||||
|
# Cisco-IOS-XR-ifmgr-cfg\">\n\t<interface-configuration></interface-configuration>\n</interface-configurations>"
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,145 @@
|
||||||
|
#
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2021 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
The from_xml filter plugin
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
name: from_xml
|
||||||
|
author: Ashwini Mhatre (@amhatre)
|
||||||
|
version_added: "2.0.2"
|
||||||
|
short_description: Convert given XML string to native python dictionary.
|
||||||
|
description:
|
||||||
|
- 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.
|
||||||
|
- For example C(config_data|ansible.utils.from_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 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: "
|
||||||
|
<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state>
|
||||||
|
"
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "{{ data|ansible.utils.from_xml }}"
|
||||||
|
|
||||||
|
##TASK######
|
||||||
|
# TASK [convert given XML to native python dictionary] *****************************************************************************************************
|
||||||
|
# task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5
|
||||||
|
# ok: [localhost] => {
|
||||||
|
# "ansible_facts": {
|
||||||
|
# "data": " <netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state> "
|
||||||
|
# },
|
||||||
|
# "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 native python dictionary
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data: "
|
||||||
|
<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state>
|
||||||
|
"
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "{{ data|ansible.utils.from_xml('xmltodict') }}"
|
||||||
|
|
||||||
|
##TASK######
|
||||||
|
# TASK [convert given XML to native python dictionary] *****************************************************************************************************
|
||||||
|
# task path: /Users/amhatre/ansible-collections/playbooks/test_utils.yaml:5
|
||||||
|
# ok: [localhost] => {
|
||||||
|
# "ansible_facts": {
|
||||||
|
# "data": " <netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state> "
|
||||||
|
# },
|
||||||
|
# "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 jinja2.filters import environmentfilter
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
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="from_xml"
|
||||||
|
)
|
||||||
|
valid, errors, updated_data = aav.validate()
|
||||||
|
if not valid:
|
||||||
|
raise AnsibleFilterError(errors)
|
||||||
|
return from_xml(**updated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
""" from_xml """
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
|
||||||
|
"""a mapping of filter names to functions"""
|
||||||
|
return {"from_xml": _from_xml}
|
|
@ -0,0 +1,138 @@
|
||||||
|
#
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2021 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
The to_xml filter plugin
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
name: to_xml
|
||||||
|
author: Ashwini Mhatre (@amhatre)
|
||||||
|
version_added: "2.0.2"
|
||||||
|
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.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.to_xml), in this case C(config_data) represents this option.
|
||||||
|
type: dict
|
||||||
|
required: True
|
||||||
|
engine:
|
||||||
|
description:
|
||||||
|
- Conversion library to use within the filter plugin.
|
||||||
|
type: str
|
||||||
|
default: xmltodict
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r"""
|
||||||
|
|
||||||
|
#### 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": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<interface-configurations xmlns=\"http://cisco.com/ns/yang/
|
||||||
|
# Cisco-IOS-XR-ifmgr-cfg\">\n\t<interface-configuration></interface-configuration>\n</interface-configurations>"
|
||||||
|
# }
|
||||||
|
|
||||||
|
#### 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": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<interface-configurations xmlns=\"http://cisco.com/ns/yang/
|
||||||
|
# Cisco-IOS-XR-ifmgr-cfg\">\n\t<interface-configuration></interface-configuration>\n</interface-configurations>"
|
||||||
|
# }
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from jinja2.filters import environmentfilter
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
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="to_xml"
|
||||||
|
)
|
||||||
|
valid, errors, updated_data = aav.validate()
|
||||||
|
if not valid:
|
||||||
|
raise AnsibleFilterError(errors)
|
||||||
|
return to_xml(**updated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
""" to_xml """
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
"""a mapping of filter names to functions"""
|
||||||
|
return {"to_xml": _to_xml}
|
|
@ -0,0 +1,52 @@
|
||||||
|
#
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2021 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
The from_xml plugin code
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
|
||||||
|
try:
|
||||||
|
import xmltodict
|
||||||
|
|
||||||
|
HAS_XMLTODICT = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_XMLTODICT = False
|
||||||
|
|
||||||
|
|
||||||
|
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 'from_xml': {msg}".format(msg=msg)
|
||||||
|
raise AnsibleFilterError(error)
|
||||||
|
|
||||||
|
|
||||||
|
def from_xml(data, engine):
|
||||||
|
"""Convert data which is in 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":
|
||||||
|
if not HAS_XMLTODICT:
|
||||||
|
_raise_error("Missing required library xmltodict")
|
||||||
|
try:
|
||||||
|
res = json.dumps(xmltodict.parse(data))
|
||||||
|
except Exception:
|
||||||
|
_raise_error("Input Xml is not valid")
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
error = "engine: {engine} is not supported ".format(engine=engine)
|
||||||
|
_raise_error(error)
|
|
@ -0,0 +1,53 @@
|
||||||
|
#
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2021 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
The to_xml plugin
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
|
||||||
|
try:
|
||||||
|
import xmltodict
|
||||||
|
|
||||||
|
HAS_XMLTODICT = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_XMLTODICT = False
|
||||||
|
|
||||||
|
|
||||||
|
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 'to_xml': {msg}".format(msg=msg)
|
||||||
|
raise AnsibleFilterError(error)
|
||||||
|
|
||||||
|
|
||||||
|
def to_xml(data, engine):
|
||||||
|
"""Convert data which is in json to xml"
|
||||||
|
|
||||||
|
:param data: The data passed in (data|to_xml(...))
|
||||||
|
:type data: xml
|
||||||
|
:param engine: Conversion library default=xmltodict
|
||||||
|
"""
|
||||||
|
if engine == "xmltodict":
|
||||||
|
if not HAS_XMLTODICT:
|
||||||
|
_raise_error("Missing required library xmltodict")
|
||||||
|
try:
|
||||||
|
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)
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
- name: Setup xml and expected json
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data: "
|
||||||
|
<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"><schemas><schema/></schemas></netconf-state>
|
||||||
|
"
|
||||||
|
output: {
|
||||||
|
"netconf-state": {
|
||||||
|
"@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring",
|
||||||
|
"schemas": {
|
||||||
|
"schema": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
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.from_xml() }}"
|
||||||
|
- test: "{{ data|ansible.utils.from_xml('xmltodict') }}"
|
||||||
|
|
||||||
|
- name: Setup invalid xml as input to ansible.utils.from_xml.
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data: "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">"
|
||||||
|
|
||||||
|
- name: validate input xml
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_result: "{{ data|ansible.utils.from_xml() }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that: "{{ msg in result.msg }}"
|
||||||
|
vars:
|
||||||
|
msg: "Error when using plugin 'from_xml': Input Xml is not valid"
|
|
@ -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 }}"
|
|
@ -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":
|
||||||
|
output: "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<interface-configurations xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg\">\n\t<interface-configuration></interface-configuration>\n</interface-configurations>"
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
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.to_xml() }}"
|
||||||
|
- test: "{{ data|ansible.utils.to_xml('xmltodict') }}"
|
||||||
|
|
||||||
|
- name: test for supported engine for to_xml filter
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
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('dicttoxml') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that: "{{ msg in result.msg }}"
|
||||||
|
vars:
|
||||||
|
msg: "Error when using plugin 'to_xml': engine: dicttoxml is not supported"
|
|
@ -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 }}"
|
|
@ -0,0 +1,49 @@
|
||||||
|
# -*- 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.from_xml import from_xml
|
||||||
|
|
||||||
|
INVALID_DATA = '<netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">'
|
||||||
|
|
||||||
|
VALID_DATA = (
|
||||||
|
'<netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">'
|
||||||
|
"<schemas><schema/></schemas></netconf-state>"
|
||||||
|
)
|
||||||
|
|
||||||
|
OUTPUT = """{"netconf-state": \
|
||||||
|
{"@xmlns": "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", "schemas": {"schema": null}}}"""
|
||||||
|
|
||||||
|
|
||||||
|
class TestFromXml(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:
|
||||||
|
from_xml(*args, **kwargs)
|
||||||
|
print(str(error.exception))
|
||||||
|
self.assertIn(
|
||||||
|
"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
|
||||||
|
args = [VALID_DATA, "xmltodict"]
|
||||||
|
result = from_xml(*args)
|
||||||
|
print(result)
|
||||||
|
self.assertEqual(result, OUTPUT)
|
|
@ -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.to_xml import to_xml
|
||||||
|
|
||||||
|
INVALID_DATA = '<netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">'
|
||||||
|
|
||||||
|
VALID_DATA = {
|
||||||
|
"interface-configurations": {
|
||||||
|
"@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OUTPUT = """<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<interface-configurations xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"></interface-configurations>"""
|
||||||
|
|
||||||
|
|
||||||
|
class TestToXml(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:
|
||||||
|
to_xml(*args, **kwargs)
|
||||||
|
print(str(error.exception))
|
||||||
|
self.assertIn(
|
||||||
|
"Error when using plugin '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 = to_xml(*args)
|
||||||
|
print(result)
|
||||||
|
self.assertEqual(result, OUTPUT)
|
Loading…
Reference in New Issue