Skip to content

Commit

Permalink
feat(apply): adds yaspin spinner to apply family of commands
Browse files Browse the repository at this point in the history
  • Loading branch information
pallabpain authored and ankitrgadiya committed Jul 21, 2023
1 parent 3ec3441 commit fd5b83e
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 87 deletions.
37 changes: 24 additions & 13 deletions riocli/apply/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
from riocli.apply.parse import Applier
from riocli.apply.template import template
from riocli.apply.util import process_files_values_secrets
from riocli.constants import Colors


@click.command(
'apply',
cls=HelpColorsCommand,
help_headers_color='yellow',
help_options_color='green',
help_headers_color=Colors.YELLOW,
help_options_color=Colors.GREEN,
)
@click.option('--dryrun', '-d', is_flag=True, default=False,
help='dry run the yaml files without applying any change')
Expand Down Expand Up @@ -70,12 +71,12 @@ def apply(
files, values, secrets)

if len(glob_files) == 0:
click.secho('no files specified', fg='red')
click.secho('No files specified', fg=Colors.RED)
raise SystemExit(1)

click.secho("----- Files Processed ----", fg="yellow")
click.secho("----- Files Processed ----", fg=Colors.YELLOW)
for file in glob_files:
click.secho(file, fg="yellow")
click.secho(file, fg=Colors.YELLOW)

rc = Applier(glob_files, abs_values, abs_secrets)
rc.parse_dependencies()
Expand All @@ -98,26 +99,36 @@ def apply(
@click.command(
'delete',
cls=HelpColorsCommand,
help_headers_color='yellow',
help_options_color='green',
help_headers_color=Colors.YELLOW,
help_options_color=Colors.GREEN,
)
@click.option('--dryrun', '-d', is_flag=True, default=False, help='dry run the yaml files without applying any change')
@click.option('--dryrun', '-d', is_flag=True, default=False,
help='dry run the yaml files without applying any change')
@click.option('--values', '-v',
help="path to values yaml file. key/values specified in the values file can be used as variables in template yamls")
help="Path to values yaml file. key/values specified in the"
" values file can be used as variables in template YAMLs")
@click.option('--secrets', '-s',
help="secret files are sops encoded value files. rio-cli expects sops to be authorized for decoding files on this computer")
@click.option('-f', '--force', '--silent', 'silent', is_flag=True, type=click.BOOL, default=False,
help="Secret files are sops encoded value files. riocli expects "
"sops to be authorized for decoding files on this computer")
@click.option('-f', '--force', '--silent', 'silent', is_flag=True,
type=click.BOOL, default=False,
help="Skip confirmation")
@click.argument('files', nargs=-1)
def delete(values: str, secrets: str, files: Iterable[str], dryrun: bool = False, silent: bool = False) -> None:
def delete(
values: str,
secrets: str,
files: Iterable[str],
dryrun: bool = False,
silent: bool = False
) -> None:
"""
Removes resources defined in the manifest
"""
glob_files, abs_values, abs_secrets = process_files_values_secrets(
files, values, secrets)

if len(glob_files) == 0:
click.secho('no files specified', fg='red')
click.secho('no files specified', fg=Colors.RED)
raise SystemExit(1)

rc = Applier(glob_files, abs_values, abs_secrets)
Expand Down
9 changes: 6 additions & 3 deletions riocli/apply/explain.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
import click
from click_help_colors import HelpColorsCommand

from riocli.constants import Colors, Symbols


