Ansible Role Tag Inheritance Issue: Why Included Tasks Ignore Applied Tags?


8 views

When working with Ansible's include_role directive, many administrators encounter unexpected behavior with tag inheritance. The core issue manifests when tags applied at the playbook level don't properly propagate to tasks within included roles.

In our example scenario, the following occurs:

- hosts: all
  tasks:
    - name: Install Icinga2 on Windows
      include_role:
        name: my.icinga2.role
        apply:
          tags:
            - install-icinga2

Despite specifying install-icinga2 tag, tasks with other tags (like install-icinga2-stack) still execute. This happens because:

  • Tags from the apply block only filter which roles to include, not which tasks within roles
  • Once a role is included, all its tasks execute unless filtered by other conditions

The current implementation follows these rules:

1. Role-level tags filter whether to include the role
2. Task tags within included roles work independently
3. Conditional statements (when) override tag filtering

Here are three approaches to achieve the desired behavior:

1. Explicit Tag Filtering

Modify the playbook to explicitly specify which tags to run:

ansible-playbook plays/icinga2-client-win.yml \
  --tags install-icinga2 \
  --skip-tags install-icinga2-stack

2. Restructure Role Tasks

Reorganize the role to use proper tag inheritance:

# tasks/main.yml
- include_tasks: common.yml
  tags: always

- include_tasks: install.yml
  tags: install-icinga2

- include_tasks: configure.yml
  tags: install-icinga2-stack

3. Combine Tags with Conditionals

For more complex scenarios, combine tags with explicit conditionals:

- include_tasks: configure.yml
  tags: install-icinga2-stack
  when: "'install-icinga2-stack' in ansible_run_tags"

For tasks like ido-install.yml where you want both tag and conditional control:

- include_tasks: ido-install.yml
  when: 
    - icinga2_ido_enable == true
    - "'install-icinga2-ido' in ansible_run_tags"
  tags: 
    - install-icinga2-stack 
    - install-icinga2-ido
  • Document all tags used in role metadata (meta/main.yml)
  • Use consistent tag naming conventions
  • Test tag behavior with --list-tasks before execution
  • Consider using block with tags for logical grouping

For complex scenarios, you can create a custom filter plugin:

# filter_plugins/tag_utils.py
def filter_by_tags(tasks, include_tags, exclude_tags):
    return [t for t in tasks if 
            (not t.tags or set(t.tags) & set(include_tags)) and
            not (set(t.tags) & set(exclude_tags))]

class FilterModule(object):
    def filters(self):
        return {'filter_by_tags': filter_by_tags}

When working with Ansible's include_role directive, many users encounter unexpected behavior with tag propagation. The core problem manifests when:

  1. Tags defined at the playbook level don't properly filter tasks within included roles
  2. Conditional statements (when:) override tag-based filtering
  3. Tasks with multiple tags behave differently than expected

In your specific case, two distinct issues are occurring simultaneously:

# Playbook level tag application
- include_role:
    name: my.icinga2.role
    apply:
      tags:
        - install-icinga2

# Role tasks with multiple tags
- include_tasks: configure.yml
  tags: ['install-icinga2-stack']  # Still gets executed

Ansible's tag filtering operates differently between playbook-level tags and role-internal tags. The apply parameter in include_role only affects tasks directly under the role's main.yml, not nested includes.

Here are three approaches to achieve proper tag filtering:

1. Explicit Tag Inheritance

- include_role:
    name: my.icinga2.role
    tasks_from: install.yml
    apply:
      tags: install-icinga2

2. Dynamic Tag Management

- name: Control task execution
  include_tasks: "{{ item }}"
  loop:
    - vars.yml
    - install.yml
    - ido-install.yml
    - configure.yml
  tags: 
    - "{{ (item == 'ido-install.yml' and icinga2_ido_enable) | ternary('install-icinga2-ido','skip') }}"
    - "{{ (item == 'configure.yml') | ternary('skip','install-icinga2') }}"

3. Role Restructuring Approach

Create separate entry points in your role:

# roles/my.icinga2.role/tasks/main.yml
- include_tasks: "{{ include_tasks_from | default('default.yml') }}"
  tags: "{{ include_tags | default(omit) }}"

# Then call from playbook:
- include_role:
    name: my.icinga2.role
    vars:
      include_tasks_from: install.yml
      include_tags: install-icinga2

For the ido-install.yml issue, combine conditionals and tags explicitly:

- include_tasks: ido-install.yml
  when: 
    - icinga2_ido_enable | default(false)
    - "'install-icinga2-ido' in ansible_run_tags or 'install-icinga2' in ansible_run_tags"

This ensures the task only runs when BOTH the condition is true AND the proper tag is specified.

  • Use consistent tag naming conventions (e.g., prefix with role name)
  • Avoid tag overlap between different functional areas
  • Document tag usage in role's README or meta/main.yml
  • Test tag filtering with --list-tasks before execution
ansible-playbook playbook.yml --list-tasks --tags install-icinga2