Ansible: tags

Tags in Ansible are used for filtering tasks based on a list of strings – tags. This feature isn’t complicated until you just want to run or skip all of the tasks with a specific tag.

Can I run tasks based on multiple tags? What happens with my dependencies if I run my playbook with tags? Are imported/included tasks and roles affected?

For first-timers, tags and “when” conditions could look the same but they are not. While “when” keyword can contain any condition with any complexity than tags just “is the requested tag in the tags list or not” filter.

There’s one more difference when you check the output of a playbook run: the “when” keyword is evaluated at run time and based on the result, the task will be run or not. Tags evaluate before the first play starts. If a task’s tags and the provided tag list have no intersection then the task will be ignored from the current execution completely. If a task is missing due to tagging, then you won’t see it in the playbook’s output but in case of the “when” keyword you’ll see the task as “skipped“.

All of the coming sections will show, how to manipulate the execution of a playbook using the –tags and –skip-tags command-line options.

Note
If a task is ignored due to tagging, then variables used in the task don’t need to be initialized.

Reserved tags and keywords

There are 2 reserved tags and 3 keywords, which are special and predefined. The following section will describe these specialties.

Special keywords

We can use special keywords as command line parameters for –tags and –skip-tags in conjunction with custom or reserved tags to modify the list of executed tasks.

  • tagged: all of the tagged tasks
  • untagged: all tasks without any tags
  • all: don’t care, just run all of it. This is the default.

Reserved tags

Tasks, with “always” tag will be always executed except:

  • always” is explicitly skipped
  • the task has another tag which is explicitly skipped

Tasks, with “never” tag will never be executed except:

  • never explicitly in the “–tags” list
  • the task has another tag which is explicitly requested

When we are speaking about explicitly skipping or requesting these reserved tags, they work just like any other regular tag. Regular tag, I mean the ones that you create. Just don’t forget, if you specify only one of the reserved tags, you might get a very short task list.

For running all tasks and tasks with tag “never“, it’s not enough to specify “–tags never” because you would only override the default tag (“all“), so in this case, you will run only “never” tagged tasks. Instead, you should use “–tags all,never” to run every task even the “never” tagged ones.

Demo playbook

You can download the playbook and its roles from our GitHub repository. From the tagging perspective, the functionality of the modules isn’t irrelevant thus all the tasks are just a “command” module executing echo. I used this instead of the “debug” module, as “command” always shows as changed so it triggers its handlers.

For demonstration purposes, I added the list of explicitly defined tags to the end of the name of the tasks, for example: “Include config tasks (configure)”.

All of the below commands are executed from the git root directory with the latest Ansible version (at the time of writing, it’s Ansible 2.8.).

Good2Know
If you define “#!/usr/bin/env ansible-playbook” or “#!/usr/bin/env ansible” commands as the interpreter of the playbook and make them executable, just as you would define for a bash script, then you can just run the playbook like “./playbook.yml”.

List all tags

This command can be handy if you want to know what tags are used across the whole playbook.

$ ./playbook.yml --list-tags

playbook: ./playbook.yml

  play #1 (localhost): Deploy Application 1	TAGS: [first_play]
      TASK TAGS: [always, application1, configure, firewall, first_play, install, logging, monitoring, repository, role_tag_from_play]

  play #2 (localhost): Deploy Application 2	TAGS: [second_play]
      TASK TAGS: [application2, configure, firewall, install, logging, monitoring, repository, second_play]

  play #3 (localhost): An untagged task	TAGS: []
      TASK TAGS: []

List all tasks

This command will show all the available tasks and their tags, however, the output might not be full if you use any of the “include” modules. (Details about the include modules are coming later.)

Scopes and inheritances

Although you can add tags to tasks, blocks, roles or plays, they will behave slightly differently. In the next section, I will show these differences. The irrelevant parts of the output will be omitted, so we can focus on the interesting parts.

Tasks

This is the most commonly used, all of the tasks with the specified tag will be ran, nothing special. Import and include tasks are exceptions and we’ll deal with them later.

Blocks

Blocks are used for grouping tasks together and to apply some parameters to all of them in one place. Just like in the main task file of the DEMO application roles, see below. We apply the same tag to all of the tasks, even the included and imported ones.

Ansible code snippet:

- block:
  - name: Include install tasks (install)
    include_tasks: install.yml
    tags:
      - install

  - name: Just for debugging (never, debug)
    command: echo
    tags:
      - never
      - debug

  - name: Include config tasks (configure)
    include_tasks: configure.yml
    tags:
      - configure

  tags:
    - application1

