Ansible dict format error: considered as string

2020-01-19 01:03发布

问题:

I have a rather strange problem with an Ansible playbook.

This bug only occurs in my Docker container with Debian:9.9-slim and I've tried with different Docker distributions but it doesn't change the problem.

The errors I'm showing you may have line inconsistencies. This is because I have commented on a whole part of my playbook and I am showing you the playbook without the comments for reasons of professional secrecy.

So, my application is a flask-restplus API that launches playbooks via ansible-runner. In this case, ansible provides a dictionary called "device" via extravars:

    {
        "admin_password": "password",
        "admin_user": "admin",
        "dns1_server_ip": "0.0.0.0",
        "equipement_name": "DEVICE_NAME",
        "hostname": "ANSIBLE_BUG",
        "ip": "0.0.0.0",
        "port": 80
    }

This is my playbook:

    - hosts: localhost
      connection: local
      any_errors_fatal: no
      tasks:
        - debug:
            msg: '{{ device }}'

        - debug:
            msg: '{{ device["ip"] }}'

I've also try this way:

    - hosts: localhost
      connection: local
      any_errors_fatal: no
      tasks:
        - debug:
            msg: '{{ device }}'

        - debug:
            msg: '{{ device.ip }}'

And I've this error:

deploy okay [WARNING]: Unable to parse /etc/ansible/hosts as an inventory source
 [WARNING]: No inventory was parsed, only implicit localhost is available
 [WARNING]: provided hosts list is empty, only localhost is available. Note
that the implicit localhost does not match 'all'

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

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": {
        "admin_password": "",
        "admin_user": "admin",
        "dns1_server_ip": "8.8.8.8",
        "equipement_name": "UTM-POULAIN-INET",
        "hostname": "POULAIN",
        "ip": "212.234.67.114",
        "port": 22
    }
}

TASK [debug] *******************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'str object' has no attribute 'ip'\n\nThe error appears to have been in '/bfm-app/ansible_svc/app/core/ansible/staging/mff/main.yml': line 60, column 9, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n
     - debug:\n        ^ here\n"}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=1

Someone on an other post suggest me to use "from_json" like this:

    - debug: 
        msg: '{{ device | from_json }}' 

    - debug: 
        msg: '{{ (device | from_json)["ip"] }}'

And i've got a new error:

    deploy okay [WARNING]: Unable to parse /etc/ansible/hosts as an inventory source
    [WARNING]: No inventory was parsed, only implicit localhost is available
    [WARNING]: provided hosts list is empty, only localhost is available. Note
    that the implicit localhost does not match 'all'

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

    TASK [Gathering Facts] *********************************************************
    ok: [localhost]

    TASK [debug] *******************************************************************
    fatal: [localhost]: FAILED! => {"msg": "the field 'args' has an invalid value ({'msg': '{{ device | from_json }}'}), and could not be converted to an dict.The error was: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)\n\nThe error appears to have been in '/bfm-app/ansible_svc/app/core/ansible/staging/mff/main.yml': line 56, column 9, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n      # #=============================#\n      - debug:\n        ^ here\n"}

    PLAY RECAP *********************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=1

I tried commenting on the first msg debug, but I have the same error.

So I've been trying to iterate on device:

    - debug: 
        msg: '{{ item }}'
      with_dict: "{{ device }}"

And it's work:

    deploy okay [WARNING]: Unable to parse /etc/ansible/hosts as an inventory source
    [WARNING]: No inventory was parsed, only implicit localhost is available
    [WARNING]: provided hosts list is empty, only localhost is available. Note
    that the implicit localhost does not match 'all'

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

    TASK [Gathering Facts] *********************************************************

    ok: [localhost]

    TASK [debug] *******************************************************************
    ok: [localhost] => (item={'value': 'ANSIBLE_BUG', 'key': 'hostname'}) => {
        "msg": {
            "key": "hostname",
            "value": "ANSIBLE_BUG"
        }
    }
    ok: [localhost] => (item={'value': '0.0.0.0', 'key': 'dns1_server_ip'}) => {
        "msg": {
            "key": "dns1_server_ip",
            "value": "0.0.0.0"
        }
    }
    ok: [localhost] => (item={'value': 'password', 'key': 'admin_password'}) => {
        "msg": {
            "key": "admin_password",
            "value": "password"
        }
    }
    ok: [localhost] => (item={'value': 80, 'key': 'port'}) => {
        "msg": {
            "key": "port",
            "value": 80
        }
    }
    ok: [localhost] => (item={'value': 'user', 'key': 'admin_user'}) => {
        "msg": {
            "key": "admin_user",
            "value": "user"
        }
    }
    ok: [localhost] => (item={'value': '0.0.0.0', 'key': 'ip'}) => {
        "msg": {
            "key": "ip",
            "value": "0.0.0.0"
        }
    }
    ok: [localhost] => (item={'value': 'DEVICE_NAME', 'key': 'equipement_name'}) => {
        "msg": {
            "key": "equipement_name",
            "value": "DEVICE_NAME"
        }
    }

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=0    unreachable=0    failed=0

I conclude device is therefore a dictionary.

But I wanted to make sure, so I created a variable to which I assign "device":

    - set_fact:
        device_dict: '{{ device }}'

    - debug: 
        msg: '{{ device_dict }}'

    - debug: 
        msg: '{{ device_dict["ip"] }}'

And it's work:

    deploy okay [WARNING]: Unable to parse /etc/ansible/hosts as an inventory source
    [WARNING]: No inventory was parsed, only implicit localhost is available
    [WARNING]: provided hosts list is empty, only localhost is available. Note
    that the implicit localhost does not match 'all'

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

    TASK [Gathering Facts] *********************************************************
    ok: [localhost]

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

    TASK [debug] *******************************************************************
    ok: [localhost] => {
        "msg": {
            "admin_password": "password",
            "admin_user": "admin",
            "dns1_server_ip": "0.0.0.0",
            "equipement_name": "DEVICE_NAME",
            "hostname": "ANSIBLE_BUG",
            "ip": "0.0.0.0",
            "port": 80
        }
    }

    TASK [debug] *******************************************************************
    ok: [localhost] => {
        "msg": "0.0.0.0"
    }

    PLAY RECAP *********************************************************************
    localhost                  : ok=4    changed=0    unreachable=0    failed=0

    PLAY RECAP *********************************************************************
    localhost                  : ok=4    changed=0    unreachable=0    failed=0

As a reminder, this playbook is used in a Docker container. Except if I run it on the host machine running Debian 9, my initial playbook works perfectly.

I could use the last solution exposed above, but this would make me modify a large number of playbooks containing even a large number of tasks, so I will only do this as a last resort.

If you have any questions don't hesitate :)

Do you have any idea what the problem might be?