anyscale 0.26.61__py3-none-any.whl → 0.26.63__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 (42) hide show
  1. anyscale/_private/anyscale_client/anyscale_client.py +4 -1
  2. anyscale/_private/docgen/__main__.py +0 -2
  3. anyscale/_private/docgen/models.md +2 -0
  4. anyscale/client/README.md +17 -3
  5. anyscale/client/openapi_client/__init__.py +10 -3
  6. anyscale/client/openapi_client/api/default_api.py +3229 -2400
  7. anyscale/client/openapi_client/models/__init__.py +10 -3
  8. anyscale/client/openapi_client/models/api_key_info.py +280 -0
  9. anyscale/client/openapi_client/models/api_key_parameters.py +29 -3
  10. anyscale/client/openapi_client/models/{aggregatedinstanceusagecsv_list_response.py → apikeyinfo_list_response.py} +15 -15
  11. anyscale/client/openapi_client/models/compute_node_type.py +29 -1
  12. anyscale/client/openapi_client/models/gpu_usage.py +236 -0
  13. anyscale/client/openapi_client/models/node_metrics.py +404 -0
  14. anyscale/client/openapi_client/models/node_metrics_response.py +123 -0
  15. anyscale/client/openapi_client/models/nodemetricsresponse_response.py +121 -0
  16. anyscale/client/openapi_client/models/operator_metrics.py +27 -1
  17. anyscale/client/openapi_client/models/revoke_api_keys_request.py +123 -0
  18. anyscale/client/openapi_client/models/revoke_api_keys_response.py +202 -0
  19. anyscale/client/openapi_client/models/revokeapikeysresponse_response.py +121 -0
  20. anyscale/client/openapi_client/models/{cloud_hosting_type.py → task_summary_config.py} +33 -13
  21. anyscale/client/openapi_client/models/task_table_config.py +29 -3
  22. anyscale/client/openapi_client/models/task_table_row.py +29 -3
  23. anyscale/client/openapi_client/models/worker_node_type.py +29 -1
  24. anyscale/commands/cloud_commands.py +146 -61
  25. anyscale/commands/command_examples.py +12 -0
  26. anyscale/commands/service_account_commands.py +0 -21
  27. anyscale/compute_config/_private/compute_config_sdk.py +4 -0
  28. anyscale/compute_config/models.py +24 -0
  29. anyscale/controllers/cloud_controller.py +94 -20
  30. anyscale/sdk/anyscale_client/models/compute_node_type.py +29 -1
  31. anyscale/sdk/anyscale_client/models/worker_node_type.py +29 -1
  32. anyscale/service_account/_private/service_account_sdk.py +10 -1
  33. anyscale/version.py +1 -1
  34. anyscale/workspace/commands.py +23 -114
  35. {anyscale-0.26.61.dist-info → anyscale-0.26.63.dist-info}/METADATA +1 -1
  36. {anyscale-0.26.61.dist-info → anyscale-0.26.63.dist-info}/RECORD +41 -34
  37. anyscale/client/openapi_client/models/aggregated_instance_usage_csv.py +0 -889
  38. {anyscale-0.26.61.dist-info → anyscale-0.26.63.dist-info}/WHEEL +0 -0
  39. {anyscale-0.26.61.dist-info → anyscale-0.26.63.dist-info}/entry_points.txt +0 -0
  40. {anyscale-0.26.61.dist-info → anyscale-0.26.63.dist-info}/licenses/LICENSE +0 -0
  41. {anyscale-0.26.61.dist-info → anyscale-0.26.63.dist-info}/licenses/NOTICE +0 -0
  42. {anyscale-0.26.61.dist-info → anyscale-0.26.63.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,121 @@
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 RevokeapikeysresponseResponse(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
+ 'result': 'RevokeApiKeysResponse'
37
+ }
38
+
39
+ attribute_map = {
40
+ 'result': 'result'
41
+ }
42
+
43
+ def __init__(self, result=None, local_vars_configuration=None): # noqa: E501
44
+ """RevokeapikeysresponseResponse - a model defined in OpenAPI""" # noqa: E501
45
+ if local_vars_configuration is None:
46
+ local_vars_configuration = Configuration()
47
+ self.local_vars_configuration = local_vars_configuration
48
+
49
+ self._result = None
50
+ self.discriminator = None
51
+
52
+ self.result = result
53
+
54
+ @property
55
+ def result(self):
56
+ """Gets the result of this RevokeapikeysresponseResponse. # noqa: E501
57
+
58
+
59
+ :return: The result of this RevokeapikeysresponseResponse. # noqa: E501
60
+ :rtype: RevokeApiKeysResponse
61
+ """
62
+ return self._result
63
+
64
+ @result.setter
65
+ def result(self, result):
66
+ """Sets the result of this RevokeapikeysresponseResponse.
67
+
68
+
69
+ :param result: The result of this RevokeapikeysresponseResponse. # noqa: E501
70
+ :type: RevokeApiKeysResponse
71
+ """
72
+ if self.local_vars_configuration.client_side_validation and result is None: # noqa: E501
73
+ raise ValueError("Invalid value for `result`, must not be `None`") # noqa: E501
74
+
75
+ self._result = result
76
+
77
+ def to_dict(self):
78
+ """Returns the model properties as a dict"""
79
+ result = {}
80
+
81
+ for attr, _ in six.iteritems(self.openapi_types):
82
+ value = getattr(self, attr)
83
+ if isinstance(value, list):
84
+ result[attr] = list(map(
85
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
86
+ value
87
+ ))
88
+ elif hasattr(value, "to_dict"):
89
+ result[attr] = value.to_dict()
90
+ elif isinstance(value, dict):
91
+ result[attr] = dict(map(
92
+ lambda item: (item[0], item[1].to_dict())
93
+ if hasattr(item[1], "to_dict") else item,
94
+ value.items()
95
+ ))
96
+ else:
97
+ result[attr] = value
98
+
99
+ return result
100
+
101
+ def to_str(self):
102
+ """Returns the string representation of the model"""
103
+ return pprint.pformat(self.to_dict())
104
+
105
+ def __repr__(self):
106
+ """For `print` and `pprint`"""
107
+ return self.to_str()
108
+
109
+ def __eq__(self, other):
110
+ """Returns true if both objects are equal"""
111
+ if not isinstance(other, RevokeapikeysresponseResponse):
112
+ return False
113
+
114
+ return self.to_dict() == other.to_dict()
115
+
116
+ def __ne__(self, other):
117
+ """Returns true if both objects are not equal"""
118
+ if not isinstance(other, RevokeapikeysresponseResponse):
119
+ return True
120
+
121
+ return self.to_dict() != other.to_dict()
@@ -18,21 +18,13 @@ import six
18
18
  from openapi_client.configuration import Configuration
19
19
 
20
20
 
21
- class CloudHostingType(object):
21
+ class TaskSummaryConfig(object):
22
22
  """NOTE: This class is auto generated by OpenAPI Generator.
23
23
  Ref: https://openapi-generator.tech
24
24
 
25
25
  Do not edit the class manually.
26
26
  """
27
27
 
28
- """
29
- allowed enum values
30
- """
31
- ANYSCALE_HOSTED = "anyscale_hosted"
32
- CUSTOMER_HOSTED = "customer_hosted"
33
-
34
- allowable_values = [ANYSCALE_HOSTED, CUSTOMER_HOSTED] # noqa: E501
35
-
36
28
  """
37
29
  Attributes:
38
30
  openapi_types (dict): The key is attribute name
@@ -41,18 +33,46 @@ class CloudHostingType(object):
41
33
  and the value is json key in definition.
42
34
  """
43
35
  openapi_types = {
36
+ 'data_operator_id': 'str'
44
37
  }
45
38
 
46
39
  attribute_map = {
40
+ 'data_operator_id': 'data_operator_id'
47
41
  }
48
42
 
49
- def __init__(self, local_vars_configuration=None): # noqa: E501
50
- """CloudHostingType - a model defined in OpenAPI""" # noqa: E501
43
+ def __init__(self, data_operator_id=None, local_vars_configuration=None): # noqa: E501
44
+ """TaskSummaryConfig - a model defined in OpenAPI""" # noqa: E501
51
45
  if local_vars_configuration is None:
52
46
  local_vars_configuration = Configuration()
53
47
  self.local_vars_configuration = local_vars_configuration
48
+
49
+ self._data_operator_id = None
54
50
  self.discriminator = None
55
51
 
52
+ if data_operator_id is not None:
53
+ self.data_operator_id = data_operator_id
54
+
55
+ @property
56
+ def data_operator_id(self):
57
+ """Gets the data_operator_id of this TaskSummaryConfig. # noqa: E501
58
+
59
+
60
+ :return: The data_operator_id of this TaskSummaryConfig. # noqa: E501
61
+ :rtype: str
62
+ """
63
+ return self._data_operator_id
64
+
65
+ @data_operator_id.setter
66
+ def data_operator_id(self, data_operator_id):
67
+ """Sets the data_operator_id of this TaskSummaryConfig.
68
+
69
+
70
+ :param data_operator_id: The data_operator_id of this TaskSummaryConfig. # noqa: E501
71
+ :type: str
72
+ """
73
+
74
+ self._data_operator_id = data_operator_id
75
+
56
76
  def to_dict(self):
57
77
  """Returns the model properties as a dict"""
58
78
  result = {}
@@ -87,14 +107,14 @@ class CloudHostingType(object):
87
107
 
88
108
  def __eq__(self, other):
89
109
  """Returns true if both objects are equal"""
90
- if not isinstance(other, CloudHostingType):
110
+ if not isinstance(other, TaskSummaryConfig):
91
111
  return False
92
112
 
93
113
  return self.to_dict() == other.to_dict()
94
114
 
95
115
  def __ne__(self, other):
96
116
  """Returns true if both objects are not equal"""
97
- if not isinstance(other, CloudHostingType):
117
+ if not isinstance(other, TaskSummaryConfig):
98
118
  return True
99
119
 
100
120
  return self.to_dict() != other.to_dict()
@@ -39,7 +39,8 @@ class TaskTableConfig(object):
39
39
  'text': 'str',
40
40
  'job_id': 'str',
41
41
  'exception_type': 'str',
42
- 'attempts': 'TaskAttempts'
42
+ 'attempts': 'TaskAttempts',
43
+ 'data_operator_id': 'str'
43
44
  }
