Warning: The information here applies to ansible version
2.4.3. This problem identified here seems to have been fixed in version2.5.0and tags on included tasks work how you would expect. The behaviour of tags has changed between2.4.3and2.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 taskand then run it like so;
$ ansible-playbook ~/git/ansible-tags-article-code/playbook-1.yml \
--tags wordpress-templateHowever, 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=0Turning 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 tagand 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=0so 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.comIt 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 loopSo 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_roleAh, 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=0It’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_listwhen 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 tagwill 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=0This 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-mewill 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=0So 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.