The below XML-file lists JARs with versions:
<?xml version='1.0'?>
<Files>
<!-- ... -->
<File Check="Y">public/foo-1.1.0.jar</File>
<File Check="Y">public/bar-2.3.11.jar</File>
</Files>
I need to update the foo
to version 1.2.1. I’d like to use the Ansible xml-module — but I can’t figure out the syntax, so that it would:
- Replace any existing entry for
foo
, if it refers to a different version. - Add a new entry for
foo
, if none exists. - Optional: leave only a single entry for
foo
— with the specified version — even if there were duplicate ones before (such as from earlier botched upgrade-attempts).
The only approach that comes to mind is to use two tasks: remove any existing entries (state=absent
) first, and then create a new one.
Can anyone suggest, how the same can be accomplished in one go?
3
“Editing” an existing XML file and “programming” in a kind of IF THEN ELSE manner will become in Ansible cumbersome and error-prone, see the simplified minimal example
---
- hosts: localhost
gather_facts: false
vars:
XML: "{{ lookup('file', 'files.xml') }}"
FOO_PATH: public/foo-1.2.1.jar
tasks:
- name: Convert XML to YML
set_fact:
YML: "{{ XML | ansible.utils.from_xml }}"
- name: Show content
debug:
msg: "{{ YML }}"
- name: Update path
ansible.utils.update_fact:
updates:
- path: "YML.Files.File.0['#text']"
value: "{{ FOO_PATH }}"
register: updated
- name: Show updated content
debug:
msg: "{{ updated.YML | ansible.utils.to_xml }}"
- name: Write out XML file
copy:
dest: updated.files.xml
content: "{{ updated.YML | ansible.utils.to_xml }}"
resulting into an output of
TASK [Convert XML to YML] ******************************
ok: [localhost]
TASK [Show content] ************************************
ok: [localhost] =>
msg:
Files:
File:
- '#text': public/foo-1.1.0.jar
'@Check': Y
- '#text': public/bar-2.3.11.jar
'@Check': Y
TASK [Update path] *************************************
changed: [localhost]
TASK [Show updated content] ****************************
ok: [localhost] =>
msg: |-
<?xml version="1.0" encoding="utf-8"?>
<Files>
<File Check="Y">public/foo-1.2.1.jar</File>
<File Check="Y">public/bar-2.3.11.jar</File>
</Files>
and the respective file with content. But it will already break if the input file contain not the correct order like in
<?xml version='1.0'?>
<Files>
<!-- ... -->
<File Check="Y">public/bar-2.3.11.jar</File>
<File Check="Y">public/foo-1.0.0.jar</File>
<File Check="Y">public/foo-1.1.0.jar</File>
</Files>
Documentation
from_xml
filter – Convert given XML string to native python dictionaryupdate_fact
module – Update currently set factsto_xml
filter – Convert given JSON string to XML
Declaring the Desired State seems to be the better approach. A minimal example with Inline Templating
---
- hosts: localhost
gather_facts: false
vars:
XML: "{{ lookup('file', 'files.xml') }}"
FOO_PATH: public/foo-1.2.1.jar
tasks:
- name: Convert XML to YML
set_fact:
YML: "{{ XML | ansible.utils.from_xml }}"
- name: Gather and set path
set_fact:
BAR_PATH: "{{ (YML.Files.File | selectattr('#text', 'contains', 'bar') | first)['#text'] }}"
- name: Create XML file
copy:
dest: created.files.xml
content: |
<?xml version="1.0" encoding="utf-8"?>
<Files>
<File Check="Y">{{ FOO_PATH }}</File>
<File Check="Y">{{ BAR_PATH }}</File>
</Files>
will just create the file in the necessary final state.
<?xml version="1.0" encoding="utf-8"?>
<Files>
<File Check="Y">public/foo-1.2.1.jar</File>
<File Check="Y">public/bar-2.3.11.jar</File>
</Files>
Documentation
copy
module – Copy files to remote locations – Parametercontent
template
module – Template a file out to a target host
1
Try a powershell script
using assembly System.Xml.Linq
$input_filename = 'c:temptest.xml'
$output_filename = 'c:temptest1.xml'
$doc = [System.Xml.Linq.XDocument]::Load($input_filename)
$files = $doc.Descendants('Files')
$foos = [System.Linq.Enumerable]::Where($files, [Func[object,bool]]{ param($x) [string]$x.Value.StartsWith('public/foo')})
$pattern = '(public/foo-)(.*)(.jar)'
$newVersion = '1.2.1'
foreach($foo in $foos)
{
$version = $foo[0].Value
$newValue = $version -replace $pattern, '$1xyz$3'
$newValue = $newValue -replace 'xyz', "$newVersion"
$foo.FirstNode.SetValue($newValue)
}
$doc.Save($output_filename)
2