44
45
 
45
46
  attribute_map = {
@@ -49,10 +50,11 @@ class TaskTableConfig(object):
49
50
  'text': 'text',
50
51
  'job_id': 'job_id',
51
52
  'exception_type': 'exception_type',
52
- 'attempts': 'attempts'
53
+ 'attempts': 'attempts',
54
+ 'data_operator_id': 'data_operator_id'
53
55
  }
54
56
 
55
- def __init__(self, task_id=None, function_name=None, current_state=None, text=None, job_id=None, exception_type=None, attempts=None, local_vars_configuration=None): # noqa: E501
57
+ def __init__(self, task_id=None, function_name=None, current_state=None, text=None, job_id=None, exception_type=None, attempts=None, data_operator_id=None, local_vars_configuration=None): # noqa: E501
56
58
  """TaskTableConfig - a model defined in OpenAPI""" # noqa: E501
57
59
  if local_vars_configuration is None:
58
60
  local_vars_configuration = Configuration()
@@ -65,6 +67,7 @@ class TaskTableConfig(object):
65
67
  self._job_id = None
66
68
  self._exception_type = None
67
69
  self._attempts = None
70
+ self._data_operator_id = None
68
71
  self.discriminator = None
69
72
 
70
73
  if task_id is not None:
@@ -81,6 +84,8 @@ class TaskTableConfig(object):
81
84
  self.exception_type = exception_type
