Course outline

Ansible / Intermediate: reuse, secrets, and scoped validation / Lesson 9

Package reusable behavior as role interfaces

Design role inputs, defaults, tasks, handlers, dependencies, and static versus dynamic reuse with explicit validation.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain role layout and public input ownership.
  • Distinguish imports from dynamic includes.
  • Validate role arguments and prevent hidden cross-role coupling.

A role is a reusable contract, not only a directory

Roles organize tasks, handlers, defaults, variables, files, templates, and metadata through a known layout. Reuse becomes reliable when the role declares required inputs, optional defaults, supported platforms, expected effects, and acceptance requirements. Avoid a role that silently consumes generic variables from unrelated playbooks. Prefix public inputs and distinguish values the caller may override from internal implementation state. A role should not become a bag of every task an organization has ever needed. Cohesive responsibility and a small input schema make it easier to review scope, version changes, and failure behavior.

Defaults and role variables have different override intent

Role defaults normally provide low-precedence customization points, while role vars can override sources a caller expected to control. Choose placement from ownership, not convenience. Validate required keys and types rather than giving every input a harmless-looking default. Dependency roles can introduce additional tasks, variables, and handlers, so the visible role name alone does not describe the full execution graph. Document dependencies and their versions. Repeated role invocation and duplicate handling also have documented rules; do not assume a dependency executes once per call or that repeating a role is always equivalent to repeating its task list.

Static import and dynamic include evaluate at different times

Static imports expand reusable content during playbook processing, while dynamic includes bring content into the runtime flow under their rules. This affects when variables and conditions are evaluated, which tasks are visible to listing/tag workflows, and how inherited keywords apply. A condition on imported content can apply to individual expanded tasks, while an include condition controls whether the inclusion occurs; details must be checked for the chosen construct. Choose one from the intended execution contract rather than replacing names mechanically. Test tags, variables, and handler availability in the actual reuse context.

Make a dynamic role call’s parameters visible

A short include_role task can express which role is requested and which data is supplied. It does not prove the role exists, its dependency chain is safe, or its arguments are supported. The following fictional lab role is illustrative and is never loaded. A real project should use documented role argument validation where available and maintain an interface changelog. Keep validation independent from host-changing tasks so malformed input is rejected before effects. Public variable exposure and tag inheritance should be chosen explicitly using supported options rather than inferred from one observed run.

yaml Illustrative role invocation with namespaced inputs
- name: Illustrate a role interface
  hosts: lab_nodes
  gather_facts: false
  tasks:
    - name: Request the fictional report role
      ansible.builtin.include_role:
        name: lab_report
      vars:
        lab_report_settings:
          workers: 2
          mode: review

lab_report is a fictional role used to explain the interface. YAML parsing does not resolve or execute it, install dependencies, or contact a target.

Test the data interface separately from role execution

An offline schema assertion can check required keys and type constraints in a synthetic input object. It is not the role argument-spec engine and cannot validate plugin loading or managed-node behavior. Use it as a small illustration of what the real role must require. In a later authorized disposable test, exercise valid input, missing input, unsupported platform, first convergence, second-run stability, and recovery after partial failure. Keep role tests focused on public behavior rather than internal task counts, so refactoring a well-defined implementation does not break an otherwise valid contract.

python Check a declared role-input contract offline
settings = {'workers': 2, 'mode': 'review'}
required = {'workers', 'mode'}
assert required <= settings.keys()
assert type(settings['workers']) is int and settings['workers'] > 0
assert settings['mode'] in {'review', 'archive'}
print('synthetic_role_contract=valid')
Expected output
synthetic_role_contract=valid

The assertions apply only to this Python fixture. They do not invoke Ansible role validation, discover a role directory, or execute tasks.

Common errors and fixes

A role consumes unrelated generic variables

Why it happens
Its public input boundary was never defined.
How to fix it
Introduce namespaced inputs and documented defaults.
Prevent it next time
Test the role in more than one caller context.

include and import are treated as synonyms

Why it happens
Processing time and inheritance behavior were ignored.
How to fix it
Choose and test the intended reuse contract.
Prevent it next time
Include tag and conditional cases in reuse tests.

A role name hides unreviewed dependencies

Why it happens
The transitive execution graph was omitted.
How to fix it
Inventory dependency roles, versions, and effects.
Prevent it next time
Review dependency changes as scope changes.

Check your understanding

1. Why should an override-friendly input usually live in role defaults rather than an arbitrary high-precedence source?

Show worked solution

The location should reflect caller ownership. A low-precedence default lets an explicit project input customize behavior under documented rules. A stronger role variable can unexpectedly defeat that intention and create hidden coupling.

2. What does passing an offline role schema test leave unproved?

Show worked solution

It leaves role loading, dependency resolution, templating, module compatibility, target effects, idempotence, and application health unproved. Report the data-contract check accurately and plan those additional checks only in a separately authorized environment.

Key takeaways

  • Design roles as small versioned public interfaces.
  • Review timing, inheritance, and transitive dependencies explicitly.

Official references