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:
import
tasks will be parsed at the beginning when you run your playbookinclude
tasks 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
import
is 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_role
task itself was skipped because thewhen:
clause is applied to theinclude_role
taskimport_role
task applied thewhen:
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_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.