Ansible 101 - Include vs Import

24 minute read

I am asked quite often about the differences between using import or include in Ansible. For example import_role or include_role - what should you expect when using one or the other?

Ansible documentation does a great job in explaining a lot of this and I recommend at least starting there. However, I felt a need to write a few example Playbooks for my own and to give others another way to look at it.

Below are some example playbooks that are meant to give you a better understanding on the differences between import and include.

Example - Parsing

A major difference between import and include tasks:

  • import tasks will be parsed at the beginning when you run your playbook
  • include tasks will be parsed at the moment Ansible hits them

Let use an example.

We create a simple Ansible Role with a couple tasks - one that sets a variable and another that has a typo and should fail when parsed. It should show a syntax error. (Hint: Create the role quickly using ansible-galaxy init example command)

example/tasks/main.yml:

---
- set_fact:
    role_setfact_var: testvalue

- debugger:

Now we write the Playbook to run a simple debug task first and then we include the role.

test.yml:

---
- hosts: localhost
  gather_facts: no

  tasks:
    - debug:

    - include_role:
        name: example

Running the Playbook shows that the first task debug was successfull but then Ansible tried to perform the tasks inside the role and it hit our syntax error.

PLAY [localhost] **************************************************************************************

TASK [debug] ******************************************************************************************
ok: [localhost] => {
    "msg": "Hello world!"
}

TASK [include_role : example] *************************************************************************
ERROR! no action detected in task. This often indicates a misspelled module name, or incorrect module path.

The error appears to have been in '/Users/jwadleig/Projects/ansible-example-include-vs-import/roles/example/tasks/main.yml': line 3, column 3, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

    role_setfact_var: testvalue
- debugger:
  ^ here

  to retry, use: --limit @/Users/jwadleig/Projects/ansible-example-include-vs-import/test.retry

PLAY RECAP ********************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0

This all seems quite normal. Now let us instead use import_role in our playbook and see the output again.

ERROR! no action detected in task. This often indicates a misspelled module name, or incorrect module path.

The error appears to have been in '/Users/jwadleig/Projects/ansible-example-include-vs-import/roles/example/tasks/main.yml': line 3, column 3, but may be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

    role_setfact_var: testvalue
- debugger:
  ^ here

Wait, why didn’t Ansible run our first task? The reason is because the import_role task was processed at the time playbooks are parsed. So it found the syntax error much earlier and so it never was able to even run our first task with the debug module.

This is an example that really emphasizes the fact that import is parsed quite early. This is what is referred to as static.

Example - Using when: conditional clause

---
- hosts: localhost
  gather_facts: no

  tasks:
    - name: Conditional role
      include_role:
        name: example
      when: false

    - name: Apply condition to each task in role
      import_role:
        name: example
      when: false

Running this playbook results in the following output.

$ ansible-playbook test.yml

PLAY [localhost] **************************************************************************************

TASK [include_role : example] *************************************************************************
skipping: [localhost]

TASK [example : set_fact] *****************************************************************************
skipping: [localhost]

PLAY RECAP ********************************************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=0

Notice the following things:

  • include_role task itself was skipped because the when: clause is applied to the include_role task
  • import_role task applied the when: clause to the task inside the role, so the output only showed the task inside the role that was skipped

In fact, Ansible documentation states:

Most keywords, loops and conditionals will only be applied to the imported tasks, not to the statement itself.

Example - Using tags

Another common question is how tags are affected when using import vs include.

  • When using tags with include_role, the tags are applied only to the include_task itself - not the tasks inside the role!
  • When using tags with import_role, the tags are applied to all the tasks inside the role and not to the import_role task itself.

Let’s fix our example role and remove the typos.

example/tasks/main.yml:

---
- set_fact:
    role_setfact_var: "inside example role"

- debug: var=role_setfact_var

Let’s write a playbook that uses tags. Note that we will add a debug task to make sure we know when the include_role task has completed and the next task is starting. This will help us understand what is happening.

---
- hosts: localhost
  gather_facts: no

  tasks:
    - name: Apply tags to only this task (include_role)
      include_role:
        name: example
      tags:
        - install

    - debug:
        msg: "include_role completed"
      tags:
        - install

    - name: Apply tags to tasks inside the role (import_role)
      import_role:
        name: example
      tags:
        - install

Running the playbook without tags results in Ansible running all tasks. Notice that “inside example role” was printed twice - once for import_role and once for include_role.

PLAY [localhost] ***********************************************************************

TASK [Apply tags to only this task (include_role)] *************************************

TASK [example : set_fact] **************************************************************
ok: [localhost]

TASK [example : debug] *****************************************************************
ok: [localhost] => {
    "role_setfact_var": "inside example role"
}

TASK [debug] ***************************************************************************
ok: [localhost] => {
    "msg": "include_role completed"
}

TASK [example : set_fact] **************************************************************
ok: [localhost]

TASK [example : debug] *****************************************************************
ok: [localhost] => {
    "role_setfact_var": "inside example role"
}

PLAY RECAP *****************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Now let’s run the same playbook but limit the tasks but passing a tag.

ansible-playbook test.yml --tags=install

The results will show you how tags behave differently with include_role and import_role. Notice the message “inside example role” is printed only once from inside the import_role task.

PLAY [localhost] ***********************************************************************

TASK [Apply tags to only this task (include_role)] *************************************

TASK [debug] ***************************************************************************
ok: [localhost] => {
    "msg": "include_role completed"
}

TASK [example : set_fact] **************************************************************
ok: [localhost]

TASK [example : debug] *****************************************************************
ok: [localhost] => {
    "role_setfact_var": "inside example role"
}

PLAY RECAP *****************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

If you really want include_role to apply tags to all tasks inside the role, then you need to use the apply option. Let’s make that change in our playbook and test it. Notice we still keep the tag on the include_role task to make sure this task is executed, otherwise none of the tasks inside the role will run.

---
- hosts: localhost
  gather_facts: no

  tasks:
    - name: Apply tags to only this task (include_role)
      include_role:
        name: example
        apply:
          tags:
            - install
      tags:
        - install

    - debug:
        msg: "include_role completed"
      tags:
        - install

    - name: Apply tags to tasks inside the role (import_role)
      import_role:
        name: example
      tags:
        - install

Now run the playbook again and pass the tag limit.

ansible-playbook test.yml --tags=install

And the results show all tasks are running now.

PLAY [localhost] ***********************************************************************

TASK [Apply tags to only this task (include_role)] *************************************

TASK [example : set_fact] **************************************************************
ok: [localhost]

TASK [example : debug] *****************************************************************
ok: [localhost] => {
    "role_setfact_var": "inside example role"
}

TASK [debug] ***************************************************************************
ok: [localhost] => {
    "msg": "include_role completed"
}

TASK [example : set_fact] **************************************************************
ok: [localhost]

TASK [example : debug] *****************************************************************
ok: [localhost] => {
    "role_setfact_var": "inside example role"
}

PLAY RECAP *****************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

For include_role, you can use the apply option for other keywords.

References