29. 07. 2024 Lorenzo Candeago DevOps

include_task vs import_task in Ansible

After updating one of our machines, we found that some of our Ansible playbooks were failing with the following error:

ERROR! [DEPRECATED]: ansible.builtin.include has been removed. Use include_tasks or import_tasks instead. This feature was removed from ansible-core in a release after 2023-05-16. Please update your playbooks.

The include module was removed in ansible 2.16, while the include warning deprecation was already present in ansible 2.12.

At this point, we can choose between two possible modules to replace include: import_tasks or include_tasks.

What’s the difference between the two modules? As noted in Creating Reusable Playbooks, include and import statements are very similar, however the Ansible executor engine treats them very differently.

  • All import* statements are pre-processed at the time playbooks are parsed.
  • All include* statements are processed as they are encountered during the execution of the playbook.

Let’s see what that means with a couple of simple playbooks. First, here’s a simple task that just prints a message to be included/imported:

# debug_task.yaml
---
- name: Task message
   ansible.builtin.debug:
     msg: "{{ message }}"

and a playbook that uses that task:

- name: Demonstrate import_tasks vs include_tasks
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Import tasks statically
       ansible.builtin.import_tasks: debug_task.yaml
       vars:
         message: "This is an imported task"

    - name: Include tasks dynamically
       ansible.builtin.include_tasks: debug_task.yaml
       vars:
         message: "This is an included task"

Running it yields the following output:

PLAY [Demonstrate import_task vs include_tasks] ************************************************************************************************************************************

TASK [Task message] ****************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "This is an imported task"
}

TASK [Include tasks dynamically] ***************************************************************************************************************************************************
included: /home/lo/ansible-for-blogs/task_to_include.yaml for localhost

TASK [Task message] ****************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "This is an included task"
}

We can already spot the first differences: when importing a task, the name of the import task ( Import tasks statically) is not printed.

Instead we see only the imported task name.

This gives us a hint of what’s going on: To simplify, when importing a task, Ansible replaces the placeholder (import_task block) with the actual content of the task. The playbook with import_tasks is equivalent to the following playbook without the import_tasks:

- name: Demonstrate import_tasks vs include_tasks
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Task message
      ansible.builtin.debug:
        msg: "{{ message }}"
      vars:
        message: "message"

Another thing to notice is that because of this, we cannot use loops when using include_tasks: if we try to do that we get the following error:

ERROR! You cannot use loops on 'import_tasks' statements. You should use 'include_tasks' instead.

Another thing to note is that if we want to have the name of the task to import as a variable in the inventory or group_vars, we cannot use import_tasks: the following playbook will fail if task_name is defined as a group_var or is in the inventory:

- name: This will not work
  hosts: localhost
  gather_facts: no
  tasks:
    - name: "Import tasks statically where task_name is defined in inventory or group_vars"
       ansible.builtin.import_tasks: "{{task_name}}"

while the same will work when using import_tasks even if task_name is defined in group_vars or in the inventory.

So to sum up, if a task needs to be called multiple times in a playbook with different variable values, i.e., you want to execute a task where the variable is defined in a loop, you can use include_tasks. If you just want to split a long playbook into multiple task files, and the tasks don’t need to call the same task with different variable values, you can use import_tasks. There are some extra details to pay attention to such as how tags are treated when including or how handlers work in the case of include vs import. You can read more details about that here.

These Solutions are Engineered by Humans

Did you find this article interesting? Are you an “under the hood” kind of person? We’re really big on automation and we’re always looking for people in a similar vein to fill roles like this one as well as other roles here at Würth Phoenix.

Lorenzo Candeago

Lorenzo Candeago

DevOps Engineer at Würth Phoenix

Author

Lorenzo Candeago

DevOps Engineer at Würth Phoenix

Leave a Reply

Your email address will not be published. Required fields are marked *

Archive