Ansible: generate variables in loops

18,392

Solution 1

You may define template of your object and use it in loops:

---
- hosts: localhost
  gather_facts: no
  vars:
    ip_template:
      primary_ip: "192.168.100.{{ item }}"
      primary_gw: "192.168.100.254"
      extra_ip: "10.0.1.{{ item }}"
      extra_gw: "10.0.1.254"
  tasks:
    # execute single task, no need in list
    - debug:
        msg: "{{ ip_template }}"
      with_sequence: "start=1 count=5"

    # construct list
    - set_fact:
        ip_list: "{{ (ip_list | default([])) + [ip_template] }}"
      with_sequence: "start=1 count=5"
    - debug:
        msg: "{{ ip_list }}"

If you still want to have ip_list defined in your variables, you need to construct complex Jinja2 statement to generate list in JSON-format and not YAML, as you tried. Tiny example of such string with two objects inside: '[{"ip":"192.168.0.10","gw":"192.168.0.1"},{"ip":"192.168.0.20","gw":"192.168.0.1"}]'

Solution 2

If there are many complex variables needs to be generated by loops, and if/else. Sometimes, it would become nasty and less understandable when trying to generate them in tasks. Create variable files in jinja template format might help.

For example, create the variable generated by jinja template:

# variable file
{% for idx in range(1, 101) %}
- primary_ip: 192.168.100.{{ idx }}
  primary_gw: 192.168.100.254
  extra_ip: 10.0.1.{{ idx }}
  extra_gw: 10.0.1.254
{% endfor %}

Then read variable in tasks by template lookup plugin:

# playbook tasks
- name: Read variables
  set_fact:
    ip_list: "{{ lookup('template', 'path_to_file') | from_yaml }}"

And the result will be:

TASK [set_fact] *******************************************************************************************************************
Tuesday 06 February 2018  17:16:55 +0800 (0:00:00.110)       0:00:00.110 ******
Tuesday 06 February 2018  17:16:55 +0800 (0:00:00.108)       0:00:00.108 ******
ok: [localhost]

TASK [debug] **********************************************************************************************************************
Tuesday 06 February 2018  17:16:55 +0800 (0:00:00.137)       0:00:00.247 ******
Tuesday 06 February 2018  17:16:55 +0800 (0:00:00.137)       0:00:00.245 ******
ok: [localhost] => {
    "ip_list": [
        {
            "extra_gw": "10.0.1.254",
            "extra_ip": "10.0.1.1",
            "primary_gw": "192.168.100.254",
            "primary_ip": "192.168.100.1"
        },
        {
            "extra_gw": "10.0.1.254",
            "extra_ip": "10.0.1.2",
            "primary_gw": "192.168.100.254",
            "primary_ip": "192.168.100.2"
        },
        ...........
Share:
18,392
bruin
Author by

bruin

I am new to functional programming world in general. Occasionally use JavaScript at work; Want to learn more and to see if the knowledge (or the way of thinking) can be applied, directly or indirectly, to daily software engineering work.

Updated on June 28, 2022

Comments

  • bruin
    bruin almost 2 years

    In my variable file, I need to define a list variable whose items are of similar pattern, and also share some (redundant) info. Instead of manually type all those info, I would like to generate the list variable in a loop.

    e.g., I have 100 hosts, with primary ip address 192.168.100.[1:100], and each host has an extra ip addresses 10.0.1.[1:100]. All primary IPs use the same gateway, say 192.168.100.254, and all extra IPs use another gateway, say 10.0.1.254.

    In a task, I want to loop through all hosts, and for each host, it's primary ip, extra ip, and gateways are all needed. I want to use "with_items" in my task, so I want to have a list variable "IP_ADDRS", within which each item is a dict like below:

    { primary_ip: 192.168.100.x, primary_gw: 192.168.100.254, extra_ip: 10.0.1.x, extra_gw: 10.0.1.254}
    

    Instead of define IP_ADDRS manually:

    IP_ADDRS:
      - { primary_ip: 192.168.100.1, primary_gw: 192.168.100.254, extra_ip: 10.0.1.1, extra_gw: 10.0.1.254}
      - { primary_ip: 192.168.100.2, primary_gw: 192.168.100.254, extra_ip: 10.0.1.2, extra_gw: 10.0.1.254}
      - ...
    

    I want to generate the list variable "IP_ADDRS" somehow...

    I tried jinja2 statements, something like below:

    IP_ADDRS: >
      "{% for idx in range(1, 101) %}
      - { primary_ip: 192.168.100.{{ idx }}, primary_gw: 192.168.100.254, extra_ip: 10.0.1.{{ idx }}, extra_gw: 10.0.1.254 }
      "{% endfor %}"
    

    When I print IP_ADDRS using debug module, it does literally print all items in the list, BUT it seems Ansible is not treat this variable as a LIST, thus

    with_items: {{ IP_ADDRS }}

    does not work as I expected.

    Is there any thing wrong with the jinja2 statement, or is there a way to achieve the same result?

    Thanks a lot,

    /bruin

  • bruin
    bruin about 7 years
    Thanks a lot. I will give it a try soon!
  • bruin
    bruin about 7 years
    As I tested, this works great! "set_fact" is what I didn't know, with that I feel that I can fly :) Thanks again, Konstantin!