Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix tdp browse operation #636

Merged
merged 9 commits into from
Dec 16, 2024
172 changes: 109 additions & 63 deletions tdp/cli/commands/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@

import click
from sqlalchemy import Engine
from tabulate import tabulate

from tdp.cli.params import database_dsn_option
from tdp.cli.utils import (
print_deployment,
print_object,
print_table,
print_operations,
)
from tdp.core.models import DeploymentModel, OperationModel
from tdp.core.entities.operation import OperationName
from tdp.core.models.deployment_model import DeploymentModel
from tdp.core.models.operation_model import OperationModel
from tdp.dao import Dao


Expand Down Expand Up @@ -58,79 +60,123 @@ def browse(
):
"""Browse deployments."""
with Dao(db_engine) as dao:
# Print last deployment plan
if plan and last:
raise click.BadOptionUsage(
"--plan", "Cannot use --plan and --last together."
)
if deployment_id and (plan or last):
raise click.BadOptionUsage(
"deployment_id", "Cannot use deployment_id with --plan or --last."
)
# Print the planned deployment
if plan:
deployment_plan = dao.get_planned_deployment()
if deployment_plan:
_print_deployment(deployment_plan)
else:
click.echo("No planned deployment found")
click.echo("Create a deployment plan using the `tdp plan` command")
browse_plan(dao)
return
# Print the last deployment
elif last:
deployment = dao.get_last_deployment()
if deployment:
_print_deployment(deployment)
else:
click.echo("No deployment found")
click.echo("Create a deployment plan using the `tdp plan` command")
browse_last(dao)
return

# Print a specific operation
if deployment_id and operation:
_print_operations(dao.get_operation(deployment_id, operation))
return

# Print a specific deployment
if deployment_id:
deployment = dao.get_deployment(deployment_id)
if deployment:
_print_deployment(deployment)
elif deployment_id and not operation:
browse_deployment(dao, deployment_id)
return
# Print a specific operation
elif deployment_id and operation:
browse_operation(dao, deployment_id, operation)
return

# Print all deployments
_print_deployments(dao.get_last_deployments(limit=limit, offset=offset))
else:
browse_deployments(dao, limit, offset)


def _print_deployments(deployments: Iterable[DeploymentModel]) -> None:
"""Print a list of deployments in a human readable format.

Args:
deployments: List of deployments to print.
"""
if not deployments:
click.echo("No deployment found")
def browse_plan(dao: Dao) -> None:
if deployment_plan := dao.get_planned_deployment():
print_deployment(deployment_plan, filter_out=["logs"])
else:
click.echo("No planned deployment found")
click.echo("Create a deployment plan using the `tdp plan` command")

print_table(
[d.to_dict(filter_out=["options"]) for d in deployments],
)


def _print_deployment(deployment: DeploymentModel) -> None:
"""Print a deployment in a human readable format.

Args:
deployment: Deployment to print.
"""
if not deployment:
click.echo("Deployment does not exist.")
return
def browse_last(dao: Dao) -> None:
if last_deployment := dao.get_last_deployment():
print_deployment(last_deployment, filter_out=["logs"])
else:
click.echo("No deployment found")
click.echo("Create a deployment plan using the `tdp plan` command")

print_deployment(deployment, filter_out=["logs"])

def browse_deployment(dao: Dao, deployment_id: int) -> None:
if deployment := dao.get_deployment(deployment_id):
print_deployment(deployment, filter_out=["logs"])
else:
click.echo(f"Deployment {deployment_id} does not exist.")


def browse_operation(dao: Dao, deployment_id: int, operation: str) -> None:
record = None
# Try to parse the operation argument as an integer
try:
operation_order = int(operation)
record = dao.get_operation(deployment_id, operation_order)
# If the operation argument is not an integer, consider that it is an
# operation name
except ValueError:
# Check if the operation name is valid
try:
OperationName.from_str(operation)
except ValueError as e:
raise click.BadParameter(
f"Operation {operation} is not a valid operation name."
) from e
operations = dao.get_operations_by_name(deployment_id, operation)
if len(operations) == 1:
record = operations[0]
# If there are multiple operations with the given name, print them asking for a
# specific operation order
elif len(operations) > 1:
click.secho(
f'Multiple operations "{operations[0].operation}" found for '
+ f"deployment {deployment_id}:",
bold=True,
)
print_operations(operations, filter_out=["logs"])
click.echo("\nUse the operation order to print a specific operation.")
return
if record:
_print_operation(record)
else:
click.echo(f'Operation "{operation}" not found for deployment {deployment_id}')
click.echo(
"Either the deployment does not exist or the operation is not"
+ " found for the deployment."
)


def browse_deployments(dao: Dao, limit: int, offset: int) -> None:
deployments = dao.get_last_deployments(limit=limit, offset=offset)
if len(deployments) > 0:
_print_deployments(deployments)
else:
click.echo("No deployments found.")
click.echo("Create a deployment plan using the `tdp plan` command.")


def _print_operation(operation: OperationModel) -> None:
click.echo(
tabulate(
operation.to_dict(filter_out=["logs"]).items(),
tablefmt="plain",
)
)
if operation.logs:
click.secho("\nLogs", bold=True)
click.echo(str(operation.logs, "utf-8"))

