diff --git a/validations_libs/cli/history.py b/validations_libs/cli/history.py index f3ec8f84..0d01becc 100644 --- a/validations_libs/cli/history.py +++ b/validations_libs/cli/history.py @@ -62,11 +62,10 @@ class ListHistory(BaseLister): ("Limiting output to the maximum of " "{} last validations.").format(history_limit)) - actions = ValidationActions() + actions = ValidationActions(log_path=parsed_args.validation_log_dir) return actions.show_history( validation_ids=parsed_args.validation, - log_path=parsed_args.validation_log_dir, history_limit=history_limit) diff --git a/validations_libs/cli/run.py b/validations_libs/cli/run.py index cf1fd303..5072bca7 100644 --- a/validations_libs/cli/run.py +++ b/validations_libs/cli/run.py @@ -171,7 +171,8 @@ class Run(BaseCommand): # Get config: config = self.base.config - v_actions = ValidationActions(parsed_args.validation_dir) + v_actions = ValidationActions( + parsed_args.validation_dir, log_path=parsed_args.validation_log_dir) # Ansible execution should be quiet while using the validations_json # default callback and be verbose while passing ANSIBLE_SDTOUT_CALLBACK # environment variable to Ansible through the --extra-env-vars argument @@ -213,7 +214,6 @@ class Run(BaseCommand): python_interpreter=parsed_args.python_interpreter, quiet=quiet_mode, ssh_user=parsed_args.ssh_user, - log_path=parsed_args.validation_log_dir, validation_config=config, skip_list=skip_list) except RuntimeError as e: diff --git a/validations_libs/cli/show.py b/validations_libs/cli/show.py index ca40eaed..26c22f72 100644 --- a/validations_libs/cli/show.py +++ b/validations_libs/cli/show.py @@ -71,8 +71,8 @@ class ShowGroup(BaseLister): self.base.set_argument_parser(self, parsed_args) v_actions = ValidationActions(parsed_args.validation_dir) + return v_actions.group_information( - constants.VALIDATION_GROUPS_INFO, validation_config=self.base.config) diff --git a/validations_libs/group.py b/validations_libs/group.py index 2bb8f986..ca2a91e6 100644 --- a/validations_libs/group.py +++ b/validations_libs/group.py @@ -68,10 +68,10 @@ class Group(object): return self.data @property - def get_formated_group(self): - """Get a formated content for output display + def get_formated_groups(self): + """Get a formated list of groups for output display - :return: + :return: information about parsed groups :rtype: `list` of `tuples` :Example: diff --git a/validations_libs/tests/cli/test_history.py b/validations_libs/tests/cli/test_history.py index f33f1011..6b361755 100644 --- a/validations_libs/tests/cli/test_history.py +++ b/validations_libs/tests/cli/test_history.py @@ -29,7 +29,8 @@ class TestListHistory(BaseCommand): self.cmd = history.ListHistory(self.app, None) @mock.patch('validations_libs.validation_actions.ValidationActions.' - 'show_history') + 'show_history', + autospec=True) def test_list_history(self, mock_history): arglist = ['--validation-log-dir', '/foo/log/dir'] verifylist = [('validation_log_dir', '/foo/log/dir')] @@ -80,7 +81,8 @@ class TestGetHistory(BaseCommand): @mock.patch('validations_libs.validation_logs.ValidationLogs.' 'get_logfile_content_by_uuid', - return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST) + return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST, + autospec=True) def test_get_history(self, mock_logs): arglist = ['123'] verifylist = [('uuid', '123')] @@ -91,7 +93,8 @@ class TestGetHistory(BaseCommand): @mock.patch('validations_libs.validation_logs.ValidationLogs.' 'get_logfile_content_by_uuid', - return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST) + return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST, + autospec=True) def test_get_history_from_log_dir(self, mock_logs): arglist = ['123', '--validation-log-dir', '/foo/log/dir'] verifylist = [('uuid', '123'), ('validation_log_dir', '/foo/log/dir')] @@ -102,7 +105,8 @@ class TestGetHistory(BaseCommand): @mock.patch('validations_libs.validation_logs.ValidationLogs.' 'get_logfile_content_by_uuid', - return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST) + return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST, + autospec=True) def test_get_history_full_arg(self, mock_logs): arglist = ['123', '--full'] verifylist = [('uuid', '123'), ('full', True)] diff --git a/validations_libs/tests/cli/test_list.py b/validations_libs/tests/cli/test_list.py index e8537c92..4fd27fbc 100644 --- a/validations_libs/tests/cli/test_list.py +++ b/validations_libs/tests/cli/test_list.py @@ -30,7 +30,8 @@ class TestList(BaseCommand): @mock.patch('validations_libs.validation_actions.ValidationActions.' 'list_validations', - return_value=fakes.VALIDATIONS_LIST) + return_value=fakes.VALIDATIONS_LIST, + autospec=True) def test_list_validations(self, mock_list): arglist = ['--validation-dir', 'foo'] verifylist = [('validation_dir', 'foo')] @@ -59,7 +60,8 @@ class TestList(BaseCommand): @mock.patch('validations_libs.validation_actions.ValidationActions.' 'list_validations', - return_value=[]) + return_value=[], + autospec=True) def test_list_validations_empty(self, mock_list): arglist = ['--validation-dir', 'foo'] verifylist = [('validation_dir', 'foo')] @@ -69,7 +71,8 @@ class TestList(BaseCommand): self.assertEqual(result, []) @mock.patch('validations_libs.utils.parse_all_validations_on_disk', - return_value=fakes.VALIDATIONS_LIST_GROUP) + return_value=fakes.VALIDATIONS_LIST_GROUP, + autospec=True) def test_list_validations_group(self, mock_list): arglist = ['--validation-dir', 'foo', '--group', 'prep'] verifylist = [('validation_dir', 'foo'), @@ -82,7 +85,8 @@ class TestList(BaseCommand): self.assertEqual(result, val_list) @mock.patch('validations_libs.utils.parse_all_validations_on_disk', - return_value=fakes.VALIDATIONS_LIST_GROUP) + return_value=fakes.VALIDATIONS_LIST_GROUP, + autospec=True) def test_list_validations_by_category(self, mock_list): arglist = ['--validation-dir', 'foo', '--category', 'networking'] verifylist = [('validation_dir', 'foo'), @@ -95,7 +99,8 @@ class TestList(BaseCommand): self.assertEqual(result, val_list) @mock.patch('validations_libs.utils.parse_all_validations_on_disk', - return_value=fakes.VALIDATIONS_LIST_GROUP) + return_value=fakes.VALIDATIONS_LIST_GROUP, + autospec=True) def test_list_validations_by_product(self, mock_list): arglist = ['--validation-dir', 'foo', '--product', 'product1'] verifylist = [('validation_dir', 'foo'), diff --git a/validations_libs/tests/cli/test_run.py b/validations_libs/tests/cli/test_run.py index 66f17759..b60e7bef 100644 --- a/validations_libs/tests/cli/test_run.py +++ b/validations_libs/tests/cli/test_run.py @@ -32,7 +32,8 @@ class TestRun(BaseCommand): @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=None) + return_value=None, + autospec=True) def test_run_command_return_none(self, mock_run): args = self._set_args(['--validation', 'foo']) verifylist = [('validation_name', ['foo'])] @@ -43,7 +44,8 @@ class TestRun(BaseCommand): @mock.patch('validations_libs.cli.common.open') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN)) + return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN), + autospec=True) def test_run_command_success(self, mock_run, mock_open): args = self._set_args(['--validation', 'foo']) verifylist = [('validation_name', ['foo'])] @@ -65,10 +67,12 @@ class TestRun(BaseCommand): return_value='doe') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN)) + return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN), + autospec=True) @mock.patch('validations_libs.utils.load_config', return_value={}) - def test_run_command_extra_vars(self, mock_config, mock_run, - mock_user, mock_print, mock_log_dir): + def test_run_command_extra_vars(self, mock_config, + mock_run, mock_user, + mock_print, mock_log_dir): run_called_args = { 'inventory': 'localhost', 'limit_hosts': None, @@ -83,7 +87,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': None } @@ -95,7 +98,9 @@ class TestRun(BaseCommand): self._set_args(arglist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - mock_run.assert_called_with(**run_called_args) + call_args = mock_run.mock_calls[0][2] + + self.assertDictEqual(call_args, run_called_args) @mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR') @mock.patch('validations_libs.cli.common.print_dict') @@ -103,10 +108,11 @@ class TestRun(BaseCommand): return_value='doe') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN)) + return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN), + autospec=True) @mock.patch('validations_libs.utils.load_config', return_value={}) - def test_run_command_extra_vars_twice(self, mock_config, - mock_run, mock_user, mock_print, + def test_run_command_extra_vars_twice(self, mock_config, mock_run, + mock_user, mock_print, mock_log_dir): run_called_args = { 'inventory': 'localhost', @@ -122,7 +128,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': None } @@ -135,7 +140,9 @@ class TestRun(BaseCommand): self._set_args(arglist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - mock_run.assert_called_with(**run_called_args) + call_args = mock_run.mock_calls[0][2] + + self.assertDictEqual(call_args, run_called_args) def test_run_command_exclusive_vars(self): arglist = ['--validation', 'foo', @@ -154,7 +161,8 @@ class TestRun(BaseCommand): return_value='doe') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN)) + return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN), + autospec=True) @mock.patch('validations_libs.utils.load_config', return_value={}) def test_run_command_extra_vars_file(self, mock_config, mock_run, mock_user, mock_open, @@ -174,7 +182,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': None } @@ -186,14 +193,17 @@ class TestRun(BaseCommand): self._set_args(arglist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - mock_run.assert_called_with(**run_called_args) + call_args = mock_run.mock_calls[0][2] + + self.assertDictEqual(call_args, run_called_args) @mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR') @mock.patch('getpass.getuser', return_value='doe') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN)) + return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN), + autospec=True) @mock.patch('validations_libs.utils.load_config', return_value={}) def test_run_command_extra_env_vars(self, mock_config, mock_run, mock_user, mock_log_dir): @@ -211,7 +221,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': None } @@ -223,14 +232,17 @@ class TestRun(BaseCommand): self._set_args(arglist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - mock_run.assert_called_with(**run_called_args) + call_args = mock_run.mock_calls[0][2] + + self.assertDictEqual(call_args, run_called_args) @mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR') @mock.patch('getpass.getuser', return_value='doe') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN)) + return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN), + autospec=True) @mock.patch('validations_libs.utils.load_config', return_value={}) def test_run_command_extra_env_vars_with_custom_callback(self, mock_config, @@ -240,7 +252,6 @@ class TestRun(BaseCommand): run_called_args = { 'inventory': 'localhost', 'limit_hosts': None, - 'log_path': mock_log_dir, 'quiet': False, 'group': [], 'category': [], @@ -264,14 +275,17 @@ class TestRun(BaseCommand): self._set_args(arglist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - mock_run.assert_called_with(**run_called_args) + call_args = mock_run.mock_calls[0][2] + + self.assertDictEqual(call_args, run_called_args) @mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR') @mock.patch('getpass.getuser', return_value='doe') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN)) + return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN), + autospec=True) @mock.patch('validations_libs.utils.load_config', return_value={}) def test_run_command_extra_env_vars_twice(self, mock_config, mock_run, mock_user, @@ -290,7 +304,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': None } @@ -303,14 +316,17 @@ class TestRun(BaseCommand): self._set_args(arglist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - mock_run.assert_called_with(**run_called_args) + call_args = mock_run.mock_calls[0][2] + + self.assertDictEqual(call_args, run_called_args) @mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR') @mock.patch('getpass.getuser', return_value='doe') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN)) + return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN), + autospec=True) @mock.patch('validations_libs.utils.load_config', return_value={}) def test_run_command_extra_env_vars_and_extra_vars(self, mock_config, @@ -331,7 +347,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': None } @@ -345,7 +360,9 @@ class TestRun(BaseCommand): self._set_args(arglist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - mock_run.assert_called_with(**run_called_args) + call_args = mock_run.mock_calls[0][2] + + self.assertDictEqual(call_args, run_called_args) def test_run_command_exclusive_wrong_extra_vars(self): arglist = ['--validation', 'foo', @@ -356,50 +373,18 @@ class TestRun(BaseCommand): self.assertRaises(Exception, self.check_parser, self.cmd, arglist, verifylist) + @mock.patch('validations_libs.utils.find_config_file', + return_value="/etc/validations_foo.cfg") @mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR') @mock.patch('getpass.getuser', return_value='doe') @mock.patch('validations_libs.validation_actions.ValidationActions.' 'run_validations', - return_value=copy.deepcopy(fakes.FAKE_FAILED_RUN)) + return_value=copy.deepcopy(fakes.FAKE_FAILED_RUN), + autospec=True) @mock.patch('validations_libs.utils.load_config', return_value={}) - def test_run_command_failed_validation(self, mock_config, - mock_run, mock_user, mock_log_dir): - run_called_args = { - 'inventory': 'localhost', - 'limit_hosts': None, - 'group': [], - 'category': [], - 'product': [], - 'extra_vars': None, - 'validations_dir': '/usr/share/ansible/validation-playbooks', - 'base_dir': '/usr/share/ansible', - 'validation_name': ['foo'], - 'extra_env_vars': None, - 'python_interpreter': sys.executable, - 'quiet': True, - 'ssh_user': 'doe', - 'log_path': mock_log_dir, - 'validation_config': {}, - 'skip_list': None - } - - arglist = ['--validation', 'foo'] - verifylist = [('validation_name', ['foo'])] - - self._set_args(arglist) - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(RuntimeError, self.cmd.take_action, parsed_args) - mock_run.assert_called_with(**run_called_args) - - @mock.patch('getpass.getuser', - return_value='doe') - @mock.patch('validations_libs.validation_actions.ValidationActions.' - 'run_validations', - return_value=[]) - @mock.patch('validations_libs.utils.load_config', return_value={}) - def test_run_command_no_validation(self, mock_config, mock_run, - mock_user): + def test_run_command_failed_validation(self, mock_config, mock_run, mock_user, + mock_log_dir, mock_config_file): run_called_args = { 'inventory': 'localhost', 'limit_hosts': None, @@ -418,8 +403,56 @@ class TestRun(BaseCommand): 'skip_list': None } - arglist = ['--validation', 'foo'] - verifylist = [('validation_name', ['foo'])] + arglist = [ + '--validation', 'foo', + '--extra-vars', 'key=value', + '--extra-env-vars', 'key2=value2'] + verifylist = [ + ('validation_name', ['foo']), + ('extra_vars', {'key': 'value'}), + ('extra_env_vars', {'key2': 'value2'})] + + self._set_args(arglist) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(RuntimeError, self.cmd.take_action, parsed_args) + call_args = mock_run.mock_calls[0][2] + + self.assertDictEqual(call_args, run_called_args) + + @mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR') + @mock.patch('getpass.getuser', + return_value='doe') + @mock.patch('validations_libs.validation_actions.ValidationActions.' + 'run_validations', + return_value=[], + autospec=True) + def test_run_command_no_validation(self, mock_run, mock_user, mock_log_dir): + run_called_args = { + 'inventory': 'localhost', + 'limit_hosts': None, + 'group': [], + 'category': [], + 'product': [], + 'extra_vars': {'key': 'value'}, + 'validations_dir': '/usr/share/ansible/validation-playbooks', + 'base_dir': '/usr/share/ansible', + 'validation_name': ['foo'], + 'extra_env_vars': {'key2': 'value2'}, + 'python_interpreter': sys.executable, + 'quiet': True, + 'ssh_user': 'doe', + 'validation_config': {}, + 'skip_list': None, + 'log_path': mock_log_dir} + + arglist = [ + '--validation', 'foo', + '--extra-vars', 'key=value', + '--extra-env-vars', 'key2=value2'] + verifylist = [ + ('validation_name', ['foo']), + ('extra_vars', {'key': 'value'}), + ('extra_env_vars', {'key2': 'value2'})] self._set_args(arglist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -451,7 +484,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': None } @@ -489,7 +521,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': None } @@ -526,7 +557,6 @@ class TestRun(BaseCommand): 'python_interpreter': sys.executable, 'quiet': True, 'ssh_user': 'doe', - 'log_path': mock_log_dir, 'validation_config': {}, 'skip_list': {'key': 'value'} } diff --git a/validations_libs/tests/cli/test_show.py b/validations_libs/tests/cli/test_show.py index c4deb82f..af62ab02 100644 --- a/validations_libs/tests/cli/test_show.py +++ b/validations_libs/tests/cli/test_show.py @@ -17,6 +17,8 @@ try: except ImportError: import mock +from validations_libs import group +from validations_libs.validation_actions import ValidationActions from validations_libs.cli import show from validations_libs.tests import fakes from validations_libs.tests.cli.fakes import BaseCommand @@ -48,22 +50,17 @@ class TestShowGroup(BaseCommand): @mock.patch('yaml.safe_load', return_value=fakes.GROUP) @mock.patch('six.moves.builtins.open') def test_show_validations_group_info(self, mock_open, mock_yaml, mock_actions): - arglist = [] - mock_info = mock.MagicMock() - mock_info.group_information = mock.MagicMock(return_value='foo') - mock_actions.return_value = mock_info + method_calls = [ + mock.call(fakes.FAKE_VALIDATIONS_PATH), + mock.call().group_information(validation_config={})] + + arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) - group_info = self.cmd.take_action(parsed_args) - - mock_actions.assert_called_once_with( - validation_path=fakes.FAKE_VALIDATIONS_PATH) - - mock_info.group_information.assert_called_once() - - self.assertEqual('foo', group_info) + self.cmd.take_action(parsed_args) + mock_actions.assert_called_with(fakes.FAKE_VALIDATIONS_PATH) class TestShowParameter(BaseCommand): @@ -73,10 +70,15 @@ class TestShowParameter(BaseCommand): self.cmd = show.ShowParameter(self.app, None) @mock.patch('six.moves.builtins.open') - def test_show_validations_parameters_by_group(self, mock_open): + @mock.patch('validations_libs.validation_actions.ValidationActions.' + 'show_validations_parameters', autospec=True) + def test_show_validations_parameters_by_group(self, mock_show, mock_open): arglist = ['--group', 'prep'] verifylist = [('group', ['prep'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + mock_show.assert_called_once() def test_show_parameter_exclusive_group(self): arglist = ['--validation', 'foo', '--group', 'bar'] @@ -86,23 +88,32 @@ class TestShowParameter(BaseCommand): arglist, verifylist) @mock.patch('six.moves.builtins.open') - def test_show_validations_parameters_by_validations(self, mock_open): + @mock.patch('validations_libs.validation_actions.ValidationActions.' + 'show_validations_parameters', autospec=True) + def test_show_validations_parameters_by_validations(self, mock_show, mock_open): arglist = ['--group', 'prep'] verifylist = [('group', ['prep'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + mock_show.assert_called_once() @mock.patch('validations_libs.validation_actions.ValidationActions.' - 'show_validations_parameters') + 'show_validations_parameters', autospec=True) def test_show_validations_parameters_by_categories(self, mock_show): arglist = ['--category', 'os'] verifylist = [('category', ['os'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) + mock_show.assert_called_once() + @mock.patch('validations_libs.validation_actions.ValidationActions.' - 'show_validations_parameters') + 'show_validations_parameters', autospec=True) def test_show_validations_parameters_by_products(self, mock_show): arglist = ['--product', 'product1'] verifylist = [('product', ['product1'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) + + mock_show.assert_called_once() diff --git a/validations_libs/tests/fakes.py b/validations_libs/tests/fakes.py index 9e15fee7..514becc6 100644 --- a/validations_libs/tests/fakes.py +++ b/validations_libs/tests/fakes.py @@ -57,9 +57,9 @@ VALIDATION_LIST_RESULT = (('ID', 'Name', 'Groups', 'Categories', 'Products'), ['product1'])]) GROUPS_LIST = [ - ('group1', 'Group1 description'), - ('group2', 'Group2 description'), - ('group3', 'Group3 description'), + 'group1', + 'group2', + 'group3' ] BAD_VALIDATIONS_LOGS_CONTENTS_LIST = [{ diff --git a/validations_libs/tests/test_group.py b/validations_libs/tests/test_group.py index d408946c..36456fc0 100644 --- a/validations_libs/tests/test_group.py +++ b/validations_libs/tests/test_group.py @@ -40,7 +40,7 @@ class TestGroup(TestCase): def test_get_formated_group(self, mock_open, mock_yaml): grp = Group('/tmp/foo') ret = [('no-op', 'noop-foo'), ('post', 'post-foo'), ('pre', 'pre-foo')] - data = grp.get_formated_group + data = grp.get_formated_groups self.assertEqual(data, ret) @mock.patch('yaml.safe_load', return_value=fakes.GROUP) diff --git a/validations_libs/tests/test_utils.py b/validations_libs/tests/test_utils.py index 4657c3fc..9dc24524 100644 --- a/validations_libs/tests/test_utils.py +++ b/validations_libs/tests/test_utils.py @@ -354,22 +354,6 @@ class TestUtils(TestCase): result = utils.get_validation_group_name_list('/foo/groups.yaml') self.assertEqual(result, ['no-op', 'post', 'pre']) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk', - return_value=[fakes.FAKE_METADATA]) - @mock.patch('yaml.safe_load', return_value=fakes.GROUP) - @mock.patch('six.moves.builtins.open') - def test_get_validations_details(self, mock_open, mock_load, mock_parse): - - result = utils.get_validations_details('foo') - self.assertEqual(result, fakes.FAKE_METADATA) - - @mock.patch('six.moves.builtins.open') - def test_get_validations_details_wrong_type(self, mock_open): - validation = ['foo'] - self.assertRaises(TypeError, - utils.get_validations_details, - validation=validation) - def test_get_validations_parameters_wrong_validations_data_type(self): self.assertRaises(TypeError, utils.get_validations_parameters, diff --git a/validations_libs/tests/test_validation_actions.py b/validations_libs/tests/test_validation_actions.py index dbbf7e8e..775f80e2 100644 --- a/validations_libs/tests/test_validation_actions.py +++ b/validations_libs/tests/test_validation_actions.py @@ -20,8 +20,6 @@ except ImportError: import mock from mock import ANY -import json - from unittest import TestCase from validations_libs.tests import fakes @@ -62,8 +60,7 @@ class TestValidationActions(TestCase): skip_list = {'fake': {'hosts': 'ALL', 'reason': None, 'lp': None - } - } + }} run = ValidationActions() run_return = run.run_validations(playbook, inventory, @@ -124,9 +121,8 @@ class TestValidationActions(TestCase): } } - run = ValidationActions() + run = ValidationActions(log_path='/var/log/validations') run_return = run.run_validations(playbook, inventory, - log_path='/var/log/validations', validations_dir='/tmp/foo', skip_list=skip_list, limit_hosts='!cloud1') @@ -185,9 +181,8 @@ class TestValidationActions(TestCase): } } - run = ValidationActions() + run = ValidationActions(log_path='/var/log/validations') run_return = run.run_validations(playbook, inventory, - log_path='/var/log/validations', validations_dir='/tmp/foo', skip_list=skip_list, limit_hosts='cloud,cloud1,!cloud2') @@ -256,9 +251,8 @@ class TestValidationActions(TestCase): run = ValidationActions() self.assertRaises(RuntimeError, run.run_validations, - validation_name='fake.yaml', - validations_dir='/tmp/foo' - ) + validation_name=['fake'], + validations_dir='/tmp/foo') @mock.patch('validations_libs.utils.get_validations_playbook') def test_validation_run_not_all_found(self, mock_validation_play): @@ -271,7 +265,7 @@ class TestValidationActions(TestCase): validations_dir='/tmp/foo') except RuntimeError as runtime_error: self.assertEqual( - "Validation ['foo'] not found in /tmp/foo.", + "Following validations were not found in '/tmp/foo': foo", str(runtime_error)) else: self.fail("Runtime error exception should have been raised") @@ -435,7 +429,7 @@ class TestValidationActions(TestCase): @mock.patch('six.moves.builtins.open') def test_group_information(self, mock_open, mock_yaml, mock_data): v_actions = ValidationActions() - col, values = v_actions.group_information('512e') + col, values = v_actions.group_information() self.assertEqual(col, ('Groups', 'Description', 'Number of Validations')) self.assertEqual(values, [('no-op', 'noop-foo', 1), diff --git a/validations_libs/utils.py b/validations_libs/utils.py index cd45ea92..9596dc83 100644 --- a/validations_libs/utils.py +++ b/validations_libs/utils.py @@ -382,36 +382,6 @@ def get_validation_group_name_list(groups_path=None): return gp.get_groups_keys_list -def get_validations_details(validation): - """Return information details for a validation - - :param validation: Name of the validation - :type validation: `string` - :return: The information of the validation - :rtype: `dict` - :raises: a `TypeError` exception if `validation` is not a string - - :Example: - - >>> validation = "check-something" - >>> get_validations_details(validation) - {'description': 'Verify that the server has enough something.', - 'groups': ['group1', 'group2'], - 'categories': ['category1', 'category2'], - 'products': ['product1', 'product2'], - 'id': 'check-something', - 'name': 'Verify the server fits the something requirements'} - """ - if not isinstance(validation, six.string_types): - raise TypeError("The 'validation' argument must be a String") - - results = parse_all_validations_on_disk(constants.ANSIBLE_VALIDATION_DIR) - for r in results: - if r['id'] == validation: - return r - return {} - - def get_validations_data( validation, path=constants.ANSIBLE_VALIDATION_DIR, diff --git a/validations_libs/validation_actions.py b/validations_libs/validation_actions.py index 81f8ccff..a77d26b4 100644 --- a/validations_libs/validation_actions.py +++ b/validations_libs/validation_actions.py @@ -42,10 +42,23 @@ class ValidationActions(object): """ - def __init__(self, validation_path=constants.ANSIBLE_VALIDATION_DIR): - + def __init__(self, validation_path=constants.ANSIBLE_VALIDATION_DIR, + groups_path=constants.VALIDATION_GROUPS_INFO, + log_path=constants.VALIDATIONS_LOG_BASEDIR): + """ + :param groups_path: The absolute path to the validation groups + definition file. + (Defaults to ``constants.VALIDATION_GROUPS_INFO``) + :type groups_path: ``string`` + :param log_path: The absolute path of the validations logs directory + (Defaults to ``constants.VALIDATIONS_LOG_BASEDIR``) + :type log_path: ``string`` + """ self.log = logging.getLogger(__name__ + ".ValidationActions") self.validation_path = validation_path + self.log_path = log_path + + self.groups_path = groups_path def list_validations(self, groups=None, @@ -84,7 +97,7 @@ class ValidationActions(object): | val3 | val_name3 | ['group4'] | ['category3'] | ['product3'] | +------+-----------+----------------------+---------------+--------------+ - :Example: + :example: >>> path = "/foo/bar" >>> groups = ['group1'] @@ -130,13 +143,16 @@ class ValidationActions(object): return (column_names, return_values) def show_validations(self, validation, - log_path=constants.VALIDATIONS_LOG_BASEDIR, + log_path=None, validation_config=None): """Display detailed information about a Validation :param validation: The name of the validation :type validation: `string` - :param log_path: The absolute path of the validations logs + :param log_path: The absolute path of the validations logs. + The 'log_path' argument is deprecated and + will be removed in the next release. + Use the 'log_path' argument of the init method. :type log_path: `string` :param validation_config: A dictionary of configuration for Validation loaded from an validation.cfg file. @@ -146,7 +162,7 @@ class ValidationActions(object): :return: The detailed information for a validation :rtype: `dict` - :Example: + :example: >>> path = "/foo/bar" >>> validation = 'foo' @@ -166,7 +182,14 @@ class ValidationActions(object): """ self.log = logging.getLogger(__name__ + ".show_validations") # Get validation data: - vlog = ValidationLogs(log_path) + if log_path: + self.log.warning(( + "The 'log_path' argument is deprecated and" + " will be removed in the next release. " + "Use the 'log_path' argument of the init method.")) + vlog = ValidationLogs(log_path) + else: + vlog = ValidationLogs(self.log_path) data = v_utils.get_validations_data( validation, self.validation_path, @@ -185,75 +208,91 @@ class ValidationActions(object): data.update(data_format) return data - def _skip_hosts(self, skip_list, playbook, limit_hosts=None): + def _skip_hosts(self, skip_list, limit_hosts=None): """Check Ansible Hosts and return an updated limit_hosts - :param skip_list: The list of the validation to skip - :type validation_name: ``dict`` - :param playbook: The name of the playbook - :type base_dir: ``string`` + :param skip_list: list of hosts to skip with reasons why + :type skip_list: `dict` :param limit_hosts: Limit the execution to the hosts. :type limit_hosts: ``string`` :return the limit hosts according the skip_list or None if the validation should be skipped on ALL hosts. - :example - limit_hosts = 'cloud1,cloud2' - skip_list = {'xyz': {'hosts': 'cloud1', - 'reason': None, - 'lp': None} - } - >>> _skip_hosts(skip_list, playbook, limit_hosts='cloud1,cloud2') - 'cloud2,!cloud1' + :example: + >>> v_actions = ValidationActions() + >>> limit_hosts = 'cloud1,cloud2' + >>> skip_list = { + ... 'xyz': { + ... 'hosts': 'cloud1', + ... 'reason': None, + ... 'lp': None}} + >>> v_actions._skip_hosts(skip_list, validation, limit_hosts='cloud1,cloud2') + '!cloud1,cloud2' """ - hosts = skip_list[playbook].get('hosts', 'all') + hosts = skip_list.get('hosts', 'all') if hosts.lower() == 'all': return None - else: - _hosts = ['!{}'.format(hosts)] - if limit_hosts: - # check if skipped hosts is already in limit host - _hosts.extend([limit for limit in limit_hosts.split(',') - if hosts not in limit]) - return ','.join(_hosts) + _hosts = ['!{}'.format(hosts)] + if limit_hosts: + # check if skipped hosts is already in limit host + _hosts.extend([limit for limit in limit_hosts.split(',') + if hosts not in limit]) + return ','.join(_hosts) def _skip_playbook(self, skip_list, playbook, limit_hosts=None): - """Check if playbook is in the ski plist - :param skip_list: The list of the validation to skip - :type validation_name: ``dict`` + """Check if playbook is in the skiplist + :param skip_list: Dictionary of validations to skip. + :type skip_list: `dictionary` :param playbook: The name of the playbook - :type base_dir: ``string`` + :type playbook: `string` :param limit_hosts: Limit the execution to the hosts. - :type limit_hosts: ``string`` + :type limit_hosts: `string` :return a tuple of playbook and hosts - :example - skip_list = {'xyz': {'hosts': 'cloud1', - 'reason': None, - 'lp': None} - } - If playbook not in skip list: - >>> _skip_playbook(skip_list, 'foo', None) - ('foo', None) + :rtype: `tuple` - If playbook in the skip list, but with restriction only on - host cloud1: - >>> _skip_playbook(skip_list, 'xyz', None) - ('xyz', '!cloud1') + :example: - If playbook in the skip list, and should be skip on ALL hosts: - skip_list = {'xyz': {'hosts': 'ALL', - 'reason': None, - 'lp': None} - } - >>> _skip_playbook(skip_list, 'xyz', None) - (None, None) + >>> skip_list = { + ... 'xyz': { + ... 'hosts': 'cloud1', + ... 'reason': None, + ... 'lp': None}} + If playbook is not in skip list: + >>> v_actions = ValidationActions() + >>> v_actions._skip_playbook(skip_list, 'foo', None) + ('foo', None) + + If playbook is in the skip list, but with restriction only on + host cloud1: + >>> v_actions = ValidationActions() + >>> v_actions._skip_playbook(skip_list, 'xyz', None) + ('xyz', '!cloud1') + + If playbook in the skip list, and should be skip on ALL hosts: + >>> skip_list = { + ... 'xyz': { + ... 'hosts': 'ALL', + ... 'reason': None, + ... 'lp': None}} + >>> v_actions = ValidationActions() + >>> v_actions._skip_playbook(skip_list, 'xyz', None) + (None, None) """ if skip_list: - if playbook in skip_list.keys(): - _hosts = self._skip_hosts(skip_list, playbook, - limit_hosts) + if playbook in skip_list: + + self.log.info(( + "Validation '{}' skipped on following hosts '{}' " + "with reason: '{}'.").format( + playbook, + skip_list[playbook].get('hosts', 'All'), + skip_list[playbook].get('reason', None))) + + _hosts = self._skip_hosts( + skip_list[playbook], + limit_hosts) if _hosts: return playbook, _hosts else: @@ -266,6 +305,14 @@ class ValidationActions(object): with the last time the file was modified serving as a key. Finally we take the last `n` logs, where `n` == `history_limit` and return them while discarding the time information. + + :param logs: List of validation log file paths + :type logs: `list` + :param history_limit: number of entries to display + :type history_limit: `int` + + :return: List of time-modified, path tuples of length =< history_limit + :rtype: `list` """ history_limit = min(history_limit, len(logs)) @@ -280,17 +327,16 @@ class ValidationActions(object): group=None, category=None, product=None, extra_vars=None, validations_dir=None, extra_env_vars=None, ansible_cfg=None, quiet=True, - workdir=None, limit_hosts=None, run_async=False, + limit_hosts=None, run_async=False, base_dir=constants.DEFAULT_VALIDATIONS_BASEDIR, - log_path=constants.VALIDATIONS_LOG_BASEDIR, - python_interpreter=None, skip_list=None, + log_path=None, python_interpreter=None, skip_list=None, callback_whitelist=None, output_callback='validation_stdout', ssh_user=None, validation_config=None): """Run one or multiple validations by name(s), by group(s) or by product(s) - :param validation_name: A list of validation names + :param validation_name: A list of validation names. :type validation_name: ``list`` :param inventory: Either proper inventory file, or a comma-separated list. (Defaults to ``localhost``) @@ -315,8 +361,6 @@ class ValidationActions(object): :type ansible_cfg: ``string`` :param quiet: Disable all output (Defaults to ``True``) :type quiet: ``Boolean`` - :param workdir: Location of the working directory - :type workdir: ``string`` :param limit_hosts: Limit the execution to the hosts. :type limit_hosts: ``string`` :param run_async: Enable the Ansible asynchronous mode @@ -329,6 +373,9 @@ class ValidationActions(object): :param log_path: The absolute path of the validations logs directory (Defaults to ``constants.VALIDATIONS_LOG_BASEDIR``) + The absolute path of the validations logs directory. + The 'log_path' argument is deprecated and will be removed in the next release. + Use the 'log_path' argument of the init method. :type log_path: ``string`` :param python_interpreter: Path to the Python interpreter to be used for module execution on remote targets, @@ -359,7 +406,7 @@ class ValidationActions(object): Status, Status_by_Host, UUID and Unreachable_Hosts) :rtype: ``list`` - :Example: + :example: >>> path = "/u/s/a" >>> validation_name = ['foo', 'bar'] @@ -402,27 +449,45 @@ class ValidationActions(object): for val in validations: playbooks.append("{path}/{id}.yaml".format(**val)) elif validation_name: + self.log.debug( + "Getting the {} validation.".format( + validation_name)) + playbooks = v_utils.get_validations_playbook( validations_dir, validation_name, validation_config=validation_config) if not playbooks or len(validation_name) != len(playbooks): - p = [] + found_playbooks = [] for play in playbooks: - p.append(os.path.basename(os.path.splitext(play)[0])) + found_playbooks.append( + os.path.basename(os.path.splitext(play)[0])) - unknown_validation = list(set(validation_name) - set(p)) + unknown_validations = list( + set(validation_name) - set(found_playbooks)) - msg = "Validation {} not found in {}.".format( - unknown_validation, validations_dir) + msg = ( + "Following validations were not found in '{}': {}" + ).format(validations_dir, ', '.join(unknown_validations)) raise RuntimeError(msg) else: raise RuntimeError("No validations found") + if log_path: + self.log.warning(( + "The 'log_path' argument is deprecated and" + " will be removed in the next release. " + "Use the 'log_path' argument of the init method.")) + log_path = v_utils.create_log_dir(log_path) + else: + log_path = v_utils.create_log_dir(self.log_path) + + self.log.debug(( + 'Running the validations with Ansible.\n' + 'Gathered playbooks:\n -{}').format( + '\n -'.join(playbooks))) - log_path = v_utils.create_log_dir(log_path) - self.log.debug('Running the validations with Ansible') results = [] for playbook in playbooks: # Check if playbook should be skipped and on which hosts @@ -485,17 +550,6 @@ class ValidationActions(object): 'validations': _playbook.split('.')[0], 'UUID': validation_uuid, }) - # Print hosts which has been skipped: - if _hosts: - skipped_hosts = [h.replace('!', '') - for h in _hosts.split(',') if '!' in h] - if skipped_hosts: - msg = ("Validation {} has been skipped " - "on hosts: {}").format(_play, - ','.join(skipped_hosts)) - self.log.info(msg) - else: - self.log.info('Skipping Validations: {}'.format(playbook)) if run_async: return results @@ -504,7 +558,7 @@ class ValidationActions(object): vlog = ValidationLogs(log_path) return vlog.get_results(uuid) - def group_information(self, groups, validation_config=None): + def group_information(self, groups=None, validation_config=None): """Get Information about Validation Groups This is used to print table from python ``Tuple`` with ``PrettyTable``. @@ -519,7 +573,10 @@ class ValidationActions(object): | group3 | Description of group3 | 1 | +----------+--------------------------+-----------------------+ - :param groups: The absolute path of the groups.yaml file + :param groups: The absolute path of the groups.yaml file. + The argument is deprecated and will be removed + in the next release. + Use the 'groups_path' argument of the init method. :type groups: ``string`` :param validation_config: A dictionary of configuration for Validation loaded from an validation.cfg file. @@ -529,19 +586,27 @@ class ValidationActions(object): the numbers of validation belonging to them. :rtype: ``tuple`` - :Example: + :example: >>> groups = "/foo/bar/groups.yaml" - >>> actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR) - >>> group_info = actions.group_information(groups) + >>> actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR, groups) + >>> group_info = actions.group_information() >>> print(group_info) (('Groups', 'Desciption', 'Number of Validations'), [('group1', 'Description of group1', 3), ('group2', 'Description of group2', 12), ('group3', 'Description of group3', 1)]) """ - val_gp = Group(groups) - group_definitions = val_gp.get_formated_group + if groups: + self.log.warning(( + "The 'groups' argument is deprecated and" + " will be removed in the next release. " + "Use the 'groups_path' argument of the init method.")) + val_group = Group(groups) + else: + val_group = Group(self.groups_path) + + group_definitions = val_group.get_formated_groups group_info = [] @@ -599,7 +664,7 @@ class ValidationActions(object): :return: A JSON or a YAML dump (By default, JSON). if `download_file` is used, a file containing only the parameters will be created in the file system. - :exemple: + :example: >>> validations = ['check-cpu', 'check-ram'] >>> groups = None @@ -620,26 +685,8 @@ class ValidationActions(object): } } } + """ - if not validations: - validations = [] - elif not isinstance(validations, list): - raise TypeError("The 'validations' argument must be a List") - - if not groups: - groups = [] - elif not isinstance(groups, list): - raise TypeError("The 'groups' argument must be a List") - - if not categories: - categories = [] - elif not isinstance(categories, list): - raise TypeError("The 'categories' argument must be a List") - - if not products: - products = [] - elif not isinstance(products, list): - raise TypeError("The 'products' argument must be a List") supported_format = ['json', 'yaml'] @@ -667,7 +714,7 @@ class ValidationActions(object): params_only = {} try: with open(download_file, 'w') as parameters_file: - for val_name in params.keys(): + for val_name in params: params_only.update(params[val_name].get('parameters')) if output_format == 'json': @@ -698,7 +745,7 @@ class ValidationActions(object): return params def show_history(self, validation_ids=None, extension='json', - log_path=constants.VALIDATIONS_LOG_BASEDIR, + log_path=None, history_limit=None): """Return validation executions history @@ -706,7 +753,10 @@ class ValidationActions(object): :type validation_ids: a list of strings :param extension: The log file extension (Defaults to ``json``) :type extension: ``string`` - :param log_path: The absolute path of the validations logs directory + :param log_path: The absolute path of the validations logs directory. + The 'log_path' argument is deprecated and will + be removed in the next release. + Use the 'log_path' argument of the init method. :type log_path: ``string`` :param history_limit: The number of most recent history logs to be displayed. @@ -716,7 +766,7 @@ class ValidationActions(object): history :rtype: ``tuple`` - :Example: + :example: >>> actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR) >>> print(actions.show_history()) @@ -754,8 +804,17 @@ class ValidationActions(object): 'PASSED', '2020-11-13T11:47:50.279662Z', '0:00:02.237')]) + """ - vlogs = ValidationLogs(log_path) + if log_path: + self.log.warning(( + "The 'log_path' argument is deprecated and" + " will be removed in the next release. " + "Use the 'log_path' argument of the init method.")) + vlogs = ValidationLogs(log_path) + else: + vlogs = ValidationLogs(self.log_path) + if validation_ids: if not isinstance(validation_ids, list): validation_ids = [validation_ids] @@ -794,13 +853,16 @@ class ValidationActions(object): :type uuid: ``string`` :param status: The status of the execution (Defaults to FAILED) :type status: ``string`` - :param log_path: The absolute path of the validations logs directory + :param log_path: The absolute path of the validations logs directory. + The 'log_path' argument is deprecated and will + be removed in the next release. + Use the 'log_path' argument of the init method. :type log_path: ``string`` :return: A list of validations execution with details and by status :rtype: ``tuple`` - :Example: + :example: >>> actions = ValidationActions(validation_path='/foo/bar') >>> status = actions.get_status(validation_id='foo')) @@ -831,7 +893,15 @@ class ValidationActions(object): 'failed': True, 'msg': 'Debug mode is not disabled.'})]) """ - vlogs = ValidationLogs(log_path) + if log_path: + self.log.warning(( + "The 'log_path' argument is deprecated and" + " will be removed in the next release. " + "Use the 'log_path' argument of the init method.")) + vlogs = ValidationLogs(log_path) + else: + vlogs = ValidationLogs(self.log_path) + if validation_id: logs = vlogs.get_logfile_by_validation(validation_id) elif uuid: