We were recently confronted with one of our playbook constraint while trying to identify which database software could be uninstalled from our servers.
Until now, we ran the playbook to de-install a specific version which were “discovered manually”. However, we even got lazy to run this “discovery phase” manually.

This short post explains how to design a play around the results of a specific task like generating a list of obsolete Oracle RDBMS software.

Indeed, we tried to loop over registered results from a simple SHELL command like fuser; The goal was to build a list with the result of fuser and iterate over the whole list.

Here the task snippet of what we tried achieve:

    - name: Find all installed Oracle RDBMS binaries
      find:
        paths: /u01/app/oracle/product/
        recurse: no
        file_type: directory
      register: reg_oracle_home_installed

    - name: Check if ORACLE_HOME is used
      shell: "/sbin/fuser {{ ora_home.path }}/bin/oracle"
      register: reg_oracle_home_status
      loop: "{{ reg_oracle_home_installed.files }}"
      loop_control:
        loop_var: ora_home
        label: "{{ ora_home.path }}"
      changed_when: false
      failed_when: reg_oracle_home_status.rc is not regex('^(1|0)$')

    - name: Dynamic list of candidate ORACLE_HOME to deinstall
      debug:
        msg: " {{ entry.ora_home.path }}"
      loop: "{{ reg_oracle_home_status.results }}"
      loop_control:
        loop_var: entry
        label: "{{ entry.ora_home.path }}"

According to the Ansible documentation, the return values for registered data structure in loops should return a list per item.However, processing the “results” list is not working as expected which explains why you are still reading my post 😉.

Below, the returned data structure debug output:

ok: [vmoel7] => (item={'stderr_lines': [], u'cmd': u'fuser /u01/app/oracle/product/18_7_0_0_RU190716_v0/bin/oracle', u'stdout': u'', u'msg': u'non-zero return code', u'delta': u'0:00:00.027083', 'stdout_lines': [], 'failed_when_result': False, 'ansible_loop_var': u'item', u'end': u'2020-10-15 22:13:42.461691', 'item': {u'islnk': False, u'uid': 54321, u'rgrp': True, u'xoth': True, u'rusr': True, u'woth': False, u'nlink': 72, u'issock': False, u'mtime': 1563907992.349, u'gr_name': u'oinstall', u'path': u'/u01/app/oracle/product/18_7_0_0_RU190716_v0', u'xusr': True, u'atime': 1593096574.662, u'inode': 132, u'isgid': False, u'size': 4096, u'isdir': True, u'wgrp': False, u'ctime': 1563907992.349, u'isblk': False, u'xgrp': True, u'isuid': False, u'dev': 2065, u'roth': True, u'isreg': False, u'isfifo': False, u'mode': u'0755', u'pw_name': u'oracle', u'gid': 54321, u'ischr': False, u'wusr': True}, u'changed': True, u'failed': False, u'stderr': u'', u'rc': 1, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'fuser /u01/app/oracle/product/18_7_0_0_RU190716_v0/bin/oracle', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, u'start': u'2020-10-15 22:13:42.434608'}) => {
    "msg": "unsed OracleHome={'stderr_lines': [], 'ansible_loop_var': u'item', u'end': u'2020-10-15 22:13:42.461691', u'stdout': u'', 'item': {u'uid': 54321, u'woth': False, u'mtime': 1563907992.349, u'inode': 132, u'isgid': False, u'size': 4096, u'roth': True, u'isuid': False, u'isreg': False, u'pw_name': u'oracle', u'gid': 54321, u'ischr': False, u'wusr': True, u'xoth': True, u'rusr': True, u'nlink': 72, u'issock': False, u'rgrp': True, u'gr_name': u'oinstall', u'path': u'/u01/app/oracle/product/18_7_0_0_RU190716_v0', u'xusr': True, u'atime': 1593096574.662, u'isdir': True, u'ctime': 1563907992.349, u'wgrp': False, u'xgrp': True, u'dev': 2065, u'isblk': False, u'isfifo': False, u'mode': u'0755', u'islnk': False}, u'changed': True, u'rc': 1, u'failed': False, u'cmd': u'fuser /u01/app/oracle/product/18_7_0_0_RU190716_v0/bin/oracle', u'stderr': u'', u'delta': u'0:00:00.027083', u'invocation': {u'module_args': {u'creates': None, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'fuser /u01/app/oracle/product/18_7_0_0_RU190716_v0/bin/oracle', u'removes': None, u'argv': None, u'warn': True, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, 'stdout_lines': [], 'failed_when_result': False, u'start': u'2020-10-15 22:13:42.434608', u'msg': u'non-zero return code'}"
}

Indeed, it looks like a YAML list but it isn’t … Lets quickly verify the variable type using “type_debug”
Code Snippet:

    - debug:
        msg: "reg_oracle_home_status.results: {{ reg_oracle_home_status.results | type_debug }}"

Output:

ok: [vmoel7] => {
    "msg": "reg_oracle_home_status.results: list"
}

Hum, it’s a list! Hence, It’s must be a bug.

So, let’s try to reformat it using the from_yaml Python method. Follows, the output after python from_yaml conversion:

ok: [vmoel7] => {
    "msg": [
        {
            "cmd": "fuser /u01/app/oracle/product/18_7_0_0_RU190716_v0/bin/oracle",
            …
            "msg": "non-zero return code",
            "rc": 1,
            "start": "2020-10-15 22:22:40.857383",
            "stderr": "",
            "stderr_lines": [],
            "stdout": "",
            "stdout_lines": []
       },
        {
            "cmd": "fuser /u01/app/oracle/product/19_8_0_0_RU200714_v0/bin/oracle",
            "rc": 0,
            …
        }
      …

Better isn’t it?

Now, we are able to process the list in a loop as expected:

    - name: Dynamic list of candidate ORACLE_HOME to deinstall
      debug:
        msg: "deinstall ORACLE_HOME={{ entry.ora_home.path}}"
      loop: "{{ reg_oracle_home_status.results | from_yaml | list }}"
      loop_control:
        loop_var: entry
        label: "{{ entry.ora_home.path }}"
      # ReturnCode=0 - if used
      when: entry.rc != 0

Sounds obvious? But took a couple of hours to debug.

it’s probably the case for plenty of Ansible nerds all over the world if you look at the number of forum threads.

The proof? You are still reading this post 😊