def _print_operations(operations: list[OperationModel]) -> None:
"""Print a list of operations in a human readable format.

Args:
operation: Operation to print.
"""
# Print general operation infos
click.secho("Operation(s) details", bold=True)
for operation in operations:
click.echo(print_object(operation.to_dict()))
# Print operation records
if operation.logs:
click.secho("\nOperations", bold=True)
click.echo(str(operation.logs, "utf-8"))
def _print_deployments(deployments: Iterable[DeploymentModel]) -> None:
click.echo(
tabulate(
[d.to_dict(filter_out=["options"]) for d in deployments],
headers="keys",
)
)
9 changes: 6 additions & 3 deletions tdp/cli/commands/status/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import click
from sqlalchemy import Engine
from tabulate import tabulate

from tdp.cli.params import (
collections_option,
Expand All @@ -22,7 +23,6 @@
from tdp.cli.utils import (
check_services_cleanliness,
print_hosted_entity_status_log,
print_table,
)
from tdp.core.models.sch_status_log_model import SCHStatusLogModel
from tdp.core.variables import ClusterVariables
Expand Down Expand Up @@ -119,6 +119,9 @@ def show(


def _print_sch_status_logs(sch_status: Iterable[SCHStatusLogModel]) -> None:
print_table(
[status.to_dict(filter_out=["id", "timestamp"]) for status in sch_status],
click.echo(
tabulate(
[status.to_dict(filter_out=["id", "timestamp"]) for status in sch_status],
headers="keys",
)
)
37 changes: 18 additions & 19 deletions tdp/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from tabulate import tabulate

from tdp.core.entities.hosted_entity_status import HostedEntityStatus
from tdp.core.models.operation_model import OperationModel

if TYPE_CHECKING:
from tdp.core.models import DeploymentModel
Expand Down Expand Up @@ -47,47 +48,45 @@ def print_deployment(
) -> None:
# Print general deployment infos
click.secho("Deployment details", bold=True)
click.echo(print_object(deployment.to_dict(filter_out=filter_out)))
click.echo(
tabulate(
deployment.to_dict(filter_out=filter_out).items(),
tablefmt="plain",
)
)

# Print deployment operations
click.secho("\nOperations", bold=True)
print_table(
[o.to_dict(filter_out=filter_out) for o in deployment.operations],
print_operations(
deployment.operations, filter_out=[*(filter_out or []), "deployment_id"]
)


def print_object(obj: dict) -> None:
"""Print an object in a human readable format.
def print_operations(
operations: Iterable[OperationModel], /, *, filter_out: Optional[list[str]] = None
) -> None:
"""Print a list of operations in a human readable format.

Args:
obj: Object to print.
operations: List of operations to print.
"""
click.echo(
tabulate(
obj.items(),
tablefmt="plain",
[o.to_dict(filter_out=filter_out) for o in operations],
headers="keys",
)
)


def print_table(rows) -> None:
"""Print a list of rows in a human readable format.

Args:
rows: List of rows to print.
"""
def print_hosted_entity_status_log(sch_status: Iterable[HostedEntityStatus]) -> None:
click.echo(
tabulate(
rows,
[status.export_tabulate() for status in sch_status],
headers="keys",
)
)


def print_hosted_entity_status_log(sch_status: Iterable[HostedEntityStatus]) -> None:
print_table([status.export_tabulate() for status in sch_status])


def _parse_line(line: str) -> tuple[str, Optional[str], Optional[list[str]]]:
"""Parses a line which contains an operation, and eventually a host and extra vars.

Expand Down
21 changes: 18 additions & 3 deletions tdp/dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,10 @@ def get_deployment(self, id: int) -> Optional[DeploymentModel]:
self._check_session()
return self.session.get(DeploymentModel, id)

def get_operation(
def get_operations_by_name(
self, deployment_id: int, operation_name: str
) -> list[OperationModel]:
"""Get an operation.
"""Get all operations for a deployment from their name.

Args:
deployment_id: The deployment ID.
Expand All @@ -255,6 +255,21 @@ def get_operation(
.all()
)

def get_operation(
self, deployment_id: int, operation_order: int
) -> Optional[OperationModel]:
"""Get an operation by deployment ID and operation order.

Args:
deployment_id: The deployment ID.
operation_order: The operation order.
"""
return (
self.session.query(OperationModel)
.filter_by(deployment_id=deployment_id, operation_order=operation_order)
.one_or_none()
)

def get_planned_deployment(self) -> Optional[DeploymentModel]:
self._check_session()
return (
Expand All @@ -272,7 +287,7 @@ def get_last_deployment(self) -> Optional[DeploymentModel]:

def get_last_deployments(
self, limit: Optional[int] = None, offset: Optional[int] = None
) -> Iterable[DeploymentModel]:
) -> list[DeploymentModel]:
"""Get last deployments in ascending order.

Use limit and offset to paginate the results.
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/test_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,9 @@ def test_operation(db_engine):
session.commit()
with Dao(db_engine) as dao:
assert assert_equal_values_in_model(
dao.get_operation(deployment_id=1, operation_name="test_operation")[0],
dao.get_operations_by_name(
deployment_id=1, operation_name="test_operation"
)[0],
OperationModel(
deployment_id=1,
operation_order=1,
Expand Down
Loading