简体   繁体   中英

Create a dictionary from an xml in Ansible

So, I am working with Ansible and I am retrieving specific information from some devices. In particular, I have a task like this:

<rpc-reply message-id="urn:uuid:1914-b84d7ff">
   <lldp-neighbors-information style="brief">
      <lldp-neighbor-information>
         <lldp-local-port-id>A1</lldp-local-port-id>
         <lldp-local-parent-interface-name>-</lldp-local-parent-interface-name>
         <lldp-remote-chassis-id-subtype>A2</lldp-remote-chassis-id-subtype>
         <lldp-remote-chassis-id>A3</lldp-remote-chassis-id>
         <lldp-remote-port-id-subtype>A4</lldp-remote-port-id-subtype>
         <lldp-remote-port-id>A5</lldp-remote-port-id>
         <lldp-remote-system-name>A6</lldp-remote-system-name>
      </lldp-neighbor-information>
      <lldp-neighbor-information>
         <lldp-local-port-id>B1</lldp-local-port-id>
         <lldp-local-parent-interface-name>-</lldp-local-parent-interface-name>
         <lldp-remote-chassis-id-subtype>B2</lldp-remote-chassis-id-subtype>
         <lldp-remote-chassis-id>B3</lldp-remote-chassis-id>
         <lldp-remote-port-id-subtype>B4</lldp-remote-port-id-subtype>
         <lldp-remote-port-id>B5</lldp-remote-port-id>
         <lldp-remote-system-name>B6</lldp-remote-system-name>
      </lldp-neighbor-information>
      <lldp-neighbor-information>
         <lldp-local-port-id>C1</lldp-local-port-id>
         <lldp-local-parent-interface-name>-</lldp-local-parent-interface-name>
         <lldp-remote-chassis-id-subtype>C2</lldp-remote-chassis-id-subtype>
         <lldp-remote-chassis-id>C3</lldp-remote-chassis-id>
         <lldp-remote-port-id-subtype>C4</lldp-remote-port-id-subtype>
         <lldp-remote-port-id>C5</lldp-remote-port-id>
      </lldp-neighbor-information>
   </lldp-neighbors-information>
</rpc-reply>

My goal is to build a dictionary like this :

{'A6': ['A1', 'A2'], 'B6': ['B1', 'B2']}

What I have done is creating a list with all my keys by doing the following tasks:

- name: Retrieve lldp system names
  xml:
    xmlstring: "{{ item.string | regex_replace('\n', '') }}"
    xpath: "{{ item.path }}"
    content: text
  loop:
    - { path: "/rpc-reply/lldp-neighbors-information/lldp-neighbor-information/lldp-remote-system-name", string: "{{xml_reply.xml}}" }
  register: sys_names


- name: Save all sys names in a list
  set_fact:
    sys_names_list: "{{ sys_names.results[0].matches | map('dict2items') | list | json_query('[].value') }}"

Then I could create a second list for the elements [A1, B1] and a 3rd list [A2, B2] respectively. So then I could just combine the 3 lists and create my dictionary.

So there are 3 questions here :

  1. Is there a way to directly build an ansible-dictionary from xml elements or should I keep writing my own module ?
  2. Since the last element C6 would be my 3rd key BUT does NOT exist, I want to skip it. How is that possible in my task above?
  3. How do I combine combine the 3 lists and create my dictionary, having skipped the 3rd element ? Otherwise my lists will not match the correct info ..

You can perform this by combining a filter that converts your xml file to a structured json in order to facilitate data manipulations.

In this example, I use this converter from xml to json .

File filter_plugins/from_xml.py :

#!/usr/bin/python
# From https://github.com/nasgoncalves/ansible-xml-to-json-filter/blob/master/filter_plugins/xml_to_json.py

import json
import xml.etree.ElementTree as ET
from collections import defaultdict


