with_dict and Jinja templates - Error

Hi all,

Trying to pull together my understanding of Jinja’s looping constructs, Ansible’s with_dict and from what I’ve seen out in the wild (i.e. on GitHub). I’ve taken a look around at some other examples but can’t seem to adapt them to my approach (i.e. https://github.com/timmahoney/ansible-redhat-extra-repos/blob/master/defaults/main.yml is close to what I’m after)

At the moment I’m attempting to loop over a YAML dict of OS user ↔ DB user mappings (for pg_ident.conf) and am running into an error:

# host_vars/
postgres_users:
baltar:
os: “{{ username }}”
db: “{{ db_name }}”
caprica:
os: “{{ username }}”
db: “{{ db_name }}”

# pg_ident.conf.j2

{% for user in postgres_users %}
{{ user.db }} {{ user.os }} {{ user.key }}
{% endfor %}

# roles/postgresql/tasks/postgres.yml

  • name: copy postgres ident maps
    template: src=pg_ident.conf.j2 dest=/etc/postgresql/9.3/main/pg_ident.conf owner=postgres group=postgres mode=0640
    register: postgres_ident
    with_dict: postgres_users

# error message

TASK: [postgres | copy postgres ident maps] ***********************************
fatal: [default] => {‘msg’: “AnsibleUndefinedVariable: One or more undefined variables: ‘str object’ has no attribute ‘db’”, ‘failed’: True}
fatal: [default] => {‘msg’: ‘One or more items failed.’, ‘failed’: True, ‘changed’: False, ‘results’: [{‘msg’: “AnsibleUndefinedVariable: One or more undefined variables: ‘str object’ has no attribute ‘db’”, ‘failed’: True}]}

With dict works a little differently than with_items.

with_items will return one item in the postgres_users list, one after another, if it were a list of users.

postgres_users:

  • name: boxey
    db: cargo_ship

Now, when using with_dict, it works differently, returning keys as item.key and values as item.value

http://docs.ansible.com/playbooks_loops.html#looping-over-hashes

I’d consider either passing a list as above or using with_dict, but noting what it returns.

Meanwhile, this looks problematic:

template: src=pg_ident.conf.j2 dest=/etc/postgresql/9.3/main/pg_ident.conf owner=postgres group=postgres mode=0640

Why?

You’re evaluating it multiple times in a loop, but there is no variable in the destination path. The result is you’ve re-templated the file many times.

I think you don’t need the loop, and just need to simply reference postgres_users as a variable in the template, and templating the file once is fine.

But you can’t use a simple for loop, because postgres_users is not a list.

Try:

{% for (key, value) in postgres_users.iteritems() %}

{% endfor %}

if you want to keep things as a hash/dictionary.

Meanwhile, this looks problematic:

template: src=pg_ident.conf.j2 dest=/etc/postgresql/9.3/main/pg_ident.conf owner=postgres group=postgres mode=0640

Why?
You’re evaluating it multiple times in a loop, but there is no variable in the destination path. The result is you’ve re-templated the file many times.
I think you don’t need the loop, and just need to simply reference postgres_users as a variable in the template, and templating the file once is fine.

I was originally under the impression that you needed to pass lists/dicts to templates explicitly (i.e. for use by Jinja) - hadn’t realised that they were global.

You’ve solved my confusion though (and dropped in a BSG reference to boot!) — thanks Michael.

No problem!

“hadn’t realised that they were global.”

Not always global, some variables are scoped to the host because they are set on the group or host (like group_vars, host_vars, facts, etc). But yeah you don’t have to pass anything explicitly to the template module ever, it grabs everything that should be in scope, and takes care of sorting variable precedence out automatically.