
One of the most confusing Ansible features is the tags, and in this blog, I will try to clarify how they work.
A tag is an attribute that you can set to an Ansible structure (plays, roles, tasks), and then when you run a playbook you can use –tags or –skip-tags to execute a subset of tasks.
Let’s look at this basic playbook example:
--- - hosts: localhost tasks: - name: Install PMM2 client yum: name: pmm-client2 state: present tags: - install - name: create pmm-admin flags source template: src: pmm-admin-flags.txt.j2 dest: /etc/pmm/pmm-admin-flags.txt tags: - configure - name: setup pmm-agent command: "pmm-agent setup --force @/etc/pmm/pmm-agent-flags.txt" tags: - configure ...
Looking at the play we can see two tags – install and configure. You can also list all tags in a play using –list-tags
% ansible-playbook tags.yaml --list-tags playbook: tags.yaml play #1 (local): local TAGS: [] TASK TAGS: [configure, install]
You can list all tasks and tags with –list-tasks
% ansible-playbook tags.yaml --list-tasks playbook: tags.yaml play #1 (local): local TAGS: [] tasks: Install PMM2 client TAGS: [install] create pmm-admin flags source TAGS: [configure] setup pmm-agent TAGS: [configure]
Now to see the effect of tags we can –list-tasks applying tags filters to know which tasks will run.
% ansible-playbook tags.yaml --list-tasks --tags=install playbook: tags.yaml play #1 (local): local TAGS: [] tasks: Install PMM2 client TAGS: [install]
% ansible-playbook tags.yaml --list-tasks --tags=configure playbook: tags.yaml play #1 (local): local TAGS: [] tasks: create pmm-admin flags source TAGS: [configure] setup pmm-agent TAGS: [configure]
% ansible-playbook tags.yaml --list-tasks --skip-tags=install playbook: tags.yaml play #1 (local): local TAGS: [] tasks: create pmm-admin flags source TAGS: [configure] setup pmm-agent TAGS: [configure]
Ansible Tags Inheritance
So far, so good. Now let’s check how tags inheritance works. When you add a tag to a play or a static import (role or task), the included tasks inherit the tag. Let’s see an example:
--- - hosts: local tags: - pmm tasks: - name: Install PMM2 client
This is the same play as before but I just added the pmm tag (Percona Monitoring and Management) to play structure. And we can see the inheritance:
% ansible-playbook tags.yaml --list-tasks playbook: tags.yaml play #1 (local): local TAGS: [pmm] tasks: Install PMM2 client TAGS: [install, pmm] create pmm-admin flags source TAGS: [configure, pmm] setup pmm-agent TAGS: [configure, pmm]
All the tasks now have the pmm tag.
Also, with roles:
- hosts: dbservers,!rds become: yes roles: - role: percona_repo tags: - percona_repo - toolkit - pmm - role: percona-toolkit tags: - toolkit - role: pmm tags: - pmm
When we set a tag on a role structure definition, all the tasks in those roles will inherit the tags. Which, in this case, means that all the tasks in the pmm role will have the pmm tag when we run this playbook.
As I mentioned before, inheritance only works with static imports (import_*). For dynamic includes (include_*), you would need to explicitly apply the tags.
- name: Static import pmm role import_role: pmm tags: - pmm - name: Dynamic include pmm role include_role: pmm apply: tags: - pmm tags: - pmm
Be careful with this if you also have include_tasks inside your roles! Those dynamic included tasks won’t inherit the tag and then won’t run. A common example would be to include tasks based on the OS family.
% cat roles/pmm/tasks/main.yaml --- - name: include {{ ansible_os_family }} tasks include_tasks: "{{ ansible_os_family }}.yaml" apply: tags: - pmm % cat roles/pmm/tasks/Debian.yaml --- - name: Install pmm-client with apt apt: name: pmm-client2 state: present % cat roles/pmm/tasks/RedHat.yaml --- - name: Install pmm-client with yum yum: name: pmm-client2 state: present
In this example, we had to re-apply tags for the dynamic included tasks. You could also be using static imports, but in that case, you would need to import two files and use when:
- name: import tasks for RedHat import_tasks: RedHat.yaml when: ansible_os_family == 'RedHat' - name: import tasks for Debian import_tasks: Debian.yaml when: ansible_os_family == 'Debian'
In this case, imported tasks will inherit tags from the top, but also all tasks on both files will be processed inheriting the when conditional from the import statement. This means that the when condition is not applied to the import_tasks itself, but to all the tasks imported from it. So for large roles, I don’t recommend this, as still all tasks would need to be processed and the output would become hard to analyze.
Special Ansible Tags
As you may see in the latest playbook example, the percona_repo role has three tags: one for itself (percona_repo) and one for each role that installs packages (toolkit, pmm). So what if I keep adding roles for other package management that would require the percona_repo to be present? In that case, I will need to keep adding more tags to the percona_repo role, which is not efficient. Of course, you can use dependencies on the role’s meta, but for the context of this blog, I will just use tags. For these cases, Ansible has two special tags: always and never.
always tagged tasks will run unless you skip it with –skip-tags=always.
never tagged tasks won’t run unless a tag on it is specified to run –tags=never.
Let’s extend the initial sample and add always and never tags.
--- - hosts: localhost tags: - pmm tasks: - name: Uninstall PMM2 client yum: name: pmm-client2 state: absent tags: - never - uninstall - name: Install PMM2 client yum: name: pmm-client2 state: present tags: - install - always - name: create pmm-admin flags source template: src: pmm-admin-flags.txt.j2 dest: /etc/pmm/pmm-admin-flags.txt tags: - configure - name: setup pmm-agent command: "pmm-agent setup --force @/etc/pmm/pmm-agent-flags.txt" tags: - configure ...
There is now a new task “Uninstall PMM2 client” with the never tag, and “Install PMM2 client” with always.
% ansible-playbook tags.yaml --list-tasks playbook: tags.yaml play #1 (localhost): localhost TAGS: [pmm] tasks: Install PMM2 client TAGS: [always, install, pmm] create pmm-admin flags source TAGS: [configure, pmm] setup pmm-agent TAGS: [configure, pmm]
We can see the never task was skipped as expected.
% ansible-playbook tags.yaml --list-tasks --tags=configure playbook: tags.yaml play #1 (localhost): localhost TAGS: [pmm] tasks: Install PMM2 client TAGS: [always, install, pmm] create pmm-admin flags source TAGS: [configure, pmm] setup pmm-agent TAGS: [configure, pmm]
Running with configure tag, the install task is also executed as we added the always tag.
Be careful with the inheritance effect. As we said:
- Never tagged tasks run only if one the tags are specified to run
- Tags are inherited from parent blocks
In this play, we see that the pmm tag is inherited from the play to the tags, so what happens if we run with the pmm tag?
% ansible-playbook tags.yaml --list-tasks --tags=pmm playbook: tags.yaml play #1 (localhost): localhost TAGS: [pmm] tasks: Uninstall PMM2 client TAGS: [never, pmm, uninstall] Install PMM2 client TAGS: [always, install, pmm] create pmm-admin flags source TAGS: [configure, pmm] setup pmm-agent TAGS: [configure, pmm]
The task tagged as never is executed as-is, inheriting the pmm tag. So you will need to be careful when you use role tasks that are inheriting tags.
Conclusion
Tags are a powerful feature in Ansible, as it allows you to run a subset of tasks, and are very helpful for large plays. But it is really important to understand how inheritance works, and the difference between dynamic and static imports.