Merge "Create dedicated CLI for the Validation Framework"
This commit is contained in:
commit
8b44eeb231
@ -7,3 +7,4 @@ six>=1.11.0 # MIT
|
|||||||
PyYAML>=3.13 # MIT
|
PyYAML>=3.13 # MIT
|
||||||
ansible>=2.8,!=2.8.9,!=2.9.12,<2.10.0
|
ansible>=2.8,!=2.8.9,!=2.9.12,<2.10.0
|
||||||
ansible-runner>=1.4.0 # Apache-2.0
|
ansible-runner>=1.4.0 # Apache-2.0
|
||||||
|
cliff>=3.2.0 # Apache-2.0
|
||||||
|
11
setup.cfg
11
setup.cfg
@ -36,3 +36,14 @@ mapping_file = babel.cfg
|
|||||||
output_file = validations-libs/locale/validations-libs.pot
|
output_file = validations-libs/locale/validations-libs.pot
|
||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
|
console_scripts:
|
||||||
|
validation = validations_libs.cli.app:main
|
||||||
|
|
||||||
|
validation.cli:
|
||||||
|
list = validations_libs.cli.lister:ValidationList
|
||||||
|
show = validations_libs.cli.show:Show
|
||||||
|
show_group = validations_libs.cli.show:ShowGroup
|
||||||
|
show_parameter = validations_libs.cli.show:ShowParameter
|
||||||
|
run = validations_libs.cli.run:Run
|
||||||
|
history_list = validations_libs.cli.history:ListHistory
|
||||||
|
history_get = validations_libs.cli.history:GetHistory
|
||||||
|
15
validations_libs/cli/__init__.py
Normal file
15
validations_libs/cli/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
58
validations_libs/cli/app.py
Normal file
58
validations_libs/cli/app.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from cliff.app import App
|
||||||
|
from cliff.commandmanager import CommandManager
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationCliApp(App):
|
||||||
|
"""Cliff application for the `ValidationCli` tool.
|
||||||
|
:param description: one-liner explaining the program purpose
|
||||||
|
:param version: application version number
|
||||||
|
:param command_manager: plugin loader
|
||||||
|
:param deferred_help: Allow subcommands to accept `–help` with allowing
|
||||||
|
to defer help print after initialize_app
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ValidationCliApp, self).__init__(
|
||||||
|
description="Validations Framework Command Line Interface (CLI)",
|
||||||
|
version='1.0',
|
||||||
|
command_manager=CommandManager('validation.cli'),
|
||||||
|
deferred_help=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize_app(self, argv):
|
||||||
|
self.LOG.debug('Initialize Validation App.')
|
||||||
|
|
||||||
|
def prepare_to_run_command(self, cmd):
|
||||||
|
self.LOG.debug('prepare_to_run_command %s', cmd.__class__.__name__)
|
||||||
|
|
||||||
|
def clean_up(self, cmd, result, err):
|
||||||
|
self.LOG.debug('clean_up %s', cmd.__class__.__name__)
|
||||||
|
if err:
|
||||||
|
self.LOG.debug('got an error: %s', err)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv[1:]):
|
||||||
|
v_cli = ValidationCliApp()
|
||||||
|
return v_cli.run(argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
70
validations_libs/cli/common.py
Normal file
70
validations_libs/cli/common.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
|
from validations_libs import constants
|
||||||
|
from validations_libs import utils as v_utils
|
||||||
|
|
||||||
|
GROUP_FILE = constants.VALIDATION_GROUPS_INFO
|
||||||
|
|
||||||
|
# PrettyTable Colors:
|
||||||
|
RED = "\033[1;31m"
|
||||||
|
GREEN = "\033[0;32m"
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
RESET = "\033[0;0m"
|
||||||
|
YELLOW = "\033[0;33m"
|
||||||
|
|
||||||
|
|
||||||
|
def print_dict(data):
|
||||||
|
"""Print table from python dict with PrettyTable"""
|
||||||
|
table = PrettyTable(border=True, header=True, padding_width=1)
|
||||||
|
# Set Field name by getting the result dict keys
|
||||||
|
try:
|
||||||
|
table.field_names = data[0].keys()
|
||||||
|
table.align = 'l'
|
||||||
|
except IndexError:
|
||||||
|
raise IndexError()
|
||||||
|
for row in data:
|
||||||
|
if row.get('Status_by_Host'):
|
||||||
|
hosts = []
|
||||||
|
for host in row['Status_by_Host'].split(', '):
|
||||||
|
try:
|
||||||
|
_name, _status = host.split(',')
|
||||||
|
except ValueError:
|
||||||
|
# if ValueError, then host is in unknown state:
|
||||||
|
_name = host
|
||||||
|
_status = 'UNKNOWN'
|
||||||
|
color = (GREEN if _status == 'PASSED' else
|
||||||
|
(YELLOW if _status == 'UNREACHABLE' else RED))
|
||||||
|
_name = '{}{}{}'.format(color, _name, RESET)
|
||||||
|
hosts.append(_name)
|
||||||
|
row['Status_by_Host'] = ', '.join(hosts)
|
||||||
|
if row.get('Status'):
|
||||||
|
status = row.get('Status')
|
||||||
|
color = (CYAN if status in ['starting', 'running']
|
||||||
|
else GREEN if status == 'PASSED' else RED)
|
||||||
|
row['Status'] = '{}{}{}'.format(color, status, RESET)
|
||||||
|
table.add_row(row.values())
|
||||||
|
print(table)
|
||||||
|
|
||||||
|
|
||||||
|
def write_output(output_log, results):
|
||||||
|
"""Write output log file as Json format"""
|
||||||
|
with open(output_log, 'w') as output:
|
||||||
|
output.write(json.dumps({'results': results}, indent=4,
|
||||||
|
sort_keys=True))
|
86
validations_libs/cli/history.py
Normal file
86
validations_libs/cli/history.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from cliff.command import Command
|
||||||
|
from cliff.lister import Lister
|
||||||
|
|
||||||
|
from validations_libs import constants
|
||||||
|
from validations_libs.validation_actions import ValidationActions
|
||||||
|
from validations_libs.validation_logs import ValidationLogs
|
||||||
|
|
||||||
|
|
||||||
|
class ListHistory(Lister):
|
||||||
|
"""Display Validations execution history"""
|
||||||
|
|
||||||
|
def get_parser(self, parser):
|
||||||
|
parser = super(ListHistory, self).get_parser(parser)
|
||||||
|
|
||||||
|
parser.add_argument('--validation',
|
||||||
|
metavar="<validation>",
|
||||||
|
type=str,
|
||||||
|
help='Display execution history for a validation')
|
||||||
|
parser.add_argument('--validation-log-dir', dest='validation_log_dir',
|
||||||
|
default=constants.VALIDATIONS_LOG_BASEDIR,
|
||||||
|
help=("Path where the validation log files "
|
||||||
|
"is located."))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
actions = ValidationActions(parsed_args.validation_log_dir)
|
||||||
|
return actions.show_history(parsed_args.validation)
|
||||||
|
|
||||||
|
|
||||||
|
class GetHistory(Command):
|
||||||
|
"""Display details about a Validation execution"""
|
||||||
|
|
||||||
|
def get_parser(self, parser):
|
||||||
|
parser = super(GetHistory, self).get_parser(parser)
|
||||||
|
parser.add_argument('uuid',
|
||||||
|
metavar="<uuid>",
|
||||||
|
type=str,
|
||||||
|
help='Validation UUID Run')
|
||||||
|
|
||||||
|
parser.add_argument('--full',
|
||||||
|
action='store_true',
|
||||||
|
help='Show Full Details for the run')
|
||||||
|
|
||||||
|
parser.add_argument('--validation-log-dir', dest='validation_log_dir',
|
||||||
|
default=constants.VALIDATIONS_LOG_BASEDIR,
|
||||||
|
help=("Path where the validation log files "
|
||||||
|
"is located."))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
vlogs = ValidationLogs(logs_path=parsed_args.validation_log_dir)
|
||||||
|
data = vlogs.get_logfile_content_by_uuid(parsed_args.uuid)
|
||||||
|
if data:
|
||||||
|
if parsed_args.full:
|
||||||
|
for d in data:
|
||||||
|
print(json.dumps(d, indent=4, sort_keys=True))
|
||||||
|
else:
|
||||||
|
for d in data:
|
||||||
|
for p in d.get('validation_output', []):
|
||||||
|
print(json.dumps(p['task'],
|
||||||
|
indent=4,
|
||||||
|
sort_keys=True))
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Could not find the log file linked to this UUID: %s" %
|
||||||
|
parsed_args.uuid)
|
55
validations_libs/cli/lister.py
Normal file
55
validations_libs/cli/lister.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from cliff.lister import Lister
|
||||||
|
|
||||||
|
from validations_libs.validation_actions import ValidationActions
|
||||||
|
from validations_libs import constants
|
||||||
|
from validations_libs.cli.parseractions import CommaListAction
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationList(Lister):
|
||||||
|
"""Validation List client implementation class"""
|
||||||
|
|
||||||
|
def get_parser(self, parser):
|
||||||
|
"""Argument parser for validation run"""
|
||||||
|
parser = super(ValidationList, self).get_parser(parser)
|
||||||
|
parser.add_argument('--group', '-g',
|
||||||
|
metavar='<group>[,<group>,...]',
|
||||||
|
action=CommaListAction,
|
||||||
|
default=[],
|
||||||
|
help=("Run 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('--validation-dir', dest='validation_dir',
|
||||||
|
default=constants.ANSIBLE_VALIDATION_DIR,
|
||||||
|
help=("Path where the validation playbooks "
|
||||||
|
"is located."))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
"""Take validation action"""
|
||||||
|
# Get parameters:
|
||||||
|
group = parsed_args.group
|
||||||
|
validation_dir = parsed_args.validation_dir
|
||||||
|
|
||||||
|
v_actions = ValidationActions(validation_path=validation_dir)
|
||||||
|
return (v_actions.list_validations(group))
|
45
validations_libs/cli/parseractions.py
Normal file
45
validations_libs/cli/parseractions.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
class CommaListAction(argparse.Action):
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
setattr(namespace, self.dest, values.split(','))
|
||||||
|
|
||||||
|
|
||||||
|
class KeyValueAction(argparse.Action):
|
||||||
|
"""A custom action to parse arguments as key=value pairs
|
||||||
|
Ensures that ``dest`` is a dict and values are strings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
# Make sure we have an empty dict rather than None
|
||||||
|
if getattr(namespace, self.dest, None) is None:
|
||||||
|
setattr(namespace, self.dest, {})
|
||||||
|
|
||||||
|
# Add value if an assignment else remove it
|
||||||
|
if '=' in values and values.count('=') == 1:
|
||||||
|
values_list = values.split('=', 1)
|
||||||
|
if '' == values_list[0]:
|
||||||
|
msg = ("Property key must be specified: %s")
|
||||||
|
raise argparse.ArgumentTypeError(msg % str(values))
|
||||||
|
else:
|
||||||
|
getattr(namespace, self.dest, {}).update([values_list])
|
||||||
|
else:
|
||||||
|
msg = ("Expected 'key=value' type, but got: %s")
|
||||||
|
raise argparse.ArgumentTypeError(msg % str(values))
|
174
validations_libs/cli/run.py
Normal file
174
validations_libs/cli/run.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import getpass
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from cliff.command import Command
|
||||||
|
|
||||||
|
from validations_libs import constants
|
||||||
|
from validations_libs.validation_actions import ValidationActions
|
||||||
|
from validations_libs.cli import common
|
||||||
|
from validations_libs.cli.parseractions import CommaListAction, KeyValueAction
|
||||||
|
|
||||||
|
|
||||||
|
class Run(Command):
|
||||||
|
"""Validation Run client implementation class"""
|
||||||
|
|
||||||
|
def get_parser(self, parser):
|
||||||
|
"""Argument parser for validation run"""
|
||||||
|
parser = super(Run, self).get_parser(parser)
|
||||||
|
parser.add_argument(
|
||||||
|
'--limit', action='store', required=False, help=(
|
||||||
|
"A string that identifies a single node or comma-separated "
|
||||||
|
"list of nodes to be upgraded in parallel in this upgrade "
|
||||||
|
" run invocation. For example: --limit \"compute-0,"
|
||||||
|
" compute-1, compute-5\".")
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--ssh-user',
|
||||||
|
dest='ssh_user',
|
||||||
|
default=getpass.getuser(),
|
||||||
|
help=("Ssh User name for the Ansible ssh connection.")
|
||||||
|
)
|
||||||
|
parser.add_argument('--validation-dir', dest='validation_dir',
|
||||||
|
default=constants.ANSIBLE_VALIDATION_DIR,
|
||||||
|
help=("Path where the validation playbooks "
|
||||||
|
"is located."))
|
||||||
|
|
||||||
|
parser.add_argument('--ansible-base-dir', dest='ansible_base_dir',
|
||||||
|
default=constants.DEFAULT_VALIDATIONS_BASEDIR,
|
||||||
|
help=("Path where the ansible roles, library "
|
||||||
|
"and plugins are located."))
|
||||||
|
|
||||||
|
parser.add_argument('--inventory', '-i', type=str,
|
||||||
|
default="localhost",
|
||||||
|
help="Path of the Ansible inventory.")
|
||||||
|
|
||||||
|
parser.add_argument('--output-log', dest='output_log',
|
||||||
|
default=None,
|
||||||
|
help=("Path where the run result will be stored."))
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--extra-env-vars',
|
||||||
|
action=KeyValueAction,
|
||||||
|
default=None,
|
||||||
|
metavar="key1=<val1> [--extra-vars key3=<val3>]",
|
||||||
|
help=(
|
||||||
|
" Add extra environment variables you may need "
|
||||||
|
"to provide to your Ansible execution "
|
||||||
|
"as KEY=VALUE pairs. Note that if you pass the same "
|
||||||
|
"KEY multiple times, the last given VALUE for that same KEY "
|
||||||
|
"will override the other(s)")
|
||||||
|
)
|
||||||
|
|
||||||
|
extra_vars_group = parser.add_mutually_exclusive_group(required=False)
|
||||||
|
extra_vars_group.add_argument(
|
||||||
|
'--extra-vars',
|
||||||
|
default=None,
|
||||||
|
metavar="key1=<val1> [--extra-vars key3=<val3>]",
|
||||||
|
action=KeyValueAction,
|
||||||
|
help=(
|
||||||
|
"Add Ansible extra variables to the validation(s) execution "
|
||||||
|
"as KEY=VALUE pair(s). Note that if you pass the same "
|
||||||
|
"KEY multiple times, the last given VALUE for that same KEY "
|
||||||
|
"will override the other(s)")
|
||||||
|
)
|
||||||
|
|
||||||
|
extra_vars_group.add_argument(
|
||||||
|
'--extra-vars-file',
|
||||||
|
action='store',
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
"Add a JSON/YAML file containing extra variable "
|
||||||
|
"to a validation: "
|
||||||
|
"--extra-vars-file /home/stack/vars.[json|yaml]."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ex_group = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
ex_group.add_argument(
|
||||||
|
'--validation',
|
||||||
|
metavar='<validation_id>[,<validation_id>,...]',
|
||||||
|
dest="validation_name",
|
||||||
|
action=CommaListAction,
|
||||||
|
default=[],
|
||||||
|
help=("Run 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', '-g',
|
||||||
|
metavar='<group>[,<group>,...]',
|
||||||
|
action=CommaListAction,
|
||||||
|
default=[],
|
||||||
|
help=("Run 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):
|
||||||
|
"""Take validation action"""
|
||||||
|
v_actions = ValidationActions(
|
||||||
|
validation_path=parsed_args.validation_dir)
|
||||||
|
|
||||||
|
extra_vars = parsed_args.extra_vars
|
||||||
|
if parsed_args.extra_vars_file:
|
||||||
|
try:
|
||||||
|
with open(parsed_args.extra_vars_file, 'r') as env_file:
|
||||||
|
extra_vars = yaml.safe_load(env_file.read())
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
error_msg = (
|
||||||
|
"The extra_vars file must be properly formatted YAML/JSON."
|
||||||
|
"Details: %s." % e)
|
||||||
|
raise RuntimeError(error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
results = v_actions.run_validations(
|
||||||
|
inventory=parsed_args.inventory,
|
||||||
|
limit_hosts=parsed_args.limit,
|
||||||
|
group=parsed_args.group,
|
||||||
|
extra_vars=extra_vars,
|
||||||
|
validations_dir=parsed_args.validation_dir,
|
||||||
|
base_dir=parsed_args.ansible_base_dir,
|
||||||
|
validation_name=parsed_args.validation_name,
|
||||||
|
extra_env_vars=parsed_args.extra_env_vars,
|
||||||
|
quiet=True,
|
||||||
|
ssh_user=parsed_args.ssh_user)
|
||||||
|
except RuntimeError as e:
|
||||||
|
raise RuntimeError(e)
|
||||||
|
|
||||||
|
_rc = None
|
||||||
|
if results:
|
||||||
|
_rc = any([1 for r in results if r['Status'] == 'FAILED'])
|
||||||
|
|
||||||
|
if parsed_args.output_log:
|
||||||
|
common.write_output(parsed_args.output_log, results)
|
||||||
|
common.print_dict(results)
|
||||||
|
|
||||||
|
if _rc:
|
||||||
|
raise RuntimeError("One or more validations have failed.")
|
151
validations_libs/cli/show.py
Normal file
151
validations_libs/cli/show.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from cliff.show import ShowOne
|
||||||
|
|
||||||
|
from validations_libs.validation_actions import ValidationActions
|
||||||
|
from validations_libs import constants
|
||||||
|
from validations_libs.cli.parseractions import CommaListAction
|
||||||
|
|
||||||
|
|
||||||
|
class Show(ShowOne):
|
||||||
|
"""Validation Show client implementation class"""
|
||||||
|
|
||||||
|
def get_parser(self, parser):
|
||||||
|
"""Argument parser for validation show"""
|
||||||
|
parser = super(Show, self).get_parser(parser)
|
||||||
|
parser.add_argument('--validation-dir', dest='validation_dir',
|
||||||
|
default=constants.ANSIBLE_VALIDATION_DIR,
|
||||||
|
help=("Path where the validation playbooks "
|
||||||
|
"is located."))
|
||||||
|
parser.add_argument('validation_name',
|
||||||
|
metavar="<validation>",
|
||||||
|
type=str,
|
||||||
|
help="Show a specific validation.")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
"""Take validation action"""
|
||||||
|
# Get parameters:
|
||||||
|
validation_dir = parsed_args.validation_dir
|
||||||
|
validation_name = parsed_args.validation_name
|
||||||
|
|
||||||
|
v_actions = ValidationActions(validation_path=validation_dir)
|
||||||
|
data = v_actions.show_validations(validation_name)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
return data.keys(), data.values()
|
||||||
|
|
||||||
|
|
||||||
|
class ShowGroup(ShowOne):
|
||||||
|
"""Validation Show group client implementation class"""
|
||||||
|
|
||||||
|
def get_parser(self, parser):
|
||||||
|
"""Argument parser for validation show group"""
|
||||||
|
parser = super(ShowGroup, self).get_parser(parser)
|
||||||
|
parser.add_argument('--validation-dir', dest='validation_dir',
|
||||||
|
default=constants.ANSIBLE_VALIDATION_DIR,
|
||||||
|
help=("Path where the validation playbooks "
|
||||||
|
"is located."))
|
||||||
|
parser.add_argument('--group', '-g',
|
||||||
|
metavar='<group_name>',
|
||||||
|
dest="group",
|
||||||
|
help=("Show a specific group."))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
"""Take validation action"""
|
||||||
|
# Get parameters:
|
||||||
|
validation_dir = parsed_args.validation_dir
|
||||||
|
group = parsed_args.group
|
||||||
|
|
||||||
|
v_actions = ValidationActions(validation_path=validation_dir)
|
||||||
|
return v_actions.group_information(group)
|
||||||
|
|
||||||
|
|
||||||
|
class ShowParameter(ShowOne):
|
||||||
|
"""Display Validations Parameters"""
|
||||||
|
|
||||||
|
def get_parser(self, parser):
|
||||||
|
parser = super(ShowParameter, self).get_parser(parser)
|
||||||
|
|
||||||
|
parser.add_argument('--validation-dir', dest='validation_dir',
|
||||||
|
default=constants.ANSIBLE_VALIDATION_DIR,
|
||||||
|
help=("Path where the validation playbooks "
|
||||||
|
"is located."))
|
||||||
|
|
||||||
|
ex_group = parser.add_mutually_exclusive_group(required=False)
|
||||||
|
ex_group.add_argument(
|
||||||
|
'--validation',
|
||||||
|
metavar='<validation_id>[,<validation_id>,...]',
|
||||||
|
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', '-g',
|
||||||
|
metavar='<group_id>[,<group_id>,...]',
|
||||||
|
action=CommaListAction,
|
||||||
|
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(
|
||||||
|
'--download',
|
||||||
|
action='store',
|
||||||
|
default=None,
|
||||||
|
help=("Create a json or a yaml file "
|
||||||
|
"containing all the variables "
|
||||||
|
"available for the validations: "
|
||||||
|
"/tmp/myvars")
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--format-output',
|
||||||
|
action='store',
|
||||||
|
metavar='<format_output>',
|
||||||
|
default='json',
|
||||||
|
choices=['json', 'yaml'],
|
||||||
|
help=("Print representation of the validation. "
|
||||||
|
"The choices of the output format is json,yaml. ")
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
v_actions = ValidationActions(parsed_args.validation_dir)
|
||||||
|
params = v_actions.show_validations_parameters(
|
||||||
|
parsed_args.validation_name,
|
||||||
|
parsed_args.group,
|
||||||
|
parsed_args.format_output,
|
||||||
|
parsed_args.download)
|
||||||
|
if parsed_args.download:
|
||||||
|
print("The file {} has been created successfully".format(
|
||||||
|
parsed_args.download))
|
||||||
|
return params.keys(), params.values()
|
14
validations_libs/tests/cli/__init__.py
Normal file
14
validations_libs/tests/cli/__init__.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
42
validations_libs/tests/cli/fakes.py
Normal file
42
validations_libs/tests/cli/fakes.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from validations_libs.cli import app
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCommand(TestCase):
|
||||||
|
|
||||||
|
def check_parser(self, cmd, args, verify_args):
|
||||||
|
cmd_parser = cmd.get_parser('check_parser')
|
||||||
|
try:
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
except SystemExit:
|
||||||
|
raise Exception("Argument parse failed")
|
||||||
|
for av in verify_args:
|
||||||
|
attr, value = av
|
||||||
|
if attr:
|
||||||
|
self.assertIn(attr, parsed_args)
|
||||||
|
self.assertEqual(value, getattr(parsed_args, attr))
|
||||||
|
return parsed_args
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(BaseCommand, self).setUp()
|
||||||
|
self.app = app.ValidationCliApp()
|
83
validations_libs/tests/cli/test_history.py
Normal file
83
validations_libs/tests/cli/test_history.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from validations_libs.cli import history
|
||||||
|
from validations_libs.tests import fakes
|
||||||
|
from validations_libs.tests.cli.fakes import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TestListHistory(BaseCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestListHistory, self).setUp()
|
||||||
|
self.cmd = history.ListHistory(self.app, None)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'show_history')
|
||||||
|
def test_list_history(self, mock_history):
|
||||||
|
arglist = ['--validation-log-dir', '/foo/log/dir']
|
||||||
|
verifylist = [('validation_log_dir', '/foo/log/dir')]
|
||||||
|
|
||||||
|
col = ('UUID', 'Validations', 'Status', 'Execution at', 'Duration')
|
||||||
|
values = [('008886df-d297-1eaa-2a74-000000000008',
|
||||||
|
'512e', 'PASSED',
|
||||||
|
'2019-11-25T13:40:14.404623Z',
|
||||||
|
'0:00:03.753')]
|
||||||
|
mock_history.return_value = (col, values)
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.assertEqual(result, (col, values))
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetHistory(BaseCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestGetHistory, self).setUp()
|
||||||
|
self.cmd = history.GetHistory(self.app, None)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_logs.ValidationLogs.'
|
||||||
|
'get_logfile_content_by_uuid',
|
||||||
|
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST)
|
||||||
|
def test_get_history(self, mock_logs):
|
||||||
|
arglist = ['123']
|
||||||
|
verifylist = [('uuid', '123')]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_logs.ValidationLogs.'
|
||||||
|
'get_logfile_content_by_uuid',
|
||||||
|
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST)
|
||||||
|
def test_get_history_from_log_dir(self, mock_logs):
|
||||||
|
arglist = ['123', '--validation-log-dir', '/foo/log/dir']
|
||||||
|
verifylist = [('uuid', '123'), ('validation_log_dir', '/foo/log/dir')]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_logs.ValidationLogs.'
|
||||||
|
'get_logfile_content_by_uuid',
|
||||||
|
return_value=fakes.VALIDATIONS_LOGS_CONTENTS_LIST)
|
||||||
|
def test_get_history_full_arg(self, mock_logs):
|
||||||
|
arglist = ['123', '--full']
|
||||||
|
verifylist = [('uuid', '123'), ('full', True)]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
78
validations_libs/tests/cli/test_list.py
Normal file
78
validations_libs/tests/cli/test_list.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from validations_libs.cli import lister
|
||||||
|
from validations_libs.tests import fakes
|
||||||
|
from validations_libs.tests.cli.fakes import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TestList(BaseCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestList, self).setUp()
|
||||||
|
self.cmd = lister.ValidationList(self.app, None)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'list_validations',
|
||||||
|
return_value=fakes.VALIDATIONS_LIST)
|
||||||
|
def test_list_validations(self, mock_list):
|
||||||
|
arglist = ['--validation-dir', 'foo']
|
||||||
|
verifylist = [('validation_dir', 'foo')]
|
||||||
|
|
||||||
|
list = [{'description': 'My Validation One Description',
|
||||||
|
'groups': ['prep', 'pre-deployment'],
|
||||||
|
'id': 'my_val1',
|
||||||
|
'name': 'My Validation One Name',
|
||||||
|
'parameters': {}
|
||||||
|
}, {
|
||||||
|
'description': 'My Validation Two Description',
|
||||||
|
'groups': ['prep', 'pre-introspection'],
|
||||||
|
'id': 'my_val2',
|
||||||
|
'name': 'My Validation Two Name',
|
||||||
|
'parameters': {'min_value': 8}
|
||||||
|
}]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.assertEqual(result, list)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'list_validations',
|
||||||
|
return_value=[])
|
||||||
|
def test_list_validations_empty(self, mock_list):
|
||||||
|
arglist = ['--validation-dir', 'foo']
|
||||||
|
verifylist = [('validation_dir', 'foo')]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.utils.parse_all_validations_on_disk',
|
||||||
|
return_value=fakes.VALIDATIONS_LIST_GROUP)
|
||||||
|
def test_list_validations_group(self, mock_list):
|
||||||
|
arglist = ['--validation-dir', 'foo', '--group', 'prep']
|
||||||
|
verifylist = [('validation_dir', 'foo'),
|
||||||
|
('group', ['prep'])]
|
||||||
|
|
||||||
|
list = fakes.VALIDATION_LIST_RESULT
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.assertEqual(result, list)
|
274
validations_libs/tests/cli/test_run.py
Normal file
274
validations_libs/tests/cli/test_run.py
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from validations_libs.cli import run
|
||||||
|
from validations_libs.tests import fakes
|
||||||
|
from validations_libs.tests.cli.fakes import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TestRun(BaseCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestRun, self).setUp()
|
||||||
|
self.cmd = run.Run(self.app, None)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=None)
|
||||||
|
def test_run_command_return_none(self, mock_run):
|
||||||
|
arglist = ['--validation', 'foo']
|
||||||
|
verifylist = [('validation_name', ['foo'])]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.assertEqual(result, None)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=fakes.FAKE_SUCCESS_RUN)
|
||||||
|
def test_run_command_success(self, mock_run):
|
||||||
|
arglist = ['--validation', 'foo']
|
||||||
|
verifylist = [('validation_name', ['foo'])]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
def test_run_command_exclusive_group(self):
|
||||||
|
arglist = ['--validation', 'foo', '--group', 'bar']
|
||||||
|
verifylist = [('validation_name', ['foo'], 'group', 'bar')]
|
||||||
|
|
||||||
|
self.assertRaises(Exception, self.check_parser, self.cmd,
|
||||||
|
arglist, verifylist)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.cli.common.print_dict')
|
||||||
|
@mock.patch('getpass.getuser',
|
||||||
|
return_value='doe')
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=fakes.FAKE_SUCCESS_RUN)
|
||||||
|
def test_run_command_extra_vars(self, mock_run, mock_user, mock_print):
|
||||||
|
run_called_args = {
|
||||||
|
'inventory': 'localhost',
|
||||||
|
'limit_hosts': None,
|
||||||
|
'group': [],
|
||||||
|
'extra_vars': {'key': 'value'},
|
||||||
|
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||||
|
'base_dir': '/usr/share/ansible/',
|
||||||
|
'validation_name': ['foo'],
|
||||||
|
'extra_env_vars': None,
|
||||||
|
'quiet': True,
|
||||||
|
'ssh_user': 'doe'}
|
||||||
|
|
||||||
|
arglist = ['--validation', 'foo',
|
||||||
|
'--extra-vars', 'key=value']
|
||||||
|
verifylist = [('validation_name', ['foo']),
|
||||||
|
('extra_vars', {'key': 'value'})]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
mock_run.assert_called_with(**run_called_args)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.cli.common.print_dict')
|
||||||
|
@mock.patch('getpass.getuser',
|
||||||
|
return_value='doe')
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=fakes.FAKE_SUCCESS_RUN)
|
||||||
|
def test_run_command_extra_vars_twice(self, mock_run, mock_user,
|
||||||
|
mock_print):
|
||||||
|
run_called_args = {
|
||||||
|
'inventory': 'localhost',
|
||||||
|
'limit_hosts': None,
|
||||||
|
'group': [],
|
||||||
|
'extra_vars': {'key': 'value2'},
|
||||||
|
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||||
|
'base_dir': '/usr/share/ansible/',
|
||||||
|
'validation_name': ['foo'],
|
||||||
|
'extra_env_vars': None,
|
||||||
|
'quiet': True,
|
||||||
|
'ssh_user': 'doe'}
|
||||||
|
|
||||||
|
arglist = ['--validation', 'foo',
|
||||||
|
'--extra-vars', 'key=value1',
|
||||||
|
'--extra-vars', 'key=value2']
|
||||||
|
verifylist = [('validation_name', ['foo']),
|
||||||
|
('extra_vars', {'key': 'value2'})]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
mock_run.assert_called_with(**run_called_args)
|
||||||
|
|
||||||
|
def test_run_command_exclusive_vars(self):
|
||||||
|
arglist = ['--validation', 'foo',
|
||||||
|
'--extra-vars', 'key=value1',
|
||||||
|
'--extra-vars-file', '/foo/vars.yaml']
|
||||||
|
verifylist = [('validation_name', ['foo']),
|
||||||
|
('extra_vars', {'key': 'value2'})]
|
||||||
|
|
||||||
|
self.assertRaises(Exception, self.check_parser, self.cmd,
|
||||||
|
arglist, verifylist)
|
||||||
|
|
||||||
|
@mock.patch('yaml.safe_load', return_value={'key': 'value'})
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
@mock.patch('getpass.getuser',
|
||||||
|
return_value='doe')
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=fakes.FAKE_SUCCESS_RUN)
|
||||||
|
def test_run_command_extra_vars_file(self, mock_run, mock_user, mock_open,
|
||||||
|
mock_yaml):
|
||||||
|
run_called_args = {
|
||||||
|
'inventory': 'localhost',
|
||||||
|
'limit_hosts': None,
|
||||||
|
'group': [],
|
||||||
|
'extra_vars': {'key': 'value'},
|
||||||
|
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||||
|
'base_dir': '/usr/share/ansible/',
|
||||||
|
'validation_name': ['foo'],
|
||||||
|
'extra_env_vars': None,
|
||||||
|
'quiet': True,
|
||||||
|
'ssh_user': 'doe'}
|
||||||
|
|
||||||
|
arglist = ['--validation', 'foo',
|
||||||
|
'--extra-vars-file', '/foo/vars.yaml']
|
||||||
|
verifylist = [('validation_name', ['foo']),
|
||||||
|
('extra_vars_file', '/foo/vars.yaml')]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
mock_run.assert_called_with(**run_called_args)
|
||||||
|
|
||||||
|
@mock.patch('getpass.getuser',
|
||||||
|
return_value='doe')
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=fakes.FAKE_SUCCESS_RUN)
|
||||||
|
def test_run_command_extra_env_vars(self, mock_run, mock_user):
|
||||||
|
run_called_args = {
|
||||||
|
'inventory': 'localhost',
|
||||||
|
'limit_hosts': None,
|
||||||
|
'group': [],
|
||||||
|
'extra_vars': None,
|
||||||
|
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||||
|
'base_dir': '/usr/share/ansible/',
|
||||||
|
'validation_name': ['foo'],
|
||||||
|
'extra_env_vars': {'key': 'value'},
|
||||||
|
'quiet': True,
|
||||||
|
'ssh_user': 'doe'}
|
||||||
|
|
||||||
|
arglist = ['--validation', 'foo',
|
||||||
|
'--extra-env-vars', 'key=value']
|
||||||
|
verifylist = [('validation_name', ['foo']),
|
||||||
|
('extra_env_vars', {'key': 'value'})]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
mock_run.assert_called_with(**run_called_args)
|
||||||
|
|
||||||
|
@mock.patch('getpass.getuser',
|
||||||
|
return_value='doe')
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=fakes.FAKE_SUCCESS_RUN)
|
||||||
|
def test_run_command_extra_env_vars_twice(self, mock_run, mock_user):
|
||||||
|
run_called_args = {
|
||||||
|
'inventory': 'localhost',
|
||||||
|
'limit_hosts': None,
|
||||||
|
'group': [],
|
||||||
|
'extra_vars': None,
|
||||||
|
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||||
|
'base_dir': '/usr/share/ansible/',
|
||||||
|
'validation_name': ['foo'],
|
||||||
|
'extra_env_vars': {'key': 'value2'},
|
||||||
|
'quiet': True,
|
||||||
|
'ssh_user': 'doe'}
|
||||||
|
|
||||||
|
arglist = ['--validation', 'foo',
|
||||||
|
'--extra-env-vars', 'key=value1',
|
||||||
|
'--extra-env-vars', 'key=value2']
|
||||||
|
verifylist = [('validation_name', ['foo']),
|
||||||
|
('extra_env_vars', {'key': 'value2'})]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
mock_run.assert_called_with(**run_called_args)
|
||||||
|
|
||||||
|
@mock.patch('getpass.getuser',
|
||||||
|
return_value='doe')
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=fakes.FAKE_SUCCESS_RUN)
|
||||||
|
def test_run_command_extra_env_vars_and_extra_vars(self, mock_run,
|
||||||
|
mock_user):
|
||||||
|
run_called_args = {
|
||||||
|
'inventory': 'localhost',
|
||||||
|
'limit_hosts': None,
|
||||||
|
'group': [],
|
||||||
|
'extra_vars': {'key': 'value'},
|
||||||
|
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||||
|
'base_dir': '/usr/share/ansible/',
|
||||||
|
'validation_name': ['foo'],
|
||||||
|
'extra_env_vars': {'key2': 'value2'},
|
||||||
|
'quiet': True,
|
||||||
|
'ssh_user': 'doe'}
|
||||||
|
|
||||||
|
arglist = ['--validation', 'foo',
|
||||||
|
'--extra-vars', 'key=value',
|
||||||
|
'--extra-env-vars', 'key2=value2']
|
||||||
|
verifylist = [('validation_name', ['foo']),
|
||||||
|
('extra_vars', {'key': 'value'}),
|
||||||
|
('extra_env_vars', {'key2': 'value2'})]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
mock_run.assert_called_with(**run_called_args)
|
||||||
|
|
||||||
|
def test_run_command_exclusive_wrong_extra_vars(self):
|
||||||
|
arglist = ['--validation', 'foo',
|
||||||
|
'--extra-vars', 'key=value1,key=value2']
|
||||||
|
verifylist = [('validation_name', ['foo']),
|
||||||
|
('extra_vars', {'key': 'value2'})]
|
||||||
|
|
||||||
|
self.assertRaises(Exception, self.check_parser, self.cmd,
|
||||||
|
arglist, verifylist)
|
||||||
|
|
||||||
|
@mock.patch('getpass.getuser',
|
||||||
|
return_value='doe')
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'run_validations',
|
||||||
|
return_value=fakes.FAKE_FAILED_RUN)
|
||||||
|
def test_run_command_failed_validation(self, mock_run, mock_user):
|
||||||
|
run_called_args = {
|
||||||
|
'inventory': 'localhost',
|
||||||
|
'limit_hosts': None,
|
||||||
|
'group': [],
|
||||||
|
'extra_vars': {'key': 'value'},
|
||||||
|
'validations_dir': '/usr/share/ansible/validation-playbooks',
|
||||||
|
'base_dir': '/usr/share/ansible/',
|
||||||
|
'validation_name': ['foo'],
|
||||||
|
'extra_env_vars': {'key2': 'value2'},
|
||||||
|
'quiet': True,
|
||||||
|
'ssh_user': 'doe'}
|
||||||
|
|
||||||
|
arglist = ['--validation', 'foo']
|
||||||
|
verifylist = [('validation_name', ['foo'])]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.assertRaises(RuntimeError, self.cmd.take_action, parsed_args)
|
79
validations_libs/tests/cli/test_show.py
Normal file
79
validations_libs/tests/cli/test_show.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from validations_libs.cli import show
|
||||||
|
from validations_libs.tests import fakes
|
||||||
|
from validations_libs.tests.cli.fakes import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TestShow(BaseCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShow, self).setUp()
|
||||||
|
self.cmd = show.Show(self.app, None)
|
||||||
|
|
||||||
|
@mock.patch('validations_libs.validation_actions.ValidationActions.'
|
||||||
|
'show_validations')
|
||||||
|
def test_show_validations(self, mock_show):
|
||||||
|
arglist = ['foo']
|
||||||
|
verifylist = [('validation_name', 'foo')]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShowGroup(BaseCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShowGroup, self).setUp()
|
||||||
|
self.cmd = show.ShowGroup(self.app, None)
|
||||||
|
|
||||||
|
@mock.patch('yaml.safe_load', return_value=fakes.GROUP)
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_show_validations_group_info(self, mock_open, mock_yaml):
|
||||||
|
arglist = ['--group', 'group.yaml']
|
||||||
|
verifylist = [('group', 'group.yaml')]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShowParameter(BaseCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShowParameter, self).setUp()
|
||||||
|
self.cmd = show.ShowParameter(self.app, None)
|
||||||
|
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_show_validations_parameters_by_group(self, mock_open):
|
||||||
|
arglist = ['--group', 'prep']
|
||||||
|
verifylist = [('group', ['prep'])]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
def test_show_parameter_exclusive_group(self):
|
||||||
|
arglist = ['--validation', 'foo', '--group', 'bar']
|
||||||
|
verifylist = [('validation_name', ['foo'], 'group', ['bar'])]
|
||||||
|
|
||||||
|
self.assertRaises(Exception, self.check_parser, self.cmd,
|
||||||
|
arglist, verifylist)
|
||||||
|
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_show_validations_parameters_by_validations(self, mock_open):
|
||||||
|
arglist = ['--group', 'prep']
|
||||||
|
verifylist = [('group', ['prep'])]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
@ -27,6 +27,19 @@ VALIDATIONS_LIST = [{
|
|||||||
'parameters': {'min_value': 8}
|
'parameters': {'min_value': 8}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
VALIDATIONS_LIST_GROUP = [{
|
||||||
|
'description': 'My Validation Two Description',
|
||||||
|
'groups': ['prep', 'pre-introspection'],
|
||||||
|
'id': 'my_val2',
|
||||||
|
'name': 'My Validation Two Name',
|
||||||
|
'parameters': {'min_value': 8}
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
VALIDATION_LIST_RESULT = (('ID', 'Name', 'Groups'),
|
||||||
|
[('my_val2', 'My Validation Two Name',
|
||||||
|
['prep', 'pre-introspection'])])
|
||||||
|
|
||||||
GROUPS_LIST = [
|
GROUPS_LIST = [
|
||||||
('group1', 'Group1 description'),
|
('group1', 'Group1 description'),
|
||||||
('group2', 'Group2 description'),
|
('group2', 'Group2 description'),
|
||||||
@ -252,6 +265,36 @@ GROUP = {'no-op': [{'description': 'noop-foo'}],
|
|||||||
'pre': [{'description': 'pre-foo'}],
|
'pre': [{'description': 'pre-foo'}],
|
||||||
'post': [{'description': 'post-foo'}]}
|
'post': [{'description': 'post-foo'}]}
|
||||||
|
|
||||||
|
FAKE_SUCCESS_RUN = [{'Duration': '0:00:01.761',
|
||||||
|
'Host_Group': 'overcloud',
|
||||||
|
'Status': 'PASSED',
|
||||||
|
'Status_by_Host': 'subnode-1,PASSED, subnode-2,PASSED',
|
||||||
|
'UUID': '123',
|
||||||
|
'Unreachable_Hosts': '',
|
||||||
|
'Validations': 'foo'}]
|
||||||
|
|
||||||
|
FAKE_FAILED_RUN = [{'Duration': '0:00:01.761',
|
||||||
|
'Host_Group': 'overcloud',
|
||||||
|
'Status': 'FAILED',
|
||||||
|
'Status_by_Host': 'subnode-1,FAILED, subnode-2,PASSED',
|
||||||
|
'UUID': '123',
|
||||||
|
'Unreachable_Hosts': '',
|
||||||
|
'Validations': 'foo'},
|
||||||
|
{'Duration': '0:00:01.761',
|
||||||
|
'Host_Group': 'overcloud',
|
||||||
|
'Status': 'FAILED',
|
||||||
|
'Status_by_Host': 'subnode-1,FAILED, subnode-2,PASSED',
|
||||||
|
'UUID': '123',
|
||||||
|
'Unreachable_Hosts': '',
|
||||||
|
'Validations': 'foo'},
|
||||||
|
{'Duration': '0:00:01.761',
|
||||||
|
'Host_Group': 'overcloud',
|
||||||
|
'Status': 'PASSED',
|
||||||
|
'Status_by_Host': 'subnode-1,PASSED, subnode-2,PASSED',
|
||||||
|
'UUID': '123',
|
||||||
|
'Unreachable_Hosts': '',
|
||||||
|
'Validations': 'foo'}]
|
||||||
|
|
||||||
|
|
||||||
def fake_ansible_runner_run_return(status='successful', rc=0):
|
def fake_ansible_runner_run_return(status='successful', rc=0):
|
||||||
return status, rc
|
return status, rc
|
||||||
|
@ -308,8 +308,7 @@ class TestValidationActions(TestCase):
|
|||||||
{'parameters': fakes.FAKE_METADATA}}
|
{'parameters': fakes.FAKE_METADATA}}
|
||||||
v_actions = ValidationActions()
|
v_actions = ValidationActions()
|
||||||
result = v_actions.show_validations_parameters('foo')
|
result = v_actions.show_validations_parameters('foo')
|
||||||
self.assertEqual(result, json.dumps(mock_get_param.return_value,
|
self.assertEqual(result, mock_get_param.return_value)
|
||||||
indent=4, sort_keys=True))
|
|
||||||
|
|
||||||
@mock.patch('six.moves.builtins.open')
|
@mock.patch('six.moves.builtins.open')
|
||||||
def test_show_validations_parameters_non_supported_format(self, mock_open):
|
def test_show_validations_parameters_non_supported_format(self, mock_open):
|
||||||
|
@ -44,9 +44,8 @@ class ValidationActions(object):
|
|||||||
self.log = logging.getLogger(__name__ + ".ValidationActions")
|
self.log = logging.getLogger(__name__ + ".ValidationActions")
|
||||||
self.validation_path = (validation_path if validation_path
|
self.validation_path = (validation_path if validation_path
|
||||||
else constants.ANSIBLE_VALIDATION_DIR)
|
else constants.ANSIBLE_VALIDATION_DIR)
|
||||||
self.group = group
|
|
||||||
|
|
||||||
def list_validations(self):
|
def list_validations(self, group=None):
|
||||||
"""Get a list of the available validations
|
"""Get a list of the available validations
|
||||||
|
|
||||||
This is used to print table from python ``Tuple`` with ``PrettyTable``.
|
This is used to print table from python ``Tuple`` with ``PrettyTable``.
|
||||||
@ -76,7 +75,7 @@ class ValidationActions(object):
|
|||||||
"""
|
"""
|
||||||
self.log = logging.getLogger(__name__ + ".list_validations")
|
self.log = logging.getLogger(__name__ + ".list_validations")
|
||||||
validations = v_utils.parse_all_validations_on_disk(
|
validations = v_utils.parse_all_validations_on_disk(
|
||||||
self.validation_path, self.group)
|
self.validation_path, group)
|
||||||
|
|
||||||
return_values = []
|
return_values = []
|
||||||
column_name = ('ID', 'Name', 'Groups')
|
column_name = ('ID', 'Name', 'Groups')
|
||||||
@ -498,15 +497,7 @@ class ValidationActions(object):
|
|||||||
allow_unicode=True,
|
allow_unicode=True,
|
||||||
default_flow_style=False,
|
default_flow_style=False,
|
||||||
indent=2))
|
indent=2))
|
||||||
if output_format == 'json':
|
return params
|
||||||
return json.dumps(params,
|
|
||||||
indent=4,
|
|
||||||
sort_keys=True)
|
|
||||||
else:
|
|
||||||
return yaml.safe_dump(params,
|
|
||||||
allow_unicode=True,
|
|
||||||
default_flow_style=False,
|
|
||||||
indent=2)
|
|
||||||
|
|
||||||
def show_history(self, validation_ids=None, extension='json',
|
def show_history(self, validation_ids=None, extension='json',
|
||||||
log_path=constants.VALIDATIONS_LOG_BASEDIR):
|
log_path=constants.VALIDATIONS_LOG_BASEDIR):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user