Output of the playbook:

$ ./playbook.yml

PLAY [Deploy Application 1] ******************************************************************************************

...SNIPPET...

repository : Configure repository (configure)] *****************************************************************
changed: [localhost] => (item=repo_app1)

TASK [application1 : Include install tasks (install)] ****************************************************************
included: /home/segal/projects/inframates/blog/2019/10-October/ansible_tags/roles/application1/tasks/install.yml for localhost

TASK [application1 : Install Application1 (install)] *****************************************************************
changed: [localhost]

TASK [application1 : Include config tasks (configure)] ***************************************************************
included: /home/segal/projects/inframates/blog/2019/10-October/ansible_tags/roles/application1/tasks/configure.yml for localhost

TASK [application1 : Configure Application1 (configure)] *************************************************************
changed: [localhost]

TASK [application1 : Configure Application1 without tag ()] **********************************************************
changed: [localhost]

TASK [firewall : Include config tasks ()] ****************************************************************************
included: /home/segal/projects/inframates/blog/2019/10-October/ansible_tags/roles/firewall/tasks/configure.yml for localhost

...SNIPPET...

TASK [repository : Configure repository (configure)] *****************************************************************
changed: [localhost] => (item=repo_app2)

TASK [application2 : Install Application2 (install)] *****************************************************************
changed: [localhost]

TASK [application2 : Configure Application2 ()] **********************************************************************
changed: [localhost]

TASK [firewall : Include config tasks ()] ****************************************************************************
included: /home/segal/projects/inframates/blog/2019/10-October/ansible_tags/roles/firewall/tasks/configure.yml for localhost

...SNIPPET...

With blocks, you can easily tag multiple tasks in one place as you can see above. If you have to add some new tasks later, those also will be tagged. It’s an easy way to make sure that all of your tasks are tagged and not forgotten.

Imports vs Includes

It doesn’t matter what you import or include, it can be a task, role or even a playbook, however, there are key differences between import and include.

Imports

Imports are similar to blocks. If you import something, then it will receive all the properties of the import task. The import itself won’t show up in the output of the playbooks. For example, if you tag an import task, then you also tag all the imported entities:

  • Tag an import_tasks task: you’ll tag all of the tasks from the imported file.
  • Tag an import_role task: you’ll tag every task in the imported role.
  • Tag an import_playbook: you’ll tag every role in the imported playbook.

Don’t forget: it’s recursive!

Includes

Tagging includes works just like tagging any regular task. Properties will not propagate to the included entities. Includes will show up in the output of the playbooks, just like other tasks.

  • Tag an include_tasks: you won’t tag any of the tasks from the included file.
  • Tag an include_role: you won’t tag any of the tasks from the included role.
  • Tag an include_playbook: you won’t tag any of the roles from the included playbook.

Be aware, if you don’t tag an include just the included tasks, then the included tasks won’t be executed, because they won’t be included.

Includes vs imports example

Let’s see the output of the next run:

$ ./playbook.yml --tags configure

PLAY [Deploy Application 1]

...SNIPPET...

TASK [repository : Configure repository (configure)] *****************************************************************
changed: [localhost] => (item=repo_app1)

TASK [application1 : Include config tasks (configure)] ***************************************************************
included: /home/segal/projects/inframates/blog/2019/10-October/ansible_tags/roles/application1/tasks/configure.yml for localhost

TASK [application1 : Configure Application1 (configure)] *************************************************************
changed: [localhost]

TASK [monitoring : Install monitoring packages (install)] ************************************************************
changed: [localhost]


...SNIPPET...

PLAY [Deploy Application 2]

...SNIPPET...

TASK [repository : Configure repository (configure)] *****************************************************************
changed: [localhost] => (item=repo_app2)

TASK [application2 : Configure Application2 ()] **********************************************************************
changed: [localhost]

TASK [monitoring : Configure monitoring (configure)] *****************************************************************
changed: [localhost]

...SNIPPET...

Task “Configure Application2” in “application2” role has no tags, but as you can see, it also executed, because of the tag provided to the import but on the other side none of the firewall tasks ran. “Drop firewall config” task has the “configure” tag but its include task was filtered due to the missing tag thus this task has never run.

typeprocessingDo you see it as a task in
the output of the playbook?
tag children
includedynamicruntimeyesno
importstaticpre-processednoyes

Roles

Roles listed under a play’s “roles” keyword works like “import_role” tasks.

