anyscale 0.24.88__py3-none-any.whl → 0.25.5__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 +56 -0
- anyscale/_private/anyscale_client/anyscale_client.py +179 -28
- anyscale/_private/anyscale_client/common.py +109 -2
- anyscale/_private/anyscale_client/fake_anyscale_client.py +239 -1
- anyscale/_private/docgen/README.md +1 -1
- anyscale/_private/docgen/__main__.py +71 -21
- anyscale/_private/docgen/api.md +13 -20
- anyscale/_private/docgen/generator.py +3 -2
- anyscale/_private/docgen/models.md +4 -49
- anyscale/_private/workload/workload_config.py +21 -7
- anyscale/aggregated_instance_usage/__init__.py +1 -1
- anyscale/aggregated_instance_usage/commands.py +2 -4
- anyscale/aggregated_instance_usage/models.py +8 -8
- anyscale/client/README.md +25 -22
- anyscale/client/openapi_client/__init__.py +16 -14
- anyscale/client/openapi_client/api/default_api.py +1139 -959
- anyscale/client/openapi_client/models/__init__.py +16 -14
- anyscale/client/openapi_client/models/baseimagesenum.py +43 -1
- anyscale/client/openapi_client/models/{session_event_types.py → cloud_deployment_config.py} +35 -24
- anyscale/client/openapi_client/models/{platformfinetuningjob_response.py → clouddeploymentconfig_response.py} +11 -11
- anyscale/client/openapi_client/models/{log_level_types.py → cluster_event_source.py} +12 -7
- anyscale/client/openapi_client/models/{company_size.py → cluster_size.py} +10 -10
- anyscale/client/openapi_client/models/cluster_status_details.py +2 -1
- anyscale/client/openapi_client/models/{sessionevent_list_response.py → clusterevent_list_response.py} +15 -15
- anyscale/client/openapi_client/models/create_experimental_workspace.py +29 -1
- anyscale/client/openapi_client/models/create_notification_channel_record.py +29 -3
- anyscale/client/openapi_client/models/decorated_interactive_session.py +1 -57
- anyscale/client/openapi_client/models/decorated_job.py +1 -57
- anyscale/client/openapi_client/models/decorated_job_submission.py +1 -29
- anyscale/client/openapi_client/models/decorated_production_job.py +1 -29
- anyscale/client/openapi_client/models/decorated_session.py +1 -57
- anyscale/client/openapi_client/models/decorated_unified_job.py +1 -30
- anyscale/client/openapi_client/models/{resubmit_ft_job_request.py → describe_machine_pool_request.py} +21 -20
- anyscale/client/openapi_client/models/describe_machine_pool_response.py +123 -0
- anyscale/client/openapi_client/models/describemachinepoolresponse_response.py +121 -0
- anyscale/client/openapi_client/models/ha_jobs_sort_field.py +1 -2
- anyscale/client/openapi_client/models/internal_production_job.py +1 -29
- anyscale/client/openapi_client/models/jobs_sort_field.py +1 -2
- anyscale/client/openapi_client/models/machine_allocation_state.py +3 -1
- anyscale/client/openapi_client/models/machine_state_info.py +326 -0
- anyscale/client/openapi_client/models/{fine_tuning_job_status.py → notification_channel_slack_config.py} +34 -16
- anyscale/client/openapi_client/models/organization_marketing_questions.py +80 -54
- anyscale/client/openapi_client/models/request_state_info.py +210 -0
- anyscale/client/openapi_client/models/{platformfinetuningjob_list_response.py → scheduler_info.py} +43 -38
- anyscale/client/openapi_client/models/serve_deployment_fast_api_docs_status.py +123 -0
- anyscale/client/openapi_client/models/serve_deployment_state.py +2 -1
- anyscale/client/openapi_client/models/servedeploymentfastapidocsstatus_response.py +121 -0
- anyscale/client/openapi_client/models/sessions_sort_field.py +1 -2
- anyscale/client/openapi_client/models/supportedbaseimagesenum.py +43 -1
- anyscale/client/openapi_client/models/unified_job_sort_field.py +1 -2
- anyscale/client/openapi_client/models/update_cloud_collaborator.py +121 -0
- anyscale/client/openapi_client/models/usage_by_cluster.py +28 -1
- anyscale/client/openapi_client/models/usage_by_user.py +30 -3
- anyscale/client/openapi_client/models/workload_info.py +210 -0
- anyscale/cloud/__init__.py +83 -0
- anyscale/cloud/_private/cloud_sdk.py +25 -0
- anyscale/cloud/commands.py +45 -0
- anyscale/cloud/models.py +91 -0
- anyscale/cluster_compute.py +1 -1
- anyscale/commands/aggregated_instance_usage_commands.py +4 -4
- anyscale/commands/cloud_commands.py +87 -14
- anyscale/commands/command_examples.py +65 -0
- anyscale/commands/job_commands.py +15 -3
- anyscale/commands/machine_pool_commands.py +113 -1
- anyscale/commands/organization_invitation_commands.py +98 -0
- anyscale/commands/project_commands.py +52 -2
- anyscale/commands/resource_quota_commands.py +98 -11
- anyscale/commands/service_account_commands.py +65 -8
- anyscale/commands/service_commands.py +61 -1
- anyscale/commands/session_commands_hidden.py +5 -1
- anyscale/commands/user_commands.py +1 -1
- anyscale/commands/util.py +2 -2
- anyscale/commands/workspace_commands.py +1 -1
- anyscale/connect.py +1 -1
- anyscale/connect_utils/project.py +7 -4
- anyscale/controllers/cloud_controller.py +63 -30
- anyscale/controllers/cloud_functional_verification_controller.py +1 -1
- anyscale/controllers/cluster_controller.py +3 -11
- anyscale/controllers/compute_config_controller.py +1 -1
- anyscale/controllers/experimental_integrations_controller.py +1 -1
- anyscale/controllers/job_controller.py +8 -6
- anyscale/controllers/list_controller.py +2 -2
- anyscale/controllers/machine_pool_controller.py +12 -1
- anyscale/controllers/project_controller.py +4 -3
- anyscale/controllers/schedule_controller.py +1 -1
- anyscale/controllers/service_controller.py +1 -1
- anyscale/controllers/workspace_controller.py +1 -1
- anyscale/models/job_model.py +1 -1
- anyscale/organization_invitation/__init__.py +61 -0
- anyscale/organization_invitation/_private/organization_invitation_sdk.py +24 -0
- anyscale/organization_invitation/commands.py +84 -0
- anyscale/organization_invitation/models.py +45 -0
- anyscale/project/__init__.py +35 -0
- anyscale/project/_private/project_sdk.py +27 -0
- anyscale/project/commands.py +56 -0
- anyscale/project/models.py +91 -0
- anyscale/{project.py → project_utils.py} +3 -4
- anyscale/resource_quota/__init__.py +99 -0
- anyscale/resource_quota/_private/resource_quota_sdk.py +120 -0
- anyscale/resource_quota/commands.py +150 -0
- anyscale/resource_quota/models.py +303 -0
- anyscale/scripts.py +4 -0
- anyscale/sdk/anyscale_client/__init__.py +0 -5
- anyscale/sdk/anyscale_client/api/default_api.py +119 -150
- anyscale/sdk/anyscale_client/models/__init__.py +0 -5
- anyscale/sdk/anyscale_client/models/baseimagesenum.py +43 -1
- anyscale/sdk/anyscale_client/models/cluster_status_details.py +2 -1
- anyscale/sdk/anyscale_client/models/jobs_sort_field.py +1 -2
- anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +43 -1
- anyscale/sdk/anyscale_client/sdk.py +1 -1
- anyscale/service/__init__.py +21 -0
- anyscale/service/_private/service_sdk.py +13 -0
- anyscale/service/commands.py +35 -0
- anyscale/service_account/__init__.py +88 -0
- anyscale/service_account/_private/service_account_sdk.py +101 -0
- anyscale/service_account/commands.py +147 -0
- anyscale/service_account/models.py +66 -0
- anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
- anyscale/shared_anyscale_utils/utils/id_gen.py +2 -0
- anyscale/user/__init__.py +1 -1
- anyscale/user/commands.py +1 -1
- anyscale/user/models.py +25 -15
- anyscale/util.py +23 -0
- anyscale/utils/cloud_utils.py +1 -1
- anyscale/version.py +1 -1
- anyscale/workspace_utils.py +1 -1
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/METADATA +1 -5
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/RECORD +134 -119
- anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +0 -156
- anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +0 -353
- anyscale/client/openapi_client/models/finish_ft_job_request.py +0 -204
- anyscale/client/openapi_client/models/platform_fine_tuning_job.py +0 -577
- anyscale/client/openapi_client/models/session_event.py +0 -267
- anyscale/client/openapi_client/models/session_event_cause.py +0 -150
- anyscale/controllers/resource_quota_controller.py +0 -183
- anyscale/controllers/service_account_controller.py +0 -168
- anyscale/sdk/anyscale_client/models/log_level_types.py +0 -100
- anyscale/sdk/anyscale_client/models/session_event.py +0 -267
- anyscale/sdk/anyscale_client/models/session_event_cause.py +0 -150
- anyscale/sdk/anyscale_client/models/session_event_types.py +0 -111
- anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +0 -147
- anyscale/utils/imports/azure.py +0 -14
- /anyscale/{cloud.py → cloud_utils.py} +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/LICENSE +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/NOTICE +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/WHEEL +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/entry_points.txt +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/top_level.txt +0 -0
@@ -217,6 +217,10 @@ $ anyscale machine-pool update --name can-testing --spec-file spec.yaml
|
|
217
217
|
Updated machine pool 'can-testing'.
|
218
218
|
"""
|
219
219
|
|
220
|
+
MACHINE_POOL_DESCRIBE_EXAMPLE = """\
|
221
|
+
$ anyscale machine-pool describe --name can-testing --mode machines
|
222
|
+
"""
|
223
|
+
|
220
224
|
MACHINE_POOL_DELETE_EXAMPLE = """\
|
221
225
|
$ anyscale machine-pool delete --name can-testing
|
222
226
|
Deleted machine pool 'can-testing'.
|
@@ -498,4 +502,65 @@ USER_BATCH_CREATE_EXAMPLE = """\
|
|
498
502
|
$ anyscale user batch-create --users-file users.yaml
|
499
503
|
(anyscale +0.5s) Creating users...
|
500
504
|
(anyscale +0.8s) 2 users created.
|
505
|
+
|
506
|
+
$ cat users_file.yaml
|
507
|
+
create_users:
|
508
|
+
- name: name1
|
509
|
+
email: test1@anyscale.com
|
510
|
+
password: ''
|
511
|
+
is_sso_user: false
|
512
|
+
lastname: lastname1
|
513
|
+
title: title1
|
514
|
+
- name: name2
|
515
|
+
email: test2@anyscale.com
|
516
|
+
password: ''
|
517
|
+
is_sso_user: false
|
518
|
+
lastname: lastname2
|
519
|
+
title: title2
|
520
|
+
"""
|
521
|
+
|
522
|
+
ORGANIZATION_INVITATION_CREATE_EXAMPLE = """\
|
523
|
+
$ anyscale organization-invitation create --emails test1@anyscale.com,test2@anyscale.com
|
524
|
+
(anyscale +0.5s) Creating organization invitations...
|
525
|
+
(anyscale +1.7s) Organization invitations sent to: test1@anyscale.com, test2@anyscale.com
|
526
|
+
"""
|
527
|
+
|
528
|
+
ORGANIZATION_INVITATION_LIST_EXAMPLE = """\
|
529
|
+
$ anyscale organization-invitation list
|
530
|
+
ID Email Created At Expires At
|
531
|
+
------------- ----------------- ------------------- -------------------
|
532
|
+
orginv_abcedf test@anyscale.com 11/25/2024 10:24 PM 12/02/2024 10:24 PM
|
533
|
+
"""
|
534
|
+
|
535
|
+
ORGANIZATION_INVITATION_DELETE_EXAMPLE = """\
|
536
|
+
$ anyscale organization-invitation delete --email test@anyscale.com
|
537
|
+
(anyscale +0.6s) Organization invitation for test@anyscale.com deleted.
|
538
|
+
"""
|
539
|
+
|
540
|
+
PROJECT_ADD_COLLABORATORS_EXAMPLE = """\
|
541
|
+
$ anyscale project add-collaborators --cloud cloud_name --project project_name --users-file collaborators.yaml
|
542
|
+
(anyscale +1.3s) Successfully added 3 collaborators to project project_name.
|
543
|
+
$ cat collaborators.yaml
|
544
|
+
collaborators:
|
545
|
+
- email: "test1@anyscale.com"
|
546
|
+
permission_level: "write"
|
547
|
+
- email: "test2@anyscale.com"
|
548
|
+
permission_level: "readonly"
|
549
|
+
- email: "test3@anyscale.com"
|
550
|
+
permission_level: "owner"
|
551
|
+
"""
|
552
|
+
|
553
|
+
CLOUD_ADD_COLLABORATORS_EXAMPLE = """\
|
554
|
+
$ anyscale cloud add-collaborators --cloud cloud_name --users-file collaborators.yaml
|
555
|
+
(anyscale +1.3s) Successfully added 2 collaborators to cloud cloud_name.
|
556
|
+
$ cat collaborators.yaml
|
557
|
+
collaborators:
|
558
|
+
- email: "test1@anyscale.com"
|
559
|
+
permission_level: "write"
|
560
|
+
- email: "test2@anyscale.com"
|
561
|
+
permission_level: "readonly"
|
562
|
+
"""
|
563
|
+
|
564
|
+
SERVICE_ARCHIVE_EXAMPLE = """\
|
565
|
+
$ anyscale service archive --name my_service
|
501
566
|
"""
|
@@ -2,7 +2,7 @@ from io import StringIO
|
|
2
2
|
from json import dumps as json_dumps
|
3
3
|
import pathlib
|
4
4
|
from subprocess import list2cmdline
|
5
|
-
from typing import Optional, Tuple
|
5
|
+
from typing import List, Optional, Tuple
|
6
6
|
|
7
7
|
import click
|
8
8
|
import yaml
|
@@ -10,6 +10,7 @@ import yaml
|
|
10
10
|
import anyscale
|
11
11
|
from anyscale._private.models.image_uri import ImageURI
|
12
12
|
from anyscale.cli_logger import BlockLogger
|
13
|
+
from anyscale.client.openapi_client.models.ha_job_states import HaJobStates
|
13
14
|
from anyscale.commands import command_examples
|
14
15
|
from anyscale.commands.util import (
|
15
16
|
AnyscaleCommand,
|
@@ -18,7 +19,7 @@ from anyscale.commands.util import (
|
|
18
19
|
)
|
19
20
|
from anyscale.controllers.job_controller import JobController
|
20
21
|
from anyscale.job.models import JobConfig, JobLogMode, JobState, JobStatus
|
21
|
-
from anyscale.util import validate_non_negative_arg
|
22
|
+
from anyscale.util import validate_list_jobs_state_filter, validate_non_negative_arg
|
22
23
|
|
23
24
|
|
24
25
|
log = BlockLogger() # CLI Logger
|
@@ -118,7 +119,7 @@ def job_cli() -> None:
|
|
118
119
|
required=False,
|
119
120
|
default=None,
|
120
121
|
type=str,
|
121
|
-
help="Path to a requirements.txt file containing dependencies for the job.
|
122
|
+
help="Path to a requirements.txt file containing dependencies for the job. Anyscale installs these dependencies on top of the image. If you run a job from a workspace, the default is to use the workspace dependencies, but specifying this option overrides them.",
|
122
123
|
)
|
123
124
|
@click.option(
|
124
125
|
"--py-module",
|
@@ -386,6 +387,15 @@ and override the entrypoint with `python main.py`.
|
|
386
387
|
help="Max items to show in list.",
|
387
388
|
callback=validate_non_negative_arg,
|
388
389
|
)
|
390
|
+
@click.option(
|
391
|
+
"--state",
|
392
|
+
"-s",
|
393
|
+
"states",
|
394
|
+
required=False,
|
395
|
+
multiple=True,
|
396
|
+
help=f"Filter jobs by state. Accepts one or more states. Allowed states: {', '.join(HaJobStates.allowable_values)}",
|
397
|
+
callback=validate_list_jobs_state_filter,
|
398
|
+
)
|
389
399
|
def list( # noqa: A001
|
390
400
|
name: Optional[str],
|
391
401
|
id: Optional[str], # noqa: A002
|
@@ -393,6 +403,7 @@ def list( # noqa: A001
|
|
393
403
|
include_all_users: bool,
|
394
404
|
include_archived: bool,
|
395
405
|
max_items: int,
|
406
|
+
states: List[str],
|
396
407
|
) -> None:
|
397
408
|
job_controller = JobController()
|
398
409
|
job_controller.list(
|
@@ -402,6 +413,7 @@ def list( # noqa: A001
|
|
402
413
|
include_all_users=include_all_users,
|
403
414
|
include_archived=include_archived,
|
404
415
|
max_items=max_items,
|
416
|
+
states=states,
|
405
417
|
)
|
406
418
|
|
407
419
|
|
@@ -2,10 +2,16 @@
|
|
2
2
|
This file holds all of the CLI commands for the "anyscale machine-pool" path.
|
3
3
|
"""
|
4
4
|
|
5
|
+
import json
|
6
|
+
|
5
7
|
import click
|
6
8
|
import tabulate
|
7
9
|
import yaml
|
8
10
|
|
11
|
+
from anyscale.client.openapi_client.models import (
|
12
|
+
DescribeMachinePoolResponse,
|
13
|
+
SchedulerInfo,
|
14
|
+
)
|
9
15
|
from anyscale.commands import command_examples
|
10
16
|
from anyscale.commands.util import AnyscaleCommand
|
11
17
|
from anyscale.controllers.machine_pool_controller import MachinePoolController
|
@@ -71,6 +77,110 @@ def update_machine_pool(name: str, spec_file: str) -> None:
|
|
71
77
|
print(f"Updated machine pool '{name}'.")
|
72
78
|
|
73
79
|
|
80
|
+
@machine_pool_cli.command(
|
81
|
+
name="describe",
|
82
|
+
help="Describe a machine pool in Anyscale.",
|
83
|
+
cls=AnyscaleCommand,
|
84
|
+
example=command_examples.MACHINE_POOL_DESCRIBE_EXAMPLE,
|
85
|
+
is_beta=True,
|
86
|
+
)
|
87
|
+
@click.option("--name", type=str, required=True, help="Provide a machine pool name.")
|
88
|
+
@click.option(
|
89
|
+
"--format",
|
90
|
+
"format_",
|
91
|
+
type=str,
|
92
|
+
default="table",
|
93
|
+
required=False,
|
94
|
+
help="Output format (table, json).",
|
95
|
+
)
|
96
|
+
def describe(name: str, format_: str) -> None:
|
97
|
+
machine_pool_controller = MachinePoolController()
|
98
|
+
response: DescribeMachinePoolResponse = machine_pool_controller.describe_machine_pool(
|
99
|
+
machine_pool_name=name
|
100
|
+
)
|
101
|
+
scheduler_info: SchedulerInfo = response.scheduler_info # type: ignore
|
102
|
+
if format_ == "json":
|
103
|
+
print(json.dumps(scheduler_info.to_dict(), default=str))
|
104
|
+
elif format_ == "table":
|
105
|
+
machines_table = []
|
106
|
+
columns = [
|
107
|
+
"MACHINE ID",
|
108
|
+
"TYPE",
|
109
|
+
"PARTITION",
|
110
|
+
"STATE",
|
111
|
+
"WORKLOAD DETAILS",
|
112
|
+
"WORKLOAD SCORE",
|
113
|
+
"WORKLOAD START TIME",
|
114
|
+
"CLOUD INSTANCE ID",
|
115
|
+
]
|
116
|
+
for row in scheduler_info.machines:
|
117
|
+
machines_table.append(
|
118
|
+
[
|
119
|
+
row.machine_id,
|
120
|
+
row.machine_type,
|
121
|
+
row.partition,
|
122
|
+
row.allocation_state,
|
123
|
+
f"{row.workload_info.workload_type}/{row.workload_info.workload_name}/{row.workload_info.workload_cloud}"
|
124
|
+
if row.workload_info.workload_name
|
125
|
+
else "",
|
126
|
+
row.workload_score,
|
127
|
+
str(
|
128
|
+
row.workload_info.workload_start_time.astimezone().strftime(
|
129
|
+
"%m/%d/%Y %I:%M:%S %p %Z"
|
130
|
+
)
|
131
|
+
)
|
132
|
+
if row.workload_info.workload_name
|
133
|
+
else "",
|
134
|
+
row.cloud_instance_id,
|
135
|
+
]
|
136
|
+
)
|
137
|
+
|
138
|
+
# Sort by (type, partition, state, workload start time)
|
139
|
+
machines_table.sort(key=lambda x: (x[1], x[2], x[3], x[6]))
|
140
|
+
|
141
|
+
print("Machines:")
|
142
|
+
print(
|
143
|
+
tabulate.tabulate(
|
144
|
+
machines_table, tablefmt="outline", headers=columns, stralign="left"
|
145
|
+
)
|
146
|
+
)
|
147
|
+
|
148
|
+
requests_table = []
|
149
|
+
columns = [
|
150
|
+
"SIZE",
|
151
|
+
"MACHINE TYPE",
|
152
|
+
"WORKLOAD DETAILS",
|
153
|
+
"WORKLOAD START TIME",
|
154
|
+
"WORKLOAD CLOUD",
|
155
|
+
"PARTITION SCORES",
|
156
|
+
]
|
157
|
+
for row in scheduler_info.requests:
|
158
|
+
requests_table.append(
|
159
|
+
[
|
160
|
+
row.size,
|
161
|
+
row.machine_type,
|
162
|
+
f"{row.workload_info.workload_type}/{row.workload_info.workload_name}",
|
163
|
+
str(
|
164
|
+
row.workload_info.workload_start_time.astimezone().strftime(
|
165
|
+
"%m/%d/%Y %I:%M:%S %p %Z"
|
166
|
+
)
|
167
|
+
)
|
168
|
+
if row.workload_info.workload_name
|
169
|
+
else "",
|
170
|
+
row.workload_info.workload_cloud,
|
171
|
+
row.partition_scores,
|
172
|
+
]
|
173
|
+
)
|
174
|
+
|
175
|
+
# Sort by (machine type, workload start time, size)
|
176
|
+
print("Requests:")
|
177
|
+
print(
|
178
|
+
tabulate.tabulate(
|
179
|
+
requests_table, tablefmt="outline", headers=columns, stralign="left"
|
180
|
+
)
|
181
|
+
)
|
182
|
+
|
183
|
+
|
74
184
|
@machine_pool_cli.command(
|
75
185
|
name="delete",
|
76
186
|
help="Delete a machine pool in Anyscale.",
|
@@ -122,7 +232,9 @@ def list_machine_pools(format_: str) -> None:
|
|
122
232
|
]
|
123
233
|
)
|
124
234
|
print(
|
125
|
-
tabulate.tabulate(
|
235
|
+
tabulate.tabulate(
|
236
|
+
table, tablefmt="simple_grid", headers=columns, stralign="left"
|
237
|
+
)
|
126
238
|
)
|
127
239
|
elif format_ == "yaml":
|
128
240
|
rows = []
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import click
|
2
|
+
from dateutil import tz
|
3
|
+
from rich import print as rprint
|
4
|
+
import tabulate
|
5
|
+
|
6
|
+
import anyscale
|
7
|
+
from anyscale.cli_logger import BlockLogger
|
8
|
+
from anyscale.commands import command_examples
|
9
|
+
from anyscale.commands.util import AnyscaleCommand
|
10
|
+
|
11
|
+
|
12
|
+
log = BlockLogger() # CLI Logger
|
13
|
+
|
14
|
+
|
15
|
+
@click.group("organization-invitation", help="Manage organization invitations.")
|
16
|
+
def organization_invitation_cli() -> None:
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
@organization_invitation_cli.command(
|
21
|
+
name="create",
|
22
|
+
cls=AnyscaleCommand,
|
23
|
+
example=command_examples.ORGANIZATION_INVITATION_CREATE_EXAMPLE,
|
24
|
+
)
|
25
|
+
@click.option(
|
26
|
+
"--emails",
|
27
|
+
required=True,
|
28
|
+
type=str,
|
29
|
+
help="The emails to send the organization invitations to. Delimited by commas.",
|
30
|
+
)
|
31
|
+
def create(emails: str,) -> None:
|
32
|
+
"""
|
33
|
+
Creates organization invitations for the provided emails.
|
34
|
+
"""
|
35
|
+
log.info("Creating organization invitations...")
|
36
|
+
|
37
|
+
success_emails, error_messages = anyscale.organization_invitation.create(
|
38
|
+
emails.split(",")
|
39
|
+
)
|
40
|
+
|
41
|
+
if success_emails:
|
42
|
+
log.info(f"Organization invitations sent to: {', '.join(success_emails)}")
|
43
|
+
|
44
|
+
if error_messages:
|
45
|
+
for error_message in error_messages:
|
46
|
+
log.error(
|
47
|
+
f"Failed to send organization invitations with the following errors: {error_message}"
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
@organization_invitation_cli.command(
|
52
|
+
name="list",
|
53
|
+
cls=AnyscaleCommand,
|
54
|
+
example=command_examples.ORGANIZATION_INVITATION_LIST_EXAMPLE,
|
55
|
+
)
|
56
|
+
def list() -> None: # noqa: A001
|
57
|
+
"""
|
58
|
+
Lists organization invitations.
|
59
|
+
"""
|
60
|
+
organization_invitations = anyscale.organization_invitation.list()
|
61
|
+
|
62
|
+
table = tabulate.tabulate(
|
63
|
+
[
|
64
|
+
(
|
65
|
+
i.id,
|
66
|
+
i.email,
|
67
|
+
i.created_at.astimezone(tz=tz.tzlocal()).strftime("%m/%d/%Y %I:%M %p"),
|
68
|
+
i.expires_at.astimezone(tz=tz.tzlocal()).strftime("%m/%d/%Y %I:%M %p"),
|
69
|
+
)
|
70
|
+
for i in organization_invitations
|
71
|
+
],
|
72
|
+
headers=["ID", "Email", "Created At", "Expires At"],
|
73
|
+
)
|
74
|
+
rprint(table)
|
75
|
+
|
76
|
+
|
77
|
+
@organization_invitation_cli.command(
|
78
|
+
name="delete",
|
79
|
+
cls=AnyscaleCommand,
|
80
|
+
example=command_examples.ORGANIZATION_INVITATION_DELETE_EXAMPLE,
|
81
|
+
)
|
82
|
+
@click.option(
|
83
|
+
"--email",
|
84
|
+
required=True,
|
85
|
+
type=str,
|
86
|
+
help="The email of the organization invitation to delete.",
|
87
|
+
)
|
88
|
+
def delete(email: str,) -> None:
|
89
|
+
"""
|
90
|
+
Deletes an organization invitation.
|
91
|
+
"""
|
92
|
+
try:
|
93
|
+
organization_invitation_email = anyscale.organization_invitation.delete(email)
|
94
|
+
except ValueError as e:
|
95
|
+
log.error(f"Failed to delete organization invitation: {e}")
|
96
|
+
return
|
97
|
+
|
98
|
+
log.info(f"Organization invitation for {organization_invitation_email} deleted.")
|
@@ -2,10 +2,16 @@ from typing import Optional
|
|
2
2
|
|
3
3
|
import click
|
4
4
|
|
5
|
+
import anyscale
|
5
6
|
from anyscale.cli_logger import BlockLogger
|
6
|
-
from anyscale.commands
|
7
|
+
from anyscale.commands import command_examples
|
8
|
+
from anyscale.commands.util import AnyscaleCommand, LegacyAnyscaleCommand, NotRequiredIf
|
7
9
|
from anyscale.controllers.project_controller import ProjectController
|
8
|
-
from anyscale.project import
|
10
|
+
from anyscale.project.models import (
|
11
|
+
CreateProjectCollaborator,
|
12
|
+
CreateProjectCollaborators,
|
13
|
+
)
|
14
|
+
from anyscale.project_utils import validate_project_name
|
9
15
|
from anyscale.util import validate_non_negative_arg
|
10
16
|
|
11
17
|
|
@@ -201,3 +207,47 @@ def init(project_id: Optional[str], name: Optional[str],) -> None:
|
|
201
207
|
def create(name: str, parent_cloud_id: str) -> None:
|
202
208
|
project_controller = ProjectController()
|
203
209
|
project_controller.create(name, parent_cloud_id)
|
210
|
+
|
211
|
+
|
212
|
+
@project_cli.command(
|
213
|
+
name="add-collaborators",
|
214
|
+
help="Add collaborators to the project.",
|
215
|
+
cls=AnyscaleCommand,
|
216
|
+
example=command_examples.PROJECT_ADD_COLLABORATORS_EXAMPLE,
|
217
|
+
)
|
218
|
+
@click.option(
|
219
|
+
"--cloud",
|
220
|
+
"-c",
|
221
|
+
help="Name of the cloud that the project belongs to.",
|
222
|
+
required=True,
|
223
|
+
)
|
224
|
+
@click.option(
|
225
|
+
"--project",
|
226
|
+
"-p",
|
227
|
+
help="Name of the project to add collaborators to.",
|
228
|
+
required=True,
|
229
|
+
)
|
230
|
+
@click.option(
|
231
|
+
"--users-file",
|
232
|
+
help="Path to a YAML file containing a list of users to add to the project.",
|
233
|
+
required=True,
|
234
|
+
)
|
235
|
+
def add_collaborators(cloud: str, project: str, users_file: str,) -> None:
|
236
|
+
collaborators = CreateProjectCollaborators.from_yaml(users_file)
|
237
|
+
|
238
|
+
try:
|
239
|
+
anyscale.project.add_collaborators(
|
240
|
+
cloud=cloud,
|
241
|
+
project=project,
|
242
|
+
collaborators=[
|
243
|
+
CreateProjectCollaborator(**collaborator)
|
244
|
+
for collaborator in collaborators.collaborators
|
245
|
+
],
|
246
|
+
)
|
247
|
+
except ValueError as e:
|
248
|
+
log.error(f"Error adding collaborators to project: {e}")
|
249
|
+
return
|
250
|
+
|
251
|
+
log.info(
|
252
|
+
f"Successfully added {len(collaborators.collaborators)} collaborators to project {project}."
|
253
|
+
)
|
@@ -1,11 +1,14 @@
|
|
1
1
|
from typing import List, Optional, Tuple
|
2
2
|
|
3
3
|
import click
|
4
|
+
from rich import print as rprint
|
5
|
+
import tabulate
|
4
6
|
|
7
|
+
import anyscale
|
5
8
|
from anyscale.cli_logger import BlockLogger
|
6
9
|
from anyscale.commands import command_examples
|
7
10
|
from anyscale.commands.util import AnyscaleCommand
|
8
|
-
from anyscale.
|
11
|
+
from anyscale.resource_quota.models import CreateResourceQuota, ResourceQuota
|
9
12
|
from anyscale.util import validate_non_negative_arg
|
10
13
|
|
11
14
|
|
@@ -17,6 +20,43 @@ def resource_quota_cli() -> None:
|
|
17
20
|
pass
|
18
21
|
|
19
22
|
|
23
|
+
def _format_resource_quotas(resource_quotas: List[ResourceQuota]) -> str:
|
24
|
+
table_rows = []
|
25
|
+
for resource_quota in resource_quotas:
|
26
|
+
table_rows.append(
|
27
|
+
[
|
28
|
+
resource_quota.id,
|
29
|
+
resource_quota.name,
|
30
|
+
resource_quota.cloud_id,
|
31
|
+
resource_quota.project_id,
|
32
|
+
resource_quota.user_id,
|
33
|
+
resource_quota.is_enabled,
|
34
|
+
resource_quota.created_at.strftime("%m/%d/%Y"),
|
35
|
+
resource_quota.deleted_at.strftime("%m/%d/%Y")
|
36
|
+
if resource_quota.deleted_at
|
37
|
+
else None,
|
38
|
+
resource_quota.quota,
|
39
|
+
]
|
40
|
+
)
|
41
|
+
table = tabulate.tabulate(
|
42
|
+
table_rows,
|
43
|
+
headers=[
|
44
|
+
"ID",
|
45
|
+
"NAME",
|
46
|
+
"CLOUD ID",
|
47
|
+
"PROJECT ID",
|
48
|
+
"USER ID",
|
49
|
+
"IS ENABLED",
|
50
|
+
"CREATED AT",
|
51
|
+
"DELETED AT",
|
52
|
+
"QUOTA",
|
53
|
+
],
|
54
|
+
tablefmt="plain",
|
55
|
+
)
|
56
|
+
|
57
|
+
return f"Resource quotas:\n{table}"
|
58
|
+
|
59
|
+
|
20
60
|
@resource_quota_cli.command(
|
21
61
|
name="create",
|
22
62
|
help="Create a resource quota.",
|
@@ -84,8 +124,7 @@ def create( # noqa: PLR0913
|
|
84
124
|
|
85
125
|
`$ anyscale resource-quota create -n my-resource-quota --cloud my-cloud --project my-project --user-email test@myorg.com --num-cpus 10 --num-instances 10 --num-gpus 10 --num-accelerators L4 5 --num-accelerators T4 10`
|
86
126
|
"""
|
87
|
-
|
88
|
-
resource_quota_controller.create(
|
127
|
+
create_resource_quota = CreateResourceQuota(
|
89
128
|
name=name,
|
90
129
|
cloud=cloud,
|
91
130
|
project=project,
|
@@ -96,6 +135,35 @@ def create( # noqa: PLR0913
|
|
96
135
|
num_accelerators=dict(num_accelerators),
|
97
136
|
)
|
98
137
|
|
138
|
+
try:
|
139
|
+
with log.spinner("Creating resource quota..."):
|
140
|
+
resource_quota = anyscale.resource_quota.create(create_resource_quota)
|
141
|
+
|
142
|
+
create_resource_quota_message = [f"Name: {name}\nCloud name: {cloud}"]
|
143
|
+
if project:
|
144
|
+
create_resource_quota_message.append(f"Project name: {project}")
|
145
|
+
if user_email:
|
146
|
+
create_resource_quota_message.append(f"User email: {user_email}")
|
147
|
+
if num_cpus:
|
148
|
+
create_resource_quota_message.append(f"Number of CPUs: {num_cpus}")
|
149
|
+
if num_instances:
|
150
|
+
create_resource_quota_message.append(
|
151
|
+
f"Number of instances: {num_instances}"
|
152
|
+
)
|
153
|
+
if num_gpus:
|
154
|
+
create_resource_quota_message.append(f"Number of GPUs: {num_gpus}")
|
155
|
+
if num_accelerators:
|
156
|
+
create_resource_quota_message.append(
|
157
|
+
f"Number of accelerators: {dict(num_accelerators)}"
|
158
|
+
)
|
159
|
+
|
160
|
+
log.info("\n".join(create_resource_quota_message))
|
161
|
+
log.info(f"Resource quota created successfully ID: {resource_quota.id}")
|
162
|
+
|
163
|
+
except ValueError as e:
|
164
|
+
log.error(f"Error creating resource quota: {e}")
|
165
|
+
return
|
166
|
+
|
99
167
|
|
100
168
|
@resource_quota_cli.command(
|
101
169
|
name="list",
|
@@ -141,8 +209,7 @@ def list_resource_quotas(
|
|
141
209
|
|
142
210
|
`$ anyscale resource-quota list -n my-resource-quota --cloud my-cloud`
|
143
211
|
"""
|
144
|
-
|
145
|
-
resource_quota_controller.list_resource_quotas(
|
212
|
+
resource_quotas = anyscale.resource_quota.list(
|
146
213
|
name=name,
|
147
214
|
cloud=cloud,
|
148
215
|
creator_id=creator_id,
|
@@ -150,6 +217,8 @@ def list_resource_quotas(
|
|
150
217
|
max_items=max_items,
|
151
218
|
)
|
152
219
|
|
220
|
+
rprint(_format_resource_quotas(resource_quotas))
|
221
|
+
|
153
222
|
|
154
223
|
@resource_quota_cli.command(
|
155
224
|
name="delete",
|
@@ -168,8 +237,14 @@ def delete(id: str) -> None: # noqa: A002
|
|
168
237
|
|
169
238
|
`$ anyscale resource-quota delete --id rsq_123`
|
170
239
|
"""
|
171
|
-
|
172
|
-
|
240
|
+
try:
|
241
|
+
with log.spinner("Deleting resource quota..."):
|
242
|
+
anyscale.resource_quota.delete(resource_quota_id=id)
|
243
|
+
except ValueError as e:
|
244
|
+
log.error(f"Error deleting resource quota: {e}")
|
245
|
+
return
|
246
|
+
|
247
|
+
log.info(f"Resource quota with ID {id} deleted successfully.")
|
173
248
|
|
174
249
|
|
175
250
|
@resource_quota_cli.command(
|
@@ -189,8 +264,14 @@ def enable(id: str) -> None: # noqa: A002
|
|
189
264
|
|
190
265
|
`$ anyscale resource-quota enable --id rsq_123`
|
191
266
|
"""
|
192
|
-
|
193
|
-
|
267
|
+
try:
|
268
|
+
with log.spinner("Setting resource quota status..."):
|
269
|
+
anyscale.resource_quota.enable(resource_quota_id=id)
|
270
|
+
except ValueError as e:
|
271
|
+
log.error(f"Error enabling resource quota: {e}")
|
272
|
+
return
|
273
|
+
|
274
|
+
log.info(f"Enabled resource quota with ID {id} successfully.")
|
194
275
|
|
195
276
|
|
196
277
|
@resource_quota_cli.command(
|
@@ -210,5 +291,11 @@ def disable(id: str) -> None: # noqa: A002
|
|
210
291
|
|
211
292
|
`$ anyscale resource-quota disable --id rsq_123`
|
212
293
|
"""
|
213
|
-
|
214
|
-
|
294
|
+
try:
|
295
|
+
with log.spinner("Setting resource quota status..."):
|
296
|
+
anyscale.resource_quota.disable(resource_quota_id=id)
|
297
|
+
except ValueError as e:
|
298
|
+
log.error(f"Error disabling resource quota: {e}")
|
299
|
+
return
|
300
|
+
|
301
|
+
log.info(f"Disabled resource quota with ID {id} successfully.")
|