Warning: The information here applies to ansible version 2.4.3. This problem identified here seems to have been fixed in version 2.5.0 and tags on included tasks work how you would expect. The behaviour of tags has changed between 2.4.3 and 2.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 -llocalhost

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 -llocalhost

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 -llocalhost

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 -llocalhost

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 -l 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 -l 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.