Search code examples
pythonansibleansible-facts

Ansible Setting fact with dynamic key/value


I am trying to set ansible facts from the stdout of a command task I call from another role.

Role A:

- name: example command
  command: client get -s {{ service }}
  register: vars_string

- name: set vars
  set_fact: vars={{ vars_string.stdout.split('\n')}}
  when:
    - vars_string.stdout | length > 0

- name: set vars as facts
  set_fact: "{{ item }}"
  with_items: "{{ vars }}"

vars output:

"vars": [
        "tst=ansible", 
        "example=values"
]

Role B:

- debug:
    var: tst

Results from Role B:

Expectation: { "tst": "ansible" }

Reality: { "tst": "VARIABLE IS NOT DEFINED!" }

I have tried to spit vars into a dict and use set_fact: "{{ item.key }}" : "{{ item.value }}" as well. This returned the same results.

I want to be able to call by the variable name returned from the command in future roles. Any ideas?


Solution

  • Two points about your code snippet that may interest you:

    • There is already a split-by-newline version of the output from your command, it's vars_string.stdout_lines
    • I can't tell if you just chose that variable by accident, or you were trying to actually assign to the vars built-in variable, but either way, don't do that

    As best I can tell, there is no supported syntax for assigning arbitrary top-level host facts from within just a task.

    You have two choices: write out those variables to a file, then use include_vars: to read them in -- which will assign them as host facts, or concede to the way set_fact: wants things and be content with those dynamic variables living underneath a known key in hostfacts

    We'll show the latter first, because it's shorter:

    - set_fact:
        my_facts: >-
          {{ "{" + (vars_string.stdout_lines
          | map('regex_replace', '^([^=]+)=(.+)', '"\1": "\2"')
          | join(",")) + "}"
          }}
      when:
      - vars_string.stdout | length > 0
    

    Of course, be aware that trickery won't work if your keys or values have non-JSON friendly characters in them, but if that simple version doesn't work, ask a follow-up question, because there are a lot more tricks in that same vein

    The include_vars: way is:

    - tempfile:
        state: file
        suffix: .json
      register: vars_filename
    
    - copy:
        dest: '{{ vars_filename.path }}'
        content: >-
           {{ "{" + (vars_string.stdout_lines
           | map('regex_replace', '^([^=]+)=(.+)', '"\1": "\2"')
           | join(",")) + "}"
           }}
    
    - include_vars:
        file: '{{ vars_filename.path }}'
    
    - file:
        path: '{{ vars_filename.path }}'
        state: absent