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:
Gael Chamoulaud (Strider) 2021-07-12 13:58:04 +02:00 committed by Gael Chamoulaud
parent 3928305329
commit c46c90394c
13 changed files with 316 additions and 55 deletions

View File

@ -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))

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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',

View File

@ -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)

View File

@ -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'}

View File

@ -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):

View File

@ -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):

View File

@ -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')

View File

@ -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
}

View File

@ -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',

View File

@ -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 = {}