diff --git a/src/dstack/_internal/cli/commands/fleet.py b/src/dstack/_internal/cli/commands/fleet.py index 130e2c3fc..d58f50cc2 100644 --- a/src/dstack/_internal/cli/commands/fleet.py +++ b/src/dstack/_internal/cli/commands/fleet.py @@ -95,13 +95,17 @@ def _command(self, args: argparse.Namespace): def _list(self, args: argparse.Namespace): fleets = self.api.client.fleets.list(self.api.project) if not args.watch: - print_fleets_table(fleets, verbose=args.verbose) + print_fleets_table(fleets, current_project=self.api.project, verbose=args.verbose) return try: with Live(console=console, refresh_per_second=LIVE_TABLE_REFRESH_RATE_PER_SEC) as live: while True: - live.update(get_fleets_table(fleets, verbose=args.verbose)) + live.update( + get_fleets_table( + fleets, current_project=self.api.project, verbose=args.verbose + ) + ) time.sleep(LIVE_TABLE_PROVISION_INTERVAL_SECS) fleets = self.api.client.fleets.list(self.api.project) except KeyboardInterrupt: diff --git a/src/dstack/_internal/cli/services/configurators/fleet.py b/src/dstack/_internal/cli/services/configurators/fleet.py index 27b607cb4..fe1dd4c0c 100644 --- a/src/dstack/_internal/cli/services/configurators/fleet.py +++ b/src/dstack/_internal/cli/services/configurators/fleet.py @@ -141,7 +141,7 @@ def _apply_plan(self, plan: FleetPlan, command_args: argparse.Namespace): f"Provisioning [code]{fleet.name}[/]...", console=console ) as live: while not _finished_provisioning(fleet): - table = get_fleets_table([fleet]) + table = get_fleets_table([fleet], current_project=self.api.project) live.update(table) time.sleep(LIVE_TABLE_PROVISION_INTERVAL_SECS) fleet = self.api.client.fleets.get(self.api.project, fleet.name) @@ -159,6 +159,7 @@ def _apply_plan(self, plan: FleetPlan, command_args: argparse.Namespace): [fleet], verbose=_fleet_has_failed_instances(fleet), format_date=local_time, + current_project=self.api.project, ) ) if _fleet_has_failed_instances(fleet): @@ -242,7 +243,7 @@ def _apply_plan_on_old_server(self, plan: FleetPlan, command_args: argparse.Name f"Provisioning [code]{fleet.name}[/]...", console=console ) as live: while not _finished_provisioning(fleet): - table = get_fleets_table([fleet]) + table = get_fleets_table([fleet], current_project=self.api.project) live.update(table) time.sleep(LIVE_TABLE_PROVISION_INTERVAL_SECS) fleet = self.api.client.fleets.get(self.api.project, fleet.name) @@ -260,6 +261,7 @@ def _apply_plan_on_old_server(self, plan: FleetPlan, command_args: argparse.Name [fleet], verbose=_fleet_has_failed_instances(fleet), format_date=local_time, + current_project=self.api.project, ) ) if _fleet_has_failed_instances(fleet): diff --git a/src/dstack/_internal/cli/utils/fleet.py b/src/dstack/_internal/cli/utils/fleet.py index fdca4270a..70e6b5d74 100644 --- a/src/dstack/_internal/cli/utils/fleet.py +++ b/src/dstack/_internal/cli/utils/fleet.py @@ -10,13 +10,16 @@ from dstack._internal.utils.common import DateFormatter, pretty_date -def print_fleets_table(fleets: List[Fleet], verbose: bool = False) -> None: - console.print(get_fleets_table(fleets, verbose=verbose)) +def print_fleets_table(fleets: List[Fleet], current_project: str, verbose: bool = False) -> None: + console.print(get_fleets_table(fleets, current_project=current_project, verbose=verbose)) console.print() def get_fleets_table( - fleets: List[Fleet], verbose: bool = False, format_date: DateFormatter = pretty_date + fleets: List[Fleet], + current_project: str, + verbose: bool = False, + format_date: DateFormatter = pretty_date, ) -> Table: table = Table(box=None) @@ -40,6 +43,10 @@ def get_fleets_table( config = fleet.spec.configuration merged_profile = fleet.spec.merged_profile + name = fleet.name + if fleet.project_name != current_project: + name = f"{fleet.project_name}/{fleet.name}" + # Detect SSH fleet vs backend fleet if config.ssh_config is not None: # SSH fleet: fixed number of hosts, no cloud billing @@ -65,7 +72,7 @@ def get_fleets_table( nodes = f"{nodes} (cluster)" fleet_row: Dict[Union[str, int], Any] = { - "NAME": fleet.name, + "NAME": name, "NODES": nodes, "BACKEND": backend, "PRICE": max_price, diff --git a/src/tests/_internal/cli/utils/test_fleet.py b/src/tests/_internal/cli/utils/test_fleet.py index 1c1df4df2..82b489409 100644 --- a/src/tests/_internal/cli/utils/test_fleet.py +++ b/src/tests/_internal/cli/utils/test_fleet.py @@ -126,6 +126,7 @@ def create_backend_fleet( gpu_count_max: int = 0, instances: Optional[List[Instance]] = None, status: FleetStatus = FleetStatus.ACTIVE, + project_name: str = "test-project", ) -> Fleet: nodes = FleetNodesSpec(min=nodes_min, target=nodes_min, max=nodes_max) @@ -154,7 +155,7 @@ def create_backend_fleet( return Fleet( id=uuid4(), name=name, - project_name="test-project", + project_name=project_name, spec=spec, created_at=datetime(2023, 1, 2, 3, 4, 5, tzinfo=timezone.utc), status=status, @@ -222,7 +223,7 @@ def test_backend_fleet_without_verbose(self): instances=[instance], ) - table = get_fleets_table([fleet], verbose=False) + table = get_fleets_table([fleet], current_project="test-project", verbose=False) cells = get_table_cells(table) assert len(cells) == 2 # 1 fleet row + 1 instance row @@ -262,7 +263,7 @@ def test_backend_fleet_with_verbose(self): instances=[instance], ) - table = get_fleets_table([fleet], verbose=True) + table = get_fleets_table([fleet], current_project="test-project", verbose=True) cells = get_table_cells(table) assert len(cells) == 2 @@ -310,7 +311,7 @@ def test_ssh_fleet_without_verbose(self): instances=[instance1, instance2], ) - table = get_fleets_table([fleet], verbose=False) + table = get_fleets_table([fleet], current_project="test-project", verbose=False) cells = get_table_cells(table) assert len(cells) == 3 # 1 fleet row + 2 instance rows @@ -345,7 +346,7 @@ def test_ssh_fleet_with_verbose(self): instances=[instance], ) - table = get_fleets_table([fleet], verbose=True) + table = get_fleets_table([fleet], current_project="test-project", verbose=True) cells = get_table_cells(table) assert len(cells) == 2 @@ -395,7 +396,9 @@ def test_mixed_fleets(self): instances=[ssh_instance], ) - table = get_fleets_table([backend_fleet, ssh_fleet], verbose=False) + table = get_fleets_table( + [backend_fleet, ssh_fleet], current_project="test-project", verbose=False + ) cells = get_table_cells(table) assert len(cells) == 4 # 2 fleet rows + 2 instance rows @@ -433,7 +436,9 @@ def test_fleet_status_colors(self): name="terminating", status=FleetStatus.TERMINATING, instances=[terminating_instance] ) - table = get_fleets_table([active_fleet, terminating_fleet], verbose=False) + table = get_fleets_table( + [active_fleet, terminating_fleet], current_project="test-project", verbose=False + ) active_style = get_table_cell_style(table, "STATUS", 0) assert active_style == "bold white" @@ -451,7 +456,7 @@ def test_instance_status_colors(self): instances=[idle_instance, busy_instance], ) - table = get_fleets_table([fleet], verbose=False) + table = get_fleets_table([fleet], current_project="test-project", verbose=False) idle_style = get_table_cell_style(table, "STATUS", 1) assert idle_style == "bold sea_green3" @@ -462,7 +467,7 @@ def test_instance_status_colors(self): def test_empty_fleet(self): fleet = create_backend_fleet(name="empty-fleet", instances=[]) - table = get_fleets_table([fleet], verbose=False) + table = get_fleets_table([fleet], current_project="test-project", verbose=False) cells = get_table_cells(table) assert len(cells) == 1 @@ -474,7 +479,7 @@ def test_fleet_with_max_price(self): max_price=5.0, ) - table = get_fleets_table([fleet], verbose=False) + table = get_fleets_table([fleet], current_project="test-project", verbose=False) cells = get_table_cells(table) assert cells[0]["PRICE"] == "$0..$5" @@ -485,7 +490,7 @@ def test_fleet_with_multiple_backends(self): backends=[BackendType.AWS, BackendType.GCP, BackendType.AZURE], ) - table = get_fleets_table([fleet], verbose=False) + table = get_fleets_table([fleet], current_project="test-project", verbose=False) cells = get_table_cells(table) assert cells[0]["BACKEND"] == "aws, gcp, azure" @@ -496,7 +501,24 @@ def test_fleet_with_any_backend(self): backends=None, ) - table = get_fleets_table([fleet], verbose=False) + table = get_fleets_table([fleet], current_project="test-project", verbose=False) cells = get_table_cells(table) assert cells[0]["BACKEND"] == "*" + + def test_with_imported_fleet(self): + current_project_fleet = create_backend_fleet( + name="current-fleet", project_name="current-project" + ) + other_project_fleet = create_backend_fleet( + name="other-fleet", project_name="other-project" + ) + table = get_fleets_table( + [current_project_fleet, other_project_fleet], + verbose=False, + current_project="current-project", + ) + cells = get_table_cells(table) + assert len(cells) == 2 + assert cells[0]["NAME"] == "current-fleet" + assert cells[1]["NAME"] == "other-project/other-fleet"