Search This Blog

Wednesday, 10 February 2021

Ansible Variables

Ansible uses variables to manage differences between systems. With Ansible, you can execute tasks and playbooks on multiple different systems with a single command. To represent the variations among those different systems, you can create variables with standard YAML syntax, including lists and dictionaries. You can define these variables in your playbooks, in your inventory, in re-usable files or roles, or at the command line. You can also create variables during a playbook run by registering the return value or values of a task as a new variable.

After you create variables, either by defining them in a file, passing them at the command line, or registering the return value or values of a task as a new variable, you can use those variables in module arguments, in conditional “when” statements, in templates, and in loops.

Creating valid variable names

Not all strings are valid Ansible variable names. A variable name can only include letters, numbers, and underscores. Python keywords or playbook keywords are not valid variable names. A variable name cannot begin with a number.
Variable names can begin with an underscore. In many programming languages, variables that begin with an underscore are private. This is not true in Ansible. Variables that begin with an underscore are treated exactly the same as any other variable. Do not rely on this convention for privacy or security.

This table gives examples of valid and invalid variable names:
 

Valid variable Names

Not valid

foo

*foo, python keywords such as async and lambda

foo_env

Playbook keywords such as environment

foo_port

foo-port, foo port, foo.port

foo5, _foo

5foo, 12

 

Simple variables

Simple variables combine a variable name with a single value.

Defining simple variables
You can define a simple variable using standard YAML syntax. For example:
remote_install_path: /opt/my_app_config

Referencing simple variables

After you define a variable, use Jinja2 syntax to reference it. Jinja2 variables use double curly braces. For example, the expression My amp goes to {{ max_amp_value }} demonstrates the most basic form of variable substitution. You can use Jinja2 syntax in playbooks. For example:

ansible.builtin.template:
  src: foo.cfg.j2
  dest: '{{ remote_install_path }}/foo.cfg'

When to quote variables

If you start a value with {{ foo }}, you must quote the whole expression to create valid YAML syntax. If you do not quote the whole expression, the YAML parser cannot interpret the syntax - it might be a variable or it might be the start of a YAML dictionary. 

If you use a variable without quotes like this:

- hosts: app_servers
  vars:
      app_path: {{ base_path }}/22

You will see: ERROR! Syntax Error while loading YAML. If you add quotes, Ansible works correctly:

- hosts: app_servers
  vars:
       app_path: "{{ base_path }}/22"


List variables

A list variable combines a variable name with multiple values. The multiple values can be stored as an itemized list or in square brackets [], separated with commas.

Defining variables as lists
You can define variables with multiple values using YAML lists. For example:

region:
  - northeast
  - southeast
  - midwest
Referencing list variables
When you use variables defined as a list (also called an array), you can use individual, specific fields from that list. The first item in a list is item 0, the second item is item 1. For example:

region: "{{ region[0] }}"
The value of this expression would be “northeast”.

Dictionary variables

A dictionary stores the data in key-value pairs. Usually, dictionaries are used to store related data, such as the information contained in an ID or a user profile.

Defining variables as key:value dictionaries
You can define more complex variables using YAML dictionaries. A YAML dictionary maps keys to values. For example:

foo:
  field1: one
  field2: two
Referencing key:value dictionary variables
When you use variables defined as a key:value dictionary (also called a hash), you can use individual, specific fields from that dictionary using either bracket notation or dot notation:

foo['field1']
foo.field1
Both of these examples reference the same value (“one”). Bracket notation always works. Dot notation can cause problems because some keys collide with attributes and methods of python dictionaries. Use bracket notation if you use keys which start and end with two underscores (which are reserved for special meanings in python) or are any of the known public attributes:

add, append, as_integer_ratio, bit_length, capitalize, center, clear, conjugate, copy, count, decode, denominator, difference, difference_update, discard, encode, endswith, expandtabs, extend, find, format, fromhex, fromkeys, get, has_key, hex, imag, index, insert, intersection, intersection_update, isalnum, isalpha, isdecimal, isdigit, isdisjoint, is_integer, islower, isnumeric, isspace, issubset, issuperset, istitle, isupper, items, iteritems, iterkeys, itervalues, join, keys, ljust, lower, lstrip, numerator, partition, pop, popitem, real, remove, replace, reverse, rfind, rindex, rjust, rpartition, rsplit, rstrip, setdefault, sort, split, splitlines, startswith, strip, swapcase, symmetric_difference, symmetric_difference_update, title, translate, union, update, upper, values, viewitems, viewkeys, viewvalues, zfill.

