diff --git a/releasenotes/notes/tripleo_validator_cli_refactor-64c298348d405347.yaml b/releasenotes/notes/tripleo_validator_cli_refactor-64c298348d405347.yaml new file mode 100644 index 000000000..70340acef --- /dev/null +++ b/releasenotes/notes/tripleo_validator_cli_refactor-64c298348d405347.yaml @@ -0,0 +1,17 @@ +--- +Features: + - | + The TripleO Validator CLI has been revamped and new subcommands have been + created. Moreover, the latter has been fully integrated with native + openstack client library. + + To list all the available validations: + - openstack tripleo validator list + To show detailed information about a validation: + - openstack tripleo validator show + To display validations parameters: + - openstack tripleo validator show parameter + To display information about the validations groups: + - openstack tripleo validator group info + To run the validations, by name or by group(s): + - openstack tripleo validator run diff --git a/setup.cfg b/setup.cfg index c3499de9a..ebcab7996 100644 --- a/setup.cfg +++ b/setup.cfg @@ -109,8 +109,11 @@ openstack.tripleoclient.v1 = undercloud_install = tripleoclient.v1.undercloud:InstallUndercloud undercloud_upgrade = tripleoclient.v1.undercloud:UpgradeUndercloud undercloud_backup = tripleoclient.v1.undercloud_backup:BackupUndercloud + tripleo_validator_group_info = tripleoclient.v1.tripleo_validator:TripleOValidatorGroupInfo tripleo_validator_list = tripleoclient.v1.tripleo_validator:TripleOValidatorList tripleo_validator_run = tripleoclient.v1.tripleo_validator:TripleOValidatorRun + tripleo_validator_show = tripleoclient.v1.tripleo_validator:TripleOValidatorShow + tripleo_validator_show_parameter = tripleoclient.v1.tripleo_validator:TripleOValidatorShowParameter oslo.config.opts = undercloud_config = tripleoclient.config.undercloud:list_opts standalone_config = tripleoclient.config.standalone:list_opts diff --git a/tripleoclient/command.py b/tripleoclient/command.py index b77f2b364..ec640b994 100644 --- a/tripleoclient/command.py +++ b/tripleoclient/command.py @@ -41,6 +41,10 @@ class Lister(Command, command.Lister): pass +class ShowOne(Command, command.ShowOne): + pass + + class DeprecatedActionStore(_StoreAction): """To deprecated an option an store the value""" log = logging.getLogger(__name__) diff --git a/tripleoclient/constants.py b/tripleoclient/constants.py index 1ed969db6..163d5d781 100644 --- a/tripleoclient/constants.py +++ b/tripleoclient/constants.py @@ -80,7 +80,10 @@ DEFAULT_VALIDATIONS_BASEDIR = '/usr/share/openstack-tripleo-validations' ANSIBLE_VALIDATION_DIR = \ '/usr/share/openstack-tripleo-validations/playbooks' -VALIDATION_GROUPS = ['openshift-on-openstack', +VALIDATION_GROUPS_INFO = '%s/groups.yaml' % DEFAULT_VALIDATIONS_BASEDIR + +VALIDATION_GROUPS = ['no-op', + 'openshift-on-openstack', 'prep', 'pre-introspection', 'pre-deployment', diff --git a/tripleoclient/tests/v1/tripleo/test_tripleo_validator.py b/tripleoclient/tests/v1/tripleo/test_tripleo_validator.py index 7522a2afe..36637b315 100644 --- a/tripleoclient/tests/v1/tripleo/test_tripleo_validator.py +++ b/tripleoclient/tests/v1/tripleo/test_tripleo_validator.py @@ -30,9 +30,34 @@ VALIDATIONS_LIST = [{ 'groups': ['prep', 'pre-introspection'], 'id': 'my_val2', 'name': 'My Validition Two Name', - 'parameters': {} + 'parameters': {'min_value': 8} }] +GROUPS_LIST = [ + ('group1', 'Group1 description'), + ('group2', 'Group2 description'), + ('group3', 'Group3 description'), +] + + +class TestValidatorGroupInfo(utils.TestCommand): + + def setUp(self): + super(TestValidatorGroupInfo, self).setUp() + + # Get the command object to test + self.cmd = tripleo_validator.TripleOValidatorGroupInfo(self.app, None) + + @mock.patch('tripleoclient.utils.parse_all_validation_groups_on_disk', + return_value=GROUPS_LIST) + def test_show_group_info(self, mock_validations): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + class TestValidatorList(utils.TestCommand): @@ -53,6 +78,45 @@ class TestValidatorList(utils.TestCommand): self.cmd.take_action(parsed_args) +class TestValidatorShow(utils.TestCommand): + + def setUp(self): + super(TestValidatorShow, self).setUp() + + # Get the command object to test + self.cmd = tripleo_validator.TripleOValidatorShow(self.app, None) + + @mock.patch('tripleoclient.utils.parse_all_validations_on_disk', + return_value=VALIDATIONS_LIST) + def test_validation_show(self, mock_validations): + arglist = ['my_val1'] + verifylist = [('validation_id', 'my_val1')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + +class TestValidatorShowParameter(utils.TestCommand): + + def setUp(self): + super(TestValidatorShowParameter, self).setUp() + + # Get the command object to test + self.cmd = tripleo_validator.TripleOValidatorShowParameter(self.app, + None) + + @mock.patch('tripleoclient.utils.parse_all_validations_on_disk', + return_value=VALIDATIONS_LIST) + def test_validation_show_parameter(self, mock_validations): + arglist = ['--validation', 'my_val2'] + verifylist = [('validation_name', ['my_val2'])] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + class TestValidatorRun(utils.TestCommand): def setUp(self): @@ -81,10 +145,10 @@ class TestValidatorRun(utils.TestCommand): playbooks_dir = '/usr/share/openstack-tripleo-validations/playbooks' arglist = [ - '--validation-name', + '--validation', 'check-ftype' ] - verifylist = [] + verifylist = [('validation_name', ['check-ftype'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) diff --git a/tripleoclient/utils.py b/tripleoclient/utils.py index 43bd4c9f2..8f916c115 100644 --- a/tripleoclient/utils.py +++ b/tripleoclient/utils.py @@ -40,7 +40,6 @@ import socket import subprocess import sys import tempfile -import textwrap import time import yaml @@ -61,8 +60,6 @@ from tripleo_common.utils import config from tripleoclient import constants from tripleoclient import exceptions -from prettytable import PrettyTable - LOG = logging.getLogger(__name__ + ".utils") @@ -1906,6 +1903,24 @@ def get_validation_parameters(validation): return dict() +def parse_all_validation_groups_on_disk(groups_file_path=None): + results = [] + + if not groups_file_path: + groups_file_path = constants.VALIDATION_GROUPS_INFO + + if not os.path.exists(groups_file_path): + return results + + with open(groups_file_path, 'r') as grps: + contents = yaml.safe_load(grps) + + for grp_name, grp_desc in sorted(contents.items()): + results.append((grp_name, grp_desc[0].get('description'))) + + return results + + def parse_all_validations_on_disk(path, groups=None): results = [] validations_abspath = glob.glob("{path}/*.yaml".format(path=path)) @@ -1976,34 +1991,6 @@ def get_validations_parameters(validations_data, return params -def get_validations_table(validations_data): - """Return the validations information as a pretty printed table """ - param_field_name = get_param_field_name(validations_data) - - t = PrettyTable(border=True, header=True, padding_width=1) - t.title = "TripleO validations" - t.field_names = [ - "ID", "Name", - "Description", "Groups", - param_field_name.capitalize() - ] - - for validation in validations_data['validations']: - t.add_row([validation['id'], - validation['name'], - "\n".join(textwrap.wrap(validation['description'])), - "\n".join(textwrap.wrap(' '.join(validation['groups']))), - validation[param_field_name]]) - - t.sortby = "ID" - t.align["ID"] = "l" - t.align["Name"] = "l" - t.align["Description"] = "l" - t.align["Groups"] = "l" - t.align[param_field_name.capitalize()] = "l" - return t - - def get_validations_json(validations_data): """Return the validations information as a pretty printed json """ return json.dumps(validations_data, indent=4, sort_keys=True) diff --git a/tripleoclient/v1/tripleo_validator.py b/tripleoclient/v1/tripleo_validator.py index 931c03b2b..9a947683d 100644 --- a/tripleoclient/v1/tripleo_validator.py +++ b/tripleoclient/v1/tripleo_validator.py @@ -20,11 +20,13 @@ import os import pwd import six import sys +import textwrap from concurrent.futures import ThreadPoolExecutor -from osc_lib.command import command +from osc_lib import exceptions from osc_lib.i18n import _ +from tripleoclient import command from tripleoclient import constants from tripleoclient import utils as oooutils @@ -48,35 +50,116 @@ class _CommaListAction(argparse.Action): setattr(namespace, self.dest, values.split(',')) -class TripleOValidatorList(command.Command): - """List the available validations""" +class TripleOValidatorGroupInfo(command.Lister): + """Display Information about Validation Groups""" + + def get_parser(self, prog_name): + parser = super(TripleOValidatorGroupInfo, self).get_parser(prog_name) + return parser + + def take_action(self, parsed_args): + group_file = constants.VALIDATION_GROUPS_INFO + group = oooutils.parse_all_validation_groups_on_disk(group_file) + + if not group: + raise exceptions.CommandError( + "Could not find groups information file %s" % group_file) + + column_name = ("Groups", "Description") + return (column_name, group) + + +class TripleOValidatorShow(command.ShowOne): + """Display detailed information about a Validation""" + + def get_parser(self, prog_name): + parser = super(TripleOValidatorShow, self).get_parser(prog_name) + + parser.add_argument('validation_id', + metavar="", + type=str, + help='Validation ID') + + return parser + + def take_action(self, parsed_args): + validation = self.get_validations_details(parsed_args.validation_id) + if not validation: + raise exceptions.CommandError( + "Could not find validation %s" % parsed_args.validation_id) + + return self.format_validation(validation) + + def get_validations_details(self, validation): + results = oooutils.parse_all_validations_on_disk( + constants.ANSIBLE_VALIDATION_DIR) + + for r in results: + if r['id'] == validation: + return r + return [] + + def format_validation(self, validation): + column_names = ["ID"] + data = [validation.pop('id')] + + if 'name' in validation: + column_names.append("Name") + data.append(validation.pop('name')) + + if 'description' in validation: + column_names.append("Description") + data.append(textwrap.fill(validation.pop('description'))) + + other_fields = list(validation.keys()) + other_fields.sort() + for field in other_fields: + column_names.append(field.capitalize()) + data.append(validation[field]) + + return column_names, data + + +class TripleOValidatorShowParameter(command.Command): + """Display Validations Parameters""" def get_parser(self, prog_name): parser = argparse.ArgumentParser( description=self.get_description(), prog=prog_name, formatter_class=argparse.ArgumentDefaultsHelpFormatter, - add_help=False + add_help=True + ) + + ex_group = parser.add_mutually_exclusive_group(required=False) + + ex_group.add_argument( + '--validation', + metavar='[,,...]', + dest='validation_name', + action=_CommaListAction, + default=[], + help=_("List specific validations, " + "if more than one validation is required " + "separate the names with commas: " + "--validation check-ftype,512e | " + "--validation 512e") + ) + + ex_group.add_argument( + '--group', + metavar='[,,...]', + action=_CommaListGroupAction, + default=[], + help=_("List specific group validations, " + "if more than one group is required " + "separate the group names with commas: " + "pre-upgrade,prep | " + "openshift-on-openstack") ) parser.add_argument( - '--output', - action='store', - default='table', - choices=['table', 'json', 'yaml'], - help=_("Change the default output: " - "--output json|yaml") - ) - - parser.add_argument( - '--parameters', - action='store_true', - default=False, - help=_("List available validations parameters") - ) - - parser.add_argument( - '--create-vars-file', + '--download', metavar=('[json|yaml]', '/tmp/myvars'), action='store', default=[], @@ -87,30 +170,14 @@ class TripleOValidatorList(command.Command): "[yaml|json] /tmp/myvars") ) - ex_group = parser.add_mutually_exclusive_group(required=False) - - ex_group.add_argument( - '--validation-name', - metavar='[,,...]', - action=_CommaListAction, - default=[], - help=_("List specific validations, " - "if more than one validation is required " - "separate the names with commas: " - "--validation-name check-ftype,512e | " - "--validation-name 512e") - ) - - ex_group.add_argument( - '--group', - metavar='[,,...]', - action=_CommaListGroupAction, - default=[], - help=_("List specific group validations, " - "if more than one group is required " - "separate the group names with commas: " - "--group pre-upgrade,prep | " - "--group openshift-on-openstack") + parser.add_argument( + '-f', '--format', + action='store', + metavar='', + default='json', + choices=['json', 'yaml'], + help=_("Print representation of the validation. " + "The choices of the output format is json,yaml. ") ) return parser @@ -147,42 +214,72 @@ class TripleOValidatorList(command.Command): print(_("Creating variables file finished with errors")) print('Output: {}'.format(e)) - def _run_validator_list(self, parsed_args): + def _run_validator_show_parameter(self, parsed_args): + LOG.debug(_('Launch showing parameters for the validations')) + try: + validations = oooutils.parse_all_validations_on_disk( + constants.ANSIBLE_VALIDATION_DIR) + + out = oooutils.get_validations_parameters( + {'validations': validations}, + parsed_args.validation_name, + parsed_args.group + ) + + if parsed_args.download: + self._create_variables_file(out, + parsed_args.download) + else: + if parsed_args.format == 'yaml': + print(oooutils.get_validations_yaml(out)) + else: + print(oooutils.get_validations_json(out)) + except Exception as e: + raise RuntimeError(_("Validations Show Parameters " + "finished with errors\n" + "Output: {}").format(e)) + + def take_action(self, parsed_args): + self._run_validator_show_parameter(parsed_args) + + +class TripleOValidatorList(command.Lister): + """List the available validations""" + + def get_parser(self, prog_name): + parser = super(TripleOValidatorList, self).get_parser(prog_name) + + parser.add_argument( + '--group', + metavar='[,,...]', + action=_CommaListGroupAction, + default=[], + help=_("List specific group validations, " + "if more than one group is required " + "separate the group names with commas: " + "--group pre-upgrade,prep | " + "--group openshift-on-openstack") + ) + + return parser + + def take_action(self, parsed_args): LOG.debug(_('Launch listing the validations')) try: validations = oooutils.parse_all_validations_on_disk( constants.ANSIBLE_VALIDATION_DIR, parsed_args.group) - if parsed_args.parameters: - out = oooutils.get_validations_parameters( - {'validations': validations}, - parsed_args.validation_name, - parsed_args.group - ) + return_values = [] + column_name = ('ID', 'Name', 'Groups') - if parsed_args.create_vars_file: - self._create_variables_file(out, - parsed_args.create_vars_file) - else: - print(oooutils.get_validations_json(out)) - else: - if parsed_args.output == 'json': - out = oooutils.get_validations_json( - {'validations': validations}) - elif parsed_args.output == 'yaml': - out = oooutils.get_validations_yaml( - {'validations': validations}) - else: - out = oooutils.get_validations_table( - {'validations': validations}) - print(out) + for val in validations: + return_values.append((val.get('id'), val.get('name'), + val.get('groups'))) + return (column_name, return_values) except Exception as e: raise RuntimeError(_("Validations listing finished with errors\n" "Output: {}").format(e)) - def take_action(self, parsed_args): - self._run_validator_list(parsed_args) - class TripleOValidatorRun(command.Command): """Run the available validations""" @@ -240,15 +337,16 @@ class TripleOValidatorRun(command.Command): ex_group = parser.add_mutually_exclusive_group(required=True) ex_group.add_argument( - '--validation-name', + '--validation', metavar='[,,...]', + dest="validation_name", action=_CommaListAction, default=[], help=_("Run specific validations, " "if more than one validation is required " "separate the names with commas: " - "--validation-name check-ftype,512e | " - "--validation-name 512e") + "--validation check-ftype,512e | " + "--validation 512e") ) ex_group.add_argument(