From 517314b328f62737ea769c67ccc2ce3e39c85eb4 Mon Sep 17 00:00:00 2001 From: Antoni Ivanov Date: Sun, 2 Apr 2023 19:34:49 +0300 Subject: [PATCH] vdk-control-cli: use common output printer Adopting the new output_printer to print all outputs of all commands Some commands required small refactoring - to make some fields private. Signed-off-by: Antoni Ivanov --- .../control/command_groups/job/deploy_cli.py | 10 ++- .../command_groups/job/deploy_cli_impl.py | 44 ++++++------- .../control/command_groups/job/execute.py | 20 +++--- .../control/command_groups/job/list.py | 12 +--- .../control/command_groups/job/properties.py | 66 ++++++++----------- .../control/command_groups/job/show.py | 19 +++--- .../command_groups/version_group/version.py | 12 +++- .../control/command_groups/job/test_deploy.py | 14 ++-- 8 files changed, 88 insertions(+), 109 deletions(-) diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli.py index 7e9f389d96..11db40e654 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli.py @@ -178,11 +178,9 @@ def deploy( job_version = get_or_prompt("Job Version", job_version) if job_path: reason = get_or_prompt("Reason", reason) - return cmd.create( - name, team, job_path, reason, output, vdk_version, enabled - ) + return cmd.create(name, team, job_path, reason, vdk_version, enabled) else: - return cmd.update(name, team, enabled, job_version, vdk_version, output) + return cmd.update(name, team, enabled, job_version, vdk_version) if operation == DeployOperation.REMOVE.value: name = get_or_prompt("Job Name", name) team = get_or_prompt("Job Team", team) @@ -190,10 +188,10 @@ def deploy( if operation == DeployOperation.SHOW.value: name = get_or_prompt("Job Name", name) team = get_or_prompt("Job Team", team) - return cmd.show(name, team, output) + return cmd.show(name, team) if operation == DeployOperation.CREATE.value: job_path = get_or_prompt("Job Path", job_path) default_name = os.path.basename(job_path) name = get_or_prompt("Job Name", name, default_name) reason = get_or_prompt("Reason", reason) - return cmd.create(name, team, job_path, reason, output, vdk_version, enabled) + return cmd.create(name, team, job_path, reason, vdk_version, enabled) diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli_impl.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli_impl.py index 08a90d838a..f510fb2ef7 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli_impl.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli_impl.py @@ -1,14 +1,12 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 import glob -import json import logging import os from typing import Optional import click import click_spinner -from tabulate import tabulate from taurus_datajob_api import ApiException from taurus_datajob_api import DataJob from taurus_datajob_api import DataJobConfig @@ -21,6 +19,7 @@ from vdk.internal.control.job.job_config import JobConfig from vdk.internal.control.rest_lib.factory import ApiClientFactory from vdk.internal.control.rest_lib.rest_client_errors import ApiClientErrorDecorator +from vdk.internal.control.utils import output_printer from vdk.internal.control.utils.cli_utils import get_or_prompt from vdk.internal.control.utils.output_printer import OutputFormat @@ -31,7 +30,7 @@ class JobDeploy: ZIP_ARCHIVE_TYPE = "zip" ARCHIVE_SUFFIX = "-archive" - def __init__(self, rest_api_url: str, output): + def __init__(self, rest_api_url: str, output: str): self.deploy_api = ApiClientFactory(rest_api_url).get_deploy_api() self.jobs_api = ApiClientFactory(rest_api_url).get_jobs_api() self.job_sources_api = ApiClientFactory(rest_api_url).get_jobs_sources_api() @@ -39,6 +38,8 @@ def __init__(self, rest_api_url: str, output): # Ultimately this will be user facing parameter (possibly fetched from config.ini) self.__deployment_id = "production" self.__job_archive = JobArchive() + self.__output = output + self.__printer = output_printer.create_printer(self.__output) @staticmethod def __detect_keytab_files_in_job_directory(job_path: str) -> None: @@ -153,7 +154,6 @@ def update( enabled: Optional[bool], # true, false or None job_version: Optional[str], vdk_version: Optional[str], - output: str, ) -> None: deployment = DataJobDeployment(enabled=None) if job_version: @@ -163,7 +163,7 @@ def update( deployment.enabled = enabled if job_version: - self.__update_job_version(name, team, deployment, output) + self.__update_job_version(name, team, deployment) elif vdk_version or enabled is not None: self.__update_deployment(name, team, deployment) msg = f"Deployment of Data Job {name} updated; " @@ -186,16 +186,14 @@ def __update_deployment( data_job_deployment=deployment, ) - def __update_job_version( - self, name: str, team: str, deployment: DataJobDeployment, output: str - ): + def __update_job_version(self, name: str, team: str, deployment: DataJobDeployment): log.debug( f"Update Deployment version of a job {name} of team {team} : {deployment}" ) self.deploy_api.deployment_update( team_name=team, job_name=name, data_job_deployment=deployment ) - if output == OutputFormat.TEXT.value: + if self.__output == OutputFormat.TEXT.value: log.info( f"Request to deploy Data Job {name} using version {deployment.job_version} finished successfully.\n" f"It would take a few minutes for the Data Job to be deployed in the server.\n" @@ -210,7 +208,7 @@ def __update_job_version( "job_name": name, "job_version": deployment.job_version, } - click.echo(json.dumps(result)) + self.__printer.print_dict(result) @ApiClientErrorDecorator() def remove(self, name: str, team: str) -> None: @@ -221,7 +219,7 @@ def remove(self, name: str, team: str) -> None: log.info(f"Deployment of Data Job {name} removed.") @ApiClientErrorDecorator() - def show(self, name: str, team: str, output: str) -> None: + def show(self, name: str, team: str) -> None: log.debug(f"Get list of deployments for job {name} of team {team} ") deployments = self.deploy_api.deployment_list(team_name=team, job_name=name) log.debug( @@ -239,20 +237,17 @@ def show(self, name: str, team: str, output: str) -> None: ), deployments, ) - if output == OutputFormat.TEXT.value: + if self.__output == OutputFormat.TEXT.value: click.echo( "You can compare the version seen here to the one seen when " "deploying to verify your deployment was successful." ) click.echo("") - click.echo(tabulate(deployments, headers="keys")) + self.__printer.print_table(list(deployments)) else: - click.echo(json.dumps(list(deployments))) + self.__printer.print_table(list(deployments)) else: - if output == OutputFormat.TEXT.value: - click.echo("No deployments.") - else: - click.echo(json.dumps([])) + self.__printer.print_table(None) @ApiClientErrorDecorator() def create( @@ -261,7 +256,6 @@ def create( team: str, job_path: str, reason: str, - output: str, vdk_version: Optional[str], enabled: Optional[bool], ) -> None: @@ -287,7 +281,7 @@ def create( "Team Name", team or job_config.get_team() or load_default_team_name() ) - if output == OutputFormat.TEXT.value: + if self.__output == OutputFormat.TEXT.value: log.info( f"Deploy Data Job with name {name} from directory {job_path} ... \n" ) @@ -298,9 +292,11 @@ def create( try: job_archive_binary = self.__archive_binary(archive_path) - if output == OutputFormat.TEXT.value: + if self.__output == OutputFormat.TEXT.value: log.info("Uploading the data job might take some time ...") - with click_spinner.spinner(disable=(output == OutputFormat.JSON.value)): + with click_spinner.spinner( + disable=(self.__output == OutputFormat.JSON.value) + ): data_job_version = self.job_sources_api.sources_upload( team_name=team, job_name=name, @@ -309,8 +305,6 @@ def create( ) self.__update_data_job_deploy_configuration(job_path, name, team) - self.update( - name, team, enabled, data_job_version.version_sha, vdk_version, output - ) + self.update(name, team, enabled, data_job_version.version_sha, vdk_version) finally: self.__cleanup_archive(archive_path=archive_path) diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/execute.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/execute.py index 1427be0827..5cb29651d9 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/execute.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/execute.py @@ -4,15 +4,14 @@ import logging import operator import os -import sys import webbrowser from enum import Enum from enum import unique +from typing import List from typing import Optional import click import click_spinner -from tabulate import tabulate from taurus_datajob_api import DataJobExecution from taurus_datajob_api import DataJobExecutionLogs from taurus_datajob_api import DataJobExecutionRequest @@ -21,6 +20,7 @@ from vdk.internal.control.rest_lib.factory import ApiClientFactory from vdk.internal.control.rest_lib.rest_client_errors import ApiClientErrorDecorator from vdk.internal.control.utils import cli_utils +from vdk.internal.control.utils import output_printer from vdk.internal.control.utils.cli_utils import get_or_prompt from vdk.internal.control.utils.output_printer import OutputFormat @@ -54,11 +54,7 @@ def transform_execution(e: DataJobExecution): return d executions = list(map(lambda e: transform_execution(e), executions)) - - if output == OutputFormat.TEXT.value: - return tabulate(executions, headers="keys") - elif output == OutputFormat.JSON.value: - return cli_utils.json_format(list(executions)) + output_printer.create_printer(output).print_table(executions) @staticmethod def __validate_and_parse_args(arguments: str) -> str: @@ -101,13 +97,13 @@ def start(self, name: str, team: str, output: OutputFormat, arguments: str) -> N f"See execution logs using: \n\n" f"vdk execute --logs --execution-id {execution_id} -n {name} -t {team}" ) - elif output == OutputFormat.JSON.value: + else: result = { "job_name": name, "team": team, "execution_id": execution_id, } - click.echo(json.dumps(result)) + output_printer.create_printer(output).print_dict(result) @ApiClientErrorDecorator() def cancel(self, name: str, team: str, execution_id: str) -> None: @@ -126,14 +122,14 @@ def show( execution: DataJobExecution = self.__execution_api.data_job_execution_read( team_name=team, job_name=name, execution_id=execution_id ) - click.echo(self.__model_executions([execution], output)) + self.__model_executions([execution], output) @ApiClientErrorDecorator() def list(self, name: str, team: str, output: OutputFormat) -> None: - executions: list[ + executions: List[ DataJobExecution ] = self.__execution_api.data_job_execution_list(team_name=team, job_name=name) - click.echo(self.__model_executions(executions, output)) + self.__model_executions(executions, output) def __get_execution_to_log( self, name: str, team: str, execution_id: str diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/list.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/list.py index 75310fb8c6..156b5fe1bb 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/list.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/list.py @@ -1,7 +1,6 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 import datetime -import json import logging from enum import Enum from enum import unique @@ -9,14 +8,13 @@ from typing import List import click -from tabulate import tabulate from taurus_datajob_api import DataJobQueryResponse from vdk.internal.control.configuration.defaults_config import load_default_team_name from vdk.internal.control.rest_lib.factory import ApiClientFactory from vdk.internal.control.rest_lib.rest_client_errors import ApiClientErrorDecorator from vdk.internal.control.utils import cli_utils +from vdk.internal.control.utils import output_printer from vdk.internal.control.utils.cli_utils import GqlQueryBuilder -from vdk.internal.control.utils.output_printer import OutputFormat log = logging.getLogger(__name__) @@ -55,13 +53,7 @@ def list_jobs( page_number += 1 jobs = list(map(self.job_to_dict, jobs)) - if output == OutputFormat.TEXT.value: - if len(jobs) > 0: - click.echo(tabulate(jobs, headers="keys")) - else: - click.echo("No Data Jobs.") - else: - click.echo(json.dumps(list(jobs))) + output_printer.create_printer(output).print_table(jobs) @staticmethod def job_to_dict(job: Dict): diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/properties.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/properties.py index 713438e272..92e9eec089 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/properties.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/properties.py @@ -11,12 +11,12 @@ from typing import Type import click -from tabulate import tabulate from vdk.internal.control.configuration.defaults_config import load_default_team_name from vdk.internal.control.exception.vdk_exception import VDKException from vdk.internal.control.rest_lib.factory import ApiClientFactory from vdk.internal.control.rest_lib.rest_client_errors import ApiClientErrorDecorator from vdk.internal.control.utils import cli_utils +from vdk.internal.control.utils import output_printer from vdk.internal.control.utils.output_printer import OutputFormat log = logging.getLogger(__name__) @@ -41,32 +41,22 @@ def __init__( team: str, output: OutputFormat, ): - self.properties_api = ApiClientFactory(rest_api_url).get_properties_api() - self.output = output - self.job_name = job_name - self.team = team - self.deployment_id = "TODO" + self.__properties_api = ApiClientFactory(rest_api_url).get_properties_api() + self.__output = output + self.__printer = output_printer.create_printer(output) + self.__job_name = job_name + self.__team = team + self.__deployment_id = "TODO" def __get_all_remote_properties(self) -> Dict[str, Any]: - return self.properties_api.data_job_properties_read( - team_name=self.team, - job_name=self.job_name, - deployment_id=self.deployment_id, + return self.__properties_api.data_job_properties_read( + team_name=self.__team, + job_name=self.__job_name, + deployment_id=self.__deployment_id, ) def __list_properties(self, remote_props: Dict[str, Any]) -> None: - if self.output == OutputFormat.TEXT.value: - if len(remote_props) > 0: - click.echo( - tabulate( - [[k, v] for k, v in remote_props.items()], - headers=("key", "value"), - ) - ) - else: - click.echo("No properties.") - else: - click.echo(json.dumps(remote_props)) + self.__printer.print_dict(remote_props) @staticmethod def _to_bool(value: str) -> bool: @@ -138,19 +128,19 @@ def list(self) -> None: def update_properties(self, new_properties: Dict[str, str]) -> None: remote_props = self.__get_all_remote_properties() merged_props = self.__merge_props(new_properties, remote_props) - self.properties_api.data_job_properties_update( - team_name=self.team, - job_name=self.job_name, - deployment_id=self.deployment_id, + self.__properties_api.data_job_properties_update( + team_name=self.__team, + job_name=self.__job_name, + deployment_id=self.__deployment_id, request_body=merged_props, ) @ApiClientErrorDecorator() def overwrite_properties(self, new_properties: Dict[str, str]) -> None: - self.properties_api.data_job_properties_update( - team_name=self.team, - job_name=self.job_name, - deployment_id=self.deployment_id, + self.__properties_api.data_job_properties_update( + team_name=self.__team, + job_name=self.__job_name, + deployment_id=self.__deployment_id, request_body=new_properties, ) @@ -159,19 +149,19 @@ def delete_keys(self, delete_keys: Tuple[str]) -> None: props = self.__get_all_remote_properties() for key in delete_keys: props.pop(key) - self.properties_api.data_job_properties_update( - team_name=self.team, - job_name=self.job_name, - deployment_id=self.deployment_id, + self.__properties_api.data_job_properties_update( + team_name=self.__team, + job_name=self.__job_name, + deployment_id=self.__deployment_id, request_body=props, ) @ApiClientErrorDecorator() def delete_all_job_properties(self) -> None: - self.properties_api.data_job_properties_update( - team_name=self.team, - job_name=self.job_name, - deployment_id=self.deployment_id, + self.__properties_api.data_job_properties_update( + team_name=self.__team, + job_name=self.__job_name, + deployment_id=self.__deployment_id, request_body={}, ) diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/show.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/show.py index 2b3623cf5f..9acc0ef4bd 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/show.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/show.py @@ -1,6 +1,5 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 -import json import logging from typing import List @@ -14,21 +13,21 @@ from vdk.internal.control.rest_lib.factory import ApiClientFactory from vdk.internal.control.rest_lib.rest_client_errors import ApiClientErrorDecorator from vdk.internal.control.utils import cli_utils -from vdk.internal.control.utils.output_printer import json_format +from vdk.internal.control.utils import output_printer log = logging.getLogger(__name__) class JobShow: def __init__(self, rest_api_url: str, output: str): - self.jobs_api = ApiClientFactory(rest_api_url).get_jobs_api() - self.deploy_api = ApiClientFactory(rest_api_url).get_deploy_api() - self.execution_api = ApiClientFactory(rest_api_url).get_execution_api() - self.output = output + self.__jobs_api = ApiClientFactory(rest_api_url).get_jobs_api() + self.__deploy_api = ApiClientFactory(rest_api_url).get_deploy_api() + self.__execution_api = ApiClientFactory(rest_api_url).get_execution_api() + self.__printer = output_printer.create_printer(output) def __read_data_job(self, name: str, team: str) -> DataJob: try: - return self.jobs_api.data_job_read(team_name=team, job_name=name) + return self.__jobs_api.data_job_read(team_name=team, job_name=name) except ApiException as e: raise VDKException( what=f"Cannot find data job {name}", @@ -40,10 +39,10 @@ def __read_data_job(self, name: str, team: str) -> DataJob: def __read_deployments( self, job_name: str, team: str ) -> List[DataJobDeploymentStatus]: - return self.deploy_api.deployment_list(team_name=team, job_name=job_name) + return self.__deploy_api.deployment_list(team_name=team, job_name=job_name) def __read_executions(self, job_name: str, team: str) -> List[DataJobExecution]: - return self.execution_api.data_job_execution_list( + return self.__execution_api.data_job_execution_list( team_name=team, job_name=job_name ) @@ -57,7 +56,7 @@ def show_job(self, job_name: str, team: str) -> None: job_as_dict["deployments"] = list(map(lambda d: d.to_dict(), deployments)) job_as_dict["executions"] = list(map(lambda e: e.to_dict(), executions))[:2] - click.echo(json_format(job_as_dict, indent=2)) + self.__printer.print_dict(job_as_dict) @click.command( diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/version_group/version.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/version_group/version.py index 675aee2128..2d098d411b 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/version_group/version.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/version_group/version.py @@ -1,10 +1,18 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 import click +from vdk.internal.control.utils import cli_utils +from vdk.internal.control.utils import output_printer +from vdk.internal.control.utils.output_printer import OutputFormat from vdk.internal.control.utils.version_utils import __version__ from vdk.internal.control.utils.version_utils import build_details @click.command(help="Prints the version of the client") -def version(): - click.echo(f"""Version: {__version__}\nBuild details: {build_details()}""") +@cli_utils.output_option() +def version(output: str): + if output == OutputFormat.TEXT.value: + click.echo(f"""Version: {__version__}\nBuild details: {build_details()}""") + else: + data = {"version": __version__, "build_details": build_details()} + output_printer.create_printer(output).print_dict(data) diff --git a/projects/vdk-control-cli/tests/vdk/internal/control/command_groups/job/test_deploy.py b/projects/vdk-control-cli/tests/vdk/internal/control/command_groups/job/test_deploy.py index fa5a849aef..a7b65a61fb 100644 --- a/projects/vdk-control-cli/tests/vdk/internal/control/command_groups/job/test_deploy.py +++ b/projects/vdk-control-cli/tests/vdk/internal/control/command_groups/job/test_deploy.py @@ -345,9 +345,7 @@ def test_deploy_show_with_json_output(httpserver: PluginHTTPServer, tmpdir: Loca ), f"expected data not found in output: {result.output}" -def test_deploy_show_with_json_output_and_no_deployments( - httpserver: PluginHTTPServer, tmpdir: LocalPath -): +def test_deploy_show_with_json_output_and_no_deployments(httpserver: PluginHTTPServer): rest_api_url = httpserver.url_for("") response = None @@ -376,8 +374,12 @@ def test_deploy_show_with_json_output_and_no_deployments( json_result = json.loads(result.output) except JSONDecodeError as error: assert False, f"failed to parse the response as a JSON object, error: {error}" - assert isinstance(json_result, list) - assert len(list(json_result)) == 0 + assert isinstance( + json_result, list + ), f"expected a list, got: {type(json_result)}, output: {result.output}" + assert ( + len(list(json_result)) == 0 + ), f"expected an empty list, got: {json_result}, output: {result.output}" def test_deploy_show_with_missing_output_and_no_deployments( @@ -396,7 +398,7 @@ def test_deploy_show_with_missing_output_and_no_deployments( ) test_utils.assert_click_status(result, 0) assert ( - "No deployments." in result.output + "No Data." in result.output ), f"expected data not found in output: {result.output}"