xmlansiblenamespacesidempotent

Ansible XML Adding Nested Children Idempotently


I am trying to remediate V-222934 (Additional information at Tomcat Default Servlet Reference)

Starting with the web.xml file:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee                       http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- Much more in actual web.xml -->
</web-app>

I want to end with

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee                       http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
          <param-name>readonly</param-name>
          <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- Much more in actual web.xml -->
</web-app>

I started with the following Ansible:

    - name: "V-222934: DefaultServlet set to readonly for PUT and DELETE"
      community.general.xml:
        path: "{{ atl_product_installation_versioned }}/conf/web.xml"
        xpath: "/j:web-app/j:servlet/j:servlet-class[text()=\"org.apache.catalina.servlets.DefaultServlet\"]"
        insertafter: yes
        add_children:
          - init-param:
            - param-name: readonly
            - param-value: true
        pretty_print: yes
        namespaces:
          j: http://xmlns.jcp.org/xml/ns/javaee

But that threw an error complaining about a list rather than bytes/unicode.

Switching to input_type: xml I get some success:

    - name: "V-222934: DefaultServlet set to readonly for PUT and DELETE"
      community.general.xml:
        path: "{{ atl_product_installation_versioned }}/conf/web.xml"
        xpath: "/j:web-app/j:servlet/j:servlet-class[text()=\"org.apache.catalina.servlets.DefaultServlet\"]"
        insertafter: yes
        input_type: xml
        add_children:
          - "<init-param><param-name>readonly</param-name><param-value>true</param-value></init-param>"
        pretty_print: yes
        namespaces:
          j: http://xmlns.jcp.org/xml/ns/javaee

Which works but is not indempotent.

My big/primary question is: how do I make this idempotent?

Moreover, in my actual file there are more than one <servlet>. A solution I can live with is to specify /ns:servlet[1]. However, I would like to target the one with the <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>.

I have a minor question of: how can I get the yaml input type to work? (I think the code looks cleaner and makes more sense with that formatting.)

And lastly: I only know enough about namespaces to make the Ansible work. If someone has a cleaner/clearer way of writing the code, I am open to any improvements.


Solution

  • If you want to make your task idempotent, just construct a XPath expression that matches what you are looking to add. Then use that in the xpath attribute, along with the state: present – which can be omitted since it is the default of the parameter.

    So with the task:

    - xml:
        path: web.xml
        xpath: >-
          /ns:web-app
          /ns:servlet[
          ns:servlet-class[text()="org.apache.catalina.servlets.DefaultServlet"]
          ]
          /ns:init-param[
          ns:param-name[text()="readonly"]
          and ns:param-value[text()="true"]
          ]
        pretty_print: true
        namespaces:
          ns: http://xmlns.jcp.org/xml/ns/javaee
    

    Your XML ends up being

    <?xml version='1.0' encoding='UTF-8'?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        version="3.1">
        <servlet>
            <servlet-name>default</servlet-name>
            <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
            <init-param>
                <param-name>debug</param-name>
                <param-value>0</param-value>
            </init-param>
            <init-param>
                <param-name>listings</param-name>
                <param-value>false</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
            <init-param>
                <param-name>readonly</param-name>
                <param-value>true</param-value>
            </init-param>
        </servlet>
        <!-- Much more in actual web.xml -->
    </web-app>