anyscale 0.25.11__py3-none-any.whl → 0.26.1__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 (40) hide show
  1. anyscale/_private/anyscale_client/anyscale_client.py +16 -3
  2. anyscale/_private/anyscale_client/common.py +13 -0
  3. anyscale/_private/anyscale_client/fake_anyscale_client.py +9 -0
  4. anyscale/_private/docgen/__main__.py +17 -2
  5. anyscale/client/README.md +11 -10
  6. anyscale/client/openapi_client/__init__.py +6 -6
  7. anyscale/client/openapi_client/api/default_api.py +210 -83
  8. anyscale/client/openapi_client/models/__init__.py +6 -6
  9. anyscale/client/openapi_client/models/aggregated_instance_usage_csv.py +81 -3
  10. anyscale/client/openapi_client/models/create_organization_configuration.py +3 -3
  11. anyscale/client/openapi_client/models/{create_resource_alert.py → create_resource_notification.py} +40 -40
  12. anyscale/client/openapi_client/models/{list_resource_alerts_query.py → list_resource_notifications_query.py} +24 -24
  13. anyscale/client/openapi_client/models/{customer_billing_type.py → machine_launch_failure.py} +65 -14
  14. anyscale/client/openapi_client/models/organization_configuration.py +3 -3
  15. anyscale/client/openapi_client/models/organization_configuration_response.py +3 -3
  16. anyscale/client/openapi_client/models/resource_alert_event_type.py +4 -1
  17. anyscale/client/openapi_client/models/{resource_alert.py → resource_notification.py} +64 -64
  18. anyscale/client/openapi_client/models/{resourcealert_list_response.py → resourcenotification_list_response.py} +15 -15
  19. anyscale/client/openapi_client/models/{resourcealert_response.py → resourcenotification_response.py} +11 -11
  20. anyscale/client/openapi_client/models/scheduler_info.py +32 -3
  21. anyscale/client/openapi_client/models/tool.py +2 -1
  22. anyscale/client/openapi_client/models/workspace_dataplane_proxied_artifacts.py +31 -3
  23. anyscale/cloud/__init__.py +44 -5
  24. anyscale/cloud/_private/cloud_sdk.py +59 -2
  25. anyscale/cloud/commands.py +72 -4
  26. anyscale/cloud/models.py +121 -3
  27. anyscale/commands/cloud_commands.py +76 -0
  28. anyscale/commands/command_examples.py +22 -0
  29. anyscale/commands/machine_pool_commands.py +15 -10
  30. anyscale/resource_quota/commands.py +1 -2
  31. anyscale/service/_private/service_sdk.py +2 -3
  32. anyscale/shared_anyscale_utils/constants.py +3 -0
  33. anyscale/version.py +1 -1
  34. {anyscale-0.25.11.dist-info → anyscale-0.26.1.dist-info}/METADATA +1 -1
  35. {anyscale-0.25.11.dist-info → anyscale-0.26.1.dist-info}/RECORD +40 -39
  36. {anyscale-0.25.11.dist-info → anyscale-0.26.1.dist-info}/LICENSE +0 -0
  37. {anyscale-0.25.11.dist-info → anyscale-0.26.1.dist-info}/NOTICE +0 -0
  38. {anyscale-0.25.11.dist-info → anyscale-0.26.1.dist-info}/WHEEL +0 -0
  39. {anyscale-0.25.11.dist-info → anyscale-0.26.1.dist-info}/entry_points.txt +0 -0
  40. {anyscale-0.25.11.dist-info → anyscale-0.26.1.dist-info}/top_level.txt +0 -0
