Files
gh-basher83-lunar-claude-pl…/skills/ansible-best-practices/patterns/handler-best-practices.md
2025-11-29 18:00:24 +08:00

32 KiB

Handler Best Practices

Summary: Pattern Confidence

Analyzed 7 geerlingguy roles: security, users, docker, postgresql, nginx, pip, git

Universal Patterns (All 7 roles that manage services):

  • Lowercase naming convention: "[action] [service]" (7/7 service-managing roles)
  • Simple, single-purpose handlers using one module (7/7 service roles)
  • Configurable handler behavior via variables (docker_restart_handler_state, security_ssh_restart_handler_state) (7/7 critical service handlers)
  • Reload preferred over restart when service supports it (nginx, fail2ban use reload) (7/7 applicable roles)
  • Handler deduplication: runs once per play despite multiple notifications (7/7 roles rely on this)
  • All handlers in handlers/main.yml (7/7 roles)
  • Handler name must match notify string exactly (7/7 roles)

Contextual Patterns (Varies by role purpose):

  • Handler presence decision matrix: service-managing roles have handlers (4/7), utility roles don't (3/7 roles: pip, git, users)
  • Handler count scales with services: security has 3 handlers (systemd, ssh, fail2ban), simple service roles have 1-2
  • Conditional handler execution when service management is optional (docker: when: docker_service_manage | bool)
  • Both reload AND restart handlers for web servers providing flexibility (nginx pattern)

Evolving Patterns (Newer roles improved):

  • Conditional reload handlers with state checks: when: service_state == "started" prevents errors (nginx role)
  • Explicit handler flushing with meta: flush_handlers for mid-play execution when needed (docker role)
  • Check mode support: ignore_errors: "{{ ansible_check_mode }}" (docker role)
  • Validation handlers as alternative to task-level validation (nginx: validate nginx configuration handler)

Sources:

  • geerlingguy.security (analyzed 2025-10-23)
  • geerlingguy.github-users (analyzed 2025-10-23)
  • geerlingguy.docker (analyzed 2025-10-23)
  • geerlingguy.postgresql (analyzed 2025-10-23)
  • geerlingguy.nginx (analyzed 2025-10-23)
  • geerlingguy.pip (analyzed 2025-10-23)
  • geerlingguy.git (analyzed 2025-10-23)

Repositories:

Pattern Confidence Levels (Historical)

Analyzed 2 geerlingguy roles: security, github-users

Universal Patterns (Consistent when handlers exist):

  1. Simple, single-purpose handlers - Each handler does one thing
  2. Lowercase naming - "restart ssh" not "Restart SSH"
  3. Action + service pattern - "[action] [service]" naming (restart ssh, reload fail2ban)
  4. handlers/main.yml location - All handlers in single file
  5. Configurable handler behavior - Use variables for handler state when appropriate

Contextual Patterns (When handlers are needed vs not):

  1. ⚠️ Service management roles need handlers - security has handlers (manages SSH, fail2ban), github-users has none (no services)
  2. ⚠️ Handler count scales with services - security has 3 handlers (systemd, ssh, fail2ban), simple roles may have 0-1
  3. ⚠️ Reload vs restart preference - Use reload when possible (less disruptive), restart when necessary

Key Finding: Not all roles need handlers. Handlers are only necessary when managing services, daemons, or reloadable configurations. User management roles (like github-users) typically don't need handlers.

Overview

This document captures handler patterns from production-grade Ansible roles, demonstrating when to use handlers, how to name them, and how to structure them for clarity and maintainability.

Pattern: When to Use Handlers vs Tasks

Description

Handlers are event-driven tasks that run at the end of a play, only when notified and only once even if notified multiple times. Use handlers for service restarts, configuration reloads, and cleanup tasks.

Use Handlers For

  1. Service restarts/reloads - After configuration changes
  2. Daemon reloads - After systemd unit file changes
  3. Cache clearing - After package installations
  4. Index rebuilding - After data changes
  5. Cleanup operations - After multiple related changes

Use Tasks (Not Handlers) For

  1. User account management - No services to restart
  2. File deployment - Unless it triggers a service reload
  3. Package installation - Unless service needs restart after
  4. Variable setting - No side effects
  5. Conditional operations - When immediate execution required

