diff --git a/patroni/ctl.py b/patroni/ctl.py index 30070545..77e80f12 100644 --- a/patroni/ctl.py +++ b/patroni/ctl.py @@ -24,20 +24,22 @@ import yaml from click import ClickException from collections import defaultdict from contextlib import contextmanager -from patroni.dcs import get_dcs as _get_dcs -from patroni.exceptions import PatroniException -from patroni.postgresql import Postgresql -from patroni.postgresql.misc import postgres_version_to_int -from patroni.utils import cluster_as_json, patch_config, polling_loop -from patroni.request import PatroniRequest -from patroni.version import __version__ from prettytable import ALL, FRAME, PrettyTable from six.moves.urllib_parse import urlparse + try: from ydiff import markup_to_pager, PatchStream except ImportError: # pragma: no cover from cdiff import markup_to_pager, PatchStream +from .dcs import get_dcs as _get_dcs +from .exceptions import PatroniException +from .postgresql import Postgresql +from .postgresql.misc import postgres_version_to_int +from .utils import cluster_as_json, find_executable, patch_config, polling_loop +from .request import PatroniRequest +from .version import __version__ + CONFIG_DIR_PATH = click.get_app_dir('patroni') CONFIG_FILE_PATH = os.path.join(CONFIG_DIR_PATH, 'patronictl.yaml') DCS_DEFAULTS = {'zookeeper': {'port': 2181, 'template': "zookeeper:\n hosts: ['{host}:{port}']"}, @@ -1171,24 +1173,6 @@ def apply_yaml_file(data, filename): return format_config_for_editing(changed_data), changed_data -def find_executable(executable, path=None): - _, ext = os.path.splitext(executable) - - if (sys.platform == 'win32') and (ext != '.exe'): - executable = executable + '.exe' - - if os.path.isfile(executable): - return executable - - if path is None: - path = os.environ.get('PATH', os.defpath) - - for p in path.split(os.pathsep): - f = os.path.join(p, executable) - if os.path.isfile(f): - return f - - def invoke_editor(before_editing, cluster_name): """Starts editor command to edit configuration in human readable format diff --git a/patroni/utils.py b/patroni/utils.py index 5d1ed034..637291e5 100644 --- a/patroni/utils.py +++ b/patroni/utils.py @@ -512,3 +512,21 @@ def enable_keepalive(sock, timeout, idle, cnt=3): for opt in keepalive_socket_options(timeout, idle, cnt): sock.setsockopt(*opt) + + +def find_executable(executable, path=None): + _, ext = os.path.splitext(executable) + + if (sys.platform == 'win32') and (ext != '.exe'): + executable = executable + '.exe' + + if os.path.isfile(executable): + return executable + + if path is None: + path = os.environ.get('PATH', os.defpath) + + for p in path.split(os.pathsep): + f = os.path.join(p, executable) + if os.path.isfile(f): + return f diff --git a/patroni/validator.py b/patroni/validator.py index 117b40d6..e7aed603 100644 --- a/patroni/validator.py +++ b/patroni/validator.py @@ -4,12 +4,12 @@ import socket import re import subprocess -from patroni.utils import split_host_port, data_directory_is_empty -from patroni.ctl import find_executable -from patroni.dcs import dcs_modules -from patroni.exceptions import ConfigParseError from six import string_types +from .utils import find_executable, split_host_port, data_directory_is_empty +from .dcs import dcs_modules +from .exceptions import ConfigParseError + def data_directory_empty(data_dir): if os.path.isfile(os.path.join(data_dir, "global", "pg_control")): diff --git a/tests/test_ctl.py b/tests/test_ctl.py index d93c5d1f..c03f3a85 100644 --- a/tests/test_ctl.py +++ b/tests/test_ctl.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta from mock import patch, Mock from patroni.ctl import ctl, store_config, load_config, output_members, get_dcs, parse_dcs, \ get_all_members, get_any_member, get_cursor, query_member, configure, PatroniCtlException, apply_config_changes, \ - format_config_for_editing, show_diff, invoke_editor, format_pg_version, find_executable, CONFIG_FILE_PATH + format_config_for_editing, show_diff, invoke_editor, format_pg_version, CONFIG_FILE_PATH from patroni.dcs.etcd import AbstractEtcdClientWithFailover, Failover from patroni.utils import tzutc from psycopg2 import OperationalError @@ -629,15 +629,6 @@ class TestCtl(unittest.TestCase): self.assertEqual(format_pg_version(100001), '10.1') self.assertEqual(format_pg_version(90605), '9.6.5') - @patch('sys.platform', 'win32') - def test_find_executable(self): - with patch('os.path.isfile', Mock(return_value=True)): - self.assertEqual(find_executable('vim'), 'vim.exe') - with patch('os.path.isfile', Mock(return_value=False)): - self.assertIsNone(find_executable('vim')) - with patch('os.path.isfile', Mock(side_effect=[False, True])): - self.assertEqual(find_executable('vim', '/'), '/vim.exe') - @patch('patroni.ctl.get_dcs') def test_get_members(self, mock_get_dcs): mock_get_dcs.return_value = self.e diff --git a/tests/test_utils.py b/tests/test_utils.py index 0c6b21e4..80bad3e5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,7 +2,7 @@ import unittest from mock import Mock, patch from patroni.exceptions import PatroniException -from patroni.utils import Retry, RetryFailedError, enable_keepalive, polling_loop, validate_directory +from patroni.utils import Retry, RetryFailedError, enable_keepalive, find_executable, polling_loop, validate_directory class TestUtils(unittest.TestCase): @@ -41,6 +41,15 @@ class TestUtils(unittest.TestCase): with patch('sys.platform', platform): self.assertIsNone(enable_keepalive(Mock(), 10, 5)) + @patch('sys.platform', 'win32') + def test_find_executable(self): + with patch('os.path.isfile', Mock(return_value=True)): + self.assertEqual(find_executable('vim'), 'vim.exe') + with patch('os.path.isfile', Mock(return_value=False)): + self.assertIsNone(find_executable('vim')) + with patch('os.path.isfile', Mock(side_effect=[False, True])): + self.assertEqual(find_executable('vim', '/'), '/vim.exe') + @patch('time.sleep', Mock()) class TestRetrySleeper(unittest.TestCase):