Ansible: K.I.S.S. Optimization

Ansible:  K.I.S.S. Optimization
Photo by Jakub Żerdzicki / Unsplash

It's fascinating, but our automation code is mature enough to have compatibility issues during the operating system upgrade. Let's see how violating the "Keep it simple stupid (K.I.S.S.)" principle leads you to trouble.

A few years back, the Ansible community switched to the mode with the Ansible Core installation and additional collections you can install through the Ansible Galaxy. For some environments, you can't use a lot of modules and roles you have for granted with the previous installations. And if you can replace archive/unarchive with a shell-based alternative and improve overall playbook performance, for some modules like json_query, you need to go creative and rethink the whole approach.

To draw the background: We use Ansible to operate large Oracle Fusion Middleware installations. The bedrock of the automation is an environment inventory, which describes servers and the state of the software they run. Of course, installation data maps to the hosts with detailed domain descriptors. Here is a snippet of the domain descriptor.

domains:
  # Domain descriptor
   demo_wls_domain:
    # WebLogic Domain Machines
    machines:
      demohost1: 
         host: demohost1.mydomain.net
         port: 5554
      demohost2: 
         host: demohost2.mydomain.net
         port: 5554
    servers: 
      AdminServer: 
         machine: demohost1
       # rest of configuration 
      demo_server1: 
         machine: demohost2
       # rest of configuration 
  # The rest of the domain configuration        
       

Ansible Inventory Variable Snippet

Most of the code that controls the state of a WebLogic domain uses json_query to select a machine that matches the current host and other components in the scope of the current Ansible process. I can't remember why we decided to query YAML data structures with JSON utilities, but that was far from simple. The code below is close enough to the original to give you an idea:

- name: Get Machine List
  set_fact:
   mlist: "{{ lookup('dict',domains['demo_wls_domain'].machines) }}"  
- name: Find Machine Details
  set_fact: 
   local_machine: "{{ (mlist | json_jquery(clause))[0] }}"
  vars:
   clause: "[?value.host == '{{ inventory_hostname }}']"

Ugh

Restricted by the Ansible Core modules and functions, the obvious alternative is selectattr. This data manipulation filter does what I want: filter the list by some attribute value.

- name: Find Machine Details
  set_fact: 
   local_machine: "{{ (domain['demo_wls_domain'].machines 
    | dict2itmes 
    | selectattr('value.host','contains',inventory_hostname))[0] }}"

Same Result without JSON query.

The new code is simple and produces the same result as the original one. Happy with the result, I pushed the tested code to the repository, only to find that my original workflow failed due to missing attribute errors.

My more experienced readers already see the problem with the new code and why it is not backward compatible.

For the rest, the clue is the [0] selector. The new Ansible filters return a straight list so you can address list items directly. That does not work for Ansible versions below 2.11, where the filter returns a row generator object. So, the final code that works for old and new Ansible versions and does not require additional collections is:

- name: Find Machine Details
  set_fact: 
   local_machine: "{{ (domain['demo_wls_domain'].machines 
    | dict2itmes 
    | selectattr('value.host','contains',inventory_hostname))|first }}"

Ansible has filters for anything!

What I learn from this code optimization case:

  • Keep your solution simple and based on the core system functionality.
  • Inspect your code to find places that could be improved regularly.
  • Select the right tools for the task; Learn what you can use.
  • Source, even the good one, could age badly and need to be rewritten.