Create variable as dict using jinja2 within playbook

14,605 views
Skip to first unread message

Anth Courtney

unread,
Oct 20, 2014, 11:55:28 PM10/20/14
to ansible...@googlegroups.com
Hello there,

Is it possible to create a variable which is a dict using jinja2 within a playbook?

For example, I have the following (contrived) playbook, within which I am trying to create a 'host_info' dict which I can then access from a task:

---

- hosts: 127.0.0.1
  connection: local
  sudo: no

  vars:
    app_servers: 5
    ipaddress_base: "192.168.0"
    rmi_portbase: 10000
    host_info: |
      {%- for number in range(1,app_servers + 1) -%}
        {% set host = 'app' + number|string %}
       - hostname: host
       - ipaddress: {{ ipaddress_base }}.{{ number }}
       - rmi_port: {{ rmi_portbase|int + ( number * 10) }}
      {%- endfor %}

  tasks:

  - debug: var=item
    with_items: host_info

  - debug: var=item.hostname
    with_dict: host_info


However I can't seem to generate a dict as intended.

$ ansible-playbook -i "localhost," test.yml 

PLAY [127.0.0.1] ************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [127.0.0.1]

TASK: [debug var=item] ******************************************************** 
ok: [127.0.0.1] => (item= - hostname: host
 - ipaddress: 192.168.0.1
 - rmi_port: 10010 - hostname: host
 - ipaddress: 192.168.0.2
 - rmi_port: 10020 - hostname: host
 - ipaddress: 192.168.0.3
 - rmi_port: 10030 - hostname: host
 - ipaddress: 192.168.0.4
 - rmi_port: 10040 - hostname: host
 - ipaddress: 192.168.0.5
 - rmi_port: 10050) => {
    "item": " - hostname: host\n - ipaddress: 192.168.0.1\n - rmi_port: 10010 - hostname: host\n - ipaddress: 192.168.0.2\n - rmi_port: 10020 - hostname: host\n - ipaddress: 192.168.0.3\n - rmi_port: 10030 - hostname: host\n - ipaddress: 192.168.0.4\n - rmi_port: 10040 - hostname: host\n - ipaddress: 192.168.0.5\n - rmi_port: 10050"
}

TASK: [debug var=item.hostname] *********************************************** 
fatal: [127.0.0.1] => with_dict expects a dict

FATAL: all hosts have already failed -- aborting

PLAY RECAP ******************************************************************** 
           to retry, use: --limit @/tmp/test.retry

127.0.0.1                  : ok=2    changed=0    unreachable=1    failed=0   


Any thoughts / assistance would be appreciated.

cheers,
Anth

James Cammarata

unread,
Oct 23, 2014, 12:29:42 PM10/23/14
to ansible...@googlegroups.com
Hi Anth,

Unfortunately, this cannot be done the way you're trying it. You're creating YAML after the YAML structure is already parsed, so the variable is just storing it as a string.

