anyscale 0.26.29__py3-none-any.whl → 0.26.30__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/__init__.py +10 -0
- anyscale/_private/anyscale_client/anyscale_client.py +69 -0
- anyscale/_private/anyscale_client/common.py +38 -0
- anyscale/_private/anyscale_client/fake_anyscale_client.py +11 -0
- anyscale/_private/docgen/__main__.py +4 -0
- anyscale/commands/command_examples.py +10 -0
- anyscale/commands/job_queue_commands.py +295 -104
- anyscale/commands/list_util.py +14 -1
- anyscale/commands/machine_pool_commands.py +14 -2
- anyscale/commands/service_commands.py +6 -12
- anyscale/commands/workspace_commands_v2.py +462 -25
- anyscale/controllers/job_controller.py +5 -210
- anyscale/job_queue/__init__.py +89 -0
- anyscale/job_queue/_private/job_queue_sdk.py +158 -0
- anyscale/job_queue/commands.py +130 -0
- anyscale/job_queue/models.py +284 -0
- anyscale/scripts.py +1 -1
- anyscale/utils/ssh_websocket_proxy.py +178 -0
- anyscale/version.py +1 -1
- {anyscale-0.26.29.dist-info → anyscale-0.26.30.dist-info}/METADATA +3 -1
- {anyscale-0.26.29.dist-info → anyscale-0.26.30.dist-info}/RECORD +26 -21
- {anyscale-0.26.29.dist-info → anyscale-0.26.30.dist-info}/LICENSE +0 -0
- {anyscale-0.26.29.dist-info → anyscale-0.26.30.dist-info}/NOTICE +0 -0
- {anyscale-0.26.29.dist-info → anyscale-0.26.30.dist-info}/WHEEL +0 -0
- {anyscale-0.26.29.dist-info → anyscale-0.26.30.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.29.dist-info → anyscale-0.26.30.dist-info}/top_level.txt +0 -0
@@ -1,172 +1,363 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from datetime import datetime
|
4
|
+
from enum import Enum
|
5
|
+
from functools import partial
|
6
|
+
from json import dumps as json_dumps
|
7
|
+
import sys
|
8
|
+
from typing import Dict, get_type_hints, List, Optional
|
2
9
|
|
3
10
|
import click
|
11
|
+
from rich.console import Console
|
12
|
+
from rich.table import Table
|
4
13
|
|
5
14
|
from anyscale.client.openapi_client.models.job_queue_sort_directive import (
|
6
15
|
JobQueueSortDirective,
|
7
16
|
)
|
8
17
|
from anyscale.client.openapi_client.models.job_queue_sort_field import JobQueueSortField
|
18
|
+
from anyscale.client.openapi_client.models.session_state import SessionState
|
9
19
|
from anyscale.client.openapi_client.models.sort_order import SortOrder
|
10
20
|
from anyscale.commands import command_examples
|
21
|
+
from anyscale.commands.list_util import (
|
22
|
+
display_list,
|
23
|
+
MAX_PAGE_SIZE,
|
24
|
+
NON_INTERACTIVE_DEFAULT_MAX_ITEMS,
|
25
|
+
validate_page_size,
|
26
|
+
)
|
11
27
|
from anyscale.commands.util import AnyscaleCommand
|
12
|
-
|
13
|
-
from anyscale.
|
28
|
+
import anyscale.job_queue
|
29
|
+
from anyscale.job_queue.models import JobQueueStatus, JobQueueStatusKeys
|
30
|
+
from anyscale.util import get_endpoint, get_user_info, validate_non_negative_arg
|
14
31
|
|
15
32
|
|
16
|
-
@click.group(
|
17
|
-
"job-queues", help="Interact with production job queues running on Anyscale."
|
18
|
-
)
|
33
|
+
@click.group("job-queue", help="Manage Anyscale Job Queues.")
|
19
34
|
def job_queue_cli() -> None:
|
20
35
|
pass
|
21
36
|
|
22
37
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
for field_str in sort_fields:
|
29
|
-
descending = field_str.startswith("-")
|
30
|
-
raw_field = field_str.lstrip("-").upper()
|
31
|
-
|
32
|
-
if raw_field not in JobQueueSortField.allowable_values:
|
33
|
-
raise click.UsageError(
|
34
|
-
f"{param} must be one of {', '.join([v.lower() for v in JobQueueSortField.allowable_values])}"
|
35
|
-
)
|
38
|
+
class ViewOption(Enum):
|
39
|
+
DEFAULT = "default"
|
40
|
+
STATS = "stats"
|
41
|
+
ALL = "all"
|
36
42
|
|
37
|
-
sort_directives.append(
|
38
|
-
JobQueueSortDirective(
|
39
|
-
sort_field=raw_field,
|
40
|
-
sort_order=SortOrder.DESC if descending else SortOrder.ASC,
|
41
|
-
)
|
42
|
-
)
|
43
43
|
|
44
|
-
|
44
|
+
VIEW_COLUMNS: Dict[ViewOption, List[JobQueueStatusKeys]] = {
|
45
|
+
ViewOption.DEFAULT: [
|
46
|
+
"name",
|
47
|
+
"id",
|
48
|
+
"state",
|
49
|
+
"creator_email",
|
50
|
+
"project_id",
|
51
|
+
"created_at",
|
52
|
+
],
|
53
|
+
ViewOption.STATS: [
|
54
|
+
"id",
|
55
|
+
"name",
|
56
|
+
"total_jobs",
|
57
|
+
"active_jobs",
|
58
|
+
"successful_jobs",
|
59
|
+
"failed_jobs",
|
60
|
+
],
|
61
|
+
ViewOption.ALL: [
|
62
|
+
"name",
|
63
|
+
"id",
|
64
|
+
"state",
|
65
|
+
"creator_email",
|
66
|
+
"project_id",
|
67
|
+
"created_at",
|
68
|
+
"max_concurrency",
|
69
|
+
"idle_timeout_s",
|
70
|
+
"cloud_id",
|
71
|
+
"user_provided_id",
|
72
|
+
"execution_mode",
|
73
|
+
"total_jobs",
|
74
|
+
"active_jobs",
|
75
|
+
"successful_jobs",
|
76
|
+
"failed_jobs",
|
77
|
+
],
|
78
|
+
}
|
45
79
|
|
46
80
|
|
47
81
|
@job_queue_cli.command(
|
48
82
|
name="list",
|
49
|
-
|
83
|
+
help="List job queues.",
|
50
84
|
cls=AnyscaleCommand,
|
51
85
|
example=command_examples.JOB_QUEUE_LIST,
|
52
86
|
)
|
87
|
+
@click.option("--id", "job_queue_id", help="ID of a job queue.")
|
88
|
+
@click.option("--name", type=str, help="Filter by name.")
|
89
|
+
@click.option("--cloud", type=str, help="Filter by cloud.")
|
90
|
+
@click.option("--project", type=str, help="Filter by project.")
|
91
|
+
@click.option("--include-all-users/--only-mine", default=False)
|
53
92
|
@click.option(
|
54
|
-
"--
|
55
|
-
|
56
|
-
|
57
|
-
help="Include job queues not created by current user.",
|
93
|
+
"--cluster-status",
|
94
|
+
type=click.Choice(SessionState.allowable_values, case_sensitive=False),
|
95
|
+
help="Filter by cluster status.",
|
58
96
|
)
|
59
97
|
@click.option(
|
60
98
|
"--view",
|
61
|
-
type=click.Choice([
|
62
|
-
default=
|
63
|
-
help="
|
64
|
-
callback=lambda
|
99
|
+
type=click.Choice([opt.value for opt in ViewOption], case_sensitive=False),
|
100
|
+
default=ViewOption.DEFAULT.value,
|
101
|
+
help="Columns view.",
|
102
|
+
callback=lambda _ctx, _param, value: ViewOption(value),
|
65
103
|
)
|
66
104
|
@click.option(
|
67
|
-
"--page",
|
68
|
-
default=
|
105
|
+
"--page-size",
|
106
|
+
default=10,
|
69
107
|
type=int,
|
70
|
-
|
71
|
-
|
108
|
+
callback=validate_page_size,
|
109
|
+
help=f"Items per page (max {MAX_PAGE_SIZE}).",
|
72
110
|
)
|
73
111
|
@click.option(
|
74
112
|
"--max-items",
|
75
|
-
required=False,
|
76
113
|
type=int,
|
77
|
-
help="Max items to show in list (only valid in interactive mode).",
|
78
114
|
callback=lambda ctx, param, value: validate_non_negative_arg(ctx, param, value)
|
79
115
|
if value
|
80
116
|
else None,
|
117
|
+
help="Non-interactive max items.",
|
81
118
|
)
|
82
119
|
@click.option(
|
83
120
|
"--sort",
|
84
|
-
"
|
121
|
+
"sort_dirs",
|
85
122
|
multiple=True,
|
86
|
-
default=[
|
87
|
-
|
88
|
-
Sort by column(s). Prefix column with - to sort in descending order.
|
89
|
-
Supported columns: {', '.join([v.lower() for v in JobQueueSortField.allowable_values])}.
|
90
|
-
""",
|
91
|
-
callback=lambda _, __, value: parse_sort_fields("sort", list(value)),
|
123
|
+
default=["-created_at"],
|
124
|
+
callback=lambda _ctx, _param, values: _parse_sort_fields("sort", list(values)),
|
92
125
|
)
|
126
|
+
@click.option("--no-interactive/--interactive", default=False)
|
93
127
|
@click.option(
|
94
|
-
"--
|
95
|
-
default=True,
|
96
|
-
help="--no-interactive disables the default interactive mode.",
|
128
|
+
"--json", "json_output", is_flag=True, default=False, help="JSON output.",
|
97
129
|
)
|
98
|
-
def list_job_queues(
|
130
|
+
def list_job_queues( # noqa: PLR0913
|
131
|
+
job_queue_id: Optional[str],
|
132
|
+
name: Optional[str],
|
133
|
+
cloud: Optional[str],
|
134
|
+
project: Optional[str],
|
135
|
+
cluster_status: Optional[str],
|
99
136
|
include_all_users: bool,
|
100
|
-
view:
|
101
|
-
|
102
|
-
max_items: int,
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
137
|
+
view: ViewOption,
|
138
|
+
page_size: int,
|
139
|
+
max_items: Optional[int],
|
140
|
+
sort_dirs: List[JobQueueSortDirective],
|
141
|
+
no_interactive: bool,
|
142
|
+
json_output: bool,
|
143
|
+
) -> None:
|
144
|
+
"""List and page job queues according to filters and view."""
|
145
|
+
if max_items and not no_interactive:
|
146
|
+
raise click.UsageError("--max-items only in non-interactive mode")
|
147
|
+
|
148
|
+
effective_max = max_items or NON_INTERACTIVE_DEFAULT_MAX_ITEMS
|
149
|
+
console = Console()
|
150
|
+
stderr = Console(stderr=True)
|
151
|
+
|
152
|
+
_print_list_diagnostics(
|
153
|
+
stderr=stderr,
|
154
|
+
job_queue_id=job_queue_id,
|
155
|
+
name=name,
|
112
156
|
include_all_users=include_all_users,
|
157
|
+
cloud=cloud,
|
158
|
+
project=project,
|
159
|
+
cluster_status=cluster_status,
|
113
160
|
view=view,
|
114
|
-
|
115
|
-
|
161
|
+
sort_dirs=sort_dirs,
|
162
|
+
no_interactive=no_interactive,
|
163
|
+
page_size=page_size,
|
164
|
+
effective_max=effective_max,
|
116
165
|
)
|
117
166
|
|
167
|
+
try:
|
168
|
+
user = get_user_info()
|
169
|
+
iterator = anyscale.job_queue.list(
|
170
|
+
job_queue_id=job_queue_id,
|
171
|
+
name=name,
|
172
|
+
creator_id=None if include_all_users else (user.id if user else None),
|
173
|
+
cloud=cloud,
|
174
|
+
project=project,
|
175
|
+
page_size=page_size,
|
176
|
+
max_items=None if not no_interactive else effective_max,
|
177
|
+
sorting_directives=sort_dirs,
|
178
|
+
)
|
179
|
+
cols = VIEW_COLUMNS[view]
|
180
|
+
table_fn = partial(_create_table, view)
|
181
|
+
|
182
|
+
def row_fn(jq: JobQueueStatus) -> Dict[str, str]:
|
183
|
+
data = _format_data(jq)
|
184
|
+
return {c: data[c] for c in cols}
|
185
|
+
|
186
|
+
total = display_list(
|
187
|
+
iterator=iter(iterator),
|
188
|
+
item_formatter=row_fn,
|
189
|
+
table_creator=table_fn,
|
190
|
+
json_output=json_output,
|
191
|
+
page_size=page_size,
|
192
|
+
interactive=not no_interactive,
|
193
|
+
max_items=effective_max,
|
194
|
+
console=console,
|
195
|
+
)
|
196
|
+
if not json_output:
|
197
|
+
stderr.print(f"Fetched {total} queues" if total else "No queues found.")
|
198
|
+
|
199
|
+
except Exception as e: # noqa: BLE001
|
200
|
+
stderr.print(f"Error: {e}", style="red")
|
201
|
+
sys.exit(1)
|
202
|
+
|
118
203
|
|
119
204
|
@job_queue_cli.command(
|
120
205
|
name="update",
|
121
|
-
|
206
|
+
help="Update job queue settings.",
|
122
207
|
cls=AnyscaleCommand,
|
123
208
|
example=command_examples.JOB_QUEUE_UPDATE,
|
124
209
|
)
|
210
|
+
@click.option("--id", "job_queue_id", required=True)
|
211
|
+
@click.option("--max-concurrency", type=int)
|
212
|
+
@click.option("--idle-timeout-s", type=int)
|
125
213
|
@click.option(
|
126
|
-
"--
|
127
|
-
)
|
128
|
-
@click.option(
|
129
|
-
"--name",
|
130
|
-
"job_queue_name",
|
131
|
-
required=False,
|
132
|
-
default=None,
|
133
|
-
help="Name of the job queue.",
|
134
|
-
)
|
135
|
-
@click.option(
|
136
|
-
"--max-concurrency",
|
137
|
-
required=False,
|
138
|
-
default=None,
|
139
|
-
help="Maximum concurrency of the job queue",
|
140
|
-
)
|
141
|
-
@click.option(
|
142
|
-
"--idle-timeout-s",
|
143
|
-
required=False,
|
144
|
-
default=None,
|
145
|
-
help="Idle timeout of the job queue",
|
214
|
+
"--json", "json_output", is_flag=True, default=False, help="JSON output.",
|
146
215
|
)
|
147
216
|
def update_job_queue(
|
148
|
-
job_queue_id: str,
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
217
|
+
job_queue_id: str,
|
218
|
+
max_concurrency: Optional[int],
|
219
|
+
idle_timeout_s: Optional[int],
|
220
|
+
json_output: bool,
|
221
|
+
) -> None:
|
222
|
+
"""Update the max_concurrency or idle_timeout_s of a job queue."""
|
223
|
+
if max_concurrency is None and idle_timeout_s is None:
|
224
|
+
raise click.ClickException("Specify --max-concurrency or --idle-timeout-s")
|
225
|
+
stderr = Console(stderr=True)
|
226
|
+
stderr.print(f"Updating job queue '{job_queue_id}'...")
|
227
|
+
try:
|
228
|
+
jq = anyscale.job_queue.update(
|
229
|
+
job_queue_id=job_queue_id,
|
230
|
+
job_queue_name=None,
|
231
|
+
max_concurrency=max_concurrency,
|
232
|
+
idle_timeout_s=idle_timeout_s,
|
233
|
+
)
|
234
|
+
if json_output:
|
235
|
+
Console().print_json(json_dumps(_format_data(jq), indent=2))
|
236
|
+
else:
|
237
|
+
_display_single(jq, stderr, ViewOption.ALL)
|
238
|
+
except Exception as e: # noqa: BLE001
|
239
|
+
stderr.print(f"Update failed: {e}", style="red")
|
240
|
+
sys.exit(1)
|
159
241
|
|
160
242
|
|
161
243
|
@job_queue_cli.command(
|
162
|
-
name="
|
163
|
-
|
244
|
+
name="status",
|
245
|
+
help="Show job queue details.",
|
164
246
|
cls=AnyscaleCommand,
|
165
247
|
example=command_examples.JOB_QUEUE_INFO,
|
166
248
|
)
|
249
|
+
@click.option("--id", "job_queue_id", required=True)
|
167
250
|
@click.option(
|
168
|
-
"--
|
251
|
+
"--view",
|
252
|
+
type=click.Choice([opt.value for opt in ViewOption], case_sensitive=False),
|
253
|
+
default=ViewOption.DEFAULT.value,
|
254
|
+
help="Columns view.",
|
255
|
+
callback=lambda _ctx, _param, value: ViewOption(value),
|
169
256
|
)
|
170
|
-
|
171
|
-
|
172
|
-
|
257
|
+
@click.option(
|
258
|
+
"--json", "json_output", is_flag=True, default=False, help="JSON output.",
|
259
|
+
)
|
260
|
+
def status(job_queue_id: str, view: ViewOption, json_output: bool,) -> None:
|
261
|
+
"""Fetch and display a single job queue's details."""
|
262
|
+
stderr = Console(stderr=True)
|
263
|
+
stderr.print(f"Fetching job queue '{job_queue_id}'...")
|
264
|
+
try:
|
265
|
+
jq = anyscale.job_queue.status(job_queue_id=job_queue_id)
|
266
|
+
if json_output:
|
267
|
+
Console().print_json(json_dumps(_format_data(jq), indent=2))
|
268
|
+
else:
|
269
|
+
_display_single(jq, stderr, view)
|
270
|
+
except Exception as e: # noqa: BLE001
|
271
|
+
stderr.print(f"Failed: {e}", style="red")
|
272
|
+
sys.exit(1)
|
273
|
+
|
274
|
+
|
275
|
+
def _parse_sort_fields(
|
276
|
+
param: str, sort_fields: List[str],
|
277
|
+
) -> List[JobQueueSortDirective]:
|
278
|
+
"""Convert a list of string fields into JobQueueSortDirective objects."""
|
279
|
+
directives: List[JobQueueSortDirective] = []
|
280
|
+
opts = ", ".join(v.lower() for v in JobQueueSortField.allowable_values)
|
281
|
+
for field_str in sort_fields:
|
282
|
+
desc = field_str.startswith("-")
|
283
|
+
raw = field_str.lstrip("-").upper()
|
284
|
+
if raw not in JobQueueSortField.allowable_values:
|
285
|
+
raise click.UsageError(f"{param} must be one of {opts}")
|
286
|
+
directives.append(
|
287
|
+
JobQueueSortDirective(
|
288
|
+
sort_field=raw, sort_order=SortOrder.DESC if desc else SortOrder.ASC,
|
289
|
+
)
|
290
|
+
)
|
291
|
+
return directives
|
292
|
+
|
293
|
+
|
294
|
+
def _create_table(view: ViewOption, show_header: bool) -> Table:
|
295
|
+
"""Create a Rich Table with columns based on the selected view."""
|
296
|
+
table = Table(show_header=show_header, expand=True)
|
297
|
+
for key in VIEW_COLUMNS[view]:
|
298
|
+
table.add_column(key.replace("_", " ").upper(), overflow="fold")
|
299
|
+
return table
|
300
|
+
|
301
|
+
|
302
|
+
def _format_data(jq: JobQueueStatus) -> Dict[str, str]:
|
303
|
+
"""Format a JobQueueStatus object into a dictionary of strings for display."""
|
304
|
+
data = {}
|
305
|
+
for key in get_type_hints(JobQueueStatus):
|
306
|
+
value = getattr(jq, key, None)
|
307
|
+
if isinstance(value, datetime):
|
308
|
+
data[key] = value.strftime("%Y-%m-%d %H:%M:%S") if value else ""
|
309
|
+
elif value is None:
|
310
|
+
data[key] = ""
|
311
|
+
else:
|
312
|
+
data[key] = str(value)
|
313
|
+
return data
|
314
|
+
|
315
|
+
|
316
|
+
def _display_single(jq: JobQueueStatus, stderr: Console, view: ViewOption,) -> None:
|
317
|
+
"""Display a single job queue's details in a table using the selected view.
|
318
|
+
|
319
|
+
Args:
|
320
|
+
jq: The JobQueueStatus object to display.
|
321
|
+
stderr: The Rich Console object to print to.
|
322
|
+
view: The ViewOption determining which columns to display.
|
323
|
+
"""
|
324
|
+
table = _create_table(view, show_header=True)
|
325
|
+
data = _format_data(jq)
|
326
|
+
table.add_row(*(data[col] for col in VIEW_COLUMNS[view]))
|
327
|
+
stderr.print(table)
|
328
|
+
|
329
|
+
|
330
|
+
def _print_list_diagnostics( # noqa: PLR0913
|
331
|
+
stderr: Console,
|
332
|
+
job_queue_id: Optional[str],
|
333
|
+
name: Optional[str],
|
334
|
+
include_all_users: bool,
|
335
|
+
cloud: Optional[str],
|
336
|
+
project: Optional[str],
|
337
|
+
cluster_status: Optional[str],
|
338
|
+
view: ViewOption,
|
339
|
+
sort_dirs: List[JobQueueSortDirective],
|
340
|
+
no_interactive: bool,
|
341
|
+
page_size: int,
|
342
|
+
effective_max: int,
|
343
|
+
) -> None:
|
344
|
+
"""Prints diagnostic information for the list_job_queues command."""
|
345
|
+
stderr.print("[bold]Listing with:[/]")
|
346
|
+
stderr.print(f"id: {job_queue_id or '<any>'}")
|
347
|
+
stderr.print(f"name: {name or '<any>'}")
|
348
|
+
stderr.print(f"creator: {'all' if include_all_users else 'mine'}")
|
349
|
+
stderr.print(f"cloud: {cloud or '<any>'}")
|
350
|
+
stderr.print(f"project: {project or '<any>'}")
|
351
|
+
stderr.print(f"cluster: {cluster_status or '<any>'}")
|
352
|
+
stderr.print(f"view: {view.value}")
|
353
|
+
|
354
|
+
formatted_sort_dirs = [
|
355
|
+
f"{'-' if d.sort_order == SortOrder.DESC else ''}{(d.sort_field or '').lower()}"
|
356
|
+
for d in sort_dirs
|
357
|
+
]
|
358
|
+
stderr.print(f"sort: {formatted_sort_dirs}")
|
359
|
+
|
360
|
+
stderr.print(f"mode: {'batch' if no_interactive else 'interactive'}")
|
361
|
+
stderr.print(f"page-size: {page_size}")
|
362
|
+
stderr.print(f"max-items: {effective_max}")
|
363
|
+
stderr.print(f"UI: {get_endpoint('/job-queues')}\n")
|
anyscale/commands/list_util.py
CHANGED
@@ -2,10 +2,23 @@ import itertools
|
|
2
2
|
from json import dumps as json_dumps
|
3
3
|
from typing import Any, Callable, Dict, Iterator, List, Optional
|
4
4
|
|
5
|
+
import click
|
5
6
|
from rich.console import Console
|
6
7
|
from rich.table import Table
|
7
8
|
|
8
|
-
from anyscale.util import AnyscaleJSONEncoder
|
9
|
+
from anyscale.util import AnyscaleJSONEncoder, validate_non_negative_arg
|
10
|
+
|
11
|
+
|
12
|
+
MAX_PAGE_SIZE = 50
|
13
|
+
NON_INTERACTIVE_DEFAULT_MAX_ITEMS = 10
|
14
|
+
|
15
|
+
|
16
|
+
def validate_page_size(ctx, param, value):
|
17
|
+
"""Click callback to validate page size argument."""
|
18
|
+
value = validate_non_negative_arg(ctx, param, value)
|
19
|
+
if value is not None and value > MAX_PAGE_SIZE:
|
20
|
+
raise click.BadParameter(f"must be less than or equal to {MAX_PAGE_SIZE}.")
|
21
|
+
return value
|
9
22
|
|
10
23
|
|
11
24
|
def _paginate(iterator: Iterator[Any], page_size: Optional[int]) -> Iterator[List[Any]]:
|
@@ -260,7 +260,13 @@ def list_machine_pools(format_: str) -> None:
|
|
260
260
|
raise click.ClickException(f"Invalid output format '{format}'.")
|
261
261
|
|
262
262
|
|
263
|
-
@machine_pool_cli.command(
|
263
|
+
@machine_pool_cli.command(
|
264
|
+
name="attach",
|
265
|
+
help="Attach a machine pool to a cloud.",
|
266
|
+
cls=AnyscaleCommand,
|
267
|
+
example=command_examples.MACHINE_POOL_ATTACH_EXAMPLE,
|
268
|
+
is_beta=True,
|
269
|
+
)
|
264
270
|
@click.option("--name", type=str, required=True, help="Provide a machine pool name.")
|
265
271
|
@click.option("--cloud", type=str, required=True, help="Provide a cloud name.")
|
266
272
|
def attach_machine_pool_to_cloud(name: str, cloud: str) -> None:
|
@@ -271,7 +277,13 @@ def attach_machine_pool_to_cloud(name: str, cloud: str) -> None:
|
|
271
277
|
print(f"Attached machine pool '{name}' to cloud '{cloud}'.")
|
272
278
|
|
273
279
|
|
274
|
-
@machine_pool_cli.command(
|
280
|
+
@machine_pool_cli.command(
|
281
|
+
name="detach",
|
282
|
+
help="Detach a machine pool from a cloud.",
|
283
|
+
cls=AnyscaleCommand,
|
284
|
+
example=command_examples.MACHINE_POOL_DETACH_EXAMPLE,
|
285
|
+
is_beta=True,
|
286
|
+
)
|
275
287
|
@click.option("--name", type=str, required=True, help="Provide a machine pool name.")
|
276
288
|
@click.option("--cloud", type=str, required=True, help="Provide a cloud name.")
|
277
289
|
def detach_machine_pool_from_cloud(name: str, cloud: str) -> None:
|
@@ -13,7 +13,12 @@ import yaml
|
|
13
13
|
from anyscale._private.models.image_uri import ImageURI
|
14
14
|
from anyscale.cli_logger import BlockLogger
|
15
15
|
from anyscale.commands import command_examples
|
16
|
-
from anyscale.commands.list_util import
|
16
|
+
from anyscale.commands.list_util import (
|
17
|
+
display_list,
|
18
|
+
MAX_PAGE_SIZE,
|
19
|
+
NON_INTERACTIVE_DEFAULT_MAX_ITEMS,
|
20
|
+
validate_page_size,
|
21
|
+
)
|
17
22
|
from anyscale.commands.util import (
|
18
23
|
AnyscaleCommand,
|
19
24
|
convert_kv_strings_to_dict,
|
@@ -642,17 +647,6 @@ def rollout( # noqa: PLR0913
|
|
642
647
|
)
|
643
648
|
|
644
649
|
|
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
650
|
def validate_max_items(ctx, param, value):
|
657
651
|
if value is None:
|
658
652
|
return None
|