19

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?

older coder
  • 614
  • 1
  • 4
  • 12
  • I am pretty sure ansible does not allow this by design – because shared mutable state is evil: https://stackoverflow.com/questions/19158339/why-are-global-variables-evil – stefreak Nov 28 '17 at 20:20
  • @stefreak yes globals are evil but 1. This is a framework, not a program. We need to work around the framework. 2. I need a way for branching in ansible with different hosts and without a variable, this can get very difficult. This bounty is closed, but no one answered it. – older coder Nov 28 '17 at 23:19

5 Answers5

21

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"
}
Konstantin Suvorov
  • 65,183
  • 9
  • 162
  • 193
  • I'm not specifically worried about run_once. I'm worried about setting/reading variables – older coder Nov 22 '17 at 21:23
  • @KonstantinSuvorov. What is hosts: mytest? because of inventory_hostname, it looks like "aaa" or maybe a group name? – older coder Nov 28 '17 at 23:36
  • Yes `mytest` is a group with hosts `aaa`, `bbb`, `ccc`. I use it to apply play to all hosts at once: to set identical fact `myvar` on all of them and then print it with `debug` to show that it is the same on every host. This facts are persistent throughout the playbook, so subsequent plays can access `myvar` too. – Konstantin Suvorov Nov 29 '17 at 03:30
  • 1
    This does not answer the question. You will be setting a fact for all the hosts you are delegating to. But you are not setting a fact for a host you are not running the task in. – Apeasant Jul 14 '21 at 14:14
6

https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#fact-caching

As shown elsewhere in the docs, it is possible for one server to reference variables about another, like so: {{ hostvars['asdf.example.com']['ansible_os_family'] }}

This even applies to variables set dynamically in playbooks.

nlevitt
  • 576
  • 5
  • 4
  • 1
    This applies - if you know which host you're applying it to. Run it on 4, but set a variable, only one variable with the host name that it applies to, then use it in another play – older coder Nov 21 '19 at 23:14
  • 3
    localhost is always there. You can do `set_facts: ...` with `delegate_to: localhost` and then use `{{ hostvars['localhost']['your_var'] }}` on any machine. (Use `when: ` to only run `set_facts` when desired.) – simohe Sep 14 '20 at 20:03
0

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 }}"
Todd Lewis
  • 61
  • 1
  • 5
-1

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"
  • Sumit, this doesn't apply. I'm running it on 4 hosts, finding the one that has a drive E, then running another play to do something on the host that has drive E. You know which host you're setting it on. – older coder Nov 21 '19 at 23:07
-1

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)