Add initial code
[ta/build-tools.git] / tools / executor.py
diff --git a/tools/executor.py b/tools/executor.py
new file mode 100755 (executable)
index 0000000..f2a428c
--- /dev/null
@@ -0,0 +1,107 @@
+# Copyright 2019 Nokia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import logging
+import shlex
+import subprocess
+
+
+class ExecutionError(Exception):
+    def __init__(self, msg, result):
+        super(ExecutionError, self).__init__(msg)
+        self.result = result
+
+
+class Result(object):
+    def __init__(self, status, stdout, stderr):
+        self.status = status
+        self.stdout = stdout
+        self.stderr = stderr
+
+    @property
+    def str_status(self):
+        return 'Status:"{}"'.format(self.status)
+
+    @property
+    def str_stdout(self):
+        return 'Stdout:"{}"'.format(self.stdout)
+
+    @property
+    def str_stderr(self):
+        return 'Stderr:"{}"'.format(self.stderr)
+
+    def __str__(self):
+        return '\n'.join([self.str_status, self.str_stdout, self.str_stderr])
+
+
+def run(*args, **kwargs):
+    return Executor().run(*args, **kwargs)
+
+
+class Executor(object):
+    def __init__(self, shell=False, chdir=None):
+        self.shell = shell
+        self.chdir = chdir
+
+    def run(self, cmd, raise_on_error=True, raise_on_stderr=True, retries=0):
+        result = self._run(cmd, retries)
+        if raise_on_error and result.status != 0:
+            raise ExecutionError('Command "{}" execution status NOT zero: {}'.format(
+                cmd, str(result)), result.status)
+        if raise_on_stderr and result.stderr:
+            raise ExecutionError('Command "{}" execution stderr not empty: {}'.format(
+                cmd, str(result)), result.status)
+        return result
+
+    @staticmethod
+    def _log_result(result):
+        logging.debug('Result: %s', str(result))
+
+    def _run(self, cmd, retries):
+        logstr = 'Executing command: "{}"'.format(cmd)
+        cwd = os.getcwd()
+        if self.chdir is not None:
+            os.chdir(self.chdir)
+            logstr += ' in dir {}'.format(self.chdir)
+        logging.debug(logstr)
+        result = self._run_with_retries(cmd, retries)
+        if self.chdir is not None:
+            os.chdir(cwd)
+        return result
+
+    def _run_with_retries(self, cmd, retries):
+        result = self._execute(cmd)
+        while result.status != 0 and retries > 0:
+            logging.debug('Retrying, retries left: %s', retries)
+            retries -= 1
+            result = self._execute(cmd)
+            self._log_result(result)
+            if result.status == 0:
+                break
+        return result
+
+    def _execute(self, cmd):
+        if not self.shell:
+            if isinstance(cmd, list):
+                cmd_args = cmd[:]
+            else:
+                cmd_args = shlex.split(cmd)
+        else:
+            cmd_args = cmd
+        process = subprocess.Popen(cmd_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                                   shell=self.shell)
+        stdout, stderr = process.communicate()
+        result = Result(process.returncode, stdout, stderr)
+        return result