ansible-collections / ansible.utils

A collection of ansible utilities for the content creator.
GNU General Public License v3.0
74 stars 76 forks source link

from_xml filter returns a string instead of a python dictionary #114

Closed jpiron closed 2 months ago

jpiron commented 2 years ago
SUMMARY

The from_xml filter returns a string instead of returning a python dictionary as its documentation reports and as the from_json and from_yaml filters do.

ISSUE TYPE
COMPONENT NAME

from_xml

ANSIBLE VERSION
ansible [core 2.11.3] 
  config file = /home/user/workspace/ansible-playbooks-2/ansible.cfg
  configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/user/workspace/ansible-playbooks-2/.direnv/python-3.8.10/lib/python3.8/site-packages/ansible
  ansible collection location = /home/user/workspace/ansible-playbooks-2/collections
  executable location = /home/user/workspace/ansible-playbooks-2/.direnv/python-3.8.10/bin/ansible
  python version = 3.8.10 (default, Nov 26 2021, 20:14:08) [GCC 9.3.0]
  jinja version = 3.0.1
  libyaml = True
COLLECTION VERSION
# /home/user/workspace/ansible-playbooks-2/collections/ansible_collections
Collection    Version
------------- -------
ansible.utils 2.4.2  
CONFIGURATION
AGNOSTIC_BECOME_PROMPT(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = False
ANSIBLE_FORCE_COLOR(env: ANSIBLE_FORCE_COLOR) = True
CALLBACKS_ENABLED(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = ['timer', 'ansible.posix.profile_tasks']
COLLECTIONS_PATHS(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = ['/home/user/workspace/ansible-playbooks-2/collections']
DEFAULT_CONNECTION_PLUGIN_PATH(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = ['/home/user/workspace/ansible-playbooks-2/connection_plugins']
DEFAULT_FORKS(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = 10
DEFAULT_HOST_LIST(env: ANSIBLE_INVENTORY) = ['/home/user/workspace/ansible-playbooks-2/inventories/test']
DEFAULT_TIMEOUT(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = 10
DEFAULT_VAULT_PASSWORD_FILE(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = /home/user/workspace/ansible-playbooks-2/inventories/vault_password.sh
DISPLAY_SKIPPED_HOSTS(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = False
HOST_KEY_CHECKING(env: ANSIBLE_HOST_KEY_CHECKING) = False
INVENTORY_ANY_UNPARSED_IS_FAILED(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = True
INVENTORY_ENABLED(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = ['host_list', 'ini', 'constructed']
INVENTORY_IGNORE_EXTS(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = ['~', '.orig', '.bak', '.cfg', '.retry', '.pyc', '.pyo', 'LICENSE', '.md', '.txt', 'secrets.yml', 'vars.yml', 'ssh_private_key']
PLAYBOOK_DIR(env: ANSIBLE_PLAYBOOK_DIR) = /home/user/workspace/ansible-playbooks-2
RETRY_FILES_ENABLED(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = True
RETRY_FILES_SAVE_PATH(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = /tmp
TASK_TIMEOUT(env: ANSIBLE_TASK_TIMEOUT) = 180
VARIABLE_PRECEDENCE(/home/user/workspace/ansible-playbooks-2/ansible.cfg) = ['all_inventory', 'groups_inventory', 'all_plugins_play', 'groups_plugins_play', 'all_plugins_inventory', 'groups_plugins_inventory']
OS / ENVIRONMENT

Ubuntu 20.04.3 LTS (Focal Fossa)

STEPS TO REPRODUCE
- hosts: localhost
  diff: true
  gather_facts: false

  tasks:
    - debug:
        msg: "{{ (some_xml | ansible.utils.from_xml)['root']['leaf'] }}"
      vars:
        some_xml: |
          <?xml version="1.0" encoding="UTF-8"?>
          <root>
              <leaf>
                <attribute>foobar</attribute>
              </leaf>
          </root>
EXPECTED RESULTS

This should print

"msg": {                 
    "attribute": "foobar"
}                        
ACTUAL RESULTS
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'str object' has no attribute 'root'\n\nThe error appears to be in '/home/john/workspace_tagpay/ansible-playbooks-2/foo.tmp.yml': line 6, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n  tasks:\n    - debug:\n      ^ here\n"}
ashwini-mhatre commented 2 years ago

@jpiron Hi, can you try following code snippet?

-
  hosts: localhost
  connection: ansible.netcommon.network_cli
  gather_facts: no
  vars:
    ansible_persistent_log_messages: True
    some_xml: |
      <?xml version="1.0" encoding="UTF-8"?>
      <root>
          <leaf>
            <attribute>foobar</attribute>
          </leaf>
      </root>
  tasks:
    - ansible.builtin.set_fact:
        test: "{{ (some_xml | ansible.utils.from_xml)}}"
    - debug:
        msg: "{{test.root.leaf}}"
jpiron commented 2 years ago

Hi @ashwini-mhatre, using an intermediate variable works but this prevents from doing something like:

- hosts: localhost
  connection: local
  gather_facts: no
  tasks:
    - uri:
        url: http://httpbin.org/xml
        headers:
          accept: "application/xml"
        return_content: true
      register: result
      until: (result.content | ansible.utils.from_xml)['slideshow']['@author'] == 'Yours Truly'
      retries: 3
      delay: 2

It works by chaining the from_json filter like the following:

- hosts: localhost
  connection: local
  gather_facts: no
  tasks:
    - uri:
        url: http://httpbin.org/xml
        headers:
          accept: "application/xml"
        return_content: true
      register: result
      until: (result.content | ansible.utils.from_xml | from_json)['slideshow']['@author'] == 'Yours Truly'
      retries: 3
      delay: 2

but it shouldn't be required if the goal is to provide the same behavior as from_yaml and from_json filters.

jnm27 commented 1 year ago

Concur, this should be fixed

jnm27 commented 1 year ago

Workaround is to use the from_yaml filter afterwards