82
85
  if attempts is not None:
83
86
  self.attempts = attempts
87
+ if data_operator_id is not None:
88
+ self.data_operator_id = data_operator_id
84
89
 
85
90
  @property
86
91
  def task_id(self):
@@ -229,6 +234,27 @@ class TaskTableConfig(object):
229
234
 
230
235
  self._attempts = attempts
231
236
 
237
+ @property
238
+ def data_operator_id(self):
239
+ """Gets the data_operator_id of this TaskTableConfig. # noqa: E501
240
+
241
+
242
+ :return: The data_operator_id of this TaskTableConfig. # noqa: E501
243
+ :rtype: str
244
+ """
245
+ return self._data_operator_id
246
+
247
+ @data_operator_id.setter
248
+ def data_operator_id(self, data_operator_id):
249
+ """Sets the data_operator_id of this TaskTableConfig.
250
+
251
+
252
+ :param data_operator_id: The data_operator_id of this TaskTableConfig. # noqa: E501
253
+ :type: str
254
+ """
255
+
256
+ self._data_operator_id = data_operator_id
257
+
232
258
  def to_dict(self):
233
259
  """Returns the model properties as a dict"""
234
260
  result = {}
@@ -51,7 +51,8 @@ class TaskTableRow(object):
51
51
  'worker_pid': 'str',
