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/salt_utils.py b/cfg_checker/common/salt_utils.py
index b3f5cae..f7ea50b 100644
--- a/cfg_checker/common/salt_utils.py
+++ b/cfg_checker/common/salt_utils.py
@@ -7,12 +7,13 @@
 
 import requests
 
-from cfg_checker.common import config, logger, logger_cli
+from cfg_checker.common import logger, logger_cli
 from cfg_checker.common.exception import InvalidReturnException, SaltException
 from cfg_checker.common.other import shell
+from cfg_checker.common.ssh_utils import ssh_shell_p
 
 
-def _extract_password(_raw):
+def _extract_salt_password(_raw):
     if not isinstance(_raw, str):
         raise InvalidReturnException(_raw)
     else:
@@ -26,45 +27,40 @@
     return _json["local"]
 
 
-def get_remote_env_password():
+def get_remote_salt_env_password(config):
     """Uses ssh call with configured options to get password from salt master
 
     :return: password string
     """
     _salt_cmd = "salt-call --out=json pillar.get _param:salt_api_password"
-    _ssh_cmd = ["ssh"]
-    # Build SSH cmd
-    if config.ssh_key:
-        _ssh_cmd.append("-i " + config.ssh_key)
-    if config.ssh_user:
-        _ssh_cmd.append(config.ssh_user+'@'+config.ssh_host)
-    else:
-        _ssh_cmd.append(config.ssh_host)
-    if config.ssh_uses_sudo:
-        _ssh_cmd.append("sudo")
-
-    _ssh_cmd.append(_salt_cmd)
-    _ssh_cmd = " ".join(_ssh_cmd)
-    logger_cli.debug("... calling salt: '{}'".format(_ssh_cmd))
+    logger_cli.debug("... calling salt using ssh: '{}'".format(_salt_cmd))
     try:
-        _result = shell(_ssh_cmd)
+        _result = ssh_shell_p(
+            _salt_cmd,
+            config.ssh_host,
+            username=config.ssh_user,
+            keypath=config.ssh_key,
+            piped=False,
+            use_sudo=config.ssh_uses_sudo,
+            silent=True
+        )
         if len(_result) < 1:
             raise InvalidReturnException(
                 "# Empty value returned for '{}".format(
-                    _ssh_cmd
+                    _salt_cmd
                 )
             )
         else:
-            return _extract_password(_result)
+            return _extract_salt_password(_result)
     except OSError as e:
         raise SaltException(
             "Salt error calling '{}': '{}'\n"
-            "\nConsider checking 'SALT_ENV' "
-            "and '<pkg>/etc/<env>.env' files".format(_ssh_cmd, e.strerror)
+            "\nConsider checking 'MCP_ENV' "
+            "and '<pkg>/etc/<env>.env' files".format(_salt_cmd, e.strerror)
         )
 
 
-def get_local_password():
+def get_salt_local_password(config):
     """Calls salt locally to get password from the pillar
 
     :return: password string
@@ -80,10 +76,10 @@
     except OSError as e:
         raise SaltException(
             "Salt error calling '{}': '{}'\n"
-            "\nConsider checking 'SALT_ENV' "
+            "\nConsider checking 'MCP_ENV' "
             "and '<pkg>/etc/<env>.env' files".format(_cmd, e.strerror)
         )
-    return _extract_password(_result)
+    return _extract_salt_password(_result)
 
 
 def list_to_target_string(node_list, separator):
@@ -94,9 +90,6 @@
 
 
 class SaltRest(object):
-    _host = config.salt_host
-    _port = config.salt_port
-    uri = "http://" + config.salt_host + ":" + config.salt_port
     _auth = {}
 
     default_headers = {
@@ -105,7 +98,13 @@
         'X-Auth-Token': None
     }
 
-    def __init__(self):
+    def __init__(self, config):
+        self.config = config
+
+        self._host = config.mcp_host
+        self._port = config.salt_port
+        self.uri = "http://" + config.mcp_host + ":" + config.salt_port
+
         self._token = self._login()
         self.last_response = None
 
@@ -154,12 +153,12 @@
 
     def _login(self):
         # if there is no password - try to get local, if this available
-        if config.salt_env == "local":
-            _pass = get_local_password()
+        if self.config.env_name == "local":
+            _pass = get_salt_local_password(self.config)
         else:
-            _pass = get_remote_env_password()
+            _pass = get_remote_salt_env_password(self.config)
         login_payload = {
-            'username': config.salt_user,
+            'username': self.config.salt_user,
             'password': _pass,
             'eauth': 'pam'
         }
@@ -212,8 +211,8 @@
 class SaltRemote(SaltRest):
     master_node = ""
 
-    def __init__(self):
-        super(SaltRemote, self).__init__()
+    def __init__(self, config):
+        super(SaltRemote, self).__init__(config)
 
     def cmd(
             self,
@@ -226,7 +225,7 @@
             tgt_type=None,
             timeout=None
     ):
-        _timeout = timeout if timeout is not None else config.salt_timeout
+        _timeout = timeout if timeout is not None else self.config.salt_timeout
         _payload = {
             'fun': fun,
             'tgt': tgt,
@@ -256,7 +255,7 @@
         _payload = {
             'client': 'runner',
             'fun': fun,
-            'timeout': config.salt_timeout
+            'timeout': self.config.salt_timeout
         }
 
         if kwarg:
@@ -275,7 +274,7 @@
         _payload = {
             'client': 'wheel',
             'fun': fun,
-            'timeout': config.salt_timeout
+            'timeout': self.config.salt_timeout
         }
 
         if arg:
@@ -358,11 +357,13 @@
 
         :return: json result from salt test.ping
         """
-        if config.skip_nodes:
-            logger.info("# Nodes to be skipped: {0}".format(config.skip_nodes))
+        if self.config.skip_nodes:
+            logger.info(
+                "# Nodes to be skipped: {0}".format(self.config.skip_nodes)
+            )
             _r = self.cmd(
                 '* and not ' + list_to_target_string(
-                    config.skip_nodes,
+                    self.config.skip_nodes,
                     'and not'
                 ),
                 'test.ping',