diff --git a/lib/ansible/modules/files/xml.py b/lib/ansible/modules/files/xml.py
index e62a3c37b5..66a52e2917 100644
--- a/lib/ansible/modules/files/xml.py
+++ b/lib/ansible/modules/files/xml.py
@@ -122,6 +122,26 @@ options:
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() 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() 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:
@@ -202,6 +222,16 @@ EXAMPLES = r'''
- 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:
@@ -446,16 +476,35 @@ def set_target_children(module, tree, xpath, namespaces, children, in_type):
finish(module, tree, xpath, namespaces, changed=changed)
-def add_target_children(module, tree, xpath, namespaces, children, in_type):
+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)
- for node in tree.xpath(xpath, namespaces=namespaces):
- node.extend(new_kids)
+ 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]
@@ -776,6 +825,8 @@ def main():
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,
# TODO: Implement this as soon as #28662 (required_by functionality) is merged
@@ -790,6 +841,8 @@ def main():
['content', 'text', ['xpath']],
['count', True, ['xpath']],
['print_match', True, ['xpath']],
+ ['insertbefore', True, ['xpath']],
+ ['insertafter', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
@@ -798,6 +851,7 @@ def main():
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match', 'set_children', 'value'],
['path', 'xmlstring'],
+ ['insertbefore', 'insertafter'],
],
)
@@ -817,6 +871,8 @@ def main():
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:
@@ -881,7 +937,7 @@ def main():
# add_children set?
if add_children:
- add_target_children(module, doc, xpath, namespaces, add_children, input_type)
+ add_target_children(module, doc, xpath, namespaces, add_children, input_type, insertbefore, insertafter)
# No?: Carry on
diff --git a/test/integration/targets/xml/results/test-add-children-insertafter.xml b/test/integration/targets/xml/results/test-add-children-insertafter.xml
new file mode 100644
index 0000000000..8da9633636
--- /dev/null
+++ b/test/integration/targets/xml/results/test-add-children-insertafter.xml
@@ -0,0 +1,17 @@
+
+
+ Tasty Beverage Co.
+
+ Rochefort 10
+ St. Bernardus Abbot 12
+ Old Rasputin
+ Old Motor Oil
+ Old Curmudgeon
+ Schlitz
+
+ 10
+
+
+ http://tastybeverageco.com
+
+
diff --git a/test/integration/targets/xml/results/test-add-children-insertbefore.xml b/test/integration/targets/xml/results/test-add-children-insertbefore.xml
new file mode 100644
index 0000000000..c409e54bfa
--- /dev/null
+++ b/test/integration/targets/xml/results/test-add-children-insertbefore.xml
@@ -0,0 +1,17 @@
+
+
+ Tasty Beverage Co.
+
+ Rochefort 10
+ Old Rasputin
+ Old Motor Oil
+ Old Curmudgeon
+ St. Bernardus Abbot 12
+ Schlitz
+
+ 10
+
+
+ http://tastybeverageco.com
+
+
diff --git a/test/integration/targets/xml/tasks/main.yml b/test/integration/targets/xml/tasks/main.yml
index e992990a28..ad2967351b 100644
--- a/test/integration/targets/xml/tasks/main.yml
+++ b/test/integration/targets/xml/tasks/main.yml
@@ -37,6 +37,8 @@
- include_tasks: test-add-children-elements.yml
- include_tasks: test-add-children-from-groupvars.yml
+ - include_tasks: test-add-children-insertafter.yml
+ - include_tasks: test-add-children-insertbefore.yml
- include_tasks: test-add-children-with-attributes.yml
- include_tasks: test-add-element-implicitly.yml
- include_tasks: test-count.yml
diff --git a/test/integration/targets/xml/tasks/test-add-children-insertafter.yml b/test/integration/targets/xml/tasks/test-add-children-insertafter.yml
new file mode 100644
index 0000000000..2d42e2d54e
--- /dev/null
+++ b/test/integration/targets/xml/tasks/test-add-children-insertafter.yml
@@ -0,0 +1,32 @@
+---
+ - name: Setup test fixture
+ copy:
+ src: fixtures/ansible-xml-beers.xml
+ dest: /tmp/ansible-xml-beers.xml
+
+
+ - name: Add child element
+ xml:
+ path: /tmp/ansible-xml-beers.xml
+ xpath: '/business/beers/beer[text()="St. Bernardus Abbot 12"]'
+ insertafter: yes
+ add_children:
+ - beer: Old Rasputin
+ - beer: Old Motor Oil
+ - beer: Old Curmudgeon
+ pretty_print: yes
+ register: add_children_insertafter
+
+ - name: Compare to expected result
+ copy:
+ src: results/test-add-children-insertafter.xml
+ dest: /tmp/ansible-xml-beers.xml
+ check_mode: yes
+ diff: yes
+ register: comparison
+
+ - name: Test expected result
+ assert:
+ that:
+ - add_children_insertafter.changed == true
+ - comparison.changed == false # identical
diff --git a/test/integration/targets/xml/tasks/test-add-children-insertbefore.yml b/test/integration/targets/xml/tasks/test-add-children-insertbefore.yml
new file mode 100644
index 0000000000..8550f12cf7
--- /dev/null
+++ b/test/integration/targets/xml/tasks/test-add-children-insertbefore.yml
@@ -0,0 +1,32 @@
+---
+ - name: Setup test fixture
+ copy:
+ src: fixtures/ansible-xml-beers.xml
+ dest: /tmp/ansible-xml-beers.xml
+
+
+ - name: Add child element
+ xml:
+ path: /tmp/ansible-xml-beers.xml
+ xpath: '/business/beers/beer[text()="St. Bernardus Abbot 12"]'
+ insertbefore: yes
+ add_children:
+ - beer: Old Rasputin
+ - beer: Old Motor Oil
+ - beer: Old Curmudgeon
+ pretty_print: yes
+ register: add_children_insertbefore
+
+ - name: Compare to expected result
+ copy:
+ src: results/test-add-children-insertbefore.xml
+ dest: /tmp/ansible-xml-beers.xml
+ check_mode: yes
+ diff: yes
+ register: comparison
+
+ - name: Test expected result
+ assert:
+ that:
+ - add_children_insertbefore.changed == true
+ - comparison.changed == false # identical