DevOps Peach

Linux · Ansible – Configuration Automation · Lesson 4 of 5

Modules, variables, conditions, and loops

Use purpose-built modules and place task keywords at the right level.

Practice interactively →

Choose a purpose-built module

Modules such as copy, file, package, apt, service, and user describe desired state better than raw shell commands. command does not invoke a shell; shell is needed for pipes or redirection but can be non-idempotent and vulnerable when input is concatenated.

Module Typical job
template Render a Jinja2 file with variables and copy it
lineinfile Ensure one line exists in a file
get_url Download a file
git Check out a repository at a version
systemd Manage a unit and run daemon-reload
user, group Manage accounts

ansible-doc ansible.builtin.template shows a module's options and examples without leaving the terminal.

flowchart LR
  F["Facts<br/>ansible_os_family"] --> W{"when:<br/>Debian?"}
  W -- "yes" --> APT["apt module<br/>state: present"]
  W -- "no" --> DNF["dnf module<br/>state: present"]
  APT --> REG["register: result<br/>changed?"]
  REG --> LOOP["loop: users<br/>one item each"]
  %% step F: Facts gathered from each host describe its OS, IPs and hardware.
  %% step W: when decides per host whether a task runs.
  %% step APT: Purpose-built modules like apt are idempotent: running twice changes nothing.
  %% step DNF: Another branch handles Red Hat family hosts.
  %% step REG: register saves a task's result so later tasks can use it.
  %% step LOOP: loop repeats one task for each item in a list.

Variables and facts

Variables can come from inventory, group_vars, a play's vars: or vars_files:, a role's defaults, or the command line with -e "version=1.2", which has the highest precedence. Facts are variables Ansible gathers automatically; ansible web1 -m ansible.builtin.setup prints them. Common ones are ansible_facts.os_family, ansible_facts.distribution, and ansible_facts.default_ipv4.

Jinja2 filters transform values: {{ port | default(80) }} supplies a fallback, {{ name | upper }} changes case, and {{ items | length }} counts a list.

Loops, conditions, and results

- name: Install selected packages
  ansible.builtin.apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - curl
  when: ansible_facts.os_family == "Debian"
  register: install_result

loop, when, and register are task keywords at the same level as the module call; they do not belong inside module arguments. When a YAML value begins with a Jinja expression, quote the entire value: name: "{{ package_name }}".

Keyword Purpose
loop Run the task for each item
when Run only when an expression is true
register Save the result dictionary
debug Display a variable or message
changed_when / failed_when Decide what counts as changed or failed

Facts have exact casing; RedHat differs from Redhat. The package module is more portable but does not expose every apt- or dnf-specific option.

Handling errors

A failed task stops that host by default. ignore_errors: true continues anyway and should be rare. A block with rescue and always sections works like try, catch, and finally: run steps, recover if one fails, and always clean up.

Practice interactively →