dictionaryansible

Accessing dictionary value using command line-created variable in Ansible


[I posted this question earlier but it had a number of mistakes so I took it down. My sincere apologies if I wasted anyone's time.]

I have a dictionary in a config file:

foo:
  key1: value1
  key2: value2
  key3: value3
# …

When I initiate the playbook I add the following string variables at the command line:

--extra-vars "k1=key1 k2=key2"

If I want to access a particular dictionary value during a play (assuming I've included the config file in my 'vars_files'), I can do so simply like this:

- debug:
    var="foo.{{ k1 }}"
#      => foo.key1: value1

Let's say the dictionary name is changeable so I want that to be a variable as well. I can add an additional command line variable:

--extra-vars "k1=key1 k2=key2 dict_name=foo"

I try to access value1 again but this time, it appears that Ansible parses the syntax as a literal string rather than a reference to a variable in the config file. For example:

- set_fact:
    dict_value: "{{ dictname ~ '.' ~ k1 }}"

or

- set_fact:
    dict_value: "{{ dictname ~ k1 }}"

[For the second case, I add the '.' to the command line definition, e.g. -e "dict_name=foo."]

I now get a literal result:

- debug:
    var=dict_value
# => dict_value: foo.key1

as opposed to => foo.key1: value1 when I had the dictionary name hardwired as a string. "foo.{{ k1 }}", as per above, is recognized as requesting access to the dictionary variable in the config file. When I try to make foo a variable as well, it becomes a literal string.

To summarize then I want to access a dictionary value with a composite created from variables for both the dictionary name and the key (and somehow the '.' needs to be accommodated: "{{ dict_name }}" appended to '.' appended to "{{ key_name }}" = "{{ composite_variable_to_lookup_value}}".


Solution

  • First of all, your original intention is actually valid (note that I've corrected your syntax). You just need to remember that var parameter of the debug module holds the name of the variable, whereas when you define a variable using set_fact module, it's a template that could be rendered to a string. So the result is totally expected. Since debug accepts either a var name or a msg message template, the snippet below illustrates what's happening in the most straightforward way:

    ---
    - name: Test
      hosts: localhost
      connection: local
      gather_facts: false
      vars:
        foo:
          key1: value1
          key2: value2
          key2: value3
      tasks:
        - name: Show the variable value
          debug:
            var: "{{ dict_name }}.{{ k1 }}"
          # => foo.key1: value1
    
        - name: Show the literal string
          debug:
            msg: "{{ dict_name }}.{{ k1 }}"
          # => msg: foo.key1
    

    If you want not only to display the value, but to set it as another variable, you can use vars lookup:

    ---
    - name: Test
      hosts: localhost
      connection: local
      gather_facts: false
      vars:
        foo:
          key1: value1
          key2: value2
          key2: value3
        dict_name: foo
        key_name: key1
      tasks:
        - name: Set the value of the key as another variable
          set_fact:
            composite_variable_to_lookup_value:
              "{{ lookup('vars', dict_name)[key_name] }}"
    
        - name: Show the results
          debug:
            var: composite_variable_to_lookup_value
          # => composite_variable_to_lookup_value: foo.key1 = value1        
    

    Note that you will not be able to get it neither as {{ hostvars[inventory_hostname][dict_name][key_name] }} (see https://stackoverflow.com/a/48362348/16417367) nor as {{ lookup('vars', dict_name ~ '.' ~ key_name }} (vars lookup only returns top level variable names).