Add validation config file mechanism
Change-Id: I05c54a43bc0a03878793cca3f51e23f4a8b63a23
This commit is contained in:
parent
eaf304f62e
commit
8faa3ea06b
|
@ -22,6 +22,9 @@ classifier =
|
|||
[files]
|
||||
packages = validations_libs
|
||||
|
||||
data_files =
|
||||
share/validation = validation.cfg
|
||||
|
||||
[compile_catalog]
|
||||
directory = validations-libs/locale
|
||||
domain = validations-lib
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
[default]
|
||||
validation_path = /usr/share/ansible/validations-playbook
|
||||
validation_log_path = validations
|
||||
|
||||
[callback]
|
||||
# Configuration for HTTP Server callback
|
||||
http_json_server = http://localhost
|
||||
http_json_port = 8080
|
||||
|
||||
# White list callback
|
||||
callback_whitelist = validation_json
|
|
@ -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,
|
||||
config=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,9 @@ class Ansible(object):
|
|||
``auto_silent`` or the default one
|
||||
``auto_legacy``)
|
||||
:type python_interpreter: ``string``
|
||||
:param config: A dictionary of configuration for Validation loaded
|
||||
from an validation.cfg file.
|
||||
:type config: ``dict``
|
||||
|
||||
:return: A ``tuple`` containing the the absolute path of the executed
|
||||
playbook, the return code and the status of the run
|
||||
|
@ -405,16 +422,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 +452,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 +470,16 @@ class Ansible(object):
|
|||
|
||||
if parallel_run:
|
||||
r_opts['directory_isolation_base_path'] = ansible_artifact_path
|
||||
|
||||
if config:
|
||||
if 'ansible_runner' in config.keys():
|
||||
r_opts.update(config['ansible_runner'])
|
||||
if 'ansible_environment' in config.keys():
|
||||
envvars.update(config['ansible_environment'])
|
||||
self._dump_validation_config(config, 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'] = \
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
#!/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 os
|
||||
|
||||
from cliff.command import Command
|
||||
from cliff.lister import Lister
|
||||
from cliff.show import ShowOne
|
||||
|
||||
from validations_libs import utils
|
||||
|
||||
|
||||
class BaseCommand(Command):
|
||||
"""Base Command client implementation class"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
"""Argument parser for base command"""
|
||||
parser = super(BaseCommand, self).get_parser(parser)
|
||||
parser.add_argument(
|
||||
'--config',
|
||||
dest='config',
|
||||
default=None,
|
||||
help=("Config file path for Validation.")
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
super(BaseCommand, self).take_action(parsed_args)
|
||||
config = {}
|
||||
if parsed_args.config:
|
||||
# The CLI args should be only in 'default' section
|
||||
config = utils.load_config(os.path.abspath(parsed_args.config))
|
||||
return config
|
||||
|
||||
|
||||
class BaseLister(Lister):
|
||||
"""Base Lister client implementation class"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
"""Argument parser for base lister"""
|
||||
parser = super(BaseLister, self).get_parser(parser)
|
||||
parser.add_argument(
|
||||
'--config',
|
||||
dest='config',
|
||||
default=None,
|
||||
help=("Config file path for Validation.")
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
super(BaseLister, self).take_action(parsed_args)
|
||||
config = {}
|
||||
if parsed_args.config:
|
||||
# The CLI args should be only in 'default' section
|
||||
config = utils.load_config(os.path.abspath(parsed_args.config))
|
||||
return config
|
||||
|
||||
|
||||
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)
|
||||
parser.add_argument(
|
||||
'--config',
|
||||
dest='config',
|
||||
default=None,
|
||||
help=("Config file path for Validation.")
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
super(BaseShow, self).take_action(parsed_args)
|
||||
config = {}
|
||||
if parsed_args.config:
|
||||
# The CLI args should be only in 'default' section
|
||||
config = utils.load_config(os.path.abspath(parsed_args.config))
|
||||
return config
|
|
@ -16,15 +16,14 @@
|
|||
|
||||
import json
|
||||
|
||||
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
|
||||
from validations_libs.cli.base import BaseCommand
|
||||
from validations_libs.cli.base import BaseLister
|
||||
|
||||
|
||||
class ListHistory(Lister):
|
||||
class ListHistory(BaseLister):
|
||||
"""Display Validations execution history"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
|
@ -50,27 +49,30 @@ class ListHistory(Lister):
|
|||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
config = super(ListHistory, self).take_action(parsed_args)
|
||||
default_cfg = config.get('default', {})
|
||||
|
||||
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))
|
||||
validation_log_dir = default_cfg.get('validation_log_dir',
|
||||
parsed_args.validation_log_dir)
|
||||
history_limit = default_cfg.get('history_limit',
|
||||
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(parsed_args.validation_log_dir)
|
||||
actions = ValidationActions(validation_log_dir)
|
||||
|
||||
return actions.show_history(
|
||||
validation_ids=parsed_args.validation,
|
||||
history_limit=parsed_args.history_limit)
|
||||
history_limit=history_limit)
|
||||
|
||||
|
||||
class GetHistory(Command):
|
||||
class GetHistory(BaseCommand):
|
||||
"""Display details about a Validation execution"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
|
@ -92,6 +94,12 @@ class GetHistory(Command):
|
|||
|
||||
def take_action(self, parsed_args):
|
||||
|
||||
config = super(GetHistory, self).take_action(parsed_args)
|
||||
default_cfg = config.get('default', {})
|
||||
|
||||
validation_log_dir = default_cfg.get('validation_log_dir',
|
||||
parsed_args.validation_log_dir)
|
||||
|
||||
self.app.LOG.debug(
|
||||
(
|
||||
"Obtaining information about the validation run {}\n"
|
||||
|
|
|
@ -14,14 +14,16 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cliff.lister import Lister
|
||||
import json
|
||||
import sys
|
||||
|
||||
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):
|
||||
"""Validation List client implementation class"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
|
@ -44,9 +46,12 @@ class ValidationList(Lister):
|
|||
|
||||
def take_action(self, parsed_args):
|
||||
"""Take validation action"""
|
||||
config = super(ValidationList, self).take_action(parsed_args)
|
||||
default_cfg = config.get('default', {})
|
||||
|
||||
validation_dir = default_cfg.get('validation_dir',
|
||||
parsed_args.validation_dir)
|
||||
group = parsed_args.group
|
||||
validation_dir = parsed_args.validation_dir
|
||||
|
||||
v_actions = ValidationActions(validation_path=validation_dir)
|
||||
return (v_actions.list_validations(group))
|
||||
|
|
|
@ -17,15 +17,14 @@
|
|||
import getpass
|
||||
import sys
|
||||
|
||||
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.base import BaseCommand
|
||||
from validations_libs.cli.parseractions import CommaListAction, KeyValueAction
|
||||
|
||||
|
||||
class Run(Command):
|
||||
class Run(BaseCommand):
|
||||
"""Validation Run client implementation class"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
|
@ -137,8 +136,16 @@ class Run(Command):
|
|||
|
||||
def take_action(self, parsed_args):
|
||||
"""Take validation action"""
|
||||
v_actions = ValidationActions(
|
||||
validation_path=parsed_args.validation_dir)
|
||||
config = super(Run, self).take_action(parsed_args)
|
||||
default_cfg = config.get('default', {})
|
||||
|
||||
validation_dir = default_cfg.get('validation_dir',
|
||||
parsed_args.validation_dir)
|
||||
ansible_base_dir = default_cfg.get('ansible_base_dir',
|
||||
parsed_args.ansible_base_dir)
|
||||
ssh_user = default_cfg.get('ssh_user', parsed_args.ssh_user)
|
||||
|
||||
v_actions = ValidationActions(validation_dir)
|
||||
|
||||
# Ansible execution should be quiet while using the validations_json
|
||||
# default callback and be verbose while passing ANSIBLE_SDTOUT_CALLBACK
|
||||
|
@ -163,14 +170,14 @@ class Run(Command):
|
|||
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,
|
||||
validations_dir=validation_dir,
|
||||
base_dir=ansible_base_dir,
|
||||
validation_name=parsed_args.validation_name,
|
||||
extra_env_vars=extra_env_vars,
|
||||
python_interpreter=parsed_args.python_interpreter,
|
||||
quiet=quiet_mode,
|
||||
ssh_user=parsed_args.ssh_user,
|
||||
)
|
||||
ssh_user=ssh_user,
|
||||
config=config)
|
||||
except RuntimeError as e:
|
||||
raise RuntimeError(e)
|
||||
|
||||
|
|
|
@ -14,14 +14,16 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cliff.show import ShowOne
|
||||
import json
|
||||
import sys
|
||||
|
||||
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
|
||||
|
||||
|
||||
class Show(ShowOne):
|
||||
class Show(BaseShow):
|
||||
"""Validation Show client implementation class"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
|
@ -40,7 +42,11 @@ class Show(ShowOne):
|
|||
def take_action(self, parsed_args):
|
||||
"""Take validation action"""
|
||||
# Get parameters:
|
||||
validation_dir = parsed_args.validation_dir
|
||||
config = super(Show, self).take_action(parsed_args)
|
||||
default_cfg = config.get('default', {})
|
||||
|
||||
validation_dir = default_cfg.get('validation_dir',
|
||||
parsed_args.validation_dir)
|
||||
validation_name = parsed_args.validation_name
|
||||
|
||||
v_actions = ValidationActions(validation_path=validation_dir)
|
||||
|
@ -50,7 +56,7 @@ class Show(ShowOne):
|
|||
return data.keys(), data.values()
|
||||
|
||||
|
||||
class ShowGroup(ShowOne):
|
||||
class ShowGroup(BaseShow):
|
||||
"""Validation Show group client implementation class"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
|
@ -69,14 +75,18 @@ class ShowGroup(ShowOne):
|
|||
def take_action(self, parsed_args):
|
||||
"""Take validation action"""
|
||||
# Get parameters:
|
||||
validation_dir = parsed_args.validation_dir
|
||||
config = super(ShowGroup, self).take_action(parsed_args)
|
||||
default_cfg = config.get('default', {})
|
||||
|
||||
validation_dir = default_cfg.get('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):
|
||||
class ShowParameter(BaseShow):
|
||||
"""Display Validations Parameters"""
|
||||
|
||||
def get_parser(self, parser):
|
||||
|
@ -136,7 +146,13 @@ class ShowParameter(ShowOne):
|
|||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
v_actions = ValidationActions(parsed_args.validation_dir)
|
||||
config = super(ShowParameter, self).take_action(parsed_args)
|
||||
default_cfg = config.get('default', {})
|
||||
|
||||
validation_dir = default_cfg.get('validation_dir',
|
||||
parsed_args.validation_dir)
|
||||
|
||||
v_actions = ValidationActions(validation_dir)
|
||||
params = v_actions.show_validations_parameters(
|
||||
parsed_args.validation_name,
|
||||
parsed_args.group,
|
||||
|
|
|
@ -30,3 +30,7 @@ VALIDATIONS_LOG_BASEDIR = ('/var/log/validations'
|
|||
|
||||
VALIDATION_ANSIBLE_ARTIFACT_PATH = '{}/artifacts/'.format(
|
||||
VALIDATIONS_LOG_BASEDIR)
|
||||
|
||||
ANSIBLE_RUNNER_CONFIG_PARAMETERS = ['verbosity', 'extravars', 'fact_cache',
|
||||
'fact_cache_type', 'inventory', 'playbook',
|
||||
'project_dir', 'quiet', 'rotate_artifacts']
|
||||
|
|
|
@ -76,7 +76,9 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': None,
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
arglist = ['--validation', 'foo',
|
||||
'--extra-vars', 'key=value']
|
||||
|
@ -106,7 +108,9 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': None,
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
arglist = ['--validation', 'foo',
|
||||
'--extra-vars', 'key=value1',
|
||||
|
@ -148,7 +152,9 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': None,
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
arglist = ['--validation', 'foo',
|
||||
'--extra-vars-file', '/foo/vars.yaml']
|
||||
|
@ -176,7 +182,9 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': {'key': 'value'},
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
arglist = ['--validation', 'foo',
|
||||
'--extra-env-vars', 'key=value']
|
||||
|
@ -206,7 +214,9 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': {'ANSIBLE_STDOUT_CALLBACK': 'default'},
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': False,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
arglist = ['--validation', 'foo',
|
||||
'--extra-env-vars', 'ANSIBLE_STDOUT_CALLBACK=default']
|
||||
|
@ -234,7 +244,9 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': {'key': 'value2'},
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
arglist = ['--validation', 'foo',
|
||||
'--extra-env-vars', 'key=value1',
|
||||
|
@ -264,7 +276,9 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': {'key2': 'value2'},
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
arglist = ['--validation', 'foo',
|
||||
'--extra-vars', 'key=value',
|
||||
|
@ -303,7 +317,9 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': {'key2': 'value2'},
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
arglist = ['--validation', 'foo']
|
||||
verifylist = [('validation_name', ['foo'])]
|
||||
|
@ -327,10 +343,72 @@ class TestRun(BaseCommand):
|
|||
'extra_env_vars': {'key2': 'value2'},
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe'}
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@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):
|
||||
arglist = ['--validation', 'foo', '--config', 'wrong.cfg']
|
||||
verifylist = [('validation_name', ['foo']),
|
||||
('config', 'wrong.cfg')]
|
||||
|
||||
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': None,
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
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)
|
||||
@mock.patch('os.path.exists', return_value=True)
|
||||
def test_run_with_config(self, mock_exists,
|
||||
mock_run, mock_user):
|
||||
arglist = ['--validation', 'foo', '--config', 'config.cfg']
|
||||
verifylist = [('validation_name', ['foo']),
|
||||
('config', 'config.cfg')]
|
||||
|
||||
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': None,
|
||||
'python_interpreter': sys.executable,
|
||||
'quiet': True,
|
||||
'ssh_user': 'doe',
|
||||
'config': {}
|
||||
}
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.cmd.take_action(parsed_args)
|
||||
mock_run.assert_called_with(**run_called_args)
|
||||
|
|
|
@ -297,6 +297,22 @@ 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',
|
||||
'validation_log_path': 'validations',
|
||||
'ansible_base_dir': '/usr/share/ansible/',
|
||||
'ssh_user': 'stack',
|
||||
'output_log': 'output.log',
|
||||
'history_limit': 15}
|
||||
|
||||
ANSIBLE_RUNNER_CONFIG = {'verbosity': 5, 'extravars': {'minimal_ram_gb': 2},
|
||||
'fact_cache_type': 'jsonfile',
|
||||
'project_dir':
|
||||
'/usr/share/ansible/validation-playbooks',
|
||||
'quiet': True, 'rotate_artifacts': 256}
|
||||
|
||||
ANSIBLE_ENVIRONNMENT_CONFIG = {'ANSIBLE_TIMEOUT': '30',
|
||||
'ANSIBLE_HOST_KEY_CHECKING': 'False'}
|
||||
|
||||
|
||||
def fake_ansible_runner_run_return(status='successful', rc=0):
|
||||
return status, rc
|
||||
|
|
|
@ -223,3 +223,40 @@ class TestUtils(TestCase):
|
|||
self.assertRaises(TypeError,
|
||||
utils.convert_data,
|
||||
data=data_dict)
|
||||
|
||||
def test_eval_types_str(self):
|
||||
self.assertIsInstance(utils._eval_types('/usr'), str)
|
||||
|
||||
def test_eval_types_bool(self):
|
||||
self.assertIsInstance(utils._eval_types('True'), bool)
|
||||
|
||||
def test_eval_types_int(self):
|
||||
self.assertIsInstance(utils._eval_types('15'), int)
|
||||
|
||||
def test_eval_types_dict(self):
|
||||
self.assertIsInstance(utils._eval_types('{}'), dict)
|
||||
|
||||
@mock.patch('os.path.exists', return_value=True)
|
||||
@mock.patch('configparser.ConfigParser.sections',
|
||||
return_value=['default'])
|
||||
def test_load_config(self, mock_config, mock_exists):
|
||||
results = utils.load_config('foo.cfg')
|
||||
self.assertEqual(results, {})
|
||||
|
||||
def test_default_load_config(self):
|
||||
results = utils.load_config('config_demo.cfg')
|
||||
self.assertEqual(results['default'], fakes.DEFAULT_CONFIG)
|
||||
|
||||
def test_ansible_runner_load_config(self):
|
||||
results = utils.load_config('config_demo.cfg')
|
||||
self.assertEqual(results['ansible_runner'],
|
||||
fakes.ANSIBLE_RUNNER_CONFIG)
|
||||
|
||||
def test_ansible_environment_config_load_config(self):
|
||||
results = utils.load_config('config_demo.cfg')
|
||||
self.assertEqual(
|
||||
results['ansible_environment']['ANSIBLE_HOST_KEY_CHECKING'],
|
||||
fakes.ANSIBLE_ENVIRONNMENT_CONFIG['ANSIBLE_HOST_KEY_CHECKING'])
|
||||
self.assertEqual(
|
||||
results['ansible_environment']['ANSIBLE_TIMEOUT'],
|
||||
fakes.ANSIBLE_ENVIRONNMENT_CONFIG['ANSIBLE_TIMEOUT'])
|
||||
|
|
|
@ -88,12 +88,13 @@ class TestValidationActions(TestCase):
|
|||
'limit_hosts': '!cloud1',
|
||||
'ansible_artifact_path': '/tmp/',
|
||||
'extra_env_variables': None,
|
||||
'ansible_cfg': None,
|
||||
'ansible_cfg_file': None,
|
||||
'gathering_policy': 'explicit',
|
||||
'log_path': None,
|
||||
'run_async': False,
|
||||
'python_interpreter': None,
|
||||
'ssh_user': None
|
||||
'ssh_user': None,
|
||||
'config': None
|
||||
}
|
||||
|
||||
playbook = ['fake.yaml']
|
||||
|
@ -133,13 +134,14 @@ class TestValidationActions(TestCase):
|
|||
'extra_vars': None,
|
||||
'limit_hosts': '!cloud1,cloud,!cloud2',
|
||||
'extra_env_variables': None,
|
||||
'ansible_cfg': None,
|
||||
'ansible_cfg_file': None,
|
||||
'gathering_policy': 'explicit',
|
||||
'ansible_artifact_path': '/tmp/',
|
||||
'log_path': None,
|
||||
'run_async': False,
|
||||
'python_interpreter': None,
|
||||
'ssh_user': None
|
||||
'ssh_user': None,
|
||||
'config': None
|
||||
}
|
||||
|
||||
playbook = ['fake.yaml']
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
import ast
|
||||
import configparser
|
||||
import datetime
|
||||
import glob
|
||||
import logging
|
||||
|
@ -326,3 +328,60 @@ def convert_data(data=''):
|
|||
raise TypeError("The input data should be either a List or a String")
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
def _eval_types(value):
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return ast.literal_eval(value)
|
||||
except (SyntaxError, NameError):
|
||||
pass
|
||||
try:
|
||||
return str(value)
|
||||
except ValueError:
|
||||
msg = ("Can not eval or type not supported for value: {},").format(
|
||||
value)
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def load_config(config):
|
||||
"""Load Config File from CLI"""
|
||||
if not os.path.exists(config):
|
||||
msg = ("Config file {} could not be found, ignoring...").format(config)
|
||||
LOG.warning(msg)
|
||||
return {}
|
||||
parser = configparser.ConfigParser()
|
||||
parser.optionxform = str
|
||||
parser.read(config)
|
||||
data = {}
|
||||
try:
|
||||
for section in parser.sections():
|
||||
for keys, values in parser.items(section):
|
||||
if section not in data:
|
||||
# Init section in dictionary
|
||||
data[section] = {}
|
||||
if section == 'ansible_environment':
|
||||
# for Ansible environment variables we dont want to cast
|
||||
# types, each values should a type String.
|
||||
data[section][keys] = values
|
||||
elif section == 'ansible_runner' and \
|
||||
keys not in constants.ANSIBLE_RUNNER_CONFIG_PARAMETERS:
|
||||
# for Ansible runner parameters, we select only a set
|
||||
# of parameters which will be passed as **kwargs in the
|
||||
# runner, so we have to ignore all the others.
|
||||
continue
|
||||
msg = ("Incompatible key found for ansible_runner section {}, "
|
||||
"ignoring {} ...").format(section, keys)
|
||||
LOG.warning(msg)
|
||||
else:
|
||||
data[section][keys] = _eval_types(values)
|
||||
except configparser.NoSectionError:
|
||||
msg = ("Wrong format for the config file {}, "
|
||||
"section {} can not be found, ignoring...").format(config,
|
||||
section)
|
||||
LOG.warning(msg)
|
||||
return {}
|
||||
return data
|
||||
|
|
|
@ -232,7 +232,7 @@ class ValidationActions(object):
|
|||
skip_list=None,
|
||||
callback_whitelist=None,
|
||||
output_callback='validation_stdout',
|
||||
ssh_user=None):
|
||||
ssh_user=None, config=None):
|
||||
"""Run one or multiple validations by name(s) or by group(s)
|
||||
|
||||
:param validation_name: A list of validation names
|
||||
|
@ -294,6 +294,9 @@ class ValidationActions(object):
|
|||
:rtype: ``list``
|
||||
:param ssh_user: Ssh user for Ansible remote connection
|
||||
:type ssh_user: ``string``
|
||||
:param config: A dictionary of configuration for Validation loaded from
|
||||
an validation.cfg file.
|
||||
:type config: ``dict``
|
||||
|
||||
:Example:
|
||||
|
||||
|
@ -378,13 +381,14 @@ class ValidationActions(object):
|
|||
extra_vars=extra_vars,
|
||||
limit_hosts=_hosts,
|
||||
extra_env_variables=extra_env_vars,
|
||||
ansible_cfg=ansible_cfg,
|
||||
ansible_cfg_file=ansible_cfg,
|
||||
gathering_policy='explicit',
|
||||
ansible_artifact_path=artifacts_dir,
|
||||
log_path=log_path,
|
||||
run_async=run_async,
|
||||
python_interpreter=python_interpreter,
|
||||
ssh_user=ssh_user)
|
||||
ssh_user=ssh_user,
|
||||
config=config)
|
||||
results.append({'playbook': _playbook,
|
||||
'rc_code': _rc,
|
||||
'status': _status,
|
||||
|
|
Loading…
Reference in New Issue