One thing can be confusing though, if you add tags to a role listed in a play just like the “role_tag_from_play” tag, then it’s not a filter. You just tag all of the tasks in the role, NOT filtering those.

$ ./playbook.yml --tags role_tag_from_play

PLAY [Deploy Application 1] ******************************************************************************************

TASK [logging : Install logging packages (install)] ******************************************************************
changed: [localhost]

TASK [logging : Configure logging (configure)] ***********************************************************************
changed: [localhost]

TASK [monitoring : Install monitoring packages (install)] ************************************************************
changed: [localhost]

TASK [monitoring : Configure monitoring (configure)] *****************************************************************
changed: [localhost]

PLAY [Deploy Application 2] ******************************************************************************************

PLAY [An untagged task] **********************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

PLAY RECAP ***********************************************************************************************************
localhost                  : ok=5    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

We tagged only the logging role in the first play. As you see all of the logging tasks executed plus the monitoring ones because those are tagged with “always“.

Plays

Tagging a play will tag all the roles and tasks in it. If you scroll up to the output to the task list section, you can see, that a play tag was added to all of the tasks within the play.

You can try to run the following scenario if you want to check the result:

$ ./playbook.yml --tags first_play

Dependencies

Under meta, you can define some roles required to run before your role. For more details please check the official docs.

Tagging a dependency role works like tagging an “import_role” task. If you tag one, the tag applies to all of the tasks in the role. It can lead to some issues if you run your role with some tags and the dependencies are not running because of the missing tag(s) from the dependency declaration.

$ ./playbook.yml --tags install

PLAY [Deploy Application 1] ******************************************************************************************

TASK [logging : Install logging packages (install)] ******************************************************************
changed: [localhost]

TASK [repository : Configure repository (configure)] *****************************************************************
changed: [localhost] => (item=repo_app1)

TASK [application1 : Include install tasks (install)] ****************************************************************
included: /home/segal/projects/inframates/blog/2019/10-October/ansible_tags/roles/application1/tasks/install.yml for localhost

TASK [application1 : Install Application1 (install)] *****************************************************************
changed: [localhost]

TASK [monitoring : Install monitoring packages (install)] ************************************************************
changed: [localhost]

TASK [monitoring : Configure monitoring (configure)] *****************************************************************
changed: [localhost]

PLAY [Deploy Application 2] ******************************************************************************************

TASK [logging : Install logging packages (install)] ******************************************************************
changed: [localhost]

TASK [application2 : Install Application2 (install)] *****************************************************************
changed: [localhost]

TASK [monitoring : Install monitoring packages (install)] ************************************************************
changed: [localhost]

PLAY [An untagged task] **********************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

PLAY RECAP ***********************************************************************************************************
localhost                  : ok=10   changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Repository only has a task with “configure” tag so if we don’t tag the role in the dependency list then as you can see in “Deploy Application 2” play, then the role won’t execute.

Handlers

Tagging has no effect on handlers. You can tag handlers, it won’t cause any syntax error, it just makes no sense.

$ ./playbook.yml --tags first_play --skip-tags invalid_tag

PLAY [Deploy Application 1] ******************************************************************************************

...SNIPPET...

TASK [monitoring : Configure monitoring (configure)] *****************************************************************
changed: [localhost]

RUNNING HANDLER [application1 : Restart Application1 (invalid_tag)] **************************************************
changed: [localhost]

RUNNING HANDLER [firewall : Reload firewall] *************************************************************************
changed: [localhost]

PLAY [Deploy Application 2] ******************************************************************************************

PLAY [An untagged task] **********************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

PLAY RECAP ***********************************************************************************************************
localhost                  : ok=17   changed=12   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The “–skip-tags” option will be described in detail in the next section. Now it’s enough to know that with this option, you can ignore tasks. We ignored the handler’s tag, however, it was executed.

Skip tags

Now we know, how tasks got their tags, let’s see what happens if we want to skip some of them.

If you use “–skip-tags” alone, without “–tags” then it pretty the same as –tags except those tasks will be ignored witch match one of the provided tags.

–tags and –skip-tags at the same time

In this case, Ansible searches for those tasks, which has tags provided in the “–tags” option but they don’t have tags provided in the “–skip-tags”.

Use the same tag in tags and skip-tags option

If “–tags” is a subset of “–skip-tags”, then no task will be ran, except the ones which tagged as “always” and have no tags from the “skip-tags” list.

$ ./playbook.yml --tags configure --skip-tags configure

