Repository: zalando/patroni
Pull request #1994 (2021-07-01 09:26:01)

Fix problem with PyInstaller-frozen code and dot in the path
If `PyInstaller`-frozen application using `patroni` is placed in a location that contains a dot in the path, the `dcs_modules()` function in `patroni.dcs` breaks because `pkgutil.iter_importers()` treats the given path as a package name.

Ref: pyinstaller/pyinstaller#5944

This can be avoided altogether by not passing a path to `iter_importers()`, because `PyInstaller`'s `FrozenImporter` is a singleton and registered as top-level finder.

rokm: As a side note, we're planning to add support for `pkgutil.iter_importers()` to `PyInstaller` (pyinstaller/pyinstaller#5959), which will alleviate the need for that PyInstaller-specific codepath in `dcs_modules()`.

Patch:
diff --git a/patroni/dcs/__init__.py b/patroni/dcs/__init__.py
index 10c2d5f8..9e0e225c 100644
--- a/patroni/dcs/__init__.py
+++ b/patroni/dcs/__init__.py
@@ -68,7 +68,11 @@ def dcs_modules():
 
     if getattr(sys, 'frozen', False):
         toc = set()
-        for importer in pkgutil.iter_importers(dcs_dirname):
+        # dcs_dirname may contain a dot, which causes pkgutil.iter_importers()
+        # to misinterpret the path as a package name. This can be avoided
+        # altogether by not passing a path at all, because PyInstaller's
+        # FrozenImporter is a singleton and registered as top-level finder.
+        for importer in pkgutil.iter_importers():
             if hasattr(importer, 'toc'):
                 toc |= importer.toc
         return [module for module in toc if module.startswith(module_prefix) and module.count('.') == 2]
diff --git a/patroni/ha.py b/patroni/ha.py
index 7d08dcae..ee1c78e8 100644
--- a/patroni/ha.py
+++ b/patroni/ha.py
@@ -1002,7 +1002,7 @@ class Ha(object):
                 else:
                     return self.enforce_master_role(
                         'no action. I am ({0}) the leader with the lock'.format(self.state_handler.name),
-                        'promoted self to leader because i had the session lock'
+                        'promoted self to leader because I had the session lock'
                     )
             else:
                 # Either there is no connection to DCS or someone else acquired the lock


Test patch:
diff --git a/tests/test_ha.py b/tests/test_ha.py
index 85f1cfc8..68a9a4ea 100644
--- a/tests/test_ha.py
+++ b/tests/test_ha.py
@@ -300,7 +300,7 @@ class TestHa(PostgresInit):
         self.p.is_healthy = true
         self.ha.has_lock = true
         self.p.controldata = lambda: {'Database cluster state': 'in production', 'Database system identifier': SYSID}
-        self.assertEqual(self.ha.run_cycle(), 'promoted self to leader because i had the session lock')
+        self.assertEqual(self.ha.run_cycle(), 'promoted self to leader because I had the session lock')
 
     @patch('psycopg2.connect', psycopg2_connect)
     def test_acquire_lock_as_master(self):
@@ -354,7 +354,7 @@ class TestHa(PostgresInit):
         self.ha.cluster.is_unlocked = false
         self.ha.has_lock = true
         self.p.is_leader = false
-        self.assertEqual(self.ha.run_cycle(), 'promoted self to leader because i had the session lock')
+        self.assertEqual(self.ha.run_cycle(), 'promoted self to leader because I had the session lock')
 
     def test_promote_without_watchdog(self):
         self.ha.cluster.is_unlocked = false