@@ -34,15 +34,17 @@ class SchedulerInfo(object):
34
34
  """
35
35
  openapi_types = {
36
36
  'requests': 'list[RequestStateInfo]',
37
- 'machines': 'list[MachineStateInfo]'
37
+ 'machines': 'list[MachineStateInfo]',
38
+ 'recent_launch_failures': 'list[MachineLaunchFailure]'
38
39
  }
39
40
 
40
41
  attribute_map = {
41
42
  'requests': 'requests',
42
- 'machines': 'machines'
43
+ 'machines': 'machines',
44
+ 'recent_launch_failures': 'recent_launch_failures'
43
45
  }
44
46
 
45
- def __init__(self, requests=None, machines=None, local_vars_configuration=None): # noqa: E501
47
+ def __init__(self, requests=None, machines=None, recent_launch_failures=None, local_vars_configuration=None): # noqa: E501
46
48
  """SchedulerInfo - a model defined in OpenAPI""" # noqa: E501
47
49
  if local_vars_configuration is None:
48
50
  local_vars_configuration = Configuration()
@@ -50,10 +52,12 @@ class SchedulerInfo(object):
50
52
 
51
53
  self._requests = None
52
54
  self._machines = None
55
+ self._recent_launch_failures = None
53
56
  self.discriminator = None
54
57
 
55
58
  self.requests = requests
56
59
  self.machines = machines
60
+ self.recent_launch_failures = recent_launch_failures
57
61
 
58
62
  @property
59
63
  def requests(self):
@@ -105,6 +109,31 @@ class SchedulerInfo(object):
105
109
 
106
110
  self._machines = machines
107
111
 
112
+ @property
113
+ def recent_launch_failures(self):
114
+ """Gets the recent_launch_failures of this SchedulerInfo. # noqa: E501
115
+
116
+ Recent launch failures. # noqa: E501
117
+
118
+ :return: The recent_launch_failures of this SchedulerInfo. # noqa: E501
119
+ :rtype: list[MachineLaunchFailure]
120
+ """
121
+ return self._recent_launch_failures
122
+
123
+ @recent_launch_failures.setter
124
+ def recent_launch_failures(self, recent_launch_failures):
125
+ """Sets the recent_launch_failures of this SchedulerInfo.
126
+
127
+ Recent launch failures. # noqa: E501
128
+
129
+ :param recent_launch_failures: The recent_launch_failures of this SchedulerInfo. # noqa: E501
130
+ :type: list[MachineLaunchFailure]
131
+ """
132
+ if self.local_vars_configuration.client_side_validation and recent_launch_failures is None: # noqa: E501
133
+ raise ValueError("Invalid value for `recent_launch_failures`, must not be `None`") # noqa: E501
134
+
135
+ self._recent_launch_failures = recent_launch_failures
136
+
108
137
  def to_dict(self):
109
138
  """Returns the model properties as a dict"""
110
139
  result = {}
@@ -30,8 +30,9 @@ class Tool(object):
30
30
  """
31
31
  SERVICE_DASHBOARD = "service_dashboard"
32
32
  SERVE_DEPLOYMENT_DASHBOARD = "serve_deployment_dashboard"
33
+ SERVICE_ALERTING_DASHBOARD = "service_alerting_dashboard"
33
34
 
34
- allowable_values = [SERVICE_DASHBOARD, SERVE_DEPLOYMENT_DASHBOARD] # noqa: E501
35
+ allowable_values = [SERVICE_DASHBOARD, SERVE_DEPLOYMENT_DASHBOARD, SERVICE_ALERTING_DASHBOARD] # noqa: E501
35
36
 
