Search code examples
ansiblejinja2ansible-inventoryansible-template

undefined variable error in ansible when using jinja2 templates


I am practising ansible. I have come all way through basic and now I am working on building a jinja2 template and using it. There is an exercise when I need to build a report for groups and upload upload them to their respective dns server. Reports for all the server in american group will be uploaded to dns_server_america, similarly for asia.

dns_server_america ansible_host=172.20.1.100 ansible_ssh_pass=Passw0rd ansible_user=root
dns_server_asia ansible_host=172.20.1.101 ansible_ssh_pass=Passw0rd ansible_user=root

[america]
web0001 ansible_hostname=web0001.company.com ansible_host=10.1.1.101 
web0002 ansible_hostname=web0002.company.com ansible_host=10.1.1.102 


[asia]
web2001 ansible_hostname=web2001.company.com ansible_host=10.1.1.201 
web2002 ansible_hostname=web2002.company.com ansible_host=10.1.1.202 

This is the YAML.

- name: Generate dns hosts files on americas servers
  hosts: dns_server_america
  tasks:
  - template: src=templates/hosts.j2 dest=/tmp/hosts.txt
    vars:
      GROUP_NAME: america

- name: Generate dns hosts files on asia servers
  hosts: dns_server_asia
  tasks:
  - template: src=templates/hosts.j2 dest=/tmp/hosts.txt
    vars:
      GROUP_NAME: asia

This is the jinja2 template.

{% for host in groups[GROUP_NAME] %}
{{ host }} {{ hostvars[host]['ansible_host'] }}
{% endfor %}

Why are we not quoting [host] and [GROUP_NAME] in the jinja2 template. Ansible says that when variables are put in square brackets, they are supposed to be wrapped within quotes. When I enclose then with quotes, I get an error message "undefined variable" and when I remove the quotes I am able to run the playbook successfully. Please advise, I may be missing something or my theory of understanding variables could be wrong.


Solution

  • Q: "Why are we not quoting [host] and [GROUP_NAME] in the jinja2 template?"

    A: Both host and GROUP_NAME are variables. The value of the variables is needed in the index. If the names of the variables are quoted 'host' or 'GROUP_NAME' the names of the variables are used instead of the variable's values.


    Direct and indirect

    For example, the template

    shell> cat test.txt.j2
    {{ dict[index] }}
    {{ dict['index'] }}
    

    and the playbook

    shell> cat playbook.yml
    - hosts: localhost
      vars:
        dict:
          index: value of attribute index
          attr1: value of attribute attr1
      tasks:
        - template:
            src: test.txt.j2
            dest: test.txt
          vars:
            index: attr1
    

    give

    shell> cat test.txt
    value of attribute attr1
    value of attribute index
    

    This is not limited to templates. It's generally valid. For example

        - debug:
            msg:
              - "{{ dict[index] }}"
              - "{{ dict['index'] }}"
          vars:
            index: attr1
    

    gives

      msg:
      - value of attribute attr1
      - value of attribute index
    

    Dotted

    It's possible to use the "dotted" reference. For example

        - debug:
            var: dict.index
    

    gives

      dict.index: value of attribute index
    

    The "dotted" reference can be used in nested dictionaries. For example with the nested dictionary

        dict:
          index:
            var1: value of attribute index
          attr1:
            var1: value of attribute attr1
    

    both versions work as expected

        - debug:
            msg:
              - "{{ dict.index.var1 }}"
              - "{{ dict['index'].var1 }}"
          vars:
            index: attr1
    

    gives

      msg:
      - value of attribute index
      - value of attribute index
    

    Dotted references in template

    But there is a difference when a template is used. When a reference is put into the brackets [] all subsequent references must be put into the brackets as well. Otherwise, the template will fail. For example

    shell> cat test.txt.j2
    {{ dict.index.var1 }}        # OK
    {{ dict.index['var1'] }}     # OK
    {{ dict['index']['var1'] }}  # OK
    {{ dict['index'].var1 }}     # WRONG: has no attribute var1
    

    will fail

    fatal: [localhost]: FAILED! => changed=false 
      msg: 'AnsibleUndefinedVariable:
            ''ansible.parsing.yaml.objects.AnsibleUnicode object''
            has no attribute ''var1'''