@click.command(
'explain',
cls=HelpColorsCommand,
help_headers_color='yellow',
help_options_color='green',
help_headers_color=Colors.YELLOW,
help_options_color=Colors.GREEN,
help='Generates a sample resource manifest for the given type'
)
@click.option('--templates', help='Alternate root for templates',
Expand All @@ -39,5 +41,6 @@ def explain(resource: str, templates: str = None) -> None:
click.echo_via_pager(f.readlines())
raise SystemExit(0)

click.secho("[Err] Resource \"{}\" not found".format(resource), fg='red')
click.secho('{} Resource "{}" not found'.format(Symbols.ERROR, resource),
fg=Colors.RED)
raise SystemExit(1)
83 changes: 55 additions & 28 deletions riocli/apply/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@

from riocli.apply.resolver import ResolverCache
from riocli.config import Configuration
from riocli.utils import dump_all_yaml
from riocli.utils import print_separator
from riocli.utils import run_bash
from riocli.constants import Colors, Symbols
from riocli.utils import dump_all_yaml, print_separator, run_bash
from riocli.utils.mermaid import mermaid_link, mermaid_safe
from riocli.utils.spinner import with_spinner


class Applier(object):
Expand Down Expand Up @@ -79,13 +79,22 @@ def __init__(self, files: typing.List, values, secrets):
def order(self):
return self.graph.static_order()

@with_spinner(text='Applying...', timer=True)
def apply(self, *args, **kwargs):
spinner = kwargs.get('spinner')
kwargs['workers'] = int(kwargs.get('workers')
or self.DEFAULT_MAX_WORKERS)
apply_func = self.apply_async
if kwargs['workers'] == 1:
return self.apply_sync(*args, **kwargs)
apply_func = self.apply_sync

return self.apply_async(*args, **kwargs)
try:
apply_func(*args, **kwargs)
spinner.text = 'Apply successful.'
spinner.green.ok(Symbols.SUCCESS)
except Exception as e:
spinner.text = 'Apply failed. Error: {}'.format(e)
spinner.red.fail(Symbols.ERROR)

def apply_async(self, *args, **kwargs):
workers = int(kwargs.get('workers') or self.DEFAULT_MAX_WORKERS)
Expand All @@ -95,12 +104,14 @@ def apply_async(self, *args, **kwargs):
def worker():
while True:
o = task_queue.get()
if o in self.resolved_objects and 'manifest' in self.resolved_objects[o]:
if o in self.resolved_objects and 'manifest' in \
self.resolved_objects[o]:
try:
self._apply_manifest(o, *args, **kwargs)
except Exception as ex:
click.secho(
'[Err] Object "{}" apply failed. Apply will not progress further.'.format(o, str(ex)))
'[Err] Object "{}" apply failed. Apply will not progress further.'.format(
o, str(ex)))
done_queue.put(ex)
continue

Expand Down Expand Up @@ -129,17 +140,26 @@ def apply_sync(self, *args, **kwargs):
self.graph.prepare()
while self.graph.is_active():
for obj in self.graph.get_ready():
if obj in self.resolved_objects and 'manifest' in self.resolved_objects[obj]:
if (obj in self.resolved_objects and
'manifest' in self.resolved_objects[obj]):
self._apply_manifest(obj, *args, **kwargs)
self.graph.done(obj)

@with_spinner(text='Deleting...', timer=True)
def delete(self, *args, **kwargs):
spinner = kwargs.get('spinner')
delete_order = list(self.graph.static_order())
delete_order.reverse()
for obj in delete_order:
if obj in self.resolved_objects and 'manifest' in \
self.resolved_objects[obj]:
self._delete_manifest(obj, *args, **kwargs)
try:
for obj in delete_order:
if (obj in self.resolved_objects and
'manifest' in self.resolved_objects[obj]):
self._delete_manifest(obj, *args, **kwargs)
spinner.text = 'Delete successful.'
spinner.green.ok(Symbols.SUCCESS)
except Exception as e:
spinner.text = 'Delete failed. Error: {}'.format(e)
spinner.red.fail(Symbols.ERROR)

def print_resolved_manifests(self):
manifests = [o for _, o in self.objects.items()]
Expand All @@ -149,7 +169,7 @@ def parse_dependencies(
self,
check_missing=True,
delete=False,
template=False,
template=False
):
number_of_objects = 0
for f, data in self.files.items():
Expand All @@ -163,21 +183,22 @@ def parse_dependencies(
total_time = 0

for node in copy.deepcopy(self.graph).static_order():

action = 'CREATE' if not self.resolved_objects[node]['src'] == 'remote' else 'UPDATE'
if delete:
action = 'UPDATE'
if not self.resolved_objects[node]['src'] == 'remote':
action = 'CREATE'
elif delete:
action = 'DELETE'
kind = node.split(":")[0]
expected_time = round(
self.EXPECTED_TIME.get(kind.lower(), 5) / 60, 2)
total_time = total_time + expected_time
total_time += expected_time
resource_list.append([node, action, expected_time])

if not template:
self._display_context(
total_time=total_time,
total_objects=number_of_objects,
resource_list=resource_list,
resource_list=resource_list
)

if check_missing:
Expand All @@ -187,9 +208,12 @@ def parse_dependencies(
missing_resources.append(key)

if missing_resources:
click.secho("missing resources found in yaml. " +
"Please ensure the following are either available in your yaml" +
"or created on the server. {}".format(set(missing_resources)), fg="red")
click.secho(
"Missing resources found in yaml. Please ensure the "
"following are either available in your YAML or created"
" on the server. {}".format(
set(missing_resources)), fg=Colors.RED)

raise SystemExit(1)

# Manifest Operations via base.py
Expand Down Expand Up @@ -224,7 +248,7 @@ def _register_object(self, data):
self.objects[key] = data
self.resolved_objects[key] = {'src': 'local', 'manifest': data}
except KeyError:
click.secho("Key error {}".format(data), fg='red')
click.secho("Key error {}".format(data), fg=Colors.RED)
return

def _load_file_content(self, file_name, is_value=False, is_secret=False):
Expand Down Expand Up @@ -285,7 +309,8 @@ def _add_graph_node(self, key):
def _add_graph_edge(self, dependent_key, key):
self.graph.add(dependent_key, key)
self.diagram.append('\t{}[{}] --> {}[{}] '.format(mermaid_safe(key),
key, mermaid_safe(dependent_key), dependent_key))
key, mermaid_safe(
dependent_key), dependent_key))