36
37
  """
37
38
  Attributes:
@@ -36,17 +36,19 @@ class WorkspaceDataplaneProxiedArtifacts(object):
36
36
  'requirements': 'str',
37
37
  'skip_packages_tracking': 'str',
38
38
  'environment_variables': 'list[str]',
39
- 'dockerfile': 'str'
39
+ 'dockerfile': 'str',
40
+ 'dockerfile_draft': 'str'
40
41
  }
41
42
 
42
43
  attribute_map = {
43
44
  'requirements': 'requirements',
44
45
  'skip_packages_tracking': 'skip_packages_tracking',
45
46
  'environment_variables': 'environment_variables',
46
- 'dockerfile': 'dockerfile'
47
+ 'dockerfile': 'dockerfile',
48
+ 'dockerfile_draft': 'dockerfile_draft'
47
49
  }
48
50
 
49
- def __init__(self, requirements=None, skip_packages_tracking=None, environment_variables=None, dockerfile=None, local_vars_configuration=None): # noqa: E501
51
+ def __init__(self, requirements=None, skip_packages_tracking=None, environment_variables=None, dockerfile=None, dockerfile_draft=None, local_vars_configuration=None): # noqa: E501
50
52
  """WorkspaceDataplaneProxiedArtifacts - a model defined in OpenAPI""" # noqa: E501
51
53
  if local_vars_configuration is None:
52
54
  local_vars_configuration = Configuration()
@@ -56,6 +58,7 @@ class WorkspaceDataplaneProxiedArtifacts(object):
56
58
  self._skip_packages_tracking = None
57
59
  self._environment_variables = None
58
60
  self._dockerfile = None
61
+ self._dockerfile_draft = None
59
62
  self.discriminator = None
60
63
 
61
64
  if requirements is not None:
@@ -66,6 +69,8 @@ class WorkspaceDataplaneProxiedArtifacts(object):
66
69
  self.environment_variables = environment_variables
67
70
  if dockerfile is not None:
68
71
  self.dockerfile = dockerfile
72
+ if dockerfile_draft is not None:
73
+ self.dockerfile_draft = dockerfile_draft
69
74
 
70
75
  @property
71
76
  def requirements(self):
@@ -159,6 +164,29 @@ class WorkspaceDataplaneProxiedArtifacts(object):
159
164
 
160
165
  self._dockerfile = dockerfile
161
166
 
167
+ @property
168
+ def dockerfile_draft(self):
169
+ """Gets the dockerfile_draft of this WorkspaceDataplaneProxiedArtifacts. # noqa: E501
170
+
171
+ The Dockerfile.draft of the workspace. # noqa: E501
172
+
173
+ :return: The dockerfile_draft of this WorkspaceDataplaneProxiedArtifacts. # noqa: E501
174
+ :rtype: str
175
+ """
176
+ return self._dockerfile_draft
177
+
178
+ @dockerfile_draft.setter
179
+ def dockerfile_draft(self, dockerfile_draft):
180
+ """Sets the dockerfile_draft of this WorkspaceDataplaneProxiedArtifacts.
181
+
182
+ The Dockerfile.draft of the workspace. # noqa: E501
183
+
184
+ :param dockerfile_draft: The dockerfile_draft of this WorkspaceDataplaneProxiedArtifacts. # noqa: E501
185
+ :type: str
186
+ """
187
+
188
+ self._dockerfile_draft = dockerfile_draft
189
+
162
190
  def to_dict(self):
163
191
  """Returns the model properties as a dict"""
164
192
  result = {}
@@ -11,9 +11,14 @@ from anyscale.cloud._private.cloud_sdk import PrivateCloudSDK
11
11
  from anyscale.cloud.commands import (
12
12
  _ADD_COLLABORATORS_ARG_DOCSTRINGS,
13
13
  _ADD_COLLABORATORS_EXAMPLE,
14
+ _GET_ARG_DOCSTRINGS,
15
+ _GET_DEFAULT_EXAMPLE,
16
+ _GET_EXAMPLE,
14
17
  add_collaborators,
18
+ get,
19
+ get_default,
15
20
  )
16
- from anyscale.cloud.models import CreateCloudCollaborator
21
+ from anyscale.cloud.models import Cloud, CreateCloudCollaborator
17
22
  from anyscale.connect import ClientBuilder
18
23
 
19
24
 
@@ -31,12 +36,43 @@ class CloudSDK:
31
36
  doc_py_example=_ADD_COLLABORATORS_EXAMPLE,
32
37
  arg_docstrings=_ADD_COLLABORATORS_ARG_DOCSTRINGS,
33
38
  )
34
- def add_collaborators( # noqa: F811
35
- self, cloud: str, collaborators: List[CreateCloudCollaborator]
39
+ def add_collaborators(
40
+ self, cloud: str, collaborators: List[CreateCloudCollaborator],
36
41
  ) -> str:
37
- """Batch add collaborators to a cloud."""
42
+ """
43
+ Batch add collaborators to a cloud.
44
+
45
+ :param cloud: The cloud to add users to.
46
+ :param collaborators: The list of collaborators to add to the cloud.
47
+ """
38
48
  return self._private_sdk.add_collaborators(cloud, collaborators)
39
49
 
50
+ @sdk_docs(
51
+ doc_py_example=_GET_EXAMPLE, arg_docstrings=_GET_ARG_DOCSTRINGS,
52
+ )
53
+ def get_cloud(
54
+ self, id: Optional[str], name: Optional[str], # noqa: A002
55
+ ) -> Optional[Cloud]:
56
+ """
57
+ Retrieve a cloud by its name or ID.
58
+
59
+ :param id: The ID of the cloud to retrieve.
60
+ :param name: The name of the cloud to retrieve.
61
+ :return: A ``Cloud`` object if found, otherwise ``None``.
62
+ """
63
+ return self._private_sdk.get(id=id, name=name)
64
+
65
+ @sdk_docs(
66
+ doc_py_example=_GET_DEFAULT_EXAMPLE, arg_docstrings={},
67
+ )
68
+ def get_default_cloud(self) -> Optional[Cloud]:
69
+ """
70
+ Get the default cloud for your organization.
71
+
72
+ :return: The default ``Cloud`` object if it exists, otherwise ``None``.
73
+ """
74
+ return self._private_sdk.get_default()
75
+
40
76
 
41
77
  # Note: indentation here matches that of connect.py::ClientBuilder.
42
78
  BUILDER_HELP_FOOTER = """
