I want to modify $CATALINA_HOME/conf/tomcat-users.xml
in order to set up admin user and password. I have this playbook 'working':
- name: Set password from encrypted 'tomcat_admin_pass' for Tomcat User Administrator (admin) using ansible.comunity.xml
community.general.xml:
path: "{{ tomcat_live_dir }}/conf/tomcat-users.xml"
xpath: "/tomcat:tomcat-users/user[@username='admin']"
# tomcat-users is namespaced and needs more info for XPath
namespaces:
tomcat: http://tomcat.apache.org/xml
attribute: password
value: "{{ tomcat_admin_pass }}"
state: present
notify: tomcat_reload
- name: Set roles for Tomcat User Administrator (admin) using ansible.comunity.xml
community.general.xml:
path: "{{ tomcat_live_dir }}/conf/tomcat-users.xml"
xpath: "/tomcat:tomcat-users/user[@username='admin']"
# tomcat-users is namespaced and needs more info for XPath
namespaces:
tomcat: http://tomcat.apache.org/xml
attribute: roles
value: "manager-gui,admin-gui"
state: present
notify: tomcat_reload
But I end up appending two elements like this:
<user username="admin" password="my_super_pass"/><user username="admin" roles="manager-gui,admin-gui"/>
I am not very experienced in XPath
and ansible.community.xml
but my query seems to be accurate. I expected that ansible would modify only matching elements as per its idempotence behavior.
I did not want to put a Jinja2 template for tomcat-users.xml in case it is manually modified after, and another execution of my role would overwrite it.
I think your problem here is inconsistent use of XML namespaces. In your expressions, you have written:
/tomcat:tomcat-users/user[@username='admin']
But if tomcat-users
is in the tomcat
namespace, then so is user
, so you need to write:
/tomcat:tomcat-users/tomcat:user[@username='admin']
If I use this playbook:
- hosts: localhost
gather_facts: false
vars:
tomcat_admin_pass: secret
tasks:
- name: Set password from encrypted 'tomcat_admin_pass' for Tomcat User Administrator (admin) using ansible.comunity.xml
community.general.xml:
path: "tomcat-users.xml"
xpath: "/tomcat:tomcat-users/tomcat:user[@username='admin']"
# tomcat-users is namespaced and needs more info for XPath
namespaces:
tomcat: http://tomcat.apache.org/xml
attribute: password
value: "{{ tomcat_admin_pass }}"
state: present
- name: Set roles for Tomcat User Administrator (admin) using ansible.comunity.xml
community.general.xml:
path: "tomcat-users.xml"
xpath: "/tomcat:tomcat-users/tomcat:user[@username='admin']"
# tomcat-users is namespaced and needs more info for XPath
namespaces:
tomcat: http://tomcat.apache.org/xml
attribute: roles
value: "manager-gui,admin-gui"
state: present
With this tomcat-users.xml
:
<?xml version='1.0' encoding='UTF-8'?>
<tomcat-users xmlns="http://tomcat.apache.org/xml">
<role rolename="tomcat"/>
<role rolename="role1"/>
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="role1" password="tomcat" roles="role1"/>
</tomcat-users>
The result is:
<?xml version='1.0' encoding='UTF-8'?>
<tomcat-users xmlns="http://tomcat.apache.org/xml">
<role rolename="tomcat"/>
<role rolename="role1"/>
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="role1" password="tomcat" roles="role1"/>
<user username="admin" password="secret" roles="manager-gui,admin-gui"/></tomcat-users>
If I run the playbook a second time, it does not make any additional changes.
To expand on the problem a bit:
With your original xpath expression, your first task creates a new <user>
element in the file...but because the file has a default namespace, you actually end up with a <tomcat:user>
element. This means that your second task fails to find the element created by the first task, because it's looking for <user>
rather than <tomcat:user>
.