1

I have this file and I know the value of LOC for example /oracle/19.0.0. Would like to get the value of HOME NAME=, the corresponding value would be OraDB19Home1.

Looked at lookup but unable to get it fully working. Appreciate any help.

<?xml version = '1.0' encoding = 'UTF-8' standalone = 'yes'?>
<!-- Copyright (c) 1999, 2022, Oracle. All rights reserved. -->
<!-- Do not modify the contents of this file by hand. -->
<INVENTORY>
   <VERSION_INFO>
      <SAVED_WITH>13.9.4.0.0</SAVED_WITH>
      <MINIMUM_VER>2.1.0.6.0</MINIMUM_VER>
   </VERSION_INFO>
   <HOME_LIST>
      <HOME NAME="OraHome1" LOC="/oracle/agent/agent13.4" TYPE="O" IDX="3"/>
      <HOME NAME="OraDB19Home1" LOC="/oracle/19.0.0" TYPE="O" IDX="2"/>
   </HOME_LIST>
</INVENTORY>
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
calsaint
  • 83
  • 1
  • 2
  • 9

4 Answers4

2

Given the XML

shell> cat inventory.xml 
<?xml version = '1.0' encoding = 'UTF-8' standalone = 'yes'?>
<!-- Copyright (c) 1999, 2022, Oracle. All rights reserved. -->
<!-- Do not modify the contents of this file by hand. -->
<INVENTORY>
  <VERSION_INFO>
    <SAVED_WITH>13.9.4.0.0</SAVED_WITH>
    <MINIMUM_VER>2.1.0.6.0</MINIMUM_VER>
  </VERSION_INFO>
  <HOME_LIST>
    <HOME NAME="OraHome1" LOC="/oracle/agent/agent13.4" TYPE="O" IDX="3"/>
    <HOME NAME="OraDB19Home1" LOC="/oracle/19.0.0" TYPE="O" IDX="2"/>
  </HOME_LIST>
</INVENTORY>

Read the file and convert XML to YAML

  inv_xml: "{{ lookup('file', 'inventory.xml') }}"
  inv_yml: "{{ inv_xml|ansible.utils.from_xml }}"

gives

  inv_yml:
    INVENTORY:
      HOME_LIST:
        HOME:
        - '@IDX': '3'
          '@LOC': /oracle/agent/agent13.4
          '@NAME': OraHome1
          '@TYPE': O
        - '@IDX': '2'
          '@LOC': /oracle/19.0.0
          '@NAME': OraDB19Home1
          '@TYPE': O
      VERSION_INFO:
        MINIMUM_VER: 2.1.0.6.0
        SAVED_WITH: 13.9.4.0.0

Create a dictionary of LOC and NAME

  loc_name: "{{ inv_yml.INVENTORY.HOME_LIST.HOME|
                items2dict(key_name='@LOC',
                           value_name='@NAME') }}"

gives

  loc_name:
    /oracle/19.0.0: OraDB19Home1
    /oracle/agent/agent13.4: OraHome1

Then, searching is trivial

  loc: '/oracle/19.0.0'
  name_of_loc: "{{ loc_name[loc] }}"

gives

  name_of_loc: OraDB19Home1

, or in the loop

    - debug:
        msg: "The name of LOC {{ item }} is {{ loc_name[item] }}"
      loop:
        - '/oracle/19.0.0'
        - '/oracle/agent/agent13.4'

gives (abridged)

  msg: The name of LOC /oracle/19.0.0 is OraDB19Home1
  msg: The name of LOC /oracle/agent/agent13.4 is OraHome1

Example of a complete playbook for testing

shell> cat pb.yml
- hosts: localhost

  vars:

    inv_xml: "{{ lookup('file', 'inventory.xml') }}"
    inv_yml: "{{ inv_xml|ansible.utils.from_xml }}"
    loc_name: "{{ inv_yml.INVENTORY.HOME_LIST.HOME|
                  items2dict(key_name='@LOC',
                             value_name='@NAME') }}"
    loc: '/oracle/19.0.0'
    name_of_loc: "{{ loc_name[loc] }}"

  tasks:

    - debug:
        var: inv_xml
    - debug:
        var: inv_yml
    - debug:
        var: loc_name
    - debug:
        var: name_of_loc

    - debug:
        msg: "The name of LOC {{ item }} is {{ loc_name[item] }}"
      loop:
        - '/oracle/19.0.0'
        - '/oracle/agent/agent13.4'

Example of the project

shell> tree .
.
├── ansible.cfg
├── hosts
├── inventory.xml
└── pb.yml

0 directories, 4 files
shell> cat ansible.cfg 
[defaults]
gathering = explicit
collections_path = $HOME/.local/lib/python3.9/site-packages/
inventory = $PWD/hosts
roles_path = $PWD/roles
remote_tmp = ~/.ansible/tmp
retry_files_enabled = false
stdout_callback = yaml
shell> cat hosts 
localhost

Q: "Give an alternative to ansible.utils"

A: Install jc and use it in the pipe. The declaration below expands to the same YAML as before

  inv_yml: "{{ lookup('pipe', 'cat inventory.xml | jc --xml') }}"

Q: "Using possible regex?"

