Nested Variable Inheritance with Ansible: How to use a variable that refers to one of several lists of values

So what do we mean by “nested variable inheritance” anyhow? We mean that we want to use the value of a variable, to refer to another variable which contains a list of values. We’re “inheriting” the name of the list, and then using the values contained in the list when evaluating an expression.

In our use case, what we’d like to do is iterate over the list of values in a “with items:” expression, such as a list of usernames, or a list of file names. Instead of explicitly specifying the name of the list we’d like to use, we’ll use a variable who’s value is the name of the list. In this way we can write a playbook which can perform a task, but still iterate over different lists depending on which group of hosts the playbook is running against.

First let’s define an example where this is useful, and later we’ll show how it can be done. Let’s say we have three departments in our company: Exec, Engineering and Sales. We want to use an ansible playbook “add-users.yml” ans the ansible users module to add the user accounts to systems, but only those accounts which are appropriate to the specific systems.

A Basic Playbook

Our standard vars file looks like: (vars can be defined in the playbook, stored under role/vars or added via “include_vars:” )

user_list: [“user1”, “user2”, “user3”]
password: [“myp@ssw0rd”]

And our standard playbook looks like this:


– name: Add user accounts
  user: name={{ item }} password={{ password }} shell=/bin/bash
  with_items: user_list

But in this form, we’re stuck with only a single list “user_list” which contains all users which will be created. We need to use different lists for each of our departments. So let’s try again:

Breakout users into groups

Here’s our new vars file:

exec_users: [“exec_user1”, “exec_user2”, “exec_user3”] 
eng_users: [“eng_user1”, “eng_user2”, “eng_user3”]
sales_users: [“sales_user1”, “sales_user2”, “sales_user3”]   
password: [“myp@ssw0rd”]

 And our new playbook:


– name: Add Exec user accounts
  user: name={{ item }} password={{ password }} shell=/bin/bash
  with_items: exec_users

– name: Add Engineering user accounts
  user: name={{ item }} password={{ password }} shell=/bin/bash
  with_items: eng_users

– name: Add Sales user accounts
  user: name={{ item }} password={{ password }} shell=/bin/bash
  with_items: sales_users

So now we have a playbook, which will create accounts for three different sets of users, but we still need to distinguish which users we want to create on which hosts. We can add a hosts expression to our playbook to specify which hosts to run each task against.

Limiting scope of user creation by groups


– name: Add Exec user accounts
  hosts: exec

  user: name={{ item }} password={{ password }} shell=/bin/bash
  with_items: exec_users

– name: Add Engineering user accounts
  hosts: eng
  user: name={{ item }} password={{ password }} shell=/bin/bash
  with_items: eng_users

– name: Add Sales user accounts
  hosts: sales

  user: name={{ item }} password={{ password }} shell=/bin/bash
  with_items: sales_users

The problem there is we are still statically defining which hosts will have which users. Also when the playbook runs, we’ll always have tasks being skipped when the hosts we are running against don’t match  the hosts expression. And there’s the larger issue that as we get more groups, our playbook gets larger and more complex as we specify more lists and more hosts groups.

So, let’s try this another way. This time, we only want one stanza to add users, but we want to specify the list as a variable whose value is the name of the exec, eng or sales lists.

Specify which group of users to create dynamically

Our vars file is the same as before, but now we add entries to group_vars, setting the value of user_list to be one of the three lists for the appropriate group:

i.e. in group_vars/exec:

 user_list: exec_users

And now our playbook:


– name: Add user accounts
  user: name={{ item }} password={{ password }} shell=/bin/bash
  with_items: user_list

This looks like it will do the trick, unfortunately when we run the playbook, it iterates over the single value of the variable “user_list”, and creates users named “exec_users, eng_users and sales users”, rather than the lists we are trying to point to.

Ansible will normally identify a variable when you use a non-quoted value. In this use case, it doesn’t. However there is a workaround that solves this issue. We need to define all of our our vars and lists in group_vars and then explicitly tell ansible that we want to use the value of the variable specified in group vars.

The solution: Tell ansible to do what I meant, not what I said

Here’s our group_vars/all :

exec_users: [“exec_user1”, “exec_user2”, “exec_user3”] 
eng_users: [“eng_user1”, “eng_user2”, “eng_user3”]
sales_users: [“sales_user1”, “sales_user2”, “sales_user3”]
password: [“myp@ssw0rd”]

And in group_vars/exec :

user_list: exec_users

 Our secret sauce is in our playbook vars file, here we tell ansible where to get the values from:

user_list_name: “{{ hostvars[inventory_hostname][user_list] }}”

 And finally our playbook:


– name: Add user accounts
  user: name={{ item }} password={{ password }} shell=/bin/bash

  with_items: user_list_name

This finally gets us what we want, a simple generic playbook which creates user accounts. We have no extra tasks to skip or groups to re-define, and we only create the users we need on the hosts in the appropriate groups. When we add a new host, we assign it to the appropriate groups in our inventory file, and it gets all of the users which belong to the groups.