# Dependency Resolution
def _parse_dependency(self, dependent_key, model):
Expand Down Expand Up @@ -328,7 +353,8 @@ def _resolve_dependency(self, dependent_key, dependency):
dependent_key, obj_guid, dependency, obj)

if (name_or_guid == obj_name) and (
'version' in dependency and obj['packageVersion'] == dependency.get('version')):
'version' in dependency and obj[
'packageVersion'] == dependency.get('version')):
self._add_remote_object_to_resolve_tree(
dependent_key, obj_guid, dependency, obj)

Expand All @@ -348,7 +374,8 @@ def _resolve_dependency(self, dependent_key, dependency):
if key not in self.resolved_objects:
self.resolved_objects[key] = {'src': 'missing'}

def _add_remote_object_to_resolve_tree(self, dependent_key, guid, dependency, obj):
def _add_remote_object_to_resolve_tree(self, dependent_key, guid,
dependency, obj):
kind = dependency.get('kind')
name_or_guid = dependency.get('nameOrGUID')
key = '{}:{}'.format(kind, name_or_guid)
Expand Down Expand Up @@ -385,8 +412,8 @@ def _display_context(
total_objects: int,
resource_list: typing.List
) -> None:
# Display context
headers = [click.style('Resource Context', bold=True, fg='yellow')]
headers = [
click.style('Resource Context', bold=True, fg=Colors.YELLOW)]
context = [
['Expected Time (mins)', round(total_time, 2)],
['Files', len(self.files)],
Expand All @@ -398,7 +425,7 @@ def _display_context(
# Display Resource Inventory
headers = []
for header in ['Resource', 'Action', 'Expected Time (mins)']:
headers.append(click.style(header, fg='yellow', bold=True))
headers.append(click.style(header, fg=Colors.YELLOW, bold=True))

print_separator()
click.echo(tabulate(resource_list, headers=headers,
Expand Down
13 changes: 8 additions & 5 deletions riocli/apply/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@

from riocli.apply.parse import Applier
from riocli.apply.util import process_files_values_secrets
from riocli.constants import Colors


@click.command(
'template',
cls=HelpColorsCommand,
help_headers_color='yellow',
help_options_color='green',
help_headers_color=Colors.YELLOW,
help_options_color=Colors.GREEN,
)
@click.option('--values', '-v',
help="path to values yaml file. key/values specified in the values file can be used as variables in template yamls")
help='Path to values yaml file. key/values specified in the '
'values file can be used as variables in template YAMLs')
@click.option('--secrets', '-s',
help="secret files are sops encoded value files. rio-cli expects sops to be authorized for decoding files on this computer")
help='Secret files are sops encoded value files. riocli '
'expects sops to be authorized for decoding files on this computer')
@click.argument('files', nargs=-1)
def template(values: str, secrets: str, files: Iterable[str]) -> None:
"""
Expand All @@ -40,7 +43,7 @@ def template(values: str, secrets: str, files: Iterable[str]) -> None:
files, values, secrets)

if len(glob_files) == 0:
click.secho('no files specified', fg='red')
click.secho('No files specified', fg=Colors.RED)
raise SystemExit(1)

rc = Applier(glob_files, abs_values, abs_secrets)
Expand Down
Loading

0 comments on commit fd5b83e

Please sign in to comment.