Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a dictionary of registered ansible variables in vars?

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?

like image 938
user3086551 Avatar asked May 22 '18 21:05

user3086551


2 Answers

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.

https://github.com/ansible/ansible/blob/devel/test/integration/targets/include_import/role/test_include_role.yml#L96

## 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:

https://github.com/ansible/ansible/blob/devel/lib/ansible/playbook/base.py#L681

    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
like image 108
leb4r Avatar answered Oct 27 '22 15:10

leb4r


I recommend using a structured way of managing variables like:

File myvars1.yml

myvars:
  var1_name: "var1_value"
  var2_name: "var2_value"

Then read the variables like

  - name: Read all variables
    block:
      - name: Get All Variables
        stat:
          path: "{{item}}"
        with_fileglob:
          - "/myansiblehome/vars/common/myvars1.yml"
          - "/myansiblehome/vars/common/myvars2.yml"
        register: _variables_stat

      - name: Include Variables when found
        include_vars : "{{item.stat.path}}"
        when         : item.stat.exists
        with_items   : "{{_variables_stat.results}}"
        no_log       : true
    delegate_to: localhost
    become: false

After that, use like:

- name: My Running Module
  mymodule:
    myaction1: "{{ myvars.var1_name }}"
    myaction2: "{{ myvars.var2_name }}"

Hope it helps

like image 1
imjoseangel Avatar answered Oct 27 '22 15:10

imjoseangel