140

In Ansible 2.4, the include module is deprecated. In its place, it ships with two replacement modules, import_tasks and include_tasks. But they have very similar descriptions:

  • include_tasks: Includes a file with a list of tasks to be executed in the current playbook.
  • import_tasks: Imports a list of tasks to be added to the current playbook for subsequent execution.

When should I use the former, and when should I use the latter?

Ben S
  • 1,511
  • 2
  • 8
  • 5
  • (Also: the deprecation warning refers to "dynamic" and "static" tasks. I read the docs but didn't understand them.) – Ben S Sep 24 '17 at 16:43

2 Answers2

146

There's quite a bit about this topic in the documentation:

The main difference is:

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

So import is static, include is dynamic.

From my experience, you should use import when you deal with logical "units". For example, separate long list of tasks into subtask files:

main.yml:

- import_tasks: prepare_filesystem.yml
- import_tasks: install_prerequisites.yml
- import_tasks: install_application.yml

But you would use include to deal with different workflows and take decisions based on some dynamically gathered facts:

install_prerequisites:

- include_tasks: prerequisites_{{ ansible_os_family | lower }}.yml
Konstantin Suvorov
  • 3,996
  • 1
  • 12
  • 13
  • 16
    I found this link very useful: http://docs.ansible.com/ansible/latest/playbooks_conditionals.html#applying-when-to-roles-imports-and-includes It calls out a case where import and include behave differently - a 'when' conditional where the tasks in the file may change the criteria used to determine the import. With import_tasks, each task checks the criteria, so the behavior changes when the criteria changes. With include_tasks, the tasks are either present or not based on whether or not the condition was evaluated as true when the include_tasks statement was executed. If I understand well... – Ethel Evans Dec 06 '17 at 20:28
  • What was the behavior of `include`? If we were using `include` would `import_tasks` be the equivalent? – Andy Shinn Jan 18 '18 at 20:27
  • 1
    `include` had `static: yes` (behaved like `import_tasks`), and `static: no` (like `include_tasks`). – Konstantin Suvorov Jan 19 '18 at 06:28
  • What is the default for `static`? – Andy Shinn May 30 '18 at 00:46
  • 1
    `static` is `None` by default: _Since Ansible 2.0, task includes are dynamic and behave more like real tasks. This means they can be looped, skipped and use variables from any source. Ansible tries to auto detect this, but you can use the static directive (which was added in Ansible 2.1) to bypass autodetection._ – Konstantin Suvorov May 30 '18 at 07:48
  • So should one default to using `import_tasks` where it would otherwise not make any difference? – Felipe Alvarez Jul 09 '18 at 03:35
  • @FelipeAlvarez I've been using `import_tasks` since asking this question, and it's been fine. – Ben S Nov 01 '18 at 09:39
  • 1
    As docs structure and links in Ansible are ever changing :/ new link with docs mentioned by @EthelEvans is: https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#conditionals-with-re-use – Kepi Jun 30 '22 at 18:56
50

Imports are static, includes are dynamic. Imports happen at parsing time, includes at runtime.

Imports basically replace the task with the tasks from the file. There are no import tasks at runtime. So, attributes like tags, and when (and most likely the rest) are copied to every imported task.

includes are indeed executed. tags and when of an included task apply only to the task itself.

Tagged tasks from an imported file get executed if an import task is untagged. No tasks is executed from an included file if an include task is untagged.

All tasks from an imported file get executed if an import task is tagged. Only tagged tasks from an included file get executed if an include task is tagged.

Limitations of imports:

  • can't be used with with_* or loop attributes
  • can't import a file, which name depends on a variable

Limitations of includes:

  • --list-tags doesn't show tags from included files
  • --list-tasks doesn't show tasks from included files
  • you cannot use notify to trigger a handler name which comes from inside a dynamic include
  • you cannot use --start-at-task to begin execution at a task inside a dynamic include

More on it here and here.

For me that basically comes down to the fact that imports can't be used with the loop attribute.

imports would certainly fail in cases like this:

# playbook.yml
- import_tasks: set-x.yml
  when: x is not defined

# set-x.yml
- set_fact
  x: foo
- debug:
  var: x

debug is not executed, since it inherits when from the import_tasks task. So, no importing task files that change variables used in the import task's when attribute.

I had a policy to start with imports, but once I needed an include I made sure nothing is imported by the included file or its children. But that's pretty damn hard to maintain. And it's still not clear if it'll protect me from troubles. I mean, mixing includes and imports is not recommended.

I can't use only imports, since I occasionally need loops. I could probably switch to only includes. But I decided to switch to imports everywhere except for the cases where I need loops. I decided to experience all those tricky edge cases first-hand. Maybe there won't be any in my playbooks. Or hopefully I'll find a way to make it work.

UPD A possibly useful trick to create a task file that can be imported many times, but executed only once:

- name: ...
  ...
  when: not _file_executed | default(False)

- name: ...
  ...
  when: not _file_executed | default(False)

...

- name: Set _file_executed
  set_fact:
    _file_executed: True

UPD One not really expected effect of mixing includes and imports is that an include task's vars override the ones of the imported tasks:

playbook.yml:

- hosts: all
  tasks:
    - import_tasks: 2.yml
      vars:
        v1: 1
    - include_tasks: 2.yml
      vars:
        v1: 1

2.yml:

- import_tasks: 3.yml
  vars:
    v1: 2

3.yml:

- debug:
    var: v1    # 2 then 1

Probably, because include_tasks first imports the files, and then applies its vars directive.

Actually, it can also be reproduced like so:

playbook.yml:

- hosts: all
  tasks:
    - import_tasks: 2.yml
      vars:
        v1: 1
    - include_tasks: 2.yml
      vars:
        v1: 1

2.yml:

- debug:
    var: v1    # 2 then 1
  vars:
    v1: 2

UPD Another case of mixing includes and imports.

playbook.yml:

- hosts: all
  tasks:
    # say, you're bound to use include here (because you need a loop)
    - include_tasks: 2.yml
      vars:
        https: yes

2.yml:

- import_tasks: 3.yml
  when: https

3.yml:

- import_tasks: 4.yml
  vars:
    https: no  # here we're trying to temporarily override the https var
- import_tasks: 4.yml

4.yml:

- debug:
    var: https

We get true and true, see the previous case (include_tasks' vars take precedence over import_tasks' ones). To avoid that we can switch to includes in 3.yml. But then the first include in 3.yml is skipped. Since it inherites when: https from the parent task, so the first task basically reads:

- import_tasks: 4.yml
  vars:
    https: no  # here we're trying to temporarily override the https var
  when: https

The solution is to switch to includes in 2.yml as well. That prevents propagation of when: https to the child tasks.

x-yuri
  • 2,141
  • 2
  • 24
  • 29
  • 15
    Great answer!. I was frustrated with everyone on the internet just repeating what the documentation says. Thank you. – Sergio Acosta Jan 26 '19 at 02:22
  • 1
    Sergio Acosta exactly - ultimate answer is import is static and include dynamic like it should explain you anything about how it works :-P – Frank Aug 07 '20 at 08:58
  • i think i'm going includes by default, which most of my code is. there are some modules i have that are open source (e.g. ansible-consul) that use imports for their handlers, and even some tasks with a when condition, then other open source (ansible-nomad) that use imports, so im not sure if that qualifies mixing or not, since they are each different roles, does it? – Brian Thomas Jun 28 '23 at 19:06
  • 1
    @BrianThomas I guess importing a file that uses `include`s or vice versa qualifies as mixing. As long as you don't do that, you should be safe. – x-yuri Jun 30 '23 at 03:58