From 5ba9c429d56328a616ced6ab0ace0b0e4a070467 Mon Sep 17 00:00:00 2001 From: apetrich Date: Thu, 28 Oct 2021 13:59:21 +0200 Subject: [PATCH] Add the community validation paths The validation framework now respect and look for community validation on the path. There config setting for enable_community_validations is respected. The community validations appear on validations list, can be called by group, category, id and so on seamless like any other validation. Change-Id: I4a6230ac5433028c7297fa88f77728c87d60173d Signed-off-by: Adriano Petrich (apetrich) --- validations_libs/ansible.py | 25 ++- validations_libs/cli/lister.py | 3 +- validations_libs/cli/show.py | 10 +- validations_libs/tests/fakes.py | 9 +- validations_libs/tests/test_ansible.py | 51 ++++++ validations_libs/tests/test_utils.py | 151 +++++++++++++++--- .../tests/test_validation_actions.py | 66 +++++++- validations_libs/utils.py | 60 ++++++- validations_libs/validation.py | 9 +- validations_libs/validation_actions.py | 72 ++++++--- 10 files changed, 386 insertions(+), 70 deletions(-) diff --git a/validations_libs/ansible.py b/validations_libs/ansible.py index 4b1ef1fd..94272cd0 100644 --- a/validations_libs/ansible.py +++ b/validations_libs/ansible.py @@ -126,8 +126,16 @@ class Ansible(object): gathering_policy, module_path, key, extra_env_variables, ansible_timeout, callback_whitelist, base_dir, python_interpreter, - env={}): + env={}, validation_cfg_file=None): """Handle Ansible env var for Ansible config execution""" + community_roles = "" + community_library = "" + community_lookup = "" + if utils.community_validations_on(validation_cfg_file): + community_roles = f"{constants.COMMUNITY_ROLES_DIR}:" + community_library = f"{constants.COMMUNITY_LIBRARY_DIR}:" + community_lookup = f"{constants.COMMUNITY_LOOKUP_DIR}:" + cwd = os.getcwd() env['ANSIBLE_SSH_ARGS'] = ( '-o UserKnownHostsFile={} ' @@ -159,10 +167,12 @@ class Ansible(object): '{}:{}:' '/usr/share/ansible/plugins/modules:' '/usr/share/ceph-ansible/library:' + '{community_path}' '{}/library'.format( os.path.join(workdir, 'modules'), os.path.join(cwd, 'modules'), - base_dir + base_dir, + community_path=community_library ) ) env['ANSIBLE_LOOKUP_PLUGINS'] = os.path.expanduser( @@ -170,10 +180,12 @@ class Ansible(object): '{}:{}:' '/usr/share/ansible/plugins/lookup:' '/usr/share/ceph-ansible/plugins/lookup:' + '{community_path}' '{}/lookup_plugins'.format( os.path.join(workdir, 'lookup'), os.path.join(cwd, 'lookup'), - base_dir + base_dir, + community_path=community_lookup ) ) env['ANSIBLE_CALLBACK_PLUGINS'] = os.path.expanduser( @@ -215,10 +227,12 @@ class Ansible(object): '/usr/share/ansible/roles:' '/usr/share/ceph-ansible/roles:' '/etc/ansible/roles:' + '{community_path}' '{}/roles'.format( os.path.join(workdir, 'roles'), os.path.join(cwd, 'roles'), - base_dir + base_dir, + community_path=community_roles ) ) env['ANSIBLE_CALLBACK_WHITELIST'] = callback_whitelist @@ -421,7 +435,8 @@ class Ansible(object): connection, gathering_policy, module_path, key, extra_env_variables, ansible_timeout, callback_whitelist, - base_dir, python_interpreter)) + base_dir, python_interpreter, + validation_cfg_file=validation_cfg_file)) if 'ANSIBLE_CONFIG' not in env and not ansible_cfg_file: ansible_cfg_file = os.path.join(ansible_artifact_path, diff --git a/validations_libs/cli/lister.py b/validations_libs/cli/lister.py index 7ca32683..bc4d084e 100644 --- a/validations_libs/cli/lister.py +++ b/validations_libs/cli/lister.py @@ -66,4 +66,5 @@ class ValidationList(BaseLister): v_actions = ValidationActions(validation_path=validation_dir) return (v_actions.list_validations(groups=group, categories=category, - products=product)) + products=product, + validation_config=self.base.config)) diff --git a/validations_libs/cli/show.py b/validations_libs/cli/show.py index 1d4c6703..6bcead24 100644 --- a/validations_libs/cli/show.py +++ b/validations_libs/cli/show.py @@ -44,7 +44,8 @@ class Show(BaseShow): validation_name = parsed_args.validation_name v_actions = ValidationActions(validation_path=validation_dir) - data = v_actions.show_validations(validation_name) + data = v_actions.show_validations( + validation_name, validation_config=self.base.config) if data: return data.keys(), data.values() @@ -69,7 +70,9 @@ class ShowGroup(BaseLister): """Take validation action""" v_actions = ValidationActions(parsed_args.validation_dir) - return v_actions.group_information(constants.VALIDATION_GROUPS_INFO) + return v_actions.group_information( + constants.VALIDATION_GROUPS_INFO, + validation_config=self.base.config) class ShowParameter(BaseShow): @@ -160,7 +163,8 @@ class ShowParameter(BaseShow): categories=parsed_args.category, products=parsed_args.product, output_format=parsed_args.format_output, - download_file=parsed_args.download) + download_file=parsed_args.download, + validation_config=self.base.config) if parsed_args.download: self.app.LOG.info( diff --git a/validations_libs/tests/fakes.py b/validations_libs/tests/fakes.py index 161e26ff..20350196 100644 --- a/validations_libs/tests/fakes.py +++ b/validations_libs/tests/fakes.py @@ -248,7 +248,8 @@ FAKE_PLAYBOOK = [{'hosts': 'undercloud', 'categories': ['os', 'storage'], 'products': ['product1'], 'name': - 'Advanced Format 512e Support'}}}] + 'Advanced Format 512e Support', + 'path': '/tmp'}}}] FAKE_PLAYBOOK2 = [{'hosts': 'undercloud', 'roles': ['advanced_format_512e_support'], @@ -274,14 +275,16 @@ FAKE_METADATA = {'id': 'foo', 'groups': ['prep', 'pre-deployment'], 'categories': ['os', 'storage'], 'products': ['product1'], - 'name': 'Advanced Format 512e Support'} + 'name': 'Advanced Format 512e Support', + 'path': '/tmp'} FORMATED_DATA = {'Description': 'foo', 'Groups': ['prep', 'pre-deployment'], 'Categories': ['os', 'storage'], 'Products': ['product1'], 'ID': 'foo', - 'Name': 'Advanced Format 512e Support'} + 'Name': 'Advanced Format 512e Support', + 'Path': '/tmp'} GROUP = {'no-op': [{'description': 'noop-foo'}], 'pre': [{'description': 'pre-foo'}], diff --git a/validations_libs/tests/test_ansible.py b/validations_libs/tests/test_ansible.py index 827462ce..067b7648 100644 --- a/validations_libs/tests/test_ansible.py +++ b/validations_libs/tests/test_ansible.py @@ -21,6 +21,7 @@ except ImportError: from unittest import TestCase from ansible_runner import Runner +from validations_libs import constants from validations_libs.ansible import Ansible from validations_libs.tests import fakes @@ -175,6 +176,56 @@ class TestAnsible(TestCase): '/tmp/foo/fact_cache' )) + def test_ansible_env_var_with_community_validations(self): + # AP No config file (use the default True) + env = self.run._ansible_env_var( + output_callback="", ssh_user="", workdir="", connection="", + gathering_policy="", module_path="", key="", + extra_env_variables="", ansible_timeout="", + callback_whitelist="", base_dir="", python_interpreter="", + env={}, validation_cfg_file=None) + + assert(f"{constants.COMMUNITY_LIBRARY_DIR}:" in env["ANSIBLE_LIBRARY"]) + assert(f"{constants.COMMUNITY_ROLES_DIR}:" in env["ANSIBLE_ROLES_PATH"]) + assert(f"{constants.COMMUNITY_LOOKUP_DIR}:" in env["ANSIBLE_LOOKUP_PLUGINS"]) + + # AP config file with no settting (use the default True) + env = self.run._ansible_env_var( + output_callback="", ssh_user="", workdir="", connection="", + gathering_policy="", module_path="", key="", + extra_env_variables="", ansible_timeout="", + callback_whitelist="", base_dir="", python_interpreter="", + env={}, validation_cfg_file={"default": {}}) + + assert(f"{constants.COMMUNITY_LIBRARY_DIR}:" in env["ANSIBLE_LIBRARY"]) + assert(f"{constants.COMMUNITY_ROLES_DIR}:" in env["ANSIBLE_ROLES_PATH"]) + assert(f"{constants.COMMUNITY_LOOKUP_DIR}:" in env["ANSIBLE_LOOKUP_PLUGINS"]) + + # AP config file with settting True + env = self.run._ansible_env_var( + output_callback="", ssh_user="", workdir="", connection="", + gathering_policy="", module_path="", key="", + extra_env_variables="", ansible_timeout="", + callback_whitelist="", base_dir="", python_interpreter="", + env={}, validation_cfg_file={"default": {"enable_community_validations": True}}) + + assert(f"{constants.COMMUNITY_LIBRARY_DIR}:" in env["ANSIBLE_LIBRARY"]) + assert(f"{constants.COMMUNITY_ROLES_DIR}:" in env["ANSIBLE_ROLES_PATH"]) + assert(f"{constants.COMMUNITY_LOOKUP_DIR}:" in env["ANSIBLE_LOOKUP_PLUGINS"]) + + def test_ansible_env_var_without_community_validations(self): + # AP config file with settting False + env = self.run._ansible_env_var( + output_callback="", ssh_user="", workdir="", connection="", + gathering_policy="", module_path="", key="", + extra_env_variables="", ansible_timeout="", + callback_whitelist="", base_dir="", python_interpreter="", + env={}, validation_cfg_file={"default": {"enable_community_validations": False}}) + + assert(f"{constants.COMMUNITY_LIBRARY_DIR}:" not in env["ANSIBLE_LIBRARY"]) + assert(f"{constants.COMMUNITY_ROLES_DIR}:" not in env["ANSIBLE_ROLES_PATH"]) + assert(f"{constants.COMMUNITY_LOOKUP_DIR}:" not in env["ANSIBLE_LOOKUP_PLUGINS"]) + def test_get_extra_vars_dict(self): extra_vars = { 'foo': 'bar' diff --git a/validations_libs/tests/test_utils.py b/validations_libs/tests/test_utils.py index d9d0498c..1786accf 100644 --- a/validations_libs/tests/test_utils.py +++ b/validations_libs/tests/test_utils.py @@ -44,10 +44,48 @@ class TestUtils(TestCase): 'Categories': ['os', 'storage'], 'Products': ['product1'], 'ID': '512e', - 'Parameters': {}} + 'Parameters': {}, + 'Path': '/tmp'} res = utils.get_validations_data('512e') self.assertEqual(res, output) + @mock.patch('validations_libs.validation.Validation._get_content', + return_value=fakes.FAKE_PLAYBOOK[0]) + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', side_effect=(False, True)) + def test_get_community_validations_data(self, mock_exists, mock_open, mock_data): + """ + The main difference between this test and test_get_validations_data + is that this one tries to load first the validations_commons validation + then it fails as os.path.exists returns false and then looks for it in the + community validations. + """ + output = {'Name': 'Advanced Format 512e Support', + 'Description': 'foo', 'Groups': ['prep', 'pre-deployment'], + 'Categories': ['os', 'storage'], + 'Products': ['product1'], + 'ID': '512e', + 'Parameters': {}, + 'Path': '/tmp'} + res = utils.get_validations_data('512e') + self.assertEqual(res, output) + + @mock.patch('validations_libs.validation.Validation._get_content', + return_value=fakes.FAKE_PLAYBOOK[0]) + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', side_effect=(False, True)) + def test_get_community_disabled_validations_data(self, mock_exists, mock_open, mock_data): + """ + This test is similar to test_get_community_validations_data in the sense that it + doesn't find the validations_commons one and should look for community validations + but the setting is disabled by the config so it shouldn't find any validations + """ + output = {} + res = utils.get_validations_data( + '512e', + validation_config={'default': {"enable_community_validations": False}}) + self.assertEqual(res, output) + @mock.patch('os.path.exists', return_value=True) def test_get_validations_data_wrong_type(self, mock_exists): validation = ['val1'] @@ -60,11 +98,33 @@ class TestUtils(TestCase): @mock.patch('glob.glob') def test_parse_all_validations_on_disk(self, mock_glob, mock_open, mock_load): - mock_glob.return_value = \ - ['/foo/playbook/foo.yaml'] + mock_glob.side_effect = \ + (['/foo/playbook/foo.yaml'], []) result = utils.parse_all_validations_on_disk('/foo/playbook') self.assertEqual(result, [fakes.FAKE_METADATA]) + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('six.moves.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]) + + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('six.moves.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( + '/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, @@ -118,8 +178,8 @@ class TestUtils(TestCase): def test_parse_all_validations_on_disk_by_group(self, mock_glob, mock_open, mock_load): - mock_glob.return_value = \ - ['/foo/playbook/foo.yaml'] + mock_glob.side_effect = \ + (['/foo/playbook/foo.yaml'], []) result = utils.parse_all_validations_on_disk('/foo/playbook', ['prep']) self.assertEqual(result, [fakes.FAKE_METADATA]) @@ -130,8 +190,8 @@ class TestUtils(TestCase): def test_parse_all_validations_on_disk_by_category(self, mock_glob, mock_open, mock_load): - mock_glob.return_value = \ - ['/foo/playbook/foo.yaml'] + 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]) @@ -147,31 +207,80 @@ class TestUtils(TestCase): def test_parse_all_validations_on_disk_by_product(self, mock_glob, mock_open, mock_load): - mock_glob.return_value = \ - ['/foo/playbook/foo.yaml'] + 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.patch('os.path.isfile') - @mock.patch('os.listdir') + @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('six.moves.builtins.open') def test_get_validations_playbook_by_id(self, mock_open, mock_load, - mock_listdir, mock_isfile): - mock_listdir.return_value = ['foo.yaml'] + 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.patch('os.path.isfile') - @mock.patch('os.listdir') + @mock.patch('glob.glob') + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('six.moves.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 + # 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']) + + @mock.patch('os.path.isfile') + @mock.patch('glob.glob') + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('six.moves.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 + # The validations_commons validation is not found and community_vals is disabled + # So no validation should be found. + result = utils.get_validations_playbook( + '/foo/playbook', + validation_id=['foo'], + validation_config={'default': {"enable_community_validations": False}}) + self.assertEqual(result, []) + + @mock.patch('os.path.isfile') + @mock.patch('glob.glob') + @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) + @mock.patch('six.moves.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']) + self.assertEqual(result, []) + + @mock.patch('os.path.isfile') + @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('six.moves.builtins.open') def test_get_validations_playbook_by_id_group(self, mock_open, mock_load, - mock_listdir, mock_isfile): - mock_listdir.return_value = ['foo.yaml'] + 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', @@ -192,24 +301,24 @@ class TestUtils(TestCase): self.assertEqual(result, []) @mock.patch('os.path.isfile') - @mock.patch('os.listdir') + @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('six.moves.builtins.open') def test_get_validations_playbook_by_category(self, mock_open, mock_load, - mock_listdir, mock_isfile): - mock_listdir.return_value = ['foo.yaml'] + 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.patch('os.path.isfile') - @mock.patch('os.listdir') + @mock.patch('glob.glob') @mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK) @mock.patch('six.moves.builtins.open') def test_get_validations_playbook_by_product(self, mock_open, mock_load, - mock_listdir, mock_isfile): - mock_listdir.return_value = ['foo.yaml'] + 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']) diff --git a/validations_libs/tests/test_validation_actions.py b/validations_libs/tests/test_validation_actions.py index 55bfeff8..dbbf7e8e 100644 --- a/validations_libs/tests/test_validation_actions.py +++ b/validations_libs/tests/test_validation_actions.py @@ -15,8 +15,10 @@ try: from unittest import mock + from unittest.mock import ANY except ImportError: import mock + from mock import ANY import json @@ -209,7 +211,8 @@ class TestValidationActions(TestCase): 'groups': ['prep', 'pre-deployment'], 'id': 'foo', 'name': 'My Validition One Name', - 'parameters': {}}] + 'parameters': {}, + 'path': '/tmp/foobar/validation-playbooks'}] mock_ansible_run.return_value = ('foo.yaml', 0, 'successful') @@ -224,6 +227,29 @@ class TestValidationActions(TestCase): validations_dir='/tmp/foo') self.assertEqual(run_return, expected_run_return) + mock_ansible_run.assert_called_with( + workdir=ANY, + playbook='/tmp/foobar/validation-playbooks/foo.yaml', + base_dir='/usr/share/ansible', + playbook_dir='/tmp/foobar/validation-playbooks', + parallel_run=True, + inventory='tmp/inventory.yaml', + output_callback='validation_stdout', + callback_whitelist=None, + quiet=True, + extra_vars=None, + limit_hosts=None, + extra_env_variables=None, + ansible_cfg_file=None, + gathering_policy='explicit', + ansible_artifact_path=ANY, + log_path=ANY, + run_async=False, + python_interpreter=None, + ssh_user=None, + validation_cfg_file=None + ) + @mock.patch('validations_libs.utils.get_validations_playbook') def test_validation_run_wrong_validation_name(self, mock_validation_play): mock_validation_play.return_value = [] @@ -234,6 +260,31 @@ class TestValidationActions(TestCase): validations_dir='/tmp/foo' ) + @mock.patch('validations_libs.utils.get_validations_playbook') + def test_validation_run_not_all_found(self, mock_validation_play): + + mock_validation_play.return_value = ['/tmp/foo/fake.yaml'] + run = ValidationActions() + try: + run.run_validations( + validation_name=['fake', 'foo'], + validations_dir='/tmp/foo') + except RuntimeError as runtime_error: + self.assertEqual( + "Validation ['foo'] not found in /tmp/foo.", + str(runtime_error)) + else: + self.fail("Runtime error exception should have been raised") + + @mock.patch('validations_libs.utils.parse_all_validations_on_disk') + def test_validation_run_not_enough_params(self, mock_validation_play): + mock_validation_play.return_value = [] + + run = ValidationActions() + self.assertRaises(RuntimeError, run.run_validations, + validations_dir='/tmp/foo' + ) + @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) @@ -250,7 +301,8 @@ class TestValidationActions(TestCase): 'groups': ['prep', 'pre-deployment'], 'id': 'foo', 'name': 'My Validition One Name', - 'parameters': {}}] + 'parameters': {}, + 'path': '/usr/share/ansible/validation-playbooks'}] mock_ansible_run.return_value = ('foo.yaml', 0, 'failed') @@ -295,7 +347,8 @@ class TestValidationActions(TestCase): 'groups': ['prep', 'pre-deployment'], 'id': 'foo', 'name': 'My Validition One Name', - 'parameters': {}}] + 'parameters': {}, + 'path': '/usr/share/ansible/validation-playbooks'}] playbook = ['fake.yaml'] inventory = 'tmp/inventory.yaml' @@ -321,7 +374,8 @@ class TestValidationActions(TestCase): 'groups': ['prep', 'pre-deployment'], 'id': 'foo', 'name': 'My Validition One Name', - 'parameters': {}}] + 'parameters': {}, + 'path': '/usr/share/ansible/validation-playbooks'}] playbook = ['fake.yaml'] inventory = 'tmp/inventory.yaml' @@ -357,7 +411,9 @@ class TestValidationActions(TestCase): 'Categories': ['os', 'storage'], 'Products': ['product1'], 'ID': '512e', - 'Parameters': {}} + 'Parameters': {}, + 'Path': '/tmp' + } data.update({'Last execution date': '2019-11-25 13:40:14', 'Number of execution': 'Total: 1, Passed: 0, Failed: 1'}) validations_show = ValidationActions() diff --git a/validations_libs/utils.py b/validations_libs/utils.py index e21284c6..ed539468 100644 --- a/validations_libs/utils.py +++ b/validations_libs/utils.py @@ -37,6 +37,21 @@ def current_time(): return '%sZ' % datetime.datetime.utcnow().isoformat() +def community_validations_on(validation_config): + """Check for flag for community validations to be enabled + The default value is true + + :param validation_config: A dictionary of configuration for Validation + loaded from an validation.cfg file. + :type validation_config: ``dict`` + :return: A boolean with the status of community validations flag + :rtype: `bool` + """ + if not validation_config: + return True + return validation_config.get("default", {}).get("enable_community_validations", True) + + def create_log_dir(log_path=constants.VALIDATIONS_LOG_BASEDIR): """Check for presence of the selected validations log dir. Create the directory if needed, and use fallback if that @@ -136,7 +151,8 @@ def create_artifacts_dir(log_path=constants.VALIDATIONS_LOG_BASEDIR, def parse_all_validations_on_disk(path, groups=None, categories=None, - products=None): + products=None, + validation_config=None): """Return a list of validations metadata which can be sorted by Groups, by Categories or by Products. @@ -152,6 +168,10 @@ def parse_all_validations_on_disk(path, :param products: Products of validations :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 validations metadata. :rtype: `list` @@ -192,6 +212,9 @@ def parse_all_validations_on_disk(path, 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))) LOG.debug( "Attempting to parse validations by:\n" @@ -200,7 +223,6 @@ def parse_all_validations_on_disk(path, " - products: {}\n" "from {}".format(groups, categories, products, validations_abspath) ) - for playbook in validations_abspath: val = Validation(playbook) @@ -220,7 +242,8 @@ def get_validations_playbook(path, validation_id=None, groups=None, categories=None, - products=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. @@ -239,6 +262,10 @@ def get_validations_playbook(path, :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 :rtype: `list` @@ -281,12 +308,15 @@ def get_validations_playbook(path, raise TypeError("The 'products' argument must be a List") pl = [] - for f in os.listdir(path): - pl_path = join(path, f) + 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(f)[0] in validation_id or \ - os.path.basename(f) in 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) val = Validation(pl_path) @@ -377,7 +407,10 @@ def get_validations_details(validation): return {} -def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR): +def get_validations_data( + validation, + path=constants.ANSIBLE_VALIDATION_DIR, + validation_config=None): """Return validation data with format: ID, Name, Description, Groups, Parameters @@ -387,6 +420,9 @@ def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR): :type validation: `string` :param path: The path to the validations directory :type path: `string` + :param validation_config: A dictionary of configuration for Validation + loaded from an validation.cfg file. + :type validation_config: ``dict`` :return: The validation data with the format (ID, Name, Description, Groups, Parameters) :rtype: `dict` @@ -408,6 +444,9 @@ def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR): data = {} val_path = "{}/{}.yaml".format(path, validation) + comm_path = "" + if community_validations_on(validation_config): + comm_path = "{}/{}.yaml".format(constants.COMMUNITY_PLAYBOOKS_DIR, validation) LOG.debug( "Obtaining information about validation {} from {}".format( @@ -419,6 +458,11 @@ def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR): val = Validation(val_path) data.update(val.get_formated_data) data.update({'Parameters': val.get_vars}) + if not data and comm_path: + if os.path.exists(comm_path): + val = Validation(comm_path) + data.update(val.get_formated_data) + data.update({'Parameters': val.get_vars}) return data diff --git a/validations_libs/validation.py b/validations_libs/validation.py index 330e288f..34d766bc 100644 --- a/validations_libs/validation.py +++ b/validations_libs/validation.py @@ -89,6 +89,7 @@ class Validation(object): def __init__(self, validation_path): self.dict = self._get_content(validation_path) self.id = os.path.splitext(os.path.basename(validation_path))[0] + self.path = os.path.dirname(validation_path) def _get_content(self, val_path): try: @@ -176,10 +177,11 @@ class Validation(object): 'categories': ['category1', 'category2'], 'products': ['product1', 'product2'], 'id': 'val1', - 'name': 'The validation val1\'s name'} + 'name': 'The validation val1\'s name', + 'path': '/tmp/foo/'} """ if self.has_metadata_dict: - self.metadata = {'id': self.id} + self.metadata = {'id': self.id, 'path': self.path} self.metadata.update(self.dict['vars'].get('metadata')) return self.metadata else: @@ -353,7 +355,8 @@ class Validation(object): 'Description': 'description of val', 'Groups': ['group1', 'group2'], 'ID': 'val', - 'Name': 'validation one'} + 'Name': 'validation one', + 'path': '/tmp/foo/'} """ data = {} metadata = self.get_metadata diff --git a/validations_libs/validation_actions.py b/validations_libs/validation_actions.py index fa0d7037..ab3c5cb4 100644 --- a/validations_libs/validation_actions.py +++ b/validations_libs/validation_actions.py @@ -50,7 +50,8 @@ class ValidationActions(object): def list_validations(self, groups=None, categories=None, - products=None): + products=None, + validation_config=None): """Get a list of the validations selected by group membership or by category. With their names, group membership information, categories and products. @@ -66,6 +67,10 @@ class ValidationActions(object): :param products: List of validation products. :type products: `list` + :param validation_config: A dictionary of configuration for Validation + loaded from an validation.cfg file. + :type validation_config: ``dict`` + :return: Column names and a list of the selected validations :rtype: `tuple` @@ -106,7 +111,8 @@ class ValidationActions(object): path=self.validation_path, groups=groups, categories=categories, - products=products + products=products, + validation_config=validation_config ) self.log.debug( @@ -124,13 +130,18 @@ class ValidationActions(object): return (column_names, return_values) def show_validations(self, validation, - log_path=constants.VALIDATIONS_LOG_BASEDIR): + log_path=constants.VALIDATIONS_LOG_BASEDIR, + 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 :type log_path: `string` + :param validation_config: A dictionary of configuration for Validation + loaded from an validation.cfg file. + :type validation_config: ``dict`` + :return: The detailed information for a validation :rtype: `dict` @@ -156,11 +167,18 @@ class ValidationActions(object): self.log = logging.getLogger(__name__ + ".show_validations") # Get validation data: vlog = ValidationLogs(log_path) - data = v_utils.get_validations_data(validation, self.validation_path) - if not data: - msg = "Validation {} not found in the path: {}".format( + data = v_utils.get_validations_data( validation, - self.validation_path) + self.validation_path, + validation_config=validation_config) + if not data: + extra_msg = "" + if v_utils.community_validations_on(validation_config): + extra_msg = " or {}".format(constants.COMMUNITY_LIBRARY_DIR) + msg = "Validation {} not found in the path: {}{}".format( + validation, + self.validation_path, + extra_msg) raise RuntimeError(msg) logfiles = vlog.get_logfile_content_by_validation(validation) data_format = vlog.get_validations_stats(logfiles) @@ -331,15 +349,15 @@ class ValidationActions(object): (Defaults to 'None') :type skip_list: ``dict`` - :return: A list of dictionary containing the informations of the - validations executions (Validations, Duration, Host_Group, - Status, Status_by_Host, UUID and Unreachable_Hosts) - :rtype: ``list`` :param ssh_user: Ssh user for Ansible remote connection :type ssh_user: ``string`` :param validation_config: A dictionary of configuration for Validation loaded from an validation.cfg file. :type validation_config: ``dict`` + :return: A list of dictionary containing the informations of the + validations executions (Validations, Duration, Host_Group, + Status, Status_by_Host, UUID and Unreachable_Hosts) + :rtype: ``list`` :Example: @@ -378,13 +396,16 @@ class ValidationActions(object): ) validations = v_utils.parse_all_validations_on_disk( path=validations_dir, groups=group, - categories=category, products=product + categories=category, products=product, + validation_config=validation_config ) for val in validations: - playbooks.append(val.get('id') + '.yaml') + playbooks.append("{path}/{id}.yaml".format(**val)) elif validation_name: - playbooks = v_utils.get_validations_playbook(validations_dir, - 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 = [] @@ -419,7 +440,7 @@ class ValidationActions(object): workdir=artifacts_dir, playbook=playbook, base_dir=base_dir, - playbook_dir=validations_dir, + playbook_dir=os.path.dirname(playbook), parallel_run=True, inventory=inventory, output_callback=output_callback, @@ -441,7 +462,7 @@ class ValidationActions(object): workdir=artifacts_dir, playbook=playbook, base_dir=base_dir, - playbook_dir=validations_dir, + playbook_dir=os.path.dirname(playbook), parallel_run=True, inventory=inventory, output_callback=output_callback, @@ -483,7 +504,7 @@ class ValidationActions(object): vlog = ValidationLogs(log_path) return vlog.get_results(uuid) - def group_information(self, groups): + def group_information(self, groups, validation_config=None): """Get Information about Validation Groups This is used to print table from python ``Tuple`` with ``PrettyTable``. @@ -500,6 +521,9 @@ class ValidationActions(object): :param groups: The absolute path of the groups.yaml file :type groups: ``string`` + :param validation_config: A dictionary of configuration for Validation + loaded from an validation.cfg file. + :type validation_config: ``dict`` :return: The list of the available groups with their description and the numbers of validation belonging to them. @@ -523,7 +547,8 @@ class ValidationActions(object): validations = v_utils.parse_all_validations_on_disk( path=self.validation_path, - groups=[group[0] for group in group_definitions]) + groups=[group[0] for group in group_definitions], + validation_config=validation_config) # Get validations number by group for group in group_definitions: @@ -543,7 +568,8 @@ class ValidationActions(object): categories=None, products=None, output_format='json', - download_file=None): + download_file=None, + validation_config=None): """ Return Validations Parameters for one or several validations by their names, their groups, by their categories or by their products. @@ -566,6 +592,9 @@ class ValidationActions(object): :param download_file: Path of a file in which the parameters will be stored :type download_file: `string` + :param validation_config: A dictionary of configuration for Validation + loaded from an validation.cfg file. + :type validation_config: ``dict`` :return: A JSON or a YAML dump (By default, JSON). if `download_file` is used, a file containing only the @@ -622,7 +651,8 @@ class ValidationActions(object): validation_id=validations, groups=groups, categories=categories, - products=products + products=products, + validation_config=validation_config ) params = v_utils.get_validations_parameters(