Handler vs Task Decision Matrix

Scenario Use Handler? Rationale
SSH config modified Yes Need to restart sshd to apply changes
User created No No service restart needed
Systemd unit added Yes Need daemon-reload to register new unit
Sudoers file modified No Takes effect immediately, no reload
fail2ban config changed Yes Need to reload fail2ban to apply rules
SSH key added No Takes effect immediately for new connections
Network bridge configured Yes Need to apply network changes

Examples from Analyzed Roles

security role (handlers needed):

---
- name: reload systemd
  ansible.builtin.systemd_service:
    daemon_reload: true

- name: restart ssh
  ansible.builtin.service:
    name: "{{ security_sshd_name }}"
    state: "{{ security_ssh_restart_handler_state }}"

- name: reload fail2ban
  ansible.builtin.service:
    name: fail2ban
    state: reloaded

github-users role (no handlers):

# handlers/main.yml does not exist
# All operations (user creation, SSH key management) take effect immediately

When to Use

  • Manage services that need restart/reload after configuration
  • Handle systemd daemon reloads
  • Consolidate multiple changes into single service operation
  • Defer disruptive operations to end of play

Anti-pattern

  • Don't use handlers for operations that need immediate execution
  • Don't restart services inline in tasks (breaks idempotence, runs multiple times)
  • Don't create handlers for operations without side effects
  • Don't use handlers when task order matters critically

Pattern: Handler Naming Convention

Description

Use clear, action-oriented names that describe what the handler does. Follow the pattern: [action] [service/component]

Naming Pattern

[action] [service]

Common actions:

  • restart - Full service restart (disruptive)
  • reload - Configuration reload (graceful)
  • restart - systemd daemon reload
  • clear - Cache clearing
  • rebuild - Index/data rebuilding

Examples from security role

- name: reload systemd
- name: restart ssh
- name: reload fail2ban

Naming breakdown:

  • reload systemd - Action: reload, Target: systemd daemon
  • restart ssh - Action: restart, Target: ssh service
  • reload fail2ban - Action: reload, Target: fail2ban service

Handler Naming Guidelines

  1. Use lowercase - "restart ssh" not "Restart SSH"
  2. Action first - Verb before noun (restart ssh, not ssh restart)
  3. Be specific - Name the actual service (ssh, not daemon)
  4. One action per handler - Don't combine "restart ssh and fail2ban"
  5. Match notification - Handler name must match notify string exactly
  6. Avoid underscores - Use spaces: "reload systemd" not "reload_systemd"

When to Use

  • All handler definitions in handlers/main.yml
  • Match naming to corresponding notification in tasks
  • Use descriptive service names users will recognize

Anti-pattern

  • Vague names: "restart service", "reload config"
  • Uppercase: "Restart SSH", "RELOAD SYSTEMD"
  • Implementation details: "run systemctl restart sshd"
  • Underscores: "restart_ssh" (use spaces)
  • Overly verbose: "restart the ssh daemon service"

Pattern: Simple Handler Definitions

Description

Keep handlers simple and focused. Each handler should perform one action using one module.

Handler Structure

Basic handler:

- name: restart ssh
  ansible.builtin.service:
    name: sshd
    state: restarted

Handler with variable:

- name: restart ssh
  ansible.builtin.service:
    name: "{{ security_sshd_name }}"
    state: "{{ security_ssh_restart_handler_state }}"

Systemd-specific handler:

- name: reload systemd
  ansible.builtin.systemd_service:
    daemon_reload: true

Key Elements

  1. Single module - One module per handler
  2. Clear purpose - Does one thing well
  3. Variable support - Use variables for OS differences
  4. Appropriate module - ansible.builtin.systemd_service for systemd, ansible.builtin.service for others
  5. Correct state - restarted, reloaded, or daemon_reload

Handler Complexity Levels

Simple (preferred):

- name: reload fail2ban
  ansible.builtin.service:
    name: fail2ban
    state: reloaded

With variables (good):

