diff --git a/src/ceph-volume/ceph_volume/process.py b/src/ceph-volume/ceph_volume/process.py index 1a2c268dbd8..501c830d99c 100644 --- a/src/ceph-volume/ceph_volume/process.py +++ b/src/ceph-volume/ceph_volume/process.py @@ -9,6 +9,15 @@ import logging logger = logging.getLogger(__name__) +def which(executable): + """ + Proxy function to ceph_volume.util.system.which because the ``system`` + module does import ``process`` + """ + from ceph_volume.util import system + return system.which(executable) + + def log_output(descriptor, message, terminal_logging, logfile_logging): """ log output to both the logger and the terminal if terminal_logging is @@ -102,6 +111,8 @@ def run(command, **kw): stop_on_error = kw.pop('stop_on_error', True) command_msg = obfuscate(command, kw.pop('obfuscate', None)) fail_msg = kw.pop('fail_msg', None) + executable = which(command.pop(0)) + command.insert(0, executable) logger.info(command_msg) terminal.write(command_msg) terminal_logging = kw.pop('terminal_logging', True) @@ -166,6 +177,8 @@ def call(command, **kw): terminal_verbose = kw.pop('terminal_verbose', False) logfile_verbose = kw.pop('logfile_verbose', True) show_command = kw.pop('show_command', False) + executable = which(command.pop(0)) + command.insert(0, executable) command_msg = "Running command: %s" % ' '.join(command) stdin = kw.pop('stdin', None) logger.info(command_msg) diff --git a/src/ceph-volume/ceph_volume/tests/util/test_system.py b/src/ceph-volume/ceph_volume/tests/util/test_system.py index a742de4804c..690147cb29b 100644 --- a/src/ceph-volume/ceph_volume/tests/util/test_system.py +++ b/src/ceph-volume/ceph_volume/tests/util/test_system.py @@ -166,3 +166,29 @@ class TestIsBinary(object): def test_is_not_binary(self, tmpfile): binary_path = tmpfile(contents='asd\n\nlkjh0') assert system.is_binary(binary_path) is False + + +class TestWhich(object): + + def test_executable_exists_but_is_not_file(self, monkeypatch): + monkeypatch.setattr(system.os.path, 'isfile', lambda x: False) + monkeypatch.setattr(system.os.path, 'exists', lambda x: True) + assert system.which('exedir') == 'exedir' + + def test_executable_does_not_exist(self, monkeypatch): + monkeypatch.setattr(system.os.path, 'isfile', lambda x: False) + monkeypatch.setattr(system.os.path, 'exists', lambda x: False) + assert system.which('exedir') == 'exedir' + + def test_executable_exists_as_file(self, monkeypatch): + monkeypatch.setattr(system.os.path, 'isfile', lambda x: True) + monkeypatch.setattr(system.os.path, 'exists', lambda x: True) + assert system.which('ceph') == '/usr/local/bin/ceph' + + def test_warnings_when_executable_isnt_matched(self, monkeypatch, capsys): + monkeypatch.setattr(system.os.path, 'isfile', lambda x: True) + monkeypatch.setattr(system.os.path, 'exists', lambda x: False) + system.which('exedir') + stdout, stderr = capsys.readouterr() + assert 'Absolute path not found for executable: exedir' in stdout + assert 'Ensure $PATH environment variable contains common executable locations' in stdout diff --git a/src/ceph-volume/ceph_volume/util/system.py b/src/ceph-volume/ceph_volume/util/system.py index 2b4473efe62..0ba46d36276 100644 --- a/src/ceph-volume/ceph_volume/util/system.py +++ b/src/ceph-volume/ceph_volume/util/system.py @@ -5,10 +5,11 @@ import pwd import platform import tempfile import uuid -from ceph_volume import process +from ceph_volume import process, terminal from . import as_string logger = logging.getLogger(__name__) +mlogger = terminal.MultiLogger(__name__) # TODO: get these out of here and into a common area for others to consume if platform.system() == 'FreeBSD': @@ -30,6 +31,28 @@ def generate_uuid(): return str(uuid.uuid4()) +def which(executable): + """find the location of an executable""" + locations = ( + '/usr/local/bin', + '/bin', + '/usr/bin', + '/usr/local/sbin', + '/usr/sbin', + '/sbin', + ) + + for location in locations: + executable_path = os.path.join(location, executable) + if os.path.exists(executable_path) and os.path.isfile(executable_path): + return executable_path + mlogger.warning('Absolute path not found for executable: %s', executable) + mlogger.warning('Ensure $PATH environment variable contains common executable locations') + # fallback to just returning the argument as-is, to prevent a hard fail, + # and hoping that the system might have the executable somewhere custom + return executable + + def get_ceph_user_ids(): """ Return the id and gid of the ceph user