Ansible Conditionals

Ansible Conditionals

ยท

5 min read

In Ansible, conditionals offer control in playbooks, allowing tasks, plays, or even entire roles to be executed or skipped based on certain conditions. Here's an overview of the most used conditionals and their uses:

1. when

  • The most commonly used conditional. It determines if a task or play should be executed.

  • You have to write an expression that evaluates to a boolean (true/false).

Example:

You want some tasks to be executed only on some specific servers.

---

- hosts: all
  become: true
  tasks:
  - name: update repository index Ubuntu
    apt:
      update_cache: yes
    when: ansible_distribution == "Ubuntu"

  - name: update repository index Centos
    yum:
      update_cache: yes
    when: ansible_distribution == "CentOS"

Here while executing the task. Ansible will first check the when condition. If it's true only then it will execute the main task.

You can see it's skipping if the condition is not true.

From where we are getting this "ansible_os_family" variable?

while running every Ansible playbook. By default, Ansible runs the gather_facts task. that task gathers all information about servers. which gives us access to these variables.

2. changed_when

  • Modifies the behavior of a task to report a "changed" status.

  • You have to write an expression that evaluates to a boolean (true/false).

Example:

Updating the repository is a basic task Suppose you don't want to consider that as a change then you can write it like below

---

- hosts: all
  become: true
  tasks:

  - name: update repository index
    apt:
      update_cache: yes
    when: ansible_distribution == "Ubuntu"
    changed_when: false

  - name: update repository index
    yum:
      update_cache: yes
    when: ansible_distribution == "CentOS"
    changed_when: false

It will give you a changed=0

3. failed_when

  • Modifies the behavior of a task to report a "failed" status based on custom conditions.

Example: You want to check disk usage and want to mark it as a failure if usage is more than 90%.

---

- hosts: all
  vars:
    disk_usage_threshold: 90
  tasks:
    - name: Check root filesystem disk usage
      command: df -h /
      register: df_output
      changed_when: false
      failed_when: >
        (ansible_distribution == "CentOS" and (df_output.stdout_lines[-1].split()[4]|replace('%',''))|int > disk_usage_threshold) or
        (ansible_distribution == "Ubuntu" and (df_output.stdout_lines[1].split()[4]|replace('%',''))|int > disk_usage_threshold)

The output will be like the below

as my disks are not filled let me fill the disk of one server.

Now you can see that for one server script has failed showing disk is filled 94%.

4. until

  • Used for retrying a task until a certain condition is met or a certain number of retries is reached.

Example: Retry a task until a file exists.

---
- hosts: all
  tasks:
    - name: Check if file exists
      stat:
        path: "/tmp/file.txt"
      register: file_status
      until: file_status.stat.exists
      retries: 5
      delay: 3

You can see playbook is trying to check if file exist at that location or not. while it's running i logged into one server and created file. you can see it's showing file found for one server. it kept trying for another server.

5. assert

  • Used for failing a play if certain conditions are not met.

Example: Fail if the free memory is less than 10 GB.

---
- hosts: all
  tasks:
    - name: Get filesystem details for /
      ansible.builtin.setup:
        filter: ansible_mounts

    - name: Assert minimum disk space requirements
      assert:
        that:
          - item.size_available >= 10000000000  # 10 GB in bytes
        fail_msg: "Not enough disk space on {{ item.mount }}. At least 10GB required."
        success_msg: "{{ item.mount }} has sufficient space."
      with_items: "{{ ansible_mounts }}"
      when: item.mount == "/"

Output

You can see in the above image it failed for one server having insufficient space and was successful for another.

6. block and rescue

  • block groups tasks together.

  • rescue defines tasks to be executed if any task in the block fails.

  • It's like try and catch in javascript, java etc.

Example: Attempt to install a package and handle failures.

---
- hosts: all
  become: yes
  vars:
    package_name: example_package
  tasks:
    - name: Attempt to install a package
      block:
        - name: Install the "{{ package_name }}"
          package:
            name: "{{ package_name }}"
            state: present
      rescue:
        - name: Handle the failure
          debug:
            msg: "Failed to install {{ package_name }} . Check if the package name is correct or if there's a repo issue."
      always:
        - name: This always gets executed
          debug:
            msg: "This message will always be displayed, regardless of success or failure in the block above."

As you can see above script will fail to install a package called example_package as no such package exists. still, we are not shown any failure(failed=0). You can see the task in rescue is executed correctly.

if we put package_name=wget. which is actually a real package name. task will succeed. It will look like below.

7. Conditional imports and includes

  • Conditionally import playbooks or include tasks/roles based on certain conditions.

Example: install web server depending on user input

web_setup.yml

---
- hosts: all
  become: true
  vars_prompt:
    - name: webserver_type
      prompt: "Which web server do you want to install? (apache/nginx)"
      private: no

  tasks:
    - name: Include tasks based on web server choice
      include_tasks: "install_{{ webserver_type }}.yml"
      when: webserver_type in ['apache', 'nginx']

install_apache.yml

---
- name: Install Apache
  package:
    name: apache2
    state: present
  when: ansible_distribution == "Ubuntu"

- name: Install httpd
  package:
    name: httpd 
    state: present
  when: ansible_distribution == "CentOS"

install_nginx.yml

---
- name: Install Nginx
  ansible.builtin.package:
    name: nginx
    state: present

so depending on user input it will select playbook and run it.

for apache as input you can see it ran install_apache.yml

For nginx as input, you can see it ran install_nginx.yml


In conclusion, Ansible's conditionals provide a powerful way to bring logic and decision-making into playbooks, ensuring that infrastructure automation is both flexible and precise. Like any tool, the key is understanding the available options and applying them judiciously.

Happy automating! ๐Ÿค–

ย