Variables in Ansible are how we deal with differences between systems.
Once understanding variables you'll also want to dig into `playbooks_conditionals` and `playbooks_loops`.
Useful things like the "group_by" module
and the "when" conditional can also be used with variables, and to help manage differences between systems.
It's highly recommended that you consult `the Ansible-Examples github repository <http://github.com/ansible/ansible-examples/>_` to see a lot of examples of variables put to use.
In the above the model of the first harddrive may be referenced in a template or playbook as::
{{ ansible_devices.sda.model }}
Similarly, the hostname as the system reports it is::
{{ ansible_hostname }}
Facts are frequently used in conditionals (see `playbook_conditionals`) and also in templates.
Facts can be also used to create dynamic groups of hosts that match particular critera, see the :doc:`modules` documentation on 'group_by' for details, as well as in generalized conditional statements as discussed in the `playbook_conditionals` chapter.
If you know you don't need any fact data about your hosts, and know everything about your systems centrally, you
can turn off fact gathering. This has advantages in scaling Ansible in push mode with very large numbers of
systems, mainly, or if you are using Ansible on experimental platforms. In any play, just do this::
- hosts: whatever
gather_facts: no
Local Facts (Facts.d)
`````````````````````
..versionadded:: 1.3
As discussed in the playbooks chapter, Ansible facts are a way of getting data about remote systems for use in playbook variables.
Usually these are discovered automatically by the 'setup' module in Ansible. Users can also write custom facts modules, as described
in the API guide. However, what if you want to have a simple way to provide system or user
provided data for use in Ansible variables, without writing a fact module? For instance, what if you want users to be able to control some aspect about how their systems are managed? "Facts.d" is one such mechanism.
If a remotely managed system has an "/etc/ansible/facts.d" directory, any files in this directory
ending in ".fact", can be JSON, INI, or executable files returning JSON, and these can supply local facts in Ansible.
For instance assume a /etc/ansible/facts.d/preferences.fact::
[general]
asdf=1
bar=2
This will produce a hash variable fact named "general" with 'asdf' and 'bar' as members.
To validate this, run the following::
ansible <hostname> -m setup -a "filter=ansible_local"
And you will see the following fact added::
"ansible_local": {
"preferences": {
"general": {
"asdf" : "1",
"bar" : "2"
}
}
}
And this data can be accessed in a template/playbook as::
{{ ansible_local.preferences.general.asdf }}
The local namespace prevents any user supplied fact from overriding system facts
Additionally, *group_names* is a list (array) of all the groups the current host is in. This can be used in templates using Jinja2 syntax to make template source files that vary based on the group membership (or role) of the host::
{% if 'webserver' in group_names %}
# some part of a configuration file that only applies to webservers
{% endif %}
*groups* is a list of all the groups (and hosts) in the inventory. This can be used to enumerate all hosts within a group.
For example::
{% for host in groups['app_servers'] %}
# something that applies to all app servers.
{% endfor %}
A frequently used idiom is walking a group to find all IP addresses in that group::
An example of this could include pointing a frontend proxy server to all of the app servers, setting up the correct firewall rules between servers, etc.
Just a few other 'magic' variables are available... There aren't many.
Additionally, *inventory_hostname* is the name of the hostname as configured in Ansible's inventory host file. This can
be useful for when you don't want to rely on the discovered hostname `ansible_hostname` or for other mysterious
reasons. If you have a long FQDN, *inventory_hostname_short* also contains the part up to the first
period, without the rest of the domain.
Don't worry about any of this unless you think you need it. You'll know when you do.
Also available, *inventory_dir* is the pathname of the directory holding Ansible's inventory host file, *inventory_file* is the pathname and the filename pointing to the Ansible's inventory host file.
Variable File Separation
````````````````````````
It's a great idea to keep your playbooks under source control, but
you may wish to make the playbook source public while keeping certain
important variables private. Similarly, sometimes you may just
want to keep certain information in different files, away from
the main playbook.
You can do this by using an external variables file, or files, just like this::
---
- hosts: all
remote_user: root
vars:
favcolor: blue
vars_files:
- /vars/external_vars.yml
tasks:
- name: this is just a placeholder
command: /bin/echo foo
This removes the risk of sharing sensitive data with others when
sharing your playbook source with them.
The contents of each variables file is a simple YAML dictionary, like this::
---
# in the above example, this would be vars/external_vars.yml
somevar: somevalue
password: magic
..note::
It's also possible to keep per-host and per-group variables in very
similar files, this is covered in :ref:`patterns`.
Passing Variables On The Command Line
`````````````````````````````````````
In addition to `vars_prompt` and `vars_files`, it is possible to send variables over
the Ansible command line. This is particularly useful when writing a generic release playbook
where you may want to pass in the version of the application to deploy::
The key=value form is obviously simpler, but it's there if you need it!
As of Ansible 1.3, extra vars can be loaded from a JSON file with the "@" syntax::
--extra-vars "@some_file.json"
Also as of Ansible 1.3, extra vars can be formatted as YAML, either on the command line
or in a file as above.
Conditional Imports
```````````````````
.. note: this behavior is infrequently used in Ansible. You may wish to skip this section. The 'group_by' module as described in the module documentation is a better way to achieve this behavior in most cases.
Sometimes you will want to do certain things differently in a playbook based on certain criteria.
Having one playbook that works on multiple platforms and OS versions is a good example.
As an example, the name of the Apache package may be different between CentOS and Debian,
but it is easily handled with a minimum of syntax in an Ansible Playbook::
A lot of folks may ask about how variables override another. Ultimately it's Ansible's philosophy that it's better
you know where to put a variable, and then you have to think about it a lot less.
Avoid defining the variable "x" in 47 places and then ask the question "which x gets used".
Why? Because that's not Ansible's Zen philosophy of doing things.
There is only one Empire State Building. One Mona Lisa, etc. Figure out where to define a variable, and don't make
it complicated.
However, let's go ahead and get precedence out of the way! It exists. It's a real thing, and you might have
a use for it.
If multiple variables of the same name are defined in different places, they win in a certain order, which is::
* -e variables always win
* then comes "most everything else"
* then comes variables defined in inventory
* then "role defaults", which are the most "defaulty" and lose in priority to everything.
That seems a little theoretical. Let's show some examples and where you would choose to put what based on the kind of
control you might want over values.
First off, group variables are super powerful.
Site wide defaults should be defined as a 'group_vars/all' setting. Group variables are generally placed alongside
your inventory file. They can also be returned by a dynamic inventroy script (see `intro_dynamic_inventory`) or defined
in things like AnsibleWorks AWX from the UI or API::
---
# file: /etc/ansible/group_vars/all
# this is the site wide default
ntp_server: default-time.example.com
Regional information might be defined in a 'group_vars/region' variable. If this group is a child of the 'all' group (which it is, because all groups are), it will override the group that is higher up and more general::
---
# file: /etc/ansible/group_vars/boston
ntp_server: boston-time.example.com
If for some crazy reason we wanted to tell just a specific host to use a specific NTP server, it would then override the group variable!::
So that covers inventory and what you would normally set there. It's a great place for things that deal with geography or behavior. Since groups are frequently the entity that maps roles onto hosts, it is sometimes a shortcut to set variables on the group instead of defining them on a role. You could go either way.
Remember: Child groups override parent groups, and hosts always override their groups.
Next up: learning about role variable precedence.
We'll pretty much assume you are using roles at this point. You should be using roles for sure. Roles are great. You are using
roles aren't you? Hint hint.
Ok, so if you are writing a redistributable role with reasonable defaults, put those in the 'roles/x/defaults/main.yml' file. This means
the role will bring along a default value but ANYTHING in Ansible will override it. It's just a default. That's why it says "defaults" :)
See `intro_roles` for more info about this::
----
# file: roles/x/defaults/main.yml
# if not overriden in inventory or as a parameter, this is the value that will be used
http_port: 80
if you are writing a role and want to ensure the value in the role is absolutely used in that role, and is not going to be overriden
by inventory, you should but it in roles/x/vars/main.yml like so, and inventory values cannot override it. -e however, still will::
----
# file: roles/x/vars/main.yml
# this will absolutely be used in this role
http_port: 80
So the above is a great way to plug in constants about the role that are always true. If you are not sharing your role with others,
app specific behaviors like ports is fine to put in here. But if you are sharing roles with others, putting variables in here might
be bad. Nobody will be able to override them with inventory, but they still can by passing a parameter to the role.
Parameterized roles are useful.
If you are using a role and want to override a default, pass it as a parameter to the role like so::
roles:
- { name: apache, http_port: 8080 }
This makes it clear to the playbook reader that you've made a concious choice to override some default in the role, or pass in some
configuration that the role can't assume by itself. It also allows you to pass something site-specific that isn't really part of the
role you are sharing with others.
This can often be used for things that might apply to some hosts multiple times,
like so::
roles:
- { role: app_user, name: Ian }
- { role: app_user, name: Terry }
- { role: app_user, name: Graham }
- { role: app_user, name: John }
That's a bit arbitrary, but you can see how the same role was invoked multiple Times. In that example it's quite likely there was
no default for 'name' supplied at all. Ansible can yell at you when variables aren't defined -- it's the default behavior in fact.
So that's a bit about roles.
There are a few bonus things that go on with roles.
Generally speaking, variables set in one role are available to others. This means if you have a "roles/common/vars/main.yml" you
can set variables in there and make use of them in other roles and elsewhere in your playbook::
roles:
- { role: common_settings }
- { role: something, foo: 12 }
- { role: something_else }
..note:: There are some protections in place to avoid the need to namespace variables.
In the above, variables defined in common_settings are most definitely available to 'app_user' and 'something_else' tasks, but if
"something's" guaranteed to have foo set at 12, even if somewhere deep in common settings it set foo to 20.
So, that's precedence, explained in a more direct way. Don't worry about precedence, just think about if your role is defining a
variable that is a default, or a "live" variable you definitely want to use. Inventory lies in precedence right in the middle, and
if you want to forceably override something, use -e.
If you found that a little hard to understand, take a look at the "ansible-examples" repo on our github for a bit more about