PLAY [Deploy Application 1] ******************************************************************************************

TASK [monitoring : Install monitoring packages (install)] ************************************************************
changed: [localhost]

PLAY [Deploy Application 2] ******************************************************************************************

PLAY [An untagged task] **********************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

PLAY RECAP ***********************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

“Install monitoring packages” task in the “monitoring” role is tagged as “install” explicitly and “always” in the first play at play level. This is the only task that is not tagged with “configure”, but tagged as “always” thus it executed. “Configure monitoring” is also tagged with “always“, but it’s also tagged with “configure” so it’s left out from the task execution list.

Combine –tags and –skip-tags

What if you want to run all of the tasks, which have multiple tags at the same time. For example, we want to reconfigure only application1. In this case, we need to run tasks that have “application1” and “configure” tags.

Unfortunately, Ansible doesn’t have this feature. Tags are in an OR relation and there is no way right now to change this. But we have “–skip-tags” and with an ugly little hack, we can achieve this behavior.

What if we provided all of the unwanted tags in “–skip-tags” and add the required ones into “–tags”? Well, we got a long, ugly Ansible command which runs only those tasks that we need. I hope you still remember the “–list-tags” option that shows us all of the tags. Taking advantage of the tag inheritance, we can save lots of typing.

Reconfigure full stack of application1 but not application2

$ ./playbook.yml --tags configure --skip-tags second_play

PLAY [Deploy Application 1] ******************************************************************************************

TASK [logging : Configure logging (configure)] ***********************************************************************
changed: [localhost]

TASK [repository : Configure repository (configure)] *****************************************************************
changed: [localhost] => (item=repo_app1)

TASK [application1 : Include config tasks (configure)] ***************************************************************
included: /home/otp00233272/projects/inframates/blog/2019/10-October/ansible_tags/roles/application1/tasks/configure.yml for localhost

TASK [application1 : Configure Application1 (configure)] *************************************************************
changed: [localhost]

TASK [monitoring : Install monitoring packages (install)] ************************************************************
changed: [localhost]

TASK [monitoring : Configure monitoring (configure)] *****************************************************************
changed: [localhost]

RUNNING HANDLER [application1 : Restart Application1 (invalid_tag)] **************************************************
changed: [localhost]

PLAY [Deploy Application 2] ******************************************************************************************

PLAY [An untagged task] **********************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

PLAY RECAP ***********************************************************************************************************
localhost                  : ok=8    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Everything as excepted, all of the tasks with “configure” tag from the first play and none of the tasks from the second play were executed.

Reconfigure only applocation1

$ ./playbook.yml --tags configure --skip-tags application2,firewall,install,logging,monitoring,repository

 PLAY [Deploy Application 1] 
 TASK [application1 : Include config tasks (configure)] *
 included: /home/segal/projects/inframates/blog/2019/10 - October/ansible_tags/roles/application1/tasks/configure.yml for localhost
 TASK [application1 : Configure Application1 (configure)] *
 changed: [localhost]
 RUNNING HANDLER [application1 : Restart Application1 (invalid_tag)] 
 changed: [localhost]
 PLAY [Deploy Application 2] 
 PLAY RECAP *
 localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

On the downside, we had to explicitly tell Ansible which tags should be skipped but at least we reconfigure only our application.

Summary

Summarize the above results in tables.

Inheritances

Will your sub-entities inherit the parent’s tags or not? In other words, what is the scope of a tag?

Tagging…Children’s tasks also got the tags
include(.*) taskno
import(.*) taskyes
role in playyes
play in a playbookyes
block of tasksyes
role in dependencyyes
handler taskno effect on handlers

Tag matrix

Add requirements with “–tags”

Will the tasks be executed, if they are tagged with <your tag> and –tags option filled with <option>

tag / optionalluntaggedtaggedalwaysneveryour tag
untaggedrunrunskippedskippedskippedskipped
alwaysrunrunrunrunskippedrun
neverskippedskippedskippedskippedrunskipped
your tagrunskippedrunskippedskippedrun

Skipping with “–skip-tasks”

Will the tasks executed if it’s tagged as <your tag> and –skip-tags option filled with <option>

tag / optionalluntaggedtaggedalwaysneveryour tag
untaggedskippedskippedrunrunrunrun
alwaysrunrunskippedskippedrunrun
neverskippedskippedskippedrunskippedskipped
your tagskippedrunskippedrunrunskipped

I hope in this blog post I clarified how tags Ansible tags work.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s