@@ -48,13 +84,16 @@ class CloudModule(ModuleType):
48
84
  """
49
85
  A custom callable module object for `anyscale.cloud`.
50
86
 
51
- This hack is needed since `anyscale.cloud` is a function for Anyscale connect but also a module for the SDK.
87
+ This hack is needed since `anyscale.cloud` is a function for Anyscale connect
88
+ but also a module for the SDK.
52
89
  """
53
90
 
54
91
  def __init__(self):
55
92
  # Expose attributes from the SDK.
56
93
  self.CloudSDK = CloudSDK
57
94
  self.add_collaborators = add_collaborators
95
+ self.get = get
96
+ self.get_default = get_default
58
97
 
59
98
  # Expose Anyscale connect
60
99
  self.new_builder = self._new_builder()
@@ -1,10 +1,16 @@
1
- from typing import List
1
+ from typing import List, Optional
2
2
 
3
3
  from anyscale._private.sdk.base_sdk import BaseSDK
4
4
  from anyscale.client.openapi_client.models import (
5
+ Cloud as CloudModel,
5
6
  CreateCloudCollaborator as CreateCloudCollaboratorModel,
6
7
  )
7
- from anyscale.cloud.models import CreateCloudCollaborator
8
+ from anyscale.cloud.models import (
9
+ Cloud,
10
+ CloudProvider,
11
+ ComputeStack,
12
+ CreateCloudCollaborator,
13
+ )
8
14
 
9
15
 
10
16
  class PrivateCloudSDK(BaseSDK):
@@ -23,3 +29,54 @@ class PrivateCloudSDK(BaseSDK):
23
29
  for collaborator in collaborators
24
30
  ],
25
31
  )
