@cryptotaxi247 / netdata-1 / commits / 041adfdaa

ExecutableService_class: uses find_binary method to find absolute path for command, other logic fixes

Ilya committed Mar 8, 2017 at 00:35 UTC 041adfdaa01eeebcf0e716c07529b369efb2e922
1 file changed +51 -38
python.d/python_modules/base.py
+51 -38
@@ -67,6 +67,7 @@ class SimpleService(threading.Thread):
67 self.__first_run = True
68 self.order = []
69 self.definitions = {}
70 + self._data_from_check = dict()
71 if configuration is None:
72 self.error("BaseService: no configuration parameters supplied. Cannot create Service.")
73 raise RuntimeError
@@ -391,7 +392,7 @@ class SimpleService(threading.Thread):
392 Create charts
393 :return: boolean
394 """
394 - data = self._get_data()
395 + data = self._data_from_check or self._get_data()
396 if data is None:
397 self.debug("failed to receive data during create().")
398 return False
@@ -437,7 +438,8 @@ class SimpleService(threading.Thread):
438
439 return updated
440
440 - def find_binary(self, binary):
441 + @staticmethod
442 + def find_binary(binary):
443 try:
444 if isinstance(binary, str):
445 binary = os.path.basename(binary)
@@ -857,63 +859,74 @@ class LogService(SimpleService):
859
860
861 class ExecutableService(SimpleService):
860 - bad_substrings = ('&', '|', ';', '>', '<')
862
863 def __init__(self, configuration=None, name=None):
863 - self.command = ""
864 SimpleService.__init__(self, configuration=configuration, name=name)
865 + self.command = None
866
867 def _get_raw_data(self):
868 """
869 Get raw data from executed command
869 - :return: str
870 + :return: <list>
871 """
872 try:
873 p = Popen(self.command, stdout=PIPE, stderr=PIPE)
873 - except Exception as e:
874 - self.error("Executing command", self.command, "resulted in error:", str(e))
874 + except Exception as error:
875 + self.error("Executing command", self.command, "resulted in error:", str(error))
876 return None
876 - data = []
877 + data = list()
878 for line in p.stdout.readlines():
878 - data.append(str(line.decode()))
879 -
880 - if len(data) == 0:
881 - self.error("No data collected.")
882 - return None
879 + data.append(line.decode())
880
884 - return data
881 + return data or None
882
883 def check(self):
884 """
885 Parse basic configuration, check if command is whitelisted and is returning values
889 - :return: boolean
886 + :return: <boolean>
887 """
891 - if self.name is not None or self.name != str(None):
892 - self.name = ""
893 - else:
894 - self.name = str(self.name)
895 - try:
896 - self.command = str(self.configuration['command'])
897 - except (KeyError, TypeError):
898 - self.info("No command specified. Using: '" + self.command + "'")
899 - # Splitting self.command on every space so subprocess.Popen reads it properly
900 - self.command = self.command.split(' ')
888 + # Preference: 1. "command" from configuration file 2. "command" from plugin (if specified)
889 + if 'command' in self.configuration:
890 + self.command = self.configuration['command']
891 +
892 + # "command" must be: 1.not None 2. type <str>
893 + if not (self.command and isinstance(self.command, str)):
894 + self.error('Command is not defined or command type is not <str>')
895 + return False
896 +
897 + # Split "command" into: 1. command <str> 2. options <list>
898 + command, opts = self.command.split()[0], self.command.split()[1:]
899 +
900 + # Check for "bad" symbols in options. No pipes, redirects etc. TODO: what is missing?
901 + bad_opts = set(''.join(opts)) & set(['&', '|', ';', '>', '<'])
902 + if bad_opts:
903 + self.error("Bad command argument(s): %s" % bad_opts)
904 + return False
905
902 - for arg in self.command[1:]:
903 - if any(st in arg for st in self.bad_substrings):
904 - self.error("Bad command argument:" + " ".join(self.command[1:]))
906 + # Find absolute path ('echo' => '/bin/echo')
907 + if '/' not in command:
908 + command = self.find_binary(command)
909 + if not command:
910 + self.error('Can\'t locate "%s" binary in PATH(%s)' % (self.command, PATH))
911 + return False
912 + # Check if binary exist and executable
913 + else:
914 + if not (os.path.isfile(command) and os.access(command, os.X_OK)):
915 + self.error('"%s" is not a file or not executable' % command)
916 return False
917
907 - # test command and search for it in /usr/sbin or /sbin when failed
908 - base = self.command[0].split('/')[-1]
909 - if self._get_raw_data() is None:
910 - for prefix in ['/sbin/', '/usr/sbin/']:
911 - self.command[0] = prefix + base
912 - if os.path.isfile(self.command[0]):
913 - break
918 + self.command = [command] + opts if opts else [command]
919
915 - if self._get_data() is None or len(self._get_data()) == 0:
916 - self.error("Command", self.command, "returned no data")
920 + try:
921 + data = self._get_data()
922 + except Exception as error:
923 + self.error('_get_data() failed. Command: %s. Error: %s' % (self.command, error))
924 return False
925
919 - return True
926 + if isinstance(data, dict) and data:
927 + # We need this for create() method. No reason to execute get_data() again if result is not empty dict()
928 + self._data_from_check = data
929 + return True
930 + else:
931 + self.error("Command", str(self.command), "returned no data")
932 + return False