How to loop over "include_role" with a list of variable dictionaries?

Hi all, I am trying to loop over my “create_user” role with a list of “users”:

    - name: Setup users
        include_role:
          name: create_user
      vars: "{{ user_vars }}"
      loop: "{{ users_to_create }}"
      loop_control:
        loop_var: "{{ user_vars }}"

Unfortunately, this does not work, because one cannot dynamically specify the vars dictionary:
ERROR! Vars in a IncludeRole must be specified as a dictionary.

Another solution I tried, was combining block and set_fact:

    - name: Setup users
      block:
        - name: Build vars for user
          set_fact: "{{ user_vars }}"
        - name: Setup user
          include_role:
            name: create_user
      loop: "{{ users_to_create }}"
      loop_control:
        loop_var: "{{ user_vars }}"

Now, this does not work because block does not support loop.

Third try, let’s explicitly specify the vars:

    - name: Setup steward users
      include_role:
        name: create_user
      vars:
        create_user__user_name: "{{ item.create_user__user_name | default(omit)}}"
        create_user__user_group: "{{ item.create_user__user_group | default(omit)}}"
        create_user__user_uid: "{{ item.create_user__user_uid | default(omit)}}"
        create_user__user_home: "{{ item.create_user__user_home | default(omit)}}"
        create_user__ssh_authorized_keys: "{{ item.create_user__ssh_authorized_keys | default(omit)}}"
        create_user__ssh_key: "{{ item.create_user__ssh_key | default(omit)}}"
      loop: "{{ users_to_create }}"

This failed horribly, because now instead of getting the user_home specified in the role’s defaults, I get a lot of __omit_place_holder__ values.

So, what is the Ansible way of looping over include_role with a list of variable dicts?

Deceptively tricky! One note: your using loop_var incorrectly. loop_var sets the variable that the loop data is stored in, if the default item is not desired.

The block solution does not work, but the solution for a loop over a block is to do an extra layer of import/include. For example

---
#main.yml
- name: Include user tasks
  include_tasks: users.yml
  loop: "{{ users_to_create }}"
  loop_control:
    loop_var: user_to_create

---
#users.yml
- name: Build vars for user
  set_fact: { "{{ __user_var.key }}": "{{ __user_var.value }}" }
  loop: "{{ user_to_create | dict2items }}"
  loop_control:
    loop_var: __user_var

- name: Setup user
  include_role:
    name: create_user

Note that ansible does not do variable scopes, so if the first user defines a variable (like create_user__user_group) and the second user doesnt, you will probably run into trouble.

I would suggest handling this in the role, since that seems easier. You could allow the role to accept a list of users, or have the role accept empty strings as inputs and then ignore them. That way your third attempt would work:

    - name: Setup steward users
      include_role:
        name: create_user
      vars:
        create_user__user_name: "{{ item.create_user__user_name | default('')}}"
        create_user__user_group: "{{ item.create_user__user_group | default('')}}"
        create_user__user_uid: "{{ item.create_user__user_uid | default('')}}"
        create_user__user_home: "{{ item.create_user__user_home | default('')}}"
        create_user__ssh_authorized_keys: "{{ item.create_user__ssh_authorized_keys | default('')}}"
        create_user__ssh_key: "{{ item.create_user__ssh_key | default('')}}"
      loop: "{{ users_to_create }}"