Instead of doing this, I would recommend using the add_host module (http://docs.ansible.com/add_host_module.html), which can also accept any additional variables you may wish to associate with that host entry.

Thanks!

--
You received this message because you are subscribed to the Google Groups "Ansible Project" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ansible-proje...@googlegroups.com.
To post to this group, send email to ansible...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ansible-project/a3ef549a-441b-464f-a494-b635424b0356%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Steve Jacobs

unread,
Apr 9, 2015, 11:33:56 AM4/9/15
to ansible...@googlegroups.com
I realize this is an old thread, but why does this not work? It works as expected for arrays...

For instance see this blog post:

That works just fine, but if you try to modify this to output an array of dictionaries, or similar, ansible doesn't interpret the result correctly. Why?

Steve Jacobs

unread,
Apr 9, 2015, 11:34:12 AM4/9/15
to ansible...@googlegroups.com

Why is it possible to do with an array though?
- hosts: all
  connection: local
  gather_facts: false
  tasks:
    - set_fact:
        myvar: |
          {% set comma = joiner(",") %}
          [ {% for item in ['honey', 'bunch'] -%}
              {{ comma() }}"monkey"
          {%- endfor %}]
    - debug: var=myvar

PLAY [all] ********************************************************************

TASK: [set_fact ] *************************************************************
ok: [127.0.0.1]

TASK: [debug var=myvar] *******************************************************
ok: [127.0.0.1] => {
    "var": {
        "myvar": [
            "monkey",
            "monkey"
        ]
    }
}

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


But:
- hosts: all
  connection: local
  gather_facts: false
  tasks:
    - set_fact:
        myvar: |
          {% set comma = joiner(",") %}
          [ {% for item in ['honey', 'bunch'] -%}
              {{ comma() }}{ monkey: banana }
          {%- endfor %}]
    - debug: var=myvar

Produces:
TASK: [debug var=myvar] *******************************************************
ok: [127.0.0.1] => {
    "var": {
        "myvar": "[ { monkey: banana },{ monkey: banana }]"
    }
}

And:
- hosts: all
  connection: local
  gather_facts: false
  tasks:
    - set_fact:
        myvar: |
          {{ "\n" }}
          {%- for item in ['honey', 'bunch'] -%}
              - { monkey: banana }
          {% endfor %}
    - debug: var=myvar

TASK: [debug var=myvar] *******************************************************
ok: [127.0.0.1] => {
    "var": {
        "myvar": "\n- { monkey: banana }\n- { monkey: banana }\n"
    }
}

Why does one way work and the other way fail?

On Thursday, October 23, 2014 at 12:29:42 PM UTC-4, James Cammarata wrote:

Anand Buddhdev

unread,
Apr 9, 2015, 6:37:12 PM4/9/15
to ansible...@googlegroups.com
On Thursday, 9 April 2015 17:33:56 UTC+2, Steve Jacobs wrote:

I realize this is an old thread, but why does this not work? It works as expected for arrays...

I would also like to know this. I have previously tried to template playbooks using Jinja2, without success. I would like to ask again: wouldn't it be super if Ansible's playbooks were *first* run through Jinja2, so that we could use all of Jinja's features to customise some parts of playbooks?

Anand

Brian Coca

unread,
Apr 9, 2015, 7:13:47 PM4/9/15
to ansible...@googlegroups.com
> wouldn't it be super if Ansible's playbooks were *first* run through Jinja2, so that
> we could use all of Jinja's features to customise some parts of playbooks?

No.

short answer: it defeats the simplicity goal
--
Brian Coca

Steve Jacobs

unread,
Apr 9, 2015, 7:14:40 PM4/9/15
to ansible...@googlegroups.com, Brian Coca
Sure I understand why not to do this. My question is why does one way work (arrays) and the other way (dicts) not work. Simple question, I can’t find an answer to it.

-- 
Steve Jacobs
Sent with Airmail
--
You received this message because you are subscribed to a topic in the Google Groups "Ansible Project" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ansible-project/ZDz-8tcsdTA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to ansible-proje...@googlegroups.com.
To post to this group, send email to ansible...@googlegroups.com.

Brian Coca

unread,
Apr 9, 2015, 7:16:45 PM4/9/15
to Steve Jacobs, ansible...@googlegroups.com
The answer is that neither returns what you think it does, both return
a string, the issue is that with some fields they expect a string or
array or array in string (which then they split with , to get a real
array). Fields that expect dicts do not do any magic nor expect
strings at any point.


--
Brian Coca

Nehal J Wani

unread,
Feb 13, 2017, 9:07:12 AM2/13/17
to Ansible Project, broken...@gmail.com
I know this is  2 year old thread, but I bumped into this today and was able to create a dictionary. Something like this:

/tmp $ cat example.yml
- hosts: 127.0.0.1

  vars:
    app_servers: 5
    ipaddress_base: "192.168.0"
    rmi_portbase: 10000
    host_info: |
      {% set res = [] -%}
      {%- for number in range(1,app_servers + 1) -%}
        {% set ignored = res.extend([{
          'hostname': 'app' + number|string,
          'ipaddress': ipaddress_base + '.' + number|string,
          'rmi_port': rmi_portbase|int + ( number * 10)
          }]) -%}
      {%- endfor %}
      {{ res }}

  tasks:

    - debug: var=host_info
/tmp $ ansible-playbook example.yml
 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [127.0.0.1] ***************************************************************

TASK [setup] *******************************************************************
ok: [127.0.0.1]

TASK [debug] *******************************************************************
ok: [127.0.0.1] => {
    "host_info": [
        {
            "hostname": "app1", 
            "ipaddress": "192.168.0.1", 
            "rmi_port": 10010
        }, 
        {
            "hostname": "app2", 
            "ipaddress": "192.168.0.2", 
            "rmi_port": 10020
        }, 
        {
            "hostname": "app3", 
            "ipaddress": "192.168.0.3", 
            "rmi_port": 10030
        }, 
        {
            "hostname": "app4", 
            "ipaddress": "192.168.0.4", 
            "rmi_port": 10040
        }, 
        {
            "hostname": "app5", 
            "ipaddress": "192.168.0.5", 
            "rmi_port": 10050
        }
    ]
}

PLAY RECAP *********************************************************************
127.0.0.1                  : ok=2    changed=0    unreachable=0    failed=0 
Reply all
Reply to author
Forward
0 new messages