From 6b82838ed4a0e2ef587c8da35ef07e9b3895c6fc Mon Sep 17 00:00:00 2001
From: Roque <roqueporchetto@gmail.com>
Date: Mon, 14 May 2018 13:53:37 +0200
Subject: [PATCH] Automatic restart of services when configuration changes

The main idea is to rename the service wrapper using a hash of the corresponding configuration files. In that way, if the config files are updated, the corresponding script file, section in supervisor.conf and service process will be updated accordingly.
- the file name in wrapper_path contains a hash of the corresponding config files
- when config files change, and therefore the hash, the wrapper will be re-created and the corresponding service restarted
- the config files paths will be a parameter in the corresponding buildout section, if it isn't set there won't be hash-check

/reviewed-on https://lab.nexedi.com/nexedi/slapos/merge_requests/326
---
 slapos/recipe/wrapper.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/slapos/recipe/wrapper.py b/slapos/recipe/wrapper.py
index bca25bd28..42ac56659 100644
--- a/slapos/recipe/wrapper.py
+++ b/slapos/recipe/wrapper.py
@@ -36,6 +36,7 @@ class Recipe(GenericBaseRecipe):
     :param str wrapper-path: absolute path to file's destination
 
     :param lines wait-for-files: list of files to wait for
+    :param lines hash-files: list of files to be checked by hash
     :param str pidfile: path to pidfile ensure exclusivity for the process
     :param str private-dev-shm: size of private /dev/shm, using user namespaces
     :param bool reserve-cpu: command will ask for an exclusive CPU core
@@ -44,6 +45,7 @@ class Recipe(GenericBaseRecipe):
         args = shlex.split(self.options['command-line'])
         wrapper_path = self.options['wrapper-path']
         wait_files = self.options.get('wait-for-files')
+        hash_files = self.options.get('hash-files')
         pidfile = self.options.get('pidfile')
         private_dev_shm = self.options.get('private-dev-shm')
 
@@ -63,5 +65,20 @@ class Recipe(GenericBaseRecipe):
           kw['private_dev_shm'] = private_dev_shm
         if self.isTrueValue(self.options.get('reserve-cpu')):
           kw['reserve_cpu'] = True
+        if hash_files:
+          hash_file_list = hash_files.split()
+          hash = self.generateHashFromFiles(hash_file_list)
+          wrapper_path = "%s-%s" % (wrapper_path, hash)
 
         return self.createWrapper(wrapper_path, args, environment, **kw)
+
+    def generateHashFromFiles(self, file_list):
+      import hashlib
+      hasher = hashlib.md5()
+      for path in file_list:
+        with open(path, 'r') as afile:
+          buf = afile.read()
+        hasher.update("%s\n" % len(buf))
+        hasher.update(buf)
+      hash = hasher.hexdigest()
+      return hash
-- 
2.30.9