Unified command execution and unit tests

- All arguments inits moved to own clases
- Added unified way to execute commands
- Unit test structure and very basic tests
- Command line script to test coverage
- Argument parsers moved to corresponding commands
- Automatic parsers and command mapping

Change-Id: Id099d14702d9590729583dfd9574bd57022efac5
Related-PROD: PROD-28199
diff --git a/cfg_checker/cli/command.py b/cfg_checker/cli/command.py
index 0a9c2a2..e6d9cd9 100644
--- a/cfg_checker/cli/command.py
+++ b/cfg_checker/cli/command.py
@@ -1,19 +1,33 @@
+import pkgutil
 import sys
 import traceback
 
-from cfg_checker.common import logger_cli
+from cfg_checker.common import config, logger, logger_cli
 from cfg_checker.common.exception import CheckerException
+from cfg_checker.helpers.args_utils import MyParser
 
-# TODO: auto-create types for each command
-package_command_types = ['report']
-network_command_types = ['check', 'report']
-reclass_command_types = ['list', 'diff']
+main_pkg_name = __name__.split('.')[0]
+mods_package_name = "modules"
+mods_import_path = main_pkg_name + '.' + mods_package_name
+mods_prefix = mods_import_path + '.'
 
-commands = {
-    'packages': package_command_types,
-    'network': network_command_types,
-    'reclass': reclass_command_types
-}
+commands = {}
+parsers = {}
+helps = {}
+# Pure dynamic magic, loading all 'do_*' methods from available modules
+_m = __import__(mods_import_path, fromlist=[main_pkg_name])
+for _imp, modName, isMod in pkgutil.iter_modules(_m.__path__, mods_prefix):
+    # iterate all packages, add to dict
+    if isMod:
+        # load module
+        _p = _imp.find_module(modName).load_module(modName)
+        # create a shortname
+        mod_name = modName.split('.')[-1]
+        # A package! Create it and add commands
+        commands[mod_name] = \
+            [_n[3:] for _n in dir(_p) if _n.startswith("do_")]
+        parsers[mod_name] = getattr(_p, 'init_parser')
+        helps[mod_name] = getattr(_p, 'command_help')
 
 
 def execute_command(args, command):
@@ -34,7 +48,7 @@
         # form function name to call
         _method_name = "do_" + args.type
         _target_module = __import__(
-            "cfg_checker.modules."+command,
+            mods_prefix + command,
             fromlist=[""]
         )
         _method = getattr(_target_module, _method_name)
@@ -57,3 +71,23 @@
             ))
         ))
         return 1
