How to pass a different sets of variables for each iteration of loop in an Ansible task

ansibleansible-playbook

I have an Ansible task which loops through a list and for each item in the list runs a role, using include_role. For each iteration of loop I want to pass a different set of variables. Currently, I am trying to do this by creating the list as a list of dictionaries which can be specified using --extra-vars at runtime.

For example, I have task:

- name: Run the test role
  include_role:
    name: test_role
  loop: '{{ input_list }}'

And the input_list:

input_list:
  - var1: foo
    var2: bar
    var3: baz
  - var1: hello
    var3: world

But at this point how would I go about passing the current set of variables to the role? I initially tried defining each variable separately:

- name: Run the test role
  include_role:
    name: test_role
  vars:
    var1: '{{ item.var1 }}'  
    var2: '{{ item.var2 }}'  
    var3: '{{ item.var3 }}'  
  loop: '{{ input_list }}'

This works, but if one variable is missing from the input_list (i.e. should fall back to default value specified in the role defaults or at the start of the playbook for that variable) the play will fail.
So I tried using Jinja2's default filter:

- name: Run the test role
  include_role:
    name: test_role
  vars:
    var1: '{{ item.var1 | default('foo') }}'  
    var2: '{{ item.var2 | default('bar') }}'  
    var3: '{{ item.var3 | default('baz') }}'  
  loop: '{{ input_list }}'

Again, this works, but it is a bit messy as every variable needs to be defined, and it doesn't use the default values from the role. Is there a better way of doing something like this?

Best Answer

One of the options is to create alternative defaults. For example, create a minimal role for testing

shell> cat roles/test_role/defaults/main.yml
var1: default_1
var2: default_2
var3: default_3

shell> cat roles/test_role/tasks/main.yml 
- debug:
    msg: "{{ var1 }} {{ var2 }} {{ var3 }}"

The task

    - include_role:
        name: test_role

gives

  msg: default_1 default_2 default_3

Let's create alternative defaults, e.g.

shell> cat roles/test_role/defaults/alt.yml
var1: "{{ alt.var1|default('default_1') }}"
var2: "{{ alt.var2|default('default_2') }}"
var3: "{{ alt.var3|default('default_3') }}"

and use it in the include. The task below gives the same result

    - include_role:
        name: test_role
        defaults_from: alt.yml

Now, let's define the dictionary with the alternate defaults

    - include_role:
        name: test_role
        defaults_from: alt.yml
      loop: '{{ input_list }}'
      vars:
        alt: "{{ item }}"

gives

  msg: foo bar baz
  msg: hello default_2 world

The next option is to create and use alternate vars

shell> cat roles/test_role/vars/alt.yml 
var1: "{{ alt.var1|default('default_1') }}"
var2: "{{ alt.var2|default('default_2') }}"
var3: "{{ alt.var3|default('default_3') }}"

For example, the task below gives the same result. The only difference is the precedence.

    - include_role:
        name: test_role
        vars_from: alt.yml
      loop: '{{ input_list }}'
      vars:
        alt: "{{ item }}"