Defining and Using Variables
Scenario: The same port number appears in nine places across the repository. A change request asks to move it, and the eighth occurrence is missed.
New words, in plain English
- Variable - a named value. Referenced in YAML as
{{ name }}. - Jinja2 - the templating language Ansible uses for
{{ }}expressions. - Play vars - variables declared in a
vars:block inside a play. vars_files- external YAML files of variables a play pulls in.- Extra vars - values passed on the command line with
-e. They beat everything else.
Variables can be declared in many places: inline in a play's vars:, in files listed under vars_files:, in group_vars/ and host_vars/, inside role defaults/ and vars/, registered from a task result, set at run time with set_fact, and passed on the command line with -e.
Referencing one is always {{ name }}, and the YAML rule from Phase 1 applies: if a value starts with {{, the whole value must be quoted, because YAML would otherwise read the brace as the start of a dictionary.
Jinja2 expressions are not limited to bare names. Filters transform values with a pipe - {{ name | upper }}, {{ items | length }}, {{ path | basename }} - and | default(x) supplies a fallback when a variable is undefined, which is the single most useful filter in the language.
One rule that prevents a class of confusing bugs: variable names must be valid Python identifiers - letters, digits and underscores, not starting with a digit. Hyphens are not allowed. app-port is not a variable name; app_port is.
Analogy: A variable is a labelled dial rather than a number scribbled in nine places. Move the dial once and everything wired to it follows. Scribbled numbers require you to find every scrap of paper, and you will miss one.
A worked example
- name: Configure the application
hosts: webservers
vars:
app_port: 8081
app_root: /srv/checkout
vars_files:
- vars/common.yml
tasks:
- name: Render the config
ansible.builtin.template:
src: app.conf.j2
dest: "{{ app_root }}/app.conf" # quoted - starts with {{
mode: "0644"
- name: Show what was actually resolved
ansible.builtin.debug:
msg: "{{ inventory_hostname }} will listen on {{ app_port }}"
# Filters do the transforming
# {{ app_port | int }} -> a number
# {{ tls_ciphers | join(':') }} -> a list becomes HIGH:!aNULL
# {{ retries | default(3) }} -> a fallback when undefined
set_fact sets a variable at run time, on a per-host basis, and it persists for the rest of the play. It is the right tool for a value you computed from a registered result. It is the wrong tool for configuration, because a value that only exists mid-run is invisible to anyone reading the repository.
A related discipline: prefix variables with the thing they belong to. webapp_port rather than port. Ansible has one flat variable namespace per host, so a role that declares port and another that declares port will collide silently, and the resulting bug is genuinely unpleasant to find.
Warning: Variable names cannot contain hyphens. app-port is parsed as a subtraction, not a name, and the error message will not make that obvious.
Goal: Put this to work in the ansible-variable-precedence lab. Open/labs/ansible, pickansible-variable-precedence, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.