Ansible 101 - Include vs Import

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 examples to help explain the differences between import and include.
Example - Parsing
A major difference between import and include tasks:
- importtasks will be parsed at the beginning when you run your playbook
- includetasks will be parsed at the moment Ansible hits them
Lets 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
importis parsed quite early. This is what is referred to asstatic.
Example - Using conditional clauses
---
- 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_roletask itself was skipped because the- when:clause is applied to the- include_roletask
- import_roletask 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 theinclude_taskitself - 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.
 
       
       
       
      