Multi env support and Kube client integration

Kube friendly Beta

Package versions supports Kube env

Added:
  - Env type detection
  - New option: --use-env, for selecting env
    when function supports multiple detected envs
  - Updated config loading
  - Each module and command type has supported env check
    and stops execution if it is on unsupported env
  - Functions can support multiple envs
  - Kubernetes dependency
  - Kubenernetes API detection: local and remote
  - Package checking class hierachy for using Salt or Kube
  - Remote pod execution routine
  - Flexible SSH/SSH Forwarder classes: with, ssh,do(), etc
  - Multithreaded SSH script execution
  - Number of workers parameter, default 5

Fixed:
  - Config dependency
  - Command loading with supported envs list
  - Unittests structure and execution flow updated
  - Unittests fixes
  - Fixed debug mode handling
  - Unified command type/support routine
  - Nested attrs getter/setter

Change-Id: I3ade693ac21536e2b5dcee4b24d511749dc72759
Related-PROD: PROD-35811
diff --git a/cfg_checker/common/other.py b/cfg_checker/common/other.py
index 5a4c552..e3a3271 100644
--- a/cfg_checker/common/other.py
+++ b/cfg_checker/common/other.py
@@ -1,8 +1,11 @@
+import functools
 import os
 import re
 import subprocess
 
-from cfg_checker.common.const import all_roles_map, uknown_code
+from cfg_checker.common import logger_cli
+from cfg_checker.common.const import all_salt_roles_map, uknown_code, \
+    truth
 from cfg_checker.common.exception import ConfigException
 
 pkg_dir = os.path.dirname(__file__)
@@ -11,10 +14,22 @@
 pkg_dir = os.path.abspath(pkg_dir)
 
 
+# 'Dirty' and simple way to execute piped cmds
+def piped_shell(command):
+    logger_cli.debug("...cmd:'{}'".format(command))
+    _code, _out = subprocess.getstatusoutput(command)
+    if _code:
+        logger_cli.error("Non-zero return code: {}, '{}'".format(_code, _out))
+    return _out
+
+
+# 'Proper way to execute shell
 def shell(command):
+    logger_cli.debug("...cmd:'{}'".format(command))
     _ps = subprocess.Popen(
         command.split(),
-        stdout=subprocess.PIPE
+        stdout=subprocess.PIPE,
+        universal_newlines=False
     ).communicate()[0].decode()
 
     return _ps
@@ -69,7 +84,7 @@
         # node role code checks
         _code = re.findall(r"[a-zA-Z]+", fqdn.split('.')[0])
         if len(_code) > 0:
-            if _code[0] in all_roles_map:
+            if _code[0] in all_salt_roles_map:
                 return _result()
             else:
                 # log warning here
@@ -107,11 +122,11 @@
         if _isvalid:
             # try to match it with ones in map
             _c = _code[0]
-            match = any([r in _c for r in all_roles_map.keys()])
+            match = any([r in _c for r in all_salt_roles_map.keys()])
             if match:
                 # no match, try to find it
                 match = False
-                for r in all_roles_map.keys():
+                for r in all_salt_roles_map.keys():
                     _idx = _c.find(r)
                     if _idx > -1:
                         _c = _c[_idx:]
@@ -153,5 +168,25 @@
 
         return _valid, _invalid
 
+    @staticmethod
+    def to_bool(value):
+        if value.lower() in truth:
+            return True
+        else:
+            return False
+
+    # helper functions to get nested attrs
+    # https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-subobjects-chained-properties
+    # using wonder's beautiful simplification:
+    # https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects/31174427?noredirect=1#comment86638618_31174427
+    def rsetattr(self, obj, attr, val):
+        pre, _, post = attr.rpartition('.')
+        return setattr(self.rgetattr(obj, pre) if pre else obj, post, val)
+
+    def rgetattr(self, obj, attr, *args):
+        def _getattr(obj, attr):
+            return getattr(obj, attr, *args)
+        return functools.reduce(_getattr, [obj] + attr.split('.'))
+
 
 utils = Utils()