diff --git a/docsite/latest/rst/playbooks_variables.rst b/docsite/latest/rst/playbooks_variables.rst index 1e1a4742d21..e77ff04473c 100644 --- a/docsite/latest/rst/playbooks_variables.rst +++ b/docsite/latest/rst/playbooks_variables.rst @@ -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 _` 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.