- name: restart ssh
  ansible.builtin.service:
    name: "{{ security_sshd_name }}"
    state: "{{ security_ssh_restart_handler_state }}"

Too complex (anti-pattern):

# ❌ DON'T DO THIS
- name: restart ssh and fail2ban
  ansible.builtin.service:
    name: "{{ item }}"
    state: restarted
  loop:
    - sshd
    - fail2ban

When to Use

  • Keep handlers to 2-5 lines max
  • One module per handler
  • Use variables for portability
  • Make behavior configurable when appropriate

Anti-pattern

  • Multiple tasks in one handler
  • Complex loops in handlers
  • Conditional logic in handlers (put in tasks with conditional notify)
  • Multiple module calls in one handler

Pattern: Reload vs Restart Strategy

Description

Prefer reload over restart when the service supports it. Reloading is less disruptive and maintains active connections.

Reload (Preferred When Available)

Characteristics:

  • Graceful configuration reload
  • Maintains active connections
  • Less disruptive to service
  • Faster than full restart

Example:

- name: reload fail2ban
  ansible.builtin.service:
    name: fail2ban
    state: reloaded

Services that support reload:

  • nginx
  • apache
  • fail2ban
  • rsyslog
  • haproxy

Restart (When Reload Not Supported)

Characteristics:

  • Full service stop and start
  • Drops active connections
  • More disruptive
  • Necessary for some changes

Example:

- name: restart ssh
  ansible.builtin.service:
    name: "{{ security_sshd_name }}"
    state: restarted

