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
@@ -0,0 +1,210 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
"""
|
4
|
+
Managed Ray API
|
5
|
+
|
6
|
+
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501
|
7
|
+
|
8
|
+
The version of the OpenAPI document: 0.1.0
|
9
|
+
Generated by: https://openapi-generator.tech
|
10
|
+
"""
|
11
|
+
|
12
|
+
|
13
|
+
import pprint
|
14
|
+
import re # noqa: F401
|
15
|
+
|
16
|
+
import six
|
17
|
+
|
18
|
+
from openapi_client.configuration import Configuration
|
19
|
+
|
20
|
+
|
21
|
+
class WorkloadInfo(object):
|
22
|
+
"""NOTE: This class is auto generated by OpenAPI Generator.
|
23
|
+
Ref: https://openapi-generator.tech
|
24
|
+
|
25
|
+
Do not edit the class manually.
|
26
|
+
"""
|
27
|
+
|
28
|
+
"""
|
29
|
+
Attributes:
|
30
|
+
openapi_types (dict): The key is attribute name
|
31
|
+
and the value is attribute type.
|
32
|
+
attribute_map (dict): The key is attribute name
|
33
|
+
and the value is json key in definition.
|
34
|
+
"""
|
35
|
+
openapi_types = {
|
36
|
+
'workload_name': 'str',
|
37
|
+
'workload_type': 'str',
|
38
|
+
'workload_start_time': 'datetime',
|
39
|
+
'workload_cloud': 'str'
|
40
|
+
}
|
41
|
+
|
42
|
+
attribute_map = {
|
43
|
+
'workload_name': 'workload_name',
|
44
|
+
'workload_type': 'workload_type',
|
45
|
+
'workload_start_time': 'workload_start_time',
|
46
|
+
'workload_cloud': 'workload_cloud'
|
47
|
+
}
|
48
|
+
|
49
|
+
def __init__(self, workload_name=None, workload_type=None, workload_start_time=None, workload_cloud=None, local_vars_configuration=None): # noqa: E501
|
50
|
+
"""WorkloadInfo - a model defined in OpenAPI""" # noqa: E501
|
51
|
+
if local_vars_configuration is None:
|
52
|
+
local_vars_configuration = Configuration()
|
53
|
+
self.local_vars_configuration = local_vars_configuration
|
54
|
+
|
55
|
+
self._workload_name = None
|
56
|
+
self._workload_type = None
|
57
|
+
self._workload_start_time = None
|
58
|
+
self._workload_cloud = None
|
59
|
+
self.discriminator = None
|
60
|
+
|
61
|
+
self.workload_name = workload_name
|
62
|
+
self.workload_type = workload_type
|
63
|
+
self.workload_start_time = workload_start_time
|
64
|
+
self.workload_cloud = workload_cloud
|
65
|
+
|
66
|
+
@property
|
67
|
+
def workload_name(self):
|
68
|
+
"""Gets the workload_name of this WorkloadInfo. # noqa: E501
|
69
|
+
|
70
|
+
The name of the workload. # noqa: E501
|
71
|
+
|
72
|
+
:return: The workload_name of this WorkloadInfo. # noqa: E501
|
73
|
+
:rtype: str
|
74
|
+
"""
|
75
|
+
return self._workload_name
|
76
|
+
|
77
|
+
@workload_name.setter
|
78
|
+
def workload_name(self, workload_name):
|
79
|
+
"""Sets the workload_name of this WorkloadInfo.
|
80
|
+
|
81
|
+
The name of the workload. # noqa: E501
|
82
|
+
|
83
|
+
:param workload_name: The workload_name of this WorkloadInfo. # noqa: E501
|
84
|
+
:type: str
|
85
|
+
"""
|
86
|
+
if self.local_vars_configuration.client_side_validation and workload_name is None: # noqa: E501
|
87
|
+
raise ValueError("Invalid value for `workload_name`, must not be `None`") # noqa: E501
|
88
|
+
|
89
|
+
self._workload_name = workload_name
|
90
|
+
|
91
|
+
@property
|
92
|
+
def workload_type(self):
|
93
|
+
"""Gets the workload_type of this WorkloadInfo. # noqa: E501
|
94
|
+
|
95
|
+
The type of the workload. # noqa: E501
|
96
|
+
|
97
|
+
:return: The workload_type of this WorkloadInfo. # noqa: E501
|
98
|
+
:rtype: str
|
99
|
+
"""
|
100
|
+
return self._workload_type
|
101
|
+
|
102
|
+
@workload_type.setter
|
103
|
+
def workload_type(self, workload_type):
|
104
|
+
"""Sets the workload_type of this WorkloadInfo.
|
105
|
+
|
106
|
+
The type of the workload. # noqa: E501
|
107
|
+
|
108
|
+
:param workload_type: The workload_type of this WorkloadInfo. # noqa: E501
|
109
|
+
:type: str
|
110
|
+
"""
|
111
|
+
if self.local_vars_configuration.client_side_validation and workload_type is None: # noqa: E501
|
112
|
+
raise ValueError("Invalid value for `workload_type`, must not be `None`") # noqa: E501
|
113
|
+
|
114
|
+
self._workload_type = workload_type
|
115
|
+
|
116
|
+
@property
|
117
|
+
def workload_start_time(self):
|
118
|
+
"""Gets the workload_start_time of this WorkloadInfo. # noqa: E501
|
119
|
+
|
120
|
+
The start time of the workload. # noqa: E501
|
121
|
+
|
122
|
+
:return: The workload_start_time of this WorkloadInfo. # noqa: E501
|
123
|
+
:rtype: datetime
|
124
|
+
"""
|
125
|
+
return self._workload_start_time
|
126
|
+
|
127
|
+
@workload_start_time.setter
|
128
|
+
def workload_start_time(self, workload_start_time):
|
129
|
+
"""Sets the workload_start_time of this WorkloadInfo.
|
130
|
+
|
131
|
+
The start time of the workload. # noqa: E501
|
132
|
+
|
133
|
+
:param workload_start_time: The workload_start_time of this WorkloadInfo. # noqa: E501
|
134
|
+
:type: datetime
|
135
|
+
"""
|
136
|
+
if self.local_vars_configuration.client_side_validation and workload_start_time is None: # noqa: E501
|
137
|
+
raise ValueError("Invalid value for `workload_start_time`, must not be `None`") # noqa: E501
|
138
|
+
|
139
|
+
self._workload_start_time = workload_start_time
|
140
|
+
|
141
|
+
@property
|
142
|
+
def workload_cloud(self):
|
143
|
+
"""Gets the workload_cloud of this WorkloadInfo. # noqa: E501
|
144
|
+
|
145
|
+
The cloud of the workload. # noqa: E501
|
146
|
+
|
147
|
+
:return: The workload_cloud of this WorkloadInfo. # noqa: E501
|
148
|
+
:rtype: str
|
149
|
+
"""
|
150
|
+
return self._workload_cloud
|
151
|
+
|
152
|
+
@workload_cloud.setter
|
153
|
+
def workload_cloud(self, workload_cloud):
|
154
|
+
"""Sets the workload_cloud of this WorkloadInfo.
|
155
|
+
|
156
|
+
The cloud of the workload. # noqa: E501
|
157
|
+
|
158
|
+
:param workload_cloud: The workload_cloud of this WorkloadInfo. # noqa: E501
|
159
|
+
:type: str
|
160
|
+
"""
|
161
|
+
if self.local_vars_configuration.client_side_validation and workload_cloud is None: # noqa: E501
|
162
|
+
raise ValueError("Invalid value for `workload_cloud`, must not be `None`") # noqa: E501
|
163
|
+
|
164
|
+
self._workload_cloud = workload_cloud
|
165
|
+
|
166
|
+
def to_dict(self):
|
167
|
+
"""Returns the model properties as a dict"""
|
168
|
+
result = {}
|
169
|
+
|
170
|
+
for attr, _ in six.iteritems(self.openapi_types):
|
171
|
+
value = getattr(self, attr)
|
172
|
+
if isinstance(value, list):
|
173
|
+
result[attr] = list(map(
|
174
|
+
lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
|
175
|
+
value
|
176
|
+
))
|
177
|
+
elif hasattr(value, "to_dict"):
|
178
|
+
result[attr] = value.to_dict()
|
179
|
+
elif isinstance(value, dict):
|
180
|
+
result[attr] = dict(map(
|
181
|
+
lambda item: (item[0], item[1].to_dict())
|
182
|
+
if hasattr(item[1], "to_dict") else item,
|
183
|
+
value.items()
|
184
|
+
))
|
185
|
+
else:
|
186
|
+
result[attr] = value
|
187
|
+
|
188
|
+
return result
|
189
|
+
|
190
|
+
def to_str(self):
|
191
|
+
"""Returns the string representation of the model"""
|
192
|
+
return pprint.pformat(self.to_dict())
|
193
|
+
|
194
|
+
def __repr__(self):
|
195
|
+
"""For `print` and `pprint`"""
|
196
|
+
return self.to_str()
|
197
|
+
|
198
|
+
def __eq__(self, other):
|
199
|
+
"""Returns true if both objects are equal"""
|
200
|
+
if not isinstance(other, WorkloadInfo):
|
201
|
+
return False
|
202
|
+
|
203
|
+
return self.to_dict() == other.to_dict()
|
204
|
+
|
205
|
+
def __ne__(self, other):
|
206
|
+
"""Returns true if both objects are not equal"""
|
207
|
+
if not isinstance(other, WorkloadInfo):
|
208
|
+
return True
|
209
|
+
|
210
|
+
return self.to_dict() != other.to_dict()
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import inspect
|
2
|
+
import sys
|
3
|
+
from types import ModuleType
|
4
|
+
from typing import Any, Dict, List, Optional
|
5
|
+
|
6
|
+
from anyscale._private.anyscale_client import AnyscaleClientInterface
|
7
|
+
from anyscale._private.sdk import sdk_docs
|
8
|
+
from anyscale._private.sdk.base_sdk import Timer
|
9
|
+
from anyscale.cli_logger import BlockLogger
|
10
|
+
from anyscale.cloud._private.cloud_sdk import PrivateCloudSDK
|
11
|
+
from anyscale.cloud.commands import (
|
12
|
+
_ADD_COLLABORATORS_ARG_DOCSTRINGS,
|
13
|
+
_ADD_COLLABORATORS_EXAMPLE,
|
14
|
+
add_collaborators,
|
15
|
+
)
|
16
|
+
from anyscale.cloud.models import CreateCloudCollaborator
|
17
|
+
from anyscale.connect import ClientBuilder
|
18
|
+
|
19
|
+
|
20
|
+
class CloudSDK:
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
*,
|
24
|
+
client: Optional[AnyscaleClientInterface] = None,
|
25
|
+
logger: Optional[BlockLogger] = None,
|
26
|
+
timer: Optional[Timer] = None,
|
27
|
+
):
|
28
|
+
self._private_sdk = PrivateCloudSDK(client=client, logger=logger, timer=timer)
|
29
|
+
|
30
|
+
@sdk_docs(
|
31
|
+
doc_py_example=_ADD_COLLABORATORS_EXAMPLE,
|
32
|
+
arg_docstrings=_ADD_COLLABORATORS_ARG_DOCSTRINGS,
|
33
|
+
)
|
34
|
+
def add_collaborators( # noqa: F811
|
35
|
+
self, cloud: str, collaborators: List[CreateCloudCollaborator]
|
36
|
+
) -> str:
|
37
|
+
"""Batch add collaborators to a cloud."""
|
38
|
+
return self._private_sdk.add_collaborators(cloud, collaborators)
|
39
|
+
|
40
|
+
|
41
|
+
# Note: indentation here matches that of connect.py::ClientBuilder.
|
42
|
+
BUILDER_HELP_FOOTER = """
|
43
|
+
See ``anyscale.ClientBuilder`` for full documentation of
|
44
|
+
this experimental feature."""
|
45
|
+
|
46
|
+
|
47
|
+
class CloudModule(ModuleType):
|
48
|
+
"""
|
49
|
+
A custom callable module object for `anyscale.cloud`.
|
50
|
+
|
51
|
+
This hack is needed since `anyscale.cloud` is a function for Anyscale connect but also a module for the SDK.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(self):
|
55
|
+
# Expose attributes from the SDK.
|
56
|
+
self.CloudSDK = CloudSDK
|
57
|
+
self.add_collaborators = add_collaborators
|
58
|
+
|
59
|
+
# Expose Anyscale connect
|
60
|
+
self.new_builder = self._new_builder()
|
61
|
+
|
62
|
+
# This code is copied from frontend/cli/anyscale/__init__.py.
|
63
|
+
def _new_builder(self) -> Any:
|
64
|
+
target = ClientBuilder.cloud
|
65
|
+
|
66
|
+
def new_session_builder(*a: List[Any], **kw: Dict[str, Any]) -> Any:
|
67
|
+
builder = ClientBuilder()
|
68
|
+
return target(builder, *a, **kw) # type: ignore
|
69
|
+
|
70
|
+
new_session_builder.__name__ = "cloud"
|
71
|
+
new_session_builder.__doc__ = target.__doc__ + BUILDER_HELP_FOOTER # type: ignore
|
72
|
+
new_session_builder.__signature__ = inspect.signature(target) # type: ignore
|
73
|
+
|
74
|
+
return new_session_builder
|
75
|
+
|
76
|
+
def __call__(self, *args, **kwargs):
|
77
|
+
"""
|
78
|
+
Define the behavior when `anyscale.cloud` is called for Anyscale connect.
|
79
|
+
"""
|
80
|
+
return self.new_builder(*args, **kwargs)
|
81
|
+
|
82
|
+
|
83
|
+
sys.modules[__name__] = CloudModule()
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from anyscale._private.sdk.base_sdk import BaseSDK
|
4
|
+
from anyscale.client.openapi_client.models import (
|
5
|
+
CreateCloudCollaborator as CreateCloudCollaboratorModel,
|
6
|
+
)
|
7
|
+
from anyscale.cloud.models import CreateCloudCollaborator
|
8
|
+
|
9
|
+
|
10
|
+
class PrivateCloudSDK(BaseSDK):
|
11
|
+
def add_collaborators(
|
12
|
+
self, cloud: str, collaborators: List[CreateCloudCollaborator]
|
13
|
+
) -> str:
|
14
|
+
cloud_id = self.client.get_cloud_id(cloud_name=cloud, compute_config_id=None)
|
15
|
+
|
16
|
+
return self.client.add_cloud_collaborators(
|
17
|
+
cloud_id=cloud_id,
|
18
|
+
collaborators=[
|
19
|
+
CreateCloudCollaboratorModel(
|
20
|
+
email=collaborator.email,
|
21
|
+
permission_level=collaborator.permission_level.lower(),
|
22
|
+
)
|
23
|
+
for collaborator in collaborators
|
24
|
+
],
|
25
|
+
)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from anyscale._private.sdk import sdk_command
|
4
|
+
from anyscale.cloud._private.cloud_sdk import PrivateCloudSDK
|
5
|
+
from anyscale.cloud.models import CreateCloudCollaborator
|
6
|
+
|
7
|
+
|
8
|
+
_CLOUD_SDK_SINGLETON_KEY = "cloud_sdk"
|
9
|
+
|
10
|
+
_ADD_COLLABORATORS_EXAMPLE = """
|
11
|
+
import anyscale
|
12
|
+
from anyscale.cloud.models import CloudPermissionLevel, CreateCloudCollaborator
|
13
|
+
|
14
|
+
anyscale.cloud.add_collaborators(
|
15
|
+
cloud="cloud_name",
|
16
|
+
collaborators=[
|
17
|
+
CreateCloudCollaborator(
|
18
|
+
email="test1@anyscale.com",
|
19
|
+
permission_level=CloudPermissionLevel.WRITE,
|
20
|
+
),
|
21
|
+
CreateCloudCollaborator(
|
22
|
+
email="test2@anyscale.com",
|
23
|
+
permission_level=CloudPermissionLevel.READONLY,
|
24
|
+
),
|
25
|
+
],
|
26
|
+
)
|
27
|
+
"""
|
28
|
+
|
29
|
+
_ADD_COLLABORATORS_ARG_DOCSTRINGS = {
|
30
|
+
"cloud": "The cloud to add users to.",
|
31
|
+
"collaborators": "The list of collaborators to add to the cloud.",
|
32
|
+
}
|
33
|
+
|
34
|
+
|
35
|
+
@sdk_command(
|
36
|
+
_CLOUD_SDK_SINGLETON_KEY,
|
37
|
+
PrivateCloudSDK,
|
38
|
+
doc_py_example=_ADD_COLLABORATORS_EXAMPLE,
|
39
|
+
arg_docstrings=_ADD_COLLABORATORS_ARG_DOCSTRINGS,
|
40
|
+
)
|
41
|
+
def add_collaborators(
|
42
|
+
cloud: str, collaborators: List[CreateCloudCollaborator], *, _sdk: PrivateCloudSDK
|
43
|
+
) -> str:
|
44
|
+
"""Batch add collaborators to a cloud."""
|
45
|
+
return _sdk.add_collaborators(cloud, collaborators)
|
anyscale/cloud/models.py
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
from dataclasses import dataclass, field
|
2
|
+
from typing import Any, Dict, List
|
3
|
+
|
4
|
+
from anyscale._private.models import ModelBase, ModelEnum
|
5
|
+
|
6
|
+
|
7
|
+
class CloudPermissionLevel(ModelEnum):
|
8
|
+
WRITE = "WRITE"
|
9
|
+
READONLY = "READONLY"
|
10
|
+
|
11
|
+
__docstrings__ = {
|
12
|
+
WRITE: "Write permission level for the cloud",
|
13
|
+
READONLY: "Readonly permission level for the cloud",
|
14
|
+
}
|
15
|
+
|
16
|
+
|
17
|
+
@dataclass(frozen=True)
|
18
|
+
class CreateCloudCollaborator(ModelBase):
|
19
|
+
"""User to be added as a collaborator to a cloud.
|
20
|
+
"""
|
21
|
+
|
22
|
+
__doc_py_example__ = """\
|
23
|
+
import anyscale
|
24
|
+
from anyscale.cloud.models import CloudPermissionLevel, CreateCloudCollaborator
|
25
|
+
|
26
|
+
create_cloud_collaborator = CreateCloudCollaborator(
|
27
|
+
# Email of the user to be added as a collaborator
|
28
|
+
email="test@anyscale.com",
|
29
|
+
# Permission level for the user to the cloud (CloudPermissionLevel.WRITE, CloudPermissionLevel.READONLY)
|
30
|
+
permission_level=CloudPermissionLevel.READONLY,
|
31
|
+
)
|
32
|
+
"""
|
33
|
+
|
34
|
+
def _validate_email(self, email: str):
|
35
|
+
if not isinstance(email, str):
|
36
|
+
raise TypeError("Email must be a string.")
|
37
|
+
|
38
|
+
email: str = field(
|
39
|
+
metadata={"docstring": "Email of the user to be added as a collaborator."},
|
40
|
+
)
|
41
|
+
|
42
|
+
def _validate_permission_level(
|
43
|
+
self, permission_level: CloudPermissionLevel
|
44
|
+
) -> CloudPermissionLevel:
|
45
|
+
if isinstance(permission_level, str):
|
46
|
+
return CloudPermissionLevel.validate(permission_level)
|
47
|
+
elif isinstance(permission_level, CloudPermissionLevel):
|
48
|
+
return permission_level
|
49
|
+
else:
|
50
|
+
raise TypeError(
|
51
|
+
f"'permission_level' must be a 'CloudPermissionLevel' (it is {type(permission_level)})."
|
52
|
+
)
|
53
|
+
|
54
|
+
permission_level: CloudPermissionLevel = field( # type: ignore
|
55
|
+
default=CloudPermissionLevel.READONLY, # type: ignore
|
56
|
+
metadata={
|
57
|
+
"docstring": "Permission level the added user should have for the cloud" # type: ignore
|
58
|
+
f"(one of: {','.join([str(m.value) for m in CloudPermissionLevel])}", # type: ignore
|
59
|
+
},
|
60
|
+
)
|
61
|
+
|
62
|
+
|
63
|
+
@dataclass(frozen=True)
|
64
|
+
class CreateCloudCollaborators(ModelBase):
|
65
|
+
"""List of users to be added as collaborators to a cloud.
|
66
|
+
"""
|
67
|
+
|
68
|
+
__doc_py_example__ = """\
|
69
|
+
import anyscale
|
70
|
+
from anyscale.cloud.models import CloudPermissionLevel, CreateCloudCollaborator, CreateCloudCollaborators
|
71
|
+
|
72
|
+
create_cloud_collaborator = CreateCloudCollaborator(
|
73
|
+
# Email of the user to be added as a collaborator
|
74
|
+
email="test@anyscale.com",
|
75
|
+
# Permission level for the user to the cloud (CloudPermissionLevel.WRITE, CloudPermissionLevel.READONLY)
|
76
|
+
permission_level=CloudPermissionLevel.READONLY,
|
77
|
+
)
|
78
|
+
create_cloud_collaborators = CreateCloudCollaborators(
|
79
|
+
collaborators=[create_cloud_collaborator]
|
80
|
+
)
|
81
|
+
"""
|
82
|
+
|
83
|
+
collaborators: List[Dict[str, Any]] = field(
|
84
|
+
metadata={
|
85
|
+
"docstring": "List of users to be added as collaborators to a cloud."
|
86
|
+
},
|
87
|
+
)
|
88
|
+
|
89
|
+
def _validate_collaborators(self, collaborators: List[Dict[str, Any]]):
|
90
|
+
if not isinstance(collaborators, list):
|
91
|
+
raise TypeError("Collaborators must be a list.")
|
anyscale/cluster_compute.py
CHANGED
@@ -4,7 +4,7 @@ from typing import Optional, Tuple
|
|
4
4
|
from anyscale.authenticate import get_auth_api_client
|
5
5
|
from anyscale.cli_logger import BlockLogger
|
6
6
|
from anyscale.client.openapi_client.api.default_api import DefaultApi
|
7
|
-
from anyscale.
|
7
|
+
from anyscale.cloud_utils import get_cloud_id_and_name, get_last_used_cloud
|
8
8
|
from anyscale.sdk.anyscale_client import (
|
9
9
|
ArchiveStatus,
|
10
10
|
ComputeTemplateConfig,
|
@@ -26,13 +26,13 @@ def aggregated_instance_usage_cli() -> None:
|
|
26
26
|
"--start-date",
|
27
27
|
required=True,
|
28
28
|
type=str,
|
29
|
-
help="The start date of the aggregated instance usage report
|
29
|
+
help="The start date (inclusive) of the aggregated instance usage report. Format: YYYY-MM-DD",
|
30
30
|
)
|
31
31
|
@click.option(
|
32
32
|
"--end-date",
|
33
33
|
required=True,
|
34
34
|
type=str,
|
35
|
-
help="The end date of the aggregated instance usage report
|
35
|
+
help="The end date (inclusive) of the aggregated instance usage report. Format: YYYY-MM-DD",
|
36
36
|
)
|
37
37
|
@click.option(
|
38
38
|
"--cloud",
|
@@ -50,7 +50,7 @@ def aggregated_instance_usage_cli() -> None:
|
|
50
50
|
"--directory",
|
51
51
|
required=False,
|
52
52
|
type=str,
|
53
|
-
help="The directory to save the CSV file to. Default is the current directory",
|
53
|
+
help="The directory to save the CSV file to. Default is the current directory.",
|
54
54
|
)
|
55
55
|
@click.option(
|
56
56
|
"--hide-progress-bar",
|
@@ -67,7 +67,7 @@ def download_csv(
|
|
67
67
|
hide_progress_bar: Optional[bool] = False,
|
68
68
|
) -> None:
|
69
69
|
"""
|
70
|
-
Download an aggregated instance usage report as a zipped
|
70
|
+
Download an aggregated instance usage report as a zipped CSV to the provided directory.
|
71
71
|
"""
|
72
72
|
log.info("Downloading aggregated instance usage CSV...")
|
73
73
|
try:
|
@@ -1,15 +1,22 @@
|
|
1
|
+
from io import StringIO
|
1
2
|
import re
|
2
3
|
from typing import List, Optional
|
3
4
|
|
4
5
|
import click
|
6
|
+
import yaml
|
5
7
|
|
8
|
+
import anyscale
|
6
9
|
from anyscale.cli_logger import BlockLogger
|
7
10
|
from anyscale.client.openapi_client.models import ClusterManagementStackVersions
|
8
11
|
from anyscale.client.openapi_client.models.compute_stack import ComputeStack
|
9
|
-
from anyscale.
|
10
|
-
from anyscale.commands
|
12
|
+
from anyscale.cloud.models import CreateCloudCollaborator, CreateCloudCollaborators
|
13
|
+
from anyscale.commands import cloud_commands_util, command_examples
|
14
|
+
from anyscale.commands.util import AnyscaleCommand, OptionPromptNull
|
11
15
|
from anyscale.controllers.cloud_controller import CloudController
|
12
|
-
from anyscale.util import
|
16
|
+
from anyscale.util import (
|
17
|
+
allow_optional_file_storage,
|
18
|
+
validate_non_negative_arg,
|
19
|
+
)
|
13
20
|
|
14
21
|
|
15
22
|
log = BlockLogger() # CLI Logger
|
@@ -316,11 +323,12 @@ def cloud_config_get(
|
|
316
323
|
"The positional argument CLOUD_NAME and the keyword argument --name "
|
317
324
|
"were both provided. Please only provide one of these two arguments."
|
318
325
|
)
|
319
|
-
|
320
|
-
|
321
|
-
cloud_name=cloud_name or name, cloud_id=cloud_id,
|
322
|
-
)
|
326
|
+
config = CloudController().get_cloud_config(
|
327
|
+
cloud_name=cloud_name or name, cloud_id=cloud_id,
|
323
328
|
)
|
329
|
+
stream = StringIO()
|
330
|
+
yaml.dump(config.spec, stream)
|
331
|
+
print(stream.getvalue())
|
324
332
|
|
325
333
|
|
326
334
|
@cloud_config_group.command(
|
@@ -349,13 +357,26 @@ def cloud_config_get(
|
|
349
357
|
"extra data transfer cost from the cloud provider by enabling this feature."
|
350
358
|
),
|
351
359
|
)
|
360
|
+
@click.option(
|
361
|
+
"--spec-file",
|
362
|
+
type=str,
|
363
|
+
required=False,
|
364
|
+
help="Provide a path to a specification file.",
|
365
|
+
)
|
352
366
|
def cloud_config_update(
|
353
367
|
cloud_name: Optional[str],
|
354
368
|
name: Optional[str],
|
355
369
|
cloud_id: Optional[str],
|
356
370
|
enable_log_ingestion: Optional[bool],
|
371
|
+
spec_file: Optional[str],
|
357
372
|
) -> None:
|
373
|
+
if any([enable_log_ingestion is not None]) and spec_file:
|
374
|
+
raise click.ClickException(
|
375
|
+
"Please provide only one of the following arguments: --enable-log-ingestion, --disable-log-ingestion, --spec-file."
|
376
|
+
)
|
377
|
+
|
358
378
|
if any([enable_log_ingestion is not None]):
|
379
|
+
# TODO: enable_log_ingestion should be unified into cloud deployment config.
|
359
380
|
if enable_log_ingestion is True:
|
360
381
|
consent_message = click.prompt(
|
361
382
|
"--enable-log-ingestion is specified. Please note the logs produced by "
|
@@ -385,6 +406,10 @@ def cloud_config_update(
|
|
385
406
|
cloud_id=cloud_id,
|
386
407
|
enable_log_ingestion=enable_log_ingestion,
|
387
408
|
)
|
409
|
+
elif spec_file:
|
410
|
+
CloudController().update_cloud_config(
|
411
|
+
cloud_name=cloud_name or name, cloud_id=cloud_id, spec_file=spec_file,
|
412
|
+
)
|
388
413
|
else:
|
389
414
|
raise click.ClickException(
|
390
415
|
"Please provide at least one of the following arguments: --enable-log-ingestion, --disable-log-ingestion."
|
@@ -656,10 +681,9 @@ def register_cloud( # noqa: PLR0913, PLR0912, C901
|
|
656
681
|
# Check for missing required arguments for AWS clouds,
|
657
682
|
# based on the compute stack (not all args are required
|
658
683
|
# on all compute stacks).
|
659
|
-
|
684
|
+
required_resources = [
|
660
685
|
(vpc_id, "--vpc-id", (ComputeStack.VM)),
|
661
686
|
(subnet_ids, "--subnet-ids", (ComputeStack.VM)),
|
662
|
-
(file_storage_id, "--file-storage-id", (ComputeStack.VM)),
|
663
687
|
(anyscale_iam_role_id, "--anyscale-iam-role-id", (ComputeStack.VM),),
|
664
688
|
(instance_iam_role_id, "--instance-iam-role-id", (ComputeStack.VM)),
|
665
689
|
(security_group_ids, "--security-group-ids", (ComputeStack.VM)),
|
@@ -674,7 +698,14 @@ def register_cloud( # noqa: PLR0913, PLR0912, C901
|
|
674
698
|
"--anyscale-operator-iam-identity",
|
675
699
|
(ComputeStack.K8S),
|
676
700
|
),
|
677
|
-
]
|
701
|
+
]
|
702
|
+
|
703
|
+
if not allow_optional_file_storage():
|
704
|
+
required_resources.append(
|
705
|
+
(file_storage_id, "--file-storage-id", (ComputeStack.VM)),
|
706
|
+
)
|
707
|
+
|
708
|
+
for resource in required_resources:
|
678
709
|
if compute_stack in resource[2] and resource[0] is None:
|
679
710
|
missing_args.append(resource[1])
|
680
711
|
|
@@ -711,12 +742,10 @@ def register_cloud( # noqa: PLR0913, PLR0912, C901
|
|
711
742
|
# Keep the parameter naming ({resource}_name or {resource}_id) consistent with GCP to reduce confusion for customers
|
712
743
|
# Check if all required parameters are provided
|
713
744
|
# memorystore_instance_name and host_project_id are optional for GCP clouds
|
714
|
-
|
745
|
+
required_resources = [
|
715
746
|
(project_id, "--project-id", (ComputeStack.VM)),
|
716
747
|
(vpc_name, "--vpc-name", (ComputeStack.VM)),
|
717
748
|
(subnet_names, "--subnet-names", (ComputeStack.VM)),
|
718
|
-
(file_storage_id, "--file-storage-id", (ComputeStack.VM)),
|
719
|
-
(filestore_location, "--filestore-location", (ComputeStack.VM)),
|
720
749
|
(
|
721
750
|
anyscale_service_account_email,
|
722
751
|
"--anyscale-service-account-email",
|
@@ -740,7 +769,17 @@ def register_cloud( # noqa: PLR0913, PLR0912, C901
|
|
740
769
|
"--anyscale-operator-iam-identity",
|
741
770
|
(ComputeStack.K8S),
|
742
771
|
),
|
743
|
-
]
|
772
|
+
]
|
773
|
+
|
774
|
+
if not allow_optional_file_storage():
|
775
|
+
required_resources.extend(
|
776
|
+
[
|
777
|
+
(file_storage_id, "--file-storage-id", (ComputeStack.VM)),
|
778
|
+
(filestore_location, "--filestore-location", (ComputeStack.VM)),
|
779
|
+
]
|
780
|
+
)
|
781
|
+
|
782
|
+
for resource in required_resources:
|
744
783
|
if compute_stack in resource[2] and resource[0] is None:
|
745
784
|
missing_args.append(resource[1])
|
746
785
|
|
@@ -1017,3 +1056,37 @@ def cloud_edit( # noqa: PLR0913
|
|
1017
1056
|
raise click.ClickException(
|
1018
1057
|
"Please provide at least one of the following arguments: --aws-s3-id, --aws-efs-id, --aws-efs-mount-target-ip, --memorydb-cluster-id, --gcp-filestore-instance-id, --gcp-filestore-location, --gcp-cloud-storage-bucket-name, --memorystore-instance-name, --enable-auto-add-user, --disable-auto-add-user."
|
1019
1058
|
)
|
1059
|
+
|
1060
|
+
|
1061
|
+
@cloud_cli.command(
|
1062
|
+
name="add-collaborators",
|
1063
|
+
help="Add collaborators to the cloud.",
|
1064
|
+
cls=AnyscaleCommand,
|
1065
|
+
example=command_examples.CLOUD_ADD_COLLABORATORS_EXAMPLE,
|
1066
|
+
)
|
1067
|
+
@click.option(
|
1068
|
+
"--cloud", "-c", help="Name of the cloud to add collaborators to.", required=True
|
1069
|
+
)
|
1070
|
+
@click.option(
|
1071
|
+
"--users-file",
|
1072
|
+
help="Path to a YAML file containing a list of users to add to the cloud.",
|
1073
|
+
required=True,
|
1074
|
+
)
|
1075
|
+
def add_collaborators(cloud: str, users_file: str,) -> None:
|
1076
|
+
collaborators = CreateCloudCollaborators.from_yaml(users_file)
|
1077
|
+
|
1078
|
+
try:
|
1079
|
+
anyscale.cloud.add_collaborators(
|
1080
|
+
cloud=cloud,
|
1081
|
+
collaborators=[
|
1082
|
+
CreateCloudCollaborator(**collaborator)
|
1083
|
+
for collaborator in collaborators.collaborators
|
1084
|
+
],
|
1085
|
+
)
|
1086
|
+
except ValueError as e:
|
1087
|
+
log.error(f"Error adding collaborators to cloud: {e}")
|
1088
|
+
return
|
1089
|
+
|
1090
|
+
log.info(
|
1091
|
+
f"Successfully added {len(collaborators.collaborators)} collaborators to cloud {cloud}."
|
1092
|
+
)
|