anyscale 0.26.20__py3-none-any.whl → 0.26.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anyscale/_private/anyscale_client/anyscale_client.py +103 -43
- anyscale/_private/anyscale_client/common.py +37 -7
- anyscale/_private/anyscale_client/fake_anyscale_client.py +98 -27
- anyscale/_private/models/model_base.py +95 -0
- anyscale/_private/workload/workload_sdk.py +3 -1
- anyscale/aggregated_instance_usage/models.py +4 -4
- anyscale/client/README.md +1 -9
- anyscale/client/openapi_client/__init__.py +0 -3
- anyscale/client/openapi_client/api/default_api.py +151 -715
- anyscale/client/openapi_client/models/__init__.py +0 -3
- anyscale/commands/cloud_commands.py +15 -4
- anyscale/commands/command_examples.py +4 -0
- anyscale/commands/list_util.py +107 -0
- anyscale/commands/service_commands.py +267 -31
- anyscale/commands/util.py +5 -4
- anyscale/controllers/cloud_controller.py +358 -49
- anyscale/controllers/service_controller.py +7 -86
- anyscale/service/__init__.py +53 -3
- anyscale/service/_private/service_sdk.py +177 -41
- anyscale/service/commands.py +78 -1
- anyscale/service/models.py +65 -0
- anyscale/util.py +35 -1
- anyscale/utils/gcp_utils.py +20 -4
- anyscale/version.py +1 -1
- {anyscale-0.26.20.dist-info → anyscale-0.26.22.dist-info}/METADATA +1 -1
- {anyscale-0.26.20.dist-info → anyscale-0.26.22.dist-info}/RECORD +31 -33
- anyscale/client/openapi_client/models/organization_public_identifier.py +0 -121
- anyscale/client/openapi_client/models/organization_response.py +0 -121
- anyscale/client/openapi_client/models/organizationpublicidentifier_response.py +0 -121
- {anyscale-0.26.20.dist-info → anyscale-0.26.22.dist-info}/LICENSE +0 -0
- {anyscale-0.26.20.dist-info → anyscale-0.26.22.dist-info}/NOTICE +0 -0
- {anyscale-0.26.20.dist-info → anyscale-0.26.22.dist-info}/WHEEL +0 -0
- {anyscale-0.26.20.dist-info → anyscale-0.26.22.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.20.dist-info → anyscale-0.26.22.dist-info}/top_level.txt +0 -0
@@ -406,8 +406,6 @@ from openapi_client.models.organization_marketing_questions import OrganizationM
|
|
406
406
|
from openapi_client.models.organization_permission_level import OrganizationPermissionLevel
|
407
407
|
from openapi_client.models.organization_project_collaborator import OrganizationProjectCollaborator
|
408
408
|
from openapi_client.models.organization_project_collaborator_value import OrganizationProjectCollaboratorValue
|
409
|
-
from openapi_client.models.organization_public_identifier import OrganizationPublicIdentifier
|
410
|
-
from openapi_client.models.organization_response import OrganizationResponse
|
411
409
|
from openapi_client.models.organization_summary import OrganizationSummary
|
412
410
|
from openapi_client.models.organization_usage_alert import OrganizationUsageAlert
|
413
411
|
from openapi_client.models.organization_usage_alert_severity import OrganizationUsageAlertSeverity
|
@@ -418,7 +416,6 @@ from openapi_client.models.organizationinvitation_list_response import Organizat
|
|
418
416
|
from openapi_client.models.organizationinvitation_response import OrganizationinvitationResponse
|
419
417
|
from openapi_client.models.organizationinvitationbase_response import OrganizationinvitationbaseResponse
|
420
418
|
from openapi_client.models.organizationprojectcollaborator_list_response import OrganizationprojectcollaboratorListResponse
|
421
|
-
from openapi_client.models.organizationpublicidentifier_response import OrganizationpublicidentifierResponse
|
422
419
|
from openapi_client.models.organizationusagealert_list_response import OrganizationusagealertListResponse
|
423
420
|
from openapi_client.models.pcp_config import PCPConfig
|
424
421
|
from openapi_client.models.page_query import PageQuery
|
@@ -237,6 +237,7 @@ def cloud_config_group() -> None:
|
|
237
237
|
@cloud_cli.command(
|
238
238
|
name="update",
|
239
239
|
help=(
|
240
|
+
# TODO(janet): Update this help text when the -o option is un-hidden.
|
240
241
|
"Update a managed cloud to the latest configuration. Only applicable for anyscale managed clouds."
|
241
242
|
),
|
242
243
|
)
|
@@ -275,7 +276,14 @@ def cloud_config_group() -> None:
|
|
275
276
|
"are manually granted permissions to access the cloud. No existing cloud permissions are altered by specifying this flag."
|
276
277
|
),
|
277
278
|
)
|
278
|
-
|
279
|
+
@click.option(
|
280
|
+
"--file",
|
281
|
+
"-f",
|
282
|
+
help="YAML file containing the updated cloud spec.",
|
283
|
+
required=False,
|
284
|
+
hidden=True,
|
285
|
+
)
|
286
|
+
def cloud_update( # noqa: PLR0913
|
279
287
|
cloud_name: Optional[str],
|
280
288
|
name: Optional[str],
|
281
289
|
cloud_id: Optional[str],
|
@@ -283,7 +291,12 @@ def cloud_update(
|
|
283
291
|
enable_head_node_fault_tolerance: bool,
|
284
292
|
yes: bool,
|
285
293
|
enable_auto_add_user: Optional[bool],
|
294
|
+
file: Optional[str],
|
286
295
|
) -> None:
|
296
|
+
if file:
|
297
|
+
CloudController().update_cloud_deployments(file)
|
298
|
+
return
|
299
|
+
|
287
300
|
if cloud_name and name and cloud_name != name:
|
288
301
|
raise click.ClickException(
|
289
302
|
"The positional argument CLOUD_NAME and the keyword argument --name "
|
@@ -1142,9 +1155,7 @@ def get_cloud(
|
|
1142
1155
|
|
1143
1156
|
if output:
|
1144
1157
|
# Include all cloud deployments for the cloud.
|
1145
|
-
result = CloudController().get_cloud_deployments(
|
1146
|
-
cloud_id=cloud.id, cloud_name=cloud.name
|
1147
|
-
)
|
1158
|
+
result = CloudController().get_cloud_deployments(cloud_id=cloud.id)
|
1148
1159
|
|
1149
1160
|
with open(output, "w") as f:
|
1150
1161
|
yaml.dump(result, f, sort_keys=False)
|
@@ -0,0 +1,107 @@
|
|
1
|
+
import itertools
|
2
|
+
from json import dumps as json_dumps
|
3
|
+
from typing import Any, Callable, Dict, Iterator, List, Optional
|
4
|
+
|
5
|
+
from rich.console import Console
|
6
|
+
from rich.table import Table
|
7
|
+
|
8
|
+
from anyscale.util import AnyscaleJSONEncoder
|
9
|
+
|
10
|
+
|
11
|
+
def _paginate(iterator: Iterator[Any], page_size: Optional[int]) -> Iterator[List[Any]]:
|
12
|
+
if page_size is None:
|
13
|
+
yield list(iterator)
|
14
|
+
else:
|
15
|
+
while True:
|
16
|
+
page = list(itertools.islice(iterator, page_size))
|
17
|
+
if not page:
|
18
|
+
return
|
19
|
+
yield page
|
20
|
+
|
21
|
+
|
22
|
+
def display_list( # noqa: PLR0913
|
23
|
+
iterator: Iterator[Any],
|
24
|
+
item_formatter: Callable[[Any], Dict[str, Any]],
|
25
|
+
table_creator: Callable[[bool], Table],
|
26
|
+
json_output: bool,
|
27
|
+
page_size: int,
|
28
|
+
interactive: bool,
|
29
|
+
max_items: Optional[int],
|
30
|
+
console: Console,
|
31
|
+
) -> int:
|
32
|
+
"""Displays a list of items from an iterator, handling pagination and output format.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
iterator: The iterator yielding items to display.
|
36
|
+
item_formatter: A callable that takes an item and returns a dictionary
|
37
|
+
representing the row data (for table) or the JSON object.
|
38
|
+
table_creator: A callable that takes a boolean (is_first_page) and
|
39
|
+
returns a rich.Table instance. Used only if json_output is False.
|
40
|
+
json_output: If True, output items as a JSON list. Otherwise, display
|
41
|
+
them in a table created by table_creator.
|
42
|
+
page_size: The number of items to display per page in interactive mode.
|
43
|
+
interactive: If True, enables interactive pagination. If False, displays
|
44
|
+
up to max_items (or all items if max_items is None) without prompting.
|
45
|
+
max_items: The maximum total number of items to display when interactive
|
46
|
+
is False. If None, all items are displayed.
|
47
|
+
console: The rich.Console object to use for output.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
The total number of items displayed.
|
51
|
+
"""
|
52
|
+
total_count = 0
|
53
|
+
pages = _paginate(iterator, page_size if interactive else max_items)
|
54
|
+
|
55
|
+
# fetch first page under spinner
|
56
|
+
with console.status("Retrieving items…", spinner="dots"):
|
57
|
+
try:
|
58
|
+
first_page = next(pages)
|
59
|
+
except StopIteration:
|
60
|
+
first_page = []
|
61
|
+
|
62
|
+
def _render(page: List[Any], is_first: bool, page_num: int):
|
63
|
+
nonlocal total_count
|
64
|
+
total_count += len(page)
|
65
|
+
if interactive:
|
66
|
+
console.print(f"[dim]Page {page_num}[/dim]")
|
67
|
+
rows = [item_formatter(item) for item in page]
|
68
|
+
if json_output:
|
69
|
+
json_str = json_dumps(rows, indent=2, cls=AnyscaleJSONEncoder)
|
70
|
+
console.print_json(json=json_str)
|
71
|
+
else:
|
72
|
+
tbl = table_creator(is_first)
|
73
|
+
for row in rows:
|
74
|
+
tbl.add_row(*row.values())
|
75
|
+
console.print(tbl)
|
76
|
+
|
77
|
+
# render first page
|
78
|
+
if first_page:
|
79
|
+
_render(first_page, True, page_num=1)
|
80
|
+
|
81
|
+
# non-interactive: stop after first page
|
82
|
+
if not interactive:
|
83
|
+
return total_count
|
84
|
+
|
85
|
+
# interactive: prompt after full first page
|
86
|
+
if len(first_page) == page_size:
|
87
|
+
console.print()
|
88
|
+
console.print(
|
89
|
+
"[dim]Press [bold]Enter[/bold] to continue, [bold]q[/bold] to quit…[/]"
|
90
|
+
)
|
91
|
+
if input("> ").strip().lower() == "q":
|
92
|
+
return total_count
|
93
|
+
|
94
|
+
# render remaining pages
|
95
|
+
page_num = 2
|
96
|
+
for page in pages:
|
97
|
+
_render(page, False, page_num)
|
98
|
+
if len(page) == page_size:
|
99
|
+
console.print()
|
100
|
+
console.print(
|
101
|
+
"[dim]Press [bold]Enter[/bold] to continue, [bold]q[/bold] to quit…[/]"
|
102
|
+
)
|
103
|
+
if input("> ").strip().lower() == "q":
|
104
|
+
break
|
105
|
+
page_num += 1
|
106
|
+
|
107
|
+
return total_count
|
@@ -2,15 +2,18 @@ from io import StringIO
|
|
2
2
|
from json import dumps as json_dumps
|
3
3
|
import pathlib
|
4
4
|
import sys
|
5
|
-
from typing import Any, Dict, Optional, Tuple
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple
|
6
6
|
|
7
7
|
import click
|
8
|
+
from rich.console import Console
|
9
|
+
from rich.table import Table
|
8
10
|
from typing_extensions import Literal
|
9
11
|
import yaml
|
10
12
|
|
11
13
|
from anyscale._private.models.image_uri import ImageURI
|
12
14
|
from anyscale.cli_logger import BlockLogger
|
13
15
|
from anyscale.commands import command_examples
|
16
|
+
from anyscale.commands.list_util import display_list
|
14
17
|
from anyscale.commands.util import (
|
15
18
|
AnyscaleCommand,
|
16
19
|
convert_kv_strings_to_dict,
|
@@ -19,8 +22,21 @@ from anyscale.commands.util import (
|
|
19
22
|
)
|
20
23
|
from anyscale.controllers.service_controller import ServiceController
|
21
24
|
import anyscale.service
|
22
|
-
from anyscale.service import
|
23
|
-
|
25
|
+
from anyscale.service.models import (
|
26
|
+
ServiceConfig,
|
27
|
+
ServiceLogMode,
|
28
|
+
ServiceSortField,
|
29
|
+
ServiceState,
|
30
|
+
ServiceStatus,
|
31
|
+
ServiceVersionStatus,
|
32
|
+
SortOrder,
|
33
|
+
)
|
34
|
+
from anyscale.util import (
|
35
|
+
AnyscaleJSONEncoder,
|
36
|
+
get_endpoint,
|
37
|
+
validate_non_negative_arg,
|
38
|
+
validate_service_state_filter,
|
39
|
+
)
|
24
40
|
|
25
41
|
|
26
42
|
log = BlockLogger() # CLI Logger
|
@@ -389,12 +405,14 @@ def status(
|
|
389
405
|
status_dict.get("primary_version", {}).pop("config", None)
|
390
406
|
status_dict.get("canary_version", {}).pop("config", None)
|
391
407
|
|
408
|
+
console = Console()
|
392
409
|
if json:
|
393
|
-
|
410
|
+
json_str = json_dumps(status_dict, indent=2, cls=AnyscaleJSONEncoder)
|
411
|
+
console.print_json(json=json_str)
|
394
412
|
else:
|
395
413
|
stream = StringIO()
|
396
414
|
yaml.dump(status_dict, stream, sort_keys=False)
|
397
|
-
print(stream.getvalue(), end="")
|
415
|
+
console.print(stream.getvalue(), end="")
|
398
416
|
|
399
417
|
|
400
418
|
@service_cli.command(
|
@@ -624,51 +642,270 @@ def rollout( # noqa: PLR0913
|
|
624
642
|
)
|
625
643
|
|
626
644
|
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
645
|
+
MAX_PAGE_SIZE = 50
|
646
|
+
NON_INTERACTIVE_DEFAULT_MAX_ITEMS = 10
|
647
|
+
|
648
|
+
|
649
|
+
def validate_page_size(ctx, param, value):
|
650
|
+
value = validate_non_negative_arg(ctx, param, value)
|
651
|
+
if value is not None and value > MAX_PAGE_SIZE:
|
652
|
+
raise click.BadParameter(f"must be less than or equal to {MAX_PAGE_SIZE}.")
|
653
|
+
return value
|
654
|
+
|
655
|
+
|
656
|
+
def validate_max_items(ctx, param, value):
|
657
|
+
if value is None:
|
658
|
+
return None
|
659
|
+
return validate_non_negative_arg(ctx, param, value)
|
660
|
+
|
661
|
+
|
662
|
+
def _parse_sort_option(sort: Optional[str],) -> Tuple[Optional[str], SortOrder]:
|
663
|
+
"""
|
664
|
+
Given a raw sort string (e.g. "-created_at"), return
|
665
|
+
(canonical_field_name, SortOrder).
|
666
|
+
"""
|
667
|
+
if not sort:
|
668
|
+
return None, SortOrder.ASC
|
669
|
+
|
670
|
+
# build case-insensitive map of allowed fields
|
671
|
+
allowed = {f.value.lower(): f.value for f in ServiceSortField.__members__.values()}
|
672
|
+
|
673
|
+
# detect leading '-' for descending
|
674
|
+
if sort.startswith("-"):
|
675
|
+
raw = sort[1:]
|
676
|
+
order = SortOrder.DESC
|
677
|
+
else:
|
678
|
+
raw = sort
|
679
|
+
order = SortOrder.ASC
|
680
|
+
|
681
|
+
key = raw.lower()
|
682
|
+
if key not in allowed:
|
683
|
+
allowed_names = ", ".join(sorted(allowed.values()))
|
684
|
+
raise click.BadParameter(
|
685
|
+
f"Invalid sort field '{raw}'. Allowed fields: {allowed_names}"
|
686
|
+
)
|
687
|
+
|
688
|
+
return allowed[key], order
|
689
|
+
|
690
|
+
|
691
|
+
def _create_service_list_table(show_header: bool) -> Table:
|
692
|
+
table = Table(show_header=show_header, expand=True)
|
693
|
+
# NAME and ID: larger ratios, can wrap but never truncate
|
694
|
+
table.add_column(
|
695
|
+
"NAME", no_wrap=False, overflow="fold", ratio=3, min_width=15,
|
696
|
+
)
|
697
|
+
table.add_column(
|
698
|
+
"ID", no_wrap=False, overflow="fold", ratio=2, min_width=12,
|
699
|
+
)
|
700
|
+
# all other columns will wrap as needed
|
701
|
+
for heading in (
|
702
|
+
"CURRENT STATE",
|
703
|
+
"CREATOR",
|
704
|
+
"PROJECT",
|
705
|
+
"LAST DEPLOYED AT",
|
706
|
+
):
|
707
|
+
table.add_column(
|
708
|
+
heading, no_wrap=False, overflow="fold", ratio=1, min_width=8,
|
709
|
+
)
|
710
|
+
|
711
|
+
return table
|
712
|
+
|
713
|
+
|
714
|
+
def _format_service_output_data(svc: ServiceStatus) -> Dict[str, str]:
|
715
|
+
last_deployed_at = ""
|
716
|
+
if isinstance(svc.primary_version, ServiceVersionStatus):
|
717
|
+
last_deployed_at = svc.primary_version.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
718
|
+
|
719
|
+
return {
|
720
|
+
"name": svc.name,
|
721
|
+
"id": svc.id,
|
722
|
+
"current_state": str(svc.state),
|
723
|
+
"creator": str(svc.creator or ""),
|
724
|
+
"project": str(svc.project or ""),
|
725
|
+
"last_deployed_at": last_deployed_at,
|
726
|
+
}
|
727
|
+
|
728
|
+
|
729
|
+
@service_cli.command(
|
730
|
+
name="list", help="List services.", cls=AnyscaleCommand,
|
631
731
|
)
|
732
|
+
@click.option("--service-id", "--id", help="ID of the service to display.")
|
733
|
+
@click.option("--name", "-n", help="Name of the service to display.")
|
632
734
|
@click.option(
|
633
|
-
"--
|
735
|
+
"--cloud",
|
736
|
+
type=str,
|
737
|
+
help="The Anyscale Cloud of this workload; defaults to your org/workspace cloud.",
|
634
738
|
)
|
635
739
|
@click.option(
|
636
|
-
"--project
|
740
|
+
"--project",
|
741
|
+
type=str,
|
742
|
+
help="Named project to use; defaults to your org/workspace project.",
|
637
743
|
)
|
638
744
|
@click.option(
|
639
745
|
"--created-by-me",
|
746
|
+
is_flag=True,
|
747
|
+
default=False,
|
640
748
|
help="List services created by me only.",
|
749
|
+
)
|
750
|
+
@click.option(
|
751
|
+
"--state",
|
752
|
+
"-s",
|
753
|
+
"state_filter",
|
754
|
+
multiple=True,
|
755
|
+
callback=validate_service_state_filter,
|
756
|
+
help=(
|
757
|
+
"Filter by service state (repeatable). "
|
758
|
+
f"Allowed: {', '.join(s.value for s in ServiceState)}"
|
759
|
+
),
|
760
|
+
)
|
761
|
+
@click.option(
|
762
|
+
"--include-archived",
|
641
763
|
is_flag=True,
|
642
764
|
default=False,
|
765
|
+
help="Include archived services.",
|
643
766
|
)
|
644
767
|
@click.option(
|
645
768
|
"--max-items",
|
646
|
-
required=False,
|
647
|
-
default=10,
|
648
769
|
type=int,
|
649
|
-
|
650
|
-
|
770
|
+
callback=validate_max_items,
|
771
|
+
help="Max total items (only with --no-interactive).",
|
651
772
|
)
|
652
|
-
|
653
|
-
|
773
|
+
@click.option(
|
774
|
+
"--page-size",
|
775
|
+
type=int,
|
776
|
+
default=10,
|
777
|
+
show_default=True,
|
778
|
+
callback=validate_page_size,
|
779
|
+
help=f"Items per page (max {MAX_PAGE_SIZE}).",
|
780
|
+
)
|
781
|
+
@click.option(
|
782
|
+
"--interactive/--no-interactive",
|
783
|
+
default=True,
|
784
|
+
show_default=True,
|
785
|
+
help="Use interactive paging.",
|
786
|
+
)
|
787
|
+
@click.option(
|
788
|
+
"--sort",
|
789
|
+
help=(
|
790
|
+
"Sort by FIELD (prefix with '-' for desc). "
|
791
|
+
f"Allowed: {', '.join(f.value for f in ServiceSortField.__members__.values())}"
|
792
|
+
),
|
793
|
+
)
|
794
|
+
@click.option(
|
795
|
+
"-v",
|
796
|
+
"--verbose",
|
797
|
+
is_flag=True,
|
798
|
+
default=False,
|
799
|
+
help="Include full config in JSON output.",
|
800
|
+
)
|
801
|
+
@click.option(
|
802
|
+
"-j",
|
803
|
+
"--json",
|
804
|
+
"json_output",
|
805
|
+
is_flag=True,
|
806
|
+
default=False,
|
807
|
+
help="Emit structured JSON to stdout.",
|
808
|
+
)
|
809
|
+
def list( # noqa: PLR0913, A001
|
654
810
|
service_id: Optional[str],
|
655
|
-
|
811
|
+
name: Optional[str],
|
656
812
|
created_by_me: bool,
|
657
|
-
|
813
|
+
cloud: Optional[str],
|
814
|
+
project: Optional[str],
|
815
|
+
state_filter: List[str],
|
816
|
+
include_archived: bool,
|
817
|
+
max_items: Optional[int],
|
818
|
+
page_size: int,
|
819
|
+
sort: Optional[str],
|
820
|
+
json_output: bool,
|
821
|
+
interactive: bool,
|
822
|
+
verbose: bool,
|
658
823
|
):
|
659
|
-
"""List services based on the provided filters.
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
824
|
+
"""List services based on the provided filters."""
|
825
|
+
if max_items is not None and interactive:
|
826
|
+
raise click.UsageError("--max-items only allowed with --no-interactive")
|
827
|
+
|
828
|
+
# parse sort
|
829
|
+
sort_field, sort_order = _parse_sort_option(sort)
|
830
|
+
|
831
|
+
# normalize max_items
|
832
|
+
effective_max = max_items
|
833
|
+
if not interactive and effective_max is None:
|
834
|
+
stderr = Console(stderr=True)
|
835
|
+
stderr.print(
|
836
|
+
f"Defaulting to {NON_INTERACTIVE_DEFAULT_MAX_ITEMS} items in batch mode; "
|
837
|
+
"use --max-items to override."
|
838
|
+
)
|
839
|
+
effective_max = NON_INTERACTIVE_DEFAULT_MAX_ITEMS
|
840
|
+
|
841
|
+
console = Console()
|
842
|
+
stderr = Console(stderr=True)
|
843
|
+
|
844
|
+
# diagnostics
|
845
|
+
stderr.print("[bold]Listing services with:[/]")
|
846
|
+
stderr.print(f"• name = {name or '<any>'}")
|
847
|
+
stderr.print(f"• states = {', '.join(state_filter) or '<all>'}")
|
848
|
+
stderr.print(f"• created_by_me = {created_by_me}")
|
849
|
+
stderr.print(f"• include_archived= {include_archived}")
|
850
|
+
stderr.print(f"• sort = {sort or '<none>'}")
|
851
|
+
stderr.print(f"• mode = {'interactive' if interactive else 'batch'}")
|
852
|
+
stderr.print(f"• per-page limit = {page_size}")
|
853
|
+
stderr.print(f"• max-items total = {effective_max or 'all'}")
|
854
|
+
stderr.print(f"\nView your Services in the UI at {get_endpoint('/services')}\n")
|
855
|
+
|
856
|
+
creator_id = (
|
857
|
+
ServiceController().get_authenticated_user_id() if created_by_me else None
|
670
858
|
)
|
671
859
|
|
860
|
+
# choose formatter
|
861
|
+
if json_output:
|
862
|
+
|
863
|
+
def json_formatter(svc: ServiceStatus) -> Dict[str, Any]:
|
864
|
+
data = svc.to_dict()
|
865
|
+
if not verbose:
|
866
|
+
data.get("primary_version", {}).pop("config", None)
|
867
|
+
data.get("canary_version", {}).pop("config", None)
|
868
|
+
return data
|
869
|
+
|
870
|
+
formatter = json_formatter
|
871
|
+
else:
|
872
|
+
formatter = _format_service_output_data
|
873
|
+
|
874
|
+
total = 0
|
875
|
+
try:
|
876
|
+
iterator = anyscale.service.list(
|
877
|
+
service_id=service_id,
|
878
|
+
name=name,
|
879
|
+
state_filter=state_filter,
|
880
|
+
creator_id=creator_id,
|
881
|
+
cloud=cloud,
|
882
|
+
project=project,
|
883
|
+
include_archived=include_archived,
|
884
|
+
max_items=None if interactive else effective_max,
|
885
|
+
page_size=page_size,
|
886
|
+
sort_field=sort_field,
|
887
|
+
sort_order=sort_order,
|
888
|
+
)
|
889
|
+
total = display_list(
|
890
|
+
iterator=iter(iterator),
|
891
|
+
item_formatter=formatter,
|
892
|
+
table_creator=_create_service_list_table,
|
893
|
+
json_output=json_output,
|
894
|
+
page_size=page_size,
|
895
|
+
interactive=interactive,
|
896
|
+
max_items=effective_max,
|
897
|
+
console=console,
|
898
|
+
)
|
899
|
+
|
900
|
+
if not json_output:
|
901
|
+
if total > 0:
|
902
|
+
stderr.print(f"\nFetched {total} services.")
|
903
|
+
else:
|
904
|
+
stderr.print("\nNo services found.")
|
905
|
+
except Exception as e: # noqa: BLE001
|
906
|
+
log.error(f"Failed to list services: {e}")
|
907
|
+
sys.exit(1)
|
908
|
+
|
672
909
|
|
673
910
|
# TODO(mowen): Add cloud support for this when we refactor to new SDK method
|
674
911
|
@service_cli.command(name="rollback", help="Roll back a service.")
|
@@ -708,7 +945,6 @@ def rollback(
|
|
708
945
|
service_controller.rollback(service_id, max_surge_percent)
|
709
946
|
|
710
947
|
|
711
|
-
# TODO(mowen): Add cloud support for this when we refactor to new SDK method
|
712
948
|
@service_cli.command(name="terminate", help="Terminate a service.")
|
713
949
|
@click.option(
|
714
950
|
"--service-id", "--id", required=False, help="ID of service.",
|
anyscale/commands/util.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from copy import deepcopy
|
2
|
-
from typing import Dict, Tuple
|
2
|
+
from typing import Dict, Tuple, TypeVar
|
3
3
|
|
4
4
|
import click
|
5
5
|
|
@@ -136,9 +136,10 @@ def convert_kv_strings_to_dict(strings: Tuple[str]) -> Dict[str, str]:
|
|
136
136
|
return ret_dict
|
137
137
|
|
138
138
|
|
139
|
-
|
140
|
-
|
141
|
-
|
139
|
+
T = TypeVar("T", bound=WorkloadConfig)
|
140
|
+
|
141
|
+
|
142
|
+
def override_env_vars(config: T, overrides: Dict[str, str]) -> T:
|
142
143
|
"""Returns a new copy of the WorkloadConfig with env vars overridden.
|
143
144
|
|
144
145
|
This is a per-key override, so keys already in the config that are not specified in
|