Ansible XML Adding Nested Children Idempotently

103 views Asked by At

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.

1

There are 1 answers

4
β.εηοιτ.βε On BEST ANSWER

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>