From 94163f40194c58f4114d4e53181df1cf9767e4c0 Mon Sep 17 00:00:00 2001 From: Gael Chamoulaud Date: Fri, 8 Nov 2019 16:35:53 +0100 Subject: [PATCH] TripleO Validator CLI Refactor This patch hardly refactors the TripleO Validator CLI. The 'List' subcommand has been split in different new subcommands which does "one thing and only one thing". It also adds the support for the TripleO history file and integrates better the CLI with the openstack client library, which simplifies it a lot. The subcommands are: - To display the full list of validations or by group(s): ``` $ openstack tripleo validator list ``` - To display full details about one validation: ``` $ openstack tripleo validator show ``` - To display the available parameters for one or multiple validations, export them in a JSON or YAML file: ``` $ openstack tripleo validator show parameter ``` - To display full details about the groups used by the validations: ``` $ openstack tripleo validator group info ``` - To run one or several validations by names or by groups: ``` $ openstack tripleo validator run ``` Depends-On: I56b9d39c113cfb30695fe8cf2740ae147b0dd3e4 Change-Id: I09e2cba484d3a91915fb9294bc351b5d7f3aca0f Signed-off-by: Gael Chamoulaud (cherry picked from commit 29b00170b3cf54106b6e43cc42f3b66ce3a3be0b) --- ...lidator_cli_refactor-64c298348d405347.yaml | 17 ++ setup.cfg | 3 + tripleoclient/constants.py | 2 + .../v1/tripleo/test_tripleo_validator.py | 70 ++++- tripleoclient/utils.py | 49 ++-- tripleoclient/v1/tripleo_validator.py | 246 ++++++++++++------ 6 files changed, 279 insertions(+), 108 deletions(-) create mode 100644 releasenotes/notes/tripleo_validator_cli_refactor-64c298348d405347.yaml 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 74bff8eda..4ecd8e6d9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -117,8 +117,11 @@ openstack.tripleoclient.v1 = undercloud_minion_install = tripleoclient.v1.undercloud_minion:InstallUndercloudMinion undercloud_minion_upgrade = tripleoclient.v1.undercloud_minion:UpgradeUndercloudMinion 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/constants.py b/tripleoclient/constants.py index b5b6ef417..0f7ec6f1a 100644 --- a/tripleoclient/constants.py +++ b/tripleoclient/constants.py @@ -82,6 +82,8 @@ DEFAULT_VALIDATIONS_BASEDIR = '/usr/share/openstack-tripleo-validations' ANSIBLE_VALIDATION_DIR = \ '/usr/share/openstack-tripleo-validations/playbooks' +VALIDATION_GROUPS_INFO = '%s/groups.yaml' % DEFAULT_VALIDATIONS_BASEDIR + VALIDATION_GROUPS = ['no-op', 'openshift-on-openstack', 'prep', 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 c414127d1..fc192fec3 100644 --- a/tripleoclient/utils.py +++ b/tripleoclient/utils.py @@ -41,7 +41,6 @@ import socket import subprocess import sys import tempfile -import textwrap import time import yaml @@ -61,8 +60,6 @@ from six.moves.urllib import request from tripleoclient import constants from tripleoclient import exceptions -from prettytable import PrettyTable - LOG = logging.getLogger(__name__ + ".utils") @@ -1792,6 +1789,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)) @@ -1862,34 +1877,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(