52
52
  'parent_task_id': 'str',
53
53
  'ray_session_name': 'str',
54
- 'exception_type': 'str'
54
+ 'exception_type': 'str',
55
+ 'data_operator_id': 'str'
55
56
  }
56
57
 
57
58
  attribute_map = {
@@ -73,10 +74,11 @@ class TaskTableRow(object):
73
74
  'worker_pid': 'worker_pid',
74
75
  'parent_task_id': 'parent_task_id',
75
76
  'ray_session_name': 'ray_session_name',
76
- 'exception_type': 'exception_type'
77
+ 'exception_type': 'exception_type',
78
+ 'data_operator_id': 'data_operator_id'
77
79
  }
78
80
 
79
- def __init__(self, id=None, attempt_number=None, total_attempts=None, job_id=None, function_name=None, task_type=None, current_state=None, error_message=None, start_time_ns=None, start_running_time_ns=None, end_time_ns=None, required_resources=None, runtime_env=None, node_id=None, worker_id=None, worker_pid=None, parent_task_id=None, ray_session_name=None, exception_type=None, local_vars_configuration=None): # noqa: E501
81
+ def __init__(self, id=None, attempt_number=None, total_attempts=None, job_id=None, function_name=None, task_type=None, current_state=None, error_message=None, start_time_ns=None, start_running_time_ns=None, end_time_ns=None, required_resources=None, runtime_env=None, node_id=None, worker_id=None, worker_pid=None, parent_task_id=None, ray_session_name=None, exception_type=None, data_operator_id=None, local_vars_configuration=None): # noqa: E501
80
82
  """TaskTableRow - a model defined in OpenAPI""" # noqa: E501
81
83
  if local_vars_configuration is None:
82
84
  local_vars_configuration = Configuration()
@@ -101,6 +103,7 @@ class TaskTableRow(object):
101
103
  self._parent_task_id = None
102
104
  self._ray_session_name = None
103
105
  self._exception_type = None
106
+ self._data_operator_id = None
104
107
  self.discriminator = None
105
108
 
106
109
  self.id = id
@@ -133,6 +136,8 @@ class TaskTableRow(object):
133
136
  self.ray_session_name = ray_session_name
134
137
  if exception_type is not None:
135
138
  self.exception_type = exception_type
139
+ if data_operator_id is not None:
140
+ self.data_operator_id = data_operator_id
136
141
 
137
142
  @property
138
143
  def id(self):
@@ -549,6 +554,27 @@ class TaskTableRow(object):
549
554
 
550
555
  self._exception_type = exception_type
