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.
Files changed (148) hide show
  1. anyscale/__init__.py +56 -0
  2. anyscale/_private/anyscale_client/anyscale_client.py +179 -28
  3. anyscale/_private/anyscale_client/common.py +109 -2
  4. anyscale/_private/anyscale_client/fake_anyscale_client.py +239 -1
  5. anyscale/_private/docgen/README.md +1 -1
  6. anyscale/_private/docgen/__main__.py +71 -21
  7. anyscale/_private/docgen/api.md +13 -20
  8. anyscale/_private/docgen/generator.py +3 -2
  9. anyscale/_private/docgen/models.md +4 -49
  10. anyscale/_private/workload/workload_config.py +21 -7
  11. anyscale/aggregated_instance_usage/__init__.py +1 -1
  12. anyscale/aggregated_instance_usage/commands.py +2 -4
  13. anyscale/aggregated_instance_usage/models.py +8 -8
  14. anyscale/client/README.md +25 -22
  15. anyscale/client/openapi_client/__init__.py +16 -14
  16. anyscale/client/openapi_client/api/default_api.py +1139 -959
  17. anyscale/client/openapi_client/models/__init__.py +16 -14
  18. anyscale/client/openapi_client/models/baseimagesenum.py +43 -1
  19. anyscale/client/openapi_client/models/{session_event_types.py → cloud_deployment_config.py} +35 -24
  20. anyscale/client/openapi_client/models/{platformfinetuningjob_response.py → clouddeploymentconfig_response.py} +11 -11
  21. anyscale/client/openapi_client/models/{log_level_types.py → cluster_event_source.py} +12 -7
  22. anyscale/client/openapi_client/models/{company_size.py → cluster_size.py} +10 -10
  23. anyscale/client/openapi_client/models/cluster_status_details.py +2 -1
  24. anyscale/client/openapi_client/models/{sessionevent_list_response.py → clusterevent_list_response.py} +15 -15
  25. anyscale/client/openapi_client/models/create_experimental_workspace.py +29 -1
  26. anyscale/client/openapi_client/models/create_notification_channel_record.py +29 -3
  27. anyscale/client/openapi_client/models/decorated_interactive_session.py +1 -57
  28. anyscale/client/openapi_client/models/decorated_job.py +1 -57
  29. anyscale/client/openapi_client/models/decorated_job_submission.py +1 -29
  30. anyscale/client/openapi_client/models/decorated_production_job.py +1 -29
  31. anyscale/client/openapi_client/models/decorated_session.py +1 -57
  32. anyscale/client/openapi_client/models/decorated_unified_job.py +1 -30
  33. anyscale/client/openapi_client/models/{resubmit_ft_job_request.py → describe_machine_pool_request.py} +21 -20
  34. anyscale/client/openapi_client/models/describe_machine_pool_response.py +123 -0
  35. anyscale/client/openapi_client/models/describemachinepoolresponse_response.py +121 -0
  36. anyscale/client/openapi_client/models/ha_jobs_sort_field.py +1 -2
  37. anyscale/client/openapi_client/models/internal_production_job.py +1 -29
  38. anyscale/client/openapi_client/models/jobs_sort_field.py +1 -2
  39. anyscale/client/openapi_client/models/machine_allocation_state.py +3 -1
  40. anyscale/client/openapi_client/models/machine_state_info.py +326 -0
  41. anyscale/client/openapi_client/models/{fine_tuning_job_status.py → notification_channel_slack_config.py} +34 -16
  42. anyscale/client/openapi_client/models/organization_marketing_questions.py +80 -54
  43. anyscale/client/openapi_client/models/request_state_info.py +210 -0
  44. anyscale/client/openapi_client/models/{platformfinetuningjob_list_response.py → scheduler_info.py} +43 -38
  45. anyscale/client/openapi_client/models/serve_deployment_fast_api_docs_status.py +123 -0
  46. anyscale/client/openapi_client/models/serve_deployment_state.py +2 -1
  47. anyscale/client/openapi_client/models/servedeploymentfastapidocsstatus_response.py +121 -0
  48. anyscale/client/openapi_client/models/sessions_sort_field.py +1 -2
  49. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +43 -1
  50. anyscale/client/openapi_client/models/unified_job_sort_field.py +1 -2
  51. anyscale/client/openapi_client/models/update_cloud_collaborator.py +121 -0
  52. anyscale/client/openapi_client/models/usage_by_cluster.py +28 -1
  53. anyscale/client/openapi_client/models/usage_by_user.py +30 -3
  54. anyscale/client/openapi_client/models/workload_info.py +210 -0
  55. anyscale/cloud/__init__.py +83 -0
  56. anyscale/cloud/_private/cloud_sdk.py +25 -0
  57. anyscale/cloud/commands.py +45 -0
  58. anyscale/cloud/models.py +91 -0
  59. anyscale/cluster_compute.py +1 -1
  60. anyscale/commands/aggregated_instance_usage_commands.py +4 -4
  61. anyscale/commands/cloud_commands.py +87 -14
  62. anyscale/commands/command_examples.py +65 -0
  63. anyscale/commands/job_commands.py +15 -3
  64. anyscale/commands/machine_pool_commands.py +113 -1
  65. anyscale/commands/organization_invitation_commands.py +98 -0
  66. anyscale/commands/project_commands.py +52 -2
  67. anyscale/commands/resource_quota_commands.py +98 -11
  68. anyscale/commands/service_account_commands.py +65 -8
  69. anyscale/commands/service_commands.py +61 -1
  70. anyscale/commands/session_commands_hidden.py +5 -1
  71. anyscale/commands/user_commands.py +1 -1
  72. anyscale/commands/util.py +2 -2
  73. anyscale/commands/workspace_commands.py +1 -1
  74. anyscale/connect.py +1 -1
  75. anyscale/connect_utils/project.py +7 -4
  76. anyscale/controllers/cloud_controller.py +63 -30
  77. anyscale/controllers/cloud_functional_verification_controller.py +1 -1
  78. anyscale/controllers/cluster_controller.py +3 -11
  79. anyscale/controllers/compute_config_controller.py +1 -1
  80. anyscale/controllers/experimental_integrations_controller.py +1 -1
  81. anyscale/controllers/job_controller.py +8 -6
  82. anyscale/controllers/list_controller.py +2 -2
  83. anyscale/controllers/machine_pool_controller.py +12 -1
  84. anyscale/controllers/project_controller.py +4 -3
  85. anyscale/controllers/schedule_controller.py +1 -1
  86. anyscale/controllers/service_controller.py +1 -1
  87. anyscale/controllers/workspace_controller.py +1 -1
  88. anyscale/models/job_model.py +1 -1
  89. anyscale/organization_invitation/__init__.py +61 -0
  90. anyscale/organization_invitation/_private/organization_invitation_sdk.py +24 -0
  91. anyscale/organization_invitation/commands.py +84 -0
  92. anyscale/organization_invitation/models.py +45 -0
  93. anyscale/project/__init__.py +35 -0
  94. anyscale/project/_private/project_sdk.py +27 -0
  95. anyscale/project/commands.py +56 -0
  96. anyscale/project/models.py +91 -0
  97. anyscale/{project.py → project_utils.py} +3 -4
  98. anyscale/resource_quota/__init__.py +99 -0
  99. anyscale/resource_quota/_private/resource_quota_sdk.py +120 -0
  100. anyscale/resource_quota/commands.py +150 -0
  101. anyscale/resource_quota/models.py +303 -0
  102. anyscale/scripts.py +4 -0
  103. anyscale/sdk/anyscale_client/__init__.py +0 -5
  104. anyscale/sdk/anyscale_client/api/default_api.py +119 -150
  105. anyscale/sdk/anyscale_client/models/__init__.py +0 -5
  106. anyscale/sdk/anyscale_client/models/baseimagesenum.py +43 -1
  107. anyscale/sdk/anyscale_client/models/cluster_status_details.py +2 -1
  108. anyscale/sdk/anyscale_client/models/jobs_sort_field.py +1 -2
  109. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +43 -1
  110. anyscale/sdk/anyscale_client/sdk.py +1 -1
  111. anyscale/service/__init__.py +21 -0
  112. anyscale/service/_private/service_sdk.py +13 -0
  113. anyscale/service/commands.py +35 -0
  114. anyscale/service_account/__init__.py +88 -0
  115. anyscale/service_account/_private/service_account_sdk.py +101 -0
  116. anyscale/service_account/commands.py +147 -0
  117. anyscale/service_account/models.py +66 -0
  118. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  119. anyscale/shared_anyscale_utils/utils/id_gen.py +2 -0
  120. anyscale/user/__init__.py +1 -1
  121. anyscale/user/commands.py +1 -1
  122. anyscale/user/models.py +25 -15
  123. anyscale/util.py +23 -0
  124. anyscale/utils/cloud_utils.py +1 -1
  125. anyscale/version.py +1 -1
  126. anyscale/workspace_utils.py +1 -1
  127. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/METADATA +1 -5
  128. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/RECORD +134 -119
  129. anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +0 -156
  130. anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +0 -353
  131. anyscale/client/openapi_client/models/finish_ft_job_request.py +0 -204
  132. anyscale/client/openapi_client/models/platform_fine_tuning_job.py +0 -577
  133. anyscale/client/openapi_client/models/session_event.py +0 -267
  134. anyscale/client/openapi_client/models/session_event_cause.py +0 -150
  135. anyscale/controllers/resource_quota_controller.py +0 -183
  136. anyscale/controllers/service_account_controller.py +0 -168
  137. anyscale/sdk/anyscale_client/models/log_level_types.py +0 -100
  138. anyscale/sdk/anyscale_client/models/session_event.py +0 -267
  139. anyscale/sdk/anyscale_client/models/session_event_cause.py +0 -150
  140. anyscale/sdk/anyscale_client/models/session_event_types.py +0 -111
  141. anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +0 -147
  142. anyscale/utils/imports/azure.py +0 -14
  143. /anyscale/{cloud.py → cloud_utils.py} +0 -0
  144. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/LICENSE +0 -0
  145. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/NOTICE +0 -0
  146. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/WHEEL +0 -0
  147. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/entry_points.txt +0 -0
  148. {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)
@@ -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.")
@@ -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.cloud import get_cloud_id_and_name, get_last_used_cloud
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 (inclusive). Format: YYYY-MM-DD",
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 (inclusive). Format: YYYY-MM-DD",
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 csv to the provided directory.
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.commands import cloud_commands_util
10
- from anyscale.commands.util import OptionPromptNull
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 validate_non_negative_arg
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
- print(
320
- CloudController().get_cloud_config(
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
- for resource in [
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
- for resource in [
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
+ )