32
+
33
+ def get(
34
+ self, id: Optional[str], name: Optional[str], # noqa: A002
35
+ ) -> Optional[Cloud]:
36
+ if (id and name) or (not id and not name):
37
+ raise ValueError("Provide exactly one of 'id' or 'name'.")
38
+
39
+ if id:
40
+ openapi_cloud = self.client.get_cloud(cloud_id=id)
41
+ else:
42
+ assert name is not None, "Name must be provided if id is not."
43
+ openapi_cloud = self.client.get_cloud_by_name(name=name)
44
+
45
+ return self._to_sdk_cloud(openapi_cloud)
46
+
47
+ def get_default(self) -> Optional[Cloud]:
48
+ openapi_cloud = self.client.get_default_cloud()
49
+
50
+ return self._to_sdk_cloud(openapi_cloud)
51
+
52
+ def _to_sdk_cloud(self, openapi_cloud: Optional["CloudModel"]) -> Optional[Cloud]:
53
+ if openapi_cloud is None:
54
+ return None
55
+
56
+ # Validate provider, default to UNKNOWN if validation fails
57
+ if openapi_cloud.provider is not None:
58
+ try:
59
+ provider = CloudProvider.validate(openapi_cloud.provider)
60
+ except ValueError:
61
+ provider = CloudProvider.UNKNOWN
62
+ else:
63
+ provider = CloudProvider.UNKNOWN
64
+
65
+ # Validate compute_stack, default to UNKNOWN if validation fails
66
+ if openapi_cloud.compute_stack is not None:
67
+ try:
68
+ compute_stack = ComputeStack.validate(openapi_cloud.compute_stack)
69
+ except ValueError:
70
+ compute_stack = ComputeStack.UNKNOWN
71
+ else:
72
+ compute_stack = ComputeStack.UNKNOWN
73
+
74
+ return Cloud(
75
+ id=openapi_cloud.id,
76
+ name=openapi_cloud.name,
77
+ provider=provider,
78
+ region=openapi_cloud.region,
79
+ created_at=openapi_cloud.created_at,
80
+ is_default=openapi_cloud.is_default,
81
+ compute_stack=compute_stack,
82
+ )
@@ -1,7 +1,7 @@
1
- from typing import List
1
+ from typing import List, Optional
2
2
 
3
3
  from anyscale._private.sdk import sdk_command
4
- from anyscale.cloud._private.cloud_sdk import PrivateCloudSDK
4
+ from anyscale.cloud._private.cloud_sdk import Cloud, PrivateCloudSDK
5
5
  from anyscale.cloud.models import CreateCloudCollaborator
6
6
 
7
7
 
@@ -39,7 +39,75 @@ _ADD_COLLABORATORS_ARG_DOCSTRINGS = {
39
39
  arg_docstrings=_ADD_COLLABORATORS_ARG_DOCSTRINGS,
40
40
  )
41
41
  def add_collaborators(
42
- cloud: str, collaborators: List[CreateCloudCollaborator], *, _sdk: PrivateCloudSDK
42
+ cloud: str, collaborators: List[CreateCloudCollaborator], *, _sdk: PrivateCloudSDK,
43
43
  ) -> str:
44
- """Batch add collaborators to a cloud."""
44
+ """
45
+ Batch add collaborators to a cloud.
46
+
47
+ :param cloud: The cloud to add users to.
48
+ :param collaborators: The list of collaborators to add to the cloud.
49
+ """
45
50
  return _sdk.add_collaborators(cloud, collaborators)
