CIQ

Ansible Import vs. Include: What’s the Real Difference?

Ansible Import vs. Include: What’s the Real Difference?
Greg SowellFebruary 5, 2025

Bringing in certain modules in Ansible can be done either via an import or an include, but for the longest time, I never really knew the difference between the two. Documentation on the subject can be misleading, so I spent some time to suss it all out and hopefully save you some time.

Three Module Types in Ansible

There are three module types in Ansible that we should discuss in the context of include and import: import/include_task, import/include_role, or import/include_playbook. While the differences between role, task, and playbook are straightforward, the subtle differences between import and include are really difficult to discern.

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:

# 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):

# 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"
# 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:

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. However, there are a few things that are definitely 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 pluses and minuses 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.

If you need help scaling your automation practice, please reach out, as we are more than happy to assist!

Good luck out there; happy automating, and happy including!

Related posts

Ansible Import vs. Include: What’s the Real Difference?

Ansible Import vs. Include: What’s the Real Difference?

Feb 5, 2025

Ascender

How to Fix the Manual Processes That Hold Back Your Business

How to Fix the Manual Processes That Hold Back Your Business

Oct 24, 2024

Ascender

Manage Enterprise Infrastructure with Ascender by CIQ

Manage Enterprise Infrastructure with Ascender by CIQ

Sep 18, 2024

Ascender

123
>>>