summaryrefslogtreecommitdiff
path: root/test/support
diff options
context:
space:
mode:
authorMatt Martz <matt@sivel.net>2020-09-29 08:39:44 -0500
committerGitHub <noreply@github.com>2020-09-29 08:39:44 -0500
commitf2512f793135c9e7bfba71d95241935afad92f4c (patch)
treed976e1485b58a0c76a80a1500117ad2fde75423a /test/support
parentcbd3df369f0efa4f67b74ad76c431a3b2c357f26 (diff)
downloadansible-f2512f793135c9e7bfba71d95241935afad92f4c.tar.gz
Remove incidental_xml integration tests (#71917)
* Add explicit integration tests to cover incidental_xml coverage * normalize diff * More normalization * diff without changed * ci_complete ci_coverage * nuke incidental_xml * ci_complete ci_coverage
Diffstat (limited to 'test/support')
-rw-r--r--test/support/integration/plugins/modules/xml.py966
1 files changed, 0 insertions, 966 deletions
diff --git a/test/support/integration/plugins/modules/xml.py b/test/support/integration/plugins/modules/xml.py
deleted file mode 100644
index b5b35a384e..0000000000
--- a/test/support/integration/plugins/modules/xml.py
+++ /dev/null
@@ -1,966 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2014, Red Hat, Inc.
-# Copyright: (c) 2014, Tim Bielawa <tbielawa@redhat.com>
-# Copyright: (c) 2014, Magnus Hedemark <mhedemar@redhat.com>
-# Copyright: (c) 2017, Dag Wieers <dag@wieers.com>
-# 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = r'''
----
-module: xml
-short_description: Manage bits and pieces of XML files or strings
-description:
-- A CRUD-like interface to managing bits of XML files.
-version_added: '2.4'
-options:
- path:
- description:
- - Path to the file to operate on.
- - This file must exist ahead of time.
- - This parameter is required, unless C(xmlstring) is given.
- type: path
- required: yes
- aliases: [ dest, file ]
- xmlstring:
- description:
- - A string containing XML on which to operate.
- - This parameter is required, unless C(path) is given.
- type: str
- required: yes
- xpath:
- description:
- - A valid XPath expression describing the item(s) you want to manipulate.
- - Operates on the document root, C(/), by default.
- type: str
- namespaces:
- description:
- - The namespace C(prefix:uri) mapping for the XPath expression.
- - Needs to be a C(dict), not a C(list) of items.
- type: dict
- state:
- description:
- - Set or remove an xpath selection (node(s), attribute(s)).
- type: str
- choices: [ absent, present ]
- default: present
- aliases: [ ensure ]
- attribute:
- description:
- - The attribute to select when using parameter C(value).
- - This is a string, not prepended with C(@).
- type: raw
- value:
- description:
- - Desired state of the selected attribute.
- - Either a string, or to unset a value, the Python C(None) keyword (YAML Equivalent, C(null)).
- - Elements default to no value (but present).
- - Attributes default to an empty string.
- type: raw
- add_children:
- description:
- - Add additional child-element(s) to a selected element for a given C(xpath).
- - Child elements must be given in a list and each item may be either a string
- (eg. C(children=ansible) to add an empty C(<ansible/>) child element),
- or a hash where the key is an element name and the value is the element value.
- - This parameter requires C(xpath) to be set.
- type: list
- set_children:
- description:
- - Set the child-element(s) of a selected element for a given C(xpath).
- - Removes any existing children.
- - Child elements must be specified as in C(add_children).
- - This parameter requires C(xpath) to be set.
- type: list
- count:
- description:
- - Search for a given C(xpath) and provide the count of any matches.
- - This parameter requires C(xpath) to be set.
- type: bool
- default: no
- print_match:
- description:
- - Search for a given C(xpath) and print out any matches.
- - This parameter requires C(xpath) to be set.
- type: bool
- default: no
- pretty_print:
- description:
- - Pretty print XML output.
- type: bool
- default: no
- content:
- description:
- - Search for a given C(xpath) and get content.
- - This parameter requires C(xpath) to be set.
- type: str
- choices: [ attribute, text ]
- input_type:
- description:
- - Type of input for C(add_children) and C(set_children).
- type: str
- choices: [ xml, yaml ]
- default: yaml
- backup:
- description:
- - Create a backup file including the timestamp information so you can get
- the original file back if you somehow clobbered it incorrectly.
- type: bool
- default: no
- strip_cdata_tags:
- description:
- - Remove CDATA tags surrounding text values.
- - Note that this might break your XML file if text values contain characters that could be interpreted as XML.
- type: bool
- default: no
- version_added: '2.7'
- insertbefore:
- description:
- - Add additional child-element(s) before the first selected element for a given C(xpath).
- - Child elements must be given in a list and each item may be either a string
- (eg. C(children=ansible) to add an empty C(<ansible/>) child element),
- or a hash where the key is an element name and the value is the element value.
- - This parameter requires C(xpath) to be set.
- type: bool
- default: no
- version_added: '2.8'
- insertafter:
- description:
- - Add additional child-element(s) after the last selected element for a given C(xpath).
- - Child elements must be given in a list and each item may be either a string
- (eg. C(children=ansible) to add an empty C(<ansible/>) child element),
- or a hash where the key is an element name and the value is the element value.
- - This parameter requires C(xpath) to be set.
- type: bool
- default: no
- version_added: '2.8'
-requirements:
-- lxml >= 2.3.0
-notes:
-- Use the C(--check) and C(--diff) options when testing your expressions.
-- The diff output is automatically pretty-printed, so may not reflect the actual file content, only the file structure.
-- This module does not handle complicated xpath expressions, so limit xpath selectors to simple expressions.
-- Beware that in case your XML elements are namespaced, you need to use the C(namespaces) parameter, see the examples.
-- Namespaces prefix should be used for all children of an element where namespace is defined, unless another namespace is defined for them.
-seealso:
-- name: Xml module development community wiki
- description: More information related to the development of this xml module.
- link: https://github.com/ansible/community/wiki/Module:-xml
-- name: Introduction to XPath
- description: A brief tutorial on XPath (w3schools.com).
- link: https://www.w3schools.com/xml/xpath_intro.asp
-- name: XPath Reference document
- description: The reference documentation on XSLT/XPath (developer.mozilla.org).
- link: https://developer.mozilla.org/en-US/docs/Web/XPath
-author:
-- Tim Bielawa (@tbielawa)
-- Magnus Hedemark (@magnus919)
-- Dag Wieers (@dagwieers)
-'''
-
-EXAMPLES = r'''
-# Consider the following XML file:
-#
-# <business type="bar">
-# <name>Tasty Beverage Co.</name>
-# <beers>
-# <beer>Rochefort 10</beer>
-# <beer>St. Bernardus Abbot 12</beer>
-# <beer>Schlitz</beer>
-# </beers>
-# <rating subjective="true">10</rating>
-# <website>
-# <mobilefriendly/>
-# <address>http://tastybeverageco.com</address>
-# </website>
-# </business>
-
-- name: Remove the 'subjective' attribute of the 'rating' element
- xml:
- path: /foo/bar.xml
- xpath: /business/rating/@subjective
- state: absent
-
-- name: Set the rating to '11'
- xml:
- path: /foo/bar.xml
- xpath: /business/rating
- value: 11
-
-# Retrieve and display the number of nodes
-- name: Get count of 'beers' nodes
- xml:
- path: /foo/bar.xml
- xpath: /business/beers/beer
- count: yes
- register: hits
-
-- debug:
- var: hits.count
-
-# Example where parent XML nodes are created automatically
-- name: Add a 'phonenumber' element to the 'business' element
- xml:
- path: /foo/bar.xml
- xpath: /business/phonenumber
- value: 555-555-1234
-
-- name: Add several more beers to the 'beers' element
- xml:
- path: /foo/bar.xml
- xpath: /business/beers
- add_children:
- - beer: Old Rasputin
- - beer: Old Motor Oil
- - beer: Old Curmudgeon
-
-- name: Add several more beers to the 'beers' element and add them before the 'Rochefort 10' element
- xml:
- path: /foo/bar.xml
- xpath: '/business/beers/beer[text()="Rochefort 10"]'
- insertbefore: yes
- add_children:
- - beer: Old Rasputin
- - beer: Old Motor Oil
- - beer: Old Curmudgeon
-
-# NOTE: The 'state' defaults to 'present' and 'value' defaults to 'null' for elements
-- name: Add a 'validxhtml' element to the 'website' element
- xml:
- path: /foo/bar.xml
- xpath: /business/website/validxhtml
-
-- name: Add an empty 'validatedon' attribute to the 'validxhtml' element
- xml:
- path: /foo/bar.xml
- xpath: /business/website/validxhtml/@validatedon
-
-- name: Add or modify an attribute, add element if needed
- xml:
- path: /foo/bar.xml
- xpath: /business/website/validxhtml
- attribute: validatedon
- value: 1976-08-05
-
-# How to read an attribute value and access it in Ansible
-- name: Read an element's attribute values
- xml:
- path: /foo/bar.xml
- xpath: /business/website/validxhtml
- content: attribute
- register: xmlresp
-
-- name: Show an attribute value
- debug:
- var: xmlresp.matches[0].validxhtml.validatedon
-
-- name: Remove all children from the 'website' element (option 1)
- xml:
- path: /foo/bar.xml
- xpath: /business/website/*
- state: absent
-
-- name: Remove all children from the 'website' element (option 2)
- xml:
- path: /foo/bar.xml
- xpath: /business/website
- children: []
-
-# In case of namespaces, like in below XML, they have to be explicitly stated.
-#
-# <foo xmlns="http://x.test" xmlns:attr="http://z.test">
-# <bar>
-# <baz xmlns="http://y.test" attr:my_namespaced_attribute="true" />
-# </bar>
-# </foo>
-
-# NOTE: There is the prefix 'x' in front of the 'bar' element, too.
-- name: Set namespaced '/x:foo/x:bar/y:baz/@z:my_namespaced_attribute' to 'false'
- xml:
- path: foo.xml
- xpath: /x:foo/x:bar/y:baz
- namespaces:
- x: http://x.test
- y: http://y.test
- z: http://z.test
- attribute: z:my_namespaced_attribute
- value: 'false'
-'''
-
-RETURN = r'''
-actions:
- description: A dictionary with the original xpath, namespaces and state.
- type: dict
- returned: success
- sample: {xpath: xpath, namespaces: [namespace1, namespace2], state=present}
-backup_file:
- description: The name of the backup file that was created
- type: str
- returned: when backup=yes
- sample: /path/to/file.xml.1942.2017-08-24@14:16:01~
-count:
- description: The count of xpath matches.
- type: int
- returned: when parameter 'count' is set
- sample: 2
-matches:
- description: The xpath matches found.
- type: list
- returned: when parameter 'print_match' is set
-msg:
- description: A message related to the performed action(s).
- type: str
- returned: always
-xmlstring:
- description: An XML string of the resulting output.
- type: str
- returned: when parameter 'xmlstring' is set
-'''
-
-import copy
-import json
-import os
-import re
-import traceback
-
-from distutils.version import LooseVersion
-from io import BytesIO
-
-LXML_IMP_ERR = None
-try:
- from lxml import etree, objectify
- HAS_LXML = True
-except ImportError:
- LXML_IMP_ERR = traceback.format_exc()
- HAS_LXML = False
-
-from ansible.module_utils.basic import AnsibleModule, json_dict_bytes_to_unicode, missing_required_lib
-from ansible.module_utils.six import iteritems, string_types
-from ansible.module_utils._text import to_bytes, to_native
-from ansible.module_utils.common._collections_compat import MutableMapping
-
-_IDENT = r"[a-zA-Z-][a-zA-Z0-9_\-\.]*"
-_NSIDENT = _IDENT + "|" + _IDENT + ":" + _IDENT
-# Note: we can't reasonably support the 'if you need to put both ' and " in a string, concatenate
-# strings wrapped by the other delimiter' XPath trick, especially as simple XPath.
-_XPSTR = "('(?:.*)'|\"(?:.*)\")"
-
-_RE_SPLITSIMPLELAST = re.compile("^(.*)/(" + _NSIDENT + ")$")
-_RE_SPLITSIMPLELASTEQVALUE = re.compile("^(.*)/(" + _NSIDENT + ")/text\\(\\)=" + _XPSTR + "$")
-_RE_SPLITSIMPLEATTRLAST = re.compile("^(.*)/(@(?:" + _NSIDENT + "))$")
-_RE_SPLITSIMPLEATTRLASTEQVALUE = re.compile("^(.*)/(@(?:" + _NSIDENT + "))=" + _XPSTR + "$")
-_RE_SPLITSUBLAST = re.compile("^(.*)/(" + _NSIDENT + ")\\[(.*)\\]$")
-_RE_SPLITONLYEQVALUE = re.compile("^(.*)/text\\(\\)=" + _XPSTR + "$")
-
-
-def has_changed(doc):
- orig_obj = etree.tostring(objectify.fromstring(etree.tostring(orig_doc)))
- obj = etree.tostring(objectify.fromstring(etree.tostring(doc)))
- return (orig_obj != obj)
-
-
-def do_print_match(module, tree, xpath, namespaces):
- match = tree.xpath(xpath, namespaces=namespaces)
- match_xpaths = []
- for m in match:
- match_xpaths.append(tree.getpath(m))
- match_str = json.dumps(match_xpaths)
- msg = "selector '%s' match: %s" % (xpath, match_str)
- finish(module, tree, xpath, namespaces, changed=False, msg=msg)
-
-
-def count_nodes(module, tree, xpath, namespaces):
- """ Return the count of nodes matching the xpath """
- hits = tree.xpath("count(/%s)" % xpath, namespaces=namespaces)
- msg = "found %d nodes" % hits
- finish(module, tree, xpath, namespaces, changed=False, msg=msg, hitcount=int(hits))
-
-
-def is_node(tree, xpath, namespaces):
- """ Test if a given xpath matches anything and if that match is a node.
-
- For now we just assume you're only searching for one specific thing."""
- if xpath_matches(tree, xpath, namespaces):
- # OK, it found something
- match = tree.xpath(xpath, namespaces=namespaces)
- if isinstance(match[0], etree._Element):
- return True
-
- return False
-
-
-def is_attribute(tree, xpath, namespaces):
- """ Test if a given xpath matches and that match is an attribute
-
- An xpath attribute search will only match one item"""
- if xpath_matches(tree, xpath, namespaces):
- match = tree.xpath(xpath, namespaces=namespaces)
- if isinstance(match[0], etree._ElementStringResult):
- return True
- elif isinstance(match[0], etree._ElementUnicodeResult):
- return True
- return False
-
-
-def xpath_matches(tree, xpath, namespaces):
- """ Test if a node exists """
- if tree.xpath(xpath, namespaces=namespaces):
- return True
- return False
-
-
-def delete_xpath_target(module, tree, xpath, namespaces):
- """ Delete an attribute or element from a tree """
- try:
- for result in tree.xpath(xpath, namespaces=namespaces):
- # Get the xpath for this result
- if is_attribute(tree, xpath, namespaces):
- # Delete an attribute
- parent = result.getparent()
- # Pop this attribute match out of the parent
- # node's 'attrib' dict by using this match's
- # 'attrname' attribute for the key
- parent.attrib.pop(result.attrname)
- elif is_node(tree, xpath, namespaces):
- # Delete an element
- result.getparent().remove(result)
- else:
- raise Exception("Impossible error")
- except Exception as e:
- module.fail_json(msg="Couldn't delete xpath target: %s (%s)" % (xpath, e))
- else:
- finish(module, tree, xpath, namespaces, changed=True)
-
-
-def replace_children_of(children, match):
- for element in list(match):
- match.remove(element)
- match.extend(children)
-
-
-def set_target_children_inner(module, tree, xpath, namespaces, children, in_type):
- matches = tree.xpath(xpath, namespaces=namespaces)
-
- # Create a list of our new children
- children = children_to_nodes(module, children, in_type)
- children_as_string = [etree.tostring(c) for c in children]
-
- changed = False
-
- # xpaths always return matches as a list, so....
- for match in matches:
- # Check if elements differ
- if len(list(match)) == len(children):
- for idx, element in enumerate(list(match)):
- if etree.tostring(element) != children_as_string[idx]:
- replace_children_of(children, match)
- changed = True
- break
- else:
- replace_children_of(children, match)
- changed = True
-
- return changed
-
-
-def set_target_children(module, tree, xpath, namespaces, children, in_type):
- changed = set_target_children_inner(module, tree, xpath, namespaces, children, in_type)
- # Write it out
- finish(module, tree, xpath, namespaces, changed=changed)
-
-
-def add_target_children(module, tree, xpath, namespaces, children, in_type, insertbefore, insertafter):
- if is_node(tree, xpath, namespaces):
- new_kids = children_to_nodes(module, children, in_type)
- if insertbefore or insertafter:
- insert_target_children(tree, xpath, namespaces, new_kids, insertbefore, insertafter)
- else:
- for node in tree.xpath(xpath, namespaces=namespaces):
- node.extend(new_kids)
- finish(module, tree, xpath, namespaces, changed=True)
- else:
- finish(module, tree, xpath, namespaces)
-
-
-def insert_target_children(tree, xpath, namespaces, children, insertbefore, insertafter):
- """
- Insert the given children before or after the given xpath. If insertbefore is True, it is inserted before the
- first xpath hit, with insertafter, it is inserted after the last xpath hit.
- """
- insert_target = tree.xpath(xpath, namespaces=namespaces)
- loc_index = 0 if insertbefore else -1
- index_in_parent = insert_target[loc_index].getparent().index(insert_target[loc_index])
- parent = insert_target[0].getparent()
- if insertafter:
- index_in_parent += 1
- for child in children:
- parent.insert(index_in_parent, child)
- index_in_parent += 1
-
-
-def _extract_xpstr(g):
- return g[1:-1]
-
-
-def split_xpath_last(xpath):
- """split an XPath of the form /foo/bar/baz into /foo/bar and baz"""
- xpath = xpath.strip()
- m = _RE_SPLITSIMPLELAST.match(xpath)
- if m:
- # requesting an element to exist
- return (m.group(1), [(m.group(2), None)])
- m = _RE_SPLITSIMPLELASTEQVALUE.match(xpath)
- if m:
- # requesting an element to exist with an inner text
- return (m.group(1), [(m.group(2), _extract_xpstr(m.group(3)))])
-
- m = _RE_SPLITSIMPLEATTRLAST.match(xpath)
- if m:
- # requesting an attribute to exist
- return (m.group(1), [(m.group(2), None)])
- m = _RE_SPLITSIMPLEATTRLASTEQVALUE.match(xpath)
- if m:
- # requesting an attribute to exist with a value
- return (m.group(1), [(m.group(2), _extract_xpstr(m.group(3)))])
-
- m = _RE_SPLITSUBLAST.match(xpath)
- if m:
- content = [x.strip() for x in m.group(3).split(" and ")]
- return (m.group(1), [('/' + m.group(2), content)])
-
- m = _RE_SPLITONLYEQVALUE.match(xpath)
- if m:
- # requesting a change of inner text
- return (m.group(1), [("", _extract_xpstr(m.group(2)))])
- return (xpath, [])
-
-
-def nsnameToClark(name, namespaces):
- if ":" in name:
- (nsname, rawname) = name.split(":")
- # return "{{%s}}%s" % (namespaces[nsname], rawname)
- return "{{{0}}}{1}".format(namespaces[nsname], rawname)
-
- # no namespace name here
- return name
-
-
-def check_or_make_target(module, tree, xpath, namespaces):
- (inner_xpath, changes) = split_xpath_last(xpath)
- if (inner_xpath == xpath) or (changes is None):
- module.fail_json(msg="Can't process Xpath %s in order to spawn nodes! tree is %s" %
- (xpath, etree.tostring(tree, pretty_print=True)))
- return False
-
- changed = False
-
- if not is_node(tree, inner_xpath, namespaces):
- changed = check_or_make_target(module, tree, inner_xpath, namespaces)
-
- # we test again after calling check_or_make_target
- if is_node(tree, inner_xpath, namespaces) and changes:
- for (eoa, eoa_value) in changes:
- if eoa and eoa[0] != '@' and eoa[0] != '/':
- # implicitly creating an element
- new_kids = children_to_nodes(module, [nsnameToClark(eoa, namespaces)], "yaml")
- if eoa_value:
- for nk in new_kids:
- nk.text = eoa_value
-
- for node in tree.xpath(inner_xpath, namespaces=namespaces):
- node.extend(new_kids)
- changed = True
- # module.fail_json(msg="now tree=%s" % etree.tostring(tree, pretty_print=True))
- elif eoa and eoa[0] == '/':
- element = eoa[1:]
- new_kids = children_to_nodes(module, [nsnameToClark(element, namespaces)], "yaml")
- for node in tree.xpath(inner_xpath, namespaces=namespaces):
- node.extend(new_kids)
- for nk in new_kids:
- for subexpr in eoa_value:
- # module.fail_json(msg="element=%s subexpr=%s node=%s now tree=%s" %
- # (element, subexpr, etree.tostring(node, pretty_print=True), etree.tostring(tree, pretty_print=True))
- check_or_make_target(module, nk, "./" + subexpr, namespaces)
- changed = True
-
- # module.fail_json(msg="now tree=%s" % etree.tostring(tree, pretty_print=True))
- elif eoa == "":
- for node in tree.xpath(inner_xpath, namespaces=namespaces):
- if (node.text != eoa_value):
- node.text = eoa_value
- changed = True
-
- elif eoa and eoa[0] == '@':
- attribute = nsnameToClark(eoa[1:], namespaces)
-
- for element in tree.xpath(inner_xpath, namespaces=namespaces):
- changing = (attribute not in element.attrib or element.attrib[attribute] != eoa_value)
-
- if changing:
- changed = changed or changing
- if eoa_value is None:
- value = ""
- else:
- value = eoa_value
- element.attrib[attribute] = value
-
- # module.fail_json(msg="arf %s changing=%s as curval=%s changed tree=%s" %
- # (xpath, changing, etree.tostring(tree, changing, element[attribute], pretty_print=True)))
-
- else:
- module.fail_json(msg="unknown tree transformation=%s" % etree.tostring(tree, pretty_print=True))
-
- return changed
-
-
-def ensure_xpath_exists(module, tree, xpath, namespaces):
- changed = False
-
- if not is_node(tree, xpath, namespaces):
- changed = check_or_make_target(module, tree, xpath, namespaces)
-
- finish(module, tree, xpath, namespaces, changed)
-
-
-def set_target_inner(module, tree, xpath, namespaces, attribute, value):
- changed = False
-
- try:
- if not is_node(tree, xpath, namespaces):
- changed = check_or_make_target(module, tree, xpath, namespaces)
- except Exception as e:
- missing_namespace = ""
- # NOTE: This checks only the namespaces defined in root element!
- # TODO: Implement a more robust check to check for child namespaces' existence
- if tree.getroot().nsmap and ":" not in xpath:
- missing_namespace = "XML document has namespace(s) defined, but no namespace prefix(es) used in xpath!\n"
- module.fail_json(msg="%sXpath %s causes a failure: %s\n -- tree is %s" %
- (missing_namespace, xpath, e, etree.tostring(tree, pretty_print=True)), exception=traceback.format_exc())
-
- if not is_node(tree, xpath, namespaces):
- module.fail_json(msg="Xpath %s does not reference a node! tree is %s" %
- (xpath, etree.tostring(tree, pretty_print=True)))
-
- for element in tree.xpath(xpath, namespaces=namespaces):
- if not attribute:
- changed = changed or (element.text != value)
- if element.text != value:
- element.text = value
- else:
- changed = changed or (element.get(attribute) != value)
- if ":" in attribute:
- attr_ns, attr_name = attribute.split(":")
- # attribute = "{{%s}}%s" % (namespaces[attr_ns], attr_name)
- attribute = "{{{0}}}{1}".format(namespaces[attr_ns], attr_name)
- if element.get(attribute) != value:
- element.set(attribute, value)
-
- return changed
-
-
-def set_target(module, tree, xpath, namespaces, attribute, value):
- changed = set_target_inner(module, tree, xpath, namespaces, attribute, value)
- finish(module, tree, xpath, namespaces, changed)
-
-
-def get_element_text(module, tree, xpath, namespaces):
- if not is_node(tree, xpath, namespaces):
- module.fail_json(msg="Xpath %s does not reference a node!" % xpath)
-
- elements = []
- for element in tree.xpath(xpath, namespaces=namespaces):
- elements.append({element.tag: element.text})
-
- finish(module, tree, xpath, namespaces, changed=False, msg=len(elements), hitcount=len(elements), matches=elements)
-
-
-def get_element_attr(module, tree, xpath, namespaces):
- if not is_node(tree, xpath, namespaces):
- module.fail_json(msg="Xpath %s does not reference a node!" % xpath)
-
- elements = []
- for element in tree.xpath(xpath, namespaces=namespaces):
- child = {}
- for key in element.keys():
- value = element.get(key)
- child.update({key: value})
- elements.append({element.tag: child})
-
- finish(module, tree, xpath, namespaces, changed=False, msg=len(elements), hitcount=len(elements), matches=elements)
-
-
-def child_to_element(module, child, in_type):
- if in_type == 'xml':
- infile = BytesIO(to_bytes(child, errors='surrogate_or_strict'))
-
- try:
- parser = etree.XMLParser()
- node = etree.parse(infile, parser)
- return node.getroot()
- except etree.XMLSyntaxError as e:
- module.fail_json(msg="Error while parsing child element: %s" % e)
- elif in_type == 'yaml':
- if isinstance(child, string_types):
- return etree.Element(child)
- elif isinstance(child, MutableMapping):
- if len(child) > 1:
- module.fail_json(msg="Can only create children from hashes with one key")
-
- (key, value) = next(iteritems(child))
- if isinstance(value, MutableMapping):
- children = value.pop('_', None)
-
- node = etree.Element(key, value)
-
- if children is not None:
- if not isinstance(children, list):
- module.fail_json(msg="Invalid children type: %s, must be list." % type(children))
-
- subnodes = children_to_nodes(module, children)
- node.extend(subnodes)
- else:
- node = etree.Element(key)
- node.text = value
- return node
- else:
- module.fail_json(msg="Invalid child type: %s. Children must be either strings or hashes." % type(child))
- else:
- module.fail_json(msg="Invalid child input type: %s. Type must be either xml or yaml." % in_type)
-
-
-def children_to_nodes(module=None, children=None, type='yaml'):
- """turn a str/hash/list of str&hash into a list of elements"""
- children = [] if children is None else children
-
- return [child_to_element(module, child, type) for child in children]
-
-
-def make_pretty(module, tree):
- xml_string = etree.tostring(tree, xml_declaration=True, encoding='UTF-8', pretty_print=module.params['pretty_print'])
-
- result = dict(
- changed=False,
- )
-
- if module.params['path']:
- xml_file = module.params['path']
- with open(xml_file, 'rb') as xml_content:
- if xml_string != xml_content.read():
- result['changed'] = True
- if not module.check_mode:
- if module.params['backup']:
- result['backup_file'] = module.backup_local(module.params['path'])
- tree.write(xml_file, xml_declaration=True, encoding='UTF-8', pretty_print=module.params['pretty_print'])
-
- elif module.params['xmlstring']:
- result['xmlstring'] = xml_string
- # NOTE: Modifying a string is not considered a change !
- if xml_string != module.params['xmlstring']:
- result['changed'] = True
-
- module.exit_json(**result)
-
-
-def finish(module, tree, xpath, namespaces, changed=False, msg='', hitcount=0, matches=tuple()):
-
- result = dict(
- actions=dict(
- xpath=xpath,
- namespaces=namespaces,
- state=module.params['state']
- ),
- changed=has_changed(tree),
- )
-
- if module.params['count'] or hitcount:
- result['count'] = hitcount
-
- if module.params['print_match'] or matches:
- result['matches'] = matches
-
- if msg:
- result['msg'] = msg
-
- if result['changed']:
- if module._diff:
- result['diff'] = dict(
- before=etree.tostring(orig_doc, xml_declaration=True, encoding='UTF-8', pretty_print=True),
- after=etree.tostring(tree, xml_declaration=True, encoding='UTF-8', pretty_print=True),
- )
-
- if module.params['path'] and not module.check_mode:
- if module.params['backup']:
- result['backup_file'] = module.backup_local(module.params['path'])
- tree.write(module.params['path'], xml_declaration=True, encoding='UTF-8', pretty_print=module.params['pretty_print'])
-
- if module.params['xmlstring']:
- result['xmlstring'] = etree.tostring(tree, xml_declaration=True, encoding='UTF-8', pretty_print=module.params['pretty_print'])
-
- module.exit_json(**result)
-
-
-def main():
- module = AnsibleModule(
- argument_spec=dict(
- path=dict(type='path', aliases=['dest', 'file']),
- xmlstring=dict(type='str'),
- xpath=dict(type='str'),
- namespaces=dict(type='dict', default={}),
- state=dict(type='str', default='present', choices=['absent', 'present'], aliases=['ensure']),
- value=dict(type='raw'),
- attribute=dict(type='raw'),
- add_children=dict(type='list'),
- set_children=dict(type='list'),
- count=dict(type='bool', default=False),
- print_match=dict(type='bool', default=False),
- pretty_print=dict(type='bool', default=False),
- content=dict(type='str', choices=['attribute', 'text']),
- input_type=dict(type='str', default='yaml', choices=['xml', 'yaml']),
- backup=dict(type='bool', default=False),
- strip_cdata_tags=dict(type='bool', default=False),
- insertbefore=dict(type='bool', default=False),
- insertafter=dict(type='bool', default=False),
- ),
- supports_check_mode=True,
- required_by=dict(
- add_children=['xpath'],
- # TODO: Reinstate this in Ansible v2.12 when we have deprecated the incorrect use below
- # attribute=['value'],
- content=['xpath'],
- set_children=['xpath'],
- value=['xpath'],
- ),
- required_if=[
- ['count', True, ['xpath']],
- ['print_match', True, ['xpath']],
- ['insertbefore', True, ['xpath']],
- ['insertafter', True, ['xpath']],
- ],
- required_one_of=[
- ['path', 'xmlstring'],
- ['add_children', 'content', 'count', 'pretty_print', 'print_match', 'set_children', 'value'],
- ],
- mutually_exclusive=[
- ['add_children', 'content', 'count', 'print_match', 'set_children', 'value'],
- ['path', 'xmlstring'],
- ['insertbefore', 'insertafter'],
- ],
- )
-
- xml_file = module.params['path']
- xml_string = module.params['xmlstring']
- xpath = module.params['xpath']
- namespaces = module.params['namespaces']
- state = module.params['state']
- value = json_dict_bytes_to_unicode(module.params['value'])
- attribute = module.params['attribute']
- set_children = json_dict_bytes_to_unicode(module.params['set_children'])
- add_children = json_dict_bytes_to_unicode(module.params['add_children'])
- pretty_print = module.params['pretty_print']
- content = module.params['content']
- input_type = module.params['input_type']
- print_match = module.params['print_match']
- count = module.params['count']
- backup = module.params['backup']
- strip_cdata_tags = module.params['strip_cdata_tags']
- insertbefore = module.params['insertbefore']
- insertafter = module.params['insertafter']
-
- # Check if we have lxml 2.3.0 or newer installed
- if not HAS_LXML:
- module.fail_json(msg=missing_required_lib("lxml"), exception=LXML_IMP_ERR)
- elif LooseVersion('.'.join(to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('2.3.0'):
- module.fail_json(msg='The xml ansible module requires lxml 2.3.0 or newer installed on the managed machine')
- elif LooseVersion('.'.join(to_native(f) for f in etree.LXML_VERSION)) < LooseVersion('3.0.0'):
- module.warn('Using lxml version lower than 3.0.0 does not guarantee predictable element attribute order.')
-
- # Report wrongly used attribute parameter when using content=attribute
- # TODO: Remove this in Ansible v2.12 (and reinstate strict parameter test above) and remove the integration test example
- if content == 'attribute' and attribute is not None:
- module.deprecate("Parameter 'attribute=%s' is ignored when using 'content=attribute' only 'xpath' is used. Please remove entry." % attribute,
- '2.12', collection_name='ansible.builtin')
-
- # Check if the file exists
- if xml_string:
- infile = BytesIO(to_bytes(xml_string, errors='surrogate_or_strict'))
- elif os.path.isfile(xml_file):
- infile = open(xml_file, 'rb')
- else:
- module.fail_json(msg="The target XML source '%s' does not exist." % xml_file)
-
- # Parse and evaluate xpath expression
- if xpath is not None:
- try:
- etree.XPath(xpath)
- except etree.XPathSyntaxError as e:
- module.fail_json(msg="Syntax error in xpath expression: %s (%s)" % (xpath, e))
- except etree.XPathEvalError as e:
- module.fail_json(msg="Evaluation error in xpath expression: %s (%s)" % (xpath, e))
-
- # Try to parse in the target XML file
- try:
- parser = etree.XMLParser(remove_blank_text=pretty_print, strip_cdata=strip_cdata_tags)
- doc = etree.parse(infile, parser)
- except etree.XMLSyntaxError as e:
- module.fail_json(msg="Error while parsing document: %s (%s)" % (xml_file or 'xml_string', e))
-
- # Ensure we have the original copy to compare
- global orig_doc
- orig_doc = copy.deepcopy(doc)
-
- if print_match:
- do_print_match(module, doc, xpath, namespaces)
-
- if count:
- count_nodes(module, doc, xpath, namespaces)
-
- if content == 'attribute':
- get_element_attr(module, doc, xpath, namespaces)
- elif content == 'text':
- get_element_text(module, doc, xpath, namespaces)
-
- # File exists:
- if state == 'absent':
- # - absent: delete xpath target
- delete_xpath_target(module, doc, xpath, namespaces)
-
- # - present: carry on
-
- # children && value both set?: should have already aborted by now
- # add_children && set_children both set?: should have already aborted by now
-
- # set_children set?
- if set_children:
- set_target_children(module, doc, xpath, namespaces, set_children, input_type)
-
- # add_children set?
- if add_children:
- add_target_children(module, doc, xpath, namespaces, add_children, input_type, insertbefore, insertafter)
-
- # No?: Carry on
-
- # Is the xpath target an attribute selector?
- if value is not None:
- set_target(module, doc, xpath, namespaces, attribute, value)
-
- # If an xpath was provided, we need to do something with the data
- if xpath is not None:
- ensure_xpath_exists(module, doc, xpath, namespaces)
-
- # Otherwise only reformat the xml data?
- if pretty_print:
- make_pretty(module, doc)
-
- module.fail_json(msg="Don't know what to do")
-
-
-if __name__ == '__main__':
- main()