Add validation config file mechanism

Introduce validation config file.
The config default location of the config file will be stored
in /etc/validation.cfg

The variables precedence will be the following:
* user's cli args
* config file
* default interval values

Change-Id: I05c54a43bc0a03878793cca3f51e23f4a8b63a23
This commit is contained in:
matbu 2021-05-20 19:09:37 +02:00 committed by Jiri Podivin
parent c8480cbf80
commit 11488cd88d
23 changed files with 884 additions and 93 deletions

View File

@ -20,3 +20,4 @@ Command Options
~~~~~~~~~~~~~~~
.. autoprogram-cliff:: validation.cli
:application: validation

View File

@ -22,6 +22,10 @@ classifier =
[files]
packages = validations_libs
data_files =
etc =
validation.cfg
[compile_catalog]
directory = validations-libs/locale
domain = validations-lib

63
validation.cfg Normal file
View File

@ -0,0 +1,63 @@
[default]
# Default configuration for the Validation Framework
# These are mainly CLI parameters which can be set here in order to avoid
# to provide the same parameters on each runs.
# Location where the Validation playbooks are stored.
validation_dir = /usr/share/ansible/validation-playbooks
# Path where the framework is supposed to write logs and results.
# Note: this should not be a relative path.
# By default the framework log in $HOME/validations.
# Uncomment this line according to your prefered location:
# validation_log_dir = /usr/share/validations
# Location where the Ansible Validation Callback, Libraries and Modules are
# stored.
ansible_base_dir = /usr/share/ansible/
# Ssh user for the remote access
#ssh_user = stack
# Output log for the Validation results.
output_log = output.log
# Limitation of the number of results to return to the console.
history_limit = 15
fit_width = True
[ansible_runner]
# Ansible Runner configuration parameters.
# Here you can set the Runner parameters which will be used by the framework.
# Note that only those parameters are supported, any other custom parameters
# will be ignored.
# Verbosity for Ansible
verbosity = 5
# Fact cache directory location and type
# fact_cache = /var/log/validations/artifacts/
fact_cache_type = jsonfile
# Inventory for Ansible
#inventory = hosts.yaml
quiet = True
rotate_artifacts = 256
[ansible_environment]
# Ansible Environment variables.
# You can provide here, all the Ansible configuration variables documented here:
# https://docs.ansible.com/ansible/latest/reference_appendices/config.html
# Here is a set of parameters used by the Validation Framework as example:
#ANSIBLE_LOG_PATH = /home/stack/ansible.log
#ANSIBLE_REMOTE_USER = stack
ANSIBLE_CALLBACK_WHITELIST = validation_stdout,validation_json,profile_tasks
ANSIBLE_STDOUT_CALLBACK = validation_stdout
# Callback settings which are part of Ansible environment variables.
# Configuration for HTTP Server callback
HTTP_JSON_SERVER = http://localhost
HTTP_JSON_PORT = 8080

View File

