How to set an Ansible variable for all plays/hosts?

Viewed 27200

This question is NOT answered. Someone mentioned environment variables. Can you elaborate on this?

This seems like a simple problem, but not in ansible. It keeps coming up. Especially in error conditions. I need a global variable. One that I can set when processing one host play, then check at a later time with another host. In a nutshell, so I can branch later in the playbook, depending on the variable.

We have no control over custom software installation, but if it is installed, we have to put different software on other machines. To top it off, the installations vary, depending on the VM folder. My kingdom for a global var.

The scope of variables relates ONLY to the current ansible_hostname. Yes, we have group_vars/all.yml as globals, but we can't set them in a play. If I set a variable, no other host's play/task can see it. I understand the scope of variables, but I want to SET a global variable that can be read throughout all playbook plays. The actual implementation is unimportant but variable access is (important).

My Question: Is there a way to set a variable that can be checked when running a different task on another host? Something like setGlobalSpaceVar(myvar, true)? I know there isn't any such method, but I'm looking for a work-around. Rephrasing: set a variable in one tasks for one host, then later in another task for another host, read that variable.

The only way I can think of is to change a file on the controller, but that seems bogus.

An example

The following relates to oracle backups and our local executable, but I'm keeping it generic. For below - Yes, I can do a run_once, but that won't answer my question. This variable access problem keeps coming up in different contexts.

I have 4 xyz servers. I have 2 programs that need to be executed, but only on 2 different machines. I don't know which. The settings may be change for different VM environments.

Our programOne is run on the server that has a drive E. I can find which server has drive E using ansible and do the play accordingly whenever I set a variable (driveE_machine). It only applies to that host. For that, the other 3 machines won't have driveE_machine set. In a later play, I need to execute another program on ONLY one of the other 3 machines. That means I need to set a variable that can be read by the other 2 hosts that didn't run the 2nd program. I'm not sure how to do it.

Inventory file:

[xyz]
serverxyz[1:4].private.mystuff

Playbook example:

---
- name: stackoverflow variable question
  hosts: xyz
  gather_facts: no
  serial: 1
  tasks:
      - name: find out who has drive E
         win_shell: dir e:\
         register: adminPage
         ignore_errors: true

       # This sets a variable that can only be read for that host
      - name: set fact driveE_machine when rc is 0
        set_fact:
           driveE_machine: "{{inventory_hostname}}"
        when: adminPage.rc == 0

       - name: run program 1
         include: tasks/program1.yml
         when: driveE_machine is defined

       # program2.yml executes program2 and needs to set some kind of variable
       # so this include can only be executed once for the other 3 machines 
       # (not one that has driveE_machine defined and ???
       - name: run program 2
         include: tasks/program2.yml
         when: driveE_machine is undefined and ???
         # please don't say run_once: true - that won't solve my variable access question

Is there a way to set a variable that can be checked when running a task on another host?

5 Answers

No sure what you actually want, but you can set a fact for every host in a play with a single looped task (some simulation of global variable):

playbook.yml

---
- hosts: mytest
  gather_facts: no
  vars:
  tasks:
    # Set myvar fact for every host in a play
    - set_fact:
        myvar: "{{ inventory_hostname }}"
      delegate_to: "{{ item }}"
      with_items: "{{ play_hosts }}"
      run_once: yes
    # Ensure that myvar is a name of the first host
    - debug:
        msg: "{{ myvar }}"

hosts

[mytest]
aaa ansible_connection=local
bbb ansible_connection=local
ccc ansible_connection=local

result

PLAY [mytest] ******************
META: ran handlers

TASK [set_fact] ******************
ok: [aaa -> aaa] => (item=aaa) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "aaa"}
ok: [aaa -> bbb] => (item=bbb) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "bbb"}
ok: [aaa -> ccc] => (item=ccc) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "ccc"}

TASK [debug] ******************
ok: [aaa] => {
    "msg": "aaa"
}
ok: [bbb] => {
    "msg": "aaa"
}
ok: [ccc] => {
    "msg": "aaa"
}

This answer doesn't pre-suppose your hostnames, nor how many hosts have a "drive E:". It will select the first one that is reachable that also has a "drive E:". I have no windows boxes, so I fake it with a random coin toss for whether a host does or doesn't; you can of course use your original win_shell task, which I've commented out.

---

