Use handlers for letsencrypt cert updates

This change proposes calling a handler each time a certificate is
created/updated.  The handler name is based on the name of the
certificate given in the letsencrypt_certs variable, as described in
the role documentation.

Because Ansible considers calling a handler with no listeners an error
this means each letsencrypt user will need to provide a handler.

One simple option illustrated here is just to produce a stamp file.
This can facilitate cross-playbook and even cross-orchestration-tool
communication.  For example, puppet or other ansible playbooks can
detect this stamp file and schedule their reloads, etc. then remove
the stamp file.  It is conceivable more complex listeners could be
setup via other roles, etc. should the need arise.

A test is added to make sure the stamp file is created for the
letsencrypt test hosts, which are always generating a new certificate
in the gate test.

Change-Id: I4e0609c4751643d6e0c8d9eaa38f184e0ce5452e
This commit is contained in:
Ian Wienand 2019-04-16 09:41:14 +10:00
parent 8baf6cabd3
commit 733122f0df
8 changed files with 67 additions and 6 deletions

View File

@ -1,4 +1,4 @@
letsencrypt_certs:
main:
graphite01-main:
- graphite01.opendev.org
- graphite.opendev.org

View File

@ -0,0 +1,32 @@
# Handlers for "letsencrypt update {{ key }}" events
#
# Note that because Ansible requires every called handler to have a
# listener, every host will need to provide a handler somehow.
#
# NOTE(ianw): as at 04/2019 it seems that something like
#
# listen: letsencrypt updated letsencrypt01-main-service
#
# doesn't actually register the handler. May be a bug or a feature to
# do with import_tasks; currently unsure.
- name: letsencrypt updated graphite01-main
import_tasks: touch_file.yaml
vars:
touch_file: '/tmp/letsencrypt-graphite01-main.stamp'
# Gate testing hosts:
- name: letsencrypt updated letsencrypt01-main-service
import_tasks: touch_file.yaml
vars:
touch_file: '/tmp/letsencrypt01-main-service.stamp'
- name: letsencrypt updated letsencrypt01-other-service
import_tasks: touch_file.yaml
vars:
touch_file: '/tmp/letsencrypt01-other-service.stamp'
- name: letsencrypt updated letsencrypt02-main-service
import_tasks: touch_file.yaml
vars:
touch_file: '/tmp/letsencrypt02-main-service.stamp'

View File

@ -0,0 +1,5 @@
- name: 'Touch {{ touch_file }}'
file:
path: '{{ touch_file }}'
state: touch

View File

@ -10,5 +10,6 @@
chdir: /opt/acme.sh/
environment:
LETSENCRYPT_STAGING: '{{ "1" if letsencrypt_use_staging else "0" }}'
notify: 'letsencrypt updated {{ item.key }}'
# Keys generated!

View File

@ -35,16 +35,25 @@ provision process.
.. code-block:: yaml
letsencrypt_certs:
main:
hostname-main-cert:
- hostname01.opendev.org
- hostname.opendev.org
secondary:
hostname-secondary-cert:
- foo.opendev.org
will ultimately result in two certificates being provisioned on the
host in ``/etc/letsencrypt-certs/hostname01.opendev.org`` and
``/etc/letsencrypt-certs/foo.opendev.org``.
Note the creation role ``letsencrypt-create-certs`` will call a
handler ``letsencrypt updated {{ key }}`` (for example,
``letsencrypt updated hostname-main-cert``) when that certificate
is created or updated. Because Ansible errors if a handler is
called with no listeners, you *must* define a listener for event.
``letsencrypt-create-certs`` has ``handlers/main.yaml`` where
handlers can be defined. Since handlers reside in a global
namespace, you should choose an appropriately unique name.
Note that each entry will require a ``CNAME`` pointing the ACME
challenge domain to the TXT record that will be created in the
signing domain. For example above, the following records would need

View File

@ -1,7 +1,7 @@
letsencrypt_certs:
main:
letsencrypt01-main-service:
- letsencrypt01.opendev.org
- letsencrypt.opendev.org
- alias.opendev.org
secondary:
letsencrypt01-other-service:
- someotherservice.opendev.org

View File

@ -1,4 +1,4 @@
letsencrypt_certs:
main:
letsencrypt02-main-service:
- letsencrypt02.opendev.org
- letsencrypt.opendev.org

View File

@ -68,3 +68,17 @@ def test_certs_created(host):
else:
pytest.skip()
def test_updated_handler(host):
if host.backend.get_hostname() == 'letsencrypt01.opendev.org':
stamp_file = host.file('/tmp/letsencrypt01-main-service.stamp')
assert stamp_file.exists
stamp_file = host.file('/tmp/letsencrypt01-other-service.stamp')
assert stamp_file.exists
elif host.backend.get_hostname() == 'letsencrypt02.opendev.org':
stamp_file = host.file('/tmp/letsencrypt02-main-service.stamp')
assert stamp_file.exists
else:
pytest.skip()