51
+
52
+
53
+ _GET_EXAMPLE = """
54
+ import anyscale
55
+
56
+ # Get a cloud by ID
57
+ cloud_by_id = anyscale.cloud.get(id="cloud_id")
58
+
59
+ # Get a cloud by name
60
+ cloud_by_name = anyscale.cloud.get(name="cloud_name")
61
+ """
62
+
63
+ _GET_ARG_DOCSTRINGS = {
64
+ "id": "The ID of the cloud to retrieve.",
65
+ "name": "The name of the cloud to retrieve.",
66
+ }
67
+
68
+
69
+ @sdk_command(
70
+ _CLOUD_SDK_SINGLETON_KEY,
71
+ PrivateCloudSDK,
72
+ doc_py_example=_GET_EXAMPLE,
73
+ arg_docstrings=_GET_ARG_DOCSTRINGS,
74
+ )
75
+ def get(
76
+ id: Optional[str] = None, # noqa: A002
77
+ name: Optional[str] = None,
78
+ *,
79
+ _sdk: PrivateCloudSDK,
80
+ ) -> Optional[Cloud]:
81
+ """
82
+ Get the cloud model for the provided cloud ID or name.
83
+
84
+ If neither ID nor name is provided, returns `None`.
85
+
86
+ :param id: The ID of the cloud to retrieve.
87
+ :param name: The name of the cloud to retrieve.
88
+ :return: A `Cloud` object if found, otherwise `None`.
89
+ """
90
+ return _sdk.get(id=id, name=name)
91
+
92
+
93
+ _GET_DEFAULT_EXAMPLE = """
94
+ import anyscale
95
+
96
+ # Get the user's default cloud
97
+ default_cloud = anyscale.cloud.get_default()
98
+ """
99
+
100
+
101
+ @sdk_command(
102
+ _CLOUD_SDK_SINGLETON_KEY,
103
+ PrivateCloudSDK,
104
+ doc_py_example=_GET_DEFAULT_EXAMPLE,
105
+ arg_docstrings={},
106
+ )
107
+ def get_default(*, _sdk: PrivateCloudSDK,) -> Optional[Cloud]:
108
+ """
109
+ Get the user's default cloud.
110
+
111
+ :return: The default `Cloud` object if it exists, otherwise `None`.
112
+ """
113
+ return _sdk.get_default()
anyscale/cloud/models.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Any, Dict, List
2
+ from datetime import datetime
3
+ from typing import Any, Dict, List, Optional, Union
3
4
 
4
5
  from anyscale._private.models import ModelBase, ModelEnum
5
6
 
@@ -11,7 +12,7 @@ class CloudPermissionLevel(ModelEnum):
11
12
  __docstrings__ = {
12
13
  WRITE: "Write permission level for the cloud",
13
14
  READONLY: "Readonly permission level for the cloud",
14
- }
15
+ } # type: ignore
15
16
 
16
17
 
17
18
  @dataclass(frozen=True)
@@ -43,7 +44,7 @@ create_cloud_collaborator = CreateCloudCollaborator(
43
44
  self, permission_level: CloudPermissionLevel
44
45
  ) -> CloudPermissionLevel:
45
46
  if isinstance(permission_level, str):
46
- return CloudPermissionLevel.validate(permission_level)
47
+ return CloudPermissionLevel.validate(permission_level) # type: ignore
47
48
  elif isinstance(permission_level, CloudPermissionLevel):
48
49
  return permission_level
49
50
  else:
