Add Products metadata key management
This patch adds the management of the new `products` metadata key in the validation playbooks. We can now filter the validations by their groups, by their categories or by their products while listing or running them. The `list` sub command has now a new --product argument. When filtering by groups, by categories or by products (see the example below), the `list` sub command will return all the validation playbooks belonging to the 'prep' group OR all the validation playbooks belonging to the 'os' and/or 'system' categories OR all the validation playbooks to the 'tripleo' product: $ validation list -h $ validation list --group prep --category os,system --product tripleo The `run` sub command has also its new --product argument. Note that this new argument is mutually exclusive with the --validation, --group and --category arguments: $ validation run -h $ validation run --product tripleo --inventory /etc/ansible/hosts The `show parameter` sub command has the same new argument which is also mutually exclusive with the --validation, --group and the --category arguments: $ validation show parameter -h $ validation show parameter --product tripleo Change-Id: Ief13e506c2bee18da47b31f1c2d5c0dbb1ad1ecf Signed-off-by: Gael Chamoulaud (Strider) <gchamoul@redhat.com>
This commit is contained in:
parent
3928305329
commit
c46c90394c
@ -43,6 +43,13 @@ class ValidationList(Lister):
|
||||
help=("List specific category of validations, "
|
||||
"if more than one category is required "
|
||||
"separate the category names with commas."))
|
||||
parser.add_argument('--product',
|
||||
metavar='<product_id>[,<product_id>,...]',
|
||||
action=CommaListAction,
|
||||
default=[],
|
||||
help=("List specific product of validations, "
|
||||
"if more than one product is required "
|
||||
"separate the product names with commas."))
|
||||
parser.add_argument('--validation-dir', dest='validation_dir',
|
||||
default=constants.ANSIBLE_VALIDATION_DIR,
|
||||
help=("Path where the validation playbooks "
|
||||
@ -54,8 +61,10 @@ class ValidationList(Lister):
|
||||
|
||||
group = parsed_args.group
|
||||
category = parsed_args.category
|
||||
product = parsed_args.product
|
||||
validation_dir = parsed_args.validation_dir
|
||||
|
||||
v_actions = ValidationActions(validation_path=validation_dir)
|
||||
return (v_actions.list_validations(groups=group,
|
||||
categories=category))
|
||||
categories=category,
|
||||
products=product))
|
||||
|
@ -148,6 +148,15 @@ class Run(BaseCommand):
|
||||
"if more than one category is required "
|
||||
"separate the category names with commas."))
|
||||
|
||||
ex_group.add_argument(
|
||||
'--product',
|
||||
metavar='<product_id>[,<product_id>,...]',
|
||||
action=CommaListAction,
|
||||
default=[],
|
||||
help=("Run specific validations by product, "
|
||||
"if more than one product is required "
|
||||
"separate the product names with commas."))
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
@ -178,6 +187,7 @@ class Run(BaseCommand):
|
||||
limit_hosts=parsed_args.limit,
|
||||
group=parsed_args.group,
|
||||
category=parsed_args.category,
|
||||
product=parsed_args.product,
|
||||
extra_vars=extra_vars,
|
||||
validations_dir=parsed_args.validation_dir,
|
||||
base_dir=parsed_args.ansible_base_dir,
|
||||
|
@ -118,6 +118,15 @@ class ShowParameter(ShowOne):
|
||||
"if more than one category is required "
|
||||
"separate the category names with commas."))
|
||||
|
||||
ex_group.add_argument(
|
||||
'--product',
|
||||
metavar='<product_id>[,<product_id>,...]',
|
||||
action=CommaListAction,
|
||||
default=[],
|
||||
help=("List specific validations by product, "
|
||||
"if more than one product is required "
|
||||
"separate the product names with commas."))
|
||||
|
||||
parser.add_argument(
|
||||
'--download',
|
||||
action='store',
|
||||
@ -146,6 +155,7 @@ class ShowParameter(ShowOne):
|
||||
validations=parsed_args.validation_name,
|
||||
groups=parsed_args.group,
|
||||
categories=parsed_args.category,
|
||||
products=parsed_args.product,
|
||||
output_format=parsed_args.format_output,
|
||||
download_file=parsed_args.download)
|
||||
|
||||
|
@ -40,6 +40,7 @@ class TestList(BaseCommand):
|
||||
{'description': 'My Validation One Description',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'categories': ['os', 'system', 'ram'],
|
||||
'products': ['product1'],
|
||||
'id': 'my_val1',
|
||||
'name': 'My Validation One Name',
|
||||
'parameters': {}
|
||||
@ -47,6 +48,7 @@ class TestList(BaseCommand):
|
||||
'description': 'My Validation Two Description',
|
||||
'groups': ['prep', 'pre-introspection'],
|
||||
'categories': ['networking'],
|
||||
'products': ['product1'],
|
||||
'id': 'my_val2',
|
||||
'name': 'My Validation Two Name',
|
||||
'parameters': {'min_value': 8}
|
||||
@ -92,3 +94,16 @@ class TestList(BaseCommand):
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.assertEqual(result, val_list)
|
||||
|
||||
@mock.patch('validations_libs.utils.parse_all_validations_on_disk',
|
||||
return_value=fakes.VALIDATIONS_LIST_GROUP)
|
||||
def test_list_validations_by_product(self, mock_list):
|
||||
arglist = ['--validation-dir', 'foo', '--product', 'product1']
|
||||
verifylist = [('validation_dir', 'foo'),
|
||||
('product', ['product1'])]
|
||||
|
||||
val_list = fakes.VALIDATION_LIST_RESULT
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.assertEqual(result, val_list)
|
||||
|
@ -72,6 +72,7 @@ class TestRun(BaseCommand):
|
||||
'limit_hosts': None,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': {'key': 'value'},
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
@ -105,6 +106,7 @@ class TestRun(BaseCommand):
|
||||
'limit_hosts': None,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': {'key': 'value2'},
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
@ -151,6 +153,7 @@ class TestRun(BaseCommand):
|
||||
'limit_hosts': None,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': {'key': 'value'},
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
@ -182,6 +185,7 @@ class TestRun(BaseCommand):
|
||||
'limit_hosts': None,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': None,
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
@ -218,6 +222,7 @@ class TestRun(BaseCommand):
|
||||
'quiet': False,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': None,
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
@ -248,6 +253,7 @@ class TestRun(BaseCommand):
|
||||
'limit_hosts': None,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': None,
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
@ -283,6 +289,7 @@ class TestRun(BaseCommand):
|
||||
'limit_hosts': None,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': {'key': 'value'},
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
@ -325,6 +332,7 @@ class TestRun(BaseCommand):
|
||||
'limit_hosts': None,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': None,
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
@ -352,6 +360,7 @@ class TestRun(BaseCommand):
|
||||
'limit_hosts': None,
|
||||
'group': [],
|
||||
'category': [],
|
||||
'product': [],
|
||||
'extra_vars': {'key': 'value'},
|
||||
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||
'base_dir': '/usr/share/ansible',
|
||||
|
@ -98,3 +98,11 @@ class TestShowParameter(BaseCommand):
|
||||
verifylist = [('category', ['os'])]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||
'show_validations_parameters')
|
||||
def test_show_validations_parameters_by_products(self, mock_show):
|
||||
arglist = ['--product', 'product1']
|
||||
verifylist = [('product', ['product1'])]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
@ -19,6 +19,7 @@ VALIDATIONS_LIST = [{
|
||||
'description': 'My Validation One Description',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'categories': ['os', 'system', 'ram'],
|
||||
'products': ['product1'],
|
||||
'id': 'my_val1',
|
||||
'name': 'My Validation One Name',
|
||||
'parameters': {}
|
||||
@ -26,6 +27,7 @@ VALIDATIONS_LIST = [{
|
||||
'description': 'My Validation Two Description',
|
||||
'groups': ['prep', 'pre-introspection'],
|
||||
'categories': ['networking'],
|
||||
'products': ['product1'],
|
||||
'id': 'my_val2',
|
||||
'name': 'My Validation Two Name',
|
||||
'parameters': {'min_value': 8}
|
||||
@ -35,16 +37,18 @@ VALIDATIONS_LIST_GROUP = [{
|
||||
'description': 'My Validation Two Description',
|
||||
'groups': ['prep', 'pre-introspection'],
|
||||
'categories': ['networking'],
|
||||
'products': ['product1'],
|
||||
'id': 'my_val2',
|
||||
'name': 'My Validation Two Name',
|
||||
'parameters': {'min_value': 8}
|
||||
}]
|
||||
|
||||
|
||||
VALIDATION_LIST_RESULT = (('ID', 'Name', 'Groups', 'Categories'),
|
||||
VALIDATION_LIST_RESULT = (('ID', 'Name', 'Groups', 'Categories', 'Products'),
|
||||
[('my_val2', 'My Validation Two Name',
|
||||
['prep', 'pre-introspection'],
|
||||
['networking'])])
|
||||
['networking'],
|
||||
['product1'])])
|
||||
|
||||
GROUPS_LIST = [
|
||||
('group1', 'Group1 description'),
|
||||
@ -214,6 +218,7 @@ VALIDATIONS_LOGS_CONTENTS_LIST = [{
|
||||
VALIDATIONS_DATA = {'Description': 'My Validation One Description',
|
||||
'Groups': ['prep', 'pre-deployment'],
|
||||
'categories': ['os', 'system', 'ram'],
|
||||
'products': ['product1'],
|
||||
'ID': 'my_val1',
|
||||
'Name': 'My Validation One Name',
|
||||
'parameters': {}}
|
||||
@ -229,6 +234,7 @@ FAKE_WRONG_PLAYBOOK = [{
|
||||
'description': 'foo',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'categories': ['os', 'storage'],
|
||||
'products': ['product1'],
|
||||
'name': 'Advanced Format 512e Support'
|
||||
}
|
||||
}
|
||||
@ -239,6 +245,7 @@ FAKE_PLAYBOOK = [{'hosts': 'undercloud',
|
||||
'vars': {'metadata': {'description': 'foo',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'categories': ['os', 'storage'],
|
||||
'products': ['product1'],
|
||||
'name':
|
||||
'Advanced Format 512e Support'}}}]
|
||||
|
||||
@ -247,6 +254,7 @@ FAKE_PLAYBOOK2 = [{'hosts': 'undercloud',
|
||||
'vars': {'metadata': {'description': 'foo',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'categories': ['os', 'storage'],
|
||||
'products': ['product1'],
|
||||
'name':
|
||||
'Advanced Format 512e Support'},
|
||||
'foo': 'bar'}}]
|
||||
@ -264,11 +272,13 @@ FAKE_METADATA = {'id': 'foo',
|
||||
'description': 'foo',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'categories': ['os', 'storage'],
|
||||
'products': ['product1'],
|
||||
'name': 'Advanced Format 512e Support'}
|
||||
|
||||
FORMATED_DATA = {'Description': 'foo',
|
||||
'Groups': ['prep', 'pre-deployment'],
|
||||
'Categories': ['os', 'storage'],
|
||||
'Products': ['product1'],
|
||||
'ID': 'foo',
|
||||
'Name': 'Advanced Format 512e Support'}
|
||||
|
||||
|
@ -37,6 +37,7 @@ class TestUtils(TestCase):
|
||||
output = {'Name': 'Advanced Format 512e Support',
|
||||
'Description': 'foo', 'Groups': ['prep', 'pre-deployment'],
|
||||
'Categories': ['os', 'storage'],
|
||||
'Products': ['product1'],
|
||||
'ID': '512e',
|
||||
'Parameters': {}}
|
||||
res = utils.get_validations_data('512e')
|
||||
@ -76,6 +77,12 @@ class TestUtils(TestCase):
|
||||
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,
|
||||
path='/foo/playbook',
|
||||
products='foo1,foo2')
|
||||
|
||||
def test_get_validations_playbook_wrong_validation_id_type(self):
|
||||
self.assertRaises(TypeError,
|
||||
utils.get_validations_playbook,
|
||||
@ -94,6 +101,12 @@ class TestUtils(TestCase):
|
||||
path='/foo/playbook',
|
||||
categories='foo1,foo2')
|
||||
|
||||
def test_get_validations_playbook_wrong_products_type(self):
|
||||
self.assertRaises(TypeError,
|
||||
utils.get_validations_playbook,
|
||||
path='/foo/playbook',
|
||||
products='foo1,foo2')
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
@mock.patch('glob.glob')
|
||||
@ -123,6 +136,18 @@ class TestUtils(TestCase):
|
||||
utils.get_validations_playbook,
|
||||
path=['/foo/playbook'])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
@mock.patch('glob.glob')
|
||||
def test_parse_all_validations_on_disk_by_product(self, mock_glob,
|
||||
mock_open,
|
||||
mock_load):
|
||||
mock_glob.return_value = \
|
||||
['/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('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@ -173,6 +198,18 @@ class TestUtils(TestCase):
|
||||
categories=['os', 'storage'])
|
||||
self.assertEqual(result, ['/foo/playbook/foo.yaml'])
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('os.listdir')
|
||||
@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_isfile.return_value = True
|
||||
result = utils.get_validations_playbook('/foo/playbook',
|
||||
products=['product1'])
|
||||
self.assertEqual(result, ['/foo/playbook/foo.yaml'])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_validation_parameters(self, mock_open, mock_load):
|
||||
|
@ -125,6 +125,28 @@ class TestValidation(TestCase):
|
||||
categories = val.categories
|
||||
self.assertEqual(categories, [])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_products(self, mock_open, mock_yaml):
|
||||
val = Validation('/tmp/foo')
|
||||
products = val.products
|
||||
self.assertEqual(products, ['product1'])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK)
|
||||
@mock.patch('six.moves.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('six.moves.builtins.open')
|
||||
def test_products_with_no_existing_products(self, mock_open, mock_yaml):
|
||||
val = Validation('/tmp/foo')
|
||||
products = val.products
|
||||
self.assertEqual(products, [])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_ordered_dict(self, mock_open, mock_yaml):
|
||||
|
@ -30,7 +30,7 @@ class TestValidationActions(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestValidationActions, self).setUp()
|
||||
self.column_name = ('ID', 'Name', 'Groups', 'Categories')
|
||||
self.column_name = ('ID', 'Name', 'Groups', 'Categories', 'Products')
|
||||
|
||||
@mock.patch('validations_libs.utils.parse_all_validations_on_disk',
|
||||
return_value=fakes.VALIDATIONS_LIST)
|
||||
@ -41,11 +41,13 @@ class TestValidationActions(TestCase):
|
||||
(self.column_name, [('my_val1',
|
||||
'My Validation One Name',
|
||||
['prep', 'pre-deployment'],
|
||||
['os', 'system', 'ram']),
|
||||
['os', 'system', 'ram'],
|
||||
['product1']),
|
||||
('my_val2',
|
||||
'My Validation Two Name',
|
||||
['prep', 'pre-introspection'],
|
||||
['networking'])]))
|
||||
['networking'],
|
||||
['product1'])]))
|
||||
|
||||
@mock.patch('validations_libs.utils.os.access', return_value=True)
|
||||
@mock.patch('validations_libs.utils.os.path.exists', return_value=True)
|
||||
@ -351,6 +353,7 @@ class TestValidationActions(TestCase):
|
||||
data = {'Name': 'Advanced Format 512e Support',
|
||||
'Description': 'foo', 'Groups': ['prep', 'pre-deployment'],
|
||||
'Categories': ['os', 'storage'],
|
||||
'Products': ['product1'],
|
||||
'ID': '512e',
|
||||
'Parameters': {}}
|
||||
data.update({'Last execution date': '2019-11-25 13:40:14',
|
||||
@ -402,6 +405,13 @@ class TestValidationActions(TestCase):
|
||||
v_actions.show_validations_parameters,
|
||||
categories={'foo': 'bar'})
|
||||
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_show_validations_parameters_wrong_products_type(self, mock_open):
|
||||
v_actions = ValidationActions()
|
||||
self.assertRaises(TypeError,
|
||||
v_actions.show_validations_parameters,
|
||||
products={'foo': 'bar'})
|
||||
|
||||
@mock.patch('validations_libs.utils.get_validations_playbook',
|
||||
return_value=['/foo/playbook/foo.yaml'])
|
||||
@mock.patch('validations_libs.utils.get_validations_parameters')
|
||||
|
@ -128,9 +128,12 @@ def create_artifacts_dir(log_path=constants.VALIDATIONS_LOG_BASEDIR,
|
||||
raise RuntimeError()
|
||||
|
||||
|
||||
def parse_all_validations_on_disk(path, groups=None, categories=None):
|
||||
"""Return a list of validations metadata which can be sorted by Groups or by
|
||||
Categories.
|
||||
def parse_all_validations_on_disk(path,
|
||||
groups=None,
|
||||
categories=None,
|
||||
products=None):
|
||||
"""Return a list of validations metadata which can be sorted by Groups, by
|
||||
Categories or by Products.
|
||||
|
||||
:param path: The absolute path of the validations directory
|
||||
:type path: `string`
|
||||
@ -141,6 +144,9 @@ def parse_all_validations_on_disk(path, groups=None, categories=None):
|
||||
:param categories: Categories of validations
|
||||
:type categories: `list`
|
||||
|
||||
:param products: Products of validations
|
||||
:type products: `list`
|
||||
|
||||
:return: A list of validations metadata.
|
||||
:rtype: `list`
|
||||
|
||||
@ -149,11 +155,13 @@ def parse_all_validations_on_disk(path, groups=None, categories=None):
|
||||
>>> path = '/foo/bar'
|
||||
>>> parse_all_validations_on_disk(path)
|
||||
[{'categories': ['storage'],
|
||||
'products': ['product1'],
|
||||
'description': 'Detect whether the node disks use Advanced Format.',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'id': '512e',
|
||||
'name': 'Advanced Format 512e Support'},
|
||||
{'categories': ['system'],
|
||||
'products': ['product1'],
|
||||
'description': 'Make sure that the server has enough CPU cores.',
|
||||
'groups': ['prep', 'pre-introspection'],
|
||||
'id': 'check-cpu',
|
||||
@ -172,6 +180,11 @@ def parse_all_validations_on_disk(path, groups=None, categories=None):
|
||||
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")
|
||||
|
||||
results = []
|
||||
validations_abspath = glob.glob("{path}/*.yaml".format(path=path))
|
||||
|
||||
@ -179,18 +192,20 @@ def parse_all_validations_on_disk(path, groups=None, categories=None):
|
||||
"Attempting to parse validations by:\n"
|
||||
" - groups: {}\n"
|
||||
" - categories: {}\n"
|
||||
"from {}".format(groups, categories, validations_abspath)
|
||||
" - products: {}\n"
|
||||
"from {}".format(groups, categories, products, validations_abspath)
|
||||
)
|
||||
|
||||
for playbook in validations_abspath:
|
||||
val = Validation(playbook)
|
||||
|
||||
if not groups and not categories:
|
||||
if not groups and not categories and not products:
|
||||
results.append(val.get_metadata)
|
||||
continue
|
||||
|
||||
if set(groups).intersection(val.groups) or \
|
||||
set(categories).intersection(val.categories):
|
||||
set(categories).intersection(val.categories) or \
|
||||
set(products).intersection(val.products):
|
||||
results.append(val.get_metadata)
|
||||
|
||||
return results
|
||||
@ -199,9 +214,10 @@ def parse_all_validations_on_disk(path, groups=None, categories=None):
|
||||
def get_validations_playbook(path,
|
||||
validation_id=None,
|
||||
groups=None,
|
||||
categories=None):
|
||||
categories=None,
|
||||
products=None):
|
||||
"""Get a list of validations playbooks paths either by their names,
|
||||
their groups or by their categories.
|
||||
their groups, by their categories or by their products.
|
||||
|
||||
:param path: Path of the validations playbooks
|
||||
:type path: `string`
|
||||
@ -215,6 +231,9 @@ def get_validations_playbook(path,
|
||||
:param categories: List of validation category
|
||||
:type categories: `list`
|
||||
|
||||
:param products: List of validation product
|
||||
:type products: `list`
|
||||
|
||||
:return: A list of absolute validations playbooks path
|
||||
:rtype: `list`
|
||||
|
||||
@ -224,7 +243,12 @@ def get_validations_playbook(path,
|
||||
>>> validation_id = ['512e','check-cpu']
|
||||
>>> groups = None
|
||||
>>> categories = None
|
||||
>>> get_validations_playbook(path, validation_id, groups, categories)
|
||||
>>> products = None
|
||||
>>> get_validations_playbook(path=path,
|
||||
validation_id=validation_id,
|
||||
groups=groups,
|
||||
categories=categories,
|
||||
products=products)
|
||||
['/usr/share/ansible/validation-playbooks/512e.yaml',
|
||||
'/usr/share/ansible/validation-playbooks/check-cpu.yaml',]
|
||||
"""
|
||||
@ -246,6 +270,11 @@ def get_validations_playbook(path,
|
||||
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 = []
|
||||
for f in os.listdir(path):
|
||||
pl_path = join(path, f)
|
||||
@ -262,6 +291,9 @@ def get_validations_playbook(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
|
||||
|
||||
|
||||
@ -326,6 +358,7 @@ def get_validations_details(validation):
|
||||
{'description': 'Verify that the server has enough something.',
|
||||
'groups': ['group1', 'group2'],
|
||||
'categories': ['category1', 'category2'],
|
||||
'products': ['product1', 'product2'],
|
||||
'id': 'check-something',
|
||||
'name': 'Verify the server fits the something requirements'}
|
||||
"""
|
||||
@ -360,6 +393,7 @@ def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR):
|
||||
{'Description': 'Verify that the server has enough something',
|
||||
'Groups': ['group1', 'group2'],
|
||||
'Categories': ['category1', 'category2'],
|
||||
'products': ['product1', 'product2'],
|
||||
'ID': 'check-something',
|
||||
'Name': 'Verify the server fits the something requirements',
|
||||
'Parameters': {'param1': 24}}
|
||||
@ -386,18 +420,26 @@ def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR):
|
||||
def get_validations_parameters(validations_data,
|
||||
validation_name=None,
|
||||
groups=None,
|
||||
categories=None):
|
||||
categories=None,
|
||||
products=None):
|
||||
"""Return parameters for a list of validations
|
||||
|
||||
|
||||
:param validations_data: A list of absolute validations playbooks path
|
||||
: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`
|
||||
:rtype: `dict`
|
||||
@ -429,12 +471,18 @@ def get_validations_parameters(validations_data,
|
||||
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):
|
||||
set(categories).intersection(v.categories) or \
|
||||
set(products).intersection(v.products):
|
||||
params[v.id] = {
|
||||
'parameters': v.get_vars
|
||||
}
|
||||
|
@ -49,13 +49,14 @@ class Validation(object):
|
||||
``metadata`` section to read validation's name and description. These
|
||||
values are then reported by the API.
|
||||
|
||||
The validations can be grouped together by specifying a ``groups``
|
||||
and a ``categories`` metadata. ``groups`` are the deployment stage the
|
||||
validations should run on and ``categories`` are the technical
|
||||
classification for the validations.
|
||||
The validations can be grouped together by specifying a ``groups``, a
|
||||
``categories`` and a ``products`` metadata. ``groups`` are the deployment
|
||||
stage the validations should run on, ``categories`` are the technical
|
||||
classification for the validations and ``products`` are the specific
|
||||
validations which should be executed against a specific product.
|
||||
|
||||
Groups and Categories function similar to tags and a validation can thus be
|
||||
part of many groups and many categories.
|
||||
Groups, Categories and Products function similar to tags and a validation
|
||||
can thus be part of many groups and many categories.
|
||||
|
||||
Here is an example:
|
||||
|
||||
@ -74,12 +75,16 @@ class Validation(object):
|
||||
- networking
|
||||
- storage
|
||||
- security
|
||||
products:
|
||||
- product1
|
||||
- product2
|
||||
roles:
|
||||
- hello_world
|
||||
|
||||
"""
|
||||
|
||||
_col_keys = ['ID', 'Name', 'Description', 'Groups', 'Categories']
|
||||
_col_keys = ['ID', 'Name', 'Description',
|
||||
'Groups', 'Categories', 'Products']
|
||||
|
||||
def __init__(self, validation_path):
|
||||
self.dict = self._get_content(validation_path)
|
||||
@ -111,6 +116,9 @@ class Validation(object):
|
||||
- networking
|
||||
- storage
|
||||
- security
|
||||
products:
|
||||
- product1
|
||||
- product2
|
||||
roles:
|
||||
- hello_world
|
||||
|
||||
@ -138,6 +146,9 @@ class Validation(object):
|
||||
- networking
|
||||
- storage
|
||||
- security
|
||||
products:
|
||||
- product1
|
||||
- product2
|
||||
roles:
|
||||
- hello_world
|
||||
|
||||
@ -163,6 +174,7 @@ class Validation(object):
|
||||
{'description': 'Val1 desc.',
|
||||
'groups': ['group1', 'group2'],
|
||||
'categories': ['category1', 'category2'],
|
||||
'products': ['product1', 'product2'],
|
||||
'id': 'val1',
|
||||
'name': 'The validation val1\'s name'}
|
||||
"""
|
||||
@ -219,6 +231,7 @@ class Validation(object):
|
||||
'vars': {'metadata': {'description': 'description of val ',
|
||||
'groups': ['group1', 'group2'],
|
||||
'categories': ['category1', 'category2'],
|
||||
'products': ['product1', 'product2'],
|
||||
'name': 'validation one'},
|
||||
'var_name1': 'value1'}}
|
||||
"""
|
||||
@ -270,6 +283,29 @@ class Validation(object):
|
||||
"No metadata found in validation {}".format(self.id)
|
||||
)
|
||||
|
||||
@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
|
||||
|
||||
:Example:
|
||||
|
||||
>>> pl = '/foo/bar/val.yaml'
|
||||
>>> val = Validation(pl)
|
||||
>>> 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)
|
||||
)
|
||||
|
||||
@property
|
||||
def get_id(self):
|
||||
"""Get the validation id
|
||||
@ -313,6 +349,7 @@ class Validation(object):
|
||||
>>> val = Validation(pl)
|
||||
>>> print(val.get_formated_data)
|
||||
{'Categories': ['category1', 'category2'],
|
||||
'Products': ['product1', 'product2'],
|
||||
'Description': 'description of val',
|
||||
'Groups': ['group1', 'group2'],
|
||||
'ID': 'val',
|
||||
|
@ -47,9 +47,13 @@ class ValidationActions(object):
|
||||
self.validation_path = (validation_path if validation_path
|
||||
else constants.ANSIBLE_VALIDATION_DIR)
|
||||
|
||||
def list_validations(self, groups=None, categories=None):
|
||||
def list_validations(self,
|
||||
groups=None,
|
||||
categories=None,
|
||||
products=None):
|
||||
"""Get a list of the validations selected by group membership or by
|
||||
category. With their names, group membership information and categories.
|
||||
category. With their names, group membership information, categories and
|
||||
products.
|
||||
|
||||
This is used to print table from python ``Tuple`` with ``PrettyTable``.
|
||||
|
||||
@ -59,18 +63,21 @@ class ValidationActions(object):
|
||||
:param categories: List of validation categories.
|
||||
:type categories: `list`
|
||||
|
||||
:param products: List of validation products.
|
||||
:type products: `list`
|
||||
|
||||
:return: Column names and a list of the selected validations
|
||||
:rtype: `tuple`
|
||||
|
||||
.. code:: text
|
||||
|
||||
-------+-----------+----------------------+---------------+
|
||||
| ID | Name | Groups | Categories |
|
||||
+------+-----------+----------------------+---------------+
|
||||
| val1 | val_name1 | ['group1'] | ['category1'] |
|
||||
| val2 | val_name2 | ['group1', 'group2'] | ['category2'] |
|
||||
| val3 | val_name3 | ['group4'] | ['category3'] |
|
||||
+------+-----------+----------------------+---------------+
|
||||
-------+-----------+----------------------+---------------+--------------+
|
||||
| ID | Name | Groups | Categories | Products |
|
||||
+------+-----------+----------------------+---------------+--------------+
|
||||
| val1 | val_name1 | ['group1'] | ['category1'] | ['product1'] |
|
||||
| val2 | val_name2 | ['group1', 'group2'] | ['category2'] | ['product2'] |
|
||||
| val3 | val_name3 | ['group4'] | ['category3'] | ['product3'] |
|
||||
+------+-----------+----------------------+---------------+--------------+
|
||||
|
||||
:Example:
|
||||
|
||||
@ -81,16 +88,26 @@ class ValidationActions(object):
|
||||
>>> results = action.list_validations(groups=groups,
|
||||
categories=categories)
|
||||
>>> print(results
|
||||
(('ID', 'Name', 'Groups', 'Categories'),
|
||||
[('val1', 'val_name1', ['group1'], ['category1']),
|
||||
('val2', 'val_name2', ['group1', 'group2'], ['category2'])])
|
||||
(('ID', 'Name', 'Groups', 'Categories', 'Products'),
|
||||
[('val1',
|
||||
'val_name1',
|
||||
['group1'],
|
||||
['category1'],
|
||||
['product1']),
|
||||
('val2',
|
||||
'val_name2',
|
||||
['group1', 'group2'],
|
||||
['category2'],
|
||||
['product2'])])
|
||||
"""
|
||||
self.log = logging.getLogger(__name__ + ".list_validations")
|
||||
|
||||
validations = v_utils.parse_all_validations_on_disk(
|
||||
path=self.validation_path,
|
||||
groups=groups,
|
||||
categories=categories)
|
||||
categories=categories,
|
||||
products=products
|
||||
)
|
||||
|
||||
self.log.debug(
|
||||
"Parsed {} validations.".format(len(validations))
|
||||
@ -98,10 +115,11 @@ class ValidationActions(object):
|
||||
|
||||
return_values = [
|
||||
(val.get('id'), val.get('name'),
|
||||
val.get('groups'), val.get('categories'))
|
||||
val.get('groups'), val.get('categories'),
|
||||
val.get('products'))
|
||||
for val in validations]
|
||||
|
||||
column_names = ('ID', 'Name', 'Groups', 'Categories')
|
||||
column_names = ('ID', 'Name', 'Groups', 'Categories', 'Products')
|
||||
|
||||
return (column_names, return_values)
|
||||
|
||||
@ -241,17 +259,17 @@ class ValidationActions(object):
|
||||
return [path[1] for path in logs[-history_limit:]]
|
||||
|
||||
def run_validations(self, validation_name=None, inventory='localhost',
|
||||
group=None, category=None, extra_vars=None,
|
||||
validations_dir=None, extra_env_vars=None,
|
||||
ansible_cfg=None, quiet=True, workdir=None,
|
||||
limit_hosts=None, run_async=False,
|
||||
group=None, category=None, product=None,
|
||||
extra_vars=None, validations_dir=None,
|
||||
extra_env_vars=None, ansible_cfg=None, quiet=True,
|
||||
workdir=None, limit_hosts=None, run_async=False,
|
||||
base_dir=constants.DEFAULT_VALIDATIONS_BASEDIR,
|
||||
log_path=constants.VALIDATIONS_LOG_BASEDIR,
|
||||
python_interpreter=None, skip_list=None,
|
||||
callback_whitelist=None,
|
||||
output_callback='validation_stdout',
|
||||
ssh_user=None):
|
||||
"""Run one or multiple validations by name(s) or by group(s)
|
||||
output_callback='validation_stdout', ssh_user=None):
|
||||
"""Run one or multiple validations by name(s), by group(s) or by
|
||||
product(s)
|
||||
|
||||
:param validation_name: A list of validation names
|
||||
:type validation_name: ``list``
|
||||
@ -262,6 +280,8 @@ class ValidationActions(object):
|
||||
:type group: ``list``
|
||||
:param category: A list of category names
|
||||
:type category: ``list``
|
||||
:param product: A list of product names
|
||||
:type product: ``list``
|
||||
:param extra_vars: Set additional variables as a Dict or the absolute
|
||||
path of a JSON or YAML file type.
|
||||
:type extra_vars: Either a Dict or the absolute path of JSON or YAML
|
||||
@ -345,20 +365,22 @@ class ValidationActions(object):
|
||||
playbooks = []
|
||||
validations_dir = (validations_dir if validations_dir
|
||||
else self.validation_path)
|
||||
if group or category:
|
||||
if group or category or product:
|
||||
self.log.debug(
|
||||
"Getting the validations list by:\n"
|
||||
" - groups: {}\n"
|
||||
" - categories: {}".format(group, category)
|
||||
" - categories: {}\n"
|
||||
" - products: {}".format(group, category, product)
|
||||
)
|
||||
validations = v_utils.parse_all_validations_on_disk(
|
||||
path=validations_dir, groups=group, categories=category)
|
||||
path=validations_dir, groups=group,
|
||||
categories=category, products=product
|
||||
)
|
||||
for val in validations:
|
||||
playbooks.append(val.get('id') + '.yaml')
|
||||
elif validation_name:
|
||||
playbooks = v_utils.get_validations_playbook(validations_dir,
|
||||
validation_name,
|
||||
group)
|
||||
validation_name)
|
||||
|
||||
if not playbooks or len(validation_name) != len(playbooks):
|
||||
p = []
|
||||
@ -495,11 +517,12 @@ class ValidationActions(object):
|
||||
validations=None,
|
||||
groups=None,
|
||||
categories=None,
|
||||
products=None,
|
||||
output_format='json',
|
||||
download_file=None):
|
||||
"""
|
||||
Return Validations Parameters for one or several validations by their
|
||||
names, their groups or by their categories.
|
||||
names, their groups, by their categories or by their products.
|
||||
|
||||
:param validations: List of validation name(s)
|
||||
:type validations: `list`
|
||||
@ -510,6 +533,9 @@ class ValidationActions(object):
|
||||
:param categories: List of validation category(ies)
|
||||
:type categories: `list`
|
||||
|
||||
:param products: List of validation product(s)
|
||||
:type products: `list`
|
||||
|
||||
:param output_format: Output format (Supported format are JSON or YAML)
|
||||
:type output_format: `string`
|
||||
|
||||
@ -525,9 +551,10 @@ class ValidationActions(object):
|
||||
>>> validations = ['check-cpu', 'check-ram']
|
||||
>>> groups = None
|
||||
>>> categories = None
|
||||
>>> products = None
|
||||
>>> output_format = 'json'
|
||||
>>> show_validations_parameters(validations, groups,
|
||||
categories, output_format)
|
||||
categories, products, output_format)
|
||||
{
|
||||
"check-cpu": {
|
||||
"parameters": {
|
||||
@ -556,6 +583,11 @@ class ValidationActions(object):
|
||||
elif not isinstance(categories, list):
|
||||
raise TypeError("The 'categories' argument must be a List")
|
||||
|
||||
if not products:
|
||||
products = []
|
||||
elif not isinstance(products, list):
|
||||
raise TypeError("The 'products' argument must be a List")
|
||||
|
||||
supported_format = ['json', 'yaml']
|
||||
|
||||
if output_format not in supported_format:
|
||||
@ -565,13 +597,17 @@ class ValidationActions(object):
|
||||
path=self.validation_path,
|
||||
validation_id=validations,
|
||||
groups=groups,
|
||||
categories=categories)
|
||||
categories=categories,
|
||||
products=products
|
||||
)
|
||||
|
||||
params = v_utils.get_validations_parameters(
|
||||
validations_data=validation_playbooks,
|
||||
validation_name=validations,
|
||||
groups=groups,
|
||||
categories=categories)
|
||||
categories=categories,
|
||||
products=products
|
||||
)
|
||||
|
||||
if download_file:
|
||||
params_only = {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user