Registering variables

You can create variables from the output of an Ansible task with the task keyword register. You can use registered variables in any later tasks in your play. For example:

- hosts: web_servers

  tasks:

     - name: Run a shell command and register its output as a variable
       ansible.builtin.shell: /usr/bin/foo
       register: foo_result
       ignore_errors: true

     - name: Run a shell command using output of the previous task
       ansible.builtin.shell: /usr/bin/bar
       when: foo_result.rc == 5

To see the values for a particular task, run your playbook with -v.

Registered variables are stored in memory. You cannot cache registered variables for use in future plays. Registered variables are only valid on the host for the rest of the current playbook run.

Registered variables are host-level variables. When you register a variable in a task with a loop, the registered variable contains a value for each item in the loop. The data structure placed in the variable during the loop will contain a results attribute, that is a list of all responses from the module. 
Note
If a task fails or is skipped, Ansible still registers a variable with a failure or skipped status, unless the task is skipped based on tags.

Referencing nested variables

Many registered variables (and facts) are nested YAML or JSON data structures. You cannot access values from these nested data structures with the simple {{ foo }} syntax. You must use either bracket notation or dot notation. For example, to reference an IP address from your facts using the bracket notation:

{{ ansible_facts["eth0"]["ipv4"]["address"] }}
To reference an IP address from your facts using the dot notation:

{{ ansible_facts.eth0.ipv4.address }}

Transforming variables with Jinja2 filters

Jinja2 filters let you transform the value of a variable within a template expression. For example, the capitalize filter capitalizes any value passed to it; the to_yaml and to_json filters change the format of your variable values. Jinja2 includes many built-in filters and Ansible supplies many more filters. 

Where to set variables

You can define variables in a variety of places, such as in inventory, in playbooks, in reusable files, in roles, and at the command line. Ansible loads every possible variable it finds, then chooses the variable to apply based on variable precedence rules.

Defining variables in inventory

You can define different variables for each individual host, or set shared variables for a group of hosts in your inventory. For example, if all machines in the [Boston] group use ‘boston.ntp.example.com’ as an NTP server, you can set a group variable. 

Defining variables in a playbook

You can define variables directly in a playbook:

- hosts: webservers
  vars:
    http_port: 80

When you define variables in a playbook, they are visible to anyone who runs that playbook. This is especially useful if you share playbooks widely.

Defining variables in included files and roles

You can define variables in reusable variables files and/or in reusable roles. When you define variables in reusable variable files, the sensitive variables are separated from playbooks. This separation enables you to store your playbooks in a source control software and even share the playbooks, without the risk of exposing passwords or other sensitive and personal data.

This example shows how you can include variables defined in an external file:

---

- hosts: all
  remote_user: root
  vars:
    favcolor: blue
  vars_files:
    - /vars/external_vars.yml

  tasks:

  - name: This is just a placeholder
    ansible.builtin.command: /bin/echo foo
The contents of each variables file is a simple YAML dictionary. For example:

---
# in the above example, this would be vars/external_vars.yml
somevar: somevalue
password: magic

Note: You can keep per-host and per-group variables in similar files.

Defining variables at runtime

You can define variables when you run your playbook by passing variables at the command line using the --extra-vars (or -e) argument. You can also request user input with a vars_prompt (see Interactive input: prompts). When you pass variables at the command line, use a single quoted string, that contains one or more variables, in one of the formats below.

key=value format

Values passed in using the key=value syntax are interpreted as strings. Use the JSON format if you need to pass non-string values such as Booleans, integers, floats, lists, and so on.

ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"

JSON string format

ansible-playbook release.yml --extra-vars '{"version":"1.23.45","other_variable":"foo"}'
ansible-playbook arcade.yml --extra-vars '{"pacman":"mrs","ghosts":["inky","pinky","clyde","sue"]}'
When passing variables with --extra-vars, you must escape quotes and other special characters appropriately for both your markup (for example, JSON), and for your shell:

ansible-playbook arcade.yml --extra-vars "{\"name\":\"Conan O\'Brien\"}"
ansible-playbook arcade.yml --extra-vars '{"name":"Conan O'\\\''Brien"}'
ansible-playbook script.yml --extra-vars "{\"dialog\":\"He said \\\"I just can\'t get enough of those single and double-quotes"\!"\\\"\"}"
If you have a lot of special characters, use a JSON or YAML file containing the variable definitions.

