diff --git a/riocli/deployment/__init__.py b/riocli/deployment/__init__.py index ffa1f7c2..be06553d 100644 --- a/riocli/deployment/__init__.py +++ b/riocli/deployment/__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. @@ -14,22 +14,24 @@ import click from click_help_colors import HelpColorsGroup +from riocli.constants import Colors from riocli.deployment.delete import delete_deployment +from riocli.deployment.execute import execute_command +from riocli.deployment.execute import execute_command from riocli.deployment.inspect import inspect_deployment from riocli.deployment.list import list_deployments from riocli.deployment.logs import deployment_logs from riocli.deployment.ssh import ssh_init, ssh_deployment from riocli.deployment.status import status -from riocli.deployment.wait import wait_for_deployment -from riocli.deployment.execute import execute_command from riocli.deployment.update import update_deployment +from riocli.deployment.wait import wait_for_deployment @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 deployment(): """ diff --git a/riocli/deployment/delete.py b/riocli/deployment/delete.py index 55759f2e..37839961 100644 --- a/riocli/deployment/delete.py +++ b/riocli/deployment/delete.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,30 +12,49 @@ # See the License for the specific language governing permissions and # limitations under the License. import click -from click_spinner import spinner +from click_help_colors import HelpColorsCommand from riocli.config import new_client +from riocli.constants import Colors, Symbols from riocli.deployment.util import name_to_guid +from riocli.utils.spinner import with_spinner -@click.command('delete') +@click.command( + 'delete', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.option('--force', '-f', '--silent', is_flag=True, default=False, help='Skip confirmation') @click.argument('deployment-name', type=str) @name_to_guid -def delete_deployment(force: bool, deployment_name: str, deployment_guid: str) -> None: +@with_spinner(text="Deleting deployment...") +def delete_deployment( + force: bool, + deployment_name: str, + deployment_guid: str, + spinner=None, +) -> None: """ - Delete the deployment from the Platform + Deletes a deployment """ - if not force: - click.confirm('Deleting {} ({}) deployment'.format(deployment_name, deployment_guid), abort=True) + with spinner.hidden(): + if not force: + click.confirm( + 'Deleting {} ({}) deployment'.format( + deployment_name, deployment_guid), abort=True) try: - with spinner(): - client = new_client() - deployment = client.get_deployment(deployment_guid) - deployment.deprovision() - click.secho('Deployment deleted successfully!', fg='green') + client = new_client() + deployment = client.get_deployment(deployment_guid) + deployment.deprovision() + spinner.text = click.style( + 'Deployment deleted successfully.', fg=Colors.GREEN) + spinner.green.ok(Symbols.SUCCESS) except Exception as e: - click.secho(str(e), fg='red') + spinner.text = click.style( + 'Failed to delete deployment: {}'.format(e), Colors.RED) + spinner.red.fail(Symbols.ERROR) raise SystemExit(1) diff --git a/riocli/deployment/execute.py b/riocli/deployment/execute.py index 8b801404..f484ba85 100644 --- a/riocli/deployment/execute.py +++ b/riocli/deployment/execute.py @@ -11,32 +11,51 @@ # 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 -import click import typing +import click +from click_help_colors import HelpColorsCommand +from yaspin import kbi_safe_yaspin + +from riocli.constants import Colors from riocli.deployment.util import name_to_guid, select_details from riocli.utils.execute import run_on_cloud -@click.command('execute') -@click.option('--component', 'component_name', default=None) -@click.option('--exec', 'exec_name', default=None) +@click.command( + 'execute', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) +@click.option('--component', 'component_name', default=None, + help='Name of the component in the deployment') +@click.option('--exec', 'exec_name', default=None, + help='Name of a executable in the component') @click.argument('deployment-name', type=str) @click.argument('command', nargs=-1) @name_to_guid -def execute_command(component_name: str, exec_name: str, deployment_name: str, deployment_guid: str, command: typing.List[str]) -> None: +def execute_command( + component_name: str, + exec_name: str, + deployment_name: str, + deployment_guid: str, + command: typing.List[str] +) -> None: """ Execute commands on cloud deployment """ try: comp_id, exec_id, pod_name = select_details(deployment_guid, component_name, exec_name) - stdout, stderr = run_on_cloud(deployment_guid, comp_id, exec_id, pod_name, command) + + with kbi_safe_yaspin(text='Executing command `{}`...'.format(command)) as spinner: + stdout, stderr = run_on_cloud(deployment_guid, comp_id, exec_id, pod_name, command) + if stderr: - click.secho(stderr, fg='red') + click.secho(stderr, fg=Colors.RED) if stdout: - click.secho(stdout, fg='yellow') + click.secho(stdout, fg=Colors.YELLOW) except Exception as e: - click.secho(e, fg='red') - raise SystemExit(1) \ No newline at end of file + click.secho(e, fg=Colors.RED) + raise SystemExit(1) diff --git a/riocli/deployment/inspect.py b/riocli/deployment/inspect.py index bc18c482..8dd8d0c7 100644 --- a/riocli/deployment/inspect.py +++ b/riocli/deployment/inspect.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,19 +12,30 @@ # See the License for the specific language governing permissions and # limitations under the License. import click +from click_help_colors import HelpColorsCommand from rapyuta_io.clients.deployment import Deployment from riocli.config import new_client +from riocli.constants import Colors from riocli.deployment.util import name_to_guid from riocli.utils import inspect_with_format -@click.command('inspect') +@click.command( + 'inspect', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.option('--format', '-f', 'format_type', default='yaml', type=click.Choice(['json', 'yaml'], case_sensitive=False)) @click.argument('deployment-name') @name_to_guid -def inspect_deployment(format_type: str, deployment_name: str, deployment_guid: str) -> None: +def inspect_deployment( + format_type: str, + deployment_name: str, + deployment_guid: str, +) -> None: """ Inspect the deployment resource """ @@ -34,7 +45,7 @@ def inspect_deployment(format_type: str, deployment_name: str, deployment_guid: data = make_deployment_inspectable(deployment) inspect_with_format(data, format_type) except Exception as e: - click.secho(str(e), fg='red') + click.secho(str(e), fg=Colors.RED) raise SystemExit(1) diff --git a/riocli/deployment/list.py b/riocli/deployment/list.py index fa06d11d..8f539074 100644 --- a/riocli/deployment/list.py +++ b/riocli/deployment/list.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,19 +14,41 @@ import typing import click -from rapyuta_io.clients.deployment import Deployment +from click_help_colors import HelpColorsCommand +from rapyuta_io.clients.deployment import Deployment, DeploymentPhaseConstants from riocli.config import new_client +from riocli.constants import Colors from riocli.utils import tabulate_data +ALL_PHASES = [ + DeploymentPhaseConstants.INPROGRESS, + DeploymentPhaseConstants.PROVISIONING, + DeploymentPhaseConstants.SUCCEEDED, + DeploymentPhaseConstants.FAILED_TO_START, + DeploymentPhaseConstants.PARTIALLY_DEPROVISIONED, + DeploymentPhaseConstants.DEPLOYMENT_STOPPED, +] -@click.command('list') +DEFAULT_PHASES = [ + DeploymentPhaseConstants.INPROGRESS, + DeploymentPhaseConstants.PROVISIONING, + DeploymentPhaseConstants.SUCCEEDED, + DeploymentPhaseConstants.FAILED_TO_START, +] + + +@click.command( + 'list', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.option('--device', prompt_required=False, default='', type=str, help='Filter the Deployment list by Device ID') @click.option('--phase', prompt_required=False, multiple=True, - type=click.Choice(['In progress', 'Provisioning', 'Succeeded', 'Failed to start', - 'Partially deprovisioned', 'Deployment stopped']), - default=['In progress', 'Provisioning', 'Succeeded', 'Failed to start'], + type=click.Choice(ALL_PHASES), + default=DEFAULT_PHASES, help='Filter the Deployment list by Phases') def list_deployments(device: str, phase: typing.List[str]) -> None: """ @@ -38,7 +60,7 @@ def list_deployments(device: str, phase: typing.List[str]) -> None: deployments = sorted(deployments, key=lambda d: d.name.lower()) display_deployment_list(deployments, show_header=True) except Exception as e: - click.secho(str(e), fg='red') + click.secho(str(e), fg=Colors.RED) raise SystemExit(1) diff --git a/riocli/deployment/logs.py b/riocli/deployment/logs.py index 4113c135..46704122 100644 --- a/riocli/deployment/logs.py +++ b/riocli/deployment/logs.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,27 +14,41 @@ import os import click +from click_help_colors import HelpColorsCommand from riocli.config import Configuration +from riocli.constants import Colors from riocli.deployment.util import name_to_guid, select_details _LOG_URL_FORMAT = '{}/deployment/logstream?tailLines={}&deploymentId={}&componentId={}&executableId={}&podName={}' -@click.command('logs') -@click.option('--component', 'component_name', default=None) -@click.option('--exec', 'exec_name', default=None) +@click.command( + 'logs', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) +@click.option('--component', 'component_name', default=None, + help='Name of the component in the deployment') +@click.option('--exec', 'exec_name', default=None, + help='Name of a executable in the component') @click.argument('deployment-name', type=str) @name_to_guid -def deployment_logs(component_name: str, exec_name: str, deployment_name: str, deployment_guid: str) -> None: +def deployment_logs( + component_name: str, + exec_name: str, + deployment_name: str, + deployment_guid: str, +) -> None: """ - Stream the live logs for the Deployment (only Cloud) + Stream live logs from cloud deployments (not supported for device deployments) """ try: comp_id, exec_id, pod_name = select_details(deployment_guid, component_name, exec_name) stream_deployment_logs(deployment_guid, comp_id, exec_id, pod_name) except Exception as e: - click.secho(e, fg='red') + click.secho(e, fg=Colors.RED) raise SystemExit(1) @@ -42,10 +56,13 @@ def stream_deployment_logs(deployment_id, component_id, exec_id, pod_name=None): # FIXME(ankit): The Upstream API ends up timing out when there is no log being written. # IMO the correct behaviour should be to not timeout and keep the stream open. config = Configuration() + url = get_log_stream_url(config, deployment_id, component_id, exec_id, pod_name) auth = config.get_auth_header() - curl = 'curl -H "project: {}" -H "Authorization: {}" "{}"'.format(auth['project'], auth['Authorization'], url) - click.secho(curl, fg='blue') + curl = 'curl -H "project: {}" -H "Authorization: {}" "{}"'.format( + auth['project'], auth['Authorization'], url) + click.echo(click.style(curl, fg=Colors.BLUE, italic=True)) + os.system(curl) diff --git a/riocli/deployment/model.py b/riocli/deployment/model.py index 448aa969..5352fc8b 100644 --- a/riocli/deployment/model.py +++ b/riocli/deployment/model.py @@ -1,4 +1,4 @@ -# Copyright 2022 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. @@ -27,6 +27,7 @@ ROSBagUploadTypes, OverrideOptions, TopicOverrideInfo) from rapyuta_io.clients.routed_network import RoutedNetwork +from riocli.constants import Colors from riocli.deployment.errors import ERRORS from riocli.deployment.util import add_mount_volume_provision_config from riocli.jsonschema.validate import load_schema @@ -43,8 +44,11 @@ class Deployment(Model): } def find_object(self, client: Client) -> typing.Any: - guid, obj = self.rc.find_depends( - {"kind": "deployment", "nameOrGUID": self.metadata.name}) + guid, obj = self.rc.find_depends({ + "kind": "deployment", + "nameOrGUID": self.metadata.name, + }) + return obj if guid else False def create_object(self, client: Client, **kwargs) -> typing.Any: @@ -71,7 +75,7 @@ def create_object(self, client: Client, **kwargs) -> typing.Any: '>> runtime mismatch => ' + 'deployment:{}.runtime !== package:{}.runtime '.format( self.metadata.name, pkg['packageName'] - ), fg='red') + ), fg=Colors.RED) return provision_config = pkg.get_provision_configuration(plan_id) @@ -395,9 +399,9 @@ def process_deployment_errors(e: DeploymentNotRunningException): description = 'Internal rapyuta.io error' action = support_action - code = click.style(code, fg='yellow') - description = click.style(description, fg='red') - action = click.style(action, fg='green') + code = click.style(code, fg=Colors.YELLOW) + description = click.style(description, fg=Colors.RED) + action = click.style(action, fg=Colors.GREEN) msgs.append(err_fmt.format(code, description, action)) diff --git a/riocli/deployment/run.py b/riocli/deployment/run.py deleted file mode 100644 index 7f5351dd..00000000 --- a/riocli/deployment/run.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2021 Rapyuta Robotics -# -# 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 click - - -@click.command('run', hidden=True) -@click.argument('docker-image', type=str) -def run_deployment(docker_image: str) -> None: - # TODO(ankit): Implement `kubectl run` like command to instantly create a Deployment. - # Possibly implement --interactive --tty to SSH into the session - pass diff --git a/riocli/deployment/status.py b/riocli/deployment/status.py index f883484e..cd4cb93c 100644 --- a/riocli/deployment/status.py +++ b/riocli/deployment/status.py @@ -12,12 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. import click +from click_help_colors import HelpColorsCommand from riocli.config import new_client +from riocli.constants import Colors from riocli.deployment.util import name_to_guid -@click.command('status') +@click.command( + 'status', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.argument('deployment-name', type=str) @name_to_guid def status(deployment_name: str, deployment_guid: str) -> None: @@ -29,5 +36,5 @@ def status(deployment_name: str, deployment_guid: str) -> None: deployment = client.get_deployment(deployment_guid) click.secho(deployment.status) except Exception as e: - click.secho(str(e), fg='red') + click.secho(str(e), fg=Colors.RED) raise SystemExit(1) diff --git a/riocli/deployment/wait.py b/riocli/deployment/wait.py index 011a0f0e..b02de14f 100644 --- a/riocli/deployment/wait.py +++ b/riocli/deployment/wait.py @@ -12,36 +12,52 @@ # See the License for the specific language governing permissions and # limitations under the License. import click -from click_spinner import spinner +from click_help_colors import HelpColorsCommand from rapyuta_io.utils import RetriesExhausted, DeploymentNotRunningException from riocli.config import new_client +from riocli.constants import Colors +from riocli.constants import Symbols from riocli.deployment.util import name_to_guid +from riocli.utils.spinner import with_spinner -@click.command('wait') +@click.command( + 'wait', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.argument('deployment-name', type=str) @name_to_guid -def wait_for_deployment(deployment_name: str, deployment_guid: str) -> None: +@with_spinner(text="Waiting for deployment...", timer=True) +def wait_for_deployment( + deployment_name: str, + deployment_guid: str, + spinner=None, +) -> None: """ - Wait until the Deployment succeeds/fails + Wait until the deployment succeeds/fails """ try: client = new_client() - with spinner(): - deployment = client.get_deployment(deployment_guid) - # TODO(ankit): Fix the poll_deployment_till_ready for Runtime Error - status = deployment.poll_deployment_till_ready() - click.secho('Deployment status: {}'.format(status.status)) + deployment = client.get_deployment(deployment_guid) + # TODO(ankit): Fix the poll_deployment_till_ready for Runtime Error + status = deployment.poll_deployment_till_ready() + spinner.text = click.style('Deployment status: {}'.format(status.status), fg=Colors.GREEN) + spinner.green.ok(Symbols.SUCCESS) except RetriesExhausted as e: - click.secho(str(e), fg='red') - click.secho('Retry Again?', fg='red') + spinner.write(click.style(str(e), fg=Colors.RED)) + spinner.text = click.style('Try again?', Colors.RED) + spinner.red.fail(Symbols.ERROR) except DeploymentNotRunningException as e: if 'DEP_E151' in e.deployment_status.errors: - click.secho('Device is either offline or not reachable', fg='red') + spinner.text = click.style('Device is either offline or not reachable', fg=Colors.RED) else: - click.secho(str(e), fg='red') + spinner.text = click.style(str(e), fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) raise SystemExit(1) except Exception as e: - click.secho(str(e), fg='red') + spinner.text = click.style(str(e), fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) raise SystemExit(1)