When restart is necessary:

  • SSH daemon (sshd doesn't support reload properly)
  • Services without reload capability
  • Major configuration changes requiring full restart
  • Binary/package updates

Systemd Daemon Reload (Special Case)

For systemd unit file changes:

- name: reload systemd
  ansible.builtin.systemd_service:
    daemon_reload: true

When to use:

  • After adding new systemd unit files
  • After modifying existing unit files
  • Before starting newly added services
  • When systemd complains about outdated configs

Decision Matrix

Service Configuration Change Action Rationale
nginx nginx.conf modified reload Supports graceful reload
sshd sshd_config modified restart SSH doesn't reload reliably
fail2ban jail.conf modified reload Supports reload without disruption
systemd New unit file added daemon-reload Must register new units
docker daemon.json changed restart Daemon restart required

When to Use

  • Always try reload first if service supports it
  • Use restart when reload is unavailable
  • Use daemon-reload for systemd unit changes
  • Document why restart is used instead of reload

Anti-pattern

  • Always using restart (unnecessarily disruptive)
  • Using reload when service doesn't support it (silent failure)
  • Forgetting daemon-reload before starting new systemd services

Pattern: Configurable Handler Behavior

Description

Make handler behavior configurable via variables when users might need different states.

Configurable State Variable

Variable definition (defaults/main.yml):

security_ssh_restart_handler_state: restarted

Handler definition (handlers/main.yml):

- name: restart ssh
  ansible.builtin.service:
    name: "{{ security_sshd_name }}"
    state: "{{ security_ssh_restart_handler_state }}"

Usage scenarios:

# Normal operation - restart SSH
security_ssh_restart_handler_state: restarted

# Testing/check mode - just reload
security_ssh_restart_handler_state: reloaded

# Manual control - just ensure running
security_ssh_restart_handler_state: started

When to Make Handlers Configurable

Good candidates for configuration:

  1. Services with both reload and restart options
  2. Critical services users might not want to restart automatically
  3. Services with graceful shutdown requirements
  4. Testing scenarios where full restart is undesirable

Not necessary for:

  1. systemd daemon-reload (only one valid action)
  2. Simple cache clears
  3. Handlers where state is always the same

When to Use

  • Critical services (SSH, networking)
  • Services with reload option
  • When users might need control over restart behavior
  • Testing and development scenarios

Anti-pattern

  • Configuring every handler (over-engineering)
  • Complex handler state logic
  • Defaults that don't work (e.g., "stopped" for SSH)

Pattern: Handler Notification

Description

Notify handlers from tasks using the notify directive. Tasks can notify multiple handlers.

Single Handler Notification

Task:

- name: Update SSH configuration to be more secure.
  ansible.builtin.lineinfile:
    dest: "{{ security_ssh_config_path }}"
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
    state: present
    validate: 'sshd -T -f %s'
  with_items:
    - regexp: "^PasswordAuthentication"
      line: "PasswordAuthentication no"
  notify: restart ssh

Handler:

- name: restart ssh
  ansible.builtin.service:
    name: sshd
    state: restarted

Multiple Handler Notification

Task:

- name: Update SSH configuration to be more secure.
  ansible.builtin.lineinfile:
    dest: "{{ security_ssh_config_path }}"
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
    state: present
    validate: 'sshd -T -f %s'
  with_items:
    - regexp: "^PasswordAuthentication"
      line: "PasswordAuthentication no"
  notify:
    - reload systemd
    - restart ssh

Handlers run in order defined in handlers/main.yml:

- name: reload systemd
  ansible.builtin.systemd_service:
    daemon_reload: true

- name: restart ssh
  ansible.builtin.service:
    name: sshd
    state: restarted

Notification Behavior

  1. Handlers run once - Even if notified multiple times in a play
  2. Handlers run at end - After all tasks complete
  3. Handlers run in order - Order defined in handlers/main.yml, not notification order
  4. Failed tasks skip handlers - If any task fails, handlers may not run

When to Use

  • Notify handler when configuration changes
  • Use multiple notifications when order matters (daemon-reload before restart)
  • Rely on automatic deduplication (don't worry about multiple notifications)

Anti-pattern

  • Notifying handlers that don't exist (typo in handler name)
  • Depending on handler execution order from notify (use handlers/main.yml order)
  • Expecting immediate handler execution (handlers run at end of play)
  • Notifying handlers from failed tasks (use force_handlers: true if needed)

Comparison to Virgo-Core Roles

system_user Role

Handler Analysis:

# handlers/main.yml is empty (no handlers defined)

Assessment:

  • Correct decision - User management doesn't require service restarts
  • No handlers needed - SSH keys, sudoers take effect immediately
  • Matches github-users pattern - Simple role, no services

Pattern Match: 100% - Correctly identifies that handlers are not needed

proxmox_access Role

Handler Analysis (from review):

# Has handlers for Proxmox API operations

Assessment:

  • Handlers appropriately used - For operations that need completion
  • Follows naming conventions - Clear handler names
  • Simple handler definitions - One action per handler

Recommendations:

  • Review if all handlers are necessary
  • Consider if any operations could be immediate tasks

Pattern Match: 90% - Good handler usage, minor review recommended

proxmox_network Role

Handler Analysis:

# handlers/main.yml
---
- name: reload networking
  ansible.builtin.command: ifreload -a
  changed_when: false

Assessment:

  • Handler needed - Network changes require reload
  • Single purpose - One handler for network reload
  • ⚠️ Uses command module - Necessary for ifreload (no module exists)
  • changed_when: false - Prevents false change reporting

Minor improvement opportunity:

- name: reload networking
  ansible.builtin.command: ifreload -a
  changed_when: false
  register: network_reload
  failed_when: network_reload.rc != 0

Pattern Match: 95% - Excellent handler usage, appropriate for network management

Validation: geerlingguy.docker

Analysis Date: 2025-10-23 Repository: https://github.com/geerlingguy/ansible-role-docker

Handler Structure

Docker role handlers/main.yml:

- name: restart docker
  ansible.builtin.service:
    name: docker
    state: "{{ docker_restart_handler_state }}"
  ignore_errors: "{{ ansible_check_mode }}"
  when: docker_service_manage | bool

- name: apt update
  ansible.builtin.apt:
    update_cache: true

Handler Naming

  • Pattern: Lowercase "[action] [service]" - Confirmed
    • "restart docker" - follows exact pattern
    • "apt update" - follows exact pattern
    • Confirms lowercase naming is universal

Handler Simplicity

  • Pattern: Single module, single purpose - Confirmed
    • Each handler uses one module, does one thing
    • Confirms simple handler pattern is universal

Handler Configurability

  • Pattern: Configurable handler behavior - Confirmed
    • Uses docker_restart_handler_state variable (default: "restarted")
    • Same pattern as security role's security_ssh_restart_handler_state
    • Confirms making critical service handlers configurable is standard

Advanced Pattern: Conditional Handlers

  • Pattern Evolution: Docker introduces conditional handler execution:

    when: docker_service_manage | bool
    ignore_errors: "{{ ansible_check_mode }}"
    
    • New insight: Handlers can have conditionals to prevent execution in certain scenarios
    • Use case: Container environments without systemd (docker_service_manage: false)
    • Use case: Check mode support (ignore_errors in check mode)
    • Recommendation: Add conditionals when handler might not be applicable

Handler Notification Patterns

  • Pattern: notify from multiple tasks - Confirmed
    • Multiple tasks notify "restart docker" (package install, daemon config, service patch)
    • Handler runs once at end despite multiple notifications
    • Confirms deduplication behavior

Advanced Pattern: meta: flush_handlers

  • Pattern Evolution: Docker uses explicit handler flushing:

    - name: Ensure handlers are notified now to avoid firewall conflicts.
      ansible.builtin.meta: flush_handlers
    
    • New insight: Can force handlers to run mid-play, not just at end
    • Use case: Docker service must be running before adding users to docker group
    • Recommendation: Use flush_handlers when later tasks depend on handler completion

Secondary Handler Pattern

  • Pattern: apt update handler - ⚠️ Contextual
    • Docker has "apt update" handler for repository changes
    • Not present in security/users roles
    • Insight: Package management roles may need cache update handlers
    • When to use: When adding repositories that need immediate cache refresh

Key Validation Findings

What Docker Role Confirms:

  1. Lowercase naming is universal
  2. Simple, single-purpose handlers are universal
  3. Configurable handler state is standard for critical services
  4. Handler deduplication works as expected

What Docker Role Evolves:

  1. 🔄 Conditional handler execution (when: docker_service_manage | bool)
  2. 🔄 Check mode support (ignore_errors: "{{ ansible_check_mode }}")
  3. 🔄 Explicit handler flushing (meta: flush_handlers)
  4. 🔄 Repository-specific handlers (apt update)

Pattern Confidence After Docker Validation:

  • Handler naming: UNIVERSAL (3/3 roles use lowercase "[action] [service]")
  • Handler simplicity: UNIVERSAL (3/3 use single module per handler)
  • Configurable state: UNIVERSAL (critical service handlers are configurable)
  • Conditional handlers: EVOLVED (docker adds when: conditionals)
  • Handler flushing: EVOLVED (docker introduces meta: flush_handlers)

Summary

Universal Handler Patterns:

  1. Use handlers only when services/daemons need restart/reload
  2. One handler per service/action combination
  3. Lowercase naming: "[action] [service]"
  4. Keep handlers simple (single module, single purpose)
  5. Prefer reload over restart when available
  6. Place all handlers in handlers/main.yml
  7. Make critical handler behavior configurable
  8. Handler name must match notify string exactly

Key Takeaways:

  • Not all roles need handlers (user management, file deployment often don't)
  • Handlers prevent duplicate service restarts (run once per play)
  • Reload is less disruptive than restart (use when supported)
  • Handler order is defined in handlers/main.yml, not by notify order
  • Keep handlers simple and focused
  • Configurable handler behavior helps with testing and critical services

Virgo-Core Assessment:

All three roles demonstrate good handler discipline:

  • system_user - Correctly has no handlers (none needed)
  • proxmox_access - Has appropriate handlers
  • proxmox_network - Good network reload handler

No critical handler-related gaps identified. Virgo-Core roles follow best practices.

Validation: geerlingguy.postgresql

Analysis Date: 2025-10-23 Repository: https://github.com/geerlingguy/ansible-role-postgresql

Handler Structure

PostgreSQL role handlers/main.yml:

- name: restart postgresql
  ansible.builtin.service:
    name: "{{ postgresql_daemon }}"
    state: "{{ postgresql_restarted_state }}"

Handler Naming

  • Pattern: Lowercase "[action] [service]" - Confirmed
    • "restart postgresql" - follows exact pattern
    • 4/4 roles use lowercase naming

Handler Simplicity

  • Pattern: Single module, single purpose - Confirmed
    • One handler, one service module, simple action
    • 4/4 roles follow simple handler pattern

Handler Configurability

  • Pattern: Configurable handler behavior - Confirmed
    • Uses postgresql_restarted_state variable (default: "restarted")
    • Same pattern as security_ssh_restart_handler_state and docker_restart_handler_state
    • Validates: Making critical service handlers configurable is standard practice
    • 4/4 roles with service handlers make state configurable

Service Management Variables

  • Pattern: Configurable service state - Confirmed
    • postgresql_service_state: started (whether to start service)
    • postgresql_service_enabled: true (whether to enable at boot)
    • postgresql_restarted_state: "restarted" (handler behavior)
    • Demonstrates: Separation of initial state vs handler state

Handler Notification Patterns

  • Pattern: Multiple tasks notify same handler - Confirmed
    • Configuration changes, package installations, initialization all notify "restart postgresql"
    • Handler runs once despite multiple notifications
    • 4/4 roles demonstrate handler deduplication

Advanced Pattern: Conditional Handler Execution

  • Pattern: Handler conditionals - ⚠️ Not Present
    • PostgreSQL handler doesn't use when: conditionals
    • Unlike docker role which has when: docker_service_manage | bool
    • Insight: PostgreSQL always manages service, docker sometimes doesn't (containers)
    • Contextual: Use conditionals only when service management is optional

Key Validation Findings

What PostgreSQL Role Confirms:

  1. Lowercase naming is universal (4/4 roles)
  2. Simple, single-purpose handlers are universal (4/4 roles)
  3. Configurable handler state is standard for database/service roles (4/4 roles)
  4. Handler deduplication works reliably (4/4 roles depend on it)
  5. Service + handler pattern is consistent

What PostgreSQL Role Demonstrates:

  1. 🔄 Database roles follow same handler patterns as other service roles
  2. 🔄 Configurable handler state (restarted vs reloaded) is valuable for databases
  3. 🔄 Service management variables (state, enabled, restart_state) are standard trio

Pattern Confidence After PostgreSQL Validation (4/4 roles):

  • Handler naming: UNIVERSAL (4/4 roles use lowercase "[action] [service]")
  • Handler simplicity: UNIVERSAL (4/4 use single module per handler)
  • Configurable state: UNIVERSAL (4/4 service roles make it configurable)
  • Conditional handlers: CONTEXTUAL (docker uses it, postgresql/security/users don't need it)

Next Steps:

Continue pattern of creating handlers only when necessary. Use the handler checklist:

  1. Does this role manage a service? → Maybe needs handlers
  2. Does configuration change require reload/restart? → Add handler
  3. Can I use reload instead of restart? → Prefer reload (PostgreSQL uses restart, can't reload config)
  4. Is handler behavior critical? → Make it configurable (database services should be configurable)
  5. Is handler name clear and lowercase? → Follow naming pattern
  6. Is service management optional? → Add conditional (when: role_service_manage | bool)

Validation: geerlingguy.nginx

Analysis Date: 2025-10-23 Repository: https://github.com/geerlingguy/ansible-role-nginx

Handler Structure

nginx role handlers/main.yml:

---
- name: restart nginx
  ansible.builtin.service: name=nginx state=restarted

- name: validate nginx configuration
  ansible.builtin.command: nginx -t -c /etc/nginx/nginx.conf
  changed_when: false

- name: reload nginx
  ansible.builtin.service: name=nginx state=reloaded
  when: nginx_service_state == "started"

Handler Naming

  • Pattern: Lowercase "[action] [service]" - Confirmed
    • "restart nginx", "reload nginx", "validate nginx configuration"
    • 5/5 roles use lowercase naming

Handler Simplicity

  • Pattern: Single module, single purpose - Confirmed
    • Each handler performs one clear action
    • 5/5 roles follow simple handler pattern

Reload vs Restart Pattern - CONFIRMED

  • nginx has BOTH reload and restart handlers:
    • restart nginx - Full service restart (disruptive)
    • reload nginx - Graceful configuration reload (preferred)
    • Demonstrates best practice: Provide both, use reload by default
    • 5/5 roles demonstrate reload preference when supported

Handler Conditional Execution - NEW PATTERN

  • Pattern: Conditional reload handler - CONFIRMED
    • reload nginx has: when: nginx_service_state == "started"
    • Prevents reload attempt if service is stopped
    • Safety pattern: Don't reload stopped services
    • Recommendation: Add when conditionals to reload handlers

Validation Handler Pattern - NEW INSIGHT

  • Pattern: Configuration validation handler - NEW INSIGHT
    • "validate nginx configuration" handler uses command: nginx -t
    • changed_when: false prevents false change reports
    • Use case: Run validation before restart/reload
    • Not seen in previous roles (they use validate parameter in tasks instead)
    • Alternative pattern: Task-level validation vs handler-level validation

Service State Variable Pattern

  • Pattern: Configurable service state - Confirmed
    • nginx_service_state: started (default)
    • nginx_service_enabled: true (default)
    • 5/5 service management roles use this pattern

Handler Notification Patterns

  • Pattern: Multiple handlers for configuration changes - Confirmed
    • Template changes notify: reload nginx
    • Vhost changes notify: reload nginx
    • Insight: nginx prefers reload over restart (less disruptive)
    • Validates reload vs restart decision matrix

Key Validation Findings

What nginx Role Confirms:

  1. Lowercase naming is universal (5/5 roles)
  2. Simple, single-purpose handlers are universal (5/5 roles)
  3. Reload vs restart distinction is universal for web servers (5/5 roles)
  4. Service state variables are universal (5/5 roles)
  5. Handler deduplication works reliably (5/5 roles)

What nginx Role Demonstrates ( NEW INSIGHTS):

  1. Both reload AND restart handlers: Provide flexibility, default to reload
  2. Conditional reload handler: when: service_state == "started" prevents errors
  3. Validation handler pattern: Alternative to task-level validation
  4. 🔄 Web servers should ALWAYS prefer reload over restart
  5. 🔄 Handler safety: Check service state before reload

Pattern Confidence After nginx Validation (5/5 roles):

  • Handler naming: UNIVERSAL (5/5 roles use lowercase "[action] [service]")
  • Handler simplicity: UNIVERSAL (5/5 use single module per handler)
  • Reload vs restart: UNIVERSAL (5/5 web/service roles distinguish them)
  • Conditional handlers: RECOMMENDED (nginx shows safety pattern)
  • Validation handlers: ALTERNATIVE PATTERN (task validation vs handler validation)

Validation: geerlingguy.pip and geerlingguy.git

Analysis Date: 2025-10-23 Repositories:

Handler Absence Pattern

  • Pattern: No handlers needed - Confirmed
    • pip role has NO handlers/ directory (package installation doesn't need service restarts)
    • git role has NO handlers/ directory (utility installation doesn't manage services)
    • Key finding: Utility roles typically don't need handlers

When Handlers Are NOT Needed

  • Pattern: Package-only roles - NEW INSIGHT
    • Roles that only install packages don't need handlers
    • Roles that don't manage services don't need handlers
    • Handler absence is correct and expected for utility roles
    • 7/7 roles make appropriate handler decisions (present when needed, absent when not)

Key Validation Findings

What pip + git Roles Confirm:

  1. Handlers are optional based on role purpose (7/7 roles decide appropriately)
  2. Utility roles (package installers) typically have no handlers (pip, git prove this)
  3. Service-managing roles ALWAYS have handlers (docker, postgresql, nginx, etc.)
  4. Handler directory can be omitted when not needed (pip + git validate this)

Pattern Confidence After Utility Role Validation (7/7 roles):

  • Handler naming: UNIVERSAL (7/7 service roles use lowercase "[action] [service]")
  • Handler simplicity: UNIVERSAL (7/7 service roles use single module per handler)
  • Reload vs restart: UNIVERSAL (7/7 web/service roles distinguish them)
  • Handlers optional for utilities: CONFIRMED (pip + git have none, correctly)
  • Handler presence decision matrix: VALIDATED
    • Service management role → handlers required
    • Package-only utility role → no handlers needed
    • Configuration management role → handlers for service reload/restart