ansible,  Konzepte

Mastering Dynamic Task Includes in Ansible

One of the best ways (and easy) to keep your Ansible playbooks clean, modular, and DRY (Don’t Repeat Yourself) is by using dynamic task includes. Instead of writing massive, conditional playbooks with dozens of when statements, you can let your data drive your execution.

The core idea is beautifully simple:

YAML
- name: Execute dynamically targeted action
  ansible.builtin.include_tasks: "{{ action }}.yml"

Depending on the value of the action variable (e.g., install, configure, or backup), Ansible will look for and execute install.yml, configure.yml, or backup.yml on the fly.

Why Use This Pattern?

  • Cleaner Codebase: Instead of one massive main.yml packed with tasks, you break your logic into small, focused files.
  • Role Extensibility: You can create “plugin-style” architectures. Want to support a new operating system or application action? Just drop a new .yml file into the tasks directory.
  • Reduced Conditional Noise: You completely avoid appending when: action == 'install' to fifteen different tasks.

⚠️ The Gotchas (What to Watch Out For)

While highly effective, include_tasks is evaluated at runtime, which introduces a couple of architectural quirks you need to design around.

1. The Missing File Trap

If action is set to restore, but restore.yml doesn’t exist, Ansible will fail mid-playbook execution.

The Fix: Use Ansible’s query with the first_found plugin to handle fallbacks or graceful skips.

YAML
- name: Include action tasks safely
  ansible.builtin.include_tasks: "{{ item }}"
  with_first_found:
    - files:
        - "{{ action }}.yml"
        - "default_action.yml" # Fallback file
      skip: true # Or skip entirely if neither exists

Please see my follow-up post Deep Dive Missing File Trap. “Dynamic task includes” is a classic, incredibly powerful Ansible pattern—but it’s also one that comes with a few hidden gotchas that can absolutely ruin a deployment if you aren’t prepared for them.

2. Handlers and Tags Limitations

  • You cannot easily trigger handlers defined inside a dynamically included file from outside of it.
  • Running a playbook with --tags might skip your include_tasks entirely unless the include task itself carries the tag.

A Real-World Example: Multi-OS Configurations

A stellar use case for this is handling OS-specific setup without cluttering your main tasks file.

tasks/main.yml

YAML
- name: Load OS-specific configuration tasks
  ansible.builtin.include_tasks: "{{ ansible_facts['os_family'] | lower }}.yml"

tasks/debian.yml

YAML
- name: Install Debian/Ubuntu packages
  ansible.builtin.apt:
    name: Apache2
    state: present

tasks/redhat.yml

YAML
- name: Install RedHat/CentOS packages
  ansible.builtin.yum:
    name: httpd
    state: present
Quick Tip on Syntax: Notice the underscore in include_tasks. Older versions of Ansible allowed dashes (include-tasks), but modern Ansible strictly standardizes on underscores (include_tasks). Always use underscores to ensure your playbooks are future-proof!

Leave a Reply

Your email address will not be published. Required fields are marked *