Ansible global variables

Ansible has the ability to use group _vars and host _vars and is very flexible in the way they can be defined. However, there is often a need to store data globally. This is particularly true in a multi-play Ansible playbook. A global variable is one that can be stored in one play and used in another play. This article will show how the concept of global variables can be achieved in Ansible.

The following playbook demonstrates how variables can be used between two playbooks, essentially emulating the global variable concept:

---
- name: "PLAY 01 --- collect inputs and create global vars"
  hosts: localhost
  gather_facts: no

  vars_prompt:
    - name: 'router_username'
      prompt: "Please enter router username"
      private: no
    - name: 'router_password'
      prompt: "please enter router password"
      private: yes

  tasks:
    - name: Save credentials as global vars
      set_fact:
        router_username: "{{ router_username }}"
        router_password: "{{ router_password }}"
      no_log: true

- name: "PLAY 02 --- consume global vars"
  hosts: cisco_routers
  connection: network_cli
  gather_facts: no

  tasks:
    - name: fetch global vars from PLAY 01
      set_fact:
        ansible_user: "{{ hostvars['localhost']['router_username'] }}"
        ansible_ssh_pass: "{{ hostvars['localhost']['router_password'] }}"
      no_log: true

    - name: send show run to Cisco devices
      cli_command:
        command: show running-config
      register: show_run

    - name: print show_run
      debug: 
        msg: "{{ show_run.stdout_lines }}"

Here is what happens within the playbook: (i) first it prompts for a username and password, (ii) the credentials are stored as host _vars of  localhost, (iii) the same credentials are used in the second play (which runs against different group of hosts) to connect to the Cisco Devices. With this code we effectively use localhost host vars as a container for our global vars.  Please notice the set_fact task in PLAY 01. At first, it may seem redundant but the vars whose content is received through the vars_prompt are not stored in hostvars[‘locahost’][var_name]. We do that manually with set_fact.

In PLAY02, cisco_routers is used and not localhost. Let’s have a brief look at our inventory file:

[cisco_routers]
R1 ansible_host="192.168.0.101" device_role="PE"
R2 ansible_host="192.168.0.102" device_role="PE"

[cisco_routers:vars]
ansible_network_os="ios"

For each host in that group, we first create host vars ansible_user and ansible_ssh_pass from global vars.  Then we are ready to proceed to the rest of the business logic in the playbook.

In both plays no_log is set to true. This is to disable Ansible from printing sensitive data in stdout if the task fails. We certainly do not want this data to be ever printed in the Ansible output. One important thing to note here is that the data is saved in hostvars as clear text. So, if you ever do something like debug: var=hostvars the sensitive data will be printed there.

Sometimes you may want to not only set global vars, but also change them over the course of a multi-play playbook.
Let’s extend our playbook to demonstrate this:

---
- name: "PLAY 01 --- collect inputs and create global vars"
  hosts: localhost
  gather_facts: no

  vars_prompt:
    - name: 'router_username'
      prompt: "Please enter router username"
      private: no
    - name: 'router_password'
      prompt: "please enter router password"
      private: yes

  tasks:
    - name: Save credentials as global vars
      set_fact:
        router_username: "{{ router_username }}"
        router_password: "{{ router_password }}"
        show_config_sent: not yet
      no_log: true

    - name: print show_config_sent
      debug:
        msg: "{{ show_config_sent }}"

- name: "PLAY 02 --- consume global vars"
  hosts: cisco_routers
  connection: network_cli
  gather_facts: no

  tasks:
    - name: fetch global vars from PLAY 01
      set_fact:
        ansible_user: "{{ hostvars['localhost']['router_username'] }}"
        ansible_ssh_pass: "{{ hostvars['localhost']['router_password'] }}"
      no_log: true

    - name: send show run to Cisco devices
      cli_command:
        command: show running-config
      register: show_run

    - name: print show_run
      debug: 
        msg: "{{ show_run.stdout_lines }}"

    - name: update global variable
      set_fact:
        show_config_sent: yes of course
      delegate_to: localhost
      delegate_facts: true
      run_once: true

- name: "PLAY 03 --- read updated global vars from localhost"
  hosts: localhost
  gather_facts: no

  tasks:
    - name: print show_config_sent
      debug:
        msg: "{{ show_config_sent }}"

In PLAY02, in the update global variable task, we instruct Ansible to set the value of the localhost hostvar show_config_sent. Setting delegate_to: localhost and delegate_facts: true are required to achieve that.
Please notice that in PLAY03 we get the variable show_config_sent as a host variable bound to the localhost.