diff --git a/validations_libs/ansible.py b/validations_libs/ansible.py index 424de272..8182ecf6 100644 --- a/validations_libs/ansible.py +++ b/validations_libs/ansible.py @@ -320,7 +320,7 @@ class Ansible: def run(self, playbook, inventory, workdir, playbook_dir=None, connection='smart', output_callback=None, - base_dir=constants.DEFAULT_VALIDATIONS_BASEDIR, + base_dir=constants.DEFAULT_ANSIBLE_BASEDIR, ssh_user=None, key=None, module_path=None, limit_hosts=None, tags=None, skip_tags=None, verbosity=0, quiet=False, extra_vars=None, diff --git a/validations_libs/cli/community.py b/validations_libs/cli/community.py index 0c9d56ea..6496799a 100644 --- a/validations_libs/cli/community.py +++ b/validations_libs/cli/community.py @@ -56,7 +56,7 @@ class CommunityValidationInit(BaseCommand): "is located.")) parser.add_argument('--ansible-base-dir', dest='ansible_base_dir', - default=constants.DEFAULT_VALIDATIONS_BASEDIR, + default=constants.DEFAULT_ANSIBLE_BASEDIR, help=("Path where the ansible roles, library " "and plugins are located.")) return parser @@ -71,7 +71,7 @@ class CommunityValidationInit(BaseCommand): validation_dir=parsed_args.validation_dir, ansible_base_dir=parsed_args.ansible_base_dir) - if co_validation.is_community_validations_enabled(self.base.config): + if utils.community_validations_on(self.base.config): LOG.debug( ( "Checking the presence of the community validations " @@ -96,7 +96,7 @@ class CommunityValidationInit(BaseCommand): ) ) - if co_validation.is_playbook_exists(): + if co_validation.validation_exists(self.base.config): raise RuntimeError( ( "An Ansible playbook called {} " diff --git a/validations_libs/cli/lister.py b/validations_libs/cli/lister.py index 745b454c..0b633b34 100644 --- a/validations_libs/cli/lister.py +++ b/validations_libs/cli/lister.py @@ -58,7 +58,7 @@ class ValidationList(BaseLister): validation_dir = parsed_args.validation_dir group = parsed_args.group - v_actions = ValidationActions(validation_path=validation_dir) + v_actions = ValidationActions(base_validation_path=validation_dir) return (v_actions.list_validations(groups=group, categories=category, products=product, diff --git a/validations_libs/cli/run.py b/validations_libs/cli/run.py index 485ebadd..1561cbbb 100644 --- a/validations_libs/cli/run.py +++ b/validations_libs/cli/run.py @@ -52,7 +52,7 @@ class Run(BaseCommand): help=cli_constants.PLAY_PATH_DESC) parser.add_argument('--ansible-base-dir', dest='ansible_base_dir', - default=constants.DEFAULT_VALIDATIONS_BASEDIR, + default=constants.DEFAULT_ANSIBLE_BASEDIR, help=("Path where the ansible roles, library " "and plugins are located.\n")) diff --git a/validations_libs/cli/show.py b/validations_libs/cli/show.py index d3119e82..3a90fea1 100644 --- a/validations_libs/cli/show.py +++ b/validations_libs/cli/show.py @@ -44,7 +44,7 @@ class Show(BaseShow): validation_dir = parsed_args.validation_dir validation_name = parsed_args.validation_name - v_actions = ValidationActions(validation_path=validation_dir) + v_actions = ValidationActions(base_validation_path=validation_dir) data = v_actions.show_validations( validation_name, validation_config=self.base.config) diff --git a/validations_libs/community/init_validation.py b/validations_libs/community/init_validation.py index 99f075dd..3c347c81 100644 --- a/validations_libs/community/init_validation.py +++ b/validations_libs/community/init_validation.py @@ -15,16 +15,12 @@ # under the License. # -from validations_libs.logger import getLogger -import re import os -# @matbu backward compatibility for stable/train -try: - from pathlib import Path -except ImportError: - from pathlib2 import Path +import re +from pathlib import Path from validations_libs import constants, utils +from validations_libs.logger import getLogger LOG = getLogger(__name__) @@ -40,7 +36,7 @@ class CommunityValidation: self, validation_name, validation_dir=constants.ANSIBLE_VALIDATION_DIR, - ansible_base_dir=constants.DEFAULT_VALIDATIONS_BASEDIR): + ansible_base_dir=constants.DEFAULT_ANSIBLE_BASEDIR): """Construct Role and Playbook.""" self._validation_name = validation_name @@ -126,42 +122,32 @@ class CommunityValidation: return Path(self.role_dir_path).exists() or \ self.role_name in non_community_roles - def is_playbook_exists(self): - """New playbook existence check + def validation_exists(self, validation_config=None): + """New validation existence check - This class method checks if the new playbook file is already existing - in the official validations catalog and in the current community - validations directory. + This class method checks if the requested playbook file already exists + in any of the validation playbook paths. - First, it gets the list of the playbooks yaml file available in - ``constants.ANSIBLE_VALIDATIONS_DIR``. If there is a match in at least - one of the directories, it returns ``True``, otherwise ``False``. + Method first checks if the validation is in the default path, + afterwards it retrieves information about validation playbooks + in locations defined by ``constants.VALIDATION_PLAYBOOK_DIRS``, + and compares their ids with that of a new validation. + + If there is any match it returns ``True``, otherwise ``False``. + + :validation_config: parsed validation.cfg file + :dtype: ``dict`` :rtype: ``Boolean`` """ - non_community_playbooks = [] - if Path(self.validation_dir).exists(): - non_community_playbooks = [ - Path(x).name - for x in Path(self.validation_dir).iterdir() - if x.is_file() - ] + if Path(self.playbook_path).exists(): + return True - return Path(self.playbook_path).exists() or \ - self.playbook_name in non_community_playbooks + existing_validations = utils.parse_validations( + str(self.playbook_basedir.resolve()), + validation_config=validation_config) - def is_community_validations_enabled(self, base_config): - """Checks if the community validations are enabled in the config file - - :param base_config: Contents of the configuration file - :type base_config: ``Dict`` - - :rtype: ``Boolean`` - """ - config = base_config - default_conf = (config.get('default', {}) - if isinstance(config, dict) else {}) - return default_conf.get('enable_community_validations', True) + return self._validation_name in [val.id for val in existing_validations] @property def role_name(self): diff --git a/validations_libs/constants.py b/validations_libs/constants.py index dc647682..e3ea9fee 100644 --- a/validations_libs/constants.py +++ b/validations_libs/constants.py @@ -22,25 +22,29 @@ or as a fallback, when custom locations fail. import os -# @matbu backward compatibility for stable/train -try: - from pathlib import Path -except ImportError: - from pathlib2 import Path +from pathlib import Path -DEFAULT_VALIDATIONS_BASEDIR = '/usr/share/ansible' +DEFAULT_ANSIBLE_BASEDIR = '/usr/share/ansible' ANSIBLE_VALIDATION_DIR = os.path.join( - DEFAULT_VALIDATIONS_BASEDIR, + DEFAULT_ANSIBLE_BASEDIR, 'validation-playbooks') -ANSIBLE_ROLES_DIR = Path.joinpath(Path(DEFAULT_VALIDATIONS_BASEDIR), +ANSIBLE_ROLES_DIR = Path.joinpath(Path(DEFAULT_ANSIBLE_BASEDIR), 'roles') VALIDATION_GROUPS_INFO = os.path.join( - DEFAULT_VALIDATIONS_BASEDIR, + DEFAULT_ANSIBLE_BASEDIR, 'groups.yaml') +COLLECTION_VALIDATIONS_PATH = 'collections/ansible-collections/validations/*/playbooks/' + +VALIDATION_PLAYBOOK_DIRS = [ + ANSIBLE_VALIDATION_DIR, + os.path.join(os.path.expanduser('~'), COLLECTION_VALIDATIONS_PATH), + os.path.join(DEFAULT_ANSIBLE_BASEDIR, COLLECTION_VALIDATIONS_PATH) +] + # NOTE(fressi) The HOME folder environment variable may be undefined. VALIDATIONS_LOG_BASEDIR = os.path.expanduser('~/validations') diff --git a/validations_libs/exceptions.py b/validations_libs/exceptions.py index ce0e11bd..44494a00 100644 --- a/validations_libs/exceptions.py +++ b/validations_libs/exceptions.py @@ -39,3 +39,13 @@ class ValidationShowException(Exception): of the `ValidationsActions` class, cause unacceptable behavior from which it is impossible to recover. """ + + +class ValidationParsingException(Exception): + """ValidationParsingException is to be raised when playbook + retrieved from storage and parsed in the `__init__` method + of the `Validation` object is malformed, or incompatible with + the requirements of validation runtime. + The exception should be raised as soon as possible after parsing + of the file, in order to ensure the fastest report and recovery. + """ diff --git a/validations_libs/tests/cli/test_community.py b/validations_libs/tests/cli/test_community.py index 8251f178..e93c7213 100644 --- a/validations_libs/tests/cli/test_community.py +++ b/validations_libs/tests/cli/test_community.py @@ -32,7 +32,7 @@ class TestCommunityValidationInit(BaseCommand): @mock.patch( 'validations_libs.community.init_validation.CommunityValidation.execute') @mock.patch( - 'validations_libs.community.init_validation.CommunityValidation.is_playbook_exists', + 'validations_libs.community.init_validation.CommunityValidation.validation_exists', return_value=False) @mock.patch( 'validations_libs.community.init_validation.CommunityValidation.is_role_exists', @@ -50,7 +50,7 @@ class TestCommunityValidationInit(BaseCommand): self.cmd.take_action(parsed_args) @mock.patch( - 'validations_libs.community.init_validation.CommunityValidation.is_community_validations_enabled', + 'validations_libs.utils.community_validations_on', return_value=False) def test_validation_init_with_com_val_disabled(self, mock_config): args = self._set_args(['my_new_community_val']) @@ -64,7 +64,7 @@ class TestCommunityValidationInit(BaseCommand): 'validations_libs.community.init_validation.CommunityValidation.is_role_exists', return_value=True) @mock.patch( - 'validations_libs.community.init_validation.CommunityValidation.is_playbook_exists', + 'validations_libs.community.init_validation.CommunityValidation.validation_exists', return_value=False) @mock.patch('validations_libs.utils.check_community_validations_dir') def test_validation_init_with_role_existing(self, @@ -82,7 +82,7 @@ class TestCommunityValidationInit(BaseCommand): 'validations_libs.community.init_validation.CommunityValidation.is_role_exists', return_value=False) @mock.patch( - 'validations_libs.community.init_validation.CommunityValidation.is_playbook_exists', + 'validations_libs.community.init_validation.CommunityValidation.validation_exists', return_value=True) @mock.patch('validations_libs.utils.check_community_validations_dir') def test_validation_init_with_playbook_existing(self, diff --git a/validations_libs/tests/cli/test_list.py b/validations_libs/tests/cli/test_list.py index 4fd27fbc..7677b1ae 100644 --- a/validations_libs/tests/cli/test_list.py +++ b/validations_libs/tests/cli/test_list.py @@ -70,7 +70,7 @@ class TestList(BaseCommand): result = self.cmd.take_action(parsed_args) self.assertEqual(result, []) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk', + @mock.patch('validations_libs.utils.parse_validations', return_value=fakes.VALIDATIONS_LIST_GROUP, autospec=True) def test_list_validations_group(self, mock_list): @@ -84,7 +84,7 @@ class TestList(BaseCommand): result = self.cmd.take_action(parsed_args) self.assertEqual(result, val_list) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk', + @mock.patch('validations_libs.utils.parse_validations', return_value=fakes.VALIDATIONS_LIST_GROUP, autospec=True) def test_list_validations_by_category(self, mock_list): @@ -98,7 +98,7 @@ class TestList(BaseCommand): result = self.cmd.take_action(parsed_args) self.assertEqual(result, val_list) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk', + @mock.patch('validations_libs.utils.parse_validations', return_value=fakes.VALIDATIONS_LIST_GROUP, autospec=True) def test_list_validations_by_product(self, mock_list): diff --git a/validations_libs/tests/community/test_init_validation.py b/validations_libs/tests/community/test_init_validation.py index aa9867db..c6399fd2 100644 --- a/validations_libs/tests/community/test_init_validation.py +++ b/validations_libs/tests/community/test_init_validation.py @@ -13,20 +13,8 @@ # under the License. # -try: - from unittest import mock -except ImportError: - import mock - -# @matbu backward compatibility for stable/train -try: - from pathlib import PosixPath - PATHLIB = 'pathlib' -except ImportError: - from pathlib2 import PosixPath - PATHLIB = 'pathlib2' - -from unittest import TestCase +from pathlib import PosixPath +from unittest import TestCase, mock from validations_libs import constants from validations_libs.community.init_validation import \ @@ -90,10 +78,10 @@ class TestCommunityValidation(TestCase): self.assertEqual(co_val.playbook_basedir, constants.COMMUNITY_PLAYBOOKS_DIR) - @mock.patch('{}.Path.iterdir'.format(PATHLIB), + @mock.patch('pathlib.Path.iterdir', return_value=fakes.FAKE_ROLES_ITERDIR2) - @mock.patch('{}.Path.is_dir'.format(PATHLIB)) - @mock.patch('{}.Path.exists'.format(PATHLIB), side_effect=[False, True]) + @mock.patch('pathlib.Path.is_dir') + @mock.patch('pathlib.Path.exists', side_effect=[False, True]) def test_role_already_exists_in_comval(self, mock_play_path_exists, mock_path_is_dir, @@ -102,10 +90,10 @@ class TestCommunityValidation(TestCase): co_val = cv(validation_name) self.assertTrue(co_val.is_role_exists()) - @mock.patch('{}.Path.iterdir'.format(PATHLIB), + @mock.patch('pathlib.Path.iterdir', return_value=fakes.FAKE_ROLES_ITERDIR1) - @mock.patch('{}.Path.is_dir'.format(PATHLIB)) - @mock.patch('{}.Path.exists'.format(PATHLIB), side_effect=[True, False]) + @mock.patch('pathlib.Path.is_dir') + @mock.patch('pathlib.Path.exists', side_effect=[True, False]) def test_role_already_exists_in_non_comval(self, mock_play_path_exists, mock_path_is_dir, @@ -114,10 +102,10 @@ class TestCommunityValidation(TestCase): co_val = cv(validation_name) self.assertTrue(co_val.is_role_exists()) - @mock.patch('{}.Path.iterdir'.format(PATHLIB), + @mock.patch('pathlib.Path.iterdir', return_value=fakes.FAKE_ROLES_ITERDIR2) - @mock.patch('{}.Path.is_dir'.format(PATHLIB)) - @mock.patch('{}.Path.exists'.format(PATHLIB), side_effect=[True, False]) + @mock.patch('pathlib.Path.is_dir') + @mock.patch('pathlib.Path.exists', side_effect=[True, False]) def test_role_not_exists(self, mock_path_exists, mock_path_is_dir, @@ -126,41 +114,38 @@ class TestCommunityValidation(TestCase): co_val = cv(validation_name) self.assertFalse(co_val.is_role_exists()) - @mock.patch('{}.Path.iterdir'.format(PATHLIB), + @mock.patch('pathlib.Path.iterdir', return_value=fakes.FAKE_PLAYBOOKS_ITERDIR1) - @mock.patch('{}.Path.is_file'.format(PATHLIB)) - @mock.patch('{}.Path.exists'.format(PATHLIB), side_effect=[True, False]) + @mock.patch('pathlib.Path.is_file') + @mock.patch('pathlib.Path.exists', side_effect=[True, False]) def test_playbook_already_exists_in_non_comval(self, mock_path_exists, mock_path_is_file, mock_path_iterdir): validation_name = "my_val" co_val = cv(validation_name) - self.assertTrue(co_val.is_playbook_exists()) + self.assertTrue(co_val.validation_exists()) - @mock.patch('{}.Path.iterdir'.format(PATHLIB), - return_value=fakes.FAKE_PLAYBOOKS_ITERDIR2) - @mock.patch('{}.Path.is_file'.format(PATHLIB)) - @mock.patch('{}.Path.exists'.format(PATHLIB), side_effect=[False, True]) + @mock.patch('glob.glob') + @mock.patch('pathlib.Path.is_file') + @mock.patch('pathlib.Path.exists', side_effect=[True]) def test_playbook_already_exists_in_comval(self, mock_path_exists, mock_path_is_file, - mock_path_iterdir): + mock_glob): validation_name = "my_val" - co_val = cv(validation_name) - self.assertTrue(co_val.is_playbook_exists()) - @mock.patch('{}.Path.iterdir'.format(PATHLIB), - return_value=fakes.FAKE_PLAYBOOKS_ITERDIR2) - @mock.patch('{}.Path.is_file'.format(PATHLIB)) - @mock.patch('{}.Path.exists'.format(PATHLIB), side_effect=[True, False]) + co_val = cv(validation_name) + self.assertTrue(co_val.validation_exists()) + + @mock.patch('pathlib.Path.is_file') + @mock.patch('pathlib.Path.exists', side_effect=[False, False]) def test_playbook_not_exists(self, mock_path_exists, - mock_path_is_file, - mock_path_iterdir): + mock_path_is_file): validation_name = "my_val" co_val = cv(validation_name) - self.assertFalse(co_val.is_playbook_exists()) + self.assertFalse(co_val.validation_exists()) def test_execute_with_role_name_not_compliant(self): validation_name = "3_my-val" diff --git a/validations_libs/tests/test_utils.py b/validations_libs/tests/test_utils.py index e9cb2ff3..61509332 100644 --- a/validations_libs/tests/test_utils.py +++ b/validations_libs/tests/test_utils.py @@ -16,23 +16,11 @@ import logging import os import subprocess +from pathlib import PosixPath +from unittest import TestCase, mock -try: - from unittest import mock -except ImportError: - import mock - -# @matbu backward compatibility for stable/train -try: - from pathlib import PosixPath - PATHLIB = 'pathlib' -except ImportError: - from pathlib2 import PosixPath - PATHLIB = 'pathlib2' - -from unittest import TestCase - -from validations_libs import utils, constants +from validations_libs import constants, utils +from validations_libs.validation import Validation from validations_libs.tests import fakes @@ -41,6 +29,23 @@ class TestUtils(TestCase): def setUp(self): super(TestUtils, self).setUp() self.logger = mock.patch('validations_libs.logger.getLogger') + # Mocking all glob calls, so that only the first path will + # return any items + globs = [['/foo/playbook/foo.yaml'], []] + globs.extend(len(constants.VALIDATION_PLAYBOOK_DIRS)*[[]]) + + def _return_empty_list(_=None): + while True: + yield [] + + def return_on_community(path): + if 'community' in path: + return ['/home/foo/community-validations/playbooks/foo.yaml'] + return [] + + self.globs_first_only = globs + self.return_empty_list = _return_empty_list + self.return_on_community = return_on_community @mock.patch('validations_libs.validation.Validation._get_content', return_value=fakes.FAKE_PLAYBOOK[0]) @@ -101,200 +106,202 @@ class TestUtils(TestCase): utils.get_validations_data, validation) + @mock.patch('os.path.isfile', return_value=True) @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') @mock.patch('glob.glob') def test_parse_all_validations_on_disk(self, mock_glob, mock_open, - mock_load): - mock_glob.side_effect = \ - (['/foo/playbook/foo.yaml'], []) - result = utils.parse_all_validations_on_disk('/foo/playbook') - self.assertEqual(result, [fakes.FAKE_METADATA]) + mock_load, mock_is_file): + mock_glob.side_effect = self.globs_first_only + result = utils.parse_validations('/foo/playbook') + self.assertTrue(isinstance(result[0], Validation)) + self.assertEqual(result[0].get_metadata, fakes.FAKE_METADATA) + + @mock.patch('os.path.isfile', return_value=True) @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') @mock.patch('glob.glob') def test_parse_community_validations_on_disk( - self, mock_glob, mock_open, mock_load): - mock_glob.side_effect = \ - ([], ['/foo/playbook/foo.yaml']) - result = utils.parse_all_validations_on_disk('/foo/playbook') - self.assertEqual(result, [fakes.FAKE_METADATA]) + self, mock_glob, mock_open, mock_load, mock_is_file): + mock_glob.side_effect = self.globs_first_only + + result = utils.parse_validations('/foo/playbook') + self.assertTrue(isinstance(result[0], Validation)) + self.assertEqual(result[0].get_metadata, fakes.FAKE_METADATA) @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') @mock.patch('glob.glob') def test_parse_all_community_disabled_validations_on_disk( self, mock_glob, mock_open, mock_load): - mock_glob.side_effect = \ - ([], ['/foo/playbook/foo.yaml']) - result = utils.parse_all_validations_on_disk( + mock_glob.side_effect = self.return_empty_list() + result = utils.parse_validations( '/foo/playbook', validation_config={'default': {"enable_community_validations": False}}) self.assertEqual(result, []) def test_parse_all_validations_on_disk_wrong_path_type(self): self.assertRaises(TypeError, - utils.parse_all_validations_on_disk, + utils.parse_validations, path=['/foo/playbook']) def test_parse_all_validations_on_disk_wrong_groups_type(self): self.assertRaises(TypeError, - utils.parse_all_validations_on_disk, + utils.parse_validations, path='/foo/playbook', groups='foo1,foo2') def test_parse_all_validations_on_disk_wrong_categories_type(self): self.assertRaises(TypeError, - utils.parse_all_validations_on_disk, + utils.parse_validations, path='/foo/playbook', categories='foo1,foo2') def test_parse_all_validations_on_disk_wrong_products_type(self): self.assertRaises(TypeError, - utils.parse_all_validations_on_disk, + utils.parse_validations, path='/foo/playbook', products='foo1,foo2') def test_get_validations_playbook_wrong_validation_id_type(self): self.assertRaises(TypeError, - utils.get_validations_playbook, + utils.get_validations_playbook_paths, path='/foo/playbook', validation_id='foo1,foo2') def test_get_validations_playbook_wrong_groups_type(self): self.assertRaises(TypeError, - utils.get_validations_playbook, + utils.get_validations_playbook_paths, path='/foo/playbook', groups='foo1,foo2') def test_get_validations_playbook_wrong_categories_type(self): self.assertRaises(TypeError, - utils.get_validations_playbook, + utils.get_validations_playbook_paths, path='/foo/playbook', categories='foo1,foo2') def test_get_validations_playbook_wrong_products_type(self): self.assertRaises(TypeError, - utils.get_validations_playbook, + utils.get_validations_playbook_paths, path='/foo/playbook', products='foo1,foo2') + @mock.patch('os.path.isfile', return_value=True) @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') @mock.patch('glob.glob') def test_parse_all_validations_on_disk_by_group(self, mock_glob, mock_open, - mock_load): - mock_glob.side_effect = \ - (['/foo/playbook/foo.yaml'], []) - result = utils.parse_all_validations_on_disk('/foo/playbook', - ['prep']) - self.assertEqual(result, [fakes.FAKE_METADATA]) + mock_load, + mock_isfile): + mock_glob.side_effect = self.globs_first_only + result = utils.parse_validations('/foo/playbook', groups=['prep']) + self.assertEqual(result[0].get_metadata, fakes.FAKE_METADATA) + @mock.patch('os.path.isfile', return_value=True) @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') @mock.patch('glob.glob') def test_parse_all_validations_on_disk_by_category(self, mock_glob, mock_open, - mock_load): - mock_glob.side_effect = \ - (['/foo/playbook/foo.yaml'], []) - result = utils.parse_all_validations_on_disk('/foo/playbook', - categories=['os']) - self.assertEqual(result, [fakes.FAKE_METADATA]) + mock_load, + mock_isfile): + mock_glob.side_effect = self.globs_first_only + result = utils.parse_validations('/foo/playbook', + categories=['os']) + self.assertEqual(result[0].get_metadata, fakes.FAKE_METADATA) def test_get_validations_playbook_wrong_path_type(self): self.assertRaises(TypeError, - utils.get_validations_playbook, + utils.get_validations_playbook_paths, path=['/foo/playbook']) + @mock.patch('os.path.isfile', return_value=True) @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') @mock.patch('glob.glob') def test_parse_all_validations_on_disk_by_product(self, mock_glob, mock_open, - mock_load): - mock_glob.side_effect = (['/foo/playbook/foo.yaml'], []) - result = utils.parse_all_validations_on_disk('/foo/playbook', - products=['product1']) - self.assertEqual(result, [fakes.FAKE_METADATA]) + mock_load, + mock_isfile): + mock_glob.side_effect = self.globs_first_only + result = utils.parse_validations('/foo/playbook', + products=['product1']) + self.assertEqual(result[0].get_metadata, fakes.FAKE_METADATA) - @mock.patch('os.path.isfile') + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK) + @mock.patch('builtins.open') + @mock.patch('glob.glob') + def test_parse_all_validations_on_disk_parsing_error(self, mock_glob, + mock_open, mock_load): + globs_t = self.globs_first_only + globs_t[-1] = ['/usr/ansible/playbooks/nonsense.yaml'] + mock_glob.side_effect = globs_t + result = utils.parse_validations('/foo/playbook') + self.assertEqual(result, []) + + @mock.patch('os.path.isfile', return_value=True) @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') def test_get_validations_playbook_by_id(self, mock_open, mock_load, mock_glob, mock_isfile): - mock_glob.side_effect = (['/foo/playbook/foo.yaml'], []) - mock_isfile.return_value = True - result = utils.get_validations_playbook('/foo/playbook', - validation_id=['foo']) - self.assertEqual(result, ['/foo/playbook/foo.yaml']) + mock_glob.side_effect = self.globs_first_only + result = utils.parse_validations('/foo/playbook', validation_ids=['foo']) + self.assertEqual(result[0].path, '/foo/playbook/foo.yaml') - @mock.patch('os.path.isfile') + @mock.patch('os.path.isfile', return_value=True) @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') def test_get_community_playbook_by_id(self, mock_open, mock_load, mock_glob, mock_isfile): - mock_glob.side_effect = ( - [], - ['/home/foo/community-validations/playbooks/foo.yaml']) - mock_isfile.return_value = True - # AP this needs a bit of an explanation. We look at the explicity at + mock_glob.side_effect = self.return_on_community # the /foo/playbook directory but the community validation path is # implicit and we find there the id that we are looking for. - result = utils.get_validations_playbook('/foo/playbook', - validation_id=['foo']) - self.assertEqual(result, ['/home/foo/community-validations/playbooks/foo.yaml']) + result = utils.parse_validations('/foo/playbook') + self.assertEqual(result[0].path, '/home/foo/community-validations/playbooks/foo.yaml') - @mock.patch('os.path.isfile') + @mock.patch('os.path.isfile', return_value=True) @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') def test_get_community_disabled_playbook_by_id( self, mock_open, mock_load, mock_glob, mock_isfile): - mock_glob.side_effect = ( - [], - ['/home/foo/community-validations/playbooks/foo.yaml']) - mock_isfile.return_value = True + mock_glob.side_effect = self.return_on_community # The validations_commons validation is not found and community_vals is disabled # So no validation should be found. - result = utils.get_validations_playbook( + result = utils.parse_validations( '/foo/playbook', - validation_id=['foo'], + validation_ids=['foo'], validation_config={'default': {"enable_community_validations": False}}) self.assertEqual(result, []) - @mock.patch('os.path.isfile') + @mock.patch('os.path.isfile', return_value=False) @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') def test_get_community_playbook_by_id_not_found( self, mock_open, mock_load, mock_glob, mock_isfile): - mock_glob.side_effect = ( - [], - ['/home/foo/community-validations/playbooks/foo.yaml/']) - # the is file fails - mock_isfile.return_value = False - result = utils.get_validations_playbook('/foo/playbook', - validation_id=['foo']) + mock_glob.side_effect = self.return_on_community + # the is_file fails + result = utils.parse_validations('/foo/playbook', + validation_ids=['foo']) self.assertEqual(result, []) - @mock.patch('os.path.isfile') + @mock.patch('os.path.isfile', return_value=True) @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') def test_get_validations_playbook_by_id_group(self, mock_open, mock_load, mock_glob, mock_isfile): - mock_glob.side_effect = (['/foo/playbook/foo.yaml'], []) - mock_isfile.return_value = True - result = utils.get_validations_playbook('/foo/playbook', ['foo'], ['prep']) - self.assertEqual(result, ['/foo/playbook/foo.yaml', - '/foo/playbook/foo.yaml']) + mock_glob.side_effect = self.globs_first_only + result = utils.parse_validations('/foo/playbook', ['foo'], ['prep']) + self.assertEqual(result[0].path, '/foo/playbook/foo.yaml') - @mock.patch('os.path.isfile') + @mock.patch('os.path.isfile', return_value=True) @mock.patch('os.listdir') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') @@ -303,34 +310,31 @@ class TestUtils(TestCase): mock_listdir, mock_isfile): mock_listdir.return_value = ['foo.yaml'] - mock_isfile.return_value = True - result = utils.get_validations_playbook('/foo/playbook', - groups=['no_group']) + result = utils.parse_validations('/foo/playbook', + groups=['no_group']) self.assertEqual(result, []) - @mock.patch('os.path.isfile') + @mock.patch('os.path.isfile', return_value=True) @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') def test_get_validations_playbook_by_category(self, mock_open, mock_load, mock_glob, mock_isfile): - mock_glob.side_effect = (['/foo/playbook/foo.yaml'], []) - mock_isfile.return_value = True - result = utils.get_validations_playbook('/foo/playbook', - categories=['os', 'storage']) - self.assertEqual(result, ['/foo/playbook/foo.yaml']) + mock_glob.side_effect = self.globs_first_only + result = utils.parse_validations('/foo/playbook', + categories=['os', 'storage']) + self.assertEqual(result[0].path, '/foo/playbook/foo.yaml') - @mock.patch('os.path.isfile') + @mock.patch('os.path.isfile', return_value=True) @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') def test_get_validations_playbook_by_product(self, mock_open, mock_load, mock_glob, mock_isfile): - mock_glob.side_effect = (['/foo/playbook/foo.yaml'], []) - mock_isfile.return_value = True - result = utils.get_validations_playbook('/foo/playbook', - products=['product1']) - self.assertEqual(result, ['/foo/playbook/foo.yaml']) + mock_glob.side_effect = self.globs_first_only + result = utils.parse_validations('/foo/playbook', + products=['product1']) + self.assertEqual(result[0].path, '/foo/playbook/foo.yaml') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') @@ -376,30 +380,12 @@ class TestUtils(TestCase): @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK2) @mock.patch('builtins.open') - def test_get_validations_parameters_no_group(self, mock_open, mock_load): - - result = utils.get_validations_parameters(['/foo/playbook/foo.yaml'], - ['foo']) + def test_get_validations_parameters(self, mock_open, mock_load): + test_validation = Validation('/foo/playbook/foo.yaml') + result = utils.get_validations_parameters([test_validation]) output = {'foo': {'parameters': {'foo': 'bar'}}} self.assertEqual(result, output) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK2) - @mock.patch('builtins.open') - def test_get_validations_parameters_no_val(self, mock_open, mock_load): - - result = utils.get_validations_parameters(['/foo/playbook/foo.yaml'], - [], ['prep']) - output = {'foo': {'parameters': {'foo': 'bar'}}} - self.assertEqual(result, output) - - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) - @mock.patch('builtins.open') - def test_get_validations_parameters_nothing(self, mock_open, mock_load): - - result = utils.get_validations_parameters(['/foo/playbook/foo.yaml'], - [], []) - self.assertEqual(result, {}) - @mock.patch('validations_libs.utils.os.makedirs') @mock.patch( 'validations_libs.utils.os.access', @@ -521,13 +507,13 @@ class TestUtils(TestCase): results['ansible_environment']['ANSIBLE_STDOUT_CALLBACK'], fakes.ANSIBLE_ENVIRONNMENT_CONFIG['ANSIBLE_STDOUT_CALLBACK']) - @mock.patch('{}.Path.exists'.format(PATHLIB), + @mock.patch('pathlib.Path.exists', return_value=False) - @mock.patch('{}.Path.is_dir'.format(PATHLIB), + @mock.patch('pathlib.Path.is_dir', return_value=False) - @mock.patch('{}.Path.iterdir'.format(PATHLIB), + @mock.patch('pathlib.Path.iterdir', return_value=iter([])) - @mock.patch('{}.Path.mkdir'.format(PATHLIB)) + @mock.patch('pathlib.Path.mkdir') def test_check_creation_community_validations_dir(self, mock_mkdir, mock_iterdir, mock_isdir, @@ -543,11 +529,11 @@ class TestUtils(TestCase): PosixPath("/foo/bar/community-validations/lookup_plugins")] ) - @mock.patch('{}.Path.is_dir'.format(PATHLIB), return_value=True) - @mock.patch('{}.Path.exists'.format(PATHLIB), return_value=True) - @mock.patch('{}.Path.iterdir'.format(PATHLIB), + @mock.patch('pathlib.Path.is_dir', return_value=True) + @mock.patch('pathlib.Path.exists', return_value=True) + @mock.patch('pathlib.Path.iterdir', return_value=fakes.FAKE_COVAL_MISSING_SUBDIR_ITERDIR1) - @mock.patch('{}.Path.mkdir'.format(PATHLIB)) + @mock.patch('pathlib.Path.mkdir') def test_check_community_validations_dir_with_missing_subdir(self, mock_mkdir, mock_iterdir, diff --git a/validations_libs/tests/test_validation.py b/validations_libs/tests/test_validation.py index 80164394..b769204c 100644 --- a/validations_libs/tests/test_validation.py +++ b/validations_libs/tests/test_validation.py @@ -21,6 +21,7 @@ from unittest import TestCase from validations_libs.validation import Validation from validations_libs.tests import fakes +from validations_libs import exceptions class TestValidation(TestCase): @@ -44,10 +45,10 @@ class TestValidation(TestCase): @mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK) @mock.patch('builtins.open') - def test_get_metadata_wrong_playbook(self, mock_open, mock_yaml): - with self.assertRaises(NameError) as exc_mgr: - Validation('/tmp/foo').get_metadata - self.assertEqual('No metadata found in validation foo', + def test_no_metadata(self, mock_open, mock_yaml): + with self.assertRaises(exceptions.ValidationParsingException) as exc_mgr: + Validation('/tmp/foo') + self.assertEqual('No metadata found in validation /tmp/foo', str(exc_mgr.exception)) @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK2) @@ -64,14 +65,6 @@ class TestValidation(TestCase): data = val.get_vars self.assertEqual(data, {}) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK) - @mock.patch('builtins.open') - def test_get_vars_no_metadata(self, mock_open, mock_yaml): - with self.assertRaises(NameError) as exc_mgr: - Validation('/tmp/foo').get_vars - self.assertEqual('No metadata found in validation foo', - str(exc_mgr.exception)) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('builtins.open') def test_get_id(self, mock_open, mock_yaml): @@ -88,14 +81,6 @@ class TestValidation(TestCase): groups = val.groups self.assertEqual(groups, ['prep', 'pre-deployment']) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK) - @mock.patch('builtins.open') - def test_groups_with_no_metadata(self, mock_open, mock_yaml): - with self.assertRaises(NameError) as exc_mgr: - Validation('/tmp/foo').groups - self.assertEqual('No metadata found in validation foo', - str(exc_mgr.exception)) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK3) @mock.patch('builtins.open') def test_groups_with_no_existing_groups(self, mock_open, mock_yaml): @@ -110,14 +95,6 @@ class TestValidation(TestCase): categories = val.categories self.assertEqual(categories, ['os', 'storage']) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK) - @mock.patch('builtins.open') - def test_categories_with_no_metadata(self, mock_open, mock_yaml): - with self.assertRaises(NameError) as exc_mgr: - Validation('/tmp/foo').categories - self.assertEqual('No metadata found in validation foo', - str(exc_mgr.exception)) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK3) @mock.patch('builtins.open') def test_categories_with_no_existing_categories(self, mock_open, mock_yaml): @@ -132,14 +109,6 @@ class TestValidation(TestCase): products = val.products self.assertEqual(products, ['product1']) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK) - @mock.patch('builtins.open') - def test_products_with_no_metadata(self, mock_open, mock_yaml): - with self.assertRaises(NameError) as exc_mgr: - Validation('/tmp/foo').products - self.assertEqual('No metadata found in validation foo', - str(exc_mgr.exception)) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK3) @mock.patch('builtins.open') def test_products_with_no_existing_products(self, mock_open, mock_yaml): @@ -161,14 +130,6 @@ class TestValidation(TestCase): data = val.get_formated_data self.assertEqual(data, fakes.FORMATED_DATA) - @mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK) - @mock.patch('builtins.open') - def test_get_formated_data_no_metadata(self, mock_open, mock_yaml): - with self.assertRaises(NameError) as exc_mgr: - Validation('/tmp/foo').get_formated_data - self.assertEqual('No metadata found in validation foo', - str(exc_mgr.exception)) - @mock.patch('builtins.open') def test_validation_not_found(self, mock_open): mock_open.side_effect = IOError() diff --git a/validations_libs/tests/test_validation_actions.py b/validations_libs/tests/test_validation_actions.py index 80b90617..ab3fd172 100644 --- a/validations_libs/tests/test_validation_actions.py +++ b/validations_libs/tests/test_validation_actions.py @@ -13,18 +13,14 @@ # under the License. # -try: - from unittest import mock - from unittest.mock import ANY -except ImportError: - import mock - from mock import ANY - -from unittest import TestCase +from unittest import TestCase, mock +from unittest.mock import ANY +from validations_libs.exceptions import (ValidationRunException, + ValidationShowException) from validations_libs.tests import fakes +from validations_libs.validation import Validation from validations_libs.validation_actions import ValidationActions -from validations_libs.exceptions import ValidationRunException, ValidationShowException class TestValidationActions(TestCase): @@ -33,7 +29,7 @@ class TestValidationActions(TestCase): super(TestValidationActions, self).setUp() self.column_name = ('ID', 'Name', 'Groups', 'Categories', 'Products') - @mock.patch('validations_libs.utils.parse_all_validations_on_disk', + @mock.patch('validations_libs.utils.parse_validations', return_value=fakes.VALIDATIONS_LIST) def test_validation_list(self, mock_validation_dir): validations_list = ValidationActions('/tmp/foo') @@ -50,13 +46,16 @@ class TestValidationActions(TestCase): ['networking'], ['product1'])])) + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('builtins.open') @mock.patch('validations_libs.utils.os.access', return_value=True) @mock.patch('validations_libs.utils.os.path.exists', return_value=True) - @mock.patch('validations_libs.utils.get_validations_playbook', + @mock.patch('validations_libs.utils.get_validations_playbook_paths', return_value=['/tmp/foo/fake.yaml']) - def test_validation_skip_validation(self, mock_validation_play, mock_exists, mock_access): + def test_validation_skip_validation(self, mock_validation_play, mock_exists, mock_access, + mock_open, mock_yaml_load): - playbook = ['fake.yaml'] + playbook = ['fake'] inventory = 'tmp/inventory.yaml' skip_list = {'fake': {'hosts': 'ALL', 'reason': None, @@ -70,6 +69,8 @@ class TestValidationActions(TestCase): limit_hosts=None) self.assertEqual(run_return, []) + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('builtins.open') @mock.patch('validations_libs.utils.current_time', return_value='time') @mock.patch('validations_libs.utils.uuid.uuid4', @@ -79,16 +80,15 @@ class TestValidationActions(TestCase): return_value=True) @mock.patch('validations_libs.utils.os.path.exists', return_value=True) - @mock.patch('validations_libs.utils.get_validations_playbook', + @mock.patch('validations_libs.utils.get_validations_playbook_paths', return_value=['/tmp/foo/fake.yaml']) @mock.patch('validations_libs.ansible.Ansible.run') def test_validation_skip_on_specific_host(self, mock_ansible_run, mock_validation_play, - mock_exists, - mock_access, - mock_makedirs, - mock_uuid, - mock_time): + mock_exists, mock_access, + mock_makedirs, mock_uuid, + mock_time, mock_open, + mock_yaml_load): mock_ansible_run.return_value = ('fake.yaml', 0, 'successful') run_called_args = { @@ -114,7 +114,7 @@ class TestValidationActions(TestCase): 'validation_cfg_file': None } - playbook = ['fake.yaml'] + validation = ['fake'] inventory = 'tmp/inventory.yaml' skip_list = {'fake': {'hosts': 'cloud1', 'reason': None, @@ -123,13 +123,15 @@ class TestValidationActions(TestCase): } run = ValidationActions(log_path='/var/log/validations') - run_return = run.run_validations(playbook, inventory, + run_return = run.run_validations(validation, inventory, validations_dir='/tmp/foo', skip_list=skip_list, limit_hosts='!cloud1') mock_ansible_run.assert_called_with(**run_called_args) + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('builtins.open') @mock.patch('validations_libs.utils.current_time', return_value='time') @mock.patch('validations_libs.utils.uuid.uuid4', @@ -139,7 +141,7 @@ class TestValidationActions(TestCase): return_value=True) @mock.patch('validations_libs.utils.os.path.exists', return_value=True) - @mock.patch('validations_libs.utils.get_validations_playbook', + @mock.patch('validations_libs.utils.get_validations_playbook_paths', return_value=['/tmp/foo/fake.yaml']) @mock.patch('validations_libs.ansible.Ansible.run') def test_validation_skip_with_limit_host(self, mock_ansible_run, @@ -148,7 +150,9 @@ class TestValidationActions(TestCase): mock_access, mock_makedirs, mock_uuid, - mock_time): + mock_time, + mock_open, + mock_yaml_load): mock_ansible_run.return_value = ('fake.yaml', 0, 'successful') run_called_args = { @@ -174,7 +178,7 @@ class TestValidationActions(TestCase): 'validation_cfg_file': None } - playbook = ['fake.yaml'] + validation = ['fake'] inventory = 'tmp/inventory.yaml' skip_list = {'fake': {'hosts': 'cloud1', 'reason': None, @@ -183,38 +187,35 @@ class TestValidationActions(TestCase): } run = ValidationActions(log_path='/var/log/validations') - run_return = run.run_validations(playbook, inventory, + run_return = run.run_validations(validation, inventory, validations_dir='/tmp/foo', skip_list=skip_list, limit_hosts='cloud,cloud1,!cloud2') mock_ansible_run.assert_called_with(**run_called_args) + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('builtins.open') @mock.patch('validations_libs.utils.os.makedirs') @mock.patch('validations_libs.utils.os.access', return_value=True) @mock.patch('validations_libs.utils.os.path.exists', return_value=True) @mock.patch('validations_libs.validation_actions.ValidationLogs.get_results', side_effect=fakes.FAKE_SUCCESS_RUN) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk') + @mock.patch('validations_libs.utils.parse_validations') @mock.patch('validations_libs.ansible.Ansible.run') def test_validation_run_success(self, mock_ansible_run, mock_validation_dir, mock_results, mock_exists, mock_access, - mock_makedirs): + mock_makedirs, mock_open, mock_safe_load): - mock_validation_dir.return_value = [{ - 'description': 'My Validation One Description', - 'groups': ['prep', 'pre-deployment'], - 'id': 'foo', - 'name': 'My Validition One Name', - 'parameters': {}, - 'path': '/tmp/foobar/validation-playbooks'}] + mock_validation = Validation('/tmp/foobar/validation-playbooks/foo.yaml') + mock_validation_dir.return_value = [mock_validation] mock_ansible_run.return_value = ('foo.yaml', 0, 'successful') expected_run_return = fakes.FAKE_SUCCESS_RUN[0] - playbook = ['fake.yaml'] + playbook = ['foo.yaml'] inventory = 'tmp/inventory.yaml' run = ValidationActions() @@ -246,7 +247,7 @@ class TestValidationActions(TestCase): validation_cfg_file=None ) - @mock.patch('validations_libs.utils.get_validations_playbook') + @mock.patch('validations_libs.utils.get_validations_playbook_paths') def test_validation_run_wrong_validation_name(self, mock_validation_play): mock_validation_play.return_value = [] @@ -255,23 +256,15 @@ class TestValidationActions(TestCase): 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): + @mock.patch('builtins.open', side_effect=IOError) + @mock.patch('validations_libs.utils.get_validations_playbook_paths') + def test_validation_run_not_all_found(self, mock_validation_play, mock_open): mock_validation_play.return_value = ['/tmp/foo/fake.yaml'] run = ValidationActions() - try: - run.run_validations( - validation_name=['fake', 'foo'], - validations_dir='/tmp/foo') - except ValidationRunException as run_exception: - self.assertEqual( - "Following validations were not found in '/tmp/foo': foo", - str(run_exception)) - else: - self.fail("Runtime error exception should have been raised") + self.assertRaises(OSError, run.run_validations, validation_name=['fake', 'foo'], validations_dir='/tmp/foo') - @mock.patch('validations_libs.utils.parse_all_validations_on_disk') + @mock.patch('validations_libs.utils.parse_validations') def test_validation_run_not_enough_params(self, mock_validation_play): mock_validation_play.return_value = [] @@ -280,24 +273,22 @@ class TestValidationActions(TestCase): validations_dir='/tmp/foo' ) + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('builtins.open') @mock.patch('validations_libs.utils.os.makedirs') @mock.patch('validations_libs.utils.os.access', return_value=True) @mock.patch('validations_libs.utils.os.path.exists', return_value=True) @mock.patch('validations_libs.validation_logs.ValidationLogs.get_results') - @mock.patch('validations_libs.utils.parse_all_validations_on_disk') + @mock.patch('validations_libs.utils.parse_validations') @mock.patch('validations_libs.ansible.Ansible.run') def test_validation_run_failed(self, mock_ansible_run, mock_validation_dir, mock_results, mock_exists, mock_access, - mock_makedirs): + mock_makedirs, mock_open, + mock_yaml_load): - mock_validation_dir.return_value = [{ - 'description': 'My Validation One Description', - 'groups': ['prep', 'pre-deployment'], - 'id': 'foo', - 'name': 'My Validition One Name', - 'parameters': {}, - 'path': '/usr/share/ansible/validation-playbooks'}] + mock_validation = Validation('/tmp/foobar/validation-playbooks/foo.yaml') + mock_validation_dir.return_value = [mock_validation] mock_ansible_run.return_value = ('foo.yaml', 0, 'failed') @@ -326,24 +317,22 @@ class TestValidationActions(TestCase): validations_dir='/tmp/foo') self.assertEqual(run_return, expected_run_return) + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('builtins.open') @mock.patch('validations_libs.ansible.Ansible._playbook_check', side_effect=RuntimeError) @mock.patch('validations_libs.utils.os.makedirs') @mock.patch('validations_libs.utils.os.access', return_value=True) @mock.patch('validations_libs.utils.os.path.exists', return_value=True) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk') + @mock.patch('validations_libs.utils.parse_validations') def test_spinner_exception_failure_condition(self, mock_validation_dir, mock_exists, mock_access, mock_makedirs, - mock_playbook_check): - - mock_validation_dir.return_value = [{ - 'description': 'My Validation One Description', - 'groups': ['prep', 'pre-deployment'], - 'id': 'foo', - 'name': 'My Validition One Name', - 'parameters': {}, - 'path': '/usr/share/ansible/validation-playbooks'}] + mock_playbook_check, + mock_open, + mock_yaml_load): + mock_validation = Validation('/tmp/foobar/validation-playbooks/foo.yaml') + mock_validation_dir.return_value = [mock_validation] playbook = ['fake.yaml'] inventory = 'tmp/inventory.yaml' @@ -353,24 +342,22 @@ class TestValidationActions(TestCase): inventory, group=fakes.GROUPS_LIST, validations_dir='/tmp/foo') + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('builtins.open') @mock.patch('validations_libs.ansible.Ansible._playbook_check', side_effect=RuntimeError) @mock.patch('validations_libs.utils.os.makedirs') @mock.patch('validations_libs.utils.os.access', return_value=True) @mock.patch('validations_libs.utils.os.path.exists', return_value=True) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk') + @mock.patch('validations_libs.utils.parse_validations') @mock.patch('sys.__stdin__.isatty', return_value=True) def test_spinner_forced_run(self, mock_stdin_isatty, mock_validation_dir, mock_exists, mock_access, mock_makedirs, - mock_playbook_check): + mock_playbook_check, mock_open, + mock_yaml_load): - mock_validation_dir.return_value = [{ - 'description': 'My Validation One Description', - 'groups': ['prep', 'pre-deployment'], - 'id': 'foo', - 'name': 'My Validition One Name', - 'parameters': {}, - 'path': '/usr/share/ansible/validation-playbooks'}] + mock_validation = Validation('/tmp/foobar/validation-playbooks/foo.yaml') + mock_validation_dir.return_value = [mock_validation] playbook = ['fake.yaml'] inventory = 'tmp/inventory.yaml' @@ -380,7 +367,7 @@ class TestValidationActions(TestCase): inventory, group=fakes.GROUPS_LIST, validations_dir='/tmp/foo') - @mock.patch('validations_libs.utils.get_validations_playbook', + @mock.patch('validations_libs.utils.get_validations_playbook_paths', return_value=[]) def test_validation_run_no_validation(self, mock_get_val): playbook = ['fake.yaml'] @@ -390,7 +377,7 @@ class TestValidationActions(TestCase): self.assertRaises(ValidationRunException, run.run_validations, playbook, inventory) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk', + @mock.patch('validations_libs.utils.parse_validations', return_value=fakes.VALIDATIONS_LIST) @mock.patch('validations_libs.validation.Validation._get_content', return_value=fakes.FAKE_PLAYBOOK[0]) @@ -424,7 +411,7 @@ class TestValidationActions(TestCase): '512e' ) - @mock.patch('validations_libs.utils.parse_all_validations_on_disk', + @mock.patch('validations_libs.utils.parse_validations', return_value=fakes.VALIDATIONS_LIST) @mock.patch('yaml.safe_load', return_value=fakes.GROUP) @mock.patch('builtins.open') @@ -465,7 +452,7 @@ class TestValidationActions(TestCase): v_actions.show_validations_parameters, products={'foo': 'bar'}) - @mock.patch('validations_libs.utils.get_validations_playbook', + @mock.patch('validations_libs.utils.get_validations_playbook_paths', return_value=['/foo/playbook/foo.yaml']) @mock.patch('validations_libs.utils.get_validations_parameters') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) diff --git a/validations_libs/utils.py b/validations_libs/utils.py index b2b20ecc..597435b7 100644 --- a/validations_libs/utils.py +++ b/validations_libs/utils.py @@ -31,6 +31,7 @@ from validations_libs import constants from validations_libs.group import Group from validations_libs.validation import Validation from validations_libs.logger import getLogger +from validations_libs import exceptions LOG = getLogger(__name__ + ".utils") @@ -151,13 +152,14 @@ def create_artifacts_dir(log_path=constants.VALIDATIONS_LOG_BASEDIR, raise RuntimeError() -def parse_all_validations_on_disk(path, - groups=None, - categories=None, - products=None, - validation_config=None): - """Return a list of validations metadata which can be sorted by Groups, by - Categories or by Products. +def parse_validations(path, + validation_ids=None, + groups=None, + categories=None, + products=None, + validation_config=None): + """Return a list of validations metadata dictionaries which + can be sorted by Groups, Categories or Products. :param path: The absolute path of the validations directory :type path: `string` @@ -175,8 +177,8 @@ def parse_all_validations_on_disk(path, loaded from an validation.cfg file. :type validation_config: ``dict`` - :return: A list of validations metadata. - :rtype: `list` + :return: A list of `Validation` objects + :rtype: ``list`` :Example: @@ -198,6 +200,11 @@ def parse_all_validations_on_disk(path, if not isinstance(path, str): raise TypeError("The 'path' argument must be a String") + if not validation_ids: + validation_ids = [] + elif not isinstance(validation_ids, list): + raise TypeError("The 'validation_ids' argument must be a List") + if not groups: groups = [] elif not isinstance(groups, list): @@ -214,62 +221,63 @@ def parse_all_validations_on_disk(path, raise TypeError("The 'products' argument must be a List") results = [] - validations_abspath = glob.glob("{path}/*.yaml".format(path=path)) - if community_validations_on(validation_config): - validations_abspath.extend(glob.glob("{}/*.yaml".format( - constants.COMMUNITY_PLAYBOOKS_DIR))) + validations_abspaths = get_validations_playbook_paths( + path=path, + validation_config=validation_config) LOG.debug( "Attempting to parse validations by:\n" + " - ids: {}\n" " - groups: {}\n" " - categories: {}\n" " - products: {}\n" - "from {}".format(groups, categories, products, validations_abspath) + "from {}".format(validation_ids, groups, + categories, products, validations_abspaths) ) - for playbook in validations_abspath: - val = Validation(playbook) - - if not groups and not categories and not products: - results.append(val.get_metadata) + for playbook in validations_abspaths: + try: + val = Validation(playbook) + except exceptions.ValidationParsingException: + LOG.error( + "Attempt to parse playbook at location {} has failed." + "Playbooks is either not a properly formatted validation " + "or a generic ansible playbook.".format(playbook)) continue + # Skip further evaluation and return all if we have nothing to select by. + if not (validation_ids or groups or categories or products): + results.append(val) + continue + if validation_ids: + if val.id in validation_ids: + results.append(val) + if groups: + if set(groups).intersection(val.groups): + results.append(val) + if categories: + if set(categories).intersection(val.categories): + results.append(val) + if products: + if set(products).intersection(val.products): + results.append(val) - if set(groups).intersection(val.groups) or \ - set(categories).intersection(val.categories) or \ - set(products).intersection(val.products): - results.append(val.get_metadata) - + LOG.debug("Retrieved {v_count} validations in pre-defined paths: {paths}".format( + v_count=len(results), + paths=':'.join(constants.VALIDATION_PLAYBOOK_DIRS))) return results -def get_validations_playbook(path, - validation_id=None, - groups=None, - categories=None, - products=None, - validation_config=None): - """Get a list of validations playbooks paths either by their names, - their groups, by their categories or by their products. +def get_validations_playbook_paths(path, + validation_config=None): + """Get a list of validations playbooks paths. :param path: Path of the validations playbooks :type path: `string` - :param validation_id: List of validation name - :type validation_id: `list` - - :param groups: List of validation group - :type groups: `list` - - :param categories: List of validation category - :type categories: `list` - - :param products: List of validation product - :type products: `list` - :param validation_config: A dictionary of configuration for Validation loaded from an validation.cfg file. :type validation_config: ``dict`` - :return: A list of absolute validations playbooks path + :return: A list of absolute validations playbooks paths :rtype: `list` :Example: @@ -290,49 +298,18 @@ def get_validations_playbook(path, if not isinstance(path, str): raise TypeError("The 'path' argument must be a String") - if not validation_id: - validation_id = [] - elif not isinstance(validation_id, list): - raise TypeError("The 'validation_id' 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") - - pl = [] validations_abspath = glob.glob("{path}/*.yaml".format(path=path)) if community_validations_on(validation_config): validations_abspath.extend(glob.glob("{}/*.yaml".format( - constants.COMMUNITY_PLAYBOOKS_DIR))) - for pl_path in validations_abspath: - if os.path.isfile(pl_path): - if validation_id: - if os.path.splitext(os.path.basename(pl_path))[0] in validation_id or \ - os.path.basename(pl_path) in validation_id: - pl.append(pl_path) + constants.COMMUNITY_PLAYBOOKS_DIR))) - val = Validation(pl_path) - if groups: - if set(groups).intersection(val.groups): - pl.append(pl_path) - if categories: - if set(categories).intersection(val.categories): - pl.append(pl_path) - if products: - if set(products).intersection(val.products): - pl.append(pl_path) - return pl + for possible_path in constants.VALIDATION_PLAYBOOK_DIRS: + validations_abspath.extend(glob.glob("{}/*.yaml".format(possible_path))) + + # Filter out all non files that somehow got in + validations_abspath = [path for path in validations_abspath if os.path.isfile(path)] + + return validations_abspath def get_validation_parameters(validation): @@ -439,75 +416,33 @@ def get_validations_data( return data -def get_validations_parameters(validations_data, - validation_name=None, - groups=None, - categories=None, - products=None): +def get_validations_parameters(validations_data): """Return parameters for a list of validations - :param validations_data: A list of absolute validations playbooks path + :param validations_data: A list of `Validation` objects :type validations_data: `list` - :param validation_name: A list of validation name - :type validation_name: `list` - - :param groups: A list of validation groups - :type groups: `list` - - :param categories: A list of validation categories - :type categories: `list` - - :param products: A list of validation products - :type products: `list` - :return: a dictionary containing the current parameters for - each `validation_name` or `groups` + each `Validation` object :rtype: `dict` :Example: - >>> validations_data = ['/foo/bar/check-ram.yaml', - '/foo/bar/check-cpu.yaml'] - >>> validation_name = ['check-ram', 'check-cpu'] - >>> get_validations_parameters(validations_data, validation_name) + >>> validations_data = [Validation('/foo/bar/check-ram.yaml), + Validation('/foo/bar/check-cpu.yaml)] + >>> get_validations_parameters(validations_data) {'check-cpu': {'parameters': {'minimal_cpu_count': 8}}, 'check-ram': {'parameters': {'minimal_ram_gb': 24}}} """ if not isinstance(validations_data, list): raise TypeError("The 'validations_data' argument must be a List") - if not validation_name: - validation_name = [] - elif not isinstance(validation_name, list): - raise TypeError("The 'validation_name' 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") - params = {} for val in validations_data: - v = Validation(val) - if v.id in validation_name or \ - set(groups).intersection(v.groups) or \ - set(categories).intersection(v.categories) or \ - set(products).intersection(v.products): - params[v.id] = { - 'parameters': v.get_vars - } + params[val.id] = { + 'parameters': val.get_vars + } return params diff --git a/validations_libs/validation.py b/validations_libs/validation.py index 65300b1b..def5ba9c 100644 --- a/validations_libs/validation.py +++ b/validations_libs/validation.py @@ -14,6 +14,7 @@ # from validations_libs.logger import getLogger +import validations_libs.exceptions as exceptions import os import yaml from collections import OrderedDict @@ -88,8 +89,11 @@ class Validation: def __init__(self, validation_path): self.dict = self._get_content(validation_path) + if not self.has_metadata_dict: + raise exceptions.ValidationParsingException( + "No metadata found in validation {}".format(validation_path)) self.id = os.path.splitext(os.path.basename(validation_path))[0] - self.path = os.path.dirname(validation_path) + self.path = validation_path def _get_content(self, val_path): try: @@ -163,9 +167,7 @@ class Validation: """Get the metadata of a validation :return: The validation metadata - :rtype: `dict` or `None` if no metadata has been found - :raise: A `NameError` exception if no metadata has been found in the - playbook + :rtype: `dict` :Example: @@ -178,25 +180,18 @@ class Validation: 'products': ['product1', 'product2'], 'id': 'val1', 'name': 'The validation val1\'s name', - 'path': '/tmp/foo/'} + 'path': '/tmp/foo/val1.yaml'} """ - if self.has_metadata_dict: - self.metadata = {'id': self.id, 'path': self.path} - self.metadata.update(self.dict['vars'].get('metadata')) - return self.metadata - else: - raise NameError( - "No metadata found in validation {}".format(self.id) - ) + self.metadata = {'id': self.id, 'path': self.path} + self.metadata.update(self.dict['vars'].get('metadata')) + return self.metadata @property def get_vars(self): """Get only the variables of a validation :return: All the variables belonging to a validation - :rtype: `dict` or `None` if no metadata has been found - :raise: A `NameError` exception if no metadata has been found in the - playbook + :rtype: `dict` :Example: @@ -206,14 +201,9 @@ class Validation: {'var_name1': 'value1', 'var_name2': 'value2'} """ - if self.has_metadata_dict: - validation_vars = self.dict['vars'].copy() - validation_vars.pop('metadata') - return validation_vars - else: - raise NameError( - "No metadata found in validation {}".format(self.id) - ) + validation_vars = self.dict['vars'].copy() + validation_vars.pop('metadata') + return validation_vars @property def get_data(self): @@ -244,9 +234,7 @@ class Validation: """Get the validation list of groups :return: A list of groups for the validation - :rtype: `list` or `None` if no metadata has been found - :raise: A `NameError` exception if no metadata has been found in the - playbook + :rtype: `list` :Example: @@ -255,21 +243,14 @@ class Validation: >>> print(val.groups) ['group1', 'group2'] """ - if self.has_metadata_dict: - return self.dict['vars']['metadata'].get('groups', []) - else: - raise NameError( - "No metadata found in validation {}".format(self.id) - ) + return self.dict['vars']['metadata'].get('groups', []) @property def categories(self): """Get the validation list of categories :return: A list of categories for the validation - :rtype: `list` or `None` if no metadata has been found - :raise: A `NameError` exception if no metadata has been found in the - playbook + :rtype: `list` :Example: @@ -278,21 +259,14 @@ class Validation: >>> print(val.categories) ['category1', 'category2'] """ - if self.has_metadata_dict: - return self.dict['vars']['metadata'].get('categories', []) - else: - raise NameError( - "No metadata found in validation {}".format(self.id) - ) + return self.dict['vars']['metadata'].get('categories', []) @property def products(self): """Get the validation list of products :return: A list of products for the validation - :rtype: `list` or `None` if no metadata has been found - :raise: A `NameError` exception if no metadata has been found in the - playbook + :rtype: `list` :Example: @@ -301,12 +275,7 @@ class Validation: >>> print(val.products) ['product1', 'product2'] """ - if self.has_metadata_dict: - return self.dict['vars']['metadata'].get('products', []) - else: - raise NameError( - "No metadata found in validation {}".format(self.id) - ) + return self.dict['vars']['metadata'].get('products', []) @property def get_id(self): @@ -342,8 +311,6 @@ class Validation: the list of 'Categories', the list of `Groups`, the `ID` and the `Name`. :rtype: `dict` - :raise: A `NameError` exception if no metadata has been found in the - playbook :Example: diff --git a/validations_libs/validation_actions.py b/validations_libs/validation_actions.py index a1275429..2c2d66be 100644 --- a/validations_libs/validation_actions.py +++ b/validations_libs/validation_actions.py @@ -12,19 +12,21 @@ # License for the specific language governing permissions and limitations # under the License. # -from validations_libs.logger import getLogger +import json import os import sys -import json + import yaml -from validations_libs.ansible import Ansible as v_ansible -from validations_libs.group import Group -from validations_libs.cli.common import Spinner -from validations_libs.validation_logs import ValidationLogs, ValidationLog from validations_libs import constants from validations_libs import utils as v_utils -from validations_libs.exceptions import ValidationRunException, ValidationShowException +from validations_libs.ansible import Ansible as v_ansible +from validations_libs.cli.common import Spinner +from validations_libs.exceptions import (ValidationRunException, + ValidationShowException) +from validations_libs.group import Group +from validations_libs.logger import getLogger +from validations_libs.validation_logs import ValidationLog, ValidationLogs LOG = getLogger(__name__ + ".validation_actions") @@ -43,7 +45,7 @@ class ValidationActions: """ - def __init__(self, validation_path=constants.ANSIBLE_VALIDATION_DIR, + def __init__(self, base_validation_path=constants.ANSIBLE_VALIDATION_DIR, groups_path=constants.VALIDATION_GROUPS_INFO, log_path=constants.VALIDATIONS_LOG_BASEDIR): """ @@ -56,7 +58,7 @@ class ValidationActions: :type log_path: ``string`` """ self.log = getLogger(__name__ + ".ValidationActions") - self.validation_path = validation_path + self.base_validation_path = base_validation_path self.log_path = log_path self.groups_path = groups_path @@ -121,8 +123,8 @@ class ValidationActions: """ self.log = getLogger(__name__ + ".list_validations") - validations = v_utils.parse_all_validations_on_disk( - path=self.validation_path, + validations = v_utils.parse_validations( + path=self.base_validation_path, groups=groups, categories=categories, products=products, @@ -182,7 +184,7 @@ class ValidationActions: vlog = ValidationLogs(self.log_path) data = v_utils.get_validations_data( validation, - self.validation_path, + self.base_validation_path, validation_config=validation_config) if not data: extra_msg = "" @@ -190,7 +192,7 @@ class ValidationActions: extra_msg = " or {}".format(constants.COMMUNITY_LIBRARY_DIR) msg = "Validation {} not found in the path: {}{}".format( validation, - self.validation_path, + self.base_validation_path, extra_msg) raise ValidationShowException(msg) @@ -319,7 +321,7 @@ class ValidationActions: extra_vars=None, validations_dir=None, extra_env_vars=None, ansible_cfg=None, quiet=True, limit_hosts=None, run_async=False, - base_dir=constants.DEFAULT_VALIDATIONS_BASEDIR, + base_dir=constants.DEFAULT_ANSIBLE_BASEDIR, python_interpreter=None, skip_list=None, callback_whitelist=None, output_callback='vf_validation_stdout', ssh_user=None, @@ -418,48 +420,44 @@ class ValidationActions: self.log = getLogger(__name__ + ".run_validations") playbooks = [] validations_dir = (validations_dir if validations_dir - else self.validation_path) - if group or category or product: - self.log.debug( - "Getting the validations list by:\n" - " - groups: {}\n" - " - categories: {}\n" - " - products: {}".format(group, category, product) - ) - validations = v_utils.parse_all_validations_on_disk( - path=validations_dir, groups=group, - categories=category, products=product, - validation_config=validation_config - ) - for val in validations: - playbooks.append("{path}/{id}.yaml".format(**val)) - elif validation_name: + else self.base_validation_path) + self.log.debug( + "Getting the validations list by:\n" + " - ids: {}\n" + " - groups: {}\n" + " - categories: {}\n" + " - products: {}".format(validation_name, group, category, product) + ) + validations = v_utils.parse_validations( + path=validations_dir, validation_ids=validation_name, + groups=group, + categories=category, products=product, + validation_config=validation_config) + + if 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 validations or len(validation_name) != len(validations): + found_validations = [] + for val in validations: + found_validations.append(val.id) - if not playbooks or len(validation_name) != len(playbooks): - found_playbooks = [] - for play in playbooks: - found_playbooks.append( - os.path.basename(os.path.splitext(play)[0])) - - unknown_validations = list( - set(validation_name) - set(found_playbooks)) + missing_validations = list( + set(validation_name) - set(found_validations)) msg = ( "Following validations were not found in '{}': {}" - ).format(validations_dir, ', '.join(unknown_validations)) + ).format(validations_dir, ', '.join(missing_validations)) raise ValidationRunException(msg) - else: + if len(validations) == 0: raise ValidationRunException("No validations found") + for val in validations: + playbooks.append("{path}".format(path=val.path)) + log_path = v_utils.create_log_dir(self.log_path) self.log.debug(( @@ -589,8 +587,8 @@ class ValidationActions: group_info = [] - validations = v_utils.parse_all_validations_on_disk( - path=self.validation_path, + validations = v_utils.parse_validations( + path=self.base_validation_path, groups=[group[0] for group in group_definitions], validation_config=validation_config) @@ -675,22 +673,16 @@ class ValidationActions: if output_format not in supported_format: raise ValidationShowException("{} output format not supported".format(output_format)) - validation_playbooks = v_utils.get_validations_playbook( - path=self.validation_path, - validation_id=validations, + validations = v_utils.parse_validations( + path=self.base_validation_path, + validation_ids=validations, groups=groups, categories=categories, products=products, validation_config=validation_config ) - params = v_utils.get_validations_parameters( - validations_data=validation_playbooks, - validation_name=validations, - groups=groups, - categories=categories, - products=products - ) + params = v_utils.get_validations_parameters(validations_data=validations) if download_file: params_only = {}