When working with Ansible's include_tasks
functionality, many developers encounter a common pain point: tags defined in nested task files don't automatically propagate to the parent include statement. This creates a frustrating situation where running playbooks with specific tags fails to execute the intended tasks.
# main.yml
- include_tasks: dev.yml
when: ec2_tag_env == 'dev'
# dev.yml
- name: Pull the latest image
docker_image:
name: "{{ dev_image }}"
source: pull
tags:
- container
Ansible processes tags at two distinct levels:
- During playbook parsing (where it evaluates include statements)
- During task execution (where it evaluates individual tasks)
The parser needs to know which files to process before it can see the tags inside them. This creates a chicken-and-egg problem for dynamically included tasks.
Option 1: Explicit Tag Propagation
The most straightforward approach is to duplicate tags on the include statement:
- include_tasks: dev.yml
when: ec2_tag_env == 'dev'
tags:
- container
- dev
Option 2: Using import_tasks Instead
import_tasks
behaves differently than include_tasks
because it's processed statically during playbook parsing:
- import_tasks: dev.yml
when: ec2_tag_env == 'dev'
# Tags aren't needed here because import_tasks brings them in
Option 3: Dynamic Tag Collection
For complex cases, you can use a pre-task to dynamically collect and apply tags:
- name: Set tags for included tasks
set_fact:
required_tags: "{{ (required_tags | default([])) + ['container'] }}"
when: ec2_tag_env == 'dev'
- include_tasks: dev.yml
when: ec2_tag_env == 'dev'
tags: "{{ required_tags | default(omit) }}"
- Use consistent tag naming conventions across your playbooks
- Document all available tags in a central README
- Consider using tag prefixes for different environments (dev_, prod_)
- Automate tag validation in your CI/CD pipeline
For complex environments, you might want tags to behave differently based on conditions:
- name: Include environment tasks
include_tasks: "{{ ec2_tag_env }}.yml"
tags:
- "{{ 'container' if ec2_tag_env == 'dev' else omit }}"
- "{{ 'db' if ec2_tag_env == 'prod' else omit }}"
When working with Ansible's include_tasks
, a common pain point emerges regarding tag propagation. The fundamental behavior is that:
- include_tasks: file.yml # Parent task
tags: base_tag # Only these tags are evaluated initially
# file.yml contents:
- name: Sub-task
command: do_something
tags: nested_tag # These won't be considered unless parent has them
Ansible's tag evaluation works in two phases:
- First pass determines which top-level tasks to include
- Second pass evaluates subtasks only if their parent was selected
Option 1: Tag Inheritance Pattern
Create a standardized approach for tag inheritance:
# main.yml
- include_tasks: dev.yml
when: ec2_tag_env == 'dev'
tags: always,container,deploy # All possible subtask tags
Option 2: Dynamic Includes
Use variables to control inclusion:
# playbook.yml
vars:
include_container_tasks: "{{ 'container' in ansible_run_tags }}"
# main.yml
- include_tasks: dev.yml
when: ec2_tag_env == 'dev' and (include_container_tasks or not ansible_run_tags)
Option 3: Tagged Blocks
Restructure using block-level tags:
# dev.yml
- block:
- name: Pull the latest image
docker_image:
name: "{{ dev_image }}"
source: pull
tags:
- container
For complex playbooks, consider:
- Maintaining a central tag registry (e.g.,
group_vars/tags.yml
) - Using CI/CD to validate tag consistency
- Documenting tag requirements for included files
# Example registry
common_tags:
container_ops: [container, docker, image]
db_ops: [database, mysql, migration]
Create a filter to auto-collect tags:
# filter_plugins/tag_utils.py
def collect_tags(file_content):
# Parse YAML and extract all unique tags
...
# main.yml
- include_tasks: dev.yml
tags: "{{ dev.yml | collect_tags }}"