Warning: The information here applies to ansible version
2.4.3
. This problem identified here seems to have been fixed in version2.5.0
and tags on included tasks work how you would expect. The behaviour of tags has changed between2.4.3
and2.5.0
- See part-2 for the updated version.
The code for this article is available on github.
Once your ansible configuration gets decently complicated, you might well require multiple roles for different components, and a full run-through of the playbook might take several minutes. It is therefore useful to be able to run just a few tasks at once. …Usually repeatedly until you fix the bugs.
For example, today I was troubleshooting a problem with a configuration for wordpress. The main playbook included roles for collectd and logstash and the wordpress role itself called in further roles mysql and apache. The whole thing took a couple of minutes to run, even on subsequent runs after the packages have been installed.
The error was in a single template in the wordpress role similar to this;
# file: main.yml
- name: example template
template:
src: template-in.j2
dest: "/tmp/template-out.txt"
But that template isn’t processed until after about a minute when running the
full playbook, because the main.yml
tasks file was called by a role
which was in turn called from the original playbook. So for debugging when you
have to run this repeatedly, this can be a tiresome wait.
According to the ansible documentation, tags would be useful in this situation;
Tags: If you have a large playbook it may become useful to be able to run a specific part of the configuration without running the whole playbook.
Both plays and tasks support a “tags:” attribute for this reason.
You can ONLY filter tasks based on tags from the command line with –tags or –skip-tags. Adding “tags:” in any part of a play (including roles) adds those tags to the contained tasks.
http://docs.ansible.com/ansible/latest/user_guide/playbooks_tags.html#tags
The obvious thing to do is to add a tag to that task, and run ansible-playbook
with the tag as an argument.
# file: main.yml
- name: example template
template:
src: template-in.j2
dest: "/tmp/template-out.txt"
tags: wordpress-template # <--- add tags to task
and then run it like so;
$ ansible-playbook ~/git/ansible-tags-article-code/playbook-1.yml \
--tags wordpress-template
However, I found that the tagged task didn’t run;
$ ansible-playbook ./molecule/default/playbook-1.yml \
--tags wordpress-template \
--limit localhost
PLAY [Converge] *************************************************************
TASK [Gathering Facts] ******************************************************
ok: [localhost]
PLAY RECAP ******************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
Turning back to the documentation, it seems that this is because of the following caveat;
The above information does not apply to include_tasks or other dynamic includes, as the attributes applied to an include, only affect the include itself.
To test this, I added another task that definitely shouldn’t run, and tried
importing the role with the import_role
module instead;
# file: playbook-2.yml
---
- name: Converge
hosts: all
tasks:
- import_role:
name: ansible-tags-article-code
tasks_from: playbook-2-tasks-1.yml
# file: playbook-2-tasks-1.yml
- name: "this task is tagged as 'wordpress-template'"
template:
src: template-in.j2
dest: "/tmp/template-out.txt"
tags: wordpress-template <--- tags
- name: some other tasks 'not tagged with wordpress-template'
template:
src: template-in.j2
dest: "/tmp/template-out2.txt"
tags: dont-run-me # <--- no run tag
and running this, you can see the tagged task runs correctly, and the other doesn’t
$ ansible-playbook ./molecule/default/playbook-2.yml \
--tags wordpress-template \
--limit localhost
PLAY [Converge] **********************************************************
TASK [Gathering Facts] ***************************************************
ok: [localhost]
TASK [ansible-tags-article-code : this task is tagged as 'wordpress-template']
ok: [localhost]
PLAY RECAP ****************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
so great!, we just use import_role
and not include_role
and everything is
good, right?
… Actually…
The reason for using include_role
and include_tasks
is that the imported
versions are done statically in advance of variable processing. Which means you
can’t do something like this with import_role
;
# file: playbook-3.yml
- import_role:
name: ansible-tags-article-code
tasks_from: playbook-3-tasks-1.yml
vars:
site_to_process: "{{ item.site }}"
with_items:
- site: www.mywordpresssite-01.com
- site: www.mywordpresssite-02.com
- site: www.mywordpresssite-03.com
It gives you an error like this;
$ ansible-playbook ./molecule/default/playbook-3.yml \
--tags wordpress-template \
--limit localhost
ERROR! You cannot use 'static' on an include_role with a loop
So how about tagging the include_role
…
# file: playbook-4.yml
- include_role:
name: ansible-tags-article-code
tasks_from: playbook-4-tasks-1.yml
tags: wordpress-template # <- tag the include_role
Ah, well here things go wrong…
$ ansible-playbook ./molecule/default/playbook-4.yml \
--tags wordpress-template \
--limit localhost
TASK [ansible-tags-article-code : this task is tagged as 'wordpress-template']
ok: [localhost]
TASK [ansible-tags-article-code : some other tasks not tagged with wordpress-template]
ok: [localhost]
PLAY RECAP ****************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
It’s running all the tasks under the include regardless of their tag… It’s
seems that contrary to the documentation,
the include_role
and include_tasks
modules are copying the tag to the
children.
After a bit of head scratching, I found this that someone had noticed this problem and raised an issue on github.
So with a little modification, a patch to ignore tags in include
modules;
$ git diff
diff --git a/lib/ansible/playbook/block.py b/lib/ansible/playbook/block.py
index da51e1447e..a243d603c1 100644
--- a/lib/ansible/playbook/block.py
+++ b/lib/ansible/playbook/block.py
@@ -357,7 +357,8 @@ class Block(Base, Become, Conditional, Taggable):
if isinstance(task, Block):
tmp_list.append(evaluate_block(task))
elif (task.action == 'meta' or
- (task.action == 'include' and task.evaluate_tags([], play_context.skip_tags, all_vars=all_vars)) or
+ # (task.action == 'include' and task.evaluate_tags([], play_context.skip_tags, all_vars=all_vars)) or
+ (task.action in ('include', 'include_tasks', 'include_role') and task.evaluate_tags([], play_context.skip_tags, all_vars=all_vars)) or
task.evaluate_tags(play_context.only_tags, play_context.skip_tags, all_vars=all_vars)):
tmp_list.append(task)
return tmp_list
when applied to the file lib/ansible/playbook/block.py
it allows running of
specific tags on dynamic includes, like so;
# file: playbook-5.yml
- include_role:
name: ansible-tags-article-code
tasks_from: playbook-5-tasks-1.yml
# file: playbook-5-tasks-1.yml
- name: "this task is tagged as 'wordpress-template'"
template:
src: template-in.j2
dest: "/tmp/template-out.txt"
tags: wordpress-template # <--- run me tag
- name: some other tasks 'not tagged with wordpress-template'
template:
src: template-in.j2
dest: "/tmp/template-out2.txt"
tags: dont-run-me # <--- no run tag
will correctly run the dynamically included tasks/roles;
$ ansible-playbook \
~/ansible/roles/ansible-tags-article-code/molecule/default/playbook-5.yml \
--tags wordpress-template \
--limit localhost
PLAY [Converge] ***********************************************************
TASK [Gathering Facts] ****************************************************
ok: [localhost]
TASK [include_role] *******************************************************
TASK [ansible-tags-article-code : this task is tagged as 'wordpress-template']
ok: [localhost]
PLAY RECAP ****************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
This rather hacky patch also works for calling include_tasks
inside an
include_role
like this;
# file: playbook-6.yml
- include_role:
name: ansible-tags-article-code
tasks_from: playbook-6-tasks-1.yml
# file: playbook-6-tasks-1.yml
- include_tasks: "playbook-6-tasks-2.yml"
# file: playbook-6-tasks-2.yml
- name: "this task is tagged as 'wordpress-template'"
template:
src: template-in.j2
dest: "/tmp/template-out.txt"
tags: wordpress-template
- name: some other tasks 'not tagged with wordpress-template'
template:
src: template-in.j2
dest: "/tmp/template-out2.txt"
tags: dont-run-me
will correctly run the dynamically included tasks/roles;
$ ansible-playbook \
~/ansible/roles/ansible-tags-article-code/molecule/default/playbook-6.yml \
--tags wordpress-template \
--limit localhost
PLAY [Converge] ***********************************************************
TASK [Gathering Facts] ****************************************************
ok: [localhost]
TASK [include_role] *******************************************************
TASK [ansible-tags-article-code : include_tasks] **************************
included: /home/tomhodder/Dropbox/bin/ansible/roles/ansible-tags-article-code/tasks/playbook-6-tasks-2.yml for localhost
TASK [ansible-tags-article-code : this task is tagged as 'wordpress-template']
ok: [localhost]
PLAY RECAP ****************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
So now you can run just a single tagged task inside a nested set of includes without waiting for all the other tasks to run…
The code for this article is available on github.