diff --git a/validations_libs/cli/common.py b/validations_libs/cli/common.py index 5701729c..889c6ba9 100644 --- a/validations_libs/cli/common.py +++ b/validations_libs/cli/common.py @@ -18,6 +18,9 @@ import json import logging from prettytable import PrettyTable import re +import sys +import time +import threading import yaml try: @@ -110,3 +113,38 @@ def read_extra_vars_file(extra_vars_file): "The extra_vars file must be properly formatted YAML/JSON." "Details: {}.").format(error) raise RuntimeError(error_msg) + + +class Spinner(object): + """Animated spinner to indicate activity during processing""" + busy = False + delay = 0.1 + + @staticmethod + def spinning_cursor(): + while 1: + for cursor in '|/-\\': + yield cursor + + def __init__(self, delay=None): + self.spinner_generator = self.spinning_cursor() + if delay and float(delay): + self.delay = delay + + def spinner_task(self): + while self.busy: + sys.stdout.write(next(self.spinner_generator)) + sys.stdout.flush() + time.sleep(self.delay) + sys.stdout.write('\b') + sys.stdout.flush() + + def __enter__(self): + self.busy = True + threading.Thread(target=self.spinner_task).start() + + def __exit__(self, exception, value, tb): + self.busy = False + time.sleep(self.delay) + if exception is not None: + return False diff --git a/validations_libs/tests/test_validation_actions.py b/validations_libs/tests/test_validation_actions.py index 49604d73..22d9f801 100644 --- a/validations_libs/tests/test_validation_actions.py +++ b/validations_libs/tests/test_validation_actions.py @@ -273,6 +273,58 @@ class TestValidationActions(TestCase): validations_dir='/tmp/foo') self.assertEqual(run_return, expected_run_return) + @mock.patch('validations_libs.ansible.Ansible._playbook_check', + side_effect=RuntimeError) + @mock.patch('validations_libs.utils.os.makedirs') + @mock.patch('validations_libs.utils.os.access', return_value=True) + @mock.patch('validations_libs.utils.os.path.exists', return_value=True) + @mock.patch('validations_libs.utils.parse_all_validations_on_disk') + def test_spinner_exception_failure_condition(self, mock_validation_dir, + mock_exists, mock_access, + mock_makedirs, + mock_playbook_check): + + mock_validation_dir.return_value = [{ + 'description': 'My Validation One Description', + 'groups': ['prep', 'pre-deployment'], + 'id': 'foo', + 'name': 'My Validition One Name', + 'parameters': {}}] + playbook = ['fake.yaml'] + inventory = 'tmp/inventory.yaml' + + run = ValidationActions() + + self.assertRaises(RuntimeError, run.run_validations, playbook, + inventory, group=fakes.GROUPS_LIST, + validations_dir='/tmp/foo') + + @mock.patch('validations_libs.ansible.Ansible._playbook_check', + side_effect=RuntimeError) + @mock.patch('validations_libs.utils.os.makedirs') + @mock.patch('validations_libs.utils.os.access', return_value=True) + @mock.patch('validations_libs.utils.os.path.exists', return_value=True) + @mock.patch('validations_libs.utils.parse_all_validations_on_disk') + @mock.patch('sys.__stdin__.isatty', return_value=True) + def test_spinner_forced_run(self, mock_stdin_isatty, mock_validation_dir, + mock_exists, mock_access, mock_makedirs, + mock_playbook_check): + + mock_validation_dir.return_value = [{ + 'description': 'My Validation One Description', + 'groups': ['prep', 'pre-deployment'], + 'id': 'foo', + 'name': 'My Validition One Name', + 'parameters': {}}] + playbook = ['fake.yaml'] + inventory = 'tmp/inventory.yaml' + + run = ValidationActions() + + self.assertRaises(RuntimeError, run.run_validations, playbook, + inventory, group=fakes.GROUPS_LIST, + validations_dir='/tmp/foo') + @mock.patch('validations_libs.utils.get_validations_playbook', return_value=[]) def test_validation_run_no_validation(self, mock_get_val): diff --git a/validations_libs/validation_actions.py b/validations_libs/validation_actions.py index c13e70ee..7f3836d1 100644 --- a/validations_libs/validation_actions.py +++ b/validations_libs/validation_actions.py @@ -14,11 +14,13 @@ # import logging import os +import sys import json import yaml from validations_libs.ansible import Ansible as v_ansible from validations_libs.group import Group +from validations_libs.cli.common import Spinner from validations_libs.validation_logs import ValidationLogs, ValidationLog from validations_libs import constants from validations_libs import utils as v_utils @@ -374,26 +376,49 @@ class ValidationActions(object): validation_uuid, artifacts_dir = v_utils.create_artifacts_dir( log_path=log_path, prefix=os.path.basename(playbook)) run_ansible = v_ansible(validation_uuid) - _playbook, _rc, _status = run_ansible.run( - workdir=artifacts_dir, - playbook=playbook, - base_dir=base_dir, - playbook_dir=validations_dir, - parallel_run=True, - inventory=inventory, - output_callback=output_callback, - callback_whitelist=callback_whitelist, - quiet=quiet, - extra_vars=extra_vars, - limit_hosts=_hosts, - extra_env_variables=extra_env_vars, - ansible_cfg=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) + if sys.__stdin__.isatty(): + with Spinner(): + _playbook, _rc, _status = run_ansible.run( + workdir=artifacts_dir, + playbook=playbook, + base_dir=base_dir, + playbook_dir=validations_dir, + parallel_run=True, + inventory=inventory, + output_callback=output_callback, + callback_whitelist=callback_whitelist, + quiet=quiet, + extra_vars=extra_vars, + limit_hosts=_hosts, + extra_env_variables=extra_env_vars, + ansible_cfg=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) + else: + _playbook, _rc, _status = run_ansible.run( + workdir=artifacts_dir, + playbook=playbook, + base_dir=base_dir, + playbook_dir=validations_dir, + parallel_run=True, + inventory=inventory, + output_callback=output_callback, + callback_whitelist=callback_whitelist, + quiet=quiet, + extra_vars=extra_vars, + limit_hosts=_hosts, + extra_env_variables=extra_env_vars, + ansible_cfg=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) results.append({'playbook': _playbook, 'rc_code': _rc, 'status': _status,