19

I'm trying to loop a dictionary through an ansible template using jinja2 to create a number of datasources but receive this error [{'msg': "AnsibleUndefinedVariable: One or more undefined variables: 'dict object' has no attribute 'value'", 'failed': True}]}

When running a debug task it does get the correct values back so I feel like my issue is in the template itself but I've been unable to figure out what I am doing wrong.

Ansible Task

- name: debug dictionary
  debug: msg="{{ item.value.db_url }}"
  with_dict: databases

- name: copy tomcat config files
  template: src="{{ item.src }}" dest="{{ item.dest }}"
  with_items:
    - { src: 'context.xml.j2', dest: '/opt/tomcat/conf/context.xml'}
  notify: restart tomcat
  with_dict: databases

Ansible Dictionary

databases:
  db1:
    db_resource: jdbc/db1
    db_maxidle: 50
    db_maxconn: 350
    db_maxwait: 10000
    db_user: dbuser
    db_pass: "{{ dbpass }}"
    db_url: jdbc:postgresql://server:5432/dbname
    db_driver: org.postgresql.Driver

Jinja2 Template

{% for items in databases %}
    <resource name="{{ item.value.db_resource }}" auth="container" type="javax.sql.datasource"  maxtotal="{{ item.value.db_maxconn }}" maxidle="{{ item.value.db_maxidle }}" maxwaitmillis="{{ item.value.db_maxwait }}" username="{{ item.value.db_user }}" password="{{ item.value.db_pass }}" driverclassname="{{ item.value.db_driver }}" url="{{ item.value.db_url }}" />
{% endfor %}

Debug Output

ok: [IP] => (item={'key': 'db1', 'value': {'db_maxwait': 10000, 'db_maxconn': 350, 'db_maxidle': 50, 'db_driver': 'org.postgresql.Driver', 'db_pass': u'REDACTED', 'db_resource': 'jdbc/db1', 'db_user': 'dbuser', 'db_url': 'jdbc:postgresql://server:5432/dbname'}}) => {
    "item": {
        "key": "db1",
        "value": {
            "db_driver": "org.postgresql.Driver",
            "db_maxconn": 350,
            "db_maxidle": 50,
            "db_maxwait": 10000,
            "db_pass": "REDACTED",
            "db_resource": "jdbc/db1",
            "db_url": "jdbc:postgresql://server:5432/db",
            "db_user": "dbuser"
        }
    },
    "msg": "jdbc:postgresql://server:5432/dbname"
}
techraf
  • 64,883
  • 27
  • 193
  • 198
tweeks200
  • 1,837
  • 5
  • 21
  • 33
  • Why is your second task using both `with_items` and `with_dict`, especially when it doesn't seem to be making use of the latter? I'd be very surprised if that actually worked. – jwodder Jun 10 '16 at 20:59
  • Hmm originally I had multiple files in this task using the with_items. I removed it but still running into the same issue. Makes sense to separate them though, thanks. – tweeks200 Jun 10 '16 at 21:13

5 Answers5

24

I discovered today that using dict.values() loops over each dict element's values rather than its keys. So you should be able use something like this for your template.

{% for item in databases.values() %}
    <resource name="{{ item.db_resource }}" auth="container" type="javax.sql.datasource"  maxtotal="{{ item.db_maxconn }}" maxidle="{{ item.db_maxidle }}" maxwaitmillis="{{ item.db_maxwait }}" username="{{ item.db_user }}" password="{{ item.db_pass }}" driverclassname="{{ item.db_driver }}" url="{{ item.db_url }}" />
{% endfor %}

I know that it's way after the fact, but maybe somebody else searching for this answer can make use of this additional discovery.

Mike Stankavich
  • 2,709
  • 1
  • 16
  • 8
7

In Jinja, when databases is a dictionary, for items in databases will (as in Python) iterate over the keys of the dictionary, not its key/value pairs. Thus, in your template, item.value (which I'm assuming is meant to be items.value) should be databases[items] in order to get the value associated with the key items.

jwodder
  • 54,758
  • 12
  • 108
  • 124
  • That seems to make progress. I get `One or more undefined variables: dict object has no element` now but the values do show in the error output. All the variables have values in the output. Any ideas? – tweeks200 Jun 10 '16 at 21:32
6

You can achieve your goal by modifying your jinja2 template and task like this:

Jinja2 Template:

<resource name="{{ databases[item].db_resource }}" auth="container" type="javax.sql.datasource"  maxtotal="{{ databases[item].db_maxconn }}" maxidle="{{ databases[item].db_maxidle }}" maxwaitmillis="{{ databases[item].db_maxwait }}" username="{{ databases[item].db_user }}" password="{{ databases[item].db_pass }}" driverclassname="{{ databases[item].db_driver }}" url="{{ databases[item].db_url }}" />

Ansible Tasks:

- name: debug dictionary
  debug: msg="{{ databases[item].db_url }}"
  with_items: "{{ databases | list }}"

- name: copy tomcat config files
  template: src="{{ item.src }}" dest="{{ item.dest }}"
  with_items:
    - { src: 'context.xml.j2', dest: '/opt/tomcat/conf/context.xml'}
  notify: restart tomcat
  with_items: "{{ databases | list }}"

Hope that might help you, please adjust your tasks as per your requirement

Arbab Nazar
  • 22,378
  • 10
  • 76
  • 82
1

Mike, your answer saved me a lot of searching today.

In my case I was using a list of dicts, so I had to have two if statements, something like this:

{% for dict_item in quotes %}
  {% for item in dict_item.values() %}
.. {{ item.symbol }}
.. {{ item.price }}
  {% endfor %}
{% endfor %}

Thank you!

Holden
  • 11
  • 1
  • 1
    Please elaborate how your answer adds to the others, try to comment a bit in your code maybe. – JJJ Apr 25 '19 at 16:52
-1

If you are using Python 3, this should be:

{% for key, value in dictionary.items() %}
     <resource name="{{ value.db_resource }}" ...
{% endfor %}
Manel Clos
  • 1,785
  • 2
  • 19
  • 15