A: Select the line first

  inv_xml: "{{ lookup('file', 'inventory.xml') }}"

  loc: '/oracle/19.0.0'
  home_loc_regex: '^\s*<HOME .*? LOC="{{ loc }}" .*$'
  home: "{{ inv_xml.splitlines()|
            select('regex', home_loc_regex)|
            first|
            trim }}"

gives

  home: <HOME NAME="OraDB19Home1" LOC="/oracle/19.0.0" TYPE="O" IDX="2"/>

Parse the attributes

  home_dict: "{{ dict(home[6:-2]|
                      replace('\"', '')|
                      split(' ')|
                      map('split', '=')) }}"

gives

  home_dict:
    IDX: '2'
    LOC: /oracle/19.0.0
    NAME: OraDB19Home1
    TYPE: O

Q: "No filter named 'split'"

A: The filter split is available since 2.11. For the lower versions, only the '.split' method is available. In this case, use Jinja and create the YAML structure. The declarations below give the same dictionary home_dict as before

  home_dict_str: |
    {% for i in home[6:-2].split(' ') %}
    {% set arr = i.split('=') %}
    {{ arr.0 }}: {{ arr.1 }}
    {% endfor %}
  home_dict: "{{ home_dict_str|from_yaml }}"
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • Thank you so much. I was looking at these to improvise, https://stackoverflow.com/questions/72022134/how-can-i-convert-key-value-file-into-ansible-facts and the following that you answered in another thread, lost the link for that. - set_fact: MQSFILEPARSED: "{{ lookup('file', FILE).splitlines()| select('match', '^(.*) LOC=(.*)$')| map('regex_replace', my_regex, my_replace)| list }}" – calsaint Feb 08 '23 at 14:36
  • Getting ansible.utils is proving to be challenging. Appreciate if you can give an alternative using possible regex? Thanks once again. – calsaint Feb 08 '23 at 16:37
  • Use *jc* instead. I added an example. What's challenging on *ansible.utils*? – Vladimir Botka Feb 08 '23 at 16:53
  • I added the 'regex' option. – Vladimir Botka Feb 08 '23 at 17:23
  • Thank you so much once again! Our server admins aren't willing to add those utils. Thats where I develop the playbooks. These will eventually go on the Tower. And the Tower env doesnt have these utils installed. When I checked with the tower admin he said, I'll have to have a custom exec env. Wasnt sure how much of it I wanted to dabble with limited time etc. Appreciate your help always. I am racking up beers for you here in Houston :) – calsaint Feb 08 '23 at 19:29
  • Good to know :) Take a look if [Community.General](https://github.com/ansible-collections/community.general) is available to you. This is where the new stuff is added and updated. For example, you can use [*jc*](https://docs.ansible.com/ansible/latest/collections/community/general/jc_filter.html#community-general-jc-filter-convert-output-of-many-shell-commands-and-file-types-to-json) also as a filter. – Vladimir Botka Feb 08 '23 at 20:19
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/251736/discussion-between-calsaint-and-vladimir-botka). – calsaint Feb 08 '23 at 20:45
0

This is almost trivial to do with awk:

Formatted as a script file:

/<HOME NAME=.*TYPE=.*IDX=/ {
    if ($3 == "LOC=\"/oracle/19.0.0\"") {
        split($2, a, /"/);
        print a[2];
        exit;
    }
}'

Command line:

cat your_input_file | awk '/<HOME NAME=.*TYPE=.*IDX=/{ if ($3 == "LOC=\"/oracle/19.0.0\"") { split($2, a, /"/); print a[2]; exit }  }'

You can probably convert this relatively easily to something similar in python.

hlovdal
  • 26,565
  • 10
  • 94
  • 165
  • Thank you! Yes I currently use something similar in an existing shell script which I am trying to write as an ansible playbook. So was looking for something ansible solution. – calsaint Feb 08 '23 at 14:48
0
awk -v loc='LOC="/oracle/19.0.0"' '
    index($0,loc){
        print gensub(/^.*NAME="([^"]*)".*$/,"\\1",1)
    }
' inputfile

or

awk -F'=|"' -v loc='LOC="/oracle/19.0.0"' 'index($0,loc){print $3}' inputfile
awk -F'=|"' -v loc="/oracle/19.0.0" '/ LOC="[^"]*" / && $6 == loc {print $3}' inputfile

Output

OraDB19Home1
ufopilot
  • 3,269
  • 2
  • 10
  • 12
0

For me the use case looks like a

Similar Q&A

It is assumed that the unfavorable data structure within your configuration example file stays as it is provided and you are looking for a grep approach in Ansible.

- name: Gather home directory
  shell:
    cmd: "grep '{{ VERSION }}' inventory.xml | cut -d '=' -f 2 | cut -d ' ' -f 1 | tr -d '"'"
  register: home_dir

How to proceed further?

If such lookup's are necessary frequently or on a greater scale, one might take advantage from implementing a specific solution.

If the installation of ansible.utils.collection and Python library xmltodict is possible or already available, you should definitely go with the recommended solution from Vladimir Botka of from_xml filter – Convert given XML string to native python dictionary by converting XML to YAML before further processing.

Otherwise you could give a try for

Custom Module Examples

U880D
  • 8,601
  • 6
  • 24
  • 40
  • Thank you! Appreciate the help. Was trying to steer clear of cmd/shell and do it natively in ansbile. – calsaint Feb 08 '23 at 14:47