class FilterModule(object):

    def etree_to_dict(self, t):
        d = {t.tag: {} if t.attrib else None}
        children = list(t)
        if children:
            dd = defaultdict(list)
            for dc in map(self.etree_to_dict, children):
                for k, v in dc.items():
                    dd[k].append(v)
            d = {t.tag: {k: v[0] if len(v) == 1 else v
                         for k, v in dd.items()}}
        if t.attrib:
            d[t.tag].update(('@' + k, v)
                            for k, v in t.attrib.items())
        if t.text:
            text = t.text.strip()
            if children or t.attrib:
                if text:
                  d[t.tag]['#text'] = text
            else:
                d[t.tag] = text
        return d

    def filters(self):
        return {
            'from_xml': self.from_xml,
            'xml_to_json': self.xml_to_json
        }

    def from_xml(self, data):
        root = ET.ElementTree(ET.fromstring(data)).getroot()
        return self.etree_to_dict(root)

    def xml_to_json(self, data):
        return json.dumps(self.from_xml(data))

File playbook.yml , I use a jinja2 template to filter key and combine values in a list, then I convert this list to a dict with filter items2dict :

---
- hosts: localhost
  gather_facts: no
  connection: local
  tasks:
  - set_fact:
      xml_message: |
        <rpc-reply message-id="urn:uuid:1914-b84d7ff">
          <lldp-neighbors-information style="brief">
              <lldp-neighbor-information>
                <lldp-local-port-id>A1</lldp-local-port-id>
                <lldp-local-parent-interface-name>-</lldp-local-parent-interface-name>
                <lldp-remote-chassis-id-subtype>A2</lldp-remote-chassis-id-subtype>
                <lldp-remote-chassis-id>A3</lldp-remote-chassis-id>
                <lldp-remote-port-id-subtype>A4</lldp-remote-port-id-subtype>
                <lldp-remote-port-id>A5</lldp-remote-port-id>
                <lldp-remote-system-name>A6</lldp-remote-system-name>
              </lldp-neighbor-information>
              <lldp-neighbor-information>
                <lldp-local-port-id>B1</lldp-local-port-id>
                <lldp-local-parent-interface-name>-</lldp-local-parent-interface-name>
                <lldp-remote-chassis-id-subtype>B2</lldp-remote-chassis-id-subtype>
                <lldp-remote-chassis-id>B3</lldp-remote-chassis-id>
                <lldp-remote-port-id-subtype>B4</lldp-remote-port-id-subtype>
                <lldp-remote-port-id>B5</lldp-remote-port-id>
                <lldp-remote-system-name>B6</lldp-remote-system-name>
              </lldp-neighbor-information>
              <lldp-neighbor-information>
                <lldp-local-port-id>C1</lldp-local-port-id>
                <lldp-local-parent-interface-name>-</lldp-local-parent-interface-name>
                <lldp-remote-chassis-id-subtype>C2</lldp-remote-chassis-id-subtype>
                <lldp-remote-chassis-id>C3</lldp-remote-chassis-id>
                <lldp-remote-port-id-subtype>C4</lldp-remote-port-id-subtype>
                <lldp-remote-port-id>C5</lldp-remote-port-id>
              </lldp-neighbor-information>
          </lldp-neighbors-information>
        </rpc-reply>
  - set_fact:
      my_dict: |
        {% set json_message = xml_message | from_xml %}
        {% for item in json_message['rpc-reply']['lldp-neighbors-information']['lldp-neighbor-information'] %}
        {%   if "lldp-remote-system-name" in item %}
        - key: {{ item['lldp-remote-system-name'] }}
          value: [{{ item['lldp-local-port-id'] }}, {{ item['lldp-remote-chassis-id-subtype'] }}]
        {%   endif %}
        {% endfor %}
  - set_fact:
      my_dict: "{{ my_dict | from_yaml | items2dict }}"
  - debug:
      var: my_dict

Executing this playbook with ansible-playbook playbook.yml returns :

PLAY [localhost] **********************************

TASK [set_fact] ***********************************
ok: [localhost]

TASK [set_fact] ***********************************
ok: [localhost]

TASK [set_fact] ***********************************
ok: [localhost]

TASK [debug] **************************************
ok: [localhost] => {
    "my_dict": {
        "A6": [
            "A1", 
            "A2"
        ], 
        "B6": [
            "B1", 
            "B2"
        ]
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM