An approach to explaining precedence more by convention.

pull/4352/head
Michael DeHaan 11 years ago
parent 33d13a6888
commit 585b4d03a8

@ -2,21 +2,45 @@ Variables
=========
All of your systems are likely not the same. On some systems you may want to set some behavior
or configuration that is slightly different from others. Also, some of the observed behavior or state
of remote systems might need to influence how you configure those systems.
or configuration that is slightly different from others.
Also, some of the observed behavior or state
of remote systems might need to influence how you configure those systems. (Such as you can find out the IP
address of a system and even use it as a configuration value on ANOTHER system).
You might have some templates for configuration files that are mostly the same, but slightly different
based on those variables.
Variables in Ansible are how we manage with differences between systems. Once understanding variables you'll
also want to dig into `playbooks_conditionals` and `playbooks_loops`.
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.
.. contents::
:depth: 2
What Makes A Valid Variable Name
````````````````````````````````
Before we start using variables it's important to know what are valid variable names.
Variable names should be letters, numbers, and underscores. Variables should always start with a letter.
"foo_port" is a great variable. "foo5" is fine too.
"foo-port", "foo port", "foo.port" and "12" are not valid variable names.
Easy enough, let's move on.
Variables Defined in Inventory
``````````````````````````````
We've actually already covered a lot about variables in another section, so so far this shouldn't be terribly new, but
a bit of a refresher.
Often you'll want to set variables based on what groups a machine is in. For instance, maybe machines in Boston
want to use 'boston.ntp.example.com' as an NTP server.
@ -33,8 +57,10 @@ In a playbook, it's possible to define variables directly inline like so::
This can be nice as it's right there when you are reading the playbook.
Variables defined from includes
-------------------------------
Variables defined from included files and roles
-----------------------------------------------
It turns out we've already talked about variables in another place too.
As described in `intro_roles`, variables can also be included in the playbook via include files, which may or may
not be part of an "Ansible Role". Usage of roles is preferred as it provides a nice organizational system.
@ -42,9 +68,11 @@ not be part of an "Ansible Role". Usage of roles is preferred as it provides a
Using Variables: About Jinja2
`````````````````````````````
We've referenced various ways to define variables above, but how do you reference them? Ansible allows you to
It's nice enough to know about how to define variables, but how do you use them?
Ansible allows you to
reference variables in your playbooks using the Jinja2 templating system. While you can do a lot of complex
things in Jinja, only the basics are things you really need to learn.
things in Jinja, only the basics are things you really need to learn at first.
For instance, in a simple template, you can do something like
@ -56,15 +84,40 @@ This is also valid directly in playbooks, and you'll occasionally want to do thi
template: src=foo.cfg.j2 dest={{ remote_install_path}}/foo.cfg
In the above example, we used a variable to help decide where to place a file.
Inside a template you automatically have access to all of the variables that are in scope for a host. Actually
it's more than that -- you can also read variables about other hosts. We'll show how to do that in a bit.
.. note:: ansible allows Jinja2 loops and conditionals in templates, but in playbooks, we do not use them. Ansible
templates are pure machine-parseable YAML.
templates are pure machine-parseable YAML. This is an rather important feature as it means it is possible to code-generate
pieces of files, or to have other ecosystem tools read Ansible files. Not everyone will need this but it can unlock
possibilities.
Hey Wait, A YAML Gotcha
```````````````````````
YAML syntax requires that if you start a value with {{ foo }} you quote the whole line, since it wants to be
sure you aren't trying to start a YAML dictionary. This is covered on the `YAMLSyntax` page.
This won't work::
- hosts: app_servers
vars:
app_path: {{ base_path }}/22
Do it like this and you'll be fine::
- hosts: app_servers
vars:
app_path: "{{ base_path }}/22"
.. note:: YAML syntax requires that if you start a value with {{ foo }} you quote the whole line, since it wants to be
sure you aren't trying to start a YAML dictionary. This is covered on the `YAMLSyntax` page.
Information discovered from systems: Facts
``````````````````````````````````````````
There are other places where variables can come from, but these are a type of variable that are discovered, not set by the user.
Facts are information derived from speaking with your remote systems.
An example of this might be the ip address of the remote host, or what the operating system is.
@ -73,7 +126,248 @@ To see what information is available, try the following::
ansible hostname -m setup
The results of this can be 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.
This will return a ginormous amount of variable data, which may look like this, as taken from Ansible 1.4 on a Ubuntu 12.04 system::
"ansible_all_ipv4_addresses": [
"REDACTED IP ADDRESS"
],
"ansible_all_ipv6_addresses": [
"REDACTED IPV6 ADDRESS"
],
"ansible_architecture": "x86_64",
"ansible_bios_date": "09/20/2012",
"ansible_bios_version": "6.00",
"ansible_cmdline": {
"BOOT_IMAGE": "/boot/vmlinuz-3.5.0-23-generic",
"quiet": true,
"ro": true,
"root": "UUID=4195bff4-e157-4e41-8701-e93f0aec9e22",
"splash": true
},
"ansible_date_time": {
"date": "2013-10-02",
"day": "02",
"epoch": "1380756810",
"hour": "19",
"iso8601": "2013-10-02T23:33:30Z",
"iso8601_micro": "2013-10-02T23:33:30.036070Z",
"minute": "33",
"month": "10",
"second": "30",
"time": "19:33:30",
"tz": "EDT",
"year": "2013"
},
"ansible_default_ipv4": {
"address": "REDACTED",
"alias": "eth0",
"gateway": "REDACTED",
"interface": "eth0",
"macaddress": "REDACTED",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "REDACTED",
"type": "ether"
},
"ansible_default_ipv6": {},
"ansible_devices": {
"fd0": {
"holders": [],
"host": "",
"model": null,
"partitions": {},
"removable": "1",
"rotational": "1",
"scheduler_mode": "deadline",
"sectors": "0",
"sectorsize": "512",
"size": "0.00 Bytes",
"support_discard": "0",
"vendor": null
},
"sda": {
"holders": [],
"host": "SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01)",
"model": "VMware Virtual S",
"partitions": {
"sda1": {
"sectors": "39843840",
"sectorsize": 512,
"size": "19.00 GB",
"start": "2048"
},
"sda2": {
"sectors": "2",
"sectorsize": 512,
"size": "1.00 KB",
"start": "39847934"
},
"sda5": {
"sectors": "2093056",
"sectorsize": 512,
"size": "1022.00 MB",
"start": "39847936"
}
},
"removable": "0",
"rotational": "1",
"scheduler_mode": "deadline",
"sectors": "41943040",
"sectorsize": "512",
"size": "20.00 GB",
"support_discard": "0",
"vendor": "VMware,"
},
"sr0": {
"holders": [],
"host": "IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01)",
"model": "VMware IDE CDR10",
"partitions": {},
"removable": "1",
"rotational": "1",
"scheduler_mode": "deadline",
"sectors": "2097151",
"sectorsize": "512",
"size": "1024.00 MB",
"support_discard": "0",
"vendor": "NECVMWar"
}
},
"ansible_distribution": "Ubuntu",
"ansible_distribution_release": "precise",
"ansible_distribution_version": "12.04",
"ansible_domain": "",
"ansible_env": {
"COLORTERM": "gnome-terminal",
"DISPLAY": ":0",
"HOME": "/home/mdehaan",
"LANG": "C",
"LESSCLOSE": "/usr/bin/lesspipe %s %s",
"LESSOPEN": "| /usr/bin/lesspipe %s",
"LOGNAME": "root",
"LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:",
"MAIL": "/var/mail/root",
"OLDPWD": "/root/ansible/docsite",
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PWD": "/root/ansible",
"SHELL": "/bin/bash",
"SHLVL": "1",
"SUDO_COMMAND": "/bin/bash",
"SUDO_GID": "1000",
"SUDO_UID": "1000",
"SUDO_USER": "mdehaan",
"TERM": "xterm",
"USER": "root",
"USERNAME": "root",
"XAUTHORITY": "/home/mdehaan/.Xauthority",
"_": "/usr/local/bin/ansible"
},
"ansible_eth0": {
"active": true,
"device": "eth0",
"ipv4": {
"address": "REDACTED",
"netmask": "255.255.255.0",
"network": "REDACTED"
},
"ipv6": [
{
"address": "REDACTED",
"prefix": "64",
"scope": "link"
}
],
"macaddress": "REDACTED",
"module": "e1000",
"mtu": 1500,
"type": "ether"
},
"ansible_form_factor": "Other",
"ansible_fqdn": "ubuntu2",
"ansible_hostname": "ubuntu2",
"ansible_interfaces": [
"lo",
"eth0"
],
"ansible_kernel": "3.5.0-23-generic",
"ansible_lo": {
"active": true,
"device": "lo",
"ipv4": {
"address": "127.0.0.1",
"netmask": "255.0.0.0",
"network": "127.0.0.0"
},
"ipv6": [
{
"address": "::1",
"prefix": "128",
"scope": "host"
}
],
"mtu": 16436,
"type": "loopback"
},
"ansible_lsb": {
"codename": "precise",
"description": "Ubuntu 12.04.2 LTS",
"id": "Ubuntu",
"major_release": "12",
"release": "12.04"
},
"ansible_machine": "x86_64",
"ansible_memfree_mb": 74,
"ansible_memtotal_mb": 991,
"ansible_mounts": [
{
"device": "/dev/sda1",
"fstype": "ext4",
"mount": "/",
"options": "rw,errors=remount-ro",
"size_available": 15032406016,
"size_total": 20079898624
}
],
"ansible_os_family": "Debian",
"ansible_pkg_mgr": "apt",
"ansible_processor": [
"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"
],
"ansible_processor_cores": 1,
"ansible_processor_count": 1,
"ansible_processor_threads_per_core": 1,
"ansible_processor_vcpus": 1,
"ansible_product_name": "VMware Virtual Platform",
"ansible_product_serial": "REDACTED",
"ansible_product_uuid": "REDACTED",
"ansible_product_version": "None",
"ansible_python_version": "2.7.3",
"ansible_selinux": false,
"ansible_ssh_host_key_dsa_public": "REDACTED KEY VALUE"
"ansible_ssh_host_key_ecdsa_public": "REDACTED KEY VALUE"
"ansible_ssh_host_key_rsa_public": "REDACTED KEY VALUE"
"ansible_swapfree_mb": 665,
"ansible_swaptotal_mb": 1021,
"ansible_system": "Linux",
"ansible_system_vendor": "VMware, Inc.",
"ansible_user_id": "root",
"ansible_userspace_architecture": "x86_64",
"ansible_userspace_bits": "64",
"ansible_virtualization_role": "guest",
"ansible_virtualization_type": "VMware"
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.
Turning Off Facts
`````````````````
@ -131,17 +425,39 @@ or variables defined elsewhere in the playbook.
Registered Variables
````````````````````
Another major use of variables is running a command and using the result of that command to save the result into a variable.
The value of a task being executed in ansible can be saved in a variable and used later. See some examples of this in the
`playbooks_conditionals` chapter.
While it's mentioned elsewhere in that document too, here's a quick syntax example::
- hosts: web_servers
tasks:
- shell: /usr/bin/foo
register: foo_result
ignore_errors: True
- shell: /usr/bin/bar
when: foo_result.rc == 5
Accessing Complex Variable Data
```````````````````````````````
We already talked about facts a little higher up in the documentation.
Some provided facts, like networking information, are made available as nested data structures. To access
them a simple {{ foo }} is not sufficient, but it is still easy to do. Here's how we get an IP address::
{{ ansible_eth0["ipv4"]["address"] }}
OR alternatively::
{{ ansible_eth0.ipv4.address }}
Similarly, this is how we access the first element of an array::
{{ foo[0] }}
@ -316,3 +632,127 @@ Ansible's approach to configuration -- separating variables from tasks, keeps yo
from turning into arbitrary code with ugly nested ifs, conditionals, and so on - and results
in more streamlined & auditable configuration rules -- especially because there are a
minimum of decision points to track.
Variable Precedence: Where Should I Put A Variable?
```````````````````````````````````````````````````
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!::
---
# file: /etc/ansible/host_vars/xyz.boston.example.com
ntp-server: override.example.com
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
how all of these things can work together.

Loading…
Cancel
Save