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:
- https://github.com/geerlingguy/ansible-role-security
- https://github.com/geerlingguy/ansible-role-github-users
- https://github.com/geerlingguy/ansible-role-docker
- https://github.com/geerlingguy/ansible-role-postgresql
- https://github.com/geerlingguy/ansible-role-nginx
- https://github.com/geerlingguy/ansible-role-pip
- https://github.com/geerlingguy/ansible-role-git
Pattern Confidence Levels (Historical)
Analyzed 2 geerlingguy roles: security, github-users
Universal Patterns (Consistent when handlers exist):
- ✅ Simple, single-purpose handlers - Each handler does one thing
- ✅ Lowercase naming - "restart ssh" not "Restart SSH"
- ✅ Action + service pattern - "[action] [service]" naming (restart ssh, reload fail2ban)
- ✅ handlers/main.yml location - All handlers in single file
- ✅ Configurable handler behavior - Use variables for handler state when appropriate
Contextual Patterns (When handlers are needed vs not):
- ⚠️ Service management roles need handlers - security has handlers (manages SSH, fail2ban), github-users has none (no services)
- ⚠️ Handler count scales with services - security has 3 handlers (systemd, ssh, fail2ban), simple roles may have 0-1
- ⚠️ 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
- Service restarts/reloads - After configuration changes
- Daemon reloads - After systemd unit file changes
- Cache clearing - After package installations
- Index rebuilding - After data changes
- Cleanup operations - After multiple related changes
Use Tasks (Not Handlers) For
- User account management - No services to restart
- File deployment - Unless it triggers a service reload
- Package installation - Unless service needs restart after
- Variable setting - No side effects
- 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 daemonrestart ssh- Action: restart, Target: ssh servicereload fail2ban- Action: reload, Target: fail2ban service
Handler Naming Guidelines
- Use lowercase - "restart ssh" not "Restart SSH"
- Action first - Verb before noun (restart ssh, not ssh restart)
- Be specific - Name the actual service (ssh, not daemon)
- One action per handler - Don't combine "restart ssh and fail2ban"
- Match notification - Handler name must match notify string exactly
- 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
- Single module - One module per handler
- Clear purpose - Does one thing well
- Variable support - Use variables for OS differences
- Appropriate module - ansible.builtin.systemd_service for systemd, ansible.builtin.service for others
- 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:
- Services with both reload and restart options
- Critical services users might not want to restart automatically
- Services with graceful shutdown requirements
- Testing scenarios where full restart is undesirable
Not necessary for:
- systemd daemon-reload (only one valid action)
- Simple cache clears
- 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
- Handlers run once - Even if notified multiple times in a play
- Handlers run at end - After all tasks complete
- Handlers run in order - Order defined in handlers/main.yml, not notification order
- 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: trueif 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_statevariable (default: "restarted") - Same pattern as security role's
security_ssh_restart_handler_state - Confirms making critical service handlers configurable is standard
- Uses
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:
- ✅ Lowercase naming is universal
- ✅ Simple, single-purpose handlers are universal
- ✅ Configurable handler state is standard for critical services
- ✅ Handler deduplication works as expected
What Docker Role Evolves:
- 🔄 Conditional handler execution (when: docker_service_manage | bool)
- 🔄 Check mode support (ignore_errors: "{{ ansible_check_mode }}")
- 🔄 Explicit handler flushing (meta: flush_handlers)
- 🔄 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:
- Use handlers only when services/daemons need restart/reload
- One handler per service/action combination
- Lowercase naming: "[action] [service]"
- Keep handlers simple (single module, single purpose)
- Prefer reload over restart when available
- Place all handlers in handlers/main.yml
- Make critical handler behavior configurable
- 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_statevariable (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
- Uses
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
- PostgreSQL handler doesn't use
Key Validation Findings
What PostgreSQL Role Confirms:
- ✅ Lowercase naming is universal (4/4 roles)
- ✅ Simple, single-purpose handlers are universal (4/4 roles)
- ✅ Configurable handler state is standard for database/service roles (4/4 roles)
- ✅ Handler deduplication works reliably (4/4 roles depend on it)
- ✅ Service + handler pattern is consistent
What PostgreSQL Role Demonstrates:
- 🔄 Database roles follow same handler patterns as other service roles
- 🔄 Configurable handler state (
restartedvsreloaded) is valuable for databases - 🔄 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:
- Does this role manage a service? → Maybe needs handlers
- Does configuration change require reload/restart? → Add handler
- Can I use reload instead of restart? → Prefer reload (PostgreSQL uses restart, can't reload config)
- Is handler behavior critical? → Make it configurable (database services should be configurable)
- Is handler name clear and lowercase? → Follow naming pattern
- 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
whenconditionals to reload handlers
- reload nginx has:
Validation Handler Pattern - ✨ NEW INSIGHT
- Pattern: Configuration validation handler - ✨ NEW INSIGHT
- "validate nginx configuration" handler uses
command: nginx -t changed_when: falseprevents 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
- "validate nginx configuration" handler uses
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:
- ✅ Lowercase naming is universal (5/5 roles)
- ✅ Simple, single-purpose handlers are universal (5/5 roles)
- ✅ Reload vs restart distinction is universal for web servers (5/5 roles)
- ✅ Service state variables are universal (5/5 roles)
- ✅ Handler deduplication works reliably (5/5 roles)
What nginx Role Demonstrates (✨ NEW INSIGHTS):
- ✨ Both reload AND restart handlers: Provide flexibility, default to reload
- ✨ Conditional reload handler:
when: service_state == "started"prevents errors - ✨ Validation handler pattern: Alternative to task-level validation
- 🔄 Web servers should ALWAYS prefer reload over restart
- 🔄 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:
- ✅ Handlers are optional based on role purpose (7/7 roles decide appropriately)
- ✅ Utility roles (package installers) typically have no handlers (pip, git prove this)
- ✅ Service-managing roles ALWAYS have handlers (docker, postgresql, nginx, etc.)
- ✅ 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