I want to pass multiple variables to a task using vars
. Currently, I am doing it like below
vars:
var1_name: "var1_value"
var2_name: "var2_value"
As the number of variables can grow in size, I'd rather prefer to pass the dictionary of variables to the task using vars
. I have constructed a dictionary of variables like below
- name: set fact
hosts: localhost
tasks:
- set_fact:
variables: "{{ variables|default({}) | combine( {item.variable: item.value} ) }}"
with_items:
- variable: var1_name
value: "var1_value"
- variable: var2_name
value: "var2_name"
Dictionary looks something like this:
"variables": {
"var1_name": "var1_value",
"var2_name": "var2_value",
}
Now, I want to make variables in this dictionary available to roles executing on other hosts.
But, when I tried to pass dictionary to vars
like below
vars: "{{ variables }}"
Ansible throws the error:
ERROR! Vars in a Play must be specified as a dictionary, or a list of dictionaries
How to pass a dictionary variable in vars
?
After doing some searching through the Ansible source code, it looks like this is an issue even the developers of Ansible face. In some integration tests, there are specific tests that are commented out because of this same error.
ansible/tests/integration/targets/include_import/role/test_include_role.yml
## FIXME Currently failing with
## ERROR! Vars in a IncludeRole must be specified as a dictionary, or a list of dictionaries
# - name: Pass all variables in a variable to role
# include_role:
# name: role1
# tasks_from: vartest.yml
# vars: "{{ role_vars }}"
I also found out that this is the underlying function that is being called to include the variables:
def _load_vars(self, attr, ds):
'''
Vars in a play can be specified either as a dictionary directly, or
as a list of dictionaries. If the later, this method will turn the
list into a single dictionary.
'''
def _validate_variable_keys(ds):
for key in ds:
if not isidentifier(key):
raise TypeError("'%s' is not a valid variable name" % key)
try:
if isinstance(ds, dict):
_validate_variable_keys(ds)
return combine_vars(self.vars, ds)
elif isinstance(ds, list):
all_vars = self.vars
for item in ds:
if not isinstance(item, dict):
raise ValueError
_validate_variable_keys(item)
all_vars = combine_vars(all_vars, item)
return all_vars
elif ds is None:
return {}
else:
raise ValueError
except ValueError as e:
raise AnsibleParserError("Vars in a %s must be specified as a dictionary, or a list of dictionaries" % self.__class__.__name__,
obj=ds, orig_exc=e)
except TypeError as e:
raise AnsibleParserError("Invalid variable name in vars specified for %s: %s" % (self.__class__.__name__, e), obj=ds, orig_exc=e)
Seems as if since "{{ }}" is actually just a YAML string, Ansible doesn't recognize it as a dict, meaning that the vars attribute isn't being passed through the Jinja2 engine but instead being evaluated for what it actually is.
The only way to pass YAML objects around would be to use anchors, however this would require that the object be defined in whole instead of dynamically.
var: &_anchored_var
attr1: "test"
att2: "bar"
vars:
<<: *_anchored_var