@ -26,6 +26,7 @@ import yaml
from six.moves import configparser
from validations_libs import constants
from validations_libs import utils
LOG = logging.getLogger(__name__ + ".ansible")
@ -275,6 +276,17 @@ class Ansible(object):
else:
return env
def _dump_validation_config(self, config, path, filename='validation.cfg'):
"""Dump Validation config in artifact directory"""
parser = configparser.ConfigParser()
for section_key in config.keys():
parser.add_section(section_key)
for item_key in config[section_key].keys():
parser.set(section_key, item_key,
str(config[section_key][item_key]))
with open('{}/{}'.format(path, filename), 'w') as conf:
parser.write(conf)
def run(self, playbook, inventory, workdir, playbook_dir=None,
connection='smart', output_callback=None,
base_dir=constants.DEFAULT_VALIDATIONS_BASEDIR,
@ -283,9 +295,10 @@ class Ansible(object):
verbosity=0, quiet=False, extra_vars=None,
gathering_policy='smart',
extra_env_variables=None, parallel_run=False,
callback_whitelist=None, ansible_cfg=None,
callback_whitelist=None, ansible_cfg_file=None,
ansible_timeout=30, ansible_artifact_path=None,
log_path=None, run_async=False, python_interpreter=None):
log_path=None, run_async=False, python_interpreter=None,
validation_cfg_file=None):
"""Execute one or multiple Ansible playbooks
:param playbook: The Absolute path of the Ansible playbook
@ -341,9 +354,10 @@ class Ansible(object):
Custom output_callback is also whitelisted.
(Defaults to ``None``)
:type callback_whitelist: ``list`` or ``string``
:param ansible_cfg: Path to an ansible configuration file. One will be
generated in the artifact path if this option is None.
:type ansible_cfg: ``string``
:param ansible_cfg_file: Path to an ansible configuration file. One
will be generated in the artifact path if
this option is None.
:type ansible_cfg_file: ``string``
:param ansible_timeout: Timeout for ansible connections.
(Defaults to ``30 minutes``)
:type ansible_timeout: ``integer``
@ -360,6 +374,10 @@ class Ansible(object):
``auto_silent`` or the default one
``auto_legacy``)
:type python_interpreter: ``string``
:param validation_cfg_file: A dictionary of configuration for
Validation loaded from an validation.cfg
file.
:type validation_cfg_file: ``dict``
:return: A ``tuple`` containing the the absolute path of the executed
playbook, the return code and the status of the run
@ -405,16 +423,17 @@ class Ansible(object):
ansible_timeout, callback_whitelist,
base_dir, python_interpreter))
if 'ANSIBLE_CONFIG' not in env and not ansible_cfg:
ansible_cfg = os.path.join(ansible_artifact_path, 'ansible.cfg')
config = configparser.ConfigParser()
config.add_section('defaults')
config.set('defaults', 'internal_poll_interval', '0.05')
with open(ansible_cfg, 'w') as f:
config.write(f)
env['ANSIBLE_CONFIG'] = ansible_cfg
elif 'ANSIBLE_CONFIG' not in env and ansible_cfg:
env['ANSIBLE_CONFIG'] = ansible_cfg
if 'ANSIBLE_CONFIG' not in env and not ansible_cfg_file:
ansible_cfg_file = os.path.join(ansible_artifact_path,
'ansible.cfg')
ansible_config = configparser.ConfigParser()
ansible_config.add_section('defaults')
ansible_config.set('defaults', 'internal_poll_interval', '0.05')
with open(ansible_cfg_file, 'w') as f:
ansible_config.write(f)
env['ANSIBLE_CONFIG'] = ansible_cfg_file
elif 'ANSIBLE_CONFIG' not in env and ansible_cfg_file:
env['ANSIBLE_CONFIG'] = ansible_cfg_file
if log_path:
env['VALIDATIONS_LOG_DIR'] = log_path
@ -434,7 +453,6 @@ class Ansible(object):
if not BACKWARD_COMPAT:
r_opts.update({
'envvars': envvars,
'project_dir': playbook_dir,
'fact_cache': ansible_artifact_path,
'fact_cache_type': 'jsonfile'
@ -453,6 +471,17 @@ class Ansible(object):
if parallel_run:
r_opts['directory_isolation_base_path'] = ansible_artifact_path
if validation_cfg_file:
if 'ansible_runner' in validation_cfg_file.keys():
r_opts.update(validation_cfg_file['ansible_runner'])
if 'ansible_environment' in validation_cfg_file.keys():
envvars.update(validation_cfg_file['ansible_environment'])
self._dump_validation_config(validation_cfg_file,
ansible_artifact_path)
if not BACKWARD_COMPAT:
r_opts.update({'envvars': envvars})
runner_config = ansible_runner.runner_config.RunnerConfig(**r_opts)
runner_config.prepare()
runner_config.env['ANSIBLE_STDOUT_CALLBACK'] = \

View File

@ -13,7 +13,6 @@
# 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

View File

@ -14,10 +14,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
from cliff import _argparse
from cliff.command import Command
from cliff.lister import Lister
from cliff.show import ShowOne
from validations_libs import constants
from validations_libs import utils
# Handle backward compatibility for Cliff 2.16.0 in stable/train:
if hasattr(_argparse, 'SmartHelpFormatter'):
@ -26,11 +31,50 @@ else:
from cliff.command import _SmartHelpFormatter as SmartHelpFormatter
class Base:
"""Base class for CLI arguments management"""
config = {}
def _format_arg(self, parser):
"""Format arguments parser"""
namespace, argv = parser.parse_known_args()
return [arg.lstrip(parser.prefix_chars).replace('-', '_')
for arg in argv]
def set_argument_parser(self, parser, section='default'):
""" Set Arguments parser depending of the precedence ordering:
* User CLI arguments
* Configuration file
* Default CLI values
"""
cli_args = self._format_arg(parser)
args, _ = parser.parse_known_args()
self.config = utils.load_config(os.path.abspath(args.config))
config_args = self.config.get(section, {})
for key, value in args._get_kwargs():
# matbu: manage the race when user's cli arg is the same than
# the parser default value. The user's cli arg will *always*
# takes precedence on others.
if parser.get_default(key) == value and key in cli_args:
try:
cli_value = cli_args[cli_args.index(key)+1]
config_args.update({key: cli_value})
except KeyError:
print('Key not found in cli: {}').format(key)
elif parser.get_default(key) != value:
config_args.update({key: value})
elif key not in config_args.keys():
config_args.update({key: value})
parser.set_defaults(**config_args)
return parser
class BaseCommand(Command):
"""Base Command client implementation class"""
def get_parser(self, prog_name):
"""Argument parser for base command"""
self.base = Base()
parser = _argparse.ArgumentParser(
description=self.get_description(),
epilog=self.get_epilog(),
@ -40,6 +84,14 @@ class BaseCommand(Command):
)
for hook in self._hooks:
hook.obj.get_parser(parser)
parser.add_argument(
'--config',
dest='config',
default=utils.find_config_file(),
help=("Config file path for Validation.")
)
return parser
@ -49,6 +101,7 @@ class BaseLister(Lister):
def get_parser(self, prog_name):
"""Argument parser for base lister"""
parser = super(BaseLister, self).get_parser(prog_name)
self.base = Base()
vf_parser = _argparse.ArgumentParser(
description=self.get_description(),
epilog=self.get_epilog(),
@ -60,4 +113,28 @@ class BaseLister(Lister):
for action in parser._actions:
vf_parser._add_action(action)
vf_parser.add_argument(
'--config',
dest='config',
default=utils.find_config_file(),
help=("Config file path for Validation.")
)
return vf_parser
class BaseShow(ShowOne):
"""Base Show client implementation class"""
def get_parser(self, parser):
"""Argument parser for base show"""
parser = super(BaseShow, self).get_parser(parser)
self.base = Base()
parser.add_argument(
'--config',
dest='config',
default=utils.find_config_file(),
help=("Config file path for Validation.")
)
return parser

View File

@ -19,8 +19,7 @@ import json
from validations_libs import constants
from validations_libs.validation_actions import ValidationActions
from validations_libs.validation_logs import ValidationLogs
from validations_libs.cli.base import BaseCommand
from validations_libs.cli.base import BaseLister
from validations_libs.cli.base import BaseCommand, BaseLister
class ListHistory(BaseLister):
@ -46,28 +45,27 @@ class ListHistory(BaseLister):
default=constants.VALIDATIONS_LOG_BASEDIR,
help=("Path where the validation log files "
"is located."))
return parser
# Merge config and CLI args:
return self.base.set_argument_parser(parser)
def take_action(self, parsed_args):
validation_log_dir = parsed_args.validation_log_dir
history_limit = parsed_args.history_limit
if parsed_args.history_limit < 1:
raise ValueError(
(
"Number <n> of the most recent runs must be > 0. "
"You have provided {}").format(
parsed_args.history_limit))
if history_limit < 1:
msg = ("Number <n> of the most recent runs must be > 0. "
"You have provided {}").format(history_limit)
raise ValueError(msg)
self.app.LOG.info(
(
"Limiting output to the maximum of "
"{} last validations.").format(
parsed_args.history_limit))
("Limiting output to the maximum of "
"{} last validations.").format(history_limit))
actions = ValidationActions()
return actions.show_history(
validation_ids=parsed_args.validation,
log_path=parsed_args.validation_log_dir,
history_limit=parsed_args.history_limit)
history_limit=history_limit)
class GetHistory(BaseCommand):
@ -88,10 +86,10 @@ class GetHistory(BaseCommand):
default=constants.VALIDATIONS_LOG_BASEDIR,
help=("Path where the validation log files "
"is located."))
return parser
# Merge config and CLI args:
return self.base.set_argument_parser(parser)
def take_action(self, parsed_args):
self.app.LOG.debug(
(
"Obtaining information about the validation run {}\n"

View File

@ -14,14 +14,13 @@
# License for the specific language governing permissions and limitations
# under the License.
from cliff.lister import Lister
from validations_libs.validation_actions import ValidationActions
from validations_libs import constants
from validations_libs.cli.base import BaseLister
from validations_libs.cli.parseractions import CommaListAction
class ValidationList(Lister):
class ValidationList(BaseLister):
"""List the Validations Catalog"""
def get_parser(self, parser):
@ -52,7 +51,8 @@ class ValidationList(Lister):
default=constants.ANSIBLE_VALIDATION_DIR,
help=("Path where the validation playbooks "
"are located."))
return parser
# Merge config and CLI args:
return self.base.set_argument_parser(parser)
def take_action(self, parsed_args):
"""Take validation action"""
@ -61,6 +61,7 @@ class ValidationList(Lister):
category = parsed_args.category
product = parsed_args.product
validation_dir = parsed_args.validation_dir
group = parsed_args.group
v_actions = ValidationActions(validation_path=validation_dir)
return (v_actions.list_validations(groups=group,

View File

@ -156,17 +156,23 @@ class Run(BaseCommand):
"if more than one product is required "
"separate the product names with commas."))
if self.app:
# Merge config and CLI args:
return self.base.set_argument_parser(parser)
return parser
def take_action(self, parsed_args):
"""Take validation action"""
v_actions = ValidationActions(
validation_path=parsed_args.validation_dir)
# Get config:
config = self.base.config
v_actions = ValidationActions(parsed_args.validation_dir)
# Ansible execution should be quiet while using the validations_json
# default callback and be verbose while passing ANSIBLE_SDTOUT_CALLBACK
# environment variable to Ansible through the --extra-env-vars argument
quiet_mode = True
runner_config = (config.get('ansible_runner', {})
if isinstance(config, dict) else {})
quiet_mode = runner_config.get('quiet', True)
extra_env_vars = parsed_args.extra_env_vars
if extra_env_vars:
if "ANSIBLE_STDOUT_CALLBACK" in extra_env_vars.keys():
@ -178,7 +184,8 @@ class Run(BaseCommand):
"Loading extra vars file {}".format(
parsed_args.extra_vars_file))
extra_vars = common.read_extra_vars_file(parsed_args.extra_vars_file)
extra_vars = common.read_extra_vars_file(
parsed_args.extra_vars_file)
try:
results = v_actions.run_validations(
@ -195,8 +202,8 @@ class Run(BaseCommand):
python_interpreter=parsed_args.python_interpreter,
quiet=quiet_mode,
ssh_user=parsed_args.ssh_user,
log_path=parsed_args.validation_log_dir
)
log_path=parsed_args.validation_log_dir,
validation_config=config)
except RuntimeError as e:
raise RuntimeError(e)

View File

@ -14,15 +14,13 @@
# License for the specific language governing permissions and limitations
# under the License.
from cliff.show import ShowOne
from cliff.lister import Lister
from validations_libs.validation_actions import ValidationActions
from validations_libs import constants
from validations_libs.cli.parseractions import CommaListAction
from validations_libs.cli.base import BaseShow, BaseLister
class Show(ShowOne):
class Show(BaseShow):
"""Show detailed informations about a Validation"""
def get_parser(self, parser):
@ -36,7 +34,8 @@ class Show(ShowOne):
metavar="<validation>",
type=str,
help="Show a specific validation.")
return parser
# Merge config and CLI args:
return self.base.set_argument_parser(parser)
def take_action(self, parsed_args):
"""Take validation action"""
@ -51,7 +50,7 @@ class Show(ShowOne):
return data.keys(), data.values()
class ShowGroup(Lister):
class ShowGroup(BaseLister):
"""Show detailed informations about Validation Groups"""
def get_parser(self, parser):
@ -63,7 +62,8 @@ class ShowGroup(Lister):
help=("Path where the validation playbooks "
"are located."))
return parser
# Merge config and CLI args:
return self.base.set_argument_parser(parser)
def take_action(self, parsed_args):
"""Take validation action"""
@ -72,7 +72,7 @@ class ShowGroup(Lister):
return v_actions.group_information(constants.VALIDATION_GROUPS_INFO)
class ShowParameter(ShowOne):
class ShowParameter(BaseShow):
"""Show Validation(s) parameter(s)
Display Validation(s) Parameter(s) which could be overriden during an
@ -147,11 +147,13 @@ class ShowParameter(ShowOne):
help=("Print representation of the validation. "
"The choices of the output format is json,yaml. ")
)
return parser
# Merge config and CLI args:
return self.base.set_argument_parser(parser)
def take_action(self, parsed_args):
v_actions = ValidationActions(parsed_args.validation_dir)
validation_dir = parsed_args.validation_dir
v_actions = ValidationActions(validation_dir)
params = v_actions.show_validations_parameters(
validations=parsed_args.validation_name,
groups=parsed_args.group,

View File

@ -38,3 +38,7 @@ VALIDATIONS_LOG_BASEDIR = os.path.expanduser('~/validations')
VALIDATION_ANSIBLE_ARTIFACT_PATH = os.path.join(
VALIDATIONS_LOG_BASEDIR,
'artifacts')
ANSIBLE_RUNNER_CONFIG_PARAMETERS = ['verbosity', 'extravars', 'fact_cache',
'fact_cache_type', 'inventory', 'playbook',
'project_dir', 'quiet', 'rotate_artifacts']

View File

@ -12,17 +12,26 @@
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
from unittest import TestCase
from validations_libs.cli import app
from validations_libs.cli import base
from validations_libs import utils
try:
from unittest import mock
except ImportError:
import mock
class BaseCommand(TestCase):
def check_parser(self, cmd, args, verify_args):
cmd_parser = cmd.get_parser('check_parser')
try:
cmd_parser = cmd.get_parser('check_parser')
parsed_args = cmd_parser.parse_args(args)
except SystemExit:
raise Exception("Argument parse failed")
@ -35,8 +44,15 @@ class BaseCommand(TestCase):
def setUp(self):
super(BaseCommand, self).setUp()
self._set_args([])
self.app = app.ValidationCliApp()
def _set_args(self, args):
sys.argv = sys.argv[:1]
sys.argv.extend(args)
return args
KEYVALUEACTION_VALUES = {
'valid': 'foo=bar',
'invalid_noeq': 'foo>bar',

View File

@ -0,0 +1,115 @@
# 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
try:
from unittest import mock
except ImportError:
import mock
from unittest import TestCase
from validations_libs.cli import app
from validations_libs.cli import lister
from validations_libs.cli import history
class TestArgApp(TestCase):
def setUp(self):
super(TestArgApp, self).setUp()
self._set_args([])
self.app = app.ValidationCliApp()
def _set_args(self, args):
sys.argv = sys.argv[:1]
sys.argv.extend(args)
return args
def test_validation_dir_config_cli(self):
args = ['--validation-dir', 'foo']
self._set_args(args)
cmd = lister.ValidationList(self.app, None)
parser = cmd.get_parser('fake')
parsed_args = parser.parse_args(args)
self.assertEqual('foo', parsed_args.validation_dir)
@mock.patch('validations_libs.constants.ANSIBLE_VALIDATION_DIR', 'bar')
@mock.patch('validations_libs.utils.find_config_file',
return_value='validation.cfg')
def test_validation_dir_config_no_cli(self, mock_config):
args = []
self._set_args(args)
cmd = lister.ValidationList(self.app, None)
parser = cmd.get_parser('fake')
parsed_args = parser.parse_args(args)
self.assertEqual('/usr/share/ansible/validation-playbooks',
parsed_args.validation_dir)
@mock.patch('validations_libs.constants.ANSIBLE_VALIDATION_DIR', 'bar')
@mock.patch('validations_libs.utils.find_config_file',
return_value='/etc/validation.cfg')
def test_validation_dir_config_no_cli_no_config(self, mock_config):
args = []
self._set_args(args)
cmd = lister.ValidationList(self.app, None)
parser = cmd.get_parser('fake')
parsed_args = parser.parse_args(args)
self.assertEqual('bar', parsed_args.validation_dir)
@mock.patch('validations_libs.constants.ANSIBLE_VALIDATION_DIR',
'/usr/share/ansible/validation-playbooks')
@mock.patch('validations_libs.utils.find_config_file',
return_value='validation.cfg')
def test_validation_dir_config_no_cli_same_consts(self, mock_config):
args = []
self._set_args(args)
cmd = lister.ValidationList(self.app, None)
parser = cmd.get_parser('fake')
parsed_args = parser.parse_args(args)
self.assertEqual('/usr/share/ansible/validation-playbooks',
parsed_args.validation_dir)
def test_get_history_cli_arg(self):
args = ['123', '--validation-log-dir', '/foo/log/dir']
self._set_args(args)
cmd = history.GetHistory(self.app, None)
parser = cmd.get_parser('fake')
parsed_args = parser.parse_args(args)
self.assertEqual('/foo/log/dir',
parsed_args.validation_log_dir)
@mock.patch('validations_libs.utils.find_config_file',
return_value='validation.cfg')
def test_get_history_cli_arg_and_config_file(self, mock_config):
args = ['123', '--validation-log-dir', '/foo/log/dir']
self._set_args(args)
cmd = history.GetHistory(self.app, None)
parser = cmd.get_parser('fake')
parsed_args = parser.parse_args(args)
self.assertEqual('/foo/log/dir',
parsed_args.validation_log_dir)
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR',
'/home/foo/validations')
@mock.patch('validations_libs.utils.find_config_file',
return_value='validation.cfg')
def test_get_history_no_cli_arg_and_config_file(self, mock_config):
args = ['123']
self._set_args(args)
cmd = history.GetHistory(self.app, None)
parser = cmd.get_parser('fake')
parsed_args = parser.parse_args(args)
self.assertEqual('/home/foo/validations',
parsed_args.validation_log_dir)

View File

@ -0,0 +1,94 @@
# 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.cli import base
from validations_libs.tests import fakes
from validations_libs.tests.cli.fakes import BaseCommand
import argparse
class TestArgParse(argparse.ArgumentParser):
config = 'foo'
def __init__(self):
super(TestArgParse, self).__init__()
class TestBase(BaseCommand):
def setUp(self):
super(TestBase, self).setUp()
self.cmd = lister.ValidationList(self.app, None)
self.base = base.Base()
@mock.patch('argparse.ArgumentParser.parse_known_args',
return_value=(TestArgParse(), ['foo-bar']))
@mock.patch('os.path.abspath', return_value='/foo')
@mock.patch('validations_libs.utils.load_config',
return_value=fakes.DEFAULT_CONFIG)
def test_config_args(self, mock_config, mock_path, mock_argv):
cmd_parser = self.cmd.get_parser('check_parser')
self.assertEqual(['foo_bar'], self.base._format_arg(cmd_parser))
@mock.patch('os.path.abspath', return_value='/foo')
@mock.patch('validations_libs.utils.load_config',
return_value=fakes.DEFAULT_CONFIG)
def test_argument_parser_cli_choice(self, mock_load, mock_path):
arglist = ['--validation-dir', 'foo', '--config', 'validation.cfg']
verifylist = [('validation_dir', 'foo')]
self._set_args(arglist)
cmd_parser = self.cmd.get_parser('check_parser')
parser = self.base.set_argument_parser(cmd_parser)
self.assertEqual(fakes.DEFAULT_CONFIG, self.base.config)
self.assertEqual(parser.get_default('validation_dir'), 'foo')
@mock.patch('os.path.abspath', return_value='/foo')
@mock.patch('validations_libs.utils.load_config',
return_value=fakes.DEFAULT_CONFIG)
def test_argument_parser_config_choice(self, mock_load, mock_path):
arglist = []
verifylist = []
self._set_args(arglist)
cmd_parser = self.cmd.get_parser('check_parser')
parser = self.base.set_argument_parser(cmd_parser)
self.assertEqual(fakes.DEFAULT_CONFIG, self.base.config)
self.assertEqual(parser.get_default('validation_dir'),
'/usr/share/ansible/validation-playbooks')
@mock.patch('os.path.abspath', return_value='/foo')
@mock.patch('validations_libs.utils.load_config',
return_value={})
def test_argument_parser_constant_choice(self, mock_load, mock_path):
arglist = []
verifylist = []
self._set_args(arglist)
cmd_parser = self.cmd.get_parser('check_parser')
parser = self.base.set_argument_parser(cmd_parser)
self.assertEqual({}, self.base.config)
self.assertEqual(parser.get_default('validation_dir'),
'/usr/share/ansible/validation-playbooks')

View File

@ -34,6 +34,7 @@ class TestListHistory(BaseCommand):
arglist = ['--validation-log-dir', '/foo/log/dir']
verifylist = [('validation_log_dir', '/foo/log/dir')]
self._set_args(arglist)
col = ('UUID', 'Validations', 'Status', 'Execution at', 'Duration')
values = [('008886df-d297-1eaa-2a74-000000000008',
'512e', 'PASSED',
@ -44,6 +45,33 @@ class TestListHistory(BaseCommand):
result = self.cmd.take_action(parsed_args)
self.assertEqual(result, (col, values))
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_history')
@mock.patch('validations_libs.utils.load_config',
return_value=fakes.DEFAULT_CONFIG)
def test_list_history_limit_with_config(self, mock_config, mock_history):
arglist = ['--validation-log-dir', '/foo/log/dir']
verifylist = [('validation_log_dir', '/foo/log/dir')]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertEqual(parsed_args.history_limit, 15)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_history')
@mock.patch('validations_libs.utils.load_config',
return_value=fakes.WRONG_HISTORY_CONFIG)
def test_list_history_limit_with_wrong_config(self, mock_config,
mock_history):
arglist = ['--validation-log-dir', '/foo/log/dir']
verifylist = [('validation_log_dir', '/foo/log/dir')]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertEqual(parsed_args.history_limit, 0)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(ValueError, self.cmd.take_action, parsed_args)
class TestGetHistory(BaseCommand):
@ -57,6 +85,7 @@ class TestGetHistory(BaseCommand):
def test_get_history(self, mock_logs):
arglist = ['123']
verifylist = [('uuid', '123')]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
@ -68,6 +97,7 @@ class TestGetHistory(BaseCommand):
arglist = ['123', '--validation-log-dir', '/foo/log/dir']
verifylist = [('uuid', '123'), ('validation_log_dir', '/foo/log/dir')]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
@ -78,5 +108,6 @@ class TestGetHistory(BaseCommand):
arglist = ['123', '--full']
verifylist = [('uuid', '123'), ('full', True)]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)

View File

@ -34,24 +34,25 @@ class TestRun(BaseCommand):
'run_validations',
return_value=None)
def test_run_command_return_none(self, mock_run):
arglist = ['--validation', 'foo']
args = self._set_args(['--validation', 'foo'])
verifylist = [('validation_name', ['foo'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
parsed_args = self.check_parser(self.cmd, args, verifylist)
self.assertRaises(RuntimeError, self.cmd.take_action, parsed_args)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
def test_run_command_success(self, mock_run):
arglist = ['--validation', 'foo']
args = self._set_args(['--validation', 'foo'])
verifylist = [('validation_name', ['foo'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
parsed_args = self.check_parser(self.cmd, args, verifylist)
self.cmd.take_action(parsed_args)
def test_run_command_exclusive_group(self):
arglist = ['--validation', 'foo', '--group', 'bar']
self._set_args(arglist)
verifylist = [('validation_name', ['foo'], 'group', 'bar')]
self.assertRaises(Exception, self.check_parser, self.cmd,
@ -64,8 +65,9 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
def test_run_command_extra_vars(self, mock_run, mock_user, mock_print,
mock_log_dir):
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_vars(self, mock_config, mock_run,
mock_user, mock_print, mock_log_dir):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
@ -80,13 +82,15 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'validation_config': {}
}
arglist = ['--validation', 'foo',
'--extra-vars', 'key=value']
verifylist = [('validation_name', ['foo']),
('extra_vars', {'key': 'value'})]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
@ -98,8 +102,10 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
def test_run_command_extra_vars_twice(self, mock_run, mock_user,
mock_print, mock_log_dir):
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_vars_twice(self, mock_config,
mock_run, mock_user, mock_print,
mock_log_dir):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
@ -114,14 +120,16 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'validation_config': {}
}
arglist = ['--validation', 'foo',
'--extra-vars', 'key=value1',
'--extra-vars', 'key=value2']
verifylist = [('validation_name', ['foo']),
('extra_vars', {'key': 'value2'})]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
@ -144,7 +152,9 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
def test_run_command_extra_vars_file(self, mock_run, mock_user, mock_open,
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_vars_file(self, mock_config, mock_run,
mock_user, mock_open,
mock_yaml, mock_log_dir):
run_called_args = {
@ -161,13 +171,15 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'validation_config': {}
}
arglist = ['--validation', 'foo',
'--extra-vars-file', '/foo/vars.yaml']
verifylist = [('validation_name', ['foo']),
('extra_vars_file', '/foo/vars.yaml')]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
@ -178,7 +190,9 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
def test_run_command_extra_env_vars(self, mock_run, mock_user, mock_log_dir):
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_env_vars(self, mock_config, mock_run,
mock_user, mock_log_dir):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
@ -193,13 +207,15 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'validation_config': {}
}
arglist = ['--validation', 'foo',
'--extra-env-vars', 'key=value']
verifylist = [('validation_name', ['foo']),
('extra_env_vars', {'key': 'value'})]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
@ -210,7 +226,9 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_env_vars_with_custom_callback(self,
mock_config,
mock_run,
mock_user,
mock_log_dir):
@ -229,13 +247,15 @@ class TestRun(BaseCommand):
'extra_env_vars': {'ANSIBLE_STDOUT_CALLBACK': 'default'},
'python_interpreter': sys.executable,
'quiet': False,
'ssh_user': 'doe'}
'ssh_user': 'doe',
'validation_config': {}
}
arglist = ['--validation', 'foo',
'--extra-env-vars', 'ANSIBLE_STDOUT_CALLBACK=default']
verifylist = [('validation_name', ['foo']),
('extra_env_vars', {'ANSIBLE_STDOUT_CALLBACK': 'default'})]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
@ -246,7 +266,10 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
def test_run_command_extra_env_vars_twice(self, mock_run, mock_user, mock_log_dir):
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_env_vars_twice(self, mock_config,
mock_run, mock_user,
mock_log_dir):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
@ -261,14 +284,16 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'validation_config': {}
}
arglist = ['--validation', 'foo',
'--extra-env-vars', 'key=value1',
'--extra-env-vars', 'key=value2']
verifylist = [('validation_name', ['foo']),
('extra_env_vars', {'key': 'value2'})]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
@ -279,7 +304,9 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_SUCCESS_RUN))
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_extra_env_vars_and_extra_vars(self,
mock_config,
mock_run,
mock_user,
mock_log_dir):
@ -297,7 +324,9 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'validation_config': {}
}
arglist = ['--validation', 'foo',
'--extra-vars', 'key=value',
@ -305,7 +334,7 @@ class TestRun(BaseCommand):
verifylist = [('validation_name', ['foo']),
('extra_vars', {'key': 'value'}),
('extra_env_vars', {'key2': 'value2'})]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)
@ -325,7 +354,9 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=copy.deepcopy(fakes.FAKE_FAILED_RUN))
def test_run_command_failed_validation(self, mock_run, mock_user, mock_log_dir):
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_failed_validation(self, mock_config,
mock_run, mock_user, mock_log_dir):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
@ -340,10 +371,14 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'validation_config': {}
}
arglist = ['--validation', 'foo']
verifylist = [('validation_name', ['foo'])]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(RuntimeError, self.cmd.take_action, parsed_args)
mock_run.assert_called_with(**run_called_args)
@ -353,7 +388,9 @@ class TestRun(BaseCommand):
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=[])
def test_run_command_no_validation(self, mock_run, mock_user):
@mock.patch('validations_libs.utils.load_config', return_value={})
def test_run_command_no_validation(self, mock_config, mock_run,
mock_user):
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
@ -367,10 +404,85 @@ class TestRun(BaseCommand):
'extra_env_vars': {'key2': 'value2'},
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe'}
'ssh_user': 'doe',
'validation_config': {}
}
arglist = ['--validation', 'foo']
verifylist = [('validation_name', ['foo'])]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(RuntimeError, self.cmd.take_action, parsed_args)
@mock.patch('validations_libs.constants.VALIDATIONS_LOG_BASEDIR')
@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_with_wrong_config(self, mock_run,
mock_user, mock_log_dir):
arglist = ['--validation', 'foo', '--config', 'wrong.cfg']
verifylist = [('validation_name', ['foo']),
('config', 'wrong.cfg')]
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
'group': [],
'category': [],
'product': [],
'extra_vars': None,
'validations_dir': '/usr/share/ansible/validation-playbooks',
'base_dir': '/usr/share/ansible',
'validation_name': ['foo'],
'extra_env_vars': None,
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {}
}
self._set_args(arglist)
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.constants.VALIDATIONS_LOG_BASEDIR')
@mock.patch('getpass.getuser',
return_value='doe')
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'run_validations',
return_value=fakes.FAKE_SUCCESS_RUN)
@mock.patch('os.path.exists', return_value=True)
def test_run_with_config(self, mock_exists,
mock_run, mock_user,
mock_log_dir):
arglist = ['--validation', 'foo', '--config', 'config.cfg']
verifylist = [('validation_name', ['foo']),
('config', 'config.cfg')]
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
'group': [],
'category': [],
'product': [],
'extra_vars': None,
'validations_dir': '/usr/share/ansible/validation-playbooks',
'base_dir': '/usr/share/ansible',
'validation_name': ['foo'],
'extra_env_vars': None,
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir,
'validation_config': {}
}
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)

View File

@ -33,6 +33,7 @@ class TestShow(BaseCommand):
def test_show_validations(self, mock_show):
arglist = ['foo']
verifylist = [('validation_name', 'foo')]
self._set_args(arglist)
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)

View File

@ -318,6 +318,23 @@ FAKE_FAILED_RUN = [{'Duration': '0:00:01.761',
FAKE_VALIDATIONS_PATH = '/usr/share/ansible/validation-playbooks'
DEFAULT_CONFIG = {'validation_dir': '/usr/share/ansible/validation-playbooks',