Skip to content
Nov 5 / Greg

Ansible Import Vs Include…What’s The Real Difference?

There are three module types that can be either imported or included which are: import/include_task, import/include_role, or import/include_playbook.
While these are mostly the same there are some subtle differences…so subtle in fact that I couldn’t really figure out what the difference was, so why not share what I’ve learned 🙂

So the textbook difference between the two is that:
Import is static. This means they are fully loaded before playbook execution begins.
Include is dynamic. This means they are parsed during execution as they are reached.

I’m not sure how this reads to you, but my understanding was that variables and the like would be interpreted strangely for imports…kind of like whatever the value of a variable was at load would somehow dictate their inclusion or reaction…which was wrong. Here’s a playbook I wrote to try and test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# playbook.yml
---
- name: A demo on using include vs import in Ansible with incorrect info
  hosts: localhost
  gather_facts: false
  vars:
    testvar: "Initial Value"  # Initial value for the variable
  tasks:
    - name: Display initial testvar value
      ansible.builtin.debug:
        msg: "Before import_tasks and include_tasks: testvar is {{ testvar }}"
 
    - name: Use import_tasks (static)
      import_tasks: tasks/import-tasks.yml
 
    - name: Update testvar to "Updated Value"
      set_fact:
        testvar: "Updated Value"
 
    - name: Use include_tasks (dynamic)
      include_tasks: tasks/include-tasks.yml
 
    - name: Re-run import_tasks after update
      import_tasks: tasks/import-tasks.yml

Here are the two task files it calls(really they are just spitting out debug info…nothing special here):

1
2
3
4
5
# tasks/import-tasks.yml
- name: Import Task Display testvar if it is "Updated Value"
  ansible.builtin.debug:
    msg: "Import Task: testvar is {{ testvar }}"
  when: testvar == "Updated Value"
1
2
3
4
5
# tasks/include-tasks.yml
- name: Include Task Display testvar if it is "Updated Value"
  ansible.builtin.debug:
    msg: "Include Task: testvar is {{ testvar }}"
  when: testvar == "Updated Value"

I was thinking that in the above example it wouldn’t run any of the imports(as at runtime the variable would have been set incorrectly). Take a look at the job output, though:

1
2
3
4
5
6
7
8
9
10
11
12
PLAY [A demo on using include vs import in Ansible with incorrect info] ********
TASK [Display initial testvar value] *******************************************
ok: [localhost] => {
    "msg": "Before import_tasks and include_tasks: testvar is Initial Value"
}
TASK [Import Task Display testvar if it is "Updated Value"] ********************
skipping: [localhost]
TASK [Update testvar to "Updated Value"] ***************************************
ok: [localhost]
TASK [Use include_tasks (dynamic)] *********************************************
included: /runner/project/tasks/include-tasks.yml for localhost
TASK [Include Task Display testvar if it is "Updated Value"] *******************

I tried all kinds of variations, but no matter what, I couldn’t get it to fail. It ignored the first import as one would expect, but when I reset the variable it went ahead and ran the second import…So, I surrender trying to make it functionally display their differences. There are a few things that are definetly different as per this documentation:

Using include* does have some limitations when compared to import* statements:

  • Tags which only exist inside a dynamic include will not show up in –list-tags output.
  • Tasks which only exist inside a dynamic include will not show up in –list-tasks output.
  • You cannot use notify to trigger a handler name which comes from inside a dynamic include (see note below).
  • You cannot use –start-at-task to begin execution at a task inside a dynamic include.

    Using import* can also have some limitations when compared to dynamic includes:

  • As noted above, loops cannot be used with imports at all.
  • When using variables for the target file or role name, variables from inventory sources (host/group vars, etc.) cannot be used.
  • Handlers using import* will not be triggered when notified by their name, as importing overwrites the handler’s named task with the imported task list.

    Looking at the plus’ and minus’ of each, just go with include by default.
    Most of the shortcomings of include are things I never run into, so not really a problem.
    The shortcomings of import are much more impactful: not being able to use it with loops and losing inventory source variables.

    Conclusion

    In short, use include and just ignore the import stuff.
    If you have any other topics of interest, let me know. If you would tweak or tune this, drop me a note on that too.
    Good luck out there; happy automating, and happy including!

    Leave a Comment

     

    *