551
556
 
557
+ @property
558
+ def data_operator_id(self):
559
+ """Gets the data_operator_id of this TaskTableRow. # noqa: E501
560
+
561
+
562
+ :return: The data_operator_id of this TaskTableRow. # noqa: E501
563
+ :rtype: str
564
+ """
565
+ return self._data_operator_id
566
+
567
+ @data_operator_id.setter
568
+ def data_operator_id(self, data_operator_id):
569
+ """Sets the data_operator_id of this TaskTableRow.
570
+
571
+
572
+ :param data_operator_id: The data_operator_id of this TaskTableRow. # noqa: E501
573
+ :type: str
574
+ """
575
+
576
+ self._data_operator_id = data_operator_id
577
+
552
578
  def to_dict(self):
553
579
  """Returns the model properties as a dict"""
554
580
  result = {}
@@ -36,6 +36,7 @@ class WorkerNodeType(object):
36
36
  'name': 'str',
37
37
  'instance_type': 'str',
38
38
  'resources': 'Resources',
39
+ 'labels': 'dict(str, str)',
39
40
  'aws_advanced_configurations_json': 'object',
40
41
  'gcp_advanced_configurations_json': 'object',
41
42
  'advanced_configurations_json': 'object',
@@ -50,6 +51,7 @@ class WorkerNodeType(object):
50
51
  'name': 'name',
51
52
  'instance_type': 'instance_type',
52
53
  'resources': 'resources',
54
+ 'labels': 'labels',
53
55
  'aws_advanced_configurations_json': 'aws_advanced_configurations_json',
54
56
  'gcp_advanced_configurations_json': 'gcp_advanced_configurations_json',
55
57
  'advanced_configurations_json': 'advanced_configurations_json',
@@ -60,7 +62,7 @@ class WorkerNodeType(object):
60
62
  'fallback_to_ondemand': 'fallback_to_ondemand'
61
63
  }
62
64
 
63
- def __init__(self, name=None, instance_type=None, resources=None, aws_advanced_configurations_json=None, gcp_advanced_configurations_json=None, advanced_configurations_json=None, flags=None, min_workers=None, max_workers=None, use_spot=False, fallback_to_ondemand=False, local_vars_configuration=None): # noqa: E501
65
+ def __init__(self, name=None, instance_type=None, resources=None, labels=None, aws_advanced_configurations_json=None, gcp_advanced_configurations_json=None, advanced_configurations_json=None, flags=None, min_workers=None, max_workers=None, use_spot=False, fallback_to_ondemand=False, local_vars_configuration=None): # noqa: E501
64
66
  """WorkerNodeType - a model defined in OpenAPI""" # noqa: E501
65
67
  if local_vars_configuration is None:
66
68
  local_vars_configuration = Configuration()
@@ -69,6 +71,7 @@ class WorkerNodeType(object):
69
71
  self._name = None
70
72
  self._instance_type = None
71
73
  self._resources = None
74
+ self._labels = None
72
75
  self._aws_advanced_configurations_json = None
73
76
  self._gcp_advanced_configurations_json = None
74
77
  self._advanced_configurations_json = None
@@ -83,6 +86,8 @@ class WorkerNodeType(object):
83
86
  self.instance_type = instance_type
84
87
  if resources is not None:
85
88
  self.resources = resources
89
+ if labels is not None:
90
+ self.labels = labels
86
91
  if aws_advanced_configurations_json is not None:
87
92
  self.aws_advanced_configurations_json = aws_advanced_configurations_json
88
93
  if gcp_advanced_configurations_json is not None:
@@ -173,6 +178,29 @@ class WorkerNodeType(object):
173
178
 
174
179
  self._resources = resources
175
180
 
