Ansible with_dict template use

user1432403 picture user1432403 · Sep 24, 2014 · Viewed 16.6k times · Source

I have the following task:

- name: copy server.xml
  template: src=server.xml dest=/var/containers/{{ item.key }}/conf
  with_dict: containers

And I've also added the containers dictionary in my group_vars

containers:
  frontend:
    http_port: 8080
  backend:
    http_port: 8081

Finally here is the relevant snippet from server.xml

<Connector port="{{ http_port }}" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

What I want to happen is that the relevant http_port gets used in the template module. But instead I get and error:

fatal: [localhost] => {'msg': "AnsibleUndefinedVariable: One or more undefined variables: 'http_port' is undefined", 'failed': True}

Is this possible? How do I leverage an item's values for variable substitution?

Answer

nikobelia picture nikobelia · Sep 1, 2015

Using {{ item.value.http_port }} is exactly the right solution.

When you pass with_dict, it loops through the task passing each of the items in your containers dictionary as {{ item }}, where the item has a key and whatever values that dictionary item contains - in your case, key/value pairs where the keys are http_port and the values are those two different integers - but you can pass seriously complex nested dictionaries where it gets even more important to access things with the {{ item.value.http_port }} syntax you came up with.

The thing to be wary of as you get to more complex template usage is how to mix stuff up and set defaults and use if-statements when you have some extra variables to template for one host (or container, or whatever) but not another.

To get to grips on it, read up on Jinja2, the language Ansible interprets templates in. A good example would be something like serving files over SSL on your frontend here, but not the backend. Use syntax like {{ foo | default('bar') }} to avoid Ansible getting angry about you trying to use undefined variables, and if-statements so as to make sure you're only templating the stuff you need.

A rough sketch - say you had:

containers:
  frontend:
    http_port: 8080
    https_port: 8443
    ssl_cert: ./files/keystore
    ssl_pass: "{{ vaulted_vars.ssl_pass }}" 
  backend:
    http_port: 8081

In that case, imagining you'd had a task to copy that keystore over onto the filesystem when needed, you could use something along the lines of:

<Connector port="{{ item.value.http_port }}" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="{{ item.value.https_port | default('8443')" />
           {% if item.value.ssl_cert is defined %} 
           scheme="https" secure="true" SSLEnabled="true"
           keystoreFile="${user.home}/.keystore" keystorePass="{{ item.value.ssl_pass }}"
           clientAuth="false" sslProtocol="TLS"/>
           {% endif %}

Happy templating!