+
+
+def cli_command(_title, _name):
+    my_parser = MyParser(_title)
+    parsers[_name](my_parser)
+
+    # parse arguments
+    try:
+        args = my_parser.parse_args()
+    except TypeError:
+        logger_cli.info("\n# Please, check arguments")
+        sys.exit(0)
+
+    # force use of sudo
+    config.ssh_uses_sudo = True
+
+    # Execute the command
+    result = execute_command(args, _name)
+    logger.debug(result)
+    sys.exit(result)
diff --git a/cfg_checker/cli/network.py b/cfg_checker/cli/network.py
index b53311d..5c5a4e2 100644
--- a/cfg_checker/cli/network.py
+++ b/cfg_checker/cli/network.py
@@ -1,59 +1,12 @@
-import sys
-
-from cfg_checker.common import config, logger, logger_cli
-from cfg_checker.helpers.args_utils import MyParser
-
-from command import execute_command
+from command import cli_command
 
 
-def init_network_parser(_parser):
-    # network subparser
-    net_subparsers = _parser.add_subparsers(dest='type')
-
-    net_check_parser = net_subparsers.add_parser(
-        'check',
-        help="Do network check and print the result"
+def entrypoint():
+    cli_command(
+        "# Mirantis Cloud Network checker",
+        'network'
     )
 
-    net_check_parser.add_argument(
-        '--detailed',
-        action="store_true", default=False,
-        help="Print error details after summary"
-    )
-
-    net_report_parser = net_subparsers.add_parser(
-        'report',
-        help="Generate network check report"
-    )
-
-    net_report_parser.add_argument(
-        '--html',
-        metavar='network_html_filename',
-        help="HTML filename to save report"
-    )
-
-    return _parser
-
-
-def cli_network():
-    net_parser = MyParser("# Mirantis Cloud Network checker")
-    init_network_parser(net_parser)
-
-    # parse arguments
-    try:
-        args = net_parser.parse_args()
-    except TypeError:
-        logger_cli.info("\n# Please, check arguments")
-        sys.exit(0)
-
-    # force use of sudo
-    config.ssh_uses_sudo = True
-
-    # Execute the command
-    result = execute_command(args, 'network')
-    logger.debug(result)
-    sys.exit(result)
-
 
 if __name__ == '__main__':
-    cli_network()
+    entrypoint()
diff --git a/cfg_checker/cli/package.py b/cfg_checker/cli/package.py
deleted file mode 100644
index 5fe7e0b..0000000
--- a/cfg_checker/cli/package.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import sys
-
-from cfg_checker.common import config, logger, logger_cli
-from cfg_checker.helpers.args_utils import MyParser
-
-from command import execute_command
-
-
-def init_package_parser(_parser):
-    # packages subparser
-    pkg_subparsers = _parser.add_subparsers(dest='type')
-
-    pkg_report_parser = pkg_subparsers.add_parser(
-        'report',
-        help="Report package versions to HTML file"
-    )
-    pkg_report_parser.add_argument(
-        '--full',
-        action="store_true", default=False,
-        help="HTML report will have all of the packages, not just errors"
-    )
-    pkg_report_parser.add_argument(
-        '--html',
-        metavar='packages_html_filename',
-        help="HTML filename to save report"
-    )
-    pkg_report_parser.add_argument(
-        '--csv',
-        metavar='packages_csv_filename',
-        help="CSV filename to save report"
-    )
-
-    return _parser
-
-
-def cli_package():
-    pkg_parser = MyParser("# Mirantis Cloud Package checker")
-    init_package_parser(pkg_parser)
-
-    # parse arguments
-    try:
-        args = pkg_parser.parse_args()
-    except TypeError:
-        logger_cli.info("\n# Please, check arguments")
-        sys.exit(0)
-
-    # force use of sudo
-    config.ssh_uses_sudo = True
-
-    # Execute the command
-    result = execute_command(args, 'packages')
-    logger.debug(result)
-    sys.exit(result)
-
-
-if __name__ == '__main__':
-    cli_package()
diff --git a/cfg_checker/cli/packages.py b/cfg_checker/cli/packages.py
new file mode 100644
index 0000000..c44e5bc
--- /dev/null
+++ b/cfg_checker/cli/packages.py
@@ -0,0 +1,12 @@
+from command import cli_command
+
+
+def entrypoint():
+    cli_command(
+        "# Mirantis Cloud Package checker",
+        'packages'
+    )
+
+
+if __name__ == '__main__':
+    entrypoint()
diff --git a/cfg_checker/cli/reclass.py b/cfg_checker/cli/reclass.py
index fc5961a..24eb8e2 100644
--- a/cfg_checker/cli/reclass.py
+++ b/cfg_checker/cli/reclass.py
@@ -1,67 +1,12 @@
-import sys
-
-from cfg_checker.common import config, logger, logger_cli
-from cfg_checker.helpers.args_utils import MyParser
-
-from command import execute_command
+from command import cli_command
 
 
-def init_reclass_parser(_parser):
-    # reclass subparsers
-    reclass_subparsers = _parser.add_subparsers(dest='type')
-    reclass_list_parser = reclass_subparsers.add_parser(
-        'list',
-        help="List models available to compare"
+def entrypoint():
+    cli_command(
+        '# Mirantis Cloud Reclass comparer"',
+        'packages'
     )
-    reclass_list_parser.add_argument(
-        "-p",
-        "--models-path",
-        default="/srv/salt/",
-        help="Global path to search models in"
-    )
-
-    reclass_diff_parser = reclass_subparsers.add_parser(
-        'diff',
-        help="List models available to compare"
-    )
-    reclass_diff_parser.add_argument(
-        "--model1",
-        required=True,
-        help="Model A <path>. Model name is the folder name"
-    )
-    reclass_diff_parser.add_argument(
-        "--model2",
-        required=True,
-        help="Model B <path>. Model name is the folder name"
-    )
-    reclass_diff_parser.add_argument(
-        '--html',
-        metavar='reclass_html_filename',
-        help="HTML filename to save report"
-    )
-
-    return _parser
-
-
-def cli_reclass():
-    net_parser = MyParser("# Mirantis Cloud Reclass comparer")
-    init_reclass_parser(net_parser)
-
-    # parse arguments
-    try:
-        args = net_parser.parse_args()
-    except TypeError:
-        logger_cli.info("\n# Please, check arguments")
-        sys.exit(0)
-
-    # force use of sudo
-    config.ssh_uses_sudo = True
-
-    # Execute the command
-    result = execute_command(args, 'reclass')
-    logger.debug(result)
-    sys.exit(result)
 
 
 if __name__ == "__main__":
-    cli_reclass()
+    entrypoint()