- hosts: all
  gather_facts: no
  # serial: 1
  tasks:
    # - name: find out who has drive E
    #   win_shell: dir e:\
    #   register: adminPage
    #   ignore_errors: true

    - name: "Fake finding hosts with drive E:."
      # I don't have hosts with "drive E:", so fake it.
      shell: |
        if [ $RANDOM -gt 10000 ] ; then
            exit 1
        else
            exit 0
        fi
      args:
        executable: /bin/bash
      register: adminPage
      failed_when: false
      ignore_errors: true
      
    - name: "Dict of hosts with E: drives."
      run_once: yes
      set_fact:
        driveE_status: "{{ dict(ansible_play_hosts_all |
                            zip(ansible_play_hosts_all |
                                map('extract', hostvars, ['adminPage', 'rc'] ) | list
                               ))
                        }}"

    - name: "List of hosts with E: drives."
      run_once: yes
      set_fact:
        driveE_havers: "{%- set foo=[] -%}
                        {%- for dE_s in driveE_status -%}
                           {%- if driveE_status[dE_s] == 0 -%}
                             {%- set _ = foo.append( dE_s ) -%}
                           {%- endif -%}
                        {%- endfor -%}{{ foo|list }}"                                     

    - name: "First host with an E: drive."
      run_once: yes
      set_fact:
        driveE_first: "{%- set foo=[] -%}
                        {%- for dE_s in driveE_status -%}
                           {%- if driveE_status[dE_s] == 0 -%}
                             {%- set _ = foo.append( dE_s ) -%}
                           {%- endif -%}
                        {%- endfor -%}{{ foo|list|first }}"                                     

    - name: Show me.
      run_once: yes
      debug:
        msg:
          - "driveE_status: {{ driveE_status }}"
          - "driveE_havers: {{ driveE_havers }}"
          - "driveE_first: {{ driveE_first }}"

It's working for me, you can directly use register var no need to use set_fact.

---
- hosts: manager
  tasks:
    - name: dir name
      shell: cd /tmp && pwd
      register: homedir
      when: "'manager' in group_names"

- hosts: all
  tasks:
    - name: create a test file
      shell: touch "{{hostvars['manager.example.io'['homedir'].stdout}}/t1.txt"

i have used "set_fact" module in the ansible tasks prompt menu, which is helped to pass user input into all the inventory hosts.

#MONITORING PLAYBOOK 
- hosts: all
  gather_facts: yes
  become: yes

  tasks:
    - pause:
        prompt: "\n\nWhich monitoring you want to perform?\n\n--------------------------------------\n\n1. Memory Utilization:\n2. CPU Utilization:\n3. Filesystem Utili
zation:\n4. Exist from Playbook: \n5. Fetch Nmon Report \n \nPlease select option: \n--------------------------------------\n"
      register: menu

    - set_fact:
        option: "{{ menu.user_input }}"
      delegate_to: "{{ item }}"
      with_items: "{{ play_hosts }}"
      run_once: yes

#1 Memory Monitoring

    - pause:
        prompt: "\n-------------------------------------- \n Enter monitoring Incident Number = "
      register: incident_number_result
      when: option == "1"

    - name: Standardize incident_number variable
      set_fact:
        incident_number: "{{ incident_number_result.user_input }}"
      when: option == "1"
      delegate_to: "{{ item }}"
      with_items: "{{ play_hosts }}"
      run_once: yes

ansible playbook result is

[ansibusr@ansiblemaster monitoring]$ ansible-playbook monitoring.yml

PLAY [all] **************************************************************************

TASK [Gathering Facts] **************************************************************************
ok: [node2]
ok: [node1]
[pause]


Which monitoring you want to perform?

--------------------------------------

1. Memory Utilization:
2. CPU Utilization:
3. Filesystem Utilization:
4. Exist from Playbook:
5. Fetch Nmon Report

Please select option:
--------------------------------------
:

TASK [pause] **************************************************************************
ok: [node1]

TASK [set_fact] **************************************************************************
ok: [node1 -> node1] => (item=node1)
ok: [node1 -> node2] => (item=node2)
[pause]

--------------------------------------
 Enter monitoring Incident Number = :
INC123456
TASK [pause] **************************************************************************
ok: [node1]

TASK [Standardize incident_number variable] ****************************************************************************************************************************
ok: [node1 -> node1] => (item=node1)
ok: [node1 -> node2] => (item=node2)
Related