add spinner to highlight progress

Implementing a Spinner class and utilizing it around a run action.  This
will add a moving icon that animates in place until output is reached,
letting the operator know that things are happening.

The interactive TTY test is included to ensure that during some unknown
usage such as logging to disk this feature doesn't spew unnecessary
clutter.

Change-Id: Ieef256a5c12b238008e9250c0ee182d80a2b6dfb
This commit is contained in:
David J Peacock 2021-06-09 10:49:21 -04:00
parent 85947ee8e0
commit e11b458946
3 changed files with 135 additions and 20 deletions

View File

@ -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

View File

@ -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):

View File

@ -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,