If there is a particular feature in ansible that I dont use frequently enough to understand it, yet always feel I should use it more, that feature would be tags.

Recently on a project at work, I thought that tags might be a good solution to a problem I was presented with. Since I wasn’t terribly familiar with their use, I hopped on over to the tags documentation on the ansible site.

The docs are rather sparse though, and only give the most brief of introductions to the feature. I’d like this post to serve as some added documentation about how placing tags at different points in your playbooks ultimately affect what is run.

Let’s take a look!

The first thing that you’re going to need to be aware of is the --list-tags argument to the ansible-playbook command. We’re going to use this all over the place to determine if our tags are placed properly. We don’t want too many, or too few, things to be run.

I found in my usage of tags that I would often over run tasks instead of under run.

So let’s take a look at my overly long ansible command

root@hype:~# /usr/local/bin/ansible-playbook
    --inventory-file notahost,
    -vvvv
    caphrim.yaml

To this command I am just going to be adding a single argument

--list-tags

Right. Now, let’s take a look at a section of my overly complicated playbook.

- name: Play with tags
  hosts: localhost
  gather_facts: false
  connection: local

  tags:
      - success

  tasks:
      - name: Going to play
        debug: msg="In the sandbox"

- name: Play without tags
  hosts: localhost
  gather_facts: false
  connection: local

So I have two plays above; one with tags and one without tags. Let’s look to see what happens when I use the --list-tags argument on the above plays.

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --list-tags

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: [success]
    TASK TAGS: [success]

  play #2 (Play without tags):	TAGS: []
    TASK TAGS: []

root@hype:~#

The above output is actually a lot more informative that I initially gave it credit for. It wasn’t immediately obvious what was happening though.

For the impatient, the tldr of the output is that tags are inherited from their parents; whether that be plays, roles, whatever is higher up in the stack.

Let’s break that down a bit more.

In the play #1 above, we see that a tag, success, is assigned to that play. We also have a task in play #1 though and, curiously, we also see that that task has a tag.

But wait, I didn’t assign that task a tag. If I had, it might look like this

tasks:
    - name: Going to play
      debug: msg="In the sandbox"
      tags:
          - mytag

Well, let’s do just that. Our result should then list mytag as assigned to that task

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --list-tags

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: [success]
    TASK TAGS: [mytag, success]

  play #2 (Play without tags):	TAGS: []
    TASK TAGS: []

root@hype:~#

And whatd’ya know it…wait…it still has that success tag. What happened?

In ansible, the tags that you define, how do I say, “further up the stack”, are inherited by things “further down the stack”. The “stack” in this case starts at the top (with the play) and works its way down touching roles, tasks, and other things along the way.

Because we defined a tag at the play level called success, that tag was then further applied to everything else in the play. Everything.

Furthermore, tags are AND’d together; they do not replace one-another. So when I went a step further and added a tag to my individual task, it would be wrong to then assume I would “skip” that task if I only specified the following

--tags success

In ansible, that would include my task above because it is a part of the play which is tagged success.

Let’s get more convoluted.

Suppose we have a role and inside of that role, we tag a task. Our role can be something silly, like this

# roles/solution-common/tasks/main.yaml
---

- name: Include common things
  debug:
      msg: "Including common"

We’ll assign that role to play #1 from earlier.

...

roles:
    - solution-common

...

Ok, what happens when we run the playbook with --list-tags now?

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --list-tags

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: [success]
    TASK TAGS: [mytag, success]

  play #2 (Play without tags):	TAGS: []
    TASK TAGS: []

root@hype:~#

Looks pretty much identical. What if we move that role to play #2? The output?

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --list-tags

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: [success]
    TASK TAGS: [mytag, success]

  play #2 (Play without tags):	TAGS: []
    TASK TAGS: []

root@hype:~#

Still identical. Ok, what if we add a tag to that task in the role? I’ll add the tag asdf to the task in the role. What do we get?

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --list-tags

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: [success]
    TASK TAGS: [mytag, success]

  play #2 (Play without tags):	TAGS: []
    TASK TAGS: [asdf]

root@hype:~#

Oooo a change. We can see that the asdf tag shows up in the --list-tags output. Can we see what tasks are tagged with what though? Yes, with the --list-tasks argument to ansible-playbook.

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --list-tasks

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: [success]
    Going to play	TAGS: [mytag, success]

  play #2 (Play without tags):	TAGS: []
    Include common solution vars	TAGS: [asdf]

root@hype:~#

Using this newfound --list-tasks argument, let’s take a step back and look at the tags that are applied when we remove the asdf tag from our role task and add that role back to play #1

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --list-tasks

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: [success]
    Include common solution vars	TAGS: [success]
    Going to play	TAGS: [mytag, success]

  play #2 (Play without tags):	TAGS: []

root@hype:~#

I think this is where I became initially confused until I realized that things were inherited and AND’d together. In the first task of play #1, we see that the success tag has been applied to the task in the role.

We did not actually tag this task in this role though.

So where can this ANDing of tags get you into situations? When you assign them to plays and then expect to override them in tasks. For example.

Here is our role

---

- name: Successful task
  debug:
      msg: "Success in life"
  delegate_to: localhost
  tags:
      - success

- name: Failed task
  debug:
      msg: "Failure Will Robinson!"
  delegate_to: localhost
  tags:
      - failure

And we still have our play looking like this

---

- name: Play with tags
  hosts: localhost
  gather_facts: false
  connection: local

  roles:
      - solution-common

  tags:
      - success

  tasks:
      - name: Going to play
        debug: msg="In the sandbox"
        tags:
            - mytag

- name: Play without tags
  hosts: localhost
  gather_facts: false
  connection: local

Now we want to run our ansible playbook and only run the stuff tagged with success; we don’t want that task tagged with failure to run.

When we list our tags for the plays, what do we see?

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --tags success --list-tasks

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: [success]
    Successful task	TAGS: [success]
    Failed task	TAGS: [failure, success]
    Going to play	TAGS: [mytag, success]

  play #2 (Play without tags):	TAGS: []

root@hype:~#

Well that’s weird. Our failed task is also tagged as success. When we run this playbook, we will end up running both the success and failed tagged tasks even though we specifically only asked for the success tagged tasks.

Remember that tags are inherited from parent “stuff”. Because our role is inside of a play that itself is tagged as success, so-to will everything in our role also be tagged with success; even the failed stuff.

To rememdy this situation, the easiest thing is to just always be more specific in your tagging endeavors. To “fix” our example above, just remove the success tag from the play.

---

- name: Play with tags
  hosts: localhost
  gather_facts: false
  connection: local

  roles:
      - solution-common

  tasks:
      - name: Going to play
        debug: msg="In the sandbox"
        tags:
            - mytag

- name: Play without tags
  hosts: localhost
  gather_facts: false
  connection: local

This has the desired effect

root@hype:~# ansible-playbook -i notahost, -vvvv caphrim.yaml --tags success --list-tasks

playbook: caphrim.yaml

  play #1 (Play with tags):	TAGS: []
    Successful task	TAGS: [success]

  play #2 (Play without tags):	TAGS: []

root@hype:~#

In summary, the tags feature of ansible is really neat insofar as it allows you to cherry-pick out tasks you want to run to prevent yoursef from repeating…yourself.

The existing documentation is a tad sparse in terms of examples, but once you understand the nuances of the tagging system, it is much easier to lay out your tags to most effectively take advantage of it.