Allow config.py to save/load config from zookeeper. No watching yet.
authorScott Gasch <[email protected]>
Wed, 17 Aug 2022 18:04:31 +0000 (11:04 -0700)
committerScott Gasch <[email protected]>
Wed, 17 Aug 2022 18:04:31 +0000 (11:04 -0700)
config.py
file_utils.py

index c7c686d738803484a6ae6277af7fc5ebd25d5056..f3a2dfe3888903543bc07fbcde0d96a423aed81e 100644 (file)
--- a/config.py
+++ b/config.py
@@ -85,9 +85,14 @@ import argparse
 import logging
 import os
 import pprint
+import re
 import sys
 from typing import Any, Dict, List, Optional
 
+from kazoo.client import KazooClient
+
+import scott_secrets
+
 # This module is commonly used by others in here and should avoid
 # taking any unnecessary dependencies back on them.
 
@@ -336,10 +341,35 @@ def _augment_sys_argv_from_loadfile():
             saw_other_args = True
 
     if loadfile is not None:
-        if not os.path.exists(loadfile):
+        zkpath = None
+        if loadfile[:3] == 'zk:':
+            try:
+                zk = KazooClient(
+                    hosts=scott_secrets.ZOOKEEPER_NODES,
+                    use_ssl=True,
+                    verify_certs=False,
+                    keyfile=scott_secrets.ZOOKEEPER_CLIENT_CERT,
+                    keyfile_password=scott_secrets.ZOOKEEPER_CLIENT_PASS,
+                    certfile=scott_secrets.ZOOKEEPER_CLIENT_CERT,
+                )
+                zk.start()
+                zkpath = loadfile[3:]
+                if not zkpath.startswith('/config/'):
+                    zkpath = '/config/' + zkpath
+                    zkpath = re.sub(r'//+', '/', zkpath)
+                if not zk.exists(zkpath):
+                    raise Exception(
+                        f'ERROR: --config_loadfile argument must be a file, {loadfile} not found (in zookeeper)'
+                    )
+            except Exception as e:
+                raise Exception(
+                    f'ERROR: Error talking with zookeeper while looking for {loadfile}'
+                ) from e
+        elif not os.path.exists(loadfile):
             raise Exception(
                 f'ERROR: --config_loadfile argument must be a file, {loadfile} not found.'
             )
+
         if saw_other_args:
             msg = f'Augmenting commandline arguments with those from {loadfile}.'
         else:
@@ -347,9 +377,21 @@ def _augment_sys_argv_from_loadfile():
         print(msg, file=sys.stderr)
         SAVED_MESSAGES.append(msg)
 
-        with open(loadfile, 'r') as rf:
-            newargs = rf.readlines()
-        newargs = [arg.strip('\n') for arg in newargs if 'config_savefile' not in arg]
+        newargs = []
+        if zkpath:
+            try:
+                contents = zk.get(zkpath)[0]
+                contents = contents.decode()
+                newargs = [
+                    arg.strip('\n') for arg in contents.split('\n') if 'config_savefile' not in arg
+                ]
+            except Exception as e:
+                raise Exception(f'Error reading {zkpath} from zookeeper.') from e
+            SAVED_MESSAGES.append(f'Loaded config from zookeeper from {zkpath}')
+        else:
+            with open(loadfile, 'r') as rf:
+                newargs = rf.readlines()
+            newargs = [arg.strip('\n') for arg in newargs if 'config_savefile' not in arg]
         sys.argv += newargs
 
 
@@ -403,8 +445,31 @@ def parse(entry_module: Optional[str]) -> Dict[str, Any]:
     # Check for savefile and populate it if requested.
     savefile = config['config_savefile']
     if savefile and len(savefile) > 0:
-        with open(savefile, 'w') as wf:
-            wf.write("\n".join(ORIG_ARGV[1:]))
+        data = '\n'.join(ORIG_ARGV[1:])
+        if savefile[:3] == 'zk:':
+            zkpath = savefile[3:]
+            if not zkpath.startswith('/config/'):
+                zkpath = '/config/' + zkpath
+                zkpath = re.sub(r'//+', '/', zkpath)
+            try:
+                zk = KazooClient(
+                    hosts=scott_secrets.ZOOKEEPER_NODES,
+                    use_ssl=True,
+                    verify_certs=False,
+                    keyfile=scott_secrets.ZOOKEEPER_CLIENT_CERT,
+                    keyfile_password=scott_secrets.ZOOKEEPER_CLIENT_PASS,
+                    certfile=scott_secrets.ZOOKEEPER_CLIENT_CERT,
+                )
+                zk.start()
+                if zk.exists(zkpath):
+                    zk.delete(zkpath)
+                zk.create(zkpath, data.encode())
+            except Exception as e:
+                raise Exception(f'Failed to create zookeeper path {zkpath}') from e
+            SAVED_MESSAGES.append(f'Saved config to zookeeper in {zkpath}')
+        else:
+            with open(savefile, 'w') as wf:
+                wf.write(data)
 
     # Also dump the config on stderr if requested.
     if config['config_dump']:
index 7a64f9f3eef7f8073736863bc87d408db8f49695..b7cd6bc158fd6e4c9b490cf075f96d1d3f5278c8 100644 (file)
@@ -92,6 +92,23 @@ def remove(path: str) -> None:
     os.remove(path)
 
 
+def fix_multiple_slashes(path: str) -> str:
+    """Fixes multi-slashes in paths or path-like strings
+
+    Args:
+        path: the path in which to remove multiple slashes
+
+    >>> p = '/usr/local//etc/rc.d///file.txt'
+    >>> fix_multiple_slashes(p)
+    '/usr/local/etc/rc.d/file.txt'
+
+    >>> p = 'this is a test'
+    >>> fix_multiple_slashes(p) == p
+    True
+    """
+    return re.sub(r'/+', '/', path)
+
+
 def delete(path: str) -> None:
     """This is a convenience for my dumb ass who can't remember os.remove
     sometimes.