181
+ @property
182
+ def labels(self):
183
+ """Gets the labels of this WorkerNodeType. # noqa: E501
184
+
185
+ Labels to associate the node with for scheduling purposes. Defaults to the list of Ray & Anyscale default labels. # noqa: E501
186
+
187
+ :return: The labels of this WorkerNodeType. # noqa: E501
188
+ :rtype: dict(str, str)
189
+ """
190
+ return self._labels
191
+
192
+ @labels.setter
193
+ def labels(self, labels):
194
+ """Sets the labels of this WorkerNodeType.
195
+
196
+ Labels to associate the node with for scheduling purposes. Defaults to the list of Ray & Anyscale default labels. # noqa: E501
197
+
198
+ :param labels: The labels of this WorkerNodeType. # noqa: E501
199
+ :type: dict(str, str)
200
+ """
201
+
202
+ self._labels = labels
203
+
176
204
  @property
177
205
  def aws_advanced_configurations_json(self):
178
206
  """Gets the aws_advanced_configurations_json of this WorkerNodeType. # noqa: E501
@@ -433,22 +433,130 @@ def cloud_update( # noqa: PLR0913
433
433
  help="Cloud id to get details about. Alternative to cloud name.",
434
434
  required=False,
435
435
  )
436
+ @click.option(
437
+ "--resource",
438
+ help="Name of the cloud resource to get details for. If not provided, defaults to the primary resource for the cloud.",
439
+ type=str,
440
+ required=False,
441
+ )
442
+ @click.option(
443
+ "--resource-id",
444
+ "cloud_resource_id",
445
+ help="Cloud resource ID to get details for. Alternative to cloud resource name.",
446
+ type=str,
447
+ required=False,
448
+ )
436
449
  def cloud_config_get(
437
- cloud_name: Optional[str], name: Optional[str], cloud_id: Optional[str]
450
+ cloud_name: Optional[str],
451
+ name: Optional[str],
452
+ cloud_id: Optional[str],
453
+ resource: Optional[str],
454
+ cloud_resource_id: Optional[str],
438
455
  ) -> None:
439
456
  if cloud_name and name and cloud_name != name:
440
457
  raise click.ClickException(
441
458
  "The positional argument CLOUD_NAME and the keyword argument --name "
442
459
  "were both provided. Please only provide one of these two arguments."
443
460
  )
461
+
462
+ # Validate resource selection options
463
+ if resource and cloud_resource_id:
464
+ raise click.ClickException(
465
+ "Cannot specify both --resource and --resource-id. Please provide only one."
466
+ )
467
+
444
468
  config = CloudController().get_cloud_config(
445
- cloud_name=cloud_name or name, cloud_id=cloud_id,
469
+ cloud_name=cloud_name or name,
470
+ cloud_id=cloud_id,
471
+ resource=resource,
472
+ cloud_resource_id=cloud_resource_id,
446
473
  )
447
474
  stream = StringIO()
448
475
  yaml.dump(config.spec, stream)
449
476
  print(stream.getvalue())
450
477
 
451
478
 
479
+ def _validate_cloud_config_update_args(
480
+ cloud_name: Optional[str],
481
+ name: Optional[str],
482
+ resource: Optional[str],
483
+ cloud_resource_id: Optional[str],
484
+ passed_enable_disable_flags: bool,
485
+ spec_file: Optional[str],
486
+ ) -> None:
487
+ """Validate arguments for cloud config update command."""
488
+ if cloud_name and name and cloud_name != name:
489
+ raise click.ClickException(
490
+ "The positional argument CLOUD_NAME and the keyword argument --name "
491
+ "were both provided. Please only provide one of these two arguments."
492
+ )
493
+
494
+ if resource and cloud_resource_id:
495
+ raise click.ClickException(
496
+ "Cannot specify both --resource and --resource-id. Please provide only one."
497
+ )
498
+
499
+ if passed_enable_disable_flags and spec_file:
500
+ raise click.ClickException(
501
+ "Invalid combination of arguments: --spec-file should not be provided with any other enable/disable flags."
502
+ )
503
+
504
+ if (resource or cloud_resource_id) and not spec_file:
505
+ raise click.ClickException(
506
+ "--resource and --resource-id can only be used with --spec-file."
507
+ )
508
+
509
+
510
+ def _handle_log_ingestion_config(enable_log_ingestion: Optional[bool]) -> None:
511
+ """Handle log ingestion configuration with user prompts."""
512
+ if enable_log_ingestion is True:
513
+ consent_message = click.prompt(
514
+ "--enable-log-ingestion is specified. Please note the logs produced by "
515
+ "your cluster will be ingested into Anyscale's service in region "
516
+ "us-west-2. Your clusters may incur extra data transfer cost from the "
517
+ "cloud provider. If you are sure you want to enable this feature, "
518
+ 'please type "consent"',
519
+ type=str,
520
+ )
521
+ if consent_message != "consent":
522
+ raise click.ClickException(
523
+ 'You must type "consent" to enable log ingestion.'
524
+ )
525
+ elif enable_log_ingestion is False:
526
+ confirm_response = click.confirm(
527
+ "--disable-log-ingestion is specified. Please note the logs that's "
528
+ "already ingested will not be deleted. Existing clusters will not stop"
529
+ "the log ingestion until you restart them. Logs are automatically "
530
+ "deleted after 30 days from the time of ingestion. Are you sure you "
531
+ "want to disable log ingestion?"
532
+ )
533
+ if not confirm_response:
534
+ raise click.ClickException("You must confirm to disable log ingestion.")
535
+
536
+
537
+ def _handle_system_cluster_config(enable_system_cluster: Optional[bool]) -> None:
538
+ """Handle system cluster configuration with user prompts."""
539
+ confirm_response = True
540
+ if enable_system_cluster is True:
541
+ confirm_response = click.confirm(
542
+ "--enable-system-cluster is specified. Please note that this will enable "
543
+ "system cluster functionality for the cloud and will incur extra cost. "
544
+ "Are you sure you want to enable system cluster?"
545
+ )
546
+ elif enable_system_cluster is False:
547
+ confirm_response = click.confirm(
548
+ "--disable-system-cluster is specified. This will disable system cluster "
549
+ "functionality for the cloud. Please note that this will not terminate "
550
+ "the system cluster if it is currently running. "
551
+ "Are you sure you want to disable system cluster?"
552
+ )
553
+
554
+ if enable_system_cluster is not None and not confirm_response:
555
+ raise click.ClickException(
556
+ f"You must confirm to {'enable' if enable_system_cluster else 'disable'} system cluster."
557
+ )
558
+
559
+
452
560
  @cloud_config_group.command(
453
561
  "update",
454
562
  help="Update the current configuration for a cloud.",
@@ -490,88 +598,65 @@ def cloud_config_get(
490
598
  required=False,
491
599
  help="Provide a path to a specification file.",
492
600
  )
493
- def cloud_config_update(
601
+ @click.option(
602
+ "--resource",
603
+ help="Name of the cloud resource to get details for. If not provided, defaults to the primary resource for the cloud.",
604
+ type=str,
605
+ required=False,
606
+ )
607
+ @click.option(
608
+ "--resource-id",
609
+ "cloud_resource_id",
610
+ help="Cloud resource ID to get details for. Alternative to cloud resource name.",
611
+ type=str,
612
+ required=False,
613
+ )
614
+ def cloud_config_update( # noqa: PLR0913
494
615
  cloud_name: Optional[str],
495
616
  name: Optional[str],
496
617
  cloud_id: Optional[str],
497
618
  enable_log_ingestion: Optional[bool],
498
619
  enable_system_cluster: Optional[bool],
499
620
  spec_file: Optional[str],
621
+ resource: Optional[str],
622
+ cloud_resource_id: Optional[str],
500
623
  ) -> None:
501
- if cloud_name and name and cloud_name != name:
502
- raise click.ClickException(
503
- "The positional argument CLOUD_NAME and the keyword argument --name "
504
- "were both provided. Please only provide one of these two arguments."
505
- )
506
-
507
624
  passed_enable_disable_flags = any(
508
625
  [enable_log_ingestion is not None, enable_system_cluster is not None]
509
626
  )
510
- if passed_enable_disable_flags and spec_file:
511
- raise click.ClickException(
512
- "Invalid combination of arguments: --spec-file should not be provided with any other enable/disable flags."
513
- )
514
627
 
515
- if passed_enable_disable_flags:
516
- # Handle log ingestion configuration
517
- # TODO: enable_log_ingestion should be unified into cloud deployment config.
518
- if enable_log_ingestion is True:
519
- consent_message = click.prompt(
520
- "--enable-log-ingestion is specified. Please note the logs produced by "
521
- "your cluster will be ingested into Anyscale's service in region "
522
- "us-west-2. Your clusters may incur extra data transfer cost from the "
523
- "cloud provider. If you are sure you want to enable this feature, "
524
- 'please type "consent"',
525
- type=str,
526
- )
527
- if consent_message != "consent":
528
- raise click.ClickException(
529
- 'You must type "consent" to enable log ingestion.'
530
- )
531
- elif enable_log_ingestion is False:
532
- confirm_response = click.confirm(
533
- "--disable-log-ingestion is specified. Please note the logs that's "
534
- "already ingested will not be deleted. Existing clusters will not stop"
535
- "the log ingestion until you restart them. Logs are automatically "
536
- "deleted after 30 days from the time of ingestion. Are you sure you "
537
- "want to disable log ingestion?"
538
- )
539
- if not confirm_response:
540
- raise click.ClickException("You must confirm to disable log ingestion.")
628
+ _validate_cloud_config_update_args(
629
+ cloud_name,
630
+ name,
631
+ resource,
632
+ cloud_resource_id,
633
+ passed_enable_disable_flags,
634
+ spec_file,
635
+ )
541
636
 
637
+ cloud_name_resolved = cloud_name or name
638
+
639
+ if passed_enable_disable_flags:
640
+ _handle_log_ingestion_config(enable_log_ingestion)
542
641
  CloudController().update_cloud_config(
543
- cloud_name=cloud_name or name,
642
+ cloud_name=cloud_name_resolved,
544
643
  cloud_id=cloud_id,
545
644
  enable_log_ingestion=enable_log_ingestion,
546
645
  )
547
646
 
548
- # Handle system cluster configuration
549
- if enable_system_cluster is True:
550
- confirm_response = click.confirm(
551
- "--enable-system-cluster is specified. Please note that this will enable "
552
- "system cluster functionality for the cloud and will incur extra cost. "
553
- "Are you sure you want to enable system cluster?"
554
- )
555
- elif enable_system_cluster is False:
556
- confirm_response = click.confirm(
557
- "--disable-system-cluster is specified. This will disable system cluster "
558
- "functionality for the cloud. Please note that this will not terminate "
559
- "the system cluster if it is currently running. "
560
- "Are you sure you want to disable system cluster?"
561
- )
562
- if enable_system_cluster is not None and not confirm_response:
563
- raise click.ClickException(
564
- f"You must confirm to {'enable' if enable_system_cluster else 'disable'} system cluster."
565
- )
566
-
647
+ _handle_system_cluster_config(enable_system_cluster)
567
648
  CloudController().update_system_cluster_config(
568
- cloud_name=cloud_name or name,
649
+ cloud_name=cloud_name_resolved,
569
650
  cloud_id=cloud_id,
570
651
  system_cluster_enabled=enable_system_cluster,
571
652
  )
572
653
  elif spec_file:
573
654
  CloudController().update_cloud_config(
574
- cloud_name=cloud_name or name, cloud_id=cloud_id, spec_file=spec_file,
655
+ cloud_name=cloud_name_resolved,
656
+ cloud_id=cloud_id,
657
+ spec_file=spec_file,
658
+ resource=resource,
659
+ cloud_resource_id=cloud_resource_id,
575
660
  )
576
661
  else:
577
662
  raise click.ClickException(