@@ -89,3 +90,120 @@ create_cloud_collaborators = CreateCloudCollaborators(
89
90
  def _validate_collaborators(self, collaborators: List[Dict[str, Any]]):
90
91
  if not isinstance(collaborators, list):
91
92
  raise TypeError("Collaborators must be a list.")
93
+
94
+
95
+ class ComputeStack(ModelEnum):
96
+ UNKNOWN = "UNKNOWN"
97
+ VM = "VM"
98
+ K8S = "K8S"
99
+
100
+ __docstrings__ = {
101
+ UNKNOWN: "Unknown compute stack.",
102
+ VM: "Virtual machine-based compute stack.",
103
+ K8S: "Kubernetes-based compute stack.",
104
+ } # type: ignore
105
+
106
+
107
+ class CloudProvider(ModelEnum):
108
+ UNKNOWN = "UNKNOWN"
109
+ AWS = "AWS"
110
+ GCP = "GCP"
111
+ AZURE = "AZURE"
112
+
113
+ __docstrings__ = {
114
+ UNKNOWN: "Unknown cloud provider.",
115
+ AWS: "Amazon Web Services.",
116
+ GCP: "Google Cloud Platform.",
117
+ AZURE: "Microsoft Azure.",
118
+ } # type: ignore
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class Cloud(ModelBase):
123
+ """Minimal Cloud resource model."""
124
+
125
+ __doc_py_example__ = """\
126
+ from datetime import datetime
127
+ from anyscale.cloud.models import Cloud, CloudProvider, ComputeStack
128
+
129
+ cloud = Cloud(
130
+ name="my-cloud",
131
+ id="cloud-123",
132
+ provider="AWS", # This will be validated as CloudProvider.AWS
133
+ region="us-west-2",
134
+ created_at=datetime.now(),
135
+ is_default=True,
136
+ compute_stack="VM" # This will be validated as ComputeStack.VM
137
+ )
138
+ """
139
+
140
+ name: str = field(metadata={"docstring": "Name of this Cloud."})
141
+ id: str = field(metadata={"docstring": "Unique identifier for this Cloud."})
142
+ provider: Union[CloudProvider, str] = field(
143
+ metadata={
144
+ "docstring": "Cloud provider (AWS, GCP, AZURE) or UNKNOWN if not recognized."
145
+ },
146
+ )
147
+ compute_stack: Union[ComputeStack, str] = field(
148
+ metadata={
149
+ "docstring": "The compute stack associated with this cloud's primary cloud resource, or UNKNOWN if not recognized."
150
+ },
151
+ )
152
+ region: Optional[str] = field(
153
+ default=None, metadata={"docstring": "Region for this Cloud."}
154
+ )
155
+ created_at: Optional[datetime] = field(
156
+ default=None, metadata={"docstring": "When the Cloud was created."}
157
+ )
158
+ is_default: Optional[bool] = field(
159
+ default=None, metadata={"docstring": "Whether this is the default cloud."}
160
+ )
161
+
162
+ def _validate_name(self, name: str) -> str:
163
+ if not isinstance(name, str) or not name.strip():
164
+ raise ValueError("name must be a non-empty string")
165
+ return name
166
+
167
+ def _validate_id(self, id: str) -> str: # noqa: A002
168
+ if not isinstance(id, str) or not id.strip():
169
+ raise ValueError("id must be a non-empty string")
170
+ return id
171
+
172
+ def _validate_provider(self, provider: Union[CloudProvider, str]) -> CloudProvider:
173
+ if isinstance(provider, str):
174
+ # This will raise a ValueError if the provider is unrecognized.
175
+ provider = CloudProvider(provider)
176
+ elif not isinstance(provider, CloudProvider):
177
+ raise TypeError("'provider' must be a CloudProvider.")
178
+
179
+ return provider
180
+
181
+ def _validate_region(self, region: Optional[str]) -> Optional[str]:
182
+ if region is not None and not isinstance(region, str):
183
+ raise TypeError("region must be a string")
184
+ return region
185
+
186
+ def _validate_created_at(
187
+ self, created_at: Optional[datetime]
188
+ ) -> Optional[datetime]:
189
+ if created_at is None:
190
+ return None
191
+ if not isinstance(created_at, datetime):
192
+ raise TypeError("created_at must be a datetime object")
193
+ return created_at
194
+
195
+ def _validate_is_default(self, is_default: Optional[bool]) -> Optional[bool]:
196
+ if is_default is not None and not isinstance(is_default, bool):
197
+ raise TypeError("is_default must be a bool")
198
+ return is_default
199
+
200
+ def _validate_compute_stack(
201
+ self, compute_stack: Union[ComputeStack, str]
202
+ ) -> ComputeStack:
203
+ if isinstance(compute_stack, str):
204
+ # This will raise a ValueError if the compute_stack is unrecognized.
205
+ compute_stack = ComputeStack(compute_stack)
206
+ elif not isinstance(compute_stack, ComputeStack):
207
+ raise TypeError("'compute_stack' must be a ComputeStack.")
208
+
209
+ return compute_stack
@@ -1090,3 +1090,79 @@ def add_collaborators(cloud: str, users_file: str,) -> None:
1090
1090
  log.info(
1091
1091
  f"Successfully added {len(collaborators.collaborators)} collaborators to cloud {cloud}."
1092
1092
  )
1093
+
1094
+
1095
+ @cloud_cli.command(
1096
+ name="get",
1097
+ help="Get information about a specific cloud.",
1098
+ cls=AnyscaleCommand,
1099
+ example=command_examples.CLOUD_GET_CLOUD_EXAMPLE,
1100
+ )
1101
+ @click.option(
1102
+ "--name",
1103
+ "-n",
1104
+ help="Name of the cloud to get information about.",
1105
+ type=str,
1106
+ required=False,
1107
+ )
1108
+ @click.option(
1109
+ "--cloud-id",
1110
+ "--id",
1111
+ help="ID of the cloud to get information about.",
1112
+ type=str,
1113
+ required=False,
1114
+ )
1115
+ def get_cloud(cloud_id: Optional[str], name: Optional[str]) -> None:
1116
+ """
1117
+ Retrieve a cloud by its name or ID and display its details.
1118
+
1119
+ :param cloud_id: The ID of the cloud to retrieve.
1120
+ :param name: The name of the cloud to retrieve.
1121
+ """
1122
+ # Validate that exactly one of --name or --cloud-id is provided
1123
+ if (cloud_id and name) or (not cloud_id and not name):
1124
+ log.error("Please provide exactly one of --name or --cloud-id.")
1125
+ return
1126
+
1127
+ try:
1128
+ cloud = anyscale.cloud.get(id=cloud_id, name=name)
1129
+
1130
+ if not cloud:
1131
+ log.error("Cloud not found.")
1132
+ return
1133
+
1134
+ cloud_dict = cloud.to_dict() if hasattr(cloud, "to_dict") else cloud.__dict__
1135
+
1136
+ print(yaml.dump(cloud_dict, sort_keys=False))
1137
+
1138
+ except ValueError as e:
1139
+ log.error(f"Error retrieving cloud: {e}")
1140
+
1141
+
1142
+ @cloud_cli.command(
1143
+ name="get-default",
1144
+ help="Get the default cloud for your organization.",
1145
+ cls=AnyscaleCommand,
1146
+ example=command_examples.CLOUD_GET_DEFAULT_CLOUD_EXAMPLE,
1147
+ )
1148
+ def get_default_cloud() -> None:
1149
+ """
1150
+ Retrieve and display the default cloud configured for your organization.
1151
+ """
1152
+ try:
1153
+ default_cloud = anyscale.cloud.get_default()
1154
+
1155
+ if not default_cloud:
1156
+ log.error("No default cloud found.")
1157
+ return
1158
+
1159
+ cloud_dict = (
1160
+ default_cloud.to_dict()
1161
+ if hasattr(default_cloud, "to_dict")
1162
+ else default_cloud.__dict__
1163
+ )
1164
+
1165
+ print(yaml.dump(cloud_dict, sort_keys=False))
1166
+
1167
+ except ValueError as e:
1168
+ log.error(f"Error retrieving default cloud: {e}")
@@ -561,6 +561,28 @@ collaborators:
561
561
  permission_level: "readonly"
562
562
  """
563
563
 
564
+ CLOUD_GET_CLOUD_EXAMPLE = """\
565
+ $ anyscale cloud get --name cloud_name
566
+ name: anyscale_v2_default_cloud
567
+ id: cld_123
568
+ provider: AWS
569
+ region: us-west-2
570
+ created_at: 2022-10-18 05:12:13.335803+00:00
571
+ is_default: false
572
+ compute_stack: VM
573
+ """
574
+
575
+ CLOUD_GET_DEFAULT_CLOUD_EXAMPLE = """\
576
+ $ anyscale cloud get-default
577
+ name: anyscale_v2_default_cloud
578
+ id: cld_abc
579
+ provider: AWS
580
+ region: us-west-2
581
+ created_at: 2022-10-18 05:12:13.335803+00:00
582
+ is_default: true
583
+ compute_stack: VM
584
+ """
585
+
564
586
  SERVICE_ARCHIVE_EXAMPLE = """\
565
587
  $ anyscale service archive --name my_service
566
588
  """