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