From dd33d75c5b9710b6da6d372458bb97e8eda2608d Mon Sep 17 00:00:00 2001 From: Pallab Pain Date: Tue, 30 May 2023 18:41:41 +0530 Subject: [PATCH] feat(auth): updates spinner and refactors existing code (cherry picked from commit faa774537f8e65d319df1c44caac1efa09896087) --- riocli/auth/__init__.py | 11 ++--- riocli/auth/login.py | 21 +++++---- riocli/auth/logout.py | 14 ++++-- riocli/auth/refresh_token.py | 19 +++++--- riocli/auth/staging.py | 18 ++++---- riocli/auth/status.py | 19 +++++--- riocli/auth/token.py | 19 +++++--- riocli/auth/util.py | 85 +++++++++++++++++++++++------------- 8 files changed, 132 insertions(+), 74 deletions(-) diff --git a/riocli/auth/__init__.py b/riocli/auth/__init__.py index 2e29ccce..7211d180 100644 --- a/riocli/auth/__init__.py +++ b/riocli/auth/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,13 +22,14 @@ from riocli.auth.status import status from riocli.auth.token import token from riocli.config import new_client +from riocli.constants import Colors @click.group( invoke_without_command=False, cls=HelpColorsGroup, - help_headers_color="yellow", - help_options_color="green", + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, ) def auth(): """ @@ -43,7 +44,7 @@ def get_rio_client() -> Client: auth.add_command(login) auth.add_command(logout) -auth.add_command(status) -auth.add_command(refresh_token) auth.add_command(token) +auth.add_command(status) auth.add_command(environment) +auth.add_command(refresh_token) diff --git a/riocli/auth/login.py b/riocli/auth/login.py index 227e8f50..dac8a7a5 100644 --- a/riocli/auth/login.py +++ b/riocli/auth/login.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,15 +20,17 @@ select_project, validate_token, ) +from riocli.constants import Colors, Symbols from riocli.utils.context import get_root_context -LOGIN_SUCCESS = click.style('Logged in successfully!', fg='green') +LOGIN_SUCCESS = click.style('{} Logged in successfully!'.format(Symbols.SUCCESS), fg=Colors.GREEN) @click.command( + 'login', cls=HelpColorsCommand, - help_headers_color='yellow', - help_options_color='green', + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, ) @click.option('--email', type=str, help='Email of the rapyuta.io account') @@ -88,9 +90,10 @@ def login( if not ctx.obj.exists or not interactive: ctx.obj.save() else: - click.secho("[Warning] rio already has a config file present", - fg='yellow') - click.confirm('Do you want to override the config?', abort=True) + click.confirm( + '{} Config already exists. Do you want to override' + ' the existing config?'.format(Symbols.WARNING), + abort=True) if not interactive: # When just the email and password are provided @@ -105,7 +108,7 @@ def login( if project and not organization: click.secho( 'Please specify an organization. See `rio auth login --help`', - fg='yellow') + fg=Colors.YELLOW) raise SystemExit(1) # When just the organization is provided, we save the @@ -114,7 +117,7 @@ def login( if organization and not project: select_organization(ctx.obj, organization=organization) click.secho("Your organization is set to '{}'".format( - ctx.obj.data['organization_name']), fg='green') + ctx.obj.data['organization_name']), fg=Colors.CYAN) ctx.obj.save() click.echo(LOGIN_SUCCESS) return diff --git a/riocli/auth/logout.py b/riocli/auth/logout.py index 7c71c351..ee47dd35 100644 --- a/riocli/auth/logout.py +++ b/riocli/auth/logout.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,9 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. import click +from click_help_colors import HelpColorsCommand +from riocli.constants import Colors, Symbols -@click.command() + +@click.command( + 'logout', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.pass_context def logout(ctx: click.Context): """ @@ -30,4 +38,4 @@ def logout(ctx: click.Context): ctx.obj.data.pop('project_id', None) ctx.obj.save() - click.secho('Logged out successfully!', fg='green') + click.secho('{} Logged out successfully.'.format(Symbols.SUCCESS), fg=Colors.GREEN) diff --git a/riocli/auth/refresh_token.py b/riocli/auth/refresh_token.py index f18141d2..fdd594b6 100644 --- a/riocli/auth/refresh_token.py +++ b/riocli/auth/refresh_token.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,24 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. import click +from click_help_colors import HelpColorsCommand from riocli.auth.util import get_token +from riocli.constants import Colors, Symbols from riocli.exceptions import LoggedOut -@click.command() +@click.command( + 'refresh-token', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.pass_context def refresh_token(ctx: click.Context): """ - Refreshes the authentication Token after it expires + Refreshes the authentication token after it expires """ - email = ctx.obj.data.get('email_id', None) password = ctx.obj.data.get('password', None) + if not ctx.obj.exists or not email or not password: raise LoggedOut ctx.obj.data['auth_token'] = get_token(email, password) ctx.obj.save() - click.echo('Token refreshed successfully!') + + click.secho('{} Token refreshed successfully!'.format(Symbols.SUCCESS), + fg=Colors.GREEN) diff --git a/riocli/auth/staging.py b/riocli/auth/staging.py index 74698fc2..c206fb24 100644 --- a/riocli/auth/staging.py +++ b/riocli/auth/staging.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ from riocli.auth.login import select_project, select_organization from riocli.auth.util import get_token from riocli.config import Configuration +from riocli.constants import Colors, Symbols from riocli.utils.context import get_root_context _STAGING_ENVIRONMENT_SUBDOMAIN = "apps.okd4v2.okd4beta.rapyuta.io" @@ -29,7 +30,6 @@ def environment(ctx: click.Context, name: str): """ Sets the Rapyuta.io environment to use (Internal use) """ - ctx = get_root_context(ctx) if name == 'ga': @@ -50,18 +50,16 @@ def environment(ctx: click.Context, name: str): organization = select_organization(ctx.obj) select_project(ctx.obj, organization=organization) - ctx.obj.save() - -def _validate_environment(name: str) -> bool: - valid = name in _NAMED_ENVIRONMENTS or name.startswith('pr') - if not valid: - click.secho('Invalid staging environment!', fg='red') - raise SystemExit(1) + ctx.obj.save() def _configure_environment(config: Configuration, name: str) -> None: - _validate_environment(name) + is_valid_env = name in _NAMED_ENVIRONMENTS or name.startswith('pr') + + if not is_valid_env: + click.secho('{} Invalid environment: {}'.format(Symbols.ERROR, name), fg=Colors.RED) + raise SystemExit(1) catalog = 'https://{}catalog.{}'.format(name, _STAGING_ENVIRONMENT_SUBDOMAIN) core = 'https://{}apiserver.{}'.format(name, _STAGING_ENVIRONMENT_SUBDOMAIN) diff --git a/riocli/auth/status.py b/riocli/auth/status.py index 2b1517b9..7844be4e 100644 --- a/riocli/auth/status.py +++ b/riocli/auth/status.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,18 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. import click +from click_help_colors import HelpColorsCommand +from riocli.constants import Colors -@click.command() + +@click.command( + 'status', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.pass_context def status(ctx: click.Context): """ - Shows the Login status of the CLI + Shows the login status of the CLI """ - if not ctx.obj.exists: - click.secho('Logged out 🔒', fg='red') + click.secho('🔒You are logged out', fg=Colors.YELLOW) raise SystemExit(1) if 'auth_token' in ctx.obj.data: - click.secho('Logged in 🎉', fg='green') + click.secho('🎉 You are logged in', fg=Colors.GREEN) diff --git a/riocli/auth/token.py b/riocli/auth/token.py index 53a12ddc..c76cb006 100644 --- a/riocli/auth/token.py +++ b/riocli/auth/token.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,13 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. import click +from click_help_colors import HelpColorsCommand from riocli.auth.util import get_token, TOKEN_LEVELS from riocli.config import Configuration +from riocli.constants import Colors from riocli.exceptions import LoggedOut -@click.command() +@click.command( + 'token', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.option("--email", default=None, help="Email of the Rapyuta.io account") @click.option("--password", default=None, hide_input=True, help="Password for the Rapyuta.io account") @@ -31,8 +38,9 @@ def token(email: str, password: str, level: int = 0): config = Configuration() if level not in TOKEN_LEVELS: - click.secho('Invalid token level. Valid levels are {0}'.format( - list(TOKEN_LEVELS.keys())), fg='red') + click.secho( + 'Invalid token level. Valid levels are {0}'.format( + list(TOKEN_LEVELS.keys())), fg=Colors.RED) raise SystemExit(1) if not email: @@ -44,4 +52,5 @@ def token(email: str, password: str, level: int = 0): if not config.exists or not email or not password: raise LoggedOut - click.echo(get_token(email, password, level)) + new_token = get_token(email, password) + click.echo(new_token) diff --git a/riocli/auth/util.py b/riocli/auth/util.py index ae007bfa..0abdd366 100644 --- a/riocli/auth/util.py +++ b/riocli/auth/util.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,18 +14,32 @@ import os import click -from click_spinner import spinner from rapyuta_io import Client from rapyuta_io.clients.rip_client import AuthTokenLevel from rapyuta_io.utils import UnauthorizedError from riocli.config import Configuration +from riocli.constants import Colors, Symbols from riocli.project.util import find_project_guid, find_organization_guid from riocli.utils.selector import show_selection +from riocli.utils.spinner import with_spinner + +TOKEN_LEVELS = { + 0: AuthTokenLevel.LOW, + 1: AuthTokenLevel.MED, + 2: AuthTokenLevel.HIGH +} -def select_organization(config: Configuration, - organization: str = None) -> str: +def select_organization( + config: Configuration, + organization: str = None, +) -> str: + """ + Launches the org selection prompt by listing all the orgs that the user is a part of. + + Sets the choice in the given configuration. + """ client = config.new_client(with_project=False) org_guid = None @@ -44,7 +58,7 @@ def select_organization(config: Configuration, org_guid = show_selection(org_map, "Select an organization:") if org_guid and org_guid not in org_map: - click.secho('invalid organization guid', fg='red') + click.secho('invalid organization guid', fg=Colors.RED) raise SystemExit(1) config.data['organization_id'] = org_guid @@ -53,10 +67,14 @@ def select_organization(config: Configuration, return org_guid -def select_project(config: Configuration, project: str = None, - organization: str = None) -> None: +def select_project( + config: Configuration, + project: str = None, + organization: str = None, +) -> None: """ Launches the project selection prompt by listing all the projects. + Sets the choice in the given configuration. """ client = config.new_v2_client(with_project=False) @@ -71,8 +89,8 @@ def select_project(config: Configuration, project: str = None, if len(projects) == 0: config.data['project_id'] = "" config.data['project_name'] = "" - click.secho("There are no projects in this organization", fg='black', - bg='white') + click.secho("There are no projects in this organization", + fg=Colors.BLACK, bg=Colors.WHITE) return # Sort projects based on their names for an easier selection @@ -93,17 +111,16 @@ def select_project(config: Configuration, project: str = None, config.data['project_name'], config.data['organization_name'], ) - click.secho(confirmation, fg='green') + click.secho(confirmation, fg=Colors.GREEN) -TOKEN_LEVELS = { - 0: AuthTokenLevel.LOW, - 1: AuthTokenLevel.MED, - 2: AuthTokenLevel.HIGH -} - - -def get_token(email: str, password: str, level: int = 1) -> str: +@with_spinner(text='Fetching token...') +def get_token( + email: str, + password: str, + level: int = 1, + spinner=None, +) -> str: """ Generates a new token using email and password. """ @@ -112,19 +129,21 @@ def get_token(email: str, password: str, level: int = 1) -> str: os.environ['RIO_CONFIG'] = config.filepath try: - with spinner(): - token = Client.get_auth_token( - email, password, TOKEN_LEVELS[level]) + token = Client.get_auth_token( + email, password, TOKEN_LEVELS[level]) return token - except UnauthorizedError: - click.secho("✘ incorrect email/password", fg='red') - raise SystemExit(1) + except UnauthorizedError as e: + spinner.text = click.style("incorrect email/password", fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) + raise SystemExit(1) from e except Exception as e: - click.secho(e, fg='red') - raise SystemExit(1) + click.style(str(e), fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) + raise SystemExit(1) from e -def validate_token(token: str) -> bool: +@with_spinner(text='Validating token...') +def validate_token(token: str, spinner=None) -> bool: """Validates an auth token.""" config = Configuration() if 'environment' in config.data: @@ -134,12 +153,16 @@ def validate_token(token: str) -> bool: try: user = client.get_authenticated_user() - click.secho('Token belongs to user {}'.format(user.email_id), - fg='cyan') + spinner.text = click.style( + 'Token belongs to user {}'.format(user.email_id), + fg=Colors.CYAN) + spinner.ok(Symbols.INFO) return True except UnauthorizedError: - click.secho("✘ incorrect auth token", fg='red') + spinner.text = click.style("incorrect auth token", fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) return False except Exception as e: - click.secho(e, fg='red') + spinner.text = click.style(str(e), fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) return False