How to Include Tasks in Ansible + Examples


Percy Grunwald's Profile Picture

Written by Percy Grunwald

— Last Updated February 22, 2019

You can use the Ansible include_tasks module to import tasks from other files. There are a number of reasons you might want to move tasks into separate files when using Ansible:

  1. Refactoring - chop up unfeasibly large playbooks into smaller files to make them easier to comprehend
  2. Conditionally including tasks - you may wish to run groups of tasks conditionally based on some host-specific property (e.g. the OS)
  3. Running a list of tasks for each item in a loop

I use the include_tasks module frequently and I’ve included my most common use cases in the sections below.

Common use cases

My most common use cases for using include_tasks are:

  1. Running different tasks depending on the OS of the host (e.g. Debian-based vs RHEL-based operating systems)
  2. Splitting a role’s main.yml into smaller, more manageable files

How to include tasks from a file

The most basic function of the include_tasks module is to import tasks from a file into another list of tasks. This could be within a playbook or a role’s task files. The most common use of this technique is to break up larger lists of tasks into smaller, more manageable files.

For example, imagine you have a list of tasks like the following:

---
# ./roles/my_role/tasks/main.yml

- name: install dependencies
  apt:
    name: "{{ item }}"
    state: present
  become: true
  loop:
    - nginx
    - php7.2

- name: configure the application
  copy: 
    src: my_app.conf
    dest: /etc/my_app.conf
  become: true

This is only 2 tasks, but for the sake of the example you can break these tasks into their own files:

---
# ./roles/my_role/tasks/install.yml

- name: install dependencies
  apt:
    name: "{{ item }}"
    state: present
  become: true
  loop:
    - nginx
    - php7.2
---
# ./roles/my_role/tasks/configure.yml

- name: configure the application
  copy: 
    src: my_app.conf
    dest: /etc/my_app.conf
  become: true

You can now rewrite the original list of tasks like this:

---
# ./roles/my_role/tasks/main.yml

- include_tasks: install.yml

- include_tasks: configure.yml

If you ever need to add more tasks to set up or configure the role, you can add them directly to the setup.yml or configure.yml instead of the main.yml.

Using this technique is also a nice way to refactor large lists of tasks into smaller logical groupings (i.e. this group of tasks installs things and that group of tasks configures things).

How to include tasks conditionally

Another very common use case for include_tasks is to import some tasks conditionally based on some variable in a role.

A common example would be to give the user the ability to choose whether to install a piece of software using a package manager like apt or yum or by compiling it from source.

Use the when keyword to include tasks conditionally:

# ./roles/my_role/tasks/main.yml

- include_tasks: install_with_apt.yml
  when: not install_from_source

- include_tasks: compile_and_install_from_source.yml
  when: install_from_source

In the example above, the user could set install_from_source: true or install_from_source: false to control how the package is installed.

How to include tasks with a dynamic file name

In my experience, including tasks with a dynamic file name is most commonly used to vary tasks depending on the OS of the remote host.

Consider this example:

# ./roles/redis/tasks/main.yml

- name: install redis on Debian based distros 
  apt:
    name: redis-server
    state: present
    update_cache: true
  become: true
  when: ansible_os_family == 'Debian'

- name: ensure epel-release repo is installed on RHEL based distros
  yum:
    name: epel-release
    state: present
    update_cache: true
  become: true
  when: ansible_os_family == 'RedHat'

- name: install redis on RHEL based distros
  yum:
    name: redis
    state: present
    update_cache: true
  become: true
  when: ansible_os_family == 'RedHat'

You could refactor these tasks into separate files and then use conditional logic to include them:

---
# ./roles/redis/tasks/setup-Debian.yml

- name: install redis on Debian based distros 
  apt:
    name: redis-server
    state: present
    update_cache: true
  become: true
  when: ansible_os_family == 'Debian'
---
# ./roles/redis/tasks/setup-RedHat.yml

- name: ensure epel-release repo is installed on RHEL based distros
  yum:
    name: epel-release
    state: present
    update_cache: true
  become: true
  when: ansible_os_family == 'RedHat'

- name: install redis on RHEL based distros
  yum:
    name: redis
    state: present
    update_cache: true
  become: true
  when: ansible_os_family == 'RedHat'

After creating the separate files, you can modify the main tasks file to look like this:

# ./roles/redis/tasks/main.yml

- include_tasks: setup-Debian.yml
  when: ansible_os_family == 'Debian'

- include_tasks: setup-RedHat.yml
  when: ansible_os_family == 'RedHat'

Much better, but you could make it even more terse by just using the ansible_os_family variable to include the file in a single line:

- include_tasks: "setup-{{ ansible_os_family }}.yml"

This will produce the same results as the original list of tasks. This is a common pattern used in many third party roles you’ll find on Ansible Galaxy.

How include tasks for each item in a loop

This is probably the least common use case for include_tasks, but I have used it a few times. This technique is useful any time you need to run multiple tasks for each item in a loop.

Here’s a contrived example to show you how you could use it: a greet_tasks.yml list of tasks that will create a greeting fact and then print it with debug:

---
# ./greet_tasks.yml

- name: set greeting fact
  set_fact: 
    greeting: "Greeting {{ index }}: Hello, {{ name }}!"

- name: print greeting fact
  debug: var=greeting

You can run these tasks in a loop in a playbook like this:

# ./greet.yml

- name: greet people
  hosts: "*"
  tasks:
    - include_tasks: greet.yml
      loop:
        - World
        - Percy
      loop_control:
        loop_var: name
        index_var: index

Running the playbook will produce the following output:

TASK [set greeting fact]
ok: [123.123.123.123]

TASK [print greeting fact]
ok: [123.123.123.123] => {
    "greeting": "Greeting 0: Hello, World!"
}

TASK [set greeting fact]
ok: [123.123.123.123]

TASK [print greeting fact]
ok: [123.123.123.123] => {
    "greeting": "Greeting 1: Hello, Percy!"
}

Do you have any other use cases or examples that I didn’t cover? Hit me up in the comments and let’s discuss! 😃

Comment & Share