Improving SSL Ansible role to automatically restart services
My existing approach to restarting services if SSL certificates are updated relies on role handlers listening to notification from the ssl roles. This has proven to be unsatisfactory as it relies on those roles being included, and ahead of the SSL role, for the handlers to be loaded at the time of notification. This post is about changing that behaviour so the ssl role itself restarts the services.
The plan
The idea I have is to us variables of the form ssl_restart_service_... or ssl_reload_service_..., set to the name of the service, to specify the services to be restarted or reloaded respectively. Using variables like this allows setting non-clashing variable names at the group level (e.g. ssl_restart_service_webserver: nginx could be set on the webserver group, ssl_restart_service_hashicorp_vault: vault on hashicorp_vault group etc.). These will be gathered into a single variable, listing all the services to be restarted or reloaded, by my site.yaml (so the ssl role needs no knowledge of this implementation detail of how the list of services is formed) and the role modified to take a list of services to reload/restart if the host’s is updated.
Changes to the role
The role needs to be modified to take the lists of services. I have, currently only, one service which is run as a user-scoped service (a Podman container) so I needed to be able to support this too (which is a good thing for flexibility, albeit systemd specific). I modified the role’s meta/argument_specs.yaml file first to include these new arguments:
---
argument_specs:
main:
short_description: Deploy host's SSL certificate and generate diffie-hellman parameters
author: Laurence Alexander Hurst
options:
#...
ssl_handler_restart_services:
description: List of services to restart if any certificate file(s) are updated
type: list
default: []
elements: str
ssl_handler_restart_user_scoped_services:
description: List of user-scoped (systemd) services to restart if any certificate file(s) are updated
type: list
default: []
elements: dict
options:
name:
description: Name of the service to restart
type: str
required: true
user:
description: User to be when restarting the service
type: str
required: true
ssl_handler_reload_services:
description: List of services to reload if any certificate file(s) are updated
type: list
default: []
elements: str
ssl_handler_reload_user_scoped_services:
description: List of user-scoped (systemd) services to reload if any certificate file(s) are updated
type: list
default: []
elements: dict
options:
name:
description: Name of the service to reload
type: str
required: true
user:
description: User to be when reloading the service
type: str
required: true
...
The role had no defaults file, so I created defaults/main.yaml for these new variables:
---
ssl_handler_restart_services: []
ssl_handler_restart_user_scoped_services: []
ssl_handler_reload_services: []
ssl_handler_reload_user_scoped_services: []
...
Because handlers do not support loops, but can include tasks file that contain loops, the actual restart/reloading is done by adding tasks/restart_services.yaml and tasks/reload_services.yaml that have the loops. These are identical except for reload vs restart in their names/variables/states, so I have only listed restart_services.yaml here:
---
- name: All services requested to be restarted on certificate change are restarted
become: true
ansible.builtin.service:
name: '{{ item }}'
state: restarted
loop: '{{ ssl_handler_restart_services }}'
# This is systemd specific
- name: All user-scoped services requested to be restarted on certificate change are restarted
become: true
become_user: "{{ item.user }}"
ansible.builtin.service:
name: '{{ item.name }}'
scope: user
state: restarted
loop: '{{ ssl_handler_restart_user_scoped_services }}'
...
Finally, the existing no-op handler in the role is replaced with two handlers (both listening to the existing notification) to include the tasks, and handlers listening in other roles can all be removed:
---
- name: Handler to ensure services are restarted
ansible.builtin.include_tasks:
file: restart_services.yaml
listen: "ssl certificates updated"
- name: Handler to ensure services are reloaded
ansible.builtin.include_tasks:
file: reload_services.yaml
listen: "ssl certificates updated"
...
Changes to site.yaml
All that was required was adding tasks to collate the group-based inventory variables to a single fact, added to my play that applies to ssl_host_certificate group hosts:
- name: List of services to restart is known
ansible.builtin.set_fact:
ssl_handler_restart_services: >-
{{
q(
'ansible.builtin.vars',
*q('ansible.builtin.varnames', 'ssl_handler_restart_service_.+')
)
}}
- name: List of user scoped services to restart is known
ansible.builtin.set_fact:
ssl_handler_restart_user_scoped_services: >-
{{
q(
'ansible.builtin.vars',
*q('ansible.builtin.varnames', 'ssl_handler_restart_user_scoped_service_.+')
)
}}
- name: List of services to reload is known
ansible.builtin.set_fact:
ssl_handler_reload_services: >-
{{
q(
'ansible.builtin.vars',
*q('ansible.builtin.varnames', 'ssl_handler_reload_service_.+')
)
}}
- name: List of user scoped services to reload is known
ansible.builtin.set_fact:
ssl_handler_reload_user_scoped_services: >-
{{
q(
'ansible.builtin.vars',
*q('ansible.builtin.varnames', 'ssl_handler_reload_user_scoped_service_.+')
)
}}
Inventory changes
The only changes to the inventory were to add the services to restart as variables:
group_vars/webservers.yaml (new file):
---
ssl_handler_restart_service_nginx: nginx
...
group_vars/hashicorp_vault_servers.yaml:
ssl_handler_restart_service_hashicorp_vault: vault
group_vars/omada_controller_hosts.yaml:
ssl_handler_restart_user_scoped_service_omada:
name: container-omada_controller
user: "{{ omada_user | default('omada') }}"