vars from a JSON or YAML file

ansible-playbook release.yml --extra-vars "@some_file.json"

Variable precedence: Where should I put a variable?

You can set multiple variables with the same name in many different places. When you do this, Ansible loads every possible variable it finds, then chooses the variable to apply based on variable precedence. In other words, the different variables will override each other in a certain order.

Teams and projects that agree on guidelines for defining variables (where to define certain types of variables) usually avoid variable precedence concerns. Define each variable in one place: figure out where to define a variable, and keep it simple.

Some behavioral parameters that you can set in variables you can also set in Ansible configuration, as command-line options, and using playbook keywords. For example, you can define the user Ansible uses to connect to remote devices as a variable with ansible_user, in a configuration file with DEFAULT_REMOTE_USER, as a command-line option with -u, and with the playbook keyword remote_user. If you define the same parameter in a variable and by another method, the variable overrides the other setting. This approach allows host-specific settings to override more general settings. For examples and more details on the precedence of these various settings, see Controlling how Ansible behaves: precedence rules.

Understanding variable precedence

Ansible does apply variable precedence, and you might have a use for it. Here is the order of precedence from least to greatest (the last listed variables override all other variables):
  1. command line values (for example, -u my_user, these are not variables)
  2. role defaults (defined in role/defaults/main.yml)
  3. inventory file or script group vars
  4. inventory group_vars/all
  5. playbook group_vars/all
  6. inventory group_vars/*
  7. playbook group_vars/*
  8. inventory file or script host vars
  9. inventory host_vars/*
  10. playbook host_vars/*
  11. host facts / cached set_facts
  12. play vars
  13. play vars_prompt
  14. play vars_files
  15. role vars (defined in role/vars/main.yml)
  16. block vars (only for tasks in block)
  17. task vars (only for the task)
  18. include_vars
  19. set_facts / registered vars
  20. role (and include_role) params
  21. include params
  22. extra vars (for example, -e "user=my_user")(always win precedence)
In general, Ansible gives precedence to variables that were defined more recently, more actively, and with more explicit scope. Variables in the the defaults folder inside a role are easily overridden. Anything in the vars directory of the role overrides previous versions of that variable in the namespace. Host and/or inventory variables override role defaults, but explicit includes such as the vars directory or an include_vars task override inventory variables.

Ansible merges different variables set in inventory so that more specific settings override more generic settings. For example, ansible_ssh_user specified as a group_var is overridden by ansible_user specified as a host_var. 

Scoping variables

You can decide where to set a variable based on the scope you want that value to have. Ansible has three main scopes:

Global: this is set by config, environment variables and the command line
Play: each play and contained structures, vars entries (vars; vars_files; vars_prompt), role defaults and vars.
Host: variables directly associated to a host, like inventory, include_vars, facts or registered task outputs

Inside a template, you automatically have access to all variables that are in scope for a host, plus any registered variables, facts, and magic variables.

Tips on where to set variables

You should choose where to define a variable based on the kind of control you might want over values.

Set variables in inventory that deal with geography or behavior. Since groups are frequently the entity that maps roles onto hosts, you can often set variables on the group instead of defining them on a role. Remember: child groups override parent groups, and host variables override group variables. 

Set common defaults in a group_vars/all file. Group variables are generally placed alongside your inventory file, but they can also be returned by dynamic inventory or defined in Red Hat Ansible Tower from the UI or API:

---
# file: /etc/ansible/group_vars/all
# this is the site wide default
ntp_server: default-time.example.com

Set location-specific variables in group_vars/my_location files. All groups are children of the all group, so variables set here override those set in group_vars/all:

---
# file: /etc/ansible/group_vars/boston
ntp_server: boston-time.example.com

If one host used a different NTP server, you could set that in a host_vars file, which would override the group variable:

---
# file: /etc/ansible/host_vars/xyz.boston.example.com
ntp_server: override.example.com

Set defaults in roles to avoid undefined-variable errors. If you share your roles, other users can rely on the reasonable defaults you added in the roles/x/defaults/main.yml file, or they can easily override those values in inventory or at the command line.

---
# file: roles/x/defaults/main.yml
# if no other value is supplied in inventory or as a parameter, this value will be used
http_port: 80

Set variables in roles to ensure a value is used in that role, and is not overridden by inventory variables. If you are not sharing your role with others, you can define app-specific behaviors like ports this way, in roles/x/vars/main.yml. If you are sharing roles with others, putting variables here makes them harder to override, although they still can by passing a parameter to the role or setting a variable with -e:

---
# file: roles/x/vars/main.yml
# this will absolutely be used in this role
http_port: 80

Pass variables as parameters when you call roles for maximum clarity, flexibility, and visibility. This approach overrides any defaults that exist for a role. For example:

roles:
   - role: apache
     vars:
        http_port: 8080

When you read this playbook it is clear that you have chosen to set a variable or override a default. You can also pass multiple values, which allows you to run the same role multiple times. For example:

roles:
   - role: app_user
     vars:
        myname: Ian
   - role: app_user
     vars:
       myname: Terry
   - role: app_user
     vars:
       myname: Graham
   - role: app_user
     vars:
       myname: John

Variables set in one role are available to later roles. You can set variables in a roles/common_settings/vars/main.yml file and use them in other roles and elsewhere in your playbook:

roles:
   - role: common_settings
   - role: something
     vars:
       foo: 12
   - role: something_else
Note

There are some protections in place to avoid the need to namespace variables. In this example, variables defined in ‘common_settings’ are available to ‘something’ and ‘something_else’ tasks, but tasks in ‘something’ have foo set at 12, even if ‘common_settings’ sets foo to 20.

Instead of worrying about variable precedence, we encourage you to think about how easily or how often you want to override a variable when deciding where to set it. If you are not sure what other variables are defined, and you need a particular value, use --extra-vars (-e) to override all other variables.

Using advanced variable syntax

Unsafe or raw strings

When handling values returned by lookup plugins, Ansible uses a data type called unsafe to block templating. Marking data as unsafe prevents malicious users from abusing Jinja2 templates to execute arbitrary code on target machines. The Ansible implementation ensures that unsafe values are never templated. It is more comprehensive than escaping Jinja2 with {% raw %} ... {% endraw %} tags.

You can use the same unsafe data type in variables you define, to prevent templating errors and information disclosure. You can mark values supplied by vars_prompts as unsafe. You can also use unsafe in playbooks. The most common use cases include passwords that allow special characters like { or %, and JSON arguments that look like templates but should not be templated. For example:

---
mypassword: !unsafe 234%234{435lkj{{lkjsdf


In a playbook:

---
hosts: all
vars:
    my_unsafe_variable: !unsafe 'unsafe % value'
tasks:
    ...

For complex variables such as hashes or arrays, use !unsafe on the individual elements:

---
my_unsafe_array:
    - !unsafe 'unsafe element'
    - 'safe element'

my_unsafe_hash:
    unsafe_key: !unsafe 'unsafe value'

YAML anchors and aliases: sharing variable values

YAML anchors and aliases help you define, maintain, and use shared variable values in a flexible way. You define an anchor with &, then refer to it using an alias, denoted with *. Here’s an example that sets three values with an anchor, uses two of those values with an alias, and overrides the third value:

---
...
vars:
    app1:
        jvm: &jvm_opts
            opts: '-Xms1G -Xmx2G'
            port: 1000
            path: /usr/lib/app1
    app2:
        jvm:
            <<: *jvm_opts
            path: /usr/lib/app2
...

Here, app1 and app2 share the values for opts and port using the anchor &jvm_opts and the alias *jvm_opts. The value for path is merged by << or merge operator.

Anchors and aliases also let you share complex sets of variable values, including nested variables. If you have one variable value that includes another variable value, you can define them separately:

vars:
  webapp_version: 1.0
  webapp_custom_name: ToDo_App-1.0

This is inefficient and, at scale, means more maintenance. To incorporate the version value in the name, you can use an anchor in app_version and an alias in custom_name:

vars:
  webapp:
      version: &my_version 1.0
      custom_name:
          - "ToDo_App"
          - *my_version

Now, you can re-use the value of app_version within the value of custom_name and use the output in a template:

---
- name: Using values nested inside dictionary
  hosts: localhost
  vars:
    webapp:
        version: &my_version 1.0
        custom_name:
            - "ToDo_App"
            - *my_version
  tasks:
  - name: Using Anchor value
    ansible.builtin.debug:
        msg: My app is called "{{ webapp.custom_name | join('-') }}".

You’ve anchored the value of version with the &my_version anchor, and re-used it with the *my_version alias. Anchors and aliases let you access nested values inside dictionaries.