This page looks best with JavaScript enabled

[My Ansible Journey] Variables

 ·  ☕ 6 min read  ·  🦆 Jeremy

Part 1 : Presentation and prerequisites
Part 2 : Basics and first Playbook
Part 3 : Variables
––

After a long break (and moving to Quebec!), here is the third part of the Ansible serie. This time I will try to do a shorter article by focusing on a single subject: variables!

The code regarding the examples is on GitHub

Variables

Dealing with variables in Ansible is probably one of the most complex aspect of the language. As it is possible to declare them in lot of places (22 to be precise). The list in the documentation specifies the overload order. We won’t use all the 22 places, otherwise our code would be difficult to understand. Now I will expose some examples about how to declare and use variables.

Inventory variables

First example, the ansible_host variable:

1
tig ansible_host=192.168.188.129

It’s a special one, but still a variable. The same way we can decalre another variable in the inventory file:

1
tig ansible_host=192.168.188.129 my_var="this is my variable"

There are some restrictions about the characters we are allowed to use as a variable name: it must start with a letter, can contain a numbers, letters and underscores, but no space.

To be able to use the variable in the Playbook, it should make a reference to a host (or a group of host) in an inventory. In our case we have defined a variable for the host tig, if we want to use if we will have to run the playbook on this host. Here is an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---
- name: Prerequisites
  hosts: tig
  gather_facts: yes
  remote_user: jlg
  become: true
  tasks:
    - name: Affichage variable
      debug:
        msg: "{{ my_var }}"

To call a variable you must surround it by double curly braces. It’s the Jinja2 syntax.

We use the debug module to do test, it’s the easy way!

After running the playbook, you should have the following result:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
~/ansible/ansible-tp-tig$ ansible-playbook playbook-display-var.yml -i inventory.ini -K
BECOME password:

PLAY [Prerequisites] ***************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [tig]

TASK [Affichage variable] **********************************************************************************************
ok: [tig] => {
    "msg": "this is my variable"
}

PLAY RECAP *************************************************************************************************************
tig                        : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The previous example was a good way to see how you could use variables and the link between inventory hosts / groups and variables. But it’s important to remind it’s not the right way to deal with variables. You may want to use it some time, but usually there is a better way. We will get to it.

Inside the playbook

Another simple way to set and use a variable is to do it in the playbook itself. Again, this is not ideal, but it help to understand how it works.
We can use our previous example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
---
- name: Prerequisites
  hosts: tig
  gather_facts: yes
  remote_user: jlg
  become: true
  vars:
    my_var: "this is my variable"
    
  tasks:
    - name: Variable deplay
      debug:
        msg: "{{ my_var }}"

Arrays and dictionaries

We can also use classic arrays or dictionaries this way:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  vars:
    my_var: "this is my variable"
    my_list:
      - first
      - second
      - third
    my_dict:
      field1: first
      field2: second
      field3: third

And use it in the Playbook:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  tasks:
    - name: Variable display
      debug:
        msg: "{{ my_var }}"
    - name: List display
      debug:
        msg: "{{ my_list[0] }}"
    - name: Dict display
      debug:
        msg: "{{ my_dict['field1'] }}"

We also can use an array of dictionaries:

1
2
3
4
5
    list_of_dict:
      - field1: first
        field2: second
      - field1: third
        field2: fourth

And run through it:

1
2
3
4
    - name: List of dict display
      debug:
        msg: "{{ item.field1 }}"
      loop: "{{ list_of_dict }}"

The item keyword represents each element of the array. More information here.

A global variable file

So now that we have overview some practical examples, let’s see how I personnaly recommand to organize the variables in a project.
There is no default place to store variables, but there are better places than others.

We always can create a variable file (anywhere) and import it manually, but there are ways to import it automatically if we place it in the right location:

  • groups_vars/
  • hosts_vars/

We will place a variable file in those folders. But we also have to name it after a group or a host in the inventory. In our case we will use the implicit group that contains all hosts: all. As it is a group, the file will be: group_vars/all.yml.

So here is our variable:

1
influxdb_database_name: vsphere

Using the variable with InfluxDB

In the previous article we made a Play to install InfluxDB, now we will go further with the database creation. It will be used later to store our vSphere metrics.

Here is the current Play :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
- name: InfluxDB
  hosts: tig
  remote_user: jlg
  become: true
  gather_facts: false
  tasks:

    - name: Install InfluxDB
      apt:
        update_cache: yes
        pkg: influxdb

    - name: Service started and enabled
      service:
        name: influxdb
        enabled: yes
        state: started

This Play allows us to install InfluxDB and set the service to be started and to start automatically after a reboot.

When if InfluxDB start for the first time, the internal database is created, we will wait until it’s done before creating vSphere DB.

Here the Task that will do the verification:

1
2
3
4
5
6
7
8
9
    - name: Wait for _internal database to be created after initial installation
      uri:
        url: "http://127.0.0.1:8086/query"
        method: POST
        body: 'q=SHOW DATABASES'
      register: response
      until: '"_internal" in (response | to_json)'
      retries: 5
      delay: 5

It deserves a bit of an explanation here, first we use the uri module which allows us to make an API call (Rest in our case). We set three arguments:

  • url to reach
    • With Ansible, the module is run on the host, here it is the InfluxDB server so we can just set localhost
  • method to use
  • body to pass in the API call

More details on this module here.

Then you will have to analyze the JSON response by looking for the default database name (internal). It may take some time to be created, that’s why retry 5 times with 5 seconds in-between.

Then we can create the vSphere database with our variable we set earlier:

1
2
3
4
5
6
7
8
    - name: Create database
      uri:
        url: "http://127.0.0.1:8086/query"
        method: POST
        body: 'q=CREATE DATABASE "{{ influxdb_database_name }}"'
      register: response
      changed_when: response.status == 200
      when: "influxdb_database_name not in (response.json | json_query('results[0].series[0].values[*][0]'))"

You add those two tasks into the Playbook and you are ready to run it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
~/ansible/ansible-tp-tig$ ansible-playbook playbook-display-var.yml -i inventory.ini -K
BECOME password:

PLAY [Prerequisites] ***************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [tig]

TASK [Affichage variable] **********************************************************************************************
ok: [tig] => {
    "msg": "vsphere"
}

PLAY RECAP *************************************************************************************************************
tig                        : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Conclusion

Depending of your Ansible project size and possible evolution, you will rather prefer to limit the place you add variable to 2 or 3.
Personally, I like to organize myself with these rules:

  • Inside roles (we will see that soon)
  • Link to the inventory groups in group_vars/
    • all but not only, I like to split variables inside dedicated group files
  • In the inventory file
    • Just for some specific variables, like ansible_host

See you next time for an article about Collections!

Share on