anyscale 0.26.66__py3-none-any.whl → 0.26.68__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/client/README.md +20 -0
- anyscale/client/openapi_client/__init__.py +15 -0
- anyscale/client/openapi_client/api/default_api.py +656 -0
- anyscale/client/openapi_client/models/__init__.py +15 -0
- anyscale/client/openapi_client/models/lineage_artifact.py +383 -0
- anyscale/client/openapi_client/models/lineage_artifact_sort_field.py +101 -0
- anyscale/client/openapi_client/models/lineage_artifact_type.py +100 -0
- anyscale/client/openapi_client/models/lineage_direction.py +101 -0
- anyscale/client/openapi_client/models/lineage_graph.py +179 -0
- anyscale/client/openapi_client/models/lineage_graph_node.py +439 -0
- anyscale/client/openapi_client/models/lineage_node_type.py +100 -0
- anyscale/client/openapi_client/models/lineage_workload.py +355 -0
- anyscale/client/openapi_client/models/lineage_workload_sort_field.py +101 -0
- anyscale/client/openapi_client/models/lineage_workload_type.py +101 -0
- anyscale/client/openapi_client/models/lineageartifact_list_response.py +147 -0
- anyscale/client/openapi_client/models/lineageartifact_response.py +121 -0
- anyscale/client/openapi_client/models/lineagegraph_response.py +121 -0
- anyscale/client/openapi_client/models/lineageworkload_list_response.py +147 -0
- anyscale/client/openapi_client/models/lineageworkload_response.py +121 -0
- anyscale/commands/cloud_commands.py +58 -0
- anyscale/commands/setup_k8s.py +1467 -0
- anyscale/controllers/cloud_controller.py +11 -10
- anyscale/controllers/kubernetes_verifier.py +65 -11
- anyscale/utils/cloudformation_utils.py +364 -0
- anyscale/version.py +1 -1
- {anyscale-0.26.66.dist-info → anyscale-0.26.68.dist-info}/METADATA +1 -1
- {anyscale-0.26.66.dist-info → anyscale-0.26.68.dist-info}/RECORD +32 -15
- {anyscale-0.26.66.dist-info → anyscale-0.26.68.dist-info}/WHEEL +0 -0
- {anyscale-0.26.66.dist-info → anyscale-0.26.68.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.66.dist-info → anyscale-0.26.68.dist-info}/licenses/LICENSE +0 -0
- {anyscale-0.26.66.dist-info → anyscale-0.26.68.dist-info}/licenses/NOTICE +0 -0
- {anyscale-0.26.66.dist-info → anyscale-0.26.68.dist-info}/top_level.txt +0 -0
@@ -4193,48 +4193,49 @@ class CloudController(BaseController):
|
|
4193
4193
|
"""
|
4194
4194
|
command_parts = [
|
4195
4195
|
"helm upgrade <release-name> anyscale/anyscale-operator",
|
4196
|
-
f" --set-string cloudDeploymentId={cloud_deployment_id}",
|
4197
|
-
f" --set-string cloudProvider={provider}",
|
4196
|
+
f" --set-string global.cloudDeploymentId={cloud_deployment_id}",
|
4197
|
+
f" --set-string global.cloudProvider={provider}",
|
4198
4198
|
]
|
4199
4199
|
|
4200
4200
|
# Add region for most providers (not for generic)
|
4201
4201
|
if region and provider != "generic":
|
4202
|
-
command_parts.append(f" --set-string region={region}")
|
4202
|
+
command_parts.append(f" --set-string global.region={region}")
|
4203
4203
|
|
4204
4204
|
# Add provider-specific parameters
|
4205
4205
|
if provider == "gcp" and operator_iam_identity:
|
4206
4206
|
command_parts.append(
|
4207
|
-
f" --set-string
|
4207
|
+
f" --set-string global.auth.iamIdentity={operator_iam_identity}"
|
4208
4208
|
)
|
4209
4209
|
elif provider == "azure":
|
4210
4210
|
if operator_iam_identity:
|
4211
4211
|
command_parts.append(
|
4212
|
-
f" --set-string
|
4212
|
+
f" --set-string global.auth.iamIdentity={operator_iam_identity}"
|
4213
4213
|
)
|
4214
4214
|
if anyscale_cli_token:
|
4215
4215
|
command_parts.append(
|
4216
|
-
f" --set-string anyscaleCliToken={anyscale_cli_token}"
|
4216
|
+
f" --set-string global.auth.anyscaleCliToken={anyscale_cli_token}"
|
4217
4217
|
)
|
4218
4218
|
else:
|
4219
4219
|
command_parts.append(
|
4220
|
-
" --set-string anyscaleCliToken=$ANYSCALE_CLI_TOKEN"
|
4220
|
+
" --set-string global.auth.anyscaleCliToken=$ANYSCALE_CLI_TOKEN"
|
4221
4221
|
)
|
4222
4222
|
elif provider == "generic":
|
4223
4223
|
if anyscale_cli_token:
|
4224
4224
|
command_parts.append(
|
4225
|
-
f" --set-string anyscaleCliToken={anyscale_cli_token}"
|
4225
|
+
f" --set-string global.auth.anyscaleCliToken={anyscale_cli_token}"
|
4226
4226
|
)
|
4227
4227
|
else:
|
4228
4228
|
command_parts.append(
|
4229
|
-
" --set-string anyscaleCliToken=$ANYSCALE_CLI_TOKEN"
|
4229
|
+
" --set-string global.auth.anyscaleCliToken=$ANYSCALE_CLI_TOKEN"
|
4230
4230
|
)
|
4231
4231
|
|
4232
4232
|
# Add common parameters
|
4233
4233
|
command_parts.extend(
|
4234
4234
|
[
|
4235
|
-
" --set-string
|
4235
|
+
" --set-string workloads.serviceAccount.name=anyscale-operator",
|
4236
4236
|
" --namespace <namespace>",
|
4237
4237
|
" --create-namespace",
|
4238
|
+
" --wait",
|
4238
4239
|
" -i",
|
4239
4240
|
]
|
4240
4241
|
)
|
@@ -789,20 +789,59 @@ class OperatorVerifier:
|
|
789
789
|
|
790
790
|
def _fetch_config_data(self, local_port: int) -> OperatorConfigData:
|
791
791
|
"""Fetch config data from operator."""
|
792
|
-
|
793
|
-
|
794
|
-
timeout=HTTP_REQUEST_TIMEOUT,
|
795
|
-
)
|
792
|
+
max_retries = 6
|
793
|
+
retry_delay = 5
|
796
794
|
|
797
|
-
|
798
|
-
|
795
|
+
for attempt in range(max_retries):
|
796
|
+
response = requests.get(
|
797
|
+
f"http://localhost:{local_port}{OPERATOR_CONFIG_ENDPOINT}",
|
798
|
+
timeout=HTTP_REQUEST_TIMEOUT,
|
799
|
+
)
|
799
800
|
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
801
|
+
config_data = None
|
802
|
+
config_error = None
|
803
|
+
|
804
|
+
if response.status_code == 200:
|
805
|
+
try:
|
806
|
+
config_data = response.json()
|
807
|
+
|
808
|
+
# Check if iamIdentity is present in the config
|
809
|
+
# This is necessary because the operator may not have the iamIdentity field
|
810
|
+
# immediately available after startup while it is being bootstrapped (connecting to KCP, storing registry secrets, etc.)
|
811
|
+
if config_data and "iamIdentity" in config_data:
|
812
|
+
return OperatorConfigData(
|
813
|
+
status_code=response.status_code,
|
814
|
+
response_text=response.text,
|
815
|
+
config_data=config_data,
|
816
|
+
config_error=config_error,
|
817
|
+
)
|
805
818
|
|
819
|
+
# If iamIdentity is missing and we have retries left, wait and retry
|
820
|
+
if attempt < max_retries - 1:
|
821
|
+
self.log.info(
|
822
|
+
f"Operator config endpoint returned 200 but iamIdentity not found. "
|
823
|
+
f"Retrying in {retry_delay} seconds... (attempt {attempt + 1}/{max_retries})"
|
824
|
+
)
|
825
|
+
time.sleep(retry_delay)
|
826
|
+
continue
|
827
|
+
else:
|
828
|
+
# Last attempt and still no iamIdentity, return what we have
|
829
|
+
self.log.warning(
|
830
|
+
f"iamIdentity not found after {max_retries} attempts"
|
831
|
+
)
|
832
|
+
|
833
|
+
except json.JSONDecodeError as e:
|
834
|
+
config_error = str(e)
|
835
|
+
else:
|
836
|
+
# Non-200 status code, return immediately without retrying
|
837
|
+
return OperatorConfigData(
|
838
|
+
status_code=response.status_code,
|
839
|
+
response_text=response.text,
|
840
|
+
config_data=config_data,
|
841
|
+
config_error=config_error,
|
842
|
+
)
|
843
|
+
|
844
|
+
# Return the last response (this will have config_data but no iamIdentity)
|
806
845
|
return OperatorConfigData(
|
807
846
|
status_code=response.status_code,
|
808
847
|
response_text=response.text,
|
@@ -1375,6 +1414,13 @@ class KubernetesCloudDeploymentVerifier:
|
|
1375
1414
|
):
|
1376
1415
|
cloud_deployment.file_storage = FileStorage(**cloud_deployment.file_storage)
|
1377
1416
|
|
1417
|
+
if cloud_deployment.kubernetes_config is not None and isinstance(
|
1418
|
+
cloud_deployment.kubernetes_config, dict
|
1419
|
+
):
|
1420
|
+
cloud_deployment.kubernetes_config = OpenAPIKubernetesConfig(
|
1421
|
+
**cloud_deployment.kubernetes_config
|
1422
|
+
)
|
1423
|
+
|
1378
1424
|
try:
|
1379
1425
|
return self._run_verification_steps(cloud_deployment)
|
1380
1426
|
|
@@ -1519,6 +1565,14 @@ class KubernetesCloudDeploymentVerifier:
|
|
1519
1565
|
|
1520
1566
|
def _get_kubectl_config(self):
|
1521
1567
|
"""Get kubectl context and operator namespace from user"""
|
1568
|
+
# If k8s_config is already set, skip prompting
|
1569
|
+
if self.k8s_config is not None:
|
1570
|
+
self.log.info(
|
1571
|
+
f"Using configured context='{self.k8s_config.context}', "
|
1572
|
+
f"namespace='{self.k8s_config.operator_namespace}'"
|
1573
|
+
)
|
1574
|
+
return
|
1575
|
+
|
1522
1576
|
# Check if kubectl is available
|
1523
1577
|
temp_kubectl = KubectlOperations("", self.log)
|
1524
1578
|
if not temp_kubectl.check_kubectl_available():
|
@@ -0,0 +1,364 @@
|
|
1
|
+
"""
|
2
|
+
CloudFormation utility functions for creating and managing stacks.
|
3
|
+
|
4
|
+
This module provides reusable utilities for CloudFormation stack operations
|
5
|
+
including creation, waiting, and output retrieval.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import time
|
9
|
+
from typing import Any, Dict, Optional
|
10
|
+
|
11
|
+
import boto3
|
12
|
+
from botocore.exceptions import ClientError
|
13
|
+
from click import ClickException
|
14
|
+
|
15
|
+
from anyscale.cli_logger import BlockLogger
|
16
|
+
|
17
|
+
|
18
|
+
class CloudFormationUtils:
|
19
|
+
"""Utility class for CloudFormation operations."""
|
20
|
+
|
21
|
+
def __init__(self, logger: Optional[BlockLogger] = None):
|
22
|
+
self.log = logger or BlockLogger()
|
23
|
+
|
24
|
+
def create_and_wait_for_stack( # noqa: PLR0913
|
25
|
+
self,
|
26
|
+
stack_name: str,
|
27
|
+
template_body: str,
|
28
|
+
parameters: list,
|
29
|
+
region: str,
|
30
|
+
capabilities: Optional[list] = None,
|
31
|
+
timeout_seconds: int = 600,
|
32
|
+
boto3_session: Optional[boto3.Session] = None,
|
33
|
+
update_if_exists: bool = False,
|
34
|
+
) -> Dict[str, Any]:
|
35
|
+
"""
|
36
|
+
Create a CloudFormation stack and wait for it to complete.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
stack_name: Name of the CloudFormation stack
|
40
|
+
template_body: CloudFormation template body
|
41
|
+
parameters: Stack parameters
|
42
|
+
region: AWS region
|
43
|
+
capabilities: Stack capabilities (default: CAPABILITY_NAMED_IAM, CAPABILITY_AUTO_EXPAND)
|
44
|
+
timeout_seconds: Timeout in seconds (default: 600)
|
45
|
+
boto3_session: Optional boto3 session (creates new if not provided)
|
46
|
+
update_if_exists: Whether to update stack if it already exists (default: False)
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
CloudFormation stack information
|
50
|
+
|
51
|
+
Raises:
|
52
|
+
ClickException: If stack creation fails or times out
|
53
|
+
"""
|
54
|
+
if boto3_session is None:
|
55
|
+
boto3_session = boto3.Session(region_name=region)
|
56
|
+
|
57
|
+
cfn_client = boto3_session.client("cloudformation", region_name=region)
|
58
|
+
|
59
|
+
if capabilities is None:
|
60
|
+
capabilities = ["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
|
61
|
+
|
62
|
+
# Create the stack
|
63
|
+
self.log.info(f"Creating CloudFormation stack: {stack_name}")
|
64
|
+
try:
|
65
|
+
cfn_client.create_stack(
|
66
|
+
StackName=stack_name,
|
67
|
+
TemplateBody=template_body,
|
68
|
+
Parameters=parameters,
|
69
|
+
Capabilities=capabilities,
|
70
|
+
)
|
71
|
+
except ClientError as e:
|
72
|
+
error_code = e.response.get("Error", {}).get("Code", "")
|
73
|
+
if error_code == "AlreadyExistsException" and update_if_exists:
|
74
|
+
self.log.warning(f"Stack {stack_name} already exists, updating...")
|
75
|
+
try:
|
76
|
+
cfn_client.update_stack(
|
77
|
+
StackName=stack_name,
|
78
|
+
TemplateBody=template_body,
|
79
|
+
Parameters=parameters,
|
80
|
+
Capabilities=capabilities,
|
81
|
+
)
|
82
|
+
except ClientError as update_error:
|
83
|
+
raise ClickException(
|
84
|
+
f"Failed to update existing stack: {update_error}"
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
raise ClickException(f"Failed to create CloudFormation stack: {e}")
|
88
|
+
|
89
|
+
# Wait for stack completion
|
90
|
+
return self._wait_for_stack_completion(
|
91
|
+
cfn_client, stack_name, region, timeout_seconds
|
92
|
+
)
|
93
|
+
|
94
|
+
def _wait_for_stack_completion(
|
95
|
+
self, cfn_client, stack_name: str, region: str, timeout_seconds: int
|
96
|
+
) -> Dict[str, Any]:
|
97
|
+
"""Wait for CloudFormation stack to complete with timeout."""
|
98
|
+
stack_url = f"https://{region}.console.aws.amazon.com/cloudformation/home?region={region}#/stacks/stackinfo?stackId={stack_name}"
|
99
|
+
self.log.info(f"Track progress at: {stack_url}")
|
100
|
+
|
101
|
+
with self.log.spinner("Creating cloud resources through CloudFormation..."):
|
102
|
+
start_time = time.time()
|
103
|
+
end_time = start_time + timeout_seconds
|
104
|
+
|
105
|
+
while time.time() < end_time:
|
106
|
+
try:
|
107
|
+
stacks = cfn_client.describe_stacks(StackName=stack_name)
|
108
|
+
cfn_stack = stacks["Stacks"][0]
|
109
|
+
stack_status = cfn_stack["StackStatus"]
|
110
|
+
|
111
|
+
if stack_status in (
|
112
|
+
"CREATE_FAILED",
|
113
|
+
"ROLLBACK_COMPLETE",
|
114
|
+
"ROLLBACK_IN_PROGRESS",
|
115
|
+
"UPDATE_FAILED",
|
116
|
+
):
|
117
|
+
# Clean up resources if stack failed
|
118
|
+
self._cleanup_failed_stack(cfn_client, stack_name, region)
|
119
|
+
|
120
|
+
# Get error details
|
121
|
+
error_details = self._get_stack_error_details(
|
122
|
+
cfn_client, stack_name
|
123
|
+
)
|
124
|
+
raise ClickException(
|
125
|
+
f"CloudFormation stack failed: {stack_status}. "
|
126
|
+
f"Error details: {error_details}. "
|
127
|
+
f"Check the stack at: {stack_url}"
|
128
|
+
)
|
129
|
+
|
130
|
+
if stack_status in ("CREATE_COMPLETE", "UPDATE_COMPLETE"):
|
131
|
+
self.log.info(
|
132
|
+
f"CloudFormation stack {stack_name} completed successfully"
|
133
|
+
)
|
134
|
+
return cfn_stack
|
135
|
+
|
136
|
+
# Still in progress
|
137
|
+
time.sleep(5)
|
138
|
+
|
139
|
+
except ClientError as e:
|
140
|
+
if "does not exist" in str(e):
|
141
|
+
raise ClickException(
|
142
|
+
f"CloudFormation stack {stack_name} not found"
|
143
|
+
)
|
144
|
+
raise ClickException(f"Error checking CloudFormation stack: {e}")
|
145
|
+
|
146
|
+
# Timeout
|
147
|
+
raise ClickException(
|
148
|
+
f"CloudFormation stack {stack_name} timed out after {timeout_seconds} seconds. "
|
149
|
+
f"Check the stack at: {stack_url}"
|
150
|
+
)
|
151
|
+
|
152
|
+
def _cleanup_failed_stack(self, cfn_client, stack_name: str, region: str) -> None:
|
153
|
+
"""Clean up resources from a failed CloudFormation stack."""
|
154
|
+
try:
|
155
|
+
# Try to get stack outputs to find resources to clean up
|
156
|
+
stacks = cfn_client.describe_stacks(StackName=stack_name)
|
157
|
+
stack = stacks["Stacks"][0]
|
158
|
+
|
159
|
+
# Look for S3 bucket in outputs
|
160
|
+
for output in stack.get("Outputs", []):
|
161
|
+
if "Bucket" in output.get("OutputKey", ""):
|
162
|
+
bucket_name = output.get("OutputValue")
|
163
|
+
if bucket_name:
|
164
|
+
self._cleanup_s3_bucket(bucket_name, region)
|
165
|
+
|
166
|
+
except Exception as e: # noqa: BLE001
|
167
|
+
self.log.warning(f"Failed to cleanup resources from failed stack: {e}")
|
168
|
+
|
169
|
+
def _cleanup_s3_bucket(self, bucket_name: str, region: str) -> None:
|
170
|
+
"""Clean up S3 bucket created by failed stack."""
|
171
|
+
try:
|
172
|
+
s3_client = boto3.client("s3", region_name=region)
|
173
|
+
s3_client.delete_bucket(Bucket=bucket_name)
|
174
|
+
self.log.info(f"Successfully deleted S3 bucket: {bucket_name}")
|
175
|
+
except ClientError as e:
|
176
|
+
if e.response["Error"]["Code"] != "NoSuchBucket":
|
177
|
+
self.log.error(f"Failed to delete S3 bucket {bucket_name}: {e}")
|
178
|
+
except Exception as e: # noqa: BLE001
|
179
|
+
self.log.error(f"Failed to delete S3 bucket {bucket_name}: {e}")
|
180
|
+
|
181
|
+
def _get_stack_error_details(self, cfn_client, stack_name: str) -> str:
|
182
|
+
"""Get detailed error information from CloudFormation stack events."""
|
183
|
+
try:
|
184
|
+
events = cfn_client.describe_stack_events(StackName=stack_name)
|
185
|
+
error_events = [
|
186
|
+
event
|
187
|
+
for event in events.get("StackEvents", [])
|
188
|
+
if event.get("ResourceStatus", "").endswith("_FAILED")
|
189
|
+
]
|
190
|
+
|
191
|
+
if error_events:
|
192
|
+
latest_error = error_events[0]
|
193
|
+
return (
|
194
|
+
f"Resource: {latest_error.get('LogicalResourceId', 'Unknown')}, "
|
195
|
+
f"Status: {latest_error.get('ResourceStatus', 'Unknown')}, "
|
196
|
+
f"Reason: {latest_error.get('ResourceStatusReason', 'Unknown')}"
|
197
|
+
)
|
198
|
+
else:
|
199
|
+
return "No detailed error information available"
|
200
|
+
|
201
|
+
except Exception as e: # noqa: BLE001
|
202
|
+
return f"Failed to get error details: {e}"
|
203
|
+
|
204
|
+
def get_stack_outputs(
|
205
|
+
self,
|
206
|
+
stack_name: str,
|
207
|
+
region: str,
|
208
|
+
boto3_session: Optional[boto3.Session] = None,
|
209
|
+
) -> Dict[str, str]:
|
210
|
+
"""
|
211
|
+
Get all outputs from a CloudFormation stack.
|
212
|
+
|
213
|
+
Args:
|
214
|
+
stack_name: Name of the CloudFormation stack
|
215
|
+
region: AWS region
|
216
|
+
boto3_session: Optional boto3 session (creates new if not provided)
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
Dictionary of output key-value pairs
|
220
|
+
|
221
|
+
Raises:
|
222
|
+
ClickException: If stack not found or error retrieving outputs
|
223
|
+
"""
|
224
|
+
if boto3_session is None:
|
225
|
+
boto3_session = boto3.Session(region_name=region)
|
226
|
+
|
227
|
+
cfn_client = boto3_session.client("cloudformation", region_name=region)
|
228
|
+
|
229
|
+
try:
|
230
|
+
stacks = cfn_client.describe_stacks(StackName=stack_name)
|
231
|
+
stack = stacks["Stacks"][0]
|
232
|
+
outputs = {}
|
233
|
+
|
234
|
+
for output in stack.get("Outputs", []):
|
235
|
+
outputs[output["OutputKey"]] = output["OutputValue"]
|
236
|
+
|
237
|
+
return outputs
|
238
|
+
except ClientError as e:
|
239
|
+
if "does not exist" in str(e):
|
240
|
+
raise ClickException(f"CloudFormation stack {stack_name} not found")
|
241
|
+
raise ClickException(f"Failed to get CloudFormation outputs: {e}")
|
242
|
+
|
243
|
+
def get_stack_output(
|
244
|
+
self,
|
245
|
+
stack_name: str,
|
246
|
+
output_key: str,
|
247
|
+
region: str,
|
248
|
+
boto3_session: Optional[boto3.Session] = None,
|
249
|
+
) -> str:
|
250
|
+
"""
|
251
|
+
Get a specific output value from a CloudFormation stack.
|
252
|
+
|
253
|
+
Args:
|
254
|
+
stack_name: Name of the CloudFormation stack
|
255
|
+
output_key: Key of the output to retrieve
|
256
|
+
region: AWS region
|
257
|
+
boto3_session: Optional boto3 session (creates new if not provided)
|
258
|
+
|
259
|
+
Returns:
|
260
|
+
Output value
|
261
|
+
|
262
|
+
Raises:
|
263
|
+
ClickException: If stack not found or output key not found
|
264
|
+
"""
|
265
|
+
outputs = self.get_stack_outputs(stack_name, region, boto3_session)
|
266
|
+
|
267
|
+
if output_key not in outputs:
|
268
|
+
available_keys = list(outputs.keys())
|
269
|
+
raise ClickException(
|
270
|
+
f"Output key '{output_key}' not found in stack {stack_name}. "
|
271
|
+
f"Available keys: {available_keys}"
|
272
|
+
)
|
273
|
+
|
274
|
+
return outputs[output_key]
|
275
|
+
|
276
|
+
def delete_stack(
|
277
|
+
self,
|
278
|
+
stack_name: str,
|
279
|
+
region: str,
|
280
|
+
boto3_session: Optional[boto3.Session] = None,
|
281
|
+
) -> None:
|
282
|
+
"""
|
283
|
+
Delete a CloudFormation stack.
|
284
|
+
|
285
|
+
Args:
|
286
|
+
stack_name: Name of the CloudFormation stack
|
287
|
+
region: AWS region
|
288
|
+
boto3_session: Optional boto3 session (creates new if not provided)
|
289
|
+
|
290
|
+
Raises:
|
291
|
+
ClickException: If stack deletion fails
|
292
|
+
"""
|
293
|
+
if boto3_session is None:
|
294
|
+
boto3_session = boto3.Session(region_name=region)
|
295
|
+
|
296
|
+
cfn_client = boto3_session.client("cloudformation", region_name=region)
|
297
|
+
|
298
|
+
try:
|
299
|
+
self.log.info(f"Deleting CloudFormation stack: {stack_name}")
|
300
|
+
cfn_client.delete_stack(StackName=stack_name)
|
301
|
+
|
302
|
+
# Wait for deletion to complete
|
303
|
+
with self.log.spinner("Deleting CloudFormation stack..."):
|
304
|
+
start_time = time.time()
|
305
|
+
timeout_seconds = 300 # 5 minutes for deletion
|
306
|
+
end_time = start_time + timeout_seconds
|
307
|
+
|
308
|
+
while time.time() < end_time:
|
309
|
+
try:
|
310
|
+
cfn_client.describe_stacks(StackName=stack_name)
|
311
|
+
time.sleep(5) # Still exists, wait
|
312
|
+
except ClientError as e:
|
313
|
+
if "does not exist" in str(e):
|
314
|
+
self.log.info(
|
315
|
+
f"CloudFormation stack {stack_name} deleted successfully"
|
316
|
+
)
|
317
|
+
return
|
318
|
+
raise ClickException(f"Error checking stack deletion: {e}")
|
319
|
+
|
320
|
+
raise ClickException(
|
321
|
+
f"Stack deletion timed out after {timeout_seconds} seconds"
|
322
|
+
)
|
323
|
+
|
324
|
+
except ClientError as e:
|
325
|
+
if "does not exist" in str(e):
|
326
|
+
self.log.info(f"Stack {stack_name} does not exist")
|
327
|
+
return
|
328
|
+
raise ClickException(f"Failed to delete CloudFormation stack: {e}")
|
329
|
+
|
330
|
+
|
331
|
+
# Convenience functions for backward compatibility
|
332
|
+
def create_cloudformation_stack(
|
333
|
+
stack_name: str,
|
334
|
+
template_body: str,
|
335
|
+
parameters: list,
|
336
|
+
region: str,
|
337
|
+
logger: Optional[BlockLogger] = None,
|
338
|
+
**kwargs,
|
339
|
+
) -> Dict[str, Any]:
|
340
|
+
"""Convenience function to create a CloudFormation stack."""
|
341
|
+
utils = CloudFormationUtils(logger)
|
342
|
+
return utils.create_and_wait_for_stack(
|
343
|
+
stack_name, template_body, parameters, region, **kwargs
|
344
|
+
)
|
345
|
+
|
346
|
+
|
347
|
+
def get_cloudformation_outputs(
|
348
|
+
stack_name: str, region: str, logger: Optional[BlockLogger] = None, **kwargs
|
349
|
+
) -> Dict[str, str]:
|
350
|
+
"""Convenience function to get CloudFormation stack outputs."""
|
351
|
+
utils = CloudFormationUtils(logger)
|
352
|
+
return utils.get_stack_outputs(stack_name, region, **kwargs)
|
353
|
+
|
354
|
+
|
355
|
+
def get_cloudformation_output(
|
356
|
+
stack_name: str,
|
357
|
+
output_key: str,
|
358
|
+
region: str,
|
359
|
+
logger: Optional[BlockLogger] = None,
|
360
|
+
**kwargs,
|
361
|
+
) -> str:
|
362
|
+
"""Convenience function to get a specific CloudFormation stack output."""
|
363
|
+
utils = CloudFormationUtils(logger)
|
364
|
+
return utils.get_stack_output(stack_name, output_key, region, **kwargs)
|
anyscale/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.26.
|
1
|
+
__version__ = "0.26.68"
|
@@ -28,7 +28,7 @@ anyscale/snapshot.py,sha256=UGJT5C1s_4xmQxjWODK5DFpGxHRBX5jOCdSCqXESH8E,1685
|
|
28
28
|
anyscale/tables.py,sha256=TV4F2uLnwehvbkAfaP7iuLlT2wLIo6ORH2LVdRGXW5g,2840
|
29
29
|
anyscale/telemetry.py,sha256=U90C2Vgx48z9PMTI6EbzHFbP3jWnDUutbIfMPBb8-SI,14711
|
30
30
|
anyscale/util.py,sha256=3jfGH2CyDmXrSg-3owSF7F8FnqKbLJ-aHUbepyCKuqQ,42903
|
31
|
-
anyscale/version.py,sha256=
|
31
|
+
anyscale/version.py,sha256=lpQs-Szxlm150B9mnwXj1al8clhb5Zo-ZL5AiFSuzk8,24
|
32
32
|
anyscale/workspace_utils.py,sha256=OViE88CnIF5ruVxd3kazQ0Mf2BxqtMq6wx-XQ5A2cp8,1204
|
33
33
|
anyscale/_private/anyscale_client/README.md,sha256=kSfI2Jfw5RHZWYtu0di3XtdSCx0d2pSwKMfjmDvw7Tg,3770
|
34
34
|
anyscale/_private/anyscale_client/__init__.py,sha256=807Blx3RHQeS8BmKZcsOQQ4dYoKlCnpm6Bdsif2CrHg,337
|
@@ -106,7 +106,7 @@ anyscale/background/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
106
106
|
anyscale/background/job_runner.py,sha256=LTuv9JOahyv6C9i7DLQAONgQF6--FfYZEmJrKy-sUG8,2687
|
107
107
|
anyscale/client/.gitignore,sha256=JZyvYEtT2DCSK9V5Joi6lQofhMik4PXiJRCWsg7SvqI,807
|
108
108
|
anyscale/client/.openapi-generator-ignore,sha256=pu2PTide7pJtJ-DFLzDy0cTYQJRlrB-8RRH3zGLeUds,1040
|
109
|
-
anyscale/client/README.md,sha256=
|
109
|
+
anyscale/client/README.md,sha256=yVThjY7_FVCDVT5NRRJ-05L9BheiL0Kr01CVnSie_6I,131991
|
110
110
|
anyscale/client/git_push.sh,sha256=EDCZOTTiLxbtPHmiU63qC99rGH67B7dhdPZdNUKivF0,1827
|
111
111
|
anyscale/client/requirements.txt,sha256=dkVKYUStC5h_g_87SH7pRdhXCj7ySozAJMGAFEzGgFc,126
|
112
112
|
anyscale/client/setup.cfg,sha256=l7bdKSIedeBhhoDtupsBwx1xPrlBf2yYeTH7a8kMga4,28
|
@@ -114,14 +114,14 @@ anyscale/client/setup.py,sha256=tSxqw1kAL1B9adnrnOarjnQfSbwGmnTr_kg8ZXhlm5A,1109
|
|
114
114
|
anyscale/client/test-requirements.txt,sha256=sTjmDTj5W9fh1ZAeo8UT2EBdeGDBNttj_PHiPBXg1D4,111
|
115
115
|
anyscale/client/tox.ini,sha256=M6L3UmvAdvU65LsoAF-Oi7oRjwZlCJZn8I7ofdXn5Ok,156
|
116
116
|
anyscale/client/.openapi-generator/VERSION,sha256=J0RzX-4u4jfin1kviKtmncjUePyjHm2kyvmkobOrt_E,5
|
117
|
-
anyscale/client/openapi_client/__init__.py,sha256=
|
117
|
+
anyscale/client/openapi_client/__init__.py,sha256=mOXEUcUD_s5VokuYaTXHOhFQKSvsFWVZ7kxmmJp5SMo,59881
|
118
118
|
anyscale/client/openapi_client/api_client.py,sha256=d8Un6j2Ny2vlS2qBXPVFj6_ql0k36DFahpWt_28TfCk,25563
|
119
119
|
anyscale/client/openapi_client/configuration.py,sha256=Dd5XrlHwv-wxnf0C35PG_-HBQoY3Yaz6hKrmkZz-m0E,12363
|
120
120
|
anyscale/client/openapi_client/exceptions.py,sha256=3egwsXQG2j_vARbqgBxUO1xSltAhpfiHTYVP7VXTvU0,3792
|
121
121
|
anyscale/client/openapi_client/rest.py,sha256=Ehj37v7GHW6SXV067Hze5HE42ayKaGi6a6ZlkR7u3Lg,12501
|
122
122
|
anyscale/client/openapi_client/api/__init__.py,sha256=i8u7BI2xX1GrXTL3hN0pKpYIlnT-D_uDxH2ElOfYG1I,141
|
123
|
-
anyscale/client/openapi_client/api/default_api.py,sha256=
|
124
|
-
anyscale/client/openapi_client/models/__init__.py,sha256=
|
123
|
+
anyscale/client/openapi_client/api/default_api.py,sha256=JGRjn6bzNkl2ysAD-cNlfloEioWDI3KXzZdROJjUU_U,2151488
|
124
|
+
anyscale/client/openapi_client/models/__init__.py,sha256=M51ftYXV7OZ-7DiL_hWnQXKH18zDvIjwYA4vgB0YFx4,59391
|
125
125
|
anyscale/client/openapi_client/models/access_config.py,sha256=b2mA0qtuTA5PFbp6C61Jc_T2zUMaojM1v32IhZo0MfY,3648
|
126
126
|
anyscale/client/openapi_client/models/actor_status.py,sha256=6xyX_aIqURj2raBdY9DmBxsdDACFrqqYvElGiM6YG2E,2813
|
127
127
|
anyscale/client/openapi_client/models/admin_create_user.py,sha256=9DPr8D0lKgoEZ3Z2kGsAd8L7ocFCiP6woOGLVs8SRb8,7251
|
@@ -481,6 +481,21 @@ anyscale/client/openapi_client/models/kubernetes_manager_registration_response.p
|
|
481
481
|
anyscale/client/openapi_client/models/kubernetesmanagerregistrationresponse_response.py,sha256=8UH867yGDAOgZrfXdkRzVkfTLViyTRQIZFBFd0vghu0,3825
|
482
482
|
anyscale/client/openapi_client/models/lb_resource.py,sha256=UAjfCjXZAvAn64WSltl-VWw1WyjMrCi73A1jtY2F4To,3739
|
483
483
|
anyscale/client/openapi_client/models/lbresource_response.py,sha256=AgJpUmMDDuF6ohHI2LbPoXICUKeBKWskgjaOcr59t8E,3528
|
484
|
+
anyscale/client/openapi_client/models/lineage_artifact.py,sha256=1ACZuH2FaYMW3XFGAX-IyupnvwngguaTeRfA3hTeoSE,12118
|
485
|
+
anyscale/client/openapi_client/models/lineage_artifact_sort_field.py,sha256=pOicxc4zT9fjSKG58AAiacJzo335d5y2SUdShrSm8dI,2922
|
486
|
+
anyscale/client/openapi_client/models/lineage_artifact_type.py,sha256=rZV5nndrPi0P75fpsg6ZnGRDUxwk0OFWNef22840Kl0,2854
|
487
|
+
anyscale/client/openapi_client/models/lineage_direction.py,sha256=tvIjSwnt4MSxuczUBI_KlI7gHiMCB76skHSdx_N8oIc,2848
|
488
|
+
anyscale/client/openapi_client/models/lineage_graph.py,sha256=yn34T-j0-PWdvIMCTemoOLyGZ5UQgu2HYnimmODd_Ys,5216
|
489
|
+
anyscale/client/openapi_client/models/lineage_graph_node.py,sha256=KWGpK42UCjEhe7bhR79CIJmuZdRL3NP4pwC0ig7Fs6E,13946
|
490
|
+
anyscale/client/openapi_client/models/lineage_node_type.py,sha256=bi8gje1x01FtL0l1PzN76aQbBCeOkLWk_e2YXd3jdwY,2850
|
491
|
+
anyscale/client/openapi_client/models/lineage_workload.py,sha256=F_iwZlFliVRPROmTwQZ_kwrzJ4kevnXfTY6q40ro3dk,11115
|
492
|
+
anyscale/client/openapi_client/models/lineage_workload_sort_field.py,sha256=u5seM0kQtRVrEAhbQ38MlMvGYbIBTwUB52Yc7pyr7n0,2922
|
493
|
+
anyscale/client/openapi_client/models/lineage_workload_type.py,sha256=kjZlfsIo6rRarGpnahuZEX1zM-qyqe3drQb1QO3xkZI,2914
|
494
|
+
anyscale/client/openapi_client/models/lineageartifact_list_response.py,sha256=t4QnqXbEkoADCzpm4q23EP9VPQueTLWKmDUJ6msKZZE,4422
|
495
|
+
anyscale/client/openapi_client/models/lineageartifact_response.py,sha256=VEkgCnS8A65Ia6opIQLeAVYPakrI5M0tNVRq-VZ8BQc,3583
|
496
|
+
anyscale/client/openapi_client/models/lineagegraph_response.py,sha256=YwM0XPkLdrWL5hTKYT1FzMTPvOJDJHAQaWqHmkXmHzk,3550
|
497
|
+
anyscale/client/openapi_client/models/lineageworkload_list_response.py,sha256=q6aoNXijJ7oggjGym0YkWtm_L55FCCUp4M86PbZLOGI,4422
|
498
|
+
anyscale/client/openapi_client/models/lineageworkload_response.py,sha256=ORICo_b71QEmKnT4EapJ02unA0AFf8y0_0JjNG9ROOA,3583
|
484
499
|
anyscale/client/openapi_client/models/list_databricks_connections.py,sha256=XRCO-FYXXWEcQ7qm1gL4uu7XP_1HSiwAx-AoNB4AtJA,3749
|
485
500
|
anyscale/client/openapi_client/models/list_machine_pools_response.py,sha256=lH_AGSraAalhusdwbb31uSCNBobNIdTA8mTVPkA_X7Y,3842
|
486
501
|
anyscale/client/openapi_client/models/list_machines_response.py,sha256=8aS1GNXSzf_j9MaKoRjWxJ-7xQoe1_4wS5oX3qUZEi0,3607
|
@@ -834,7 +849,7 @@ anyscale/cloud/_private/cloud_sdk.py,sha256=5TBGyGSjMI4jLOnSle1WWC6za0psP9xgTGWU
|
|
834
849
|
anyscale/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
835
850
|
anyscale/commands/aggregated_instance_usage_commands.py,sha256=TRP1X3hdIWbKg9V20VtazlDXsYAeV--M0DH3-Z5tnj4,2293
|
836
851
|
anyscale/commands/auth_commands.py,sha256=X1g6Yu9kqgPb4HLODlZTYEk8G5AVLeyizPIgagWx-p0,1026
|
837
|
-
anyscale/commands/cloud_commands.py,sha256=
|
852
|
+
anyscale/commands/cloud_commands.py,sha256=NdB4O_Nee-ute0lXWs7xIotdoVuoyHUvBbYXueODFGk,60185
|
838
853
|
anyscale/commands/cluster_commands.py,sha256=taNcffyFfqJ1MgOQd0cz9kzRXWFTdp-wfLPM4l_2tBc,13487
|
839
854
|
anyscale/commands/cluster_env_commands.py,sha256=KNWylyE8Ew1sDi7yu2Tp4RLcRu2_KJJJIzVGRyPflJo,3899
|
840
855
|
anyscale/commands/command_examples.py,sha256=pSj1DyHwlzmSxw-Ko5RUTQ57uFA7v7Jgur0s7K55bts,25182
|
@@ -859,6 +874,7 @@ anyscale/commands/schedule_commands.py,sha256=Bw2aKp_w6xcuRSVVi9FLdUjRVCr8_v4Tt2
|
|
859
874
|
anyscale/commands/service_account_commands.py,sha256=JlppTmcaDofJn7TYqd3PW4hDGykdEN56fr0PT3qgBIg,3271
|
860
875
|
anyscale/commands/service_commands.py,sha256=DBrPkW7JH3kCsr038aQStm7AF1u-n80sLjwc2Hjifks,34014
|
861
876
|
anyscale/commands/session_commands_hidden.py,sha256=APEypnUB1yV2Rr6wdSFWy1vQbAnn-lOn0rU2enF5JdM,6200
|
877
|
+
anyscale/commands/setup_k8s.py,sha256=MGiCtRrkMEDcQa0C7Sc_3YZKzwaABuAk82k3URGXz8Q,58248
|
862
878
|
anyscale/commands/user_commands.py,sha256=C-i1dGpdhboywN_2XgPS2BekKx2y6LZq8c8gvS0S-tY,1259
|
863
879
|
anyscale/commands/util.py,sha256=fIob29G1jZ4VU9PKVSJnT6hqX7wAqsS4L9sApsMZcNs,8585
|
864
880
|
anyscale/commands/workspace_commands.py,sha256=5JoF5xftkwkxHidp8xyj9POrlZE_WxwSKtZGnKCbDV4,18597
|
@@ -878,7 +894,7 @@ anyscale/connect_utils/start_interactive_session.py,sha256=m-RCH0e_bQBF6dAOskbP9
|
|
878
894
|
anyscale/controllers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
879
895
|
anyscale/controllers/auth_controller.py,sha256=hDY2sPvUP8pvh8PnlDYH5rCHjQes2v3b_KBVjMbrzeE,5127
|
880
896
|
anyscale/controllers/base_controller.py,sha256=1QFJoScFUV7YTzpKarhwPOc1SvI-xqX3TZmwxKonW6I,1998
|
881
|
-
anyscale/controllers/cloud_controller.py,sha256=
|
897
|
+
anyscale/controllers/cloud_controller.py,sha256=1mZIQ4OVIu0mQ4ibKVw8pJskiNzf0ZdDnc4FIGBS8IE,202079
|
882
898
|
anyscale/controllers/cloud_file_storage_utils.py,sha256=ifaqClEybTgxhqGWHYoH1vrlbxwjRuO-De_3666R2O4,6987
|
883
899
|
anyscale/controllers/cloud_functional_verification_controller.py,sha256=uro10r981CTlt2zlisUCmNYljbrYBiWzh7vrqrcur3I,33475
|
884
900
|
anyscale/controllers/cluster_controller.py,sha256=Sb5wVjrjpycg5iqmENAVtZ4iy9Kr6kM97_ck-KH85LM,28745
|
@@ -888,7 +904,7 @@ anyscale/controllers/config_controller.py,sha256=VsfdARHxo4tMLfeiYkTNOMGW3sIcNhV
|
|
888
904
|
anyscale/controllers/experimental_integrations_controller.py,sha256=_22_hAQCJIMg3E10s8xajoFF6Lf1HqVlAdAVt0Rh2DY,3889
|
889
905
|
anyscale/controllers/job_controller.py,sha256=sNQGzSLtp6e8PSbrmMrW_dp3pYytS8KCGcE-YjaNz5I,25425
|
890
906
|
anyscale/controllers/jobs_bg_controller.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
891
|
-
anyscale/controllers/kubernetes_verifier.py,sha256=
|
907
|
+
anyscale/controllers/kubernetes_verifier.py,sha256=0mGeE8i8ptWdGwVDOFFWuxMzqXpYNVZeDixbSUuKx2k,61300
|
892
908
|
anyscale/controllers/list_controller.py,sha256=oaOS6oo2TKPpXhGjs_laxuIVKinv3FwYfHt1CIzeTuU,11621
|
893
909
|
anyscale/controllers/logs_controller.py,sha256=x5GBUVdPYhbWRA3RfMQZJi3hBS2i35RkgzROfmY47h4,17647
|
894
910
|
anyscale/controllers/machine_controller.py,sha256=WauNi7Spzt2TFZrIN4PwULzgJZBVDFlytzFqpDQ106w,1188
|
@@ -1140,6 +1156,7 @@ anyscale/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1140
1156
|
anyscale/utils/cli_version_check_util.py,sha256=U2KU-NRf09pcs-ZZYUm1GQxs7Btp6N4Fv6CQeNoEeTM,2223
|
1141
1157
|
anyscale/utils/cloud_update_utils.py,sha256=c53AQyg4OJBkNqTD-8hHvEpdTwtins1rTycifBbQbWI,34106
|
1142
1158
|
anyscale/utils/cloud_utils.py,sha256=DtUkaIvKeX64Scia62fjnK61_gNMJr_H2e73C-ywF7k,12228
|
1159
|
+
anyscale/utils/cloudformation_utils.py,sha256=G4QJbnwve8ZQu7qzrT7ZVZ32zqf_bDxBDZBNZEtFkZg,13839
|
1143
1160
|
anyscale/utils/cluster_debug.py,sha256=P_-20L4Rt4LVAIGX-y9F61z5XYO5QbNtqf2MYJxuy_g,6391
|
1144
1161
|
anyscale/utils/connect_helpers.py,sha256=hlTKnaL27pABWPoW5nA5dpJW2ZG_2k7dXjnqtdzoBjo,5956
|
1145
1162
|
anyscale/utils/deprecation_util.py,sha256=2i1SNQIziOZXO24IuBZZUEi4ild1bqk0gfZWl2KgpXw,1160
|
@@ -1172,10 +1189,10 @@ anyscale/workspace/__init__.py,sha256=Innbm5ZhCyADEVBiYSo_vbpKwUNcMzVSAfxIGKOYe6
|
|
1172
1189
|
anyscale/workspace/commands.py,sha256=b1sqNseoPj-1VXznqQOLe0V_a663bOTvJX-TaOMJa1Y,14590
|
1173
1190
|
anyscale/workspace/models.py,sha256=uiMqoJRQNRgTcOIIsysSrtlHMtnI7paUWS34EN626Cg,10016
|
1174
1191
|
anyscale/workspace/_private/workspace_sdk.py,sha256=2CMeYfJt0UtIFCocDn1ukw1iI5esKHdopLe6duEs-qE,27599
|
1175
|
-
anyscale-0.26.
|
1176
|
-
anyscale-0.26.
|
1177
|
-
anyscale-0.26.
|
1178
|
-
anyscale-0.26.
|
1179
|
-
anyscale-0.26.
|
1180
|
-
anyscale-0.26.
|
1181
|
-
anyscale-0.26.
|
1192
|
+
anyscale-0.26.68.dist-info/licenses/LICENSE,sha256=UOPu974Wzsna6frFv1mu4VrZgNdZT7lbcNPzo5ue3qs,3494
|
1193
|
+
anyscale-0.26.68.dist-info/licenses/NOTICE,sha256=gHqDhSnUYlRXX-mDOL5FtE7774oiKyV_HO80qM3r9Xo,196
|
1194
|
+
anyscale-0.26.68.dist-info/METADATA,sha256=HkQPlzY48CjPRep1GyHX3OdjWnHzBKpNKXPFZQhxxN4,3231
|
1195
|
+
anyscale-0.26.68.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
1196
|
+
anyscale-0.26.68.dist-info/entry_points.txt,sha256=NqO18sCZn6zG6J0S38itjcN00s7aE3C3v3k5lMAfCLk,51
|
1197
|
+
anyscale-0.26.68.dist-info/top_level.txt,sha256=g3NVNS8Oh0NZwbFFgeX696C5MZZkS5dqV2NqcsbDRJE,9
|
1198
|
+
anyscale-0.26.68.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|