anyscale 0.24.86__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 (1131) hide show
  1. anyscale/__init__.py +181 -0
  2. anyscale/_private/anyscale_client/README.md +16 -0
  3. anyscale/_private/anyscale_client/__init__.py +8 -0
  4. anyscale/_private/anyscale_client/anyscale_client.py +1847 -0
  5. anyscale/_private/anyscale_client/common.py +593 -0
  6. anyscale/_private/anyscale_client/fake_anyscale_client.py +1080 -0
  7. anyscale/_private/docgen/README.md +15 -0
  8. anyscale/_private/docgen/__main__.py +700 -0
  9. anyscale/_private/docgen/api.md +1106 -0
  10. anyscale/_private/docgen/generator.py +559 -0
  11. anyscale/_private/docgen/generator_legacy.py +104 -0
  12. anyscale/_private/docgen/models.md +2261 -0
  13. anyscale/_private/models/__init__.py +2 -0
  14. anyscale/_private/models/image_uri.py +116 -0
  15. anyscale/_private/models/model_base.py +251 -0
  16. anyscale/_private/sdk/__init__.py +102 -0
  17. anyscale/_private/sdk/base_sdk.py +35 -0
  18. anyscale/_private/sdk/timer.py +46 -0
  19. anyscale/_private/utils/__init__.py +0 -0
  20. anyscale/_private/utils/progress_util.py +85 -0
  21. anyscale/_private/workload/__init__.py +2 -0
  22. anyscale/_private/workload/workload_config.py +195 -0
  23. anyscale/_private/workload/workload_sdk.py +324 -0
  24. anyscale/aggregated_instance_usage/__init__.py +36 -0
  25. anyscale/aggregated_instance_usage/_private/aggregated_instance_usage_sdk.py +30 -0
  26. anyscale/aggregated_instance_usage/commands.py +42 -0
  27. anyscale/aggregated_instance_usage/models.py +85 -0
  28. anyscale/anyscale-cloud-setup-gcp-oa.yaml +88 -0
  29. anyscale/anyscale-cloud-setup-gcp.yaml +113 -0
  30. anyscale/anyscale-cloud-setup-oa.yaml +121 -0
  31. anyscale/anyscale-cloud-setup.yaml +327 -0
  32. anyscale/anyscale_pydantic/HISTORY.md +1254 -0
  33. anyscale/anyscale_pydantic/LICENSE +21 -0
  34. anyscale/anyscale_pydantic/PKG-INFO +1351 -0
  35. anyscale/anyscale_pydantic/README.md +7 -0
  36. anyscale/anyscale_pydantic/__init__.py +131 -0
  37. anyscale/anyscale_pydantic/_hypothesis_plugin.py +391 -0
  38. anyscale/anyscale_pydantic/annotated_types.py +72 -0
  39. anyscale/anyscale_pydantic/class_validators.py +361 -0
  40. anyscale/anyscale_pydantic/color.py +494 -0
  41. anyscale/anyscale_pydantic/config.py +191 -0
  42. anyscale/anyscale_pydantic/dataclasses.py +478 -0
  43. anyscale/anyscale_pydantic/datetime_parse.py +248 -0
  44. anyscale/anyscale_pydantic/decorator.py +264 -0
  45. anyscale/anyscale_pydantic/env_settings.py +350 -0
  46. anyscale/anyscale_pydantic/error_wrappers.py +162 -0
  47. anyscale/anyscale_pydantic/errors.py +646 -0
  48. anyscale/anyscale_pydantic/fields.py +1256 -0
  49. anyscale/anyscale_pydantic/generics.py +400 -0
  50. anyscale/anyscale_pydantic/json.py +112 -0
  51. anyscale/anyscale_pydantic/main.py +1109 -0
  52. anyscale/anyscale_pydantic/mypy.py +943 -0
  53. anyscale/anyscale_pydantic/networks.py +739 -0
  54. anyscale/anyscale_pydantic/parse.py +66 -0
  55. anyscale/anyscale_pydantic/py.typed +0 -0
  56. anyscale/anyscale_pydantic/schema.py +1164 -0
  57. anyscale/anyscale_pydantic/tools.py +92 -0
  58. anyscale/anyscale_pydantic/types.py +1206 -0
  59. anyscale/anyscale_pydantic/typing.py +603 -0
  60. anyscale/anyscale_pydantic/utils.py +803 -0
  61. anyscale/anyscale_pydantic/validators.py +765 -0
  62. anyscale/anyscale_pydantic/version.py +38 -0
  63. anyscale/anyscale_schema.json +9 -0
  64. anyscale/api.py +215 -0
  65. anyscale/api_utils/README.md +2 -0
  66. anyscale/api_utils/__init__.py +0 -0
  67. anyscale/api_utils/common_utils.py +81 -0
  68. anyscale/api_utils/exceptions/__init__.py +0 -0
  69. anyscale/api_utils/exceptions/job_errors.py +2 -0
  70. anyscale/api_utils/job_logs_util.py +116 -0
  71. anyscale/api_utils/job_util.py +22 -0
  72. anyscale/api_utils/logs_util.py +61 -0
  73. anyscale/authenticate.py +298 -0
  74. anyscale/aws_iam_policies.py +465 -0
  75. anyscale/background/__init__.py +0 -0
  76. anyscale/background/job_runner.py +64 -0
  77. anyscale/cli_logger.py +378 -0
  78. anyscale/client/.gitignore +66 -0
  79. anyscale/client/.openapi-generator/VERSION +1 -0
  80. anyscale/client/.openapi-generator-ignore +23 -0
  81. anyscale/client/README.md +1070 -0
  82. anyscale/client/git_push.sh +58 -0
  83. anyscale/client/openapi_client/__init__.py +667 -0
  84. anyscale/client/openapi_client/api/__init__.py +6 -0
  85. anyscale/client/openapi_client/api/default_api.py +40922 -0
  86. anyscale/client/openapi_client/api_client.py +647 -0
  87. anyscale/client/openapi_client/configuration.py +373 -0
  88. anyscale/client/openapi_client/exceptions.py +120 -0
  89. anyscale/client/openapi_client/models/__init__.py +652 -0
  90. anyscale/client/openapi_client/models/access_config.py +122 -0
  91. anyscale/client/openapi_client/models/aggregated_instance_usage_with_cost_model.py +733 -0
  92. anyscale/client/openapi_client/models/aggregatedinstanceusagewithcostmodel_list_response.py +147 -0
  93. anyscale/client/openapi_client/models/aica_endpoint.py +527 -0
  94. anyscale/client/openapi_client/models/aica_endpoint_event.py +433 -0
  95. anyscale/client/openapi_client/models/aica_endpoint_event_level.py +103 -0
  96. anyscale/client/openapi_client/models/aica_endpoint_event_type.py +120 -0
  97. anyscale/client/openapi_client/models/aica_endpoint_scope.py +102 -0
  98. anyscale/client/openapi_client/models/aica_model.py +398 -0
  99. anyscale/client/openapi_client/models/aica_model_accelerator_map.py +123 -0
  100. anyscale/client/openapi_client/models/aica_model_configuration.py +209 -0
  101. anyscale/client/openapi_client/models/aica_observability_urls.py +178 -0
  102. anyscale/client/openapi_client/models/aicaendpoint_list_response.py +147 -0
  103. anyscale/client/openapi_client/models/aicaendpoint_response.py +121 -0
  104. anyscale/client/openapi_client/models/aicaendpointevent_list_response.py +147 -0
  105. anyscale/client/openapi_client/models/aicamodel_list_response.py +147 -0
  106. anyscale/client/openapi_client/models/aicamodel_response.py +121 -0
  107. anyscale/client/openapi_client/models/aioa_cloud_waitlist_record.py +254 -0
  108. anyscale/client/openapi_client/models/aioacloudwaitlistrecord_response.py +121 -0
  109. anyscale/client/openapi_client/models/alert_type.py +103 -0
  110. anyscale/client/openapi_client/models/anyscale_aws_account.py +121 -0
  111. anyscale/client/openapi_client/models/anyscale_service_account.py +256 -0
  112. anyscale/client/openapi_client/models/anyscale_version_response.py +121 -0
  113. anyscale/client/openapi_client/models/anyscaleawsaccount_response.py +121 -0
  114. anyscale/client/openapi_client/models/anyscaled_credential_response.py +121 -0
  115. anyscale/client/openapi_client/models/anyscaledcredentialresponse_response.py +121 -0
  116. anyscale/client/openapi_client/models/anyscaleserviceaccount_response.py +121 -0
  117. anyscale/client/openapi_client/models/anyscaleversionresponse_response.py +121 -0
  118. anyscale/client/openapi_client/models/api_key_parameters.py +147 -0
  119. anyscale/client/openapi_client/models/app_config.py +436 -0
  120. anyscale/client/openapi_client/models/app_config_config_schema.py +235 -0
  121. anyscale/client/openapi_client/models/appconfig_list_response.py +147 -0
  122. anyscale/client/openapi_client/models/appconfig_response.py +121 -0
  123. anyscale/client/openapi_client/models/application_type.py +99 -0
  124. anyscale/client/openapi_client/models/applied_snapshot.py +175 -0
  125. anyscale/client/openapi_client/models/apply_production_service_v2_model.py +490 -0
  126. anyscale/client/openapi_client/models/archive_status.py +101 -0
  127. anyscale/client/openapi_client/models/archived_logs_info.py +164 -0
  128. anyscale/client/openapi_client/models/archivedlogsinfo_response.py +121 -0
  129. anyscale/client/openapi_client/models/attach_machine_pool_to_cloud_request.py +152 -0
  130. anyscale/client/openapi_client/models/attachmachinepooltocloudresponse_response.py +121 -0
  131. anyscale/client/openapi_client/models/aviary_model_config_v2.py +358 -0
  132. anyscale/client/openapi_client/models/aws_credentials.py +181 -0
  133. anyscale/client/openapi_client/models/aws_memory_db_cluster_config.py +148 -0
  134. anyscale/client/openapi_client/models/aws_region_and_zones.py +123 -0
  135. anyscale/client/openapi_client/models/aws_region_info.py +152 -0
  136. anyscale/client/openapi_client/models/awsregionandzones_response.py +121 -0
  137. anyscale/client/openapi_client/models/bank_account_information.py +239 -0
  138. anyscale/client/openapi_client/models/base_job_status.py +105 -0
  139. anyscale/client/openapi_client/models/baseimagesenum.py +2130 -0
  140. anyscale/client/openapi_client/models/batch_response_batched_result_organization_invitation_base.py +121 -0
  141. anyscale/client/openapi_client/models/batched_result_organization_invitation_base.py +173 -0
  142. anyscale/client/openapi_client/models/billing_information.py +181 -0
  143. anyscale/client/openapi_client/models/billing_version_code.py +100 -0
  144. anyscale/client/openapi_client/models/body_aws_marketplace_registration_api_v2_organization_billing_aws_marketplace_registration_post.py +121 -0
  145. anyscale/client/openapi_client/models/buffer_registration.py +285 -0
  146. anyscale/client/openapi_client/models/build.py +607 -0
  147. anyscale/client/openapi_client/models/build_log_response.py +123 -0
  148. anyscale/client/openapi_client/models/build_registration.py +285 -0
  149. anyscale/client/openapi_client/models/build_response.py +121 -0
  150. anyscale/client/openapi_client/models/build_status.py +104 -0
  151. anyscale/client/openapi_client/models/buildlogresponse_response.py +121 -0
  152. anyscale/client/openapi_client/models/card.py +181 -0
  153. anyscale/client/openapi_client/models/card_id.py +108 -0
  154. anyscale/client/openapi_client/models/card_list_response.py +147 -0
  155. anyscale/client/openapi_client/models/change_password_params.py +148 -0
  156. anyscale/client/openapi_client/models/cleanup_leaked_grafana_dashboard_response.py +208 -0
  157. anyscale/client/openapi_client/models/cleanupleakedgrafanadashboardresponse_response.py +121 -0
  158. anyscale/client/openapi_client/models/clone_experimental_workspace.py +151 -0
  159. anyscale/client/openapi_client/models/cloud.py +802 -0
  160. anyscale/client/openapi_client/models/cloud_analytics_event.py +351 -0
  161. anyscale/client/openapi_client/models/cloud_analytics_event_cloud_provider_error.py +152 -0
  162. anyscale/client/openapi_client/models/cloud_analytics_event_cloud_resource.py +117 -0
  163. anyscale/client/openapi_client/models/cloud_analytics_event_command_name.py +103 -0
  164. anyscale/client/openapi_client/models/cloud_analytics_event_error.py +150 -0
  165. anyscale/client/openapi_client/models/cloud_analytics_event_name.py +109 -0
  166. anyscale/client/openapi_client/models/cloud_collaborator.py +175 -0
  167. anyscale/client/openapi_client/models/cloud_collaborator_value.py +177 -0
  168. anyscale/client/openapi_client/models/cloud_collaborators_query.py +122 -0
  169. anyscale/client/openapi_client/models/cloud_config.py +206 -0
  170. anyscale/client/openapi_client/models/cloud_data_bucket_access_mode.py +100 -0
  171. anyscale/client/openapi_client/models/cloud_data_bucket_file_type.py +102 -0
  172. anyscale/client/openapi_client/models/cloud_data_bucket_presigned_upload_info.py +268 -0
  173. anyscale/client/openapi_client/models/cloud_data_bucket_presigned_upload_request.py +152 -0
  174. anyscale/client/openapi_client/models/cloud_data_bucket_presigned_upload_scheme.py +100 -0
  175. anyscale/client/openapi_client/models/cloud_data_bucket_presigned_url_request.py +209 -0
  176. anyscale/client/openapi_client/models/cloud_data_bucket_presigned_url_response.py +296 -0
  177. anyscale/client/openapi_client/models/cloud_data_bucket_presigned_url_scheme.py +100 -0
  178. anyscale/client/openapi_client/models/cloud_data_bucket_request_scope.py +100 -0
  179. anyscale/client/openapi_client/models/cloud_hosting_type.py +100 -0
  180. anyscale/client/openapi_client/models/cloud_list_response.py +147 -0
  181. anyscale/client/openapi_client/models/cloud_name_options.py +121 -0
  182. anyscale/client/openapi_client/models/cloud_overview_dashboard.py +175 -0
  183. anyscale/client/openapi_client/models/cloud_project_collaborator.py +175 -0
  184. anyscale/client/openapi_client/models/cloud_project_collaborator_value.py +121 -0
  185. anyscale/client/openapi_client/models/cloud_provider.py +102 -0
  186. anyscale/client/openapi_client/models/cloud_providers.py +103 -0
  187. anyscale/client/openapi_client/models/cloud_region_and_zones.py +123 -0
  188. anyscale/client/openapi_client/models/cloud_region_info.py +152 -0
  189. anyscale/client/openapi_client/models/cloud_resource.py +740 -0
  190. anyscale/client/openapi_client/models/cloud_resource_gcp.py +691 -0
  191. anyscale/client/openapi_client/models/cloud_response.py +121 -0
  192. anyscale/client/openapi_client/models/cloud_state.py +104 -0
  193. anyscale/client/openapi_client/models/cloud_status.py +100 -0
  194. anyscale/client/openapi_client/models/cloud_type.py +100 -0
  195. anyscale/client/openapi_client/models/cloud_types.py +100 -0
  196. anyscale/client/openapi_client/models/cloud_version.py +100 -0
  197. anyscale/client/openapi_client/models/cloud_waitlist_status.py +102 -0
  198. anyscale/client/openapi_client/models/cloud_with_cloud_resource.py +830 -0
  199. anyscale/client/openapi_client/models/cloud_with_cloud_resource_gcp.py +830 -0
  200. anyscale/client/openapi_client/models/cloudcollaborator_list_response.py +147 -0
  201. anyscale/client/openapi_client/models/clouddatabucketpresigneduploadinfo_response.py +121 -0
  202. anyscale/client/openapi_client/models/clouddatabucketpresignedurlresponse_response.py +121 -0
  203. anyscale/client/openapi_client/models/cloudoverviewdashboard_response.py +121 -0
  204. anyscale/client/openapi_client/models/cloudregionandzones_response.py +121 -0
  205. anyscale/client/openapi_client/models/cloudresource_response.py +121 -0
  206. anyscale/client/openapi_client/models/cloudresourcegcp_response.py +121 -0
  207. anyscale/client/openapi_client/models/cloudwithcloudresource_response.py +121 -0
  208. anyscale/client/openapi_client/models/cloudwithcloudresourcegcp_response.py +121 -0
  209. anyscale/client/openapi_client/models/cluster_auth_response.py +148 -0
  210. anyscale/client/openapi_client/models/cluster_config.py +178 -0
  211. anyscale/client/openapi_client/models/cluster_config_with_session_idle_timeout.py +204 -0
  212. anyscale/client/openapi_client/models/cluster_environments_query.py +290 -0
  213. anyscale/client/openapi_client/models/cluster_event.py +174 -0
  214. anyscale/client/openapi_client/models/cluster_events_output.py +175 -0
  215. anyscale/client/openapi_client/models/cluster_features.py +152 -0
  216. anyscale/client/openapi_client/models/cluster_management_stack_versions.py +100 -0
  217. anyscale/client/openapi_client/models/cluster_startup.py +208 -0
  218. anyscale/client/openapi_client/models/cluster_status.py +104 -0
  219. anyscale/client/openapi_client/models/cluster_status_details.py +100 -0
  220. anyscale/client/openapi_client/models/clusterauthresponse_response.py +121 -0
  221. anyscale/client/openapi_client/models/clusterconfig_response.py +121 -0
  222. anyscale/client/openapi_client/models/clusterconfigwithsessionidletimeout_response.py +121 -0
  223. anyscale/client/openapi_client/models/clustereventsoutput_response.py +121 -0
  224. anyscale/client/openapi_client/models/clusterfeatures_response.py +121 -0
  225. anyscale/client/openapi_client/models/company_size.py +103 -0
  226. anyscale/client/openapi_client/models/compute_node_type.py +292 -0
  227. anyscale/client/openapi_client/models/compute_stack.py +100 -0
  228. anyscale/client/openapi_client/models/compute_template.py +415 -0
  229. anyscale/client/openapi_client/models/compute_template_config.py +461 -0
  230. anyscale/client/openapi_client/models/compute_template_query.py +316 -0
  231. anyscale/client/openapi_client/models/computetemplate_response.py +121 -0
  232. anyscale/client/openapi_client/models/computetemplateconfig_response.py +121 -0
  233. anyscale/client/openapi_client/models/create_aica_endpoint.py +210 -0
  234. anyscale/client/openapi_client/models/create_aioa_cloud_waitlist.py +173 -0
  235. anyscale/client/openapi_client/models/create_analytics_event.py +122 -0
  236. anyscale/client/openapi_client/models/create_app_config.py +235 -0
  237. anyscale/client/openapi_client/models/create_app_config_configuration_schema.py +235 -0
  238. anyscale/client/openapi_client/models/create_billing_version.py +181 -0
  239. anyscale/client/openapi_client/models/create_bug_report_response.py +152 -0
  240. anyscale/client/openapi_client/models/create_build.py +263 -0
  241. anyscale/client/openapi_client/models/create_byod_app_config.py +180 -0
  242. anyscale/client/openapi_client/models/create_byod_app_config_configuration_schema.py +206 -0
  243. anyscale/client/openapi_client/models/create_byod_build.py +152 -0
  244. anyscale/client/openapi_client/models/create_cloud_collaborator.py +148 -0
  245. anyscale/client/openapi_client/models/create_cloud_resource.py +682 -0
  246. anyscale/client/openapi_client/models/create_cloud_resource_gcp.py +633 -0
  247. anyscale/client/openapi_client/models/create_cloud_with_cloud_resource.py +546 -0
  248. anyscale/client/openapi_client/models/create_cluster_compute_config.py +463 -0
  249. anyscale/client/openapi_client/models/create_compute_template.py +229 -0
  250. anyscale/client/openapi_client/models/create_compute_template_config.py +464 -0
  251. anyscale/client/openapi_client/models/create_dataset.py +200 -0
  252. anyscale/client/openapi_client/models/create_experimental_workspace.py +435 -0
  253. anyscale/client/openapi_client/models/create_experimental_workspace_from_job.py +123 -0
  254. anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +156 -0
  255. anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +353 -0
  256. anyscale/client/openapi_client/models/create_instance_usage_budget.py +253 -0
  257. anyscale/client/openapi_client/models/create_internal_production_job.py +262 -0
  258. anyscale/client/openapi_client/models/create_job_queue_config.py +206 -0
  259. anyscale/client/openapi_client/models/create_job_queue_requests.py +323 -0
  260. anyscale/client/openapi_client/models/create_machine_pool_request.py +151 -0
  261. anyscale/client/openapi_client/models/create_machine_pool_response.py +123 -0
  262. anyscale/client/openapi_client/models/create_machine_request.py +151 -0
  263. anyscale/client/openapi_client/models/create_machine_response.py +123 -0
  264. anyscale/client/openapi_client/models/create_metronome_webhook_notification.py +175 -0
  265. anyscale/client/openapi_client/models/create_notification_channel_record.py +146 -0
  266. anyscale/client/openapi_client/models/create_organization_configuration.py +199 -0
  267. anyscale/client/openapi_client/models/create_organization_invitation.py +121 -0
  268. anyscale/client/openapi_client/models/create_otp_return_api_model.py +148 -0
  269. anyscale/client/openapi_client/models/create_production_job_config.py +347 -0
  270. anyscale/client/openapi_client/models/create_resource_quota.py +293 -0
  271. anyscale/client/openapi_client/models/create_schedule.py +263 -0
  272. anyscale/client/openapi_client/models/create_session_from_snapshot_options.py +565 -0
  273. anyscale/client/openapi_client/models/create_session_in_db.py +434 -0
  274. anyscale/client/openapi_client/models/create_session_response.py +174 -0
  275. anyscale/client/openapi_client/models/create_user.py +439 -0
  276. anyscale/client/openapi_client/models/create_user_project_collaborator.py +148 -0
  277. anyscale/client/openapi_client/models/create_user_project_collaborator_value.py +121 -0
  278. anyscale/client/openapi_client/models/create_workspace_from_template.py +263 -0
  279. anyscale/client/openapi_client/models/createbugreportresponse_response.py +121 -0
  280. anyscale/client/openapi_client/models/createcomputetemplateconfig_response.py +121 -0
  281. anyscale/client/openapi_client/models/createmachinepoolresponse_response.py +121 -0
  282. anyscale/client/openapi_client/models/createmachineresponse_response.py +121 -0
  283. anyscale/client/openapi_client/models/createotpreturnapimodel_response.py +121 -0
  284. anyscale/client/openapi_client/models/createsessionresponse_response.py +121 -0
  285. anyscale/client/openapi_client/models/credit_card_information.py +268 -0
  286. anyscale/client/openapi_client/models/customer_alert_status.py +101 -0
  287. anyscale/client/openapi_client/models/customer_billing_type.py +101 -0
  288. anyscale/client/openapi_client/models/dataplane_services.py +102 -0
  289. anyscale/client/openapi_client/models/dataset.py +416 -0
  290. anyscale/client/openapi_client/models/dataset_list_response.py +150 -0
  291. anyscale/client/openapi_client/models/dataset_response.py +121 -0
  292. anyscale/client/openapi_client/models/dataset_upload.py +148 -0
  293. anyscale/client/openapi_client/models/datasetupload_response.py +121 -0
  294. anyscale/client/openapi_client/models/decorated_application_template.py +493 -0
  295. anyscale/client/openapi_client/models/decorated_build.py +664 -0
  296. anyscale/client/openapi_client/models/decorated_compute_template.py +446 -0
  297. anyscale/client/openapi_client/models/decorated_compute_template_config.py +490 -0
  298. anyscale/client/openapi_client/models/decorated_interactive_session.py +793 -0
  299. anyscale/client/openapi_client/models/decorated_job.py +793 -0
  300. anyscale/client/openapi_client/models/decorated_job_queue.py +639 -0
  301. anyscale/client/openapi_client/models/decorated_job_submission.py +575 -0
  302. anyscale/client/openapi_client/models/decorated_list_service_api_model.py +670 -0
  303. anyscale/client/openapi_client/models/decorated_production_job.py +805 -0
  304. anyscale/client/openapi_client/models/decorated_production_job_state_transition.py +319 -0
  305. anyscale/client/openapi_client/models/decorated_production_service_v2_api_model.py +641 -0
  306. anyscale/client/openapi_client/models/decorated_production_service_v2_version_api_model.py +437 -0
  307. anyscale/client/openapi_client/models/decorated_runtime_env.py +488 -0
  308. anyscale/client/openapi_client/models/decorated_schedule.py +552 -0
  309. anyscale/client/openapi_client/models/decorated_serve_deployment.py +711 -0
  310. anyscale/client/openapi_client/models/decorated_service_event_api_model.py +513 -0
  311. anyscale/client/openapi_client/models/decorated_session.py +1789 -0
  312. anyscale/client/openapi_client/models/decorated_support_request.py +283 -0
  313. anyscale/client/openapi_client/models/decorated_unified_job.py +466 -0
  314. anyscale/client/openapi_client/models/decoratedapplicationtemplate_list_response.py +147 -0
  315. anyscale/client/openapi_client/models/decoratedapplicationtemplate_response.py +121 -0
  316. anyscale/client/openapi_client/models/decoratedbuild_list_response.py +147 -0
  317. anyscale/client/openapi_client/models/decoratedbuild_response.py +121 -0
  318. anyscale/client/openapi_client/models/decoratedcomputetemplate_list_response.py +147 -0
  319. anyscale/client/openapi_client/models/decoratedcomputetemplate_response.py +121 -0
  320. anyscale/client/openapi_client/models/decoratedinteractivesession_list_response.py +147 -0
  321. anyscale/client/openapi_client/models/decoratedinteractivesession_response.py +121 -0
  322. anyscale/client/openapi_client/models/decoratedjob_list_response.py +147 -0
  323. anyscale/client/openapi_client/models/decoratedjob_response.py +121 -0
  324. anyscale/client/openapi_client/models/decoratedjobqueue_list_response.py +147 -0
  325. anyscale/client/openapi_client/models/decoratedjobqueue_response.py +121 -0
  326. anyscale/client/openapi_client/models/decoratedjobsubmission_list_response.py +147 -0
  327. anyscale/client/openapi_client/models/decoratedjobsubmission_response.py +121 -0
  328. anyscale/client/openapi_client/models/decoratedlistserviceapimodel_list_response.py +147 -0
  329. anyscale/client/openapi_client/models/decoratedproductionjob_list_response.py +147 -0
  330. anyscale/client/openapi_client/models/decoratedproductionjob_response.py +121 -0
  331. anyscale/client/openapi_client/models/decoratedproductionjobstatetransition_list_response.py +147 -0
  332. anyscale/client/openapi_client/models/decoratedproductionservicev2_apimodel_response.py +121 -0
  333. anyscale/client/openapi_client/models/decoratedproductionservicev2_versionapimodel_list_response.py +147 -0
  334. anyscale/client/openapi_client/models/decoratedruntimeenv_list_response.py +147 -0
  335. anyscale/client/openapi_client/models/decoratedruntimeenv_response.py +121 -0
  336. anyscale/client/openapi_client/models/decoratedschedule_list_response.py +147 -0
  337. anyscale/client/openapi_client/models/decoratedschedule_response.py +121 -0
  338. anyscale/client/openapi_client/models/decoratedservedeployment_list_response.py +147 -0
  339. anyscale/client/openapi_client/models/decoratedservedeployment_response.py +121 -0
  340. anyscale/client/openapi_client/models/decoratedserviceeventapimodel_list_response.py +147 -0
  341. anyscale/client/openapi_client/models/decoratedsession_list_response.py +147 -0
  342. anyscale/client/openapi_client/models/decoratedsession_response.py +121 -0
  343. anyscale/client/openapi_client/models/decoratedsupportrequest_list_response.py +147 -0
  344. anyscale/client/openapi_client/models/decoratedsupportrequest_response.py +121 -0
  345. anyscale/client/openapi_client/models/decoratedunifiedjob_list_response.py +147 -0
  346. anyscale/client/openapi_client/models/decoratedunifiedjob_response.py +121 -0
  347. anyscale/client/openapi_client/models/delete_machine_pool_request.py +123 -0
  348. anyscale/client/openapi_client/models/delete_machine_request.py +206 -0
  349. anyscale/client/openapi_client/models/deleted_platform_fine_tuned_model.py +148 -0
  350. anyscale/client/openapi_client/models/deletedplatformfinetunedmodel_response.py +121 -0
  351. anyscale/client/openapi_client/models/deletemachinepoolresponse_response.py +121 -0
  352. anyscale/client/openapi_client/models/detach_machine_pool_from_cloud_request.py +152 -0
  353. anyscale/client/openapi_client/models/detachmachinepoolfromcloudresponse_response.py +121 -0
  354. anyscale/client/openapi_client/models/dismissal_type.py +100 -0
  355. anyscale/client/openapi_client/models/editable_cloud_resource.py +206 -0
  356. anyscale/client/openapi_client/models/editable_cloud_resource_gcp.py +178 -0
  357. anyscale/client/openapi_client/models/error.py +174 -0
  358. anyscale/client/openapi_client/models/event_level.py +104 -0
  359. anyscale/client/openapi_client/models/execute_command_response.py +175 -0
  360. anyscale/client/openapi_client/models/execute_interactive_command_options.py +147 -0
  361. anyscale/client/openapi_client/models/execute_shell_command_options.py +121 -0
  362. anyscale/client/openapi_client/models/executecommandresponse_response.py +121 -0
  363. anyscale/client/openapi_client/models/experimental_workspace.py +748 -0
  364. anyscale/client/openapi_client/models/experimental_workspaces_sort_field.py +100 -0
  365. anyscale/client/openapi_client/models/experimentalworkspace_list_response.py +147 -0
  366. anyscale/client/openapi_client/models/experimentalworkspace_response.py +121 -0
  367. anyscale/client/openapi_client/models/external_service_status.py +147 -0
  368. anyscale/client/openapi_client/models/external_service_status_response.py +250 -0
  369. anyscale/client/openapi_client/models/external_terminal_command.py +280 -0
  370. anyscale/client/openapi_client/models/externalservicestatusresponse_response.py +121 -0
  371. anyscale/client/openapi_client/models/feature_compatibility.py +152 -0
  372. anyscale/client/openapi_client/models/feature_flag_response.py +121 -0
  373. anyscale/client/openapi_client/models/featureflagresponse_response.py +121 -0
  374. anyscale/client/openapi_client/models/fine_tune_type.py +100 -0
  375. anyscale/client/openapi_client/models/fine_tuned_model.py +412 -0
  376. anyscale/client/openapi_client/models/fine_tuning_job_status.py +103 -0
  377. anyscale/client/openapi_client/models/finetunedmodel_list_response.py +147 -0
  378. anyscale/client/openapi_client/models/finetunedmodel_response.py +121 -0
  379. anyscale/client/openapi_client/models/finish_ft_job_request.py +204 -0
  380. anyscale/client/openapi_client/models/finish_ft_job_request_v2.py +183 -0
  381. anyscale/client/openapi_client/models/gcp_file_store_config.py +175 -0
  382. anyscale/client/openapi_client/models/gcp_memorystore_instance_config.py +148 -0
  383. anyscale/client/openapi_client/models/grafana_dashboard.py +201 -0
  384. anyscale/client/openapi_client/models/grpc_protocol_config.py +178 -0
  385. anyscale/client/openapi_client/models/ha_job_error_types.py +103 -0
  386. anyscale/client/openapi_client/models/ha_job_event_level.py +101 -0
  387. anyscale/client/openapi_client/models/ha_job_event_origin.py +100 -0
  388. anyscale/client/openapi_client/models/ha_job_goal_states.py +102 -0
  389. anyscale/client/openapi_client/models/ha_job_states.py +109 -0
  390. anyscale/client/openapi_client/models/ha_job_type.py +100 -0
  391. anyscale/client/openapi_client/models/ha_jobs_sort_field.py +105 -0
  392. anyscale/client/openapi_client/models/head_ip.py +121 -0
  393. anyscale/client/openapi_client/models/headip_response.py +121 -0
  394. anyscale/client/openapi_client/models/http_protocol_config.py +150 -0
  395. anyscale/client/openapi_client/models/http_validation_error.py +120 -0
  396. anyscale/client/openapi_client/models/idle_termination_status.py +104 -0
  397. anyscale/client/openapi_client/models/import_aica_model.py +241 -0
  398. anyscale/client/openapi_client/models/instance_usage_budget.py +572 -0
  399. anyscale/client/openapi_client/models/instance_usage_budget_evaluation_period.py +100 -0
  400. anyscale/client/openapi_client/models/instanceusagebudget_list_response.py +147 -0
  401. anyscale/client/openapi_client/models/instanceusagebudget_response.py +121 -0
  402. anyscale/client/openapi_client/models/integration_details.py +120 -0
  403. anyscale/client/openapi_client/models/interactive_session_logs.py +152 -0
  404. anyscale/client/openapi_client/models/interactivesessionlogs_response.py +121 -0
  405. anyscale/client/openapi_client/models/internal_production_job.py +663 -0
  406. anyscale/client/openapi_client/models/internalproductionjob_response.py +121 -0
  407. anyscale/client/openapi_client/models/invoice.py +413 -0
  408. anyscale/client/openapi_client/models/invoice_list_response.py +147 -0
  409. anyscale/client/openapi_client/models/invoice_status.py +102 -0
  410. anyscale/client/openapi_client/models/invoices_query.py +150 -0
  411. anyscale/client/openapi_client/models/job_access.py +102 -0
  412. anyscale/client/openapi_client/models/job_queue.py +467 -0
  413. anyscale/client/openapi_client/models/job_queue_config.py +122 -0
  414. anyscale/client/openapi_client/models/job_queue_execution_mode.py +101 -0
  415. anyscale/client/openapi_client/models/job_queue_spec.py +263 -0
  416. anyscale/client/openapi_client/models/job_queue_state.py +100 -0
  417. anyscale/client/openapi_client/models/job_queues_query.py +262 -0
  418. anyscale/client/openapi_client/models/job_run_type.py +101 -0
  419. anyscale/client/openapi_client/models/job_state_log_level_types.py +100 -0
  420. anyscale/client/openapi_client/models/job_status.py +105 -0
  421. anyscale/client/openapi_client/models/job_submissions_sort_field.py +101 -0
  422. anyscale/client/openapi_client/models/jobqueue_response.py +121 -0
  423. anyscale/client/openapi_client/models/jobs_logs.py +152 -0
  424. anyscale/client/openapi_client/models/jobs_logs_query_info.py +181 -0
  425. anyscale/client/openapi_client/models/jobs_sort_field.py +104 -0
  426. anyscale/client/openapi_client/models/jobslogs_response.py +121 -0
  427. anyscale/client/openapi_client/models/jobslogsqueryinfo_response.py +121 -0
  428. anyscale/client/openapi_client/models/json_patch_operation.py +200 -0
  429. anyscale/client/openapi_client/models/kubernetes_manager_registration_request.py +123 -0
  430. anyscale/client/openapi_client/models/kubernetes_manager_registration_response.py +123 -0
  431. anyscale/client/openapi_client/models/kubernetesmanagerregistrationresponse_response.py +121 -0
  432. anyscale/client/openapi_client/models/lb_resource.py +123 -0
  433. anyscale/client/openapi_client/models/lbresource_response.py +121 -0
  434. anyscale/client/openapi_client/models/list_machine_pools_response.py +123 -0
  435. anyscale/client/openapi_client/models/list_machines_response.py +121 -0
  436. anyscale/client/openapi_client/models/list_resource_quotas_query.py +234 -0
  437. anyscale/client/openapi_client/models/list_response_metadata.py +146 -0
  438. anyscale/client/openapi_client/models/listmachinepoolsresponse_response.py +121 -0
  439. anyscale/client/openapi_client/models/listmachinesresponse_response.py +121 -0
  440. anyscale/client/openapi_client/models/log_detail.py +187 -0
  441. anyscale/client/openapi_client/models/log_details.py +151 -0
  442. anyscale/client/openapi_client/models/log_download_config.py +206 -0
  443. anyscale/client/openapi_client/models/log_download_request.py +150 -0
  444. anyscale/client/openapi_client/models/log_download_result.py +207 -0
  445. anyscale/client/openapi_client/models/log_file_chunk.py +439 -0
  446. anyscale/client/openapi_client/models/log_filter.py +430 -0
  447. anyscale/client/openapi_client/models/log_item.py +181 -0
  448. anyscale/client/openapi_client/models/log_item_batch.py +151 -0
  449. anyscale/client/openapi_client/models/log_level_types.py +100 -0
  450. anyscale/client/openapi_client/models/log_stream.py +151 -0
  451. anyscale/client/openapi_client/models/logdetails_response.py +121 -0
  452. anyscale/client/openapi_client/models/logdownloadresult_response.py +121 -0
  453. anyscale/client/openapi_client/models/login_user_params.py +205 -0
  454. anyscale/client/openapi_client/models/logitembatch_response.py +121 -0
  455. anyscale/client/openapi_client/models/logs_output.py +202 -0
  456. anyscale/client/openapi_client/models/logsoutput_response.py +121 -0
  457. anyscale/client/openapi_client/models/logstream_response.py +121 -0
  458. anyscale/client/openapi_client/models/long_running_workload.py +256 -0
  459. anyscale/client/openapi_client/models/longrunningworkload_list_response.py +147 -0
  460. anyscale/client/openapi_client/models/machine_allocation_state.py +100 -0
  461. anyscale/client/openapi_client/models/machine_connection_state.py +100 -0
  462. anyscale/client/openapi_client/models/machine_info.py +466 -0
  463. anyscale/client/openapi_client/models/machine_pool.py +266 -0
  464. anyscale/client/openapi_client/models/metronome_customer_info_model.py +148 -0
  465. anyscale/client/openapi_client/models/metronome_dashboard_type.py +101 -0
  466. anyscale/client/openapi_client/models/metronomecustomerinfomodel_list_response.py +147 -0
  467. anyscale/client/openapi_client/models/metronomecustomerinfomodel_response.py +121 -0
  468. anyscale/client/openapi_client/models/mini_build.py +267 -0
  469. anyscale/client/openapi_client/models/mini_cloud.py +321 -0
  470. anyscale/client/openapi_client/models/mini_cluster.py +148 -0
  471. anyscale/client/openapi_client/models/mini_compute_template.py +228 -0
  472. anyscale/client/openapi_client/models/mini_compute_template_config.py +121 -0
  473. anyscale/client/openapi_client/models/mini_job_run.py +599 -0
  474. anyscale/client/openapi_client/models/mini_namespace.py +148 -0
  475. anyscale/client/openapi_client/models/mini_organization.py +148 -0
  476. anyscale/client/openapi_client/models/mini_production_job.py +202 -0
  477. anyscale/client/openapi_client/models/mini_project.py +205 -0
  478. anyscale/client/openapi_client/models/mini_runtime_environment.py +147 -0
  479. anyscale/client/openapi_client/models/mini_schedule.py +180 -0
  480. anyscale/client/openapi_client/models/mini_user.py +266 -0
  481. anyscale/client/openapi_client/models/minibuild_list_response.py +147 -0
  482. anyscale/client/openapi_client/models/minicomputetemplate_list_response.py +147 -0
  483. anyscale/client/openapi_client/models/miniproject_list_response.py +147 -0
  484. anyscale/client/openapi_client/models/monitor_logs_extension.py +100 -0
  485. anyscale/client/openapi_client/models/named_entity.py +148 -0
  486. anyscale/client/openapi_client/models/nfs_mount_target.py +151 -0
  487. anyscale/client/openapi_client/models/node_registration_aws.py +152 -0
  488. anyscale/client/openapi_client/models/node_registration_gcp.py +123 -0
  489. anyscale/client/openapi_client/models/node_registration_k8_s.py +178 -0
  490. anyscale/client/openapi_client/models/node_registration_provisioned.py +150 -0
  491. anyscale/client/openapi_client/models/node_registration_v2.py +279 -0
  492. anyscale/client/openapi_client/models/node_type.py +100 -0
  493. anyscale/client/openapi_client/models/notification_channel_email_config.py +121 -0
  494. anyscale/client/openapi_client/models/notification_channel_webhook_config.py +121 -0
  495. anyscale/client/openapi_client/models/onboarding_user_cards_query.py +122 -0
  496. anyscale/client/openapi_client/models/organization.py +490 -0
  497. anyscale/client/openapi_client/models/organization_availability.py +148 -0
  498. anyscale/client/openapi_client/models/organization_collaborator.py +259 -0
  499. anyscale/client/openapi_client/models/organization_configuration.py +280 -0
  500. anyscale/client/openapi_client/models/organization_configuration_response.py +227 -0
  501. anyscale/client/openapi_client/models/organization_invitation.py +255 -0
  502. anyscale/client/openapi_client/models/organization_invitation_base.py +121 -0
  503. anyscale/client/openapi_client/models/organization_marketing_questions.py +198 -0
  504. anyscale/client/openapi_client/models/organization_permission_level.py +100 -0
  505. anyscale/client/openapi_client/models/organization_project_collaborator.py +175 -0
  506. anyscale/client/openapi_client/models/organization_project_collaborator_value.py +148 -0
  507. anyscale/client/openapi_client/models/organization_public_identifier.py +121 -0
  508. anyscale/client/openapi_client/models/organization_response.py +121 -0
  509. anyscale/client/openapi_client/models/organization_summary.py +229 -0
  510. anyscale/client/openapi_client/models/organization_usage_alert.py +210 -0
  511. anyscale/client/openapi_client/models/organization_usage_alert_severity.py +100 -0
  512. anyscale/client/openapi_client/models/organizationavailability_response.py +121 -0
  513. anyscale/client/openapi_client/models/organizationcollaborator_list_response.py +147 -0
  514. anyscale/client/openapi_client/models/organizationconfiguration_list_response.py +147 -0
  515. anyscale/client/openapi_client/models/organizationconfigurationresponse_response.py +121 -0
  516. anyscale/client/openapi_client/models/organizationinvitation_list_response.py +147 -0
  517. anyscale/client/openapi_client/models/organizationinvitation_response.py +121 -0
  518. anyscale/client/openapi_client/models/organizationinvitationbase_response.py +121 -0
  519. anyscale/client/openapi_client/models/organizationprojectcollaborator_list_response.py +147 -0
  520. anyscale/client/openapi_client/models/organizationpublicidentifier_response.py +121 -0
  521. anyscale/client/openapi_client/models/organizationusagealert_list_response.py +147 -0
  522. anyscale/client/openapi_client/models/page_query.py +153 -0
  523. anyscale/client/openapi_client/models/pause_schedule.py +123 -0
  524. anyscale/client/openapi_client/models/permission_level.py +101 -0
  525. anyscale/client/openapi_client/models/platform_fine_tuning_job.py +577 -0
  526. anyscale/client/openapi_client/models/platformfinetuningjob_list_response.py +147 -0
  527. anyscale/client/openapi_client/models/platformfinetuningjob_response.py +121 -0
  528. anyscale/client/openapi_client/models/product_autoscaler_flag.py +122 -0
  529. anyscale/client/openapi_client/models/product_type.py +100 -0
  530. anyscale/client/openapi_client/models/productautoscalerflag_response.py +121 -0
  531. anyscale/client/openapi_client/models/production_job.py +437 -0
  532. anyscale/client/openapi_client/models/production_job_config.py +348 -0
  533. anyscale/client/openapi_client/models/production_job_event.py +378 -0
  534. anyscale/client/openapi_client/models/production_job_event_scope_filter.py +101 -0
  535. anyscale/client/openapi_client/models/production_job_state_transition.py +293 -0
  536. anyscale/client/openapi_client/models/productionjob_response.py +121 -0
  537. anyscale/client/openapi_client/models/productionjobevent_list_response.py +147 -0
  538. anyscale/client/openapi_client/models/project.py +554 -0
  539. anyscale/client/openapi_client/models/project_base.py +121 -0
  540. anyscale/client/openapi_client/models/project_collaborator.py +175 -0
  541. anyscale/client/openapi_client/models/project_collaborator_value.py +175 -0
  542. anyscale/client/openapi_client/models/project_collaborators_put_message.py +121 -0
  543. anyscale/client/openapi_client/models/project_create_message.py +148 -0
  544. anyscale/client/openapi_client/models/project_default_session_name.py +121 -0
  545. anyscale/client/openapi_client/models/project_delete_message.py +121 -0
  546. anyscale/client/openapi_client/models/project_list_response.py +147 -0
  547. anyscale/client/openapi_client/models/project_patch_message.py +121 -0
  548. anyscale/client/openapi_client/models/project_response.py +121 -0
  549. anyscale/client/openapi_client/models/projectbase_response.py +121 -0
  550. anyscale/client/openapi_client/models/projectcollaborator_list_response.py +147 -0
  551. anyscale/client/openapi_client/models/projectdefaultsessionname_response.py +121 -0
  552. anyscale/client/openapi_client/models/projects_sort_field.py +101 -0
  553. anyscale/client/openapi_client/models/projects_violating_tree_hierarchy_response.py +121 -0
  554. anyscale/client/openapi_client/models/projectsviolatingtreehierarchyresponse_response.py +121 -0
  555. anyscale/client/openapi_client/models/protocols.py +150 -0
  556. anyscale/client/openapi_client/models/provider_metadata.py +205 -0
  557. anyscale/client/openapi_client/models/providermetadata_response.py +121 -0
  558. anyscale/client/openapi_client/models/python_modules.py +150 -0
  559. anyscale/client/openapi_client/models/quota.py +198 -0
  560. anyscale/client/openapi_client/models/ray_gcs_external_storage_config.py +178 -0
  561. anyscale/client/openapi_client/models/ray_runtime_env_config.py +262 -0
  562. anyscale/client/openapi_client/models/read_billing_version.py +210 -0
  563. anyscale/client/openapi_client/models/readbillingversion_list_response.py +147 -0
  564. anyscale/client/openapi_client/models/replica_details.py +152 -0
  565. anyscale/client/openapi_client/models/replica_state.py +104 -0
  566. anyscale/client/openapi_client/models/request_email_magic_link_response.py +147 -0
  567. anyscale/client/openapi_client/models/request_otp_return_api_model.py +148 -0
  568. anyscale/client/openapi_client/models/request_password_reset_params.py +147 -0
  569. anyscale/client/openapi_client/models/requestemailmagiclinkresponse_response.py +121 -0
  570. anyscale/client/openapi_client/models/requestotpreturnapimodel_response.py +121 -0
  571. anyscale/client/openapi_client/models/reset_password_params.py +148 -0
  572. anyscale/client/openapi_client/models/resource_quota.py +465 -0
  573. anyscale/client/openapi_client/models/resource_quota_status.py +123 -0
  574. anyscale/client/openapi_client/models/resourcequota_list_response.py +147 -0
  575. anyscale/client/openapi_client/models/resourcequota_response.py +121 -0
  576. anyscale/client/openapi_client/models/resources.py +234 -0
  577. anyscale/client/openapi_client/models/resubmit_ft_job_request.py +121 -0
  578. anyscale/client/openapi_client/models/rollback_service_model.py +122 -0
  579. anyscale/client/openapi_client/models/rollout_strategy.py +100 -0
  580. anyscale/client/openapi_client/models/s3_download_location.py +148 -0
  581. anyscale/client/openapi_client/models/schedule_config.py +151 -0
  582. anyscale/client/openapi_client/models/serve_deployment_grafana_dashboard_status.py +101 -0
  583. anyscale/client/openapi_client/models/serve_deployment_logs.py +152 -0
  584. anyscale/client/openapi_client/models/serve_deployment_state.py +104 -0
  585. anyscale/client/openapi_client/models/servedeploymentlogs_response.py +121 -0
  586. anyscale/client/openapi_client/models/server_session_token.py +121 -0
  587. anyscale/client/openapi_client/models/serversessiontoken_response.py +121 -0
  588. anyscale/client/openapi_client/models/service_config.py +178 -0
  589. anyscale/client/openapi_client/models/service_event_current_state.py +108 -0
  590. anyscale/client/openapi_client/models/service_event_level.py +102 -0
  591. anyscale/client/openapi_client/models/service_event_origin.py +103 -0
  592. anyscale/client/openapi_client/models/service_event_scope.py +103 -0
  593. anyscale/client/openapi_client/models/service_event_scope_filter.py +104 -0
  594. anyscale/client/openapi_client/models/service_event_type.py +125 -0
  595. anyscale/client/openapi_client/models/service_event_verbose_message_model.py +180 -0
  596. anyscale/client/openapi_client/models/service_goal_states.py +100 -0
  597. anyscale/client/openapi_client/models/service_observability_urls.py +206 -0
  598. anyscale/client/openapi_client/models/service_sort_field.py +101 -0
  599. anyscale/client/openapi_client/models/service_type.py +100 -0
  600. anyscale/client/openapi_client/models/service_usage.py +353 -0
  601. anyscale/client/openapi_client/models/service_version_state.py +106 -0
  602. anyscale/client/openapi_client/models/serviceeventverbosemessagemodel_list_response.py +147 -0
  603. anyscale/client/openapi_client/models/session.py +834 -0
  604. anyscale/client/openapi_client/models/session_access.py +102 -0
  605. anyscale/client/openapi_client/models/session_autosync_sessions_update_message.py +121 -0
  606. anyscale/client/openapi_client/models/session_command.py +413 -0
  607. anyscale/client/openapi_client/models/session_command_finish_options.py +226 -0
  608. anyscale/client/openapi_client/models/session_command_id.py +121 -0
  609. anyscale/client/openapi_client/models/session_command_types.py +100 -0
  610. anyscale/client/openapi_client/models/session_create_message.py +148 -0
  611. anyscale/client/openapi_client/models/session_delete_message.py +121 -0
  612. anyscale/client/openapi_client/models/session_describe.py +175 -0
  613. anyscale/client/openapi_client/models/session_details.py +148 -0
  614. anyscale/client/openapi_client/models/session_event.py +267 -0
  615. anyscale/client/openapi_client/models/session_event_cause.py +150 -0
  616. anyscale/client/openapi_client/models/session_event_types.py +111 -0
  617. anyscale/client/openapi_client/models/session_execute_message.py +121 -0
  618. anyscale/client/openapi_client/models/session_finish_command_message.py +175 -0
  619. anyscale/client/openapi_client/models/session_history_item.py +146 -0
  620. anyscale/client/openapi_client/models/session_kill_command_message.py +121 -0
  621. anyscale/client/openapi_client/models/session_list_response.py +147 -0
  622. anyscale/client/openapi_client/models/session_patch_message.py +121 -0
  623. anyscale/client/openapi_client/models/session_response.py +121 -0
  624. anyscale/client/openapi_client/models/session_ssh_key.py +148 -0
  625. anyscale/client/openapi_client/models/session_starting_up_data.py +146 -0
  626. anyscale/client/openapi_client/models/session_state.py +111 -0
  627. anyscale/client/openapi_client/models/session_state_change_message.py +121 -0
  628. anyscale/client/openapi_client/models/session_state_data.py +146 -0
  629. anyscale/client/openapi_client/models/session_stopping_data.py +146 -0
  630. anyscale/client/openapi_client/models/sessioncommand_list_response.py +147 -0
  631. anyscale/client/openapi_client/models/sessioncommandid_response.py +121 -0
  632. anyscale/client/openapi_client/models/sessiondescribe_response.py +121 -0
  633. anyscale/client/openapi_client/models/sessiondetails_response.py +121 -0
  634. anyscale/client/openapi_client/models/sessionevent_list_response.py +147 -0
  635. anyscale/client/openapi_client/models/sessionhistoryitem_list_response.py +147 -0
  636. anyscale/client/openapi_client/models/sessions_sort_field.py +104 -0
  637. anyscale/client/openapi_client/models/sessionsshkey_response.py +121 -0
  638. anyscale/client/openapi_client/models/setup_initialize_session_options.py +225 -0
  639. anyscale/client/openapi_client/models/show_otp_source_return_api_model.py +121 -0
  640. anyscale/client/openapi_client/models/showotpsourcereturnapimodel_response.py +121 -0
  641. anyscale/client/openapi_client/models/snapshot_create_message.py +148 -0
  642. anyscale/client/openapi_client/models/snapshot_delete_message.py +148 -0
  643. anyscale/client/openapi_client/models/snapshot_patch_message.py +148 -0
  644. anyscale/client/openapi_client/models/socket_message_schemas.py +499 -0
  645. anyscale/client/openapi_client/models/socket_message_types.py +113 -0
  646. anyscale/client/openapi_client/models/socketmessageschemas_response.py +121 -0
  647. anyscale/client/openapi_client/models/socketmessagetypes_response.py +121 -0
  648. anyscale/client/openapi_client/models/sort_order.py +100 -0
  649. anyscale/client/openapi_client/models/sso_login_info.py +151 -0
  650. anyscale/client/openapi_client/models/ssologininfo_response.py +121 -0
  651. anyscale/client/openapi_client/models/start_session_options.py +146 -0
  652. anyscale/client/openapi_client/models/stop_session_options.py +227 -0
  653. anyscale/client/openapi_client/models/stream_publish_request.py +239 -0
  654. anyscale/client/openapi_client/models/subnet_id_with_availability_zone_aws.py +148 -0
  655. anyscale/client/openapi_client/models/support_requests_query.py +178 -0
  656. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +1570 -0
  657. anyscale/client/openapi_client/models/templatized_compute_configs.py +202 -0
  658. anyscale/client/openapi_client/models/templatized_decorated_application_templates.py +202 -0
  659. anyscale/client/openapi_client/models/templatizedcomputeconfigs_response.py +121 -0
  660. anyscale/client/openapi_client/models/templatizeddecoratedapplicationtemplates_response.py +121 -0
  661. anyscale/client/openapi_client/models/text_query.py +178 -0
  662. anyscale/client/openapi_client/models/timestamped_logs_output.py +148 -0
  663. anyscale/client/openapi_client/models/timestampedlogsoutput_response.py +121 -0
  664. anyscale/client/openapi_client/models/tool.py +100 -0
  665. anyscale/client/openapi_client/models/tracing_config.py +178 -0
  666. anyscale/client/openapi_client/models/try_login_email_response.py +208 -0
  667. anyscale/client/openapi_client/models/tryloginemailresponse_response.py +121 -0
  668. anyscale/client/openapi_client/models/unified_job_sort_field.py +103 -0
  669. anyscale/client/openapi_client/models/unified_job_status.py +114 -0
  670. anyscale/client/openapi_client/models/unified_job_type.py +100 -0
  671. anyscale/client/openapi_client/models/update_cloud_with_cloud_resource.py +178 -0
  672. anyscale/client/openapi_client/models/update_cloud_with_cloud_resource_gcp.py +178 -0
  673. anyscale/client/openapi_client/models/update_cluster_dns.py +152 -0
  674. anyscale/client/openapi_client/models/update_compute_template.py +146 -0
  675. anyscale/client/openapi_client/models/update_compute_template_config.py +464 -0
  676. anyscale/client/openapi_client/models/update_endpoint.py +152 -0
  677. anyscale/client/openapi_client/models/update_machine_pool_request.py +151 -0
  678. anyscale/client/openapi_client/models/update_model_deployment.py +152 -0
  679. anyscale/client/openapi_client/models/update_organization_collaborator.py +121 -0
  680. anyscale/client/openapi_client/models/update_project_collaborator.py +121 -0
  681. anyscale/client/openapi_client/models/update_resource_quota.py +122 -0
  682. anyscale/client/openapi_client/models/updatemachinepoolresponse_response.py +121 -0
  683. anyscale/client/openapi_client/models/upload_session_command_logs_locations.py +148 -0
  684. anyscale/client/openapi_client/models/uploadsessioncommandlogslocations_response.py +121 -0
  685. anyscale/client/openapi_client/models/user_info.py +569 -0
  686. anyscale/client/openapi_client/models/user_resend_email_options.py +147 -0
  687. anyscale/client/openapi_client/models/user_service_access_types.py +100 -0
  688. anyscale/client/openapi_client/models/userinfo_response.py +121 -0
  689. anyscale/client/openapi_client/models/utm_fields.py +224 -0
  690. anyscale/client/openapi_client/models/ux_instance.py +468 -0
  691. anyscale/client/openapi_client/models/validate_otp_params_api_model.py +121 -0
  692. anyscale/client/openapi_client/models/validation_error.py +175 -0
  693. anyscale/client/openapi_client/models/verify_response.py +147 -0
  694. anyscale/client/openapi_client/models/verifyresponse_response.py +121 -0
  695. anyscale/client/openapi_client/models/visibility.py +100 -0
  696. anyscale/client/openapi_client/models/waitlist_status_response.py +121 -0
  697. anyscale/client/openapi_client/models/waitlist_status_type.py +100 -0
  698. anyscale/client/openapi_client/models/waitliststatusresponse_response.py +121 -0
  699. anyscale/client/openapi_client/models/wand_b_run_details.py +147 -0
  700. anyscale/client/openapi_client/models/web_terminal.py +121 -0
  701. anyscale/client/openapi_client/models/webterminal_list_response.py +147 -0
  702. anyscale/client/openapi_client/models/webterminal_response.py +121 -0
  703. anyscale/client/openapi_client/models/worker_node_type.py +404 -0
  704. anyscale/client/openapi_client/models/workload_type.py +102 -0
  705. anyscale/client/openapi_client/models/workspace_dataplane_artifact.py +181 -0
  706. anyscale/client/openapi_client/models/workspace_dataplane_artifacts.py +123 -0
  707. anyscale/client/openapi_client/models/workspace_dataplane_proxied_artifacts.py +178 -0
  708. anyscale/client/openapi_client/models/workspace_event.py +325 -0
  709. anyscale/client/openapi_client/models/workspace_event_source.py +100 -0
  710. anyscale/client/openapi_client/models/workspace_event_source_filter.py +101 -0
  711. anyscale/client/openapi_client/models/workspace_readme.py +123 -0
  712. anyscale/client/openapi_client/models/workspace_snapshot_states.py +108 -0
  713. anyscale/client/openapi_client/models/workspace_template.py +353 -0
  714. anyscale/client/openapi_client/models/workspace_template_cluster_environment_metadata.py +178 -0
  715. anyscale/client/openapi_client/models/workspacedataplaneartifacts_response.py +121 -0
  716. anyscale/client/openapi_client/models/workspacedataplaneproxiedartifacts_response.py +121 -0
  717. anyscale/client/openapi_client/models/workspaceevent_list_response.py +147 -0
  718. anyscale/client/openapi_client/models/workspacereadme_response.py +121 -0
  719. anyscale/client/openapi_client/models/workspacetemplate_list_response.py +147 -0
  720. anyscale/client/openapi_client/models/workspacetemplateclusterenvironmentmetadata_response.py +121 -0
  721. anyscale/client/openapi_client/models/write_cloud.py +546 -0
  722. anyscale/client/openapi_client/models/write_cluster_config.py +123 -0
  723. anyscale/client/openapi_client/models/write_project.py +226 -0
  724. anyscale/client/openapi_client/models/write_session.py +147 -0
  725. anyscale/client/openapi_client/models/write_support_request.py +121 -0
  726. anyscale/client/openapi_client/rest.py +296 -0
  727. anyscale/client/requirements.txt +6 -0
  728. anyscale/client/setup.cfg +2 -0
  729. anyscale/client/setup.py +40 -0
  730. anyscale/client/test-requirements.txt +3 -0
  731. anyscale/client/tox.ini +9 -0
  732. anyscale/cloud.py +216 -0
  733. anyscale/cloud_resource.py +1032 -0
  734. anyscale/cluster.py +138 -0
  735. anyscale/cluster_compute.py +167 -0
  736. anyscale/cluster_env.py +173 -0
  737. anyscale/commands/__init__.py +0 -0
  738. anyscale/commands/aggregated_instance_usage_commands.py +86 -0
  739. anyscale/commands/anyscale_api/__init__.py +0 -0
  740. anyscale/commands/anyscale_api/api_commands.py +23 -0
  741. anyscale/commands/anyscale_api/session_commands_commands.py +80 -0
  742. anyscale/commands/anyscale_api/session_operations_commands.py +28 -0
  743. anyscale/commands/anyscale_api/sessions_commands.py +152 -0
  744. anyscale/commands/auth_commands.py +41 -0
  745. anyscale/commands/cloud_commands.py +1011 -0
  746. anyscale/commands/cloud_commands_util.py +10 -0
  747. anyscale/commands/cluster_commands.py +476 -0
  748. anyscale/commands/cluster_env_commands.py +139 -0
  749. anyscale/commands/command_examples.py +495 -0
  750. anyscale/commands/compute_config_commands.py +252 -0
  751. anyscale/commands/config_commands.py +213 -0
  752. anyscale/commands/exec_commands.py +14 -0
  753. anyscale/commands/experimental_integrations_commands.py +70 -0
  754. anyscale/commands/image_commands.py +125 -0
  755. anyscale/commands/job_commands.py +745 -0
  756. anyscale/commands/list_commands.py +85 -0
  757. anyscale/commands/llm/dataset_commands.py +269 -0
  758. anyscale/commands/llm/group.py +15 -0
  759. anyscale/commands/llm/models_commands.py +123 -0
  760. anyscale/commands/login_commands.py +79 -0
  761. anyscale/commands/logs_commands.py +312 -0
  762. anyscale/commands/machine_commands.py +116 -0
  763. anyscale/commands/machine_pool_commands.py +163 -0
  764. anyscale/commands/migrate_commands.py +84 -0
  765. anyscale/commands/project_commands.py +203 -0
  766. anyscale/commands/resource_quota_commands.py +214 -0
  767. anyscale/commands/schedule_commands.py +436 -0
  768. anyscale/commands/service_account_commands.py +72 -0
  769. anyscale/commands/service_commands.py +738 -0
  770. anyscale/commands/session_commands_hidden.py +179 -0
  771. anyscale/commands/util.py +152 -0
  772. anyscale/commands/workspace_commands.py +511 -0
  773. anyscale/commands/workspace_commands_v2.py +874 -0
  774. anyscale/component_activity_util.py +83 -0
  775. anyscale/compute_config/__init__.py +84 -0
  776. anyscale/compute_config/_private/compute_config_sdk.py +433 -0
  777. anyscale/compute_config/commands.py +122 -0
  778. anyscale/compute_config/models.py +630 -0
  779. anyscale/conf.py +23 -0
  780. anyscale/connect.py +1323 -0
  781. anyscale/connect_utils/__init__.py +0 -0
  782. anyscale/connect_utils/prepare_cluster.py +962 -0
  783. anyscale/connect_utils/project.py +298 -0
  784. anyscale/connect_utils/start_interactive_session.py +437 -0
  785. anyscale/controllers/__init__.py +0 -0
  786. anyscale/controllers/auth_controller.py +134 -0
  787. anyscale/controllers/base_controller.py +52 -0
  788. anyscale/controllers/cloud_controller.py +3609 -0
  789. anyscale/controllers/cloud_functional_verification_controller.py +858 -0
  790. anyscale/controllers/cluster_controller.py +720 -0
  791. anyscale/controllers/cluster_env_controller.py +219 -0
  792. anyscale/controllers/compute_config_controller.py +351 -0
  793. anyscale/controllers/config_controller.py +422 -0
  794. anyscale/controllers/experimental_integrations_controller.py +80 -0
  795. anyscale/controllers/job_controller.py +647 -0
  796. anyscale/controllers/jobs_bg_controller.py +0 -0
  797. anyscale/controllers/list_controller.py +290 -0
  798. anyscale/controllers/llm/__init__.py +0 -0
  799. anyscale/controllers/llm/models_controller.py +144 -0
  800. anyscale/controllers/logs_controller.py +449 -0
  801. anyscale/controllers/machine_controller.py +37 -0
  802. anyscale/controllers/machine_pool_controller.py +86 -0
  803. anyscale/controllers/project_controller.py +281 -0
  804. anyscale/controllers/resource_quota_controller.py +183 -0
  805. anyscale/controllers/schedule_controller.py +333 -0
  806. anyscale/controllers/service_account_controller.py +168 -0
  807. anyscale/controllers/service_controller.py +453 -0
  808. anyscale/controllers/workspace_controller.py +253 -0
  809. anyscale/feature_flags.py +4 -0
  810. anyscale/fingerprint.py +62 -0
  811. anyscale/formatters/__init__.py +0 -0
  812. anyscale/formatters/clouds_formatter.py +65 -0
  813. anyscale/formatters/common_formatter.py +22 -0
  814. anyscale/gcp_verification.py +792 -0
  815. anyscale/image/__init__.py +73 -0
  816. anyscale/image/_private/image_sdk.py +202 -0
  817. anyscale/image/commands.py +117 -0
  818. anyscale/image/models.py +57 -0
  819. anyscale/integrations.py +329 -0
  820. anyscale/job/__init__.py +166 -0
  821. anyscale/job/_private/job_sdk.py +497 -0
  822. anyscale/job/commands.py +267 -0
  823. anyscale/job/models.py +500 -0
  824. anyscale/links.py +4 -0
  825. anyscale/llm/__init__.py +2 -0
  826. anyscale/llm/dataset/__init__.py +2 -0
  827. anyscale/llm/dataset/_private/__init__.py +0 -0
  828. anyscale/llm/dataset/_private/docs.py +63 -0
  829. anyscale/llm/dataset/_private/models.py +71 -0
  830. anyscale/llm/dataset/_private/sdk.py +147 -0
  831. anyscale/llm/model/__init__.py +2 -0
  832. anyscale/llm/model/_private/models_sdk.py +62 -0
  833. anyscale/llm/model/commands.py +93 -0
  834. anyscale/llm/model/models.py +171 -0
  835. anyscale/llm/model/sdk.py +62 -0
  836. anyscale/llm/sdk.py +27 -0
  837. anyscale/memorydb_supported_zones.json +22 -0
  838. anyscale/models/job_model.py +457 -0
  839. anyscale/models/service_model.py +125 -0
  840. anyscale/project.py +501 -0
  841. anyscale/schedule/__init__.py +91 -0
  842. anyscale/schedule/_private/schedule_sdk.py +165 -0
  843. anyscale/schedule/commands.py +149 -0
  844. anyscale/schedule/models.py +145 -0
  845. anyscale/scripts.py +164 -0
  846. anyscale/sdk/anyscale_client/__init__.py +235 -0
  847. anyscale/sdk/anyscale_client/api/__init__.py +6 -0
  848. anyscale/sdk/anyscale_client/api/default_api.py +11625 -0
  849. anyscale/sdk/anyscale_client/api_client.py +647 -0
  850. anyscale/sdk/anyscale_client/configuration.py +373 -0
  851. anyscale/sdk/anyscale_client/exceptions.py +120 -0
  852. anyscale/sdk/anyscale_client/models/__init__.py +220 -0
  853. anyscale/sdk/anyscale_client/models/access_config.py +122 -0
  854. anyscale/sdk/anyscale_client/models/app_config.py +436 -0
  855. anyscale/sdk/anyscale_client/models/app_config_config_schema.py +235 -0
  856. anyscale/sdk/anyscale_client/models/appconfig_list_response.py +147 -0
  857. anyscale/sdk/anyscale_client/models/appconfig_response.py +121 -0
  858. anyscale/sdk/anyscale_client/models/apply_production_service_v2_model.py +490 -0
  859. anyscale/sdk/anyscale_client/models/apply_service_model.py +490 -0
  860. anyscale/sdk/anyscale_client/models/archive_status.py +101 -0
  861. anyscale/sdk/anyscale_client/models/base_job_status.py +105 -0
  862. anyscale/sdk/anyscale_client/models/baseimagesenum.py +2130 -0
  863. anyscale/sdk/anyscale_client/models/build.py +607 -0
  864. anyscale/sdk/anyscale_client/models/build_list_response.py +147 -0
  865. anyscale/sdk/anyscale_client/models/build_log_response.py +123 -0
  866. anyscale/sdk/anyscale_client/models/build_response.py +121 -0
  867. anyscale/sdk/anyscale_client/models/build_status.py +104 -0
  868. anyscale/sdk/anyscale_client/models/buildlogresponse_response.py +121 -0
  869. anyscale/sdk/anyscale_client/models/cloud.py +802 -0
  870. anyscale/sdk/anyscale_client/models/cloud_config.py +206 -0
  871. anyscale/sdk/anyscale_client/models/cloud_list_response.py +147 -0
  872. anyscale/sdk/anyscale_client/models/cloud_providers.py +103 -0
  873. anyscale/sdk/anyscale_client/models/cloud_response.py +121 -0
  874. anyscale/sdk/anyscale_client/models/cloud_state.py +104 -0
  875. anyscale/sdk/anyscale_client/models/cloud_status.py +100 -0
  876. anyscale/sdk/anyscale_client/models/cloud_type.py +100 -0
  877. anyscale/sdk/anyscale_client/models/cloud_types.py +100 -0
  878. anyscale/sdk/anyscale_client/models/cloud_version.py +100 -0
  879. anyscale/sdk/anyscale_client/models/clouds_query.py +150 -0
  880. anyscale/sdk/anyscale_client/models/cluster.py +721 -0
  881. anyscale/sdk/anyscale_client/models/cluster_compute.py +415 -0
  882. anyscale/sdk/anyscale_client/models/cluster_compute_config.py +461 -0
  883. anyscale/sdk/anyscale_client/models/cluster_computes_query.py +293 -0
  884. anyscale/sdk/anyscale_client/models/cluster_environment.py +380 -0
  885. anyscale/sdk/anyscale_client/models/cluster_environment_build.py +578 -0
  886. anyscale/sdk/anyscale_client/models/cluster_environment_build_log_response.py +123 -0
  887. anyscale/sdk/anyscale_client/models/cluster_environment_build_operation.py +237 -0
  888. anyscale/sdk/anyscale_client/models/cluster_environment_build_status.py +104 -0
  889. anyscale/sdk/anyscale_client/models/cluster_environments_query.py +290 -0
  890. anyscale/sdk/anyscale_client/models/cluster_head_node_info.py +152 -0
  891. anyscale/sdk/anyscale_client/models/cluster_list_response.py +147 -0
  892. anyscale/sdk/anyscale_client/models/cluster_management_stack_versions.py +100 -0
  893. anyscale/sdk/anyscale_client/models/cluster_operation.py +266 -0
  894. anyscale/sdk/anyscale_client/models/cluster_operation_type.py +101 -0
  895. anyscale/sdk/anyscale_client/models/cluster_response.py +121 -0
  896. anyscale/sdk/anyscale_client/models/cluster_services_urls.py +430 -0
  897. anyscale/sdk/anyscale_client/models/cluster_state.py +108 -0
  898. anyscale/sdk/anyscale_client/models/cluster_status.py +104 -0
  899. anyscale/sdk/anyscale_client/models/cluster_status_details.py +100 -0
  900. anyscale/sdk/anyscale_client/models/clustercompute_list_response.py +147 -0
  901. anyscale/sdk/anyscale_client/models/clustercompute_response.py +121 -0
  902. anyscale/sdk/anyscale_client/models/clusterenvironment_list_response.py +147 -0
  903. anyscale/sdk/anyscale_client/models/clusterenvironment_response.py +121 -0
  904. anyscale/sdk/anyscale_client/models/clusterenvironmentbuild_list_response.py +147 -0
  905. anyscale/sdk/anyscale_client/models/clusterenvironmentbuild_response.py +121 -0
  906. anyscale/sdk/anyscale_client/models/clusterenvironmentbuildlogresponse_response.py +121 -0
  907. anyscale/sdk/anyscale_client/models/clusterenvironmentbuildoperation_response.py +121 -0
  908. anyscale/sdk/anyscale_client/models/clusteroperation_response.py +121 -0
  909. anyscale/sdk/anyscale_client/models/clusters_query.py +234 -0
  910. anyscale/sdk/anyscale_client/models/compute_node_type.py +292 -0
  911. anyscale/sdk/anyscale_client/models/compute_stack.py +100 -0
  912. anyscale/sdk/anyscale_client/models/compute_template.py +415 -0
  913. anyscale/sdk/anyscale_client/models/compute_template_config.py +461 -0
  914. anyscale/sdk/anyscale_client/models/compute_template_query.py +316 -0
  915. anyscale/sdk/anyscale_client/models/computetemplate_list_response.py +147 -0
  916. anyscale/sdk/anyscale_client/models/computetemplate_response.py +121 -0
  917. anyscale/sdk/anyscale_client/models/computetemplateconfig_response.py +121 -0
  918. anyscale/sdk/anyscale_client/models/create_app_config.py +235 -0
  919. anyscale/sdk/anyscale_client/models/create_app_config_configuration_schema.py +235 -0
  920. anyscale/sdk/anyscale_client/models/create_build.py +263 -0
  921. anyscale/sdk/anyscale_client/models/create_byod_app_config_configuration_schema.py +206 -0
  922. anyscale/sdk/anyscale_client/models/create_byod_cluster_environment.py +180 -0
  923. anyscale/sdk/anyscale_client/models/create_byod_cluster_environment_build.py +152 -0
  924. anyscale/sdk/anyscale_client/models/create_byod_cluster_environment_configuration_schema.py +208 -0
  925. anyscale/sdk/anyscale_client/models/create_cloud.py +518 -0
  926. anyscale/sdk/anyscale_client/models/create_cluster.py +376 -0
  927. anyscale/sdk/anyscale_client/models/create_cluster_compute.py +229 -0
  928. anyscale/sdk/anyscale_client/models/create_cluster_compute_config.py +463 -0
  929. anyscale/sdk/anyscale_client/models/create_cluster_environment.py +235 -0
  930. anyscale/sdk/anyscale_client/models/create_cluster_environment_build.py +263 -0
  931. anyscale/sdk/anyscale_client/models/create_cluster_environment_configuration_schema.py +235 -0
  932. anyscale/sdk/anyscale_client/models/create_compute_template.py +229 -0
  933. anyscale/sdk/anyscale_client/models/create_compute_template_config.py +464 -0
  934. anyscale/sdk/anyscale_client/models/create_job_queue_config.py +206 -0
  935. anyscale/sdk/anyscale_client/models/create_production_job.py +234 -0
  936. anyscale/sdk/anyscale_client/models/create_production_job_config.py +347 -0
  937. anyscale/sdk/anyscale_client/models/create_project.py +207 -0
  938. anyscale/sdk/anyscale_client/models/create_schedule.py +263 -0
  939. anyscale/sdk/anyscale_client/models/create_session.py +432 -0
  940. anyscale/sdk/anyscale_client/models/create_session_command.py +152 -0
  941. anyscale/sdk/anyscale_client/models/create_sso_config.py +150 -0
  942. anyscale/sdk/anyscale_client/models/grpc_protocol_config.py +178 -0
  943. anyscale/sdk/anyscale_client/models/ha_job_goal_states.py +102 -0
  944. anyscale/sdk/anyscale_client/models/ha_job_states.py +109 -0
  945. anyscale/sdk/anyscale_client/models/http_protocol_config.py +150 -0
  946. anyscale/sdk/anyscale_client/models/http_validation_error.py +120 -0
  947. anyscale/sdk/anyscale_client/models/idle_termination_status.py +104 -0
  948. anyscale/sdk/anyscale_client/models/job.py +466 -0
  949. anyscale/sdk/anyscale_client/models/job_list_response.py +147 -0
  950. anyscale/sdk/anyscale_client/models/job_queue_config.py +122 -0
  951. anyscale/sdk/anyscale_client/models/job_queue_execution_mode.py +101 -0
  952. anyscale/sdk/anyscale_client/models/job_queue_spec.py +263 -0
  953. anyscale/sdk/anyscale_client/models/job_run_type.py +101 -0
  954. anyscale/sdk/anyscale_client/models/job_status.py +105 -0
  955. anyscale/sdk/anyscale_client/models/jobs_query.py +458 -0
  956. anyscale/sdk/anyscale_client/models/jobs_sort_field.py +104 -0
  957. anyscale/sdk/anyscale_client/models/list_response_metadata.py +146 -0
  958. anyscale/sdk/anyscale_client/models/list_service_model.py +347 -0
  959. anyscale/sdk/anyscale_client/models/listservicemodel_list_response.py +147 -0
  960. anyscale/sdk/anyscale_client/models/log_download_result.py +207 -0
  961. anyscale/sdk/anyscale_client/models/log_file_chunk.py +439 -0
  962. anyscale/sdk/anyscale_client/models/log_level_types.py +100 -0
  963. anyscale/sdk/anyscale_client/models/log_stream.py +151 -0
  964. anyscale/sdk/anyscale_client/models/logdownloadresult_response.py +121 -0
  965. anyscale/sdk/anyscale_client/models/logstream_response.py +121 -0
  966. anyscale/sdk/anyscale_client/models/node_type.py +100 -0
  967. anyscale/sdk/anyscale_client/models/object_storage_config.py +122 -0
  968. anyscale/sdk/anyscale_client/models/object_storage_config_s3.py +256 -0
  969. anyscale/sdk/anyscale_client/models/objectstorageconfig_response.py +121 -0
  970. anyscale/sdk/anyscale_client/models/operation_error.py +123 -0
  971. anyscale/sdk/anyscale_client/models/operation_progress.py +123 -0
  972. anyscale/sdk/anyscale_client/models/operation_result.py +150 -0
  973. anyscale/sdk/anyscale_client/models/organization.py +209 -0
  974. anyscale/sdk/anyscale_client/models/organization_response.py +121 -0
  975. anyscale/sdk/anyscale_client/models/page_query.py +153 -0
  976. anyscale/sdk/anyscale_client/models/pause_schedule.py +123 -0
  977. anyscale/sdk/anyscale_client/models/production_job.py +437 -0
  978. anyscale/sdk/anyscale_client/models/production_job_config.py +348 -0
  979. anyscale/sdk/anyscale_client/models/production_job_state_transition.py +293 -0
  980. anyscale/sdk/anyscale_client/models/production_service_v2_model.py +612 -0
  981. anyscale/sdk/anyscale_client/models/production_service_v2_version_model.py +437 -0
  982. anyscale/sdk/anyscale_client/models/productionjob_list_response.py +147 -0
  983. anyscale/sdk/anyscale_client/models/productionjob_response.py +121 -0
  984. anyscale/sdk/anyscale_client/models/productionservicev2_model_response.py +121 -0
  985. anyscale/sdk/anyscale_client/models/project.py +467 -0
  986. anyscale/sdk/anyscale_client/models/project_list_response.py +147 -0
  987. anyscale/sdk/anyscale_client/models/project_response.py +121 -0
  988. anyscale/sdk/anyscale_client/models/projects_query.py +234 -0
  989. anyscale/sdk/anyscale_client/models/protocols.py +150 -0
  990. anyscale/sdk/anyscale_client/models/python_modules.py +150 -0
  991. anyscale/sdk/anyscale_client/models/python_version.py +105 -0
  992. anyscale/sdk/anyscale_client/models/ray_gcs_external_storage_config.py +178 -0
  993. anyscale/sdk/anyscale_client/models/ray_runtime_env_config.py +262 -0
  994. anyscale/sdk/anyscale_client/models/resources.py +234 -0
  995. anyscale/sdk/anyscale_client/models/rollback_service_model.py +122 -0
  996. anyscale/sdk/anyscale_client/models/rollout_strategy.py +100 -0
  997. anyscale/sdk/anyscale_client/models/runtime_environment.py +406 -0
  998. anyscale/sdk/anyscale_client/models/runtimeenvironment_response.py +121 -0
  999. anyscale/sdk/anyscale_client/models/schedule_api_model.py +467 -0
  1000. anyscale/sdk/anyscale_client/models/schedule_config.py +151 -0
  1001. anyscale/sdk/anyscale_client/models/scheduleapimodel_list_response.py +147 -0
  1002. anyscale/sdk/anyscale_client/models/scheduleapimodel_response.py +121 -0
  1003. anyscale/sdk/anyscale_client/models/service_config.py +178 -0
  1004. anyscale/sdk/anyscale_client/models/service_event_current_state.py +108 -0
  1005. anyscale/sdk/anyscale_client/models/service_goal_states.py +100 -0
  1006. anyscale/sdk/anyscale_client/models/service_model.py +612 -0
  1007. anyscale/sdk/anyscale_client/models/service_observability_urls.py +206 -0
  1008. anyscale/sdk/anyscale_client/models/service_sort_field.py +101 -0
  1009. anyscale/sdk/anyscale_client/models/service_type.py +100 -0
  1010. anyscale/sdk/anyscale_client/models/service_version_state.py +106 -0
  1011. anyscale/sdk/anyscale_client/models/servicemodel_list_response.py +147 -0
  1012. anyscale/sdk/anyscale_client/models/servicemodel_response.py +121 -0
  1013. anyscale/sdk/anyscale_client/models/session.py +1535 -0
  1014. anyscale/sdk/anyscale_client/models/session_command.py +350 -0
  1015. anyscale/sdk/anyscale_client/models/session_command_types.py +100 -0
  1016. anyscale/sdk/anyscale_client/models/session_event.py +267 -0
  1017. anyscale/sdk/anyscale_client/models/session_event_cause.py +150 -0
  1018. anyscale/sdk/anyscale_client/models/session_event_types.py +111 -0
  1019. anyscale/sdk/anyscale_client/models/session_list_response.py +147 -0
  1020. anyscale/sdk/anyscale_client/models/session_operation.py +266 -0
  1021. anyscale/sdk/anyscale_client/models/session_operation_type.py +101 -0
  1022. anyscale/sdk/anyscale_client/models/session_response.py +121 -0
  1023. anyscale/sdk/anyscale_client/models/session_starting_up_data.py +146 -0
  1024. anyscale/sdk/anyscale_client/models/session_state.py +111 -0
  1025. anyscale/sdk/anyscale_client/models/session_state_data.py +146 -0
  1026. anyscale/sdk/anyscale_client/models/session_stopping_data.py +146 -0
  1027. anyscale/sdk/anyscale_client/models/sessioncommand_list_response.py +147 -0
  1028. anyscale/sdk/anyscale_client/models/sessioncommand_response.py +121 -0
  1029. anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +147 -0
  1030. anyscale/sdk/anyscale_client/models/sessionoperation_response.py +121 -0
  1031. anyscale/sdk/anyscale_client/models/sessions_query.py +206 -0
  1032. anyscale/sdk/anyscale_client/models/sort_by_clause_jobs_sort_field.py +148 -0
  1033. anyscale/sdk/anyscale_client/models/sort_order.py +100 -0
  1034. anyscale/sdk/anyscale_client/models/sso_config.py +237 -0
  1035. anyscale/sdk/anyscale_client/models/sso_mode.py +101 -0
  1036. anyscale/sdk/anyscale_client/models/ssoconfig_response.py +121 -0
  1037. anyscale/sdk/anyscale_client/models/start_cluster_options.py +178 -0
  1038. anyscale/sdk/anyscale_client/models/start_session_options.py +206 -0
  1039. anyscale/sdk/anyscale_client/models/static_sso_config.py +210 -0
  1040. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +1570 -0
  1041. anyscale/sdk/anyscale_client/models/terminate_cluster_options.py +122 -0
  1042. anyscale/sdk/anyscale_client/models/terminate_session_options.py +206 -0
  1043. anyscale/sdk/anyscale_client/models/text_query.py +178 -0
  1044. anyscale/sdk/anyscale_client/models/tracing_config.py +178 -0
  1045. anyscale/sdk/anyscale_client/models/update_app_config.py +122 -0
  1046. anyscale/sdk/anyscale_client/models/update_cloud.py +150 -0
  1047. anyscale/sdk/anyscale_client/models/update_cluster.py +206 -0
  1048. anyscale/sdk/anyscale_client/models/update_compute_template.py +146 -0
  1049. anyscale/sdk/anyscale_client/models/update_compute_template_config.py +464 -0
  1050. anyscale/sdk/anyscale_client/models/update_organization.py +123 -0
  1051. anyscale/sdk/anyscale_client/models/update_project.py +150 -0
  1052. anyscale/sdk/anyscale_client/models/update_session.py +150 -0
  1053. anyscale/sdk/anyscale_client/models/user_service_access_types.py +100 -0
  1054. anyscale/sdk/anyscale_client/models/ux_instance.py +468 -0
  1055. anyscale/sdk/anyscale_client/models/validation_error.py +175 -0
  1056. anyscale/sdk/anyscale_client/models/worker_node_type.py +404 -0
  1057. anyscale/sdk/anyscale_client/rest.py +296 -0
  1058. anyscale/sdk/anyscale_client/sdk.py +634 -0
  1059. anyscale/service/__init__.py +168 -0
  1060. anyscale/service/_private/service_sdk.py +702 -0
  1061. anyscale/service/commands.py +261 -0
  1062. anyscale/service/models.py +671 -0
  1063. anyscale/shared_anyscale_utils/__init__.py +1 -0
  1064. anyscale/shared_anyscale_utils/aws.py +153 -0
  1065. anyscale/shared_anyscale_utils/bytes_util.py +10 -0
  1066. anyscale/shared_anyscale_utils/conf.py +47 -0
  1067. anyscale/shared_anyscale_utils/default_anyscale_aws.yaml +74 -0
  1068. anyscale/shared_anyscale_utils/default_anyscale_gcp.yaml +80 -0
  1069. anyscale/shared_anyscale_utils/headers.py +38 -0
  1070. anyscale/shared_anyscale_utils/latest_ray_version.py +2 -0
  1071. anyscale/shared_anyscale_utils/project.py +15 -0
  1072. anyscale/shared_anyscale_utils/test_util.py +22 -0
  1073. anyscale/shared_anyscale_utils/tests/__init__.py +1 -0
  1074. anyscale/shared_anyscale_utils/tests/test_asyncio.py +41 -0
  1075. anyscale/shared_anyscale_utils/tests/test_ray_semver.py +63 -0
  1076. anyscale/shared_anyscale_utils/util.py +50 -0
  1077. anyscale/shared_anyscale_utils/utils/__init__.py +2 -0
  1078. anyscale/shared_anyscale_utils/utils/asyncio.py +120 -0
  1079. anyscale/shared_anyscale_utils/utils/byod.py +40 -0
  1080. anyscale/shared_anyscale_utils/utils/collections.py +33 -0
  1081. anyscale/shared_anyscale_utils/utils/id_gen.py +147 -0
  1082. anyscale/shared_anyscale_utils/utils/protected_string.py +89 -0
  1083. anyscale/shared_anyscale_utils/utils/ray_semver.py +81 -0
  1084. anyscale/snapshot.py +46 -0
  1085. anyscale/tables.py +82 -0
  1086. anyscale/util.py +1155 -0
  1087. anyscale/utils/__init__.py +0 -0
  1088. anyscale/utils/cli_version_check_util.py +63 -0
  1089. anyscale/utils/cloud_update_utils.py +862 -0
  1090. anyscale/utils/cloud_utils.py +317 -0
  1091. anyscale/utils/cluster_debug.py +191 -0
  1092. anyscale/utils/connect_helpers.py +155 -0
  1093. anyscale/utils/deprecation_util.py +32 -0
  1094. anyscale/utils/entity_arg_utils.py +43 -0
  1095. anyscale/utils/env_utils.py +17 -0
  1096. anyscale/utils/gcp_managed_setup_utils.py +888 -0
  1097. anyscale/utils/gcp_utils.py +312 -0
  1098. anyscale/utils/imports/__init__.py +0 -0
  1099. anyscale/utils/imports/all.py +13 -0
  1100. anyscale/utils/imports/azure.py +14 -0
  1101. anyscale/utils/imports/gcp.py +59 -0
  1102. anyscale/utils/logs_utils.py +141 -0
  1103. anyscale/utils/name_utils.py +33 -0
  1104. anyscale/utils/network_verification.py +153 -0
  1105. anyscale/utils/ray_utils.py +128 -0
  1106. anyscale/utils/ray_version_checker.py +48 -0
  1107. anyscale/utils/ray_version_utils.py +53 -0
  1108. anyscale/utils/runtime_env.py +487 -0
  1109. anyscale/utils/s3.py +92 -0
  1110. anyscale/utils/user_utils.py +17 -0
  1111. anyscale/utils/workload_types.py +7 -0
  1112. anyscale/utils/workspace_notification.py +39 -0
  1113. anyscale/utils/workspace_utils.py +65 -0
  1114. anyscale/version.py +1 -0
  1115. anyscale/webterminal/__init__.py +0 -0
  1116. anyscale/webterminal/bash-preexec.sh +370 -0
  1117. anyscale/webterminal/command_persister.py +164 -0
  1118. anyscale/webterminal/utils.py +176 -0
  1119. anyscale/webterminal/webterminal.py +311 -0
  1120. anyscale/workspace/__init__.py +270 -0
  1121. anyscale/workspace/_private/workspace_sdk.py +737 -0
  1122. anyscale/workspace/commands.py +472 -0
  1123. anyscale/workspace/models.py +296 -0
  1124. anyscale/workspace_utils.py +35 -0
  1125. anyscale-0.24.86.dist-info/LICENSE +68 -0
  1126. anyscale-0.24.86.dist-info/METADATA +82 -0
  1127. anyscale-0.24.86.dist-info/NOTICE +6 -0
  1128. anyscale-0.24.86.dist-info/RECORD +1131 -0
  1129. anyscale-0.24.86.dist-info/WHEEL +5 -0
  1130. anyscale-0.24.86.dist-info/entry_points.txt +2 -0
  1131. anyscale-0.24.86.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3609 @@
1
+ """
2
+ Fetches data required and formats output for `anyscale cloud` commands.
3
+ """
4
+
5
+ import copy
6
+ import json
7
+ from os import getenv
8
+ import re
9
+ import secrets
10
+ import time
11
+ from typing import Any, Dict, List, MutableSequence, Optional, Tuple
12
+ import uuid
13
+
14
+ import boto3
15
+ from botocore.exceptions import ClientError, NoCredentialsError
16
+ import click
17
+ from click import Abort, ClickException
18
+
19
+ from anyscale import __version__ as anyscale_version
20
+ from anyscale.aws_iam_policies import get_anyscale_iam_permissions_ec2_restricted
21
+ from anyscale.cli_logger import CloudSetupLogger
22
+ from anyscale.client.openapi_client.models import (
23
+ AWSMemoryDBClusterConfig,
24
+ CloudAnalyticsEventCloudResource,
25
+ CloudAnalyticsEventCommandName,
26
+ CloudAnalyticsEventName,
27
+ CloudProviders,
28
+ CloudState,
29
+ CloudWithCloudResource,
30
+ CloudWithCloudResourceGCP,
31
+ ClusterManagementStackVersions,
32
+ ComputeStack,
33
+ CreateCloudResource,
34
+ CreateCloudResourceGCP,
35
+ EditableCloudResource,
36
+ EditableCloudResourceGCP,
37
+ NFSMountTarget,
38
+ SubnetIdWithAvailabilityZoneAWS,
39
+ UpdateCloudWithCloudResource,
40
+ UpdateCloudWithCloudResourceGCP,
41
+ WriteCloud,
42
+ )
43
+ from anyscale.client.openapi_client.models.gcp_file_store_config import (
44
+ GCPFileStoreConfig,
45
+ )
46
+ from anyscale.cloud import (
47
+ get_cloud_id_and_name,
48
+ get_cloud_json_from_id,
49
+ get_cloud_resource_by_cloud_id,
50
+ get_organization_id,
51
+ )
52
+ from anyscale.cloud_resource import (
53
+ associate_aws_subnets_with_azs,
54
+ GCS_STORAGE_PREFIX,
55
+ S3_ARN_PREFIX,
56
+ S3_STORAGE_PREFIX,
57
+ verify_aws_cloudformation_stack,
58
+ verify_aws_efs,
59
+ verify_aws_iam_roles,
60
+ verify_aws_memorydb_cluster,
61
+ verify_aws_s3,
62
+ verify_aws_security_groups,
63
+ verify_aws_subnets,
64
+ verify_aws_vpc,
65
+ )
66
+ from anyscale.conf import ANYSCALE_IAM_ROLE_NAME
67
+ from anyscale.controllers.base_controller import BaseController
68
+ from anyscale.controllers.cloud_functional_verification_controller import (
69
+ CloudFunctionalVerificationController,
70
+ CloudFunctionalVerificationType,
71
+ )
72
+ from anyscale.formatters import clouds_formatter
73
+ from anyscale.shared_anyscale_utils.aws import AwsRoleArn
74
+ from anyscale.shared_anyscale_utils.conf import ANYSCALE_ENV
75
+ from anyscale.util import ( # pylint:disable=private-import
76
+ _client,
77
+ _get_aws_efs_mount_target_ip,
78
+ _get_memorydb_cluster_config,
79
+ _get_role,
80
+ _update_external_ids_for_policy,
81
+ confirm,
82
+ GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS_LONG,
83
+ get_anyscale_cross_account_iam_policies,
84
+ get_available_regions,
85
+ get_user_env_aws_account,
86
+ prepare_cloudformation_template,
87
+ REDIS_TLS_ADDRESS_PREFIX,
88
+ )
89
+ from anyscale.utils.cloud_update_utils import (
90
+ CLOUDFORMATION_TIMEOUT_SECONDS_LONG,
91
+ get_or_create_memorydb,
92
+ MEMORYDB_REDIS_PORT,
93
+ try_delete_customer_drifts_policy,
94
+ update_iam_role,
95
+ )
96
+ from anyscale.utils.cloud_utils import (
97
+ _unroll_resources_for_aws_list_call,
98
+ CloudEventProducer,
99
+ CloudSetupError,
100
+ get_errored_resources_and_reasons,
101
+ modify_memorydb_parameter_group,
102
+ validate_aws_credentials,
103
+ verify_anyscale_access,
104
+ wait_for_gcp_lb_resource_termination,
105
+ )
106
+ from anyscale.utils.imports.gcp import (
107
+ try_import_gcp_managed_setup_utils,
108
+ try_import_gcp_utils,
109
+ try_import_gcp_verify_lib,
110
+ )
111
+
112
+
113
+ ROLE_CREATION_RETRIES = 30
114
+ ROLE_CREATION_INTERVAL_SECONDS = 1
115
+
116
+ try:
117
+ CLOUDFORMATION_TIMEOUT_SECONDS = int(getenv("CLOUDFORMATION_TIMEOUT_SECONDS", 300))
118
+ except ValueError:
119
+ raise Exception(
120
+ f"CLOUDFORMATION_TIMEOUT_SECONDS is set to {getenv('CLOUDFORMATION_TIMEOUT_SECONDS')}, which is not a valid integer."
121
+ )
122
+
123
+
124
+ try:
125
+ GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS = int(
126
+ getenv("GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS", 600)
127
+ )
128
+ except ValueError:
129
+ raise Exception(
130
+ f"GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS is set to {getenv('GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS')}, which is not a valid integer."
131
+ )
132
+
133
+ IGNORE_CAPACITY_ERRORS = getenv("IGNORE_CAPACITY_ERRORS") is not None
134
+
135
+ # Constants forked from ray.autoscaler._private.aws.config
136
+ RAY = "ray-autoscaler"
137
+ DEFAULT_RAY_IAM_ROLE = RAY + "-v1"
138
+
139
+ # Only used in cloud edit.
140
+ BASE_ROLLBACK_COMMAND = "anyscale cloud edit --cloud-id={cloud_id}"
141
+
142
+
143
+ class CloudController(BaseController):
144
+ def __init__(
145
+ self,
146
+ log: Optional[CloudSetupLogger] = None,
147
+ initialize_auth_api_client: bool = True,
148
+ cli_token: Optional[str] = None,
149
+ ):
150
+ """
151
+ :param log: The logger to use for this controller. If not provided, a new logger will be created.
152
+ :param initialize_auth_api_client: Whether to initialize the auth api client.
153
+ :param cli_token: The CLI token to use for this controller. If provided, the CLI token will be used instead
154
+ of the one in the config file or the one in the environment variable. This is for setting up AIOA clouds only.
155
+ """
156
+ if log is None:
157
+ log = CloudSetupLogger()
158
+
159
+ super().__init__(
160
+ initialize_auth_api_client=initialize_auth_api_client, cli_token=cli_token,
161
+ )
162
+
163
+ self.log = log
164
+ self.log.open_block("Output")
165
+ if self.initialize_auth_api_client:
166
+ self.cloud_event_producer = CloudEventProducer(
167
+ cli_version=anyscale_version, api_client=self.api_client
168
+ )
169
+
170
+ def list_clouds(
171
+ self, cloud_name: Optional[str], cloud_id: Optional[str], max_items: int
172
+ ) -> str:
173
+ if cloud_id is not None:
174
+ clouds = [
175
+ self.api_client.get_cloud_api_v2_clouds_cloud_id_get(cloud_id).result
176
+ ]
177
+ elif cloud_name is not None:
178
+ clouds = [
179
+ self.api_client.find_cloud_by_name_api_v2_clouds_find_by_name_post(
180
+ {"name": cloud_name}
181
+ ).result
182
+ ]
183
+ else:
184
+ clouds = self.api_client.list_clouds_api_v2_clouds_get().results
185
+
186
+ clouds_output = clouds[:max_items]
187
+ output = clouds_formatter.format_clouds_output(
188
+ clouds=clouds_output, json_format=False
189
+ )
190
+ return str(output)
191
+
192
+ def _get_anyscale_cross_account_iam_policies(
193
+ self, cloud_id: str, _use_strict_iam_permissions: bool,
194
+ ) -> List[Dict[str, str]]:
195
+ iam_policy_parameters = get_anyscale_cross_account_iam_policies()
196
+ if _use_strict_iam_permissions:
197
+ anyscale_iam_permissions_ec2 = get_anyscale_iam_permissions_ec2_restricted(
198
+ cloud_id
199
+ )
200
+ for parameter in iam_policy_parameters:
201
+ if (
202
+ parameter["ParameterKey"]
203
+ == "AnyscaleCrossAccountIAMPolicySteadyState"
204
+ ):
205
+ parameter["ParameterValue"] = json.dumps(
206
+ anyscale_iam_permissions_ec2
207
+ )
208
+ break
209
+ return iam_policy_parameters
210
+
211
+ def create_cloudformation_stack( # noqa: PLR0913
212
+ self,
213
+ region: str,
214
+ cloud_id: str,
215
+ anyscale_iam_role_name: str,
216
+ cluster_node_iam_role_name: str,
217
+ enable_head_node_fault_tolerance: bool,
218
+ anyscale_aws_account: str,
219
+ _use_strict_iam_permissions: bool = False, # This should only be used in testing.
220
+ boto3_session: Optional[boto3.Session] = None,
221
+ is_anyscale_hosted: bool = False,
222
+ anyscale_hosted_network_info: Optional[Dict[str, Any]] = None,
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
+ cfn_stack_name = cloud_id.replace("_", "-").lower()
229
+
230
+ cfn_template_body = prepare_cloudformation_template(
231
+ region,
232
+ cfn_stack_name,
233
+ cloud_id,
234
+ enable_head_node_fault_tolerance,
235
+ boto3_session,
236
+ is_anyscale_hosted=is_anyscale_hosted,
237
+ )
238
+
239
+ parameters = [
240
+ {"ParameterKey": "EnvironmentName", "ParameterValue": ANYSCALE_ENV},
241
+ {"ParameterKey": "AnyscaleCLIVersion", "ParameterValue": anyscale_version},
242
+ {"ParameterKey": "CloudID", "ParameterValue": cloud_id},
243
+ {
244
+ "ParameterKey": "AnyscaleAWSAccountID",
245
+ "ParameterValue": anyscale_aws_account,
246
+ },
247
+ {
248
+ "ParameterKey": "ClusterNodeIAMRoleName",
249
+ "ParameterValue": cluster_node_iam_role_name,
250
+ },
251
+ {
252
+ "ParameterKey": "MemoryDBRedisPort",
253
+ "ParameterValue": MEMORYDB_REDIS_PORT,
254
+ },
255
+ ]
256
+ if not is_anyscale_hosted:
257
+ parameters.append(
258
+ {
259
+ "ParameterKey": "AnyscaleCrossAccountIAMRoleName",
260
+ "ParameterValue": anyscale_iam_role_name,
261
+ }
262
+ )
263
+ cross_account_iam_policies = self._get_anyscale_cross_account_iam_policies(
264
+ cloud_id, _use_strict_iam_permissions
265
+ )
266
+ for parameter in cross_account_iam_policies:
267
+ parameters.append(parameter)
268
+
269
+ tags: MutableSequence[Any] = []
270
+ if is_anyscale_hosted:
271
+ # Add is-anyscale-hosted tag to the cloudformation stack which
272
+ # the reconcile process will use to determine if the use the
273
+ # shared resources.
274
+ tags.append({"Key": "anyscale:cloud-id", "Value": cloud_id},)
275
+ tags.append({"Key": "anyscale:is-anyscale-hosted", "Value": "true"},)
276
+
277
+ # VPC ID are needed for creating security groups and subnets
278
+ # are needed for mount targets in shared VPC clouds.
279
+ if anyscale_hosted_network_info is not None:
280
+ parameters.append(
281
+ {
282
+ "ParameterKey": "VpcId",
283
+ "ParameterValue": anyscale_hosted_network_info["vpc_id"],
284
+ },
285
+ )
286
+
287
+ cfn_client.create_stack(
288
+ StackName=cfn_stack_name,
289
+ TemplateBody=cfn_template_body,
290
+ Parameters=parameters, # type: ignore
291
+ Capabilities=["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"],
292
+ Tags=tags,
293
+ )
294
+
295
+ def run_cloudformation( # noqa: PLR0913
296
+ self,
297
+ region: str,
298
+ cloud_id: str,
299
+ anyscale_iam_role_name: str,
300
+ cluster_node_iam_role_name: str,
301
+ enable_head_node_fault_tolerance: bool,
302
+ anyscale_aws_account: str,
303
+ _use_strict_iam_permissions: bool = False, # This should only be used in testing.
304
+ boto3_session: Optional[boto3.Session] = None,
305
+ ) -> Dict[str, Any]:
306
+ """
307
+ Run cloudformation to create the AWS resources for a cloud.
308
+
309
+ When enable_head_node_fault_tolerance is set to True, a memorydb cluster will be created.
310
+ """
311
+ if boto3_session is None:
312
+ boto3_session = boto3.Session(region_name=region)
313
+
314
+ cfn_client = boto3_session.client("cloudformation", region_name=region)
315
+ cfn_stack_name = cloud_id.replace("_", "-").lower()
316
+
317
+ cfn_template_body = prepare_cloudformation_template(
318
+ region,
319
+ cfn_stack_name,
320
+ cloud_id,
321
+ enable_head_node_fault_tolerance,
322
+ boto3_session,
323
+ )
324
+
325
+ cross_account_iam_policies = self._get_anyscale_cross_account_iam_policies(
326
+ cloud_id, _use_strict_iam_permissions
327
+ )
328
+
329
+ parameters = [
330
+ {"ParameterKey": "EnvironmentName", "ParameterValue": ANYSCALE_ENV},
331
+ {"ParameterKey": "AnyscaleCLIVersion", "ParameterValue": anyscale_version},
332
+ {"ParameterKey": "CloudID", "ParameterValue": cloud_id},
333
+ {
334
+ "ParameterKey": "AnyscaleAWSAccountID",
335
+ "ParameterValue": anyscale_aws_account,
336
+ },
337
+ {
338
+ "ParameterKey": "AnyscaleCrossAccountIAMRoleName",
339
+ "ParameterValue": anyscale_iam_role_name,
340
+ },
341
+ {
342
+ "ParameterKey": "ClusterNodeIAMRoleName",
343
+ "ParameterValue": cluster_node_iam_role_name,
344
+ },
345
+ {
346
+ "ParameterKey": "MemoryDBRedisPort",
347
+ "ParameterValue": MEMORYDB_REDIS_PORT,
348
+ },
349
+ ]
350
+ for parameter in cross_account_iam_policies:
351
+ parameters.append(parameter)
352
+
353
+ self.log.debug("cloudformation body:")
354
+ self.log.debug(cfn_template_body)
355
+
356
+ cfn_client.create_stack(
357
+ StackName=cfn_stack_name,
358
+ TemplateBody=cfn_template_body,
359
+ Parameters=parameters, # type: ignore
360
+ Capabilities=["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"],
361
+ )
362
+
363
+ stacks = cfn_client.describe_stacks(StackName=cfn_stack_name)
364
+ cfn_stack = stacks["Stacks"][0]
365
+ cfn_stack_url = f"https://{region}.console.aws.amazon.com/cloudformation/home?region={region}#/stacks/stackinfo?stackId={cfn_stack['StackId']}"
366
+ self.log.info(f"\nTrack progress of cloudformation at {cfn_stack_url}")
367
+ with self.log.spinner("Creating cloud resources through cloudformation..."):
368
+ start_time = time.time()
369
+ end_time = (
370
+ start_time + CLOUDFORMATION_TIMEOUT_SECONDS_LONG
371
+ if enable_head_node_fault_tolerance
372
+ else start_time + CLOUDFORMATION_TIMEOUT_SECONDS
373
+ )
374
+ while time.time() < end_time:
375
+ stacks = cfn_client.describe_stacks(StackName=cfn_stack_name)
376
+ cfn_stack = stacks["Stacks"][0]
377
+ if cfn_stack["StackStatus"] in (
378
+ "CREATE_FAILED",
379
+ "ROLLBACK_COMPLETE",
380
+ "ROLLBACK_IN_PROGRESS",
381
+ ):
382
+ bucket_name = f"anyscale-{ANYSCALE_ENV}-data-{cfn_stack_name}"
383
+ try:
384
+ boto3_session.client("s3", region_name=region).delete_bucket(
385
+ Bucket=bucket_name
386
+ )
387
+ self.log.info(f"Successfully deleted {bucket_name}")
388
+ except Exception as e: # noqa: BLE001
389
+ if not (
390
+ isinstance(e, ClientError)
391
+ and e.response["Error"]["Code"] == "NoSuchBucket"
392
+ ):
393
+ self.log.error(
394
+ f"Unable to delete the S3 bucket created by the cloud formation stack, please manually delete {bucket_name}"
395
+ )
396
+
397
+ # Describe events to get error reason
398
+ error_details = get_errored_resources_and_reasons(
399
+ cfn_client, cfn_stack_name
400
+ )
401
+ self.log.log_resource_error(
402
+ CloudAnalyticsEventCloudResource.AWS_CLOUDFORMATION,
403
+ f"Cloudformation stack failed to deploy. Detailed errors: {error_details}",
404
+ )
405
+ # Provide link to cloudformation
406
+ raise ClickException(
407
+ f"Failed to set up cloud resources. Please check your cloudformation stack for errors and to ensure that all resources created in this cloudformation stack were deleted: {cfn_stack_url}"
408
+ )
409
+ if cfn_stack["StackStatus"] == "CREATE_COMPLETE":
410
+ if enable_head_node_fault_tolerance:
411
+ modify_memorydb_parameter_group(
412
+ cfn_stack_name, region, boto3_session
413
+ )
414
+ self.log.info(
415
+ f"Cloudformation stack {cfn_stack['StackId']} Completed"
416
+ )
417
+ break
418
+
419
+ time.sleep(1)
420
+
421
+ if time.time() > end_time:
422
+ self.log.log_resource_error(
423
+ CloudAnalyticsEventCloudResource.AWS_CLOUDFORMATION,
424
+ "Cloudformation timed out.",
425
+ )
426
+ raise ClickException(
427
+ f"Timed out creating AWS resources. Please check your cloudformation stack for errors. {cfn_stack['StackId']}"
428
+ )
429
+ return cfn_stack # type: ignore
430
+
431
+ def run_deployment_manager( # noqa: PLR0913
432
+ self,
433
+ factory: Any,
434
+ deployment_name: str,
435
+ cloud_id: str,
436
+ project_id: str,
437
+ region: str,
438
+ anyscale_access_service_account: str,
439
+ workload_identity_pool_name: str,
440
+ anyscale_aws_account: str,
441
+ organization_id: str,
442
+ enable_head_node_fault_tolerance: bool,
443
+ ):
444
+ setup_utils = try_import_gcp_managed_setup_utils()
445
+
446
+ anyscale_access_service_account_name = anyscale_access_service_account.split(
447
+ "@"
448
+ )[0]
449
+ deployment_config = setup_utils.generate_deployment_manager_config(
450
+ region,
451
+ project_id,
452
+ cloud_id,
453
+ anyscale_access_service_account_name,
454
+ workload_identity_pool_name,
455
+ anyscale_aws_account,
456
+ organization_id,
457
+ enable_head_node_fault_tolerance,
458
+ )
459
+
460
+ self.log.debug("GCP Deployment Manager resource config:")
461
+ self.log.debug(deployment_config)
462
+
463
+ deployment = {
464
+ "name": deployment_name,
465
+ "target": {"config": {"content": deployment_config,},},
466
+ "labels": [{"key": "anyscale-cloud-id", "value": cloud_id},],
467
+ }
468
+
469
+ deployment_client = factory.build("deploymentmanager", "v2")
470
+ response = (
471
+ deployment_client.deployments()
472
+ .insert(project=project_id, body=deployment)
473
+ .execute()
474
+ )
475
+ deployment_url = f"https://console.cloud.google.com/dm/deployments/details/{deployment_name}?project={project_id}"
476
+
477
+ timeout_seconds = (
478
+ GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS_LONG
479
+ if enable_head_node_fault_tolerance
480
+ else GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS
481
+ )
482
+ self.log.info(
483
+ f"Note that it may take up to {int(timeout_seconds / 60)} minutes to create resources on GCP."
484
+ )
485
+ self.log.info(f"Track progress of Deployment Manager at {deployment_url}")
486
+ with self.log.spinner("Creating cloud resources through Deployment Manager..."):
487
+ start_time = time.time()
488
+ end_time = start_time + timeout_seconds
489
+ while time.time() < end_time:
490
+ current_operation = (
491
+ deployment_client.operations()
492
+ .get(operation=response["name"], project=project_id)
493
+ .execute()
494
+ )
495
+ if current_operation.get("status", None) == "DONE":
496
+ if "error" in current_operation:
497
+ self.log.error(f"Error: {current_operation['error']}")
498
+ self.log.log_resource_error(
499
+ CloudAnalyticsEventCloudResource.GCP_DEPLOYMENT,
500
+ current_operation["error"],
501
+ )
502
+ raise ClickException(
503
+ f"Failed to set up cloud resources. Please check your Deployment Manager for errors and delete all resources created in this deployment: {deployment_url}"
504
+ )
505
+ # happy path
506
+ self.log.info("Deployment succeeded.")
507
+ return True
508
+ time.sleep(1)
509
+ # timeout
510
+ self.log.log_resource_error(
511
+ CloudAnalyticsEventCloudResource.GCP_DEPLOYMENT, "Deployment timed out."
512
+ )
513
+ raise ClickException(
514
+ f"Timed out creating GCP resources. Please check your deployment for errors and delete all resources created in this deployment: {deployment_url}"
515
+ )
516
+
517
+ def get_create_cloud_resource_from_cfn_stack(
518
+ self,
519
+ cfn_stack: Dict[str, Any],
520
+ enable_head_node_fault_tolerance: bool,
521
+ anyscale_hosted_network_info: Optional[Dict[str, Any]] = None,
522
+ anyscale_control_plane_role_arn: str = "",
523
+ ) -> CreateCloudResource:
524
+ if "Outputs" not in cfn_stack:
525
+ raise ClickException(
526
+ f"Timed out setting up cloud resources. Please check your cloudformation stack for errors. {cfn_stack['StackId']}"
527
+ )
528
+
529
+ cfn_resources = {}
530
+ for resource in cfn_stack["Outputs"]:
531
+ resource_type = resource["OutputKey"]
532
+ resource_value = resource["OutputValue"]
533
+ assert (
534
+ resource_value is not None
535
+ ), f"{resource_type} is not created properly. Please delete the cloud and try creating agian."
536
+ cfn_resources[resource_type] = resource_value
537
+
538
+ aws_subnets_with_availability_zones = (
539
+ json.loads(cfn_resources["SubnetsWithAvailabilityZones"])
540
+ if not anyscale_hosted_network_info
541
+ else anyscale_hosted_network_info["subnet_ids"]
542
+ )
543
+ aws_vpc_id = (
544
+ cfn_resources["VPC"]
545
+ if not anyscale_hosted_network_info
546
+ else anyscale_hosted_network_info["vpc_id"]
547
+ )
548
+ aws_security_groups = [cfn_resources["AnyscaleSecurityGroup"]]
549
+
550
+ aws_efs_id = cfn_resources.get("EFS", None)
551
+ aws_efs_mount_target_ip = cfn_resources.get("EFSMountTargetIP", None)
552
+ anyscale_iam_role_arn = cfn_resources.get(
553
+ "AnyscaleIAMRole", anyscale_control_plane_role_arn
554
+ )
555
+ cluster_node_iam_role_arn = cfn_resources["NodeIamRole"]
556
+
557
+ if enable_head_node_fault_tolerance:
558
+ memorydb = json.loads(cfn_resources["MemoryDB"])
559
+ memorydb_cluster_config = AWSMemoryDBClusterConfig(
560
+ id=memorydb["arn"],
561
+ endpoint=f'{REDIS_TLS_ADDRESS_PREFIX}{memorydb["ClusterEndpointAddress"]}:{MEMORYDB_REDIS_PORT}',
562
+ )
563
+ else:
564
+ memorydb_cluster_config = None
565
+
566
+ bucket_name = cfn_resources.get("S3Bucket", "")
567
+ if bucket_name.startswith(S3_ARN_PREFIX):
568
+ bucket_name = bucket_name[len(S3_ARN_PREFIX) :]
569
+
570
+ return CreateCloudResource(
571
+ aws_vpc_id=aws_vpc_id,
572
+ aws_subnet_ids_with_availability_zones=[
573
+ SubnetIdWithAvailabilityZoneAWS(
574
+ subnet_id=subnet_with_az["subnet_id"],
575
+ availability_zone=subnet_with_az["availability_zone"],
576
+ )
577
+ for subnet_with_az in aws_subnets_with_availability_zones
578
+ ],
579
+ aws_iam_role_arns=[anyscale_iam_role_arn, cluster_node_iam_role_arn],
580
+ aws_security_groups=aws_security_groups,
581
+ aws_s3_id=bucket_name,
582
+ aws_efs_id=aws_efs_id,
583
+ aws_efs_mount_target_ip=aws_efs_mount_target_ip,
584
+ aws_cloudformation_stack_id=cfn_stack["StackId"],
585
+ memorydb_cluster_config=memorydb_cluster_config,
586
+ cloud_storage_bucket_name=S3_STORAGE_PREFIX + bucket_name,
587
+ )
588
+
589
+ def update_cloud_with_resources(
590
+ self,
591
+ cfn_stack: Dict[str, Any],
592
+ cloud_id: str,
593
+ enable_head_node_fault_tolerance: bool,
594
+ ):
595
+ create_cloud_resource = self.get_create_cloud_resource_from_cfn_stack(
596
+ cfn_stack, enable_head_node_fault_tolerance
597
+ )
598
+ with self.log.spinner("Updating Anyscale cloud with cloud resources..."):
599
+ self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
600
+ cloud_id=cloud_id,
601
+ update_cloud_with_cloud_resource=UpdateCloudWithCloudResource(
602
+ cloud_resource_to_update=create_cloud_resource
603
+ ),
604
+ )
605
+
606
+ def update_cloud_with_resources_gcp(
607
+ self,
608
+ factory: Any,
609
+ deployment_name: str,
610
+ cloud_id: str,
611
+ project_id: str,
612
+ anyscale_access_service_account: str,
613
+ ):
614
+ setup_utils = try_import_gcp_managed_setup_utils()
615
+ gcp_utils = try_import_gcp_utils()
616
+ anyscale_access_service_account_name = anyscale_access_service_account.split(
617
+ "@"
618
+ )[0]
619
+
620
+ cloud_resources = setup_utils.get_deployment_resources(
621
+ factory, deployment_name, project_id, anyscale_access_service_account_name
622
+ )
623
+ gcp_vpc_id = cloud_resources["compute.v1.network"]
624
+ gcp_subnet_ids = [cloud_resources["compute.v1.subnetwork"]]
625
+ gcp_cluster_node_service_account_email = f'{cloud_resources["iam.v1.serviceAccount"]}@{anyscale_access_service_account.split("@")[-1]}'
626
+ gcp_anyscale_iam_service_account_email = anyscale_access_service_account
627
+ gcp_firewall_policy = cloud_resources[
628
+ "gcp-types/compute-v1:networkFirewallPolicies"
629
+ ]
630
+ gcp_firewall_policy_ids = [gcp_firewall_policy]
631
+ gcp_cloud_storage_bucket_id = cloud_resources["storage.v1.bucket"]
632
+ gcp_deployment_manager_id = deployment_name
633
+
634
+ gcp_filestore_config = gcp_utils.get_gcp_filestore_config(
635
+ factory,
636
+ project_id,
637
+ gcp_vpc_id,
638
+ cloud_resources["filestore_location"],
639
+ cloud_resources["filestore_instance"],
640
+ self.log,
641
+ )
642
+ memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
643
+ factory, cloud_resources.get("memorystore_name"),
644
+ )
645
+ try:
646
+ setup_utils.configure_firewall_policy(
647
+ factory, gcp_vpc_id, project_id, gcp_firewall_policy
648
+ )
649
+
650
+ create_cloud_resource_gcp = CreateCloudResourceGCP(
651
+ gcp_vpc_id=gcp_vpc_id,
652
+ gcp_subnet_ids=gcp_subnet_ids,
653
+ gcp_cluster_node_service_account_email=gcp_cluster_node_service_account_email,
654
+ gcp_anyscale_iam_service_account_email=gcp_anyscale_iam_service_account_email,
655
+ gcp_filestore_config=gcp_filestore_config,
656
+ gcp_firewall_policy_ids=gcp_firewall_policy_ids,
657
+ gcp_cloud_storage_bucket_id=gcp_cloud_storage_bucket_id,
658
+ gcp_deployment_manager_id=gcp_deployment_manager_id,
659
+ memorystore_instance_config=memorystore_instance_config,
660
+ cloud_storage_bucket_name=GCS_STORAGE_PREFIX
661
+ + gcp_cloud_storage_bucket_id,
662
+ )
663
+ self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_put(
664
+ cloud_id=cloud_id,
665
+ update_cloud_with_cloud_resource_gcp=UpdateCloudWithCloudResourceGCP(
666
+ cloud_resource_to_update=create_cloud_resource_gcp,
667
+ ),
668
+ )
669
+ except Exception as e: # noqa: BLE001
670
+ self.log.error(str(e))
671
+ for firewall_policy_name in gcp_firewall_policy_ids:
672
+ setup_utils.remove_firewall_policy_associations(
673
+ factory, project_id, firewall_policy_name
674
+ )
675
+ raise ClickException(
676
+ f"Error occurred when updating resources for the cloud {cloud_id}."
677
+ )
678
+
679
+ def prepare_for_managed_cloud_setup(
680
+ self,
681
+ region: str,
682
+ cloud_name: str,
683
+ cluster_management_stack_version: ClusterManagementStackVersions,
684
+ auto_add_user: bool,
685
+ is_aioa: bool = False,
686
+ boto3_session: Optional[boto3.Session] = None,
687
+ ) -> Tuple[str, str]:
688
+ regions_available = get_available_regions(boto3_session)
689
+ if region not in regions_available:
690
+ self.cloud_event_producer.produce(
691
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE,
692
+ succeeded=False,
693
+ internal_error=f"Region '{region}' is not available.",
694
+ )
695
+ raise ClickException(
696
+ f"Region '{region}' is not available. Regions available are: "
697
+ f"{', '.join(map(repr, regions_available))}"
698
+ )
699
+
700
+ for _ in range(5):
701
+ anyscale_iam_role_name = "{}-{}".format(
702
+ ANYSCALE_IAM_ROLE_NAME, secrets.token_hex(4)
703
+ )
704
+ try:
705
+ role = _get_role(anyscale_iam_role_name, region, boto3_session)
706
+ except Exception as e: # noqa: BLE001
707
+ if isinstance(e, NoCredentialsError):
708
+ # Rewrite the error message to be more user friendly
709
+ self.cloud_event_producer.produce(
710
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE,
711
+ succeeded=False,
712
+ internal_error="Unable to locate AWS credentials.",
713
+ )
714
+ raise ClickException(
715
+ "Unable to locate AWS credentials. Please make sure you have AWS credentials configured."
716
+ )
717
+ self.cloud_event_producer.produce(
718
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE,
719
+ succeeded=False,
720
+ internal_error=str(e),
721
+ )
722
+ raise ClickException(f"Failed to get IAM role: {e}")
723
+ if role is None:
724
+ break
725
+ else:
726
+ self.cloud_event_producer.produce(
727
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE,
728
+ succeeded=False,
729
+ internal_error="Unable to find an available AWS IAM Role name",
730
+ )
731
+ raise ClickException(
732
+ "We weren't able to connect your account with the Anyscale because we weren't able to find an available IAM Role name in your account. Please reach out to support or your SA for assistance."
733
+ )
734
+
735
+ user_aws_account_id = get_user_env_aws_account(region)
736
+ self.cloud_event_producer.produce(
737
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE, succeeded=True,
738
+ )
739
+ try:
740
+ created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
741
+ write_cloud=WriteCloud(
742
+ provider="AWS",
743
+ region=region,
744
+ credentials=AwsRoleArn.from_role_name(
745
+ user_aws_account_id, anyscale_iam_role_name
746
+ ).to_string(),
747
+ name=cloud_name,
748
+ is_bring_your_own_resource=False,
749
+ cluster_management_stack_version=cluster_management_stack_version,
750
+ auto_add_user=auto_add_user,
751
+ is_aioa=is_aioa,
752
+ )
753
+ ).result
754
+ self.cloud_event_producer.set_cloud_id(created_cloud.id)
755
+ self.cloud_event_producer.produce(
756
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True,
757
+ )
758
+ except ClickException as e:
759
+ self.cloud_event_producer.produce(
760
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
761
+ succeeded=False,
762
+ internal_error=str(e),
763
+ )
764
+ raise
765
+
766
+ return anyscale_iam_role_name, created_cloud.id
767
+
768
+ def prepare_for_managed_cloud_setup_gcp( # noqa: PLR0913
769
+ self,
770
+ project_id: str,
771
+ region: str,
772
+ cloud_name: str,
773
+ factory: Any,
774
+ cluster_management_stack_version: ClusterManagementStackVersions,
775
+ auto_add_user: bool,
776
+ is_aioa: bool = False,
777
+ ) -> Tuple[str, str, str]:
778
+ setup_utils = try_import_gcp_managed_setup_utils()
779
+
780
+ # choose an service account name and create a provider pool
781
+ for _ in range(5):
782
+ token = secrets.token_hex(4)
783
+ anyscale_access_service_account = (
784
+ f"anyscale-access-{token}@{project_id}.iam.gserviceaccount.com"
785
+ )
786
+ service_account = setup_utils.get_anyscale_gcp_access_service_acount(
787
+ factory, anyscale_access_service_account
788
+ )
789
+ if service_account is not None:
790
+ continue
791
+ pool_id = f"anyscale-provider-pool-{token}"
792
+ wordload_identity_pool = setup_utils.get_workload_identity_pool(
793
+ factory, project_id, pool_id
794
+ )
795
+ if wordload_identity_pool is None:
796
+ break
797
+ else:
798
+ self.cloud_event_producer.produce(
799
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE,
800
+ succeeded=False,
801
+ internal_error="Unable to find an available GCP service account name and create a provider pool.",
802
+ )
803
+ raise ClickException(
804
+ "We weren't able to connect your account with the Anyscale because we weren't able to find an available service account name and create a provider pool in your GCP project. Please reach out to support or your SA for assistance."
805
+ )
806
+
807
+ # build credentials
808
+ project_number = setup_utils.get_project_number(factory, project_id)
809
+ provider_id = "anyscale-access"
810
+ pool_name = f"{project_number}/locations/global/workloadIdentityPools/{pool_id}"
811
+ provider_name = f"{pool_name}/providers/{provider_id}"
812
+ credentials = json.dumps(
813
+ {
814
+ "project_id": project_id,
815
+ "provider_id": provider_name,
816
+ "service_account_email": anyscale_access_service_account,
817
+ }
818
+ )
819
+ self.cloud_event_producer.produce(
820
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE, succeeded=True,
821
+ )
822
+
823
+ # create a cloud
824
+ try:
825
+ created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
826
+ write_cloud=WriteCloud(
827
+ provider="GCP",
828
+ region=region,
829
+ credentials=credentials,
830
+ name=cloud_name,
831
+ is_bring_your_own_resource=False,
832
+ cluster_management_stack_version=cluster_management_stack_version,
833
+ auto_add_user=auto_add_user,
834
+ is_aioa=is_aioa,
835
+ )
836
+ ).result
837
+ self.cloud_event_producer.set_cloud_id(created_cloud.id)
838
+ self.cloud_event_producer.produce(
839
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True,
840
+ )
841
+ except ClickException as e:
842
+ self.cloud_event_producer.produce(
843
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
844
+ succeeded=False,
845
+ internal_error=str(e),
846
+ )
847
+ raise
848
+ return anyscale_access_service_account, pool_name, created_cloud.id
849
+
850
+ def create_workload_identity_federation_provider(
851
+ self,
852
+ factory: Any,
853
+ project_id: str,
854
+ pool_id: str,
855
+ anyscale_access_service_account: str,
856
+ ):
857
+ setup_utils = try_import_gcp_managed_setup_utils()
858
+ # create provider pool
859
+ pool_display_name = "Anyscale provider pool"
860
+ pool_description = f"Workload Identity Provider Pool for Anyscale access service account {anyscale_access_service_account}"
861
+
862
+ wordload_identity_pool = setup_utils.create_workload_identity_pool(
863
+ factory, project_id, pool_id, self.log, pool_display_name, pool_description,
864
+ )
865
+ try:
866
+ # create provider
867
+ provider_display_name = "Anyscale Access"
868
+ provider_id = "anyscale-access"
869
+ anyscale_aws_account = (
870
+ self.api_client.get_anyscale_aws_account_api_v2_clouds_anyscale_aws_account_get().result.anyscale_aws_account
871
+ )
872
+ organization_id = get_organization_id(self.api_client)
873
+ setup_utils.create_anyscale_aws_provider(
874
+ factory,
875
+ organization_id,
876
+ wordload_identity_pool,
877
+ provider_id,
878
+ anyscale_aws_account,
879
+ provider_display_name,
880
+ self.log,
881
+ )
882
+ except ClickException as e:
883
+ # delete provider pool if there's an exception
884
+ setup_utils.delete_workload_identity_pool(
885
+ factory, wordload_identity_pool, self.log
886
+ )
887
+ raise ClickException(
888
+ f"Error occurred when trying to set up workload identity federation: {e}"
889
+ )
890
+
891
+ def wait_for_cloud_to_be_active(
892
+ self, cloud_id: str, cloud_provider: CloudProviders
893
+ ) -> None:
894
+ """
895
+ Waits for the cloud to be active
896
+ """
897
+ with self.log.spinner("Setting up resources on Anyscale for your new cloud..."):
898
+ try:
899
+ # The ingress for cloud admin zone may not be ready yet, so we need to wait for it to be active.
900
+ # We use the cloud functional verification controller to create a cluster compute config to wait
901
+ # for the cloud admin zone to be active.
902
+ if self.initialize_auth_api_client:
903
+ CloudFunctionalVerificationController(
904
+ self.cloud_event_producer, self.log
905
+ ).get_or_create_cluster_compute(cloud_id, cloud_provider)
906
+ except Exception: # noqa: BLE001
907
+ self.log.error(
908
+ "Timed out waiting for cloud admin zone to be active. Your cloud may not be set up properly. Please reach out to Anyscale support for assistance."
909
+ )
910
+
911
+ def setup_managed_cloud( # noqa: PLR0912
912
+ self,
913
+ *,
914
+ provider: str,
915
+ region: str,
916
+ name: str,
917
+ functional_verify: Optional[str],
918
+ cluster_management_stack_version: ClusterManagementStackVersions,
919
+ enable_head_node_fault_tolerance: bool,
920
+ project_id: Optional[str] = None,
921
+ is_aioa: bool = False,
922
+ yes: bool = False,
923
+ boto3_session: Optional[
924
+ boto3.Session
925
+ ] = None, # This is used by AIOA cloud setup
926
+ _use_strict_iam_permissions: bool = False, # This should only be used in testing.
927
+ auto_add_user: bool = True,
928
+ ) -> None:
929
+ """
930
+ Sets up a cloud provider
931
+ """
932
+ # TODO (congding): split this function into smaller functions per cloud provider
933
+ functions_to_verify = self._validate_functional_verification_args(
934
+ functional_verify
935
+ )
936
+ if provider == "aws":
937
+ if boto3_session is None:
938
+ # If boto3_session is not provided, we will create a new session with the given region.
939
+ boto3_session = boto3.Session(region_name=region)
940
+ if not validate_aws_credentials(self.log, boto3_session):
941
+ raise ClickException(
942
+ "Cloud setup requires valid AWS credentials to be set locally. Learn more: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
943
+ )
944
+ self.cloud_event_producer.init_trace_context(
945
+ CloudAnalyticsEventCommandName.SETUP, CloudProviders.AWS
946
+ )
947
+ self.cloud_event_producer.produce(
948
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True,
949
+ )
950
+ with self.log.spinner("Preparing environment for cloud setup..."):
951
+ (
952
+ anyscale_iam_role_name,
953
+ cloud_id,
954
+ ) = self.prepare_for_managed_cloud_setup(
955
+ region,
956
+ name,
957
+ cluster_management_stack_version,
958
+ auto_add_user,
959
+ is_aioa,
960
+ boto3_session,
961
+ )
962
+
963
+ try:
964
+ anyscale_aws_account = (
965
+ self.api_client.get_anyscale_aws_account_api_v2_clouds_anyscale_aws_account_get().result.anyscale_aws_account
966
+ )
967
+ cfn_stack = self.run_cloudformation(
968
+ region,
969
+ cloud_id,
970
+ anyscale_iam_role_name,
971
+ f"{cloud_id}-cluster_node_role",
972
+ enable_head_node_fault_tolerance,
973
+ anyscale_aws_account,
974
+ _use_strict_iam_permissions=_use_strict_iam_permissions,
975
+ boto3_session=boto3_session,
976
+ )
977
+ self.cloud_event_producer.produce(
978
+ CloudAnalyticsEventName.RESOURCES_CREATED, succeeded=True,
979
+ )
980
+ except Exception as e: # noqa: BLE001
981
+ self.log.error(str(e))
982
+ self.cloud_event_producer.produce(
983
+ CloudAnalyticsEventName.RESOURCES_CREATED,
984
+ succeeded=False,
985
+ logger=self.log,
986
+ internal_error=str(e),
987
+ )
988
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
989
+ cloud_id=cloud_id
990
+ )
991
+ raise ClickException("Cloud setup failed!")
992
+
993
+ try:
994
+ self.update_cloud_with_resources(
995
+ cfn_stack, cloud_id, enable_head_node_fault_tolerance
996
+ )
997
+ self.wait_for_cloud_to_be_active(cloud_id, CloudProviders.AWS)
998
+ self.cloud_event_producer.produce(
999
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True,
1000
+ )
1001
+ except Exception as e: # noqa: BLE001
1002
+ self.log.error(str(e))
1003
+ self.cloud_event_producer.produce(
1004
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
1005
+ succeeded=False,
1006
+ internal_error=str(e),
1007
+ )
1008
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
1009
+ cloud_id=cloud_id
1010
+ )
1011
+ raise ClickException("Cloud setup failed!")
1012
+
1013
+ self.verify_aws_cloud_quotas(region=region, boto3_session=boto3_session)
1014
+
1015
+ self.log.info(f"Successfully created cloud {name}, and it's ready to use.")
1016
+ elif provider == "gcp":
1017
+ if project_id is None:
1018
+ raise click.ClickException("Please provide a value for --project-id")
1019
+ gcp_utils = try_import_gcp_utils()
1020
+ setup_utils = try_import_gcp_managed_setup_utils()
1021
+ factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
1022
+ self.cloud_event_producer.init_trace_context(
1023
+ CloudAnalyticsEventCommandName.SETUP, CloudProviders.GCP
1024
+ )
1025
+ self.cloud_event_producer.produce(
1026
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True,
1027
+ )
1028
+ with self.log.spinner("Preparing environment for cloud setup..."):
1029
+ try:
1030
+ organization_id = get_organization_id(self.api_client)
1031
+ anyscale_aws_account = (
1032
+ self.api_client.get_anyscale_aws_account_api_v2_clouds_anyscale_aws_account_get().result.anyscale_aws_account
1033
+ )
1034
+ # Enable APIs in the given GCP project
1035
+ setup_utils.enable_project_apis(
1036
+ factory, project_id, self.log, enable_head_node_fault_tolerance
1037
+ )
1038
+ # We need the Google APIs Service Agent to have security admin permissions on the project
1039
+ # so that we can set IAM policy on Anyscale access service account
1040
+ project_number = setup_utils.get_project_number(
1041
+ factory, project_id
1042
+ ).split("/")[-1]
1043
+ google_api_service_agent = f"serviceAccount:{project_number}@cloudservices.gserviceaccount.com"
1044
+ setup_utils.append_project_iam_policy(
1045
+ factory,
1046
+ project_id,
1047
+ "roles/iam.securityAdmin",
1048
+ google_api_service_agent,
1049
+ )
1050
+
1051
+ except ClickException as e:
1052
+ self.cloud_event_producer.produce(
1053
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE,
1054
+ succeeded=False,
1055
+ logger=self.log,
1056
+ internal_error=str(e),
1057
+ )
1058
+ self.log.error(str(e))
1059
+ raise ClickException("Cloud setup failed!")
1060
+
1061
+ (
1062
+ anyscale_access_service_account,
1063
+ pool_name,
1064
+ cloud_id,
1065
+ ) = self.prepare_for_managed_cloud_setup_gcp(
1066
+ project_id,
1067
+ region,
1068
+ name,
1069
+ factory,
1070
+ cluster_management_stack_version,
1071
+ auto_add_user,
1072
+ is_aioa,
1073
+ )
1074
+
1075
+ pool_id = pool_name.split("/")[-1]
1076
+ deployment_name = cloud_id.replace("_", "-").lower()
1077
+ deployment_succeed = False
1078
+ try:
1079
+ with self.log.spinner(
1080
+ "Creating workload identity federation provider for Anyscale access..."
1081
+ ):
1082
+ self.create_workload_identity_federation_provider(
1083
+ factory, project_id, pool_id, anyscale_access_service_account
1084
+ )
1085
+ deployment_succeed = self.run_deployment_manager(
1086
+ factory,
1087
+ deployment_name,
1088
+ cloud_id,
1089
+ project_id,
1090
+ region,
1091
+ anyscale_access_service_account,
1092
+ pool_name,
1093
+ anyscale_aws_account,
1094
+ organization_id,
1095
+ enable_head_node_fault_tolerance,
1096
+ )
1097
+ self.cloud_event_producer.produce(
1098
+ CloudAnalyticsEventName.RESOURCES_CREATED, succeeded=True,
1099
+ )
1100
+ except Exception as e: # noqa: BLE001
1101
+ self.log.error(str(e))
1102
+ self.cloud_event_producer.produce(
1103
+ CloudAnalyticsEventName.RESOURCES_CREATED,
1104
+ succeeded=False,
1105
+ internal_error=str(e),
1106
+ logger=self.log,
1107
+ )
1108
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
1109
+ cloud_id=cloud_id
1110
+ )
1111
+ setup_utils.delete_workload_identity_pool(factory, pool_name, self.log)
1112
+ raise ClickException("Cloud setup failed!")
1113
+
1114
+ try:
1115
+ with self.log.spinner(
1116
+ "Updating Anyscale cloud with cloud resources..."
1117
+ ):
1118
+ self.update_cloud_with_resources_gcp(
1119
+ factory,
1120
+ deployment_name,
1121
+ cloud_id,
1122
+ project_id,
1123
+ anyscale_access_service_account,
1124
+ )
1125
+ self.wait_for_cloud_to_be_active(cloud_id, CloudProviders.GCP)
1126
+ self.cloud_event_producer.produce(
1127
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True,
1128
+ )
1129
+ except Exception as e: # noqa: BLE001
1130
+ self.log.error(str(e))
1131
+ self.cloud_event_producer.produce(
1132
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
1133
+ succeeded=False,
1134
+ internal_error=str(e),
1135
+ )
1136
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
1137
+ cloud_id=cloud_id
1138
+ )
1139
+ if deployment_succeed:
1140
+ # only clean up deployment if it's created successfully
1141
+ # otherwise keep the deployment for customers to check the errors
1142
+ setup_utils.delete_gcp_deployment(
1143
+ factory, project_id, deployment_name
1144
+ )
1145
+ setup_utils.delete_workload_identity_pool(factory, pool_name, self.log)
1146
+ raise ClickException("Cloud setup failed!")
1147
+
1148
+ self.log.info(f"Successfully created cloud {name}, and it's ready to use.")
1149
+ else:
1150
+ raise ClickException(
1151
+ f"Invalid Cloud provider: {provider}. Available providers are [aws, gcp]."
1152
+ )
1153
+
1154
+ if len(functions_to_verify) > 0:
1155
+ CloudFunctionalVerificationController(
1156
+ self.cloud_event_producer, self.log
1157
+ ).start_verification(
1158
+ cloud_id,
1159
+ self._get_cloud_provider_from_str(provider),
1160
+ functions_to_verify,
1161
+ yes,
1162
+ )
1163
+
1164
+ def _add_redis_cluster_aws(
1165
+ self, cloud: Any, cloud_resource: CreateCloudResource, yes: bool = False
1166
+ ):
1167
+ # Check if memorydb cluster already exists
1168
+ if cloud_resource.memorydb_cluster_config is not None:
1169
+ memorydb_name = cloud_resource.memorydb_cluster_config.id
1170
+ self.log.info(f"AWS memorydb {memorydb_name} already exists. ")
1171
+ return
1172
+
1173
+ # Get or create memorydb cluster
1174
+ try:
1175
+ memorydb_cluster_id = get_or_create_memorydb(
1176
+ cloud.region, cloud_resource.aws_cloudformation_stack_id, self.log, yes,
1177
+ )
1178
+ memorydb_cluster_config = _get_memorydb_cluster_config(
1179
+ memorydb_cluster_id, cloud.region, self.log
1180
+ )
1181
+ except Exception as e: # noqa: BLE001
1182
+ raise ClickException(f"Failed to create memorydb cluster: {e}")
1183
+
1184
+ # Edit cloud resource record
1185
+ try:
1186
+ self.api_client.edit_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_patch(
1187
+ cloud_id=cloud.id,
1188
+ editable_cloud_resource=EditableCloudResource(
1189
+ memorydb_cluster_config=memorydb_cluster_config,
1190
+ ),
1191
+ )
1192
+ except Exception as e: # noqa: BLE001
1193
+ self.log.error(str(e))
1194
+ raise ClickException(
1195
+ "Failed to update resources on Anyscale. Please reach out to Anyscale support or your SA for assistance."
1196
+ )
1197
+
1198
+ def _add_redis_cluster_gcp(
1199
+ self, cloud: Any, cloud_resource: CreateCloudResourceGCP, yes: bool = False
1200
+ ):
1201
+ if cloud_resource.memorystore_instance_config is not None:
1202
+ memorystore_name = cloud_resource.memorystore_instance_config.name
1203
+ self.log.info(f"GCP memorystore {memorystore_name} already exists. ")
1204
+ return
1205
+
1206
+ gcp_utils = try_import_gcp_utils()
1207
+ managed_setup_utils = try_import_gcp_managed_setup_utils()
1208
+ project_id = self._get_project_id(cloud, cloud.name, cloud.id)
1209
+ factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
1210
+ deployment_name = cloud_resource.gcp_deployment_manager_id
1211
+ try:
1212
+ memorystore_instance_name = managed_setup_utils.get_or_create_memorystore_gcp(
1213
+ factory,
1214
+ cloud.id,
1215
+ deployment_name,
1216
+ project_id,
1217
+ cloud.region,
1218
+ self.log,
1219
+ yes,
1220
+ )
1221
+ memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
1222
+ factory, memorystore_instance_name
1223
+ )
1224
+ except Exception as e: # noqa: BLE001
1225
+ raise ClickException(f"Failed to create GCP memorystore. Error: {e}")
1226
+ try:
1227
+ self.api_client.edit_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_patch(
1228
+ cloud_id=cloud.id,
1229
+ editable_cloud_resource_gcp=EditableCloudResourceGCP(
1230
+ memorystore_instance_config=memorystore_instance_config,
1231
+ ),
1232
+ )
1233
+ except Exception as e: # noqa: BLE001
1234
+ self.log.error(str(e))
1235
+ raise ClickException(
1236
+ "Cloud update failed! Please reach out to Anyscale support or your SA for assistance."
1237
+ )
1238
+
1239
+ def _update_auto_add_user_field(self, auto_add_user: bool, cloud) -> None:
1240
+ if cloud.auto_add_user == auto_add_user:
1241
+ self.log.info(
1242
+ f"No updated required to the auto add user value. Cloud {cloud.name}({cloud.id}) "
1243
+ f"has auto add user {'enabled' if auto_add_user else 'disabled'}."
1244
+ )
1245
+ else:
1246
+ self.api_client.update_cloud_auto_add_user_api_v2_clouds_cloud_id_auto_add_user_put(
1247
+ cloud.id, auto_add_user=auto_add_user
1248
+ )
1249
+ if auto_add_user:
1250
+ self.log.info(
1251
+ f"Auto add user for cloud {cloud.name}({cloud.id}) has been successfully enabled. Note: There may be up to 30 "
1252
+ "sec delay for all users to be granted permissions after this feature is enabled."
1253
+ )
1254
+ else:
1255
+ self.log.info(
1256
+ f"Auto add user for cloud {cloud.name}({cloud.id}) has been successfully disabled. No existing "
1257
+ "cloud permissions were altered by this flag. Users added to the organization in the future will not "
1258
+ "automatically be added to this cloud."
1259
+ )
1260
+
1261
+ def _update_customer_aggregated_logs_config(self, cloud_id: str, is_enabled: bool):
1262
+ self.api_client.update_customer_aggregated_logs_config_api_v2_clouds_cloud_id_update_customer_aggregated_logs_config_put(
1263
+ cloud_id=cloud_id, is_enabled=is_enabled,
1264
+ )
1265
+
1266
+ def update_managed_cloud( # noqa: PLR0912, C901
1267
+ self,
1268
+ cloud_name: Optional[str],
1269
+ cloud_id: Optional[str],
1270
+ enable_head_node_fault_tolerance: bool,
1271
+ functional_verify: Optional[str],
1272
+ yes: bool = False,
1273
+ auto_add_user: Optional[bool] = None,
1274
+ ) -> None:
1275
+ """
1276
+ Updates managed cloud.
1277
+
1278
+ If `enable_head_node_fault_tolerance` is set to True, we will add redis clusters to the cloud.
1279
+ Otherwise it only updates the inline IAM policies associated with the cross account IAM role for AWS clouds.
1280
+ """
1281
+ functions_to_verify = self._validate_functional_verification_args(
1282
+ functional_verify
1283
+ )
1284
+ cloud_id, cloud_name = get_cloud_id_and_name(
1285
+ self.api_client, cloud_id, cloud_name
1286
+ )
1287
+ cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(cloud_id).result
1288
+ if cloud.is_bring_your_own_resource or cloud.is_bring_your_own_resource is None:
1289
+ # If cloud.is_bring_your_own_resource is None, we couldn't tell it's a registered cloud or a managed cloud.
1290
+ # But it should be an old cloud so that we could just abort.
1291
+ raise ClickException(
1292
+ "Cannot update cloud with customer defined resources. Please modify the resources by `anyscale cloud edit`"
1293
+ )
1294
+ cloud_resource = get_cloud_resource_by_cloud_id(
1295
+ cloud_id, cloud.provider, self.api_client
1296
+ )
1297
+ if cloud_resource is None:
1298
+ raise ClickException(
1299
+ f"This cloud {cloud_name}({cloud_id}) does not contain resource records. Please delete this cloud and create a new one."
1300
+ )
1301
+ if anyscale_version == "0.0.0-dev":
1302
+ confirm(
1303
+ "You are using a development version of Anyscale CLI. Still want to update the cloud?",
1304
+ yes,
1305
+ )
1306
+ if auto_add_user is not None:
1307
+ self._update_auto_add_user_field(auto_add_user, cloud)
1308
+ msg = ""
1309
+ if cloud.provider == CloudProviders.AWS:
1310
+ msg = (
1311
+ " Note: The inline IAM policies associated with the cross account IAM role of this AWS cloud "
1312
+ f"were not updated because {'--enable-auto-add-user' if auto_add_user else '--disable-auto-add-user'} "
1313
+ "was specified. Please re-run `anyscale cloud update` if you want to update these IAM policies."
1314
+ )
1315
+ self.log.info("Cloud update completed." + msg)
1316
+ return
1317
+
1318
+ self.cloud_event_producer.init_trace_context(
1319
+ CloudAnalyticsEventCommandName.UPDATE, cloud.provider, cloud_id
1320
+ )
1321
+ if enable_head_node_fault_tolerance:
1322
+ self.cloud_event_producer.produce(
1323
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True,
1324
+ )
1325
+ try:
1326
+ if cloud.provider == CloudProviders.AWS:
1327
+ self._add_redis_cluster_aws(cloud, cloud_resource, yes)
1328
+ elif cloud.provider == CloudProviders.GCP:
1329
+ self._add_redis_cluster_gcp(cloud, cloud_resource, yes)
1330
+ else:
1331
+ raise ClickException(
1332
+ f"Unsupported cloud provider {cloud.provider}. Only AWS and GCP are supported for fault tolerance."
1333
+ )
1334
+ except Exception as e: # noqa: BLE001
1335
+ self.cloud_event_producer.produce(
1336
+ CloudAnalyticsEventName.REDIS_CLUSTER_ADDED,
1337
+ succeeded=False,
1338
+ internal_error=str(e),
1339
+ )
1340
+ raise ClickException(f"Cloud update failed! {e}")
1341
+ self.cloud_event_producer.produce(
1342
+ CloudAnalyticsEventName.REDIS_CLUSTER_ADDED, succeeded=True
1343
+ )
1344
+ else:
1345
+ # Update IAM permissions
1346
+ if cloud.provider != CloudProviders.AWS:
1347
+ raise ClickException(
1348
+ f"Unsupported cloud provider {cloud.provider}. Only AWS is supported for updating."
1349
+ )
1350
+ aws_cloudformation_stack_id = cloud_resource.aws_cloudformation_stack_id
1351
+ if aws_cloudformation_stack_id is None:
1352
+ raise ClickException(
1353
+ f"This cloud {cloud.name}({cloud.id}) does not have an associated cloudformation stack. Please contact Anyscale support."
1354
+ )
1355
+
1356
+ self.cloud_event_producer.produce(
1357
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True,
1358
+ )
1359
+ try:
1360
+ update_iam_role(
1361
+ cloud.region, aws_cloudformation_stack_id, self.log, yes
1362
+ )
1363
+ except Exception as e: # noqa: BLE001
1364
+ self.cloud_event_producer.produce(
1365
+ CloudAnalyticsEventName.IAM_ROLE_UPDATED,
1366
+ succeeded=False,
1367
+ internal_error=str(e),
1368
+ )
1369
+ raise ClickException(f"Cloud update failed! {e}")
1370
+ self.cloud_event_producer.produce(
1371
+ CloudAnalyticsEventName.IAM_ROLE_UPDATED, succeeded=True,
1372
+ )
1373
+
1374
+ self.log.info("Cloud update completed.")
1375
+
1376
+ if len(functions_to_verify) > 0:
1377
+ CloudFunctionalVerificationController(
1378
+ self.cloud_event_producer, self.log
1379
+ ).start_verification(
1380
+ cloud_id, CloudProviders.AWS, functions_to_verify, yes,
1381
+ )
1382
+
1383
+ def get_cloud_config(
1384
+ self, cloud_name: Optional[str] = None, cloud_id: Optional[str] = None,
1385
+ ) -> str:
1386
+ """Get a cloud's current JSON configuration."""
1387
+
1388
+ cloud_id, cloud_name = get_cloud_id_and_name(
1389
+ self.api_client, cloud_id, cloud_name
1390
+ )
1391
+
1392
+ return str(get_cloud_json_from_id(cloud_id, self.api_client)["config"])
1393
+
1394
+ def update_cloud_config(
1395
+ self,
1396
+ cloud_name: Optional[str] = None,
1397
+ cloud_id: Optional[str] = None,
1398
+ enable_log_ingestion: Optional[bool] = None,
1399
+ ):
1400
+ """Update a cloud's configuration."""
1401
+
1402
+ cloud_id, cloud_name = get_cloud_id_and_name(
1403
+ self.api_client, cloud_id, cloud_name
1404
+ )
1405
+ if enable_log_ingestion is not None:
1406
+ self._update_customer_aggregated_logs_config(
1407
+ cloud_id=cloud_id, is_enabled=enable_log_ingestion,
1408
+ )
1409
+ self.log.info(
1410
+ f"Successfully updated log ingestion configuration for cloud, "
1411
+ f"{cloud_id} to {enable_log_ingestion}"
1412
+ )
1413
+
1414
+ def set_default_cloud(
1415
+ self, cloud_name: Optional[str], cloud_id: Optional[str],
1416
+ ) -> None:
1417
+ """
1418
+ Sets default cloud for caller's organization. This operation can only be performed
1419
+ by organization admins, and the default cloud must have organization level
1420
+ permissions.
1421
+ """
1422
+
1423
+ cloud_id, cloud_name = get_cloud_id_and_name(
1424
+ self.api_client, cloud_id, cloud_name
1425
+ )
1426
+
1427
+ self.api_client.update_default_cloud_api_v2_organizations_update_default_cloud_post(
1428
+ cloud_id=cloud_id
1429
+ )
1430
+
1431
+ self.log.info(f"Updated default cloud to {cloud_name}")
1432
+
1433
+ def _passed_or_failed_str_from_bool(self, is_passing: bool) -> str:
1434
+ return "PASSED" if is_passing else "FAILED"
1435
+
1436
+ @staticmethod
1437
+ def _get_cloud_provider_from_str(provider: str) -> CloudProviders:
1438
+ if provider.lower() == "aws":
1439
+ return CloudProviders.AWS
1440
+ elif provider.lower() == "gcp":
1441
+ return CloudProviders.GCP
1442
+ else:
1443
+ raise ClickException(
1444
+ f"Unsupported provider {provider}. Supported providers are [aws, gcp]."
1445
+ )
1446
+
1447
+ def _validate_functional_verification_args(
1448
+ self, functional_verify: Optional[str]
1449
+ ) -> List[CloudFunctionalVerificationType]:
1450
+ if functional_verify is None:
1451
+ return []
1452
+ # validate functional_verify
1453
+ functions_to_verify = set()
1454
+ for function in functional_verify.split(","):
1455
+ fn = getattr(CloudFunctionalVerificationType, function.upper(), None)
1456
+ if fn is None:
1457
+ raise ClickException(
1458
+ f"Unsupported function {function} for --functional-verify. "
1459
+ f"Supported functions: {[function.lower() for function in CloudFunctionalVerificationType]}"
1460
+ )
1461
+ functions_to_verify.add(fn)
1462
+ return list(functions_to_verify)
1463
+
1464
+ def verify_cloud( # noqa: PLR0911
1465
+ self,
1466
+ *,
1467
+ cloud_name: Optional[str],
1468
+ cloud_id: Optional[str],
1469
+ functional_verify: Optional[str],
1470
+ boto3_session: Optional[Any] = None,
1471
+ strict: bool = False,
1472
+ _use_strict_iam_permissions: bool = False, # This should only be used in testing.
1473
+ yes: bool = False,
1474
+ ) -> bool:
1475
+ """
1476
+ Verifies a cloud by name or id.
1477
+
1478
+ Note: If your changes involve operations that may require additional permissions
1479
+ (for example, `boto3_session.client("efs").describe_backup_policy`), it's important
1480
+ to run the end-to-end test `bk_e2e/test_cloud.py` locally before pushing the changes.
1481
+ This way, you can ensure that your changes will not break the tests.
1482
+ """
1483
+ functions_to_verify = self._validate_functional_verification_args(
1484
+ functional_verify
1485
+ )
1486
+
1487
+ cloud_id, cloud_name = get_cloud_id_and_name(
1488
+ self.api_client, cloud_id, cloud_name
1489
+ )
1490
+
1491
+ cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(cloud_id).result
1492
+
1493
+ if cloud.compute_stack == ComputeStack.K8S:
1494
+ self.log.error(
1495
+ "The cloud verify command is not supported for Kubernetes clouds."
1496
+ )
1497
+ return False
1498
+
1499
+ if cloud.state in (CloudState.DELETING, CloudState.DELETED):
1500
+ self.log.info(
1501
+ f"This cloud {cloud_name}({cloud_id}) is either during deletion or deleted. Skipping verification."
1502
+ )
1503
+ return False
1504
+
1505
+ cloud_resource = get_cloud_resource_by_cloud_id(
1506
+ cloud_id, cloud.provider, self.api_client
1507
+ )
1508
+ if cloud_resource is None:
1509
+ self.log.error(
1510
+ f"This cloud {cloud_name}({cloud_id}) does not contain resource records. Please delete this cloud and create a new one."
1511
+ )
1512
+ return False
1513
+
1514
+ self.cloud_event_producer.init_trace_context(
1515
+ CloudAnalyticsEventCommandName.VERIFY,
1516
+ cloud_provider=cloud.provider,
1517
+ cloud_id=cloud_id,
1518
+ )
1519
+ self.cloud_event_producer.produce(
1520
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True
1521
+ )
1522
+
1523
+ if cloud.provider == "AWS":
1524
+ if boto3_session is None:
1525
+ boto3_session = boto3.Session(region_name=cloud.region)
1526
+ if not self.verify_aws_cloud_resources(
1527
+ cloud_resource=cloud_resource,
1528
+ boto3_session=boto3_session,
1529
+ region=cloud.region,
1530
+ cloud_id=cloud_id,
1531
+ is_bring_your_own_resource=cloud.is_bring_your_own_resource,
1532
+ is_private_network=cloud.is_private_cloud
1533
+ if cloud.is_private_cloud
1534
+ else False,
1535
+ strict=strict,
1536
+ _use_strict_iam_permissions=_use_strict_iam_permissions,
1537
+ ):
1538
+ self.cloud_event_producer.produce(
1539
+ CloudAnalyticsEventName.RESOURCES_VERIFIED,
1540
+ succeeded=False,
1541
+ logger=self.log,
1542
+ )
1543
+ return False
1544
+ elif cloud.provider == "GCP":
1545
+ credentials_dict = json.loads(cloud.credentials)
1546
+ project_id = credentials_dict["project_id"]
1547
+ host_project_id = credentials_dict.get("host_project_id")
1548
+ if not self.verify_gcp_cloud_resources(
1549
+ cloud_resource=cloud_resource,
1550
+ project_id=project_id,
1551
+ host_project_id=host_project_id,
1552
+ region=cloud.region,
1553
+ cloud_id=cloud_id,
1554
+ yes=False,
1555
+ strict=strict,
1556
+ is_private_service_cloud=cloud.is_private_service_cloud,
1557
+ ):
1558
+ self.cloud_event_producer.produce(
1559
+ CloudAnalyticsEventName.RESOURCES_VERIFIED,
1560
+ succeeded=False,
1561
+ logger=self.log,
1562
+ )
1563
+ return False
1564
+ else:
1565
+ self.log.error(
1566
+ f"This cloud {cloud_name}({cloud_id}) does not have a valid cloud provider."
1567
+ )
1568
+ self.cloud_event_producer.produce(
1569
+ CloudAnalyticsEventName.RESOURCES_VERIFIED,
1570
+ succeeded=False,
1571
+ internal_error="invalid cloud provider",
1572
+ )
1573
+ return False
1574
+
1575
+ self.cloud_event_producer.produce(
1576
+ CloudAnalyticsEventName.RESOURCES_VERIFIED, succeeded=True
1577
+ )
1578
+
1579
+ if len(functions_to_verify) == 0:
1580
+ return True
1581
+
1582
+ return CloudFunctionalVerificationController(
1583
+ self.cloud_event_producer, self.log
1584
+ ).start_verification(cloud_id, cloud.provider, functions_to_verify, yes=yes)
1585
+
1586
+ def verify_aws_cloud_resources(
1587
+ self,
1588
+ *,
1589
+ cloud_resource: CreateCloudResource,
1590
+ boto3_session: boto3.Session,
1591
+ region: str,
1592
+ is_private_network: bool,
1593
+ cloud_id: str,
1594
+ is_bring_your_own_resource: bool = False,
1595
+ ignore_capacity_errors: bool = IGNORE_CAPACITY_ERRORS,
1596
+ logger: CloudSetupLogger = None,
1597
+ strict: bool = False,
1598
+ _use_strict_iam_permissions: bool = False, # This should only be used in testing.
1599
+ ):
1600
+ if not logger:
1601
+ logger = self.log
1602
+
1603
+ verify_aws_vpc_result = verify_aws_vpc(
1604
+ cloud_resource=cloud_resource,
1605
+ boto3_session=boto3_session,
1606
+ logger=logger,
1607
+ ignore_capacity_errors=ignore_capacity_errors,
1608
+ strict=strict,
1609
+ )
1610
+ verify_aws_subnets_result = verify_aws_subnets(
1611
+ cloud_resource=cloud_resource,
1612
+ region=region,
1613
+ logger=logger,
1614
+ ignore_capacity_errors=ignore_capacity_errors,
1615
+ is_private_network=is_private_network,
1616
+ strict=strict,
1617
+ )
1618
+
1619
+ anyscale_aws_account = (
1620
+ self.api_client.get_anyscale_aws_account_api_v2_clouds_anyscale_aws_account_get().result.anyscale_aws_account
1621
+ )
1622
+ verify_aws_iam_roles_result = verify_aws_iam_roles(
1623
+ cloud_resource=cloud_resource,
1624
+ boto3_session=boto3_session,
1625
+ anyscale_aws_account=anyscale_aws_account,
1626
+ logger=logger,
1627
+ strict=strict,
1628
+ cloud_id=cloud_id,
1629
+ _use_strict_iam_permissions=_use_strict_iam_permissions,
1630
+ )
1631
+ verify_aws_security_groups_result = verify_aws_security_groups(
1632
+ cloud_resource=cloud_resource,
1633
+ boto3_session=boto3_session,
1634
+ logger=logger,
1635
+ strict=strict,
1636
+ )
1637
+ verify_aws_s3_result = verify_aws_s3(
1638
+ cloud_resource=cloud_resource,
1639
+ boto3_session=boto3_session,
1640
+ region=region,
1641
+ logger=logger,
1642
+ strict=strict,
1643
+ )
1644
+ verify_aws_efs_result = verify_aws_efs(
1645
+ cloud_resource=cloud_resource,
1646
+ boto3_session=boto3_session,
1647
+ logger=logger,
1648
+ strict=strict,
1649
+ )
1650
+ # Cloudformation is only used in managed cloud setup. Set to True in BYOR case because it's not used.
1651
+ verify_aws_cloudformation_stack_result = True
1652
+ if not is_bring_your_own_resource:
1653
+ verify_aws_cloudformation_stack_result = verify_aws_cloudformation_stack(
1654
+ cloud_resource=cloud_resource,
1655
+ boto3_session=boto3_session,
1656
+ logger=logger,
1657
+ strict=strict,
1658
+ )
1659
+ if cloud_resource.memorydb_cluster_config is not None:
1660
+ verify_aws_memorydb_cluster_result = verify_aws_memorydb_cluster(
1661
+ cloud_resource=cloud_resource,
1662
+ boto3_session=boto3_session,
1663
+ logger=logger,
1664
+ strict=strict,
1665
+ )
1666
+
1667
+ verify_anyscale_access_result = verify_anyscale_access(
1668
+ self.api_client, cloud_id, CloudProviders.AWS, logger
1669
+ )
1670
+
1671
+ verification_result_summary = [
1672
+ "Verification result:",
1673
+ f"anyscale access: {self._passed_or_failed_str_from_bool(verify_anyscale_access_result)}",
1674
+ f"vpc: {self._passed_or_failed_str_from_bool(verify_aws_vpc_result)}",
1675
+ f"subnets: {self._passed_or_failed_str_from_bool(verify_aws_subnets_result)}",
1676
+ f"iam roles: {self._passed_or_failed_str_from_bool(verify_aws_iam_roles_result)}",
1677
+ f"security groups: {self._passed_or_failed_str_from_bool(verify_aws_security_groups_result)}",
1678
+ f"s3: {self._passed_or_failed_str_from_bool(verify_aws_s3_result)}",
1679
+ f"efs: {self._passed_or_failed_str_from_bool(verify_aws_efs_result)}",
1680
+ f"cloudformation stack: {self._passed_or_failed_str_from_bool(verify_aws_cloudformation_stack_result) if not is_bring_your_own_resource else 'N/A'}",
1681
+ ]
1682
+ if cloud_resource.memorydb_cluster_config is not None:
1683
+ verification_result_summary.append(
1684
+ f"memorydb cluster: {self._passed_or_failed_str_from_bool(verify_aws_memorydb_cluster_result)}"
1685
+ )
1686
+
1687
+ logger.info("\n".join(verification_result_summary))
1688
+
1689
+ self.verify_aws_cloud_quotas(region=region, boto3_session=boto3_session)
1690
+
1691
+ return all(
1692
+ [
1693
+ verify_anyscale_access_result,
1694
+ verify_aws_vpc_result,
1695
+ verify_aws_subnets_result,
1696
+ verify_aws_iam_roles_result,
1697
+ verify_aws_security_groups_result,
1698
+ verify_aws_s3_result,
1699
+ verify_aws_efs_result,
1700
+ verify_aws_cloudformation_stack_result
1701
+ if not is_bring_your_own_resource
1702
+ else True,
1703
+ ]
1704
+ )
1705
+
1706
+ def verify_aws_cloud_quotas(
1707
+ self, *, region: str, boto3_session: Optional[Any] = None
1708
+ ):
1709
+ """
1710
+ Checks the AWS EC2 instance quotas and warns users if they are not good enough
1711
+ to support LLM workloads
1712
+ """
1713
+ if boto3_session is None:
1714
+ boto3_session = boto3.Session(region_name=region)
1715
+
1716
+ QUOTAS_CONFIG = {
1717
+ "L-3819A6DF": {
1718
+ "description": "All G and VT Spot Instance Requests",
1719
+ "min": 512,
1720
+ },
1721
+ "L-34B43A08": {
1722
+ "description": "All Standard (A, C, D, H, I, M, R, T, Z) Spot Instance Requests",
1723
+ "min": 512,
1724
+ },
1725
+ "L-7212CCBC": {
1726
+ "description": "All P4, P3 and P2 Spot Instance Requests",
1727
+ "min": 224,
1728
+ },
1729
+ "L-DB2E81BA": {
1730
+ "description": "Running On-Demand G and VT instances",
1731
+ "min": 512,
1732
+ },
1733
+ "L-1216C47A": {
1734
+ "description": "Running On-Demand Standard (A, C, D, H, I, M, R, T, Z) instances",
1735
+ "min": 544,
1736
+ },
1737
+ "L-417A185B": {"description": "Running On-Demand P instances", "min": 224,},
1738
+ }
1739
+
1740
+ quota_client = boto3_session.client("service-quotas", region_name=region)
1741
+
1742
+ self.log.info("Checking quota values...")
1743
+ # List of tuples of quota code, current quota value
1744
+ invalid_quotas = []
1745
+ for quota_code, config in QUOTAS_CONFIG.items():
1746
+ quota = quota_client.get_service_quota(
1747
+ ServiceCode="ec2", QuotaCode=quota_code
1748
+ )
1749
+ if quota["Quota"]["Value"] < config["min"]:
1750
+ invalid_quotas.append((quota_code, quota["Quota"]["Value"]))
1751
+
1752
+ if len(invalid_quotas):
1753
+ quota_errors = [
1754
+ f"- \"{QUOTAS_CONFIG[quota_code]['description']}\" should be at least {QUOTAS_CONFIG[quota_code]['min']} (curr: {value})"
1755
+ for quota_code, value in invalid_quotas
1756
+ ]
1757
+ quota_error_str = "\n".join(quota_errors)
1758
+ self.log.warning(
1759
+ "Your AWS account does not have enough quota to support LLM workloads. "
1760
+ "Please request quota increases for the following quotas:\n"
1761
+ f"{quota_error_str}\n\nFor instructions on how to increase quotas, visit this link: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html#request-increase"
1762
+ )
1763
+
1764
+ def register_generic_cloud( # noqa: PLR0913
1765
+ self,
1766
+ name: str,
1767
+ auto_add_user: bool = False,
1768
+ # Optional cloud-resource-scoped parameters.
1769
+ # Some of these are conditionally required.
1770
+ region: Optional[str] = "default",
1771
+ cloud_storage_bucket_name: Optional[str] = None,
1772
+ cloud_storage_bucket_endpoint: Optional[str] = None,
1773
+ nfs_mount_targets: Optional[List[str]] = None,
1774
+ nfs_mount_path: Optional[str] = None,
1775
+ kubernetes_zones: Optional[List[str]] = None,
1776
+ ) -> None:
1777
+ self.cloud_event_producer.init_trace_context(
1778
+ CloudAnalyticsEventCommandName.REGISTER, CloudProviders.GENERIC
1779
+ )
1780
+ self.cloud_event_producer.produce(
1781
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True
1782
+ )
1783
+
1784
+ # Handle parsing / conversion of nfs_mount_targets.
1785
+ mount_targets: List[NFSMountTarget] = []
1786
+ for target in nfs_mount_targets or []:
1787
+ parts = [part.strip() for part in target.split(",")]
1788
+ if len(parts) == 1:
1789
+ mount_targets.append(NFSMountTarget(address=parts[0]))
1790
+ elif len(parts) == 2:
1791
+ mount_targets.append(NFSMountTarget(address=parts[1], zone=parts[0]))
1792
+ else:
1793
+ raise ClickException(
1794
+ f"Invalid mount target {target}; expected (zone,address) tuple or a singular address."
1795
+ )
1796
+
1797
+ # Handle defaulting of region.
1798
+ if not region:
1799
+ region = "default"
1800
+
1801
+ # Attempt to create the cloud.
1802
+ try:
1803
+ created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
1804
+ write_cloud=WriteCloud(
1805
+ name=name,
1806
+ region=region,
1807
+ provider="GENERIC",
1808
+ is_bring_your_own_resource=True,
1809
+ cluster_management_stack_version=ClusterManagementStackVersions.V2,
1810
+ auto_add_user=auto_add_user,
1811
+ credentials="",
1812
+ )
1813
+ )
1814
+ cloud_id = created_cloud.result.id
1815
+ self.cloud_event_producer.set_cloud_id(cloud_id)
1816
+ self.cloud_event_producer.produce(
1817
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True
1818
+ )
1819
+ except ClickException as e:
1820
+ self.cloud_event_producer.produce(
1821
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
1822
+ succeeded=False,
1823
+ internal_error=str(e),
1824
+ )
1825
+ raise
1826
+
1827
+ # Attempt to create the cloud resource.
1828
+ try:
1829
+ with self.log.spinner("Registering Anyscale cloud resources..."):
1830
+ cloud_resource = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
1831
+ cloud_id=cloud_id,
1832
+ update_cloud_with_cloud_resource=UpdateCloudWithCloudResource(
1833
+ cloud_resource_to_update=CreateCloudResource(
1834
+ compute_stack=ComputeStack.K8S,
1835
+ kubernetes_zones=kubernetes_zones,
1836
+ cloud_storage_bucket_name=cloud_storage_bucket_name,
1837
+ cloud_storage_bucket_endpoint=cloud_storage_bucket_endpoint,
1838
+ nfs_mount_targets=mount_targets,
1839
+ nfs_mount_path=nfs_mount_path,
1840
+ ),
1841
+ ),
1842
+ )
1843
+
1844
+ self.cloud_event_producer.produce(
1845
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True
1846
+ )
1847
+
1848
+ except Exception as e: # noqa: BLE001
1849
+ self.log.error(str(e))
1850
+ self.cloud_event_producer.produce(
1851
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
1852
+ succeeded=False,
1853
+ internal_error=str(e),
1854
+ )
1855
+
1856
+ # Delete the cloud if registering the cloud fails
1857
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
1858
+ cloud_id=cloud_id
1859
+ )
1860
+ raise ClickException(f"Cloud registration failed! {e}")
1861
+
1862
+ # TODO (shomilj): Fetch & optionally run the Helm installation here.
1863
+ cloud_resource_id = cloud_resource.result.cloud_resource.id
1864
+ self.log.info(
1865
+ f"Cloud registration complete! When installing this cloud's Kubernetes Manager, use cloud deployment ID '{cloud_resource_id}'."
1866
+ )
1867
+
1868
+ def register_aws_cloud( # noqa: PLR0913, PLR0912, C901
1869
+ self,
1870
+ *,
1871
+ region: str,
1872
+ name: str,
1873
+ vpc_id: str,
1874
+ subnet_ids: List[str],
1875
+ efs_id: str,
1876
+ anyscale_iam_role_id: str,
1877
+ instance_iam_role_id: str,
1878
+ security_group_ids: List[str],
1879
+ cloud_storage_bucket_name: str,
1880
+ memorydb_cluster_id: Optional[str],
1881
+ functional_verify: Optional[str],
1882
+ private_network: bool,
1883
+ cluster_management_stack_version: ClusterManagementStackVersions,
1884
+ yes: bool = False,
1885
+ skip_verifications: bool = False,
1886
+ auto_add_user: bool = False,
1887
+ external_id: Optional[str] = None,
1888
+ # Default to ComputeStack.VM for backwards compatibility
1889
+ # for SDK users who do not specify a compute stack here.
1890
+ compute_stack: ComputeStack = ComputeStack.VM,
1891
+ kubernetes_zones: Optional[List[str]] = None,
1892
+ anyscale_operator_iam_identity: Optional[str] = None,
1893
+ ):
1894
+ functions_to_verify = self._validate_functional_verification_args(
1895
+ functional_verify
1896
+ )
1897
+ if not validate_aws_credentials(self.log):
1898
+ raise ClickException(
1899
+ "Cloud registration requires valid AWS credentials to be set locally. Learn more: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
1900
+ )
1901
+
1902
+ # We accept both the full ARN and the bucket name as input
1903
+ # but we unify it to the bucket name here
1904
+ if cloud_storage_bucket_name.startswith(S3_ARN_PREFIX):
1905
+ cloud_storage_bucket_name = cloud_storage_bucket_name[len(S3_ARN_PREFIX) :]
1906
+ if not cloud_storage_bucket_name.startswith(S3_STORAGE_PREFIX):
1907
+ cloud_storage_bucket_name = S3_STORAGE_PREFIX + cloud_storage_bucket_name
1908
+
1909
+ organization_id = get_organization_id(self.api_client)
1910
+ if external_id and not external_id.startswith(organization_id):
1911
+ raise ClickException(
1912
+ f"Cloud registration failed! `--external-id` must start with the organization ID: {organization_id}"
1913
+ )
1914
+
1915
+ self.cloud_event_producer.init_trace_context(
1916
+ CloudAnalyticsEventCommandName.REGISTER, CloudProviders.AWS
1917
+ )
1918
+ self.cloud_event_producer.produce(
1919
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True
1920
+ )
1921
+
1922
+ credentials = anyscale_iam_role_id
1923
+ if compute_stack == ComputeStack.K8S:
1924
+ # On K8S, we don't need to collect credentials;
1925
+ # instead, write a random value into this field
1926
+ # to maintain the property that each cloud's
1927
+ # credentials are unique.
1928
+ credentials = uuid.uuid4().hex
1929
+
1930
+ # Create a cloud without cloud resources first
1931
+ try:
1932
+ created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
1933
+ write_cloud=WriteCloud(
1934
+ provider="AWS",
1935
+ region=region,
1936
+ credentials=credentials,
1937
+ name=name,
1938
+ is_bring_your_own_resource=True,
1939
+ is_private_cloud=private_network,
1940
+ cluster_management_stack_version=cluster_management_stack_version,
1941
+ auto_add_user=auto_add_user,
1942
+ external_id=external_id,
1943
+ )
1944
+ )
1945
+ cloud_id = created_cloud.result.id
1946
+ self.cloud_event_producer.set_cloud_id(cloud_id)
1947
+ self.cloud_event_producer.produce(
1948
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True
1949
+ )
1950
+ except ClickException as e:
1951
+ self.cloud_event_producer.produce(
1952
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
1953
+ succeeded=False,
1954
+ internal_error=str(e),
1955
+ )
1956
+ raise
1957
+
1958
+ try:
1959
+ # The Anyscale IAM role is optional for the K8s stack.
1960
+ has_anyscale_iam_role = compute_stack == ComputeStack.VM or (
1961
+ compute_stack == ComputeStack.K8S and anyscale_iam_role_id
1962
+ )
1963
+
1964
+ iam_role_original_policy = None
1965
+ if has_anyscale_iam_role:
1966
+ # Update anyscale IAM role's assume policy to include the cloud id as the external ID
1967
+ role = _get_role(
1968
+ AwsRoleArn.from_string(anyscale_iam_role_id).to_role_name(), region
1969
+ )
1970
+ if role is None:
1971
+ self.log.log_resource_error(
1972
+ CloudAnalyticsEventCloudResource.AWS_IAM_ROLE,
1973
+ CloudSetupError.RESOURCE_NOT_FOUND,
1974
+ )
1975
+ raise ClickException(
1976
+ f"Failed to access IAM role {anyscale_iam_role_id}."
1977
+ )
1978
+
1979
+ iam_role_original_policy = role.assume_role_policy_document # type: ignore
1980
+ if external_id is None:
1981
+ try:
1982
+ new_policy = _update_external_ids_for_policy(
1983
+ iam_role_original_policy, cloud_id
1984
+ )
1985
+ role.AssumeRolePolicy().update(PolicyDocument=json.dumps(new_policy)) # type: ignore
1986
+ except ClientError as e:
1987
+ self.log.log_resource_exception(
1988
+ CloudAnalyticsEventCloudResource.AWS_IAM_ROLE, e
1989
+ )
1990
+ raise e
1991
+ else:
1992
+ fetched_external_ids = [
1993
+ statement.setdefault("Condition", {})
1994
+ .setdefault("StringEquals", {})
1995
+ .setdefault("sts:ExternalId", [])
1996
+ for statement in iam_role_original_policy.get("Statement", []) # type: ignore
1997
+ ]
1998
+ external_id_in_policy = all(
1999
+ external_id == fetched_external_id
2000
+ if isinstance(fetched_external_id, str)
2001
+ else external_id in fetched_external_id
2002
+ for fetched_external_id in fetched_external_ids
2003
+ )
2004
+ if not external_id_in_policy:
2005
+ raise ClickException(
2006
+ f"External ID {external_id} is not in the assume role policy of {anyscale_iam_role_id}."
2007
+ )
2008
+
2009
+ # When running on the VM compute stack, validate and retrieve the EFS mount target IP.
2010
+ # When running on the K8S compute stack, EFS is optional; if efs_id is provided, then
2011
+ # validate and retrieve the EFS mount target IP.
2012
+ enable_efs = compute_stack == ComputeStack.VM or (
2013
+ compute_stack == ComputeStack.K8S and efs_id
2014
+ )
2015
+ if enable_efs:
2016
+ try:
2017
+ boto3_session = boto3.Session(region_name=region)
2018
+ aws_efs_mount_target_ip = _get_aws_efs_mount_target_ip(
2019
+ boto3_session, efs_id
2020
+ )
2021
+ except ClientError as e:
2022
+ self.log.log_resource_exception(
2023
+ CloudAnalyticsEventCloudResource.AWS_EFS, e
2024
+ )
2025
+ raise e
2026
+ else:
2027
+ boto3_session = None
2028
+ aws_efs_mount_target_ip = None
2029
+
2030
+ # When running on the VM compute stack, associate the AWS subnets with their availability zones.
2031
+ if compute_stack == ComputeStack.VM:
2032
+ aws_subnet_ids_with_availability_zones = associate_aws_subnets_with_azs(
2033
+ subnet_ids, region, self.log
2034
+ )
2035
+ else:
2036
+ aws_subnet_ids_with_availability_zones = None
2037
+
2038
+ # If memorydb cluster is provided, get the memorydb cluster config.
2039
+ if memorydb_cluster_id is not None:
2040
+ memorydb_cluster_config = _get_memorydb_cluster_config(
2041
+ memorydb_cluster_id, region, self.log
2042
+ )
2043
+ else:
2044
+ memorydb_cluster_config = None
2045
+
2046
+ self.cloud_event_producer.produce(
2047
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE, succeeded=True
2048
+ )
2049
+ except Exception as e: # noqa: BLE001
2050
+ error = str(e)
2051
+ self.log.error(error)
2052
+ error_msg_for_event = str(e)
2053
+ if isinstance(e, NoCredentialsError):
2054
+ # If it is a credentials error, rewrite the error to be more clear
2055
+ error = "Unable to locate AWS credentials. Cloud registration requires valid AWS credentials to be set locally. Learn more: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
2056
+ error_msg_for_event = "Unable to locate AWS credentials."
2057
+ self.cloud_event_producer.produce(
2058
+ CloudAnalyticsEventName.PREPROCESS_COMPLETE,
2059
+ succeeded=False,
2060
+ logger=self.log,
2061
+ internal_error=error_msg_for_event,
2062
+ )
2063
+ # Delete the cloud if registering the cloud fails
2064
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
2065
+ cloud_id=cloud_id
2066
+ )
2067
+ try:
2068
+ if iam_role_original_policy is not None and external_id is None:
2069
+ # Revert the assume policy back to the original policy
2070
+ role.AssumeRolePolicy().update( # type: ignore
2071
+ PolicyDocument=json.dumps(iam_role_original_policy)
2072
+ )
2073
+ except Exception as revert_error: # noqa: BLE001
2074
+ raise ClickException(
2075
+ f"Cloud registration failed! {error}. Failed to revert the trust policy back to the original policy. {revert_error}"
2076
+ )
2077
+ raise ClickException(f"Cloud registration failed! {error}")
2078
+
2079
+ aws_iam_role_arns = None
2080
+ if compute_stack == ComputeStack.VM:
2081
+ aws_iam_role_arns = [anyscale_iam_role_id, instance_iam_role_id]
2082
+ elif compute_stack == ComputeStack.K8S and anyscale_iam_role_id:
2083
+ aws_iam_role_arns = [anyscale_iam_role_id]
2084
+
2085
+ try:
2086
+ # Verify cloud resources meet our requirement
2087
+ create_cloud_resource = CreateCloudResource(
2088
+ aws_vpc_id=vpc_id,
2089
+ aws_subnet_ids_with_availability_zones=aws_subnet_ids_with_availability_zones,
2090
+ aws_iam_role_arns=aws_iam_role_arns,
2091
+ aws_security_groups=security_group_ids,
2092
+ aws_s3_id=cloud_storage_bucket_name[len(S3_STORAGE_PREFIX) :],
2093
+ aws_efs_id=efs_id,
2094
+ aws_efs_mount_target_ip=aws_efs_mount_target_ip,
2095
+ memorydb_cluster_config=memorydb_cluster_config,
2096
+ compute_stack=compute_stack,
2097
+ kubernetes_zones=kubernetes_zones,
2098
+ kubernetes_dataplane_identity=anyscale_operator_iam_identity,
2099
+ cloud_storage_bucket_name=cloud_storage_bucket_name,
2100
+ )
2101
+
2102
+ # Verification is only performed for VM compute stack.
2103
+ # TODO (shomilj): Add verification to the K8S compute stack as well.
2104
+ if compute_stack == ComputeStack.VM:
2105
+ with self.log.spinner("Verifying cloud resources...") as spinner:
2106
+ assert boto3_session is not None
2107
+ if not skip_verifications and not self.verify_aws_cloud_resources(
2108
+ cloud_resource=create_cloud_resource,
2109
+ boto3_session=boto3_session,
2110
+ region=region,
2111
+ is_bring_your_own_resource=True,
2112
+ is_private_network=private_network,
2113
+ cloud_id=cloud_id,
2114
+ logger=CloudSetupLogger(spinner_manager=spinner),
2115
+ ):
2116
+ raise ClickException(
2117
+ "Please make sure all the resources provided meet the requirements and try again."
2118
+ )
2119
+
2120
+ confirm(
2121
+ "Please review the output from verification for any warnings. Would you like to proceed with cloud creation?",
2122
+ yes,
2123
+ )
2124
+ self.cloud_event_producer.produce(
2125
+ CloudAnalyticsEventName.RESOURCES_VERIFIED, succeeded=True
2126
+ )
2127
+ # Since the verify runs for a while, it's possible the user aborts the process, which throws a KeyboardInterrupt.
2128
+ except (Exception, KeyboardInterrupt) as e: # noqa: BLE001
2129
+ self.log.error(str(e))
2130
+ internal_error = str(e)
2131
+ if isinstance(e, Abort):
2132
+ internal_error = "User aborted."
2133
+ self.cloud_event_producer.produce(
2134
+ CloudAnalyticsEventName.RESOURCES_VERIFIED,
2135
+ succeeded=False,
2136
+ logger=self.log,
2137
+ internal_error=internal_error,
2138
+ )
2139
+ # Delete the cloud if registering the cloud fails
2140
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
2141
+ cloud_id=cloud_id
2142
+ )
2143
+ try:
2144
+ if iam_role_original_policy is not None and external_id is None:
2145
+ # Revert the assume policy back to the original policy
2146
+ role.AssumeRolePolicy().update( # type: ignore
2147
+ PolicyDocument=json.dumps(iam_role_original_policy)
2148
+ )
2149
+ except Exception as revert_error: # noqa: BLE001
2150
+ raise ClickException(
2151
+ f"Cloud registration failed! {e}. Failed to revert the trust policy back to the original policy. {revert_error}"
2152
+ )
2153
+
2154
+ raise ClickException(f"Cloud registration failed! {e}")
2155
+
2156
+ try:
2157
+ with self.log.spinner(
2158
+ "Updating Anyscale cloud with cloud resource..."
2159
+ ) as spinner:
2160
+ # update cloud with verified cloud resources
2161
+ cloud_resource = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
2162
+ cloud_id=cloud_id,
2163
+ update_cloud_with_cloud_resource=UpdateCloudWithCloudResource(
2164
+ cloud_resource_to_update=create_cloud_resource,
2165
+ ),
2166
+ )
2167
+ # For now, only wait for the cloud to be active if the compute stack is VM.
2168
+ # TODO (shomilj): support this fully for Kubernetes after provider metadata
2169
+ # checks are removed.
2170
+ if compute_stack == ComputeStack.VM:
2171
+ self.wait_for_cloud_to_be_active(cloud_id, CloudProviders.AWS)
2172
+ if compute_stack == ComputeStack.K8S:
2173
+ cloud_resource_id = cloud_resource.result.cloud_resource.id
2174
+ self.log.info(
2175
+ f"For registering this cloud's Kubernetes Manager, use cloud deployment ID '{cloud_resource_id}'."
2176
+ )
2177
+ self.cloud_event_producer.produce(
2178
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True
2179
+ )
2180
+ # Since the update runs for a while, it's possible the user aborts the process, which throws a KeyboardInterrupt.
2181
+ except (Exception, KeyboardInterrupt) as e: # noqa: BLE001
2182
+ # Delete the cloud if registering the cloud fails
2183
+ self.log.error(str(e))
2184
+ self.cloud_event_producer.produce(
2185
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
2186
+ succeeded=False,
2187
+ internal_error=str(e),
2188
+ )
2189
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
2190
+ cloud_id=cloud_id
2191
+ )
2192
+ try:
2193
+ if iam_role_original_policy is not None and external_id is None:
2194
+ # Revert the assume policy back to the original policy
2195
+ role.AssumeRolePolicy().update( # type: ignore
2196
+ PolicyDocument=json.dumps(iam_role_original_policy)
2197
+ )
2198
+ except Exception as revert_error: # noqa: BLE001
2199
+ raise ClickException(
2200
+ f"Cloud registration failed! {e}. Failed to revert the trust policy back to the original policy. {revert_error}"
2201
+ )
2202
+
2203
+ raise ClickException(f"Cloud registration failed! {e}")
2204
+
2205
+ self.log.info(f"Successfully created cloud {name}, and it's ready to use.")
2206
+
2207
+ if len(functions_to_verify) > 0:
2208
+ CloudFunctionalVerificationController(
2209
+ self.cloud_event_producer, self.log
2210
+ ).start_verification(cloud_id, CloudProviders.AWS, functions_to_verify, yes)
2211
+
2212
+ def verify_gcp_cloud_resources(
2213
+ self,
2214
+ *,
2215
+ cloud_resource: CreateCloudResourceGCP,
2216
+ project_id: str,
2217
+ region: str,
2218
+ cloud_id: str,
2219
+ yes: bool,
2220
+ host_project_id: Optional[str] = None,
2221
+ factory: Any = None,
2222
+ strict: bool = False,
2223
+ is_private_service_cloud: bool = False,
2224
+ ) -> bool:
2225
+ gcp_utils = try_import_gcp_utils()
2226
+ if not factory:
2227
+ factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
2228
+ GCPLogger = gcp_utils.GCPLogger
2229
+ verify_lib = try_import_gcp_verify_lib()
2230
+
2231
+ network_project_id = host_project_id if host_project_id else project_id
2232
+ use_shared_vpc = bool(host_project_id)
2233
+
2234
+ with self.log.spinner("Verifying cloud resources...") as spinner:
2235
+ gcp_logger = GCPLogger(self.log, project_id, spinner, yes)
2236
+ verify_gcp_project_result = verify_lib.verify_gcp_project(
2237
+ factory, cloud_resource, project_id, gcp_logger, strict=strict
2238
+ )
2239
+ verify_gcp_access_service_account_result = verify_lib.verify_gcp_access_service_account(
2240
+ factory, cloud_resource, project_id, gcp_logger
2241
+ )
2242
+ verify_gcp_dataplane_service_account_result = verify_lib.verify_gcp_dataplane_service_account(
2243
+ factory, cloud_resource, project_id, gcp_logger, strict=strict
2244
+ )
2245
+ verify_gcp_networking_result = verify_lib.verify_gcp_networking(
2246
+ factory,
2247
+ cloud_resource,
2248
+ network_project_id,
2249
+ region,
2250
+ gcp_logger,
2251
+ strict=strict,
2252
+ is_private_service_cloud=is_private_service_cloud,
2253
+ )
2254
+ verify_firewall_policy_result = verify_lib.verify_firewall_policy(
2255
+ factory,
2256
+ cloud_resource,
2257
+ network_project_id,
2258
+ region,
2259
+ use_shared_vpc,
2260
+ is_private_service_cloud,
2261
+ gcp_logger,
2262
+ strict=strict,
2263
+ )
2264
+ verify_filestore_result = verify_lib.verify_filestore(
2265
+ factory, cloud_resource, region, gcp_logger, strict=strict
2266
+ )
2267
+ verify_cloud_storage_result = verify_lib.verify_cloud_storage(
2268
+ factory, cloud_resource, project_id, region, gcp_logger, strict=strict,
2269
+ )
2270
+ verify_anyscale_access_result = verify_anyscale_access(
2271
+ self.api_client, cloud_id, CloudProviders.GCP, self.log
2272
+ )
2273
+ verify_memorystore_result = True
2274
+ if cloud_resource.memorystore_instance_config is not None:
2275
+ verify_memorystore_result = verify_lib.verify_memorystore(
2276
+ factory, cloud_resource, gcp_logger, strict=strict,
2277
+ )
2278
+
2279
+ verification_results = [
2280
+ "Verification result:",
2281
+ f"anyscale access: {self._passed_or_failed_str_from_bool(verify_anyscale_access_result)}",
2282
+ f"project: {self._passed_or_failed_str_from_bool(verify_gcp_project_result)}",
2283
+ f"vpc and subnet: {self._passed_or_failed_str_from_bool(verify_gcp_networking_result)}",
2284
+ f"anyscale iam service account: {self._passed_or_failed_str_from_bool(verify_gcp_access_service_account_result)}",
2285
+ f"cluster node service account: {self._passed_or_failed_str_from_bool(verify_gcp_dataplane_service_account_result)}",
2286
+ f"firewall policy: {self._passed_or_failed_str_from_bool(verify_firewall_policy_result)}",
2287
+ f"filestore: {self._passed_or_failed_str_from_bool(verify_filestore_result)}",
2288
+ f"cloud storage: {self._passed_or_failed_str_from_bool(verify_cloud_storage_result)}",
2289
+ ]
2290
+
2291
+ if cloud_resource.memorystore_instance_config is not None:
2292
+ verification_results.append(
2293
+ f"memorystore: {self._passed_or_failed_str_from_bool(verify_memorystore_result)}"
2294
+ )
2295
+
2296
+ self.log.info("\n".join(verification_results))
2297
+
2298
+ return all(
2299
+ [
2300
+ verify_anyscale_access_result,
2301
+ verify_gcp_project_result,
2302
+ verify_gcp_access_service_account_result,
2303
+ verify_gcp_dataplane_service_account_result,
2304
+ verify_gcp_networking_result,
2305
+ verify_firewall_policy_result,
2306
+ verify_filestore_result,
2307
+ verify_cloud_storage_result,
2308
+ verify_memorystore_result,
2309
+ ]
2310
+ )
2311
+
2312
+ def register_gcp_cloud( # noqa: PLR0913, PLR0912, C901
2313
+ self,
2314
+ *,
2315
+ region: str,
2316
+ name: str,
2317
+ project_id: str,
2318
+ vpc_name: str,
2319
+ subnet_names: List[str],
2320
+ filestore_instance_id: str,
2321
+ filestore_location: str,
2322
+ anyscale_service_account_email: str,
2323
+ instance_service_account_email: str,
2324
+ provider_id: str,
2325
+ firewall_policy_names: List[str],
2326
+ cloud_storage_bucket_name: str,
2327
+ memorystore_instance_name: Optional[str],
2328
+ functional_verify: Optional[str],
2329
+ private_network: bool,
2330
+ cluster_management_stack_version: ClusterManagementStackVersions,
2331
+ host_project_id: Optional[str] = None,
2332
+ yes: bool = False,
2333
+ skip_verifications: bool = False,
2334
+ auto_add_user: bool = False,
2335
+ # Default to ComputeStack.VM for backwards compatibility
2336
+ # for SDK users who do not specify a compute stack here.
2337
+ compute_stack: ComputeStack = ComputeStack.VM,
2338
+ kubernetes_zones: Optional[List[str]] = None,
2339
+ anyscale_operator_iam_identity: Optional[str] = None,
2340
+ ):
2341
+ functions_to_verify = self._validate_functional_verification_args(
2342
+ functional_verify
2343
+ )
2344
+ gcp_utils = try_import_gcp_utils()
2345
+
2346
+ # Create a cloud without cloud resources first
2347
+ # Provider ID is optional for K8s clouds.
2348
+ if compute_stack != ComputeStack.K8S:
2349
+ provider_id_re_result = re.search(
2350
+ "projects\\/[0-9]*\\/locations\\/global\\/workloadIdentityPools\\/.+\\/providers\\/[a-z0-9-]*$",
2351
+ provider_id,
2352
+ )
2353
+ if provider_id_re_result is None:
2354
+ raise ClickException(
2355
+ f"Invalid provider_id {provider_id}. Only lowercase letters, numbers, and dashes are allowed."
2356
+ )
2357
+
2358
+ self.cloud_event_producer.init_trace_context(
2359
+ CloudAnalyticsEventCommandName.REGISTER, CloudProviders.GCP
2360
+ )
2361
+ self.cloud_event_producer.produce(
2362
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True
2363
+ )
2364
+
2365
+ try:
2366
+ credentials_dict = {
2367
+ "project_id": project_id or "",
2368
+ "provider_id": provider_id or "",
2369
+ "service_account_email": anyscale_service_account_email or "",
2370
+ }
2371
+ if host_project_id:
2372
+ credentials_dict["host_project_id"] = host_project_id
2373
+ credentials = json.dumps(credentials_dict)
2374
+
2375
+ if compute_stack == ComputeStack.K8S:
2376
+ # On K8S, we don't need to collect credentials;
2377
+ # instead, write a random trace_id into this field
2378
+ # to maintain the property that each cloud's
2379
+ # credentials are unique.
2380
+ random_id = uuid.uuid4().hex
2381
+ credentials = json.dumps(
2382
+ {
2383
+ "provider_id": "",
2384
+ "project_id": random_id,
2385
+ "service_account_email": random_id,
2386
+ }
2387
+ )
2388
+
2389
+ # NOTE: For now we set the is_private_service_cloud to be the same as is_private_cloud
2390
+ # We don't expose this to the user yet since it's not recommended.
2391
+ is_private_service_cloud = private_network
2392
+
2393
+ created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
2394
+ write_cloud=WriteCloud(
2395
+ provider="GCP",
2396
+ region=region,
2397
+ credentials=credentials,
2398
+ name=name,
2399
+ is_bring_your_own_resource=True,
2400
+ is_private_cloud=private_network,
2401
+ cluster_management_stack_version=cluster_management_stack_version,
2402
+ is_private_service_cloud=is_private_service_cloud,
2403
+ auto_add_user=auto_add_user,
2404
+ )
2405
+ )
2406
+ cloud_id = created_cloud.result.id
2407
+ self.cloud_event_producer.set_cloud_id(cloud_id)
2408
+ self.cloud_event_producer.produce(
2409
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True
2410
+ )
2411
+ except ClickException as e:
2412
+ self.cloud_event_producer.produce(
2413
+ CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
2414
+ succeeded=False,
2415
+ internal_error=str(e),
2416
+ )
2417
+ raise
2418
+
2419
+ try:
2420
+ # Set defaults for Kubernetes clouds.
2421
+ if compute_stack == ComputeStack.K8S:
2422
+ instance_service_account_email = ""
2423
+ subnet_names = []
2424
+
2425
+ enable_filestore = compute_stack == ComputeStack.VM or (
2426
+ filestore_location and filestore_instance_id
2427
+ )
2428
+
2429
+ # Normally, for Kubernetes clouds, we don't need a VPC name, since networking is managed by Kubernetes.
2430
+ # For Kubernetes clouds on GCP where Filestore is enabled, we require the VPC name, since it is needed
2431
+ # to look up the relevant Mount Target IP for Filestore in the VPC.
2432
+ if compute_stack == ComputeStack.K8S:
2433
+ if enable_filestore and not vpc_name:
2434
+ raise ClickException(
2435
+ "Please provide the name of the VPC that your Kubernetes cluster is running inside of."
2436
+ )
2437
+ if (enable_filestore or memorystore_instance_name) and not project_id:
2438
+ raise ClickException("Please provide a project ID.")
2439
+
2440
+ if project_id:
2441
+ factory = gcp_utils.get_google_cloud_client_factory(
2442
+ self.log, project_id
2443
+ )
2444
+
2445
+ if enable_filestore:
2446
+ filestore_config = gcp_utils.get_gcp_filestore_config(
2447
+ factory,
2448
+ project_id,
2449
+ vpc_name,
2450
+ filestore_location,
2451
+ filestore_instance_id,
2452
+ self.log,
2453
+ )
2454
+ else:
2455
+ filestore_config = GCPFileStoreConfig(
2456
+ instance_name="", mount_target_ip="", root_dir=""
2457
+ )
2458
+ vpc_name = ""
2459
+
2460
+ if memorystore_instance_name:
2461
+ memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
2462
+ factory, memorystore_instance_name
2463
+ )
2464
+ else:
2465
+ memorystore_instance_config = None
2466
+
2467
+ if not cloud_storage_bucket_name.startswith(GCS_STORAGE_PREFIX):
2468
+ cloud_storage_bucket_name = (
2469
+ GCS_STORAGE_PREFIX + cloud_storage_bucket_name
2470
+ )
2471
+
2472
+ # Verify cloud resources meet our requirement
2473
+ create_cloud_resource_gcp = CreateCloudResourceGCP(
2474
+ gcp_vpc_id=vpc_name,
2475
+ gcp_subnet_ids=subnet_names,
2476
+ gcp_cluster_node_service_account_email=instance_service_account_email,
2477
+ gcp_anyscale_iam_service_account_email=anyscale_service_account_email
2478
+ or "",
2479
+ gcp_filestore_config=filestore_config,
2480
+ gcp_firewall_policy_ids=firewall_policy_names,
2481
+ gcp_cloud_storage_bucket_id=cloud_storage_bucket_name[
2482
+ len(GCS_STORAGE_PREFIX) :
2483
+ ],
2484
+ memorystore_instance_config=memorystore_instance_config,
2485
+ compute_stack=compute_stack,
2486
+ kubernetes_zones=kubernetes_zones,
2487
+ kubernetes_dataplane_identity=anyscale_operator_iam_identity,
2488
+ cloud_storage_bucket_name=cloud_storage_bucket_name,
2489
+ )
2490
+
2491
+ # Verification is only performed for VM compute stack.
2492
+ # TODO (shomilj): Add verification to the K8S compute stack as well.
2493
+ if compute_stack == ComputeStack.VM:
2494
+ if not skip_verifications and not self.verify_gcp_cloud_resources(
2495
+ cloud_resource=create_cloud_resource_gcp,
2496
+ project_id=project_id,
2497
+ host_project_id=host_project_id,
2498
+ region=region,
2499
+ cloud_id=cloud_id,
2500
+ yes=yes,
2501
+ factory=factory,
2502
+ is_private_service_cloud=is_private_service_cloud,
2503
+ ):
2504
+ raise ClickException(
2505
+ "Please make sure all the resources provided meet the requirements and try again."
2506
+ )
2507
+
2508
+ confirm(
2509
+ "Please review the output from verification for any warnings. Would you like to proceed with cloud creation?",
2510
+ yes,
2511
+ )
2512
+ self.cloud_event_producer.produce(
2513
+ CloudAnalyticsEventName.RESOURCES_VERIFIED, succeeded=True
2514
+ )
2515
+ except Exception as e: # noqa: BLE001
2516
+ self.log.error(str(e))
2517
+ internal_error = str(e)
2518
+ if isinstance(e, Abort):
2519
+ internal_error = "User aborted."
2520
+ self.cloud_event_producer.produce(
2521
+ CloudAnalyticsEventName.RESOURCES_VERIFIED,
2522
+ succeeded=False,
2523
+ logger=self.log,
2524
+ internal_error=internal_error,
2525
+ )
2526
+
2527
+ # Delete the cloud if registering the cloud fails
2528
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
2529
+ cloud_id=cloud_id
2530
+ )
2531
+ raise ClickException(f"Cloud registration failed! {e}")
2532
+
2533
+ try:
2534
+ # update cloud with verified cloud resources
2535
+ with self.log.spinner("Updating Anyscale cloud with cloud resources..."):
2536
+ cloud_resource = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_put(
2537
+ cloud_id=cloud_id,
2538
+ update_cloud_with_cloud_resource_gcp=UpdateCloudWithCloudResourceGCP(
2539
+ cloud_resource_to_update=create_cloud_resource_gcp,
2540
+ ),
2541
+ )
2542
+ # For now, only wait for the cloud to be active if the compute stack is VM.
2543
+ # TODO (shomilj): support this fully for Kubernetes after provider metadata
2544
+ # checks are removed.
2545
+ if compute_stack == ComputeStack.VM:
2546
+ self.wait_for_cloud_to_be_active(cloud_id, CloudProviders.GCP)
2547
+ if compute_stack == ComputeStack.K8S:
2548
+ cloud_resource_id = cloud_resource.result.cloud_resource.id
2549
+ self.log.info(
2550
+ f"For registering this cloud's Kubernetes Manager, use cloud deployment ID '{cloud_resource_id}'."
2551
+ )
2552
+ self.cloud_event_producer.produce(
2553
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True
2554
+ )
2555
+ except Exception as e: # noqa: BLE001
2556
+ self.log.error(str(e))
2557
+ self.cloud_event_producer.produce(
2558
+ CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
2559
+ succeeded=False,
2560
+ internal_error=str(e),
2561
+ )
2562
+ # Delete the cloud if registering the cloud fails
2563
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
2564
+ cloud_id=cloud_id
2565
+ )
2566
+ raise ClickException(f"Cloud registration failed! {e}")
2567
+
2568
+ self.log.info(f"Successfully created cloud {name}, and it's ready to use.")
2569
+
2570
+ if len(functions_to_verify) > 0:
2571
+ CloudFunctionalVerificationController(
2572
+ self.cloud_event_producer, self.log
2573
+ ).start_verification(cloud_id, CloudProviders.GCP, functions_to_verify, yes)
2574
+
2575
+ def delete_cloud( # noqa: PLR0912, C901
2576
+ self,
2577
+ cloud_name: Optional[str],
2578
+ cloud_id: Optional[str],
2579
+ skip_confirmation: bool,
2580
+ ) -> bool:
2581
+ """
2582
+ Deletes a cloud by name or id.
2583
+ TODO Delete all GCE resources on cloud delete
2584
+ Including: Anyscale maanged resources, ALB resources, and TLS certs
2585
+ """
2586
+ cloud_id, cloud_name = get_cloud_id_and_name(
2587
+ self.api_client, cloud_id, cloud_name
2588
+ )
2589
+
2590
+ # get cloud
2591
+ cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(
2592
+ cloud_id=cloud_id
2593
+ ).result
2594
+
2595
+ cloud_provider = cloud.provider
2596
+ assert cloud_provider in (
2597
+ CloudProviders.AWS,
2598
+ CloudProviders.GCP,
2599
+ CloudProviders.GENERIC,
2600
+ ), f"Cloud provider {cloud_provider} not supported yet"
2601
+
2602
+ cloud_resource = get_cloud_resource_by_cloud_id(
2603
+ cloud_id, cloud.provider, self.api_client
2604
+ )
2605
+ if cloud_resource is None:
2606
+ # no cloud resource found, directly delete the cloud
2607
+ try:
2608
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
2609
+ cloud_id=cloud_id
2610
+ )
2611
+ except ClickException as e:
2612
+ self.log.error(e)
2613
+ raise ClickException(f"Failed to delete cloud with name {cloud_name}.")
2614
+
2615
+ self.log.info(f"Deleted cloud with name {cloud_name}.")
2616
+ return True
2617
+
2618
+ confirmation_msg = f"\nIf the cloud {cloud_id} is deleted, you will not be able to access existing clusters of this cloud.\n"
2619
+ if cloud.is_bring_your_own_resource:
2620
+ confirmation_msg += "Note that Anyscale does not delete any of the cloud provider resources created by you.\n"
2621
+
2622
+ confirmation_msg += "For more information, refer to the documentation "
2623
+ if cloud_provider == CloudProviders.AWS:
2624
+ confirmation_msg += "https://docs.anyscale.com/administration/cloud-deployment/manage-aws-cloud#delete-an-anyscale-cloud\n"
2625
+ elif cloud_provider == CloudProviders.GCP:
2626
+ confirmation_msg += "https://docs.anyscale.com/administration/cloud-deployment/manage-gcp-cloud#delete-an-anyscale-cloud\n"
2627
+
2628
+ confirmation_msg += "Continue?"
2629
+ confirm(confirmation_msg, skip_confirmation)
2630
+
2631
+ # set cloud state to DELETING
2632
+ try:
2633
+ if (
2634
+ cloud_provider == CloudProviders.GENERIC
2635
+ or cloud_provider == CloudProviders.AWS
2636
+ ):
2637
+ with self.log.spinner("Preparing to delete Anyscale cloud..."):
2638
+ response = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
2639
+ cloud_id=cloud_id,
2640
+ update_cloud_with_cloud_resource=UpdateCloudWithCloudResource(
2641
+ state=CloudState.DELETING
2642
+ ),
2643
+ )
2644
+ elif cloud_provider == CloudProviders.GCP:
2645
+ with self.log.spinner("Preparing to delete Anyscale cloud..."):
2646
+ response = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_put(
2647
+ cloud_id=cloud_id,
2648
+ update_cloud_with_cloud_resource_gcp=UpdateCloudWithCloudResourceGCP(
2649
+ state=CloudState.DELETING
2650
+ ),
2651
+ )
2652
+
2653
+ cloud = response.result
2654
+ except ClickException as e:
2655
+ raise ClickException(
2656
+ f"Failed to update cloud state to deleting for cloud {cloud_name}: {e}"
2657
+ )
2658
+
2659
+ # Clean up cloud resources
2660
+ try:
2661
+ if cloud_provider == CloudProviders.AWS:
2662
+ self.delete_all_aws_resources(cloud)
2663
+ elif cloud_provider == CloudProviders.GCP:
2664
+ with self.log.spinner("Deleting load balancing resources..."):
2665
+ wait_for_gcp_lb_resource_termination(
2666
+ api_client=self.api_client, cloud_id=cloud_id
2667
+ )
2668
+ self.delete_all_gcp_resources(cloud)
2669
+ except Exception as e: # noqa: BLE001
2670
+ confirm(
2671
+ f"Error while trying to clean up {cloud_provider} resources:\n{e}\n"
2672
+ f"Please check your {cloud_provider} account for relevant errors.\n"
2673
+ "Do you want to force delete this cloud? You will need to clean up any associated resources on your own.\n"
2674
+ "Continue with force deletion?",
2675
+ skip_confirmation,
2676
+ )
2677
+
2678
+ # Tear down admin zone and mark cloud as deleted
2679
+ with self.log.spinner("Deleting Anyscale cloud..."):
2680
+ try:
2681
+ self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
2682
+ cloud_id=cloud_id
2683
+ )
2684
+ except ClickException as e:
2685
+ self.log.error(e)
2686
+ raise ClickException(f"Failed to delete cloud with name {cloud_name}.")
2687
+
2688
+ self.log.info(f"Deleted cloud with name {cloud_name}.")
2689
+ return True
2690
+
2691
+ def delete_all_gcp_resources(self, cloud: CloudWithCloudResource):
2692
+ if cloud.is_aioa or cloud.compute_stack == ComputeStack.K8S:
2693
+ # No resources to delete for hosted and k8s clouds
2694
+ return True
2695
+
2696
+ setup_utils = try_import_gcp_managed_setup_utils()
2697
+ gcp_utils = try_import_gcp_utils()
2698
+ credentials = json.loads(cloud.credentials)
2699
+ provider = credentials["gcp_workload_identity_pool_id"]
2700
+ service_account = credentials["service_account_email"]
2701
+ project_id = credentials["project_id"]
2702
+ factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
2703
+
2704
+ # Delete services resources
2705
+ setup_utils.delete_gcp_tls_certificates(factory, project_id, cloud.id)
2706
+
2707
+ # Clean up cloud resources
2708
+ if cloud.is_bring_your_own_resource is False: # managed cloud
2709
+ self.delete_gcp_managed_cloud(cloud=cloud)
2710
+ else:
2711
+ self.log.warning(
2712
+ f"The workload identity federation provider pool {provider} and service account {service_account} that allows Anyscale to access your GCP account is still in place. Please delete it manually if you no longer wish anyscale to have access."
2713
+ )
2714
+
2715
+ return True
2716
+
2717
+ def delete_all_aws_resources(self, cloud: CloudWithCloudResource) -> bool:
2718
+ if cloud.is_aioa or cloud.compute_stack == ComputeStack.K8S:
2719
+ # No resources to delete for hosted and k8s clouds
2720
+ return True
2721
+
2722
+ # Delete services resources
2723
+ self.delete_aws_lb_cfn_stack(cloud=cloud)
2724
+ self.delete_aws_tls_certificates(cloud=cloud)
2725
+
2726
+ # Clean up cloud resources
2727
+ if cloud.is_bring_your_own_resource is False: # managed cloud
2728
+ # Delete AWS cloud resources by deleting the cfn stack
2729
+ self.delete_aws_managed_cloud(cloud=cloud)
2730
+ else: # registered cloud
2731
+ self.log.warning(
2732
+ f"The trust policy that allows Anyscale to assume {cloud.credentials} is still in place. Please delete it manually if you no longer wish anyscale to have access."
2733
+ )
2734
+
2735
+ return True
2736
+
2737
+ def delete_aws_lb_cfn_stack(self, cloud: CloudWithCloudResource) -> bool:
2738
+
2739
+ tag_name = "anyscale-cloud-id"
2740
+ key_name = cloud.id
2741
+
2742
+ cfn_client = _client("cloudformation", cloud.region)
2743
+ # Get a list of all the CloudFormation stacks with the specified tag
2744
+ stacks = cfn_client.list_stacks()
2745
+
2746
+ stacks = _unroll_resources_for_aws_list_call(
2747
+ cfn_client.list_stacks, "StackSummaries"
2748
+ )
2749
+
2750
+ resources_to_cleanup = []
2751
+
2752
+ for stack in stacks:
2753
+ if "StackName" in stack and "StackId" in stack:
2754
+ cfn_stack_arn = stack["StackId"]
2755
+ stack_description = cfn_client.describe_stacks(StackName=cfn_stack_arn)
2756
+
2757
+ # Extract the tags from the response
2758
+ if (
2759
+ "Stacks" in stack_description
2760
+ and stack_description["Stacks"]
2761
+ and "Tags" in stack_description["Stacks"][0]
2762
+ ):
2763
+ tags = stack_description["Stacks"][0]["Tags"]
2764
+ for tag in tags:
2765
+ if (
2766
+ "Key" in tag
2767
+ and tag["Key"] == tag_name
2768
+ and "Value" in tag
2769
+ and tag["Value"] == key_name
2770
+ ):
2771
+ resources_to_cleanup.append(cfn_stack_arn)
2772
+
2773
+ resource_delete_status = []
2774
+ for cfn_stack_arn in resources_to_cleanup:
2775
+ resource_delete_status.append(
2776
+ self.delete_aws_cloudformation_stack(
2777
+ cfn_stack_arn=cfn_stack_arn, cloud=cloud
2778
+ )
2779
+ )
2780
+
2781
+ return all(resource_delete_status)
2782
+
2783
+ def delete_aws_tls_certificates(self, cloud: CloudWithCloudResource) -> bool:
2784
+ tag_name = "anyscale-cloud-id"
2785
+ key_name = cloud.id
2786
+
2787
+ acm = boto3.client("acm", cloud.region)
2788
+
2789
+ certificates = _unroll_resources_for_aws_list_call(
2790
+ acm.list_certificates, "CertificateSummaryList"
2791
+ )
2792
+
2793
+ matching_certs = []
2794
+
2795
+ for certificate in certificates:
2796
+ if "CertificateArn" in certificate:
2797
+
2798
+ certificate_arn = certificate["CertificateArn"]
2799
+ response = acm.list_tags_for_certificate(CertificateArn=certificate_arn)
2800
+
2801
+ if "Tags" in response:
2802
+ tags = response["Tags"]
2803
+ for tag in tags:
2804
+ if (
2805
+ "Key" in tag
2806
+ and tag["Key"] == tag_name
2807
+ and "Value" in tag
2808
+ and tag["Value"] == key_name
2809
+ ):
2810
+ matching_certs.append(certificate_arn)
2811
+
2812
+ resource_delete_status = []
2813
+ for certificate_arn in matching_certs:
2814
+ resource_delete_status.append(self.delete_tls_cert(certificate_arn, cloud))
2815
+ return all(resource_delete_status)
2816
+
2817
+ def delete_aws_managed_cloud(self, cloud: CloudWithCloudResource) -> bool:
2818
+ if (
2819
+ not cloud.cloud_resource
2820
+ or not cloud.cloud_resource.aws_cloudformation_stack_id
2821
+ ):
2822
+ raise ClickException(
2823
+ f"This cloud {cloud.id} does not have a cloudformation stack."
2824
+ )
2825
+
2826
+ cfn_stack_arn = cloud.cloud_resource.aws_cloudformation_stack_id
2827
+
2828
+ # If the cloud is updated, the cross account IAM role might have an inline policy for customer drifts
2829
+ # We delete the inline policy first otherwise cfn stack deletion would fail
2830
+ try_delete_customer_drifts_policy(cloud=cloud)
2831
+
2832
+ self.log.info(
2833
+ f"\nThe S3 bucket ({cloud.cloud_resource.aws_s3_id}) associated with this cloud still exists."
2834
+ "\nIf you no longer need the data associated with this bucket, please delete it."
2835
+ )
2836
+
2837
+ return self.delete_aws_cloudformation_stack(
2838
+ cfn_stack_arn=cfn_stack_arn, cloud=cloud
2839
+ )
2840
+
2841
+ def delete_aws_cloudformation_stack(
2842
+ self, cfn_stack_arn: str, cloud: CloudWithCloudResource
2843
+ ) -> bool:
2844
+ cfn_client = _client("cloudformation", cloud.region)
2845
+
2846
+ cfn_stack_url = f"https://{cloud.region}.console.aws.amazon.com/cloudformation/home?region={cloud.region}#/stacks/stackinfo?stackId={cfn_stack_arn}"
2847
+
2848
+ try:
2849
+ cfn_client.delete_stack(StackName=cfn_stack_arn)
2850
+ except ClientError:
2851
+ raise ClickException(
2852
+ f"Failed to delete cloudformation stack {cfn_stack_arn}.\nPlease view it at {cfn_stack_url}"
2853
+ ) from None
2854
+
2855
+ self.log.info(f"\nTrack progress of cloudformation at {cfn_stack_url}")
2856
+ with self.log.spinner(
2857
+ f"Deleting cloud resource {cfn_stack_arn} through cloudformation..."
2858
+ ):
2859
+ end_time = time.time() + CLOUDFORMATION_TIMEOUT_SECONDS_LONG
2860
+ while time.time() < end_time:
2861
+ try:
2862
+ cfn_stack = cfn_client.describe_stacks(StackName=cfn_stack_arn)[
2863
+ "Stacks"
2864
+ ][0]
2865
+ except ClientError as e:
2866
+ raise ClickException(
2867
+ f"Failed to fetch the cloudformation stack {cfn_stack_arn}. Please check you have the right AWS credentials and the cloudformation stack still exists. Error details: {e}"
2868
+ ) from None
2869
+
2870
+ if cfn_stack["StackStatus"] == "DELETE_COMPLETE":
2871
+ self.log.info(
2872
+ f"Cloudformation stack {cfn_stack['StackId']} is deleted."
2873
+ )
2874
+ break
2875
+
2876
+ if cfn_stack["StackStatus"] in ("DELETE_FAILED"):
2877
+ # Provide link to cloudformation
2878
+ raise ClickException(
2879
+ f"Failed to delete cloud resources. Please check your cloudformation stack for errors. {cfn_stack_url}"
2880
+ )
2881
+ time.sleep(1)
2882
+
2883
+ if time.time() > end_time:
2884
+ raise ClickException(
2885
+ f"Timed out deleting AWS resources. Please check your cloudformation stack for errors. {cfn_stack['StackId']}"
2886
+ )
2887
+
2888
+ return True
2889
+
2890
+ def delete_tls_cert(
2891
+ self, certificate_arn: str, cloud: CloudWithCloudResource
2892
+ ) -> bool:
2893
+ acm = boto3.client("acm", cloud.region)
2894
+
2895
+ try:
2896
+ acm.delete_certificate(CertificateArn=certificate_arn)
2897
+ except ClientError as e:
2898
+ raise ClickException(
2899
+ f"Failed to delete TLS certificate {certificate_arn}: {e}"
2900
+ ) from None
2901
+
2902
+ return True
2903
+
2904
+ def delete_gcp_managed_cloud(self, cloud: CloudWithCloudResourceGCP) -> bool:
2905
+ if (
2906
+ not cloud.cloud_resource
2907
+ or not cloud.cloud_resource.gcp_deployment_manager_id
2908
+ ):
2909
+ raise ClickException(
2910
+ f"This cloud {cloud.id} does not have a deployment in GCP deployment manager."
2911
+ )
2912
+ setup_utils = try_import_gcp_managed_setup_utils()
2913
+ gcp_utils = try_import_gcp_utils()
2914
+
2915
+ project_id = json.loads(cloud.credentials)["project_id"]
2916
+ factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
2917
+ deployment_name = cloud.cloud_resource.gcp_deployment_manager_id
2918
+ deployment_url = f"https://console.cloud.google.com/dm/deployments/details/{deployment_name}?project={project_id}"
2919
+
2920
+ self.log.info(f"\nTrack progress of Deployment Manager at {deployment_url}")
2921
+
2922
+ with self.log.spinner("Deleting cloud resources through Deployment Manager..."):
2923
+
2924
+ # Remove firewall policy ids
2925
+ if cloud.cloud_resource.gcp_firewall_policy_ids:
2926
+ for firewall_policy in cloud.cloud_resource.gcp_firewall_policy_ids:
2927
+ # try delete the associations
2928
+ setup_utils.remove_firewall_policy_associations(
2929
+ factory, project_id, firewall_policy
2930
+ )
2931
+
2932
+ # Delete the deployment
2933
+ setup_utils.update_deployment_with_bucket_only(
2934
+ factory, project_id, deployment_name
2935
+ )
2936
+
2937
+ self.log.info(
2938
+ f"\nThe cloud bucket ({cloud.cloud_resource.gcp_cloud_storage_bucket_id}) associated with this cloud still exists."
2939
+ "\nIf you no longer need the data associated with this bucket, please delete it."
2940
+ )
2941
+ return True
2942
+
2943
+ ### Edit cloud ###
2944
+ def _get_cloud_resource_value(self, cloud_resource: Any, resource_type: str) -> Any:
2945
+ # Special case -- memorydb_cluster_id
2946
+ if resource_type == "memorydb_cluster_id":
2947
+ memorydb_cluster_config = getattr(
2948
+ cloud_resource, "memorydb_cluster_config", None
2949
+ )
2950
+ if memorydb_cluster_config is None:
2951
+ return None
2952
+ else:
2953
+ # memorydb_cluster_config.id has format "arn:aws:memorydb:us-east-2:815664363732:cluster/cloud-edit-test".
2954
+ value = memorydb_cluster_config.id.split("/")[-1]
2955
+ return value
2956
+
2957
+ # Special case -- memorystore_instance_name
2958
+ if resource_type == "memorystore_instance_name":
2959
+ memorystore_instance_config = getattr(
2960
+ cloud_resource, "memorystore_instance_config", None
2961
+ )
2962
+ if memorystore_instance_config is None:
2963
+ return None
2964
+ else:
2965
+ value = memorystore_instance_config.name
2966
+ return value
2967
+
2968
+ # Normal cases.
2969
+ value = getattr(cloud_resource, resource_type, None)
2970
+ if value is None:
2971
+ self.log.warning(f"Old value of {resource_type} is None.")
2972
+ return None
2973
+ return value
2974
+
2975
+ def _generate_edit_details_logs(
2976
+ self, cloud_resource: Any, edit_details: Dict[str, Optional[str]]
2977
+ ) -> List[str]:
2978
+ details_logs = []
2979
+ for resource_type, value in edit_details.items():
2980
+ if value:
2981
+ old_value = self._get_cloud_resource_value(
2982
+ cloud_resource, resource_type
2983
+ )
2984
+ if old_value == value:
2985
+ raise ClickException(
2986
+ f"Specified resource is the same as existed resource -- {resource_type}: {value}"
2987
+ )
2988
+ details_logs.append(f"{resource_type}: from {old_value} -> {value}")
2989
+ return details_logs
2990
+
2991
+ def _generate_rollback_command(
2992
+ self, cloud_id: str, cloud_resource: Any, edit_details: Dict[str, Optional[str]]
2993
+ ) -> str:
2994
+ rollback_command = BASE_ROLLBACK_COMMAND.format(cloud_id=cloud_id)
2995
+ for resource_type, value in edit_details.items():
2996
+ if value:
2997
+ old_value = self._get_cloud_resource_value(
2998
+ cloud_resource, resource_type
2999
+ )
3000
+ if old_value is not None:
3001
+ # The resource type names are in CreateCloudResource (backend/server/api/product/models/clouds.py).
3002
+ # The cli command names are in cloud_edit (frontend/cli/anyscale/commands:cloud_commands).
3003
+ # The relationship between their names are cli_command_name = resource_type_name.replace("_", "-").
3004
+ # e.g. cli_command_name: aws-s3-id & resource_type_name: aws_s3_id.
3005
+ formatted_resource_type = resource_type.replace("_", "-")
3006
+ rollback_command += f" --{formatted_resource_type}={old_value}"
3007
+ # If the only edit field is redis and it was None originally, rollback_command didn't appened any args, reset the rollback command to be empty.
3008
+ if rollback_command == BASE_ROLLBACK_COMMAND.format(cloud_id=cloud_id):
3009
+ rollback_command = ""
3010
+ return rollback_command
3011
+
3012
+ def _edit_aws_cloud( # noqa: PLR0912
3013
+ self,
3014
+ *,
3015
+ cloud_id: str,
3016
+ cloud_name: str,
3017
+ cloud: Any,
3018
+ cloud_resource: Any,
3019
+ aws_s3_id: Optional[str],
3020
+ aws_efs_id: Optional[str],
3021
+ aws_efs_mount_target_ip: Optional[str],
3022
+ memorydb_cluster_id: Optional[str],
3023
+ yes: bool = False,
3024
+ ):
3025
+ # Log edit details.
3026
+ self.log.open_block("EditDetail", "\nEdit details...")
3027
+ edit_details = {
3028
+ "aws_s3_id": aws_s3_id,
3029
+ "aws_efs_id": aws_efs_id,
3030
+ "aws_efs_mount_target_ip": aws_efs_mount_target_ip,
3031
+ "memorydb_cluster_id": memorydb_cluster_id,
3032
+ }
3033
+ details_logs = self._generate_edit_details_logs(cloud_resource, edit_details)
3034
+ self.log.info(
3035
+ self.log.highlight(
3036
+ "Cloud {} ({}) edit details: \n{}".format(
3037
+ cloud_name, cloud_id, "; \n".join(details_logs)
3038
+ )
3039
+ )
3040
+ )
3041
+ self.log.close_block("EditDetail")
3042
+
3043
+ try:
3044
+ boto3_session = boto3.Session(region_name=cloud.region)
3045
+ if aws_efs_id and not aws_efs_mount_target_ip:
3046
+ # Get the mount target IP for new aws_efs_ip (consistent with cloud register).
3047
+ aws_efs_mount_target_ip = _get_aws_efs_mount_target_ip(
3048
+ boto3_session, aws_efs_id
3049
+ )
3050
+ if not aws_efs_mount_target_ip:
3051
+ raise ClickException(
3052
+ f"Failed to get the mount target IP for new aws_efs_ip {aws_efs_id}, please make sure the aws_efs_ip exists and it has mount targets."
3053
+ )
3054
+ except Exception as e: # noqa: BLE001
3055
+ self.log.error(str(e))
3056
+ raise ClickException(
3057
+ f"Failed to get the mount target IP for new aws_efs_ip {aws_efs_id}, please make sure it has mount targets."
3058
+ )
3059
+ try:
3060
+ memorydb_cluster_config = None
3061
+ if memorydb_cluster_id:
3062
+ memorydb_cluster_config = _get_memorydb_cluster_config(
3063
+ memorydb_cluster_id, cloud.region, self.log
3064
+ )
3065
+ except Exception as e: # noqa: BLE001
3066
+ self.log.error(str(e))
3067
+ raise ClickException(
3068
+ f"Failed to get the memorydb cluster config for new memorydb_cluster_id {memorydb_cluster_id}, please make sure it's created."
3069
+ )
3070
+
3071
+ # Static Verify.
3072
+ self.log.open_block(
3073
+ "Verify", "Start cloud static verification on the specified resources..."
3074
+ )
3075
+ new_cloud_resource = copy.deepcopy(cloud_resource)
3076
+ if aws_s3_id:
3077
+ new_cloud_resource.aws_s3_id = aws_s3_id
3078
+ if aws_efs_id:
3079
+ new_cloud_resource.aws_efs_id = aws_efs_id
3080
+ if aws_efs_mount_target_ip:
3081
+ new_cloud_resource.aws_efs_mount_target_ip = aws_efs_mount_target_ip
3082
+ if memorydb_cluster_id:
3083
+ new_cloud_resource.memorydb_cluster_config = memorydb_cluster_config
3084
+
3085
+ if not self.verify_aws_cloud_resources(
3086
+ cloud_resource=new_cloud_resource,
3087
+ boto3_session=boto3_session,
3088
+ region=cloud.region,
3089
+ cloud_id=cloud_id,
3090
+ is_bring_your_own_resource=cloud.is_bring_your_own_resource,
3091
+ is_private_network=cloud.is_private_cloud
3092
+ if cloud.is_private_cloud
3093
+ else False,
3094
+ ):
3095
+ raise ClickException(
3096
+ "Cloud edit failed because resource failed verification. Please check the verification results above, fix them, and try again."
3097
+ )
3098
+
3099
+ self.log.info(
3100
+ self.log.highlight(
3101
+ "Please make sure you checked the warnings from above verification results."
3102
+ )
3103
+ )
3104
+ self.log.close_block("Verify")
3105
+
3106
+ self.log.open_block(
3107
+ "Reminder", "Please read the following reminder carefully..."
3108
+ )
3109
+ self.log.info(
3110
+ self.log.highlight(
3111
+ "If there are running workloads utilizing the old resources, you may want to retain them. Please note that this edit will not automatically remove any old resources. If you wish to delete them, you'll need to handle it."
3112
+ )
3113
+ )
3114
+ self.log.info(
3115
+ self.log.highlight(
3116
+ "The cloud resources we are going to edit {} ({}): \n{}".format(
3117
+ cloud_name, cloud_id, "; \n".join(details_logs)
3118
+ )
3119
+ )
3120
+ )
3121
+ self.log.close_block("Reminder")
3122
+
3123
+ confirm(
3124
+ "Are you sure you want to edit these cloud resource? ", yes,
3125
+ )
3126
+
3127
+ # Execute edit cloud.
3128
+ self.log.open_block("Edit", "Start editing cloud...")
3129
+ try:
3130
+ self.api_client.edit_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_patch(
3131
+ cloud_id=cloud_id,
3132
+ editable_cloud_resource=EditableCloudResource(
3133
+ aws_s3_id=aws_s3_id,
3134
+ aws_efs_id=aws_efs_id,
3135
+ aws_efs_mount_target_ip=aws_efs_mount_target_ip,
3136
+ memorydb_cluster_config=memorydb_cluster_config,
3137
+ ),
3138
+ )
3139
+ except Exception as e: # noqa: BLE001
3140
+ self.log.error(str(e))
3141
+ raise ClickException(
3142
+ "Edit cloud resource failed! The backend server might be under maintainance right now, please reach out to support or your SA for assistance."
3143
+ )
3144
+
3145
+ # Hint customer rollback command.
3146
+ rollback_command = self._generate_rollback_command(
3147
+ cloud_id, cloud_resource, edit_details
3148
+ )
3149
+ self.log.info(
3150
+ self.log.highlight(
3151
+ f"Cloud {cloud_name}({cloud_id}) is successfully edited."
3152
+ )
3153
+ )
3154
+ if rollback_command:
3155
+ self.log.info(
3156
+ self.log.highlight(
3157
+ f"If you want to revert the edit, you can edit it back to the original cloud with: `{rollback_command}`"
3158
+ )
3159
+ )
3160
+ self.log.close_block("Edit")
3161
+
3162
+ def _get_project_id(self, cloud: Any, cloud_name: str, cloud_id: str) -> str:
3163
+ try:
3164
+ return json.loads(cloud.credentials)["project_id"]
3165
+ except Exception: # noqa: BLE001
3166
+ raise ClickException(
3167
+ f"Failed to get project id for cloud {cloud_name}({cloud_id}). Please ensure the provided cloud_name/cloud_id exists."
3168
+ )
3169
+
3170
+ def _get_host_project_id(
3171
+ self, cloud: Any, cloud_name: str, cloud_id: str
3172
+ ) -> Optional[str]:
3173
+ try:
3174
+ credentials = json.loads(cloud.credentials)
3175
+ return credentials.get("host_project_id")
3176
+ except Exception: # noqa: BLE001
3177
+ raise ClickException(
3178
+ f"Failed to get host project id for cloud {cloud_name}({cloud_id}). Please ensure the provided cloud_name/cloud_id exists."
3179
+ )
3180
+
3181
+ def _get_gcp_filestore_config(
3182
+ self,
3183
+ gcp_filestore_instance_id: str,
3184
+ gcp_filestore_location: str,
3185
+ project_id: str,
3186
+ cloud_resource: Any,
3187
+ gcp_utils,
3188
+ ) -> GCPFileStoreConfig:
3189
+ try:
3190
+ factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
3191
+ return gcp_utils.get_gcp_filestore_config(
3192
+ factory,
3193
+ project_id,
3194
+ cloud_resource.gcp_vpc_id,
3195
+ gcp_filestore_location,
3196
+ gcp_filestore_instance_id,
3197
+ self.log,
3198
+ )
3199
+ except Exception as e: # noqa: BLE001
3200
+ self.log.error(str(e))
3201
+ raise ClickException(
3202
+ f"Failed to construct the gcp_filestore_config from project {project_id}, please double check the provided filestore_location {gcp_filestore_location} and filestore_instance_id {gcp_filestore_instance_id}."
3203
+ )
3204
+
3205
+ def _get_gcp_edit_details(
3206
+ self,
3207
+ *,
3208
+ cloud_resource: Any,
3209
+ edit_details: Dict[str, Optional[str]],
3210
+ gcp_filestore_config: GCPFileStoreConfig,
3211
+ gcp_filestore_instance_id: Optional[str],
3212
+ gcp_filestore_location: Optional[str],
3213
+ gcp_utils,
3214
+ ) -> List[str]:
3215
+ details_logs = self._generate_edit_details_logs(cloud_resource, edit_details)
3216
+ if gcp_filestore_config:
3217
+ (
3218
+ old_filestore_location,
3219
+ old_filestore_instance_id,
3220
+ ) = gcp_utils.get_filestore_location_and_instance_id(
3221
+ cloud_resource.gcp_filestore_config
3222
+ )
3223
+ if (
3224
+ old_filestore_instance_id == gcp_filestore_instance_id
3225
+ and old_filestore_location == gcp_filestore_location
3226
+ ):
3227
+ raise ClickException(
3228
+ f"Specified resource is the same as existed resource -- gcp_filestore_instance_id: {gcp_filestore_instance_id}; gcp_filestore_location: {gcp_filestore_location}"
3229
+ )
3230
+ details_logs.append(
3231
+ f"filestore_instance_id: from {old_filestore_instance_id} -> {gcp_filestore_instance_id}"
3232
+ )
3233
+ details_logs.append(
3234
+ f"filestore_location: from {old_filestore_location} -> {gcp_filestore_location}"
3235
+ )
3236
+ return details_logs
3237
+
3238
+ def _generate_rollback_command_for_gcp(
3239
+ self,
3240
+ cloud_id: str,
3241
+ cloud_resource: Any,
3242
+ edit_details: Dict[str, Optional[str]],
3243
+ gcp_filestore_config: GCPFileStoreConfig,
3244
+ gcp_utils,
3245
+ ):
3246
+ rollback_cmd = self._generate_rollback_command(
3247
+ cloud_id, cloud_resource, edit_details
3248
+ )
3249
+ if gcp_filestore_config:
3250
+ (
3251
+ old_filestore_location,
3252
+ old_filestore_instance_id,
3253
+ ) = gcp_utils.get_filestore_location_and_instance_id(
3254
+ cloud_resource.gcp_filestore_config
3255
+ )
3256
+ rollback_cmd += f" --gcp-filestore-instance-id={old_filestore_instance_id}"
3257
+ rollback_cmd += f" --gcp-filestore-location={old_filestore_location}"
3258
+ rollback_cmd = rollback_cmd.replace(
3259
+ "gcp-cloud-storage-bucket-id", "gcp-cloud-storage-bucket-name"
3260
+ )
3261
+ return rollback_cmd
3262
+
3263
+ def _edit_gcp_cloud( # noqa: PLR0912
3264
+ self,
3265
+ *,
3266
+ cloud_id: str,
3267
+ cloud_name: str,
3268
+ cloud: Any,
3269
+ cloud_resource: Any,
3270
+ gcp_filestore_instance_id: Optional[str],
3271
+ gcp_filestore_location: Optional[str],
3272
+ gcp_cloud_storage_bucket_name: Optional[str],
3273
+ memorystore_instance_name: Optional[str],
3274
+ yes: bool = False,
3275
+ ):
3276
+ project_id = self._get_project_id(cloud, cloud_name, cloud_id)
3277
+ host_project_id = self._get_host_project_id(cloud, cloud_name, cloud_id)
3278
+ gcp_utils = try_import_gcp_utils()
3279
+
3280
+ gcp_filestore_config = None
3281
+ if gcp_filestore_instance_id and gcp_filestore_location:
3282
+ gcp_filestore_config = self._get_gcp_filestore_config(
3283
+ gcp_filestore_instance_id,
3284
+ gcp_filestore_location,
3285
+ project_id,
3286
+ cloud_resource,
3287
+ gcp_utils,
3288
+ )
3289
+
3290
+ memorystore_instance_config = None
3291
+ if memorystore_instance_name:
3292
+ factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
3293
+ memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
3294
+ factory, memorystore_instance_name
3295
+ )
3296
+
3297
+ # Log edit details.
3298
+ self.log.open_block("EditDetail", "\nEdit details...")
3299
+ edit_details = {
3300
+ "gcp_cloud_storage_bucket_id": gcp_cloud_storage_bucket_name,
3301
+ "memorystore_instance_name": memorystore_instance_name,
3302
+ }
3303
+ details_logs = self._get_gcp_edit_details(
3304
+ cloud_resource=cloud_resource,
3305
+ edit_details=edit_details,
3306
+ gcp_filestore_config=gcp_filestore_config,
3307
+ gcp_filestore_instance_id=gcp_filestore_instance_id,
3308
+ gcp_filestore_location=gcp_filestore_location,
3309
+ gcp_utils=gcp_utils,
3310
+ )
3311
+ self.log.info(
3312
+ self.log.highlight(
3313
+ "Cloud edit details {} ({}): \n{}".format(
3314
+ cloud_name, cloud_id, "; \n".join(details_logs)
3315
+ )
3316
+ )
3317
+ )
3318
+ self.log.close_block("EditDetail")
3319
+
3320
+ # Static Verify.
3321
+ self.log.open_block(
3322
+ "Verify", "Start cloud static verification on the specified resources..."
3323
+ )
3324
+ new_cloud_resource = copy.deepcopy(cloud_resource)
3325
+ if gcp_filestore_config:
3326
+ new_cloud_resource.gcp_filestore_config = gcp_filestore_config
3327
+ if gcp_cloud_storage_bucket_name:
3328
+ new_cloud_resource.gcp_cloud_storage_bucket_id = (
3329
+ gcp_cloud_storage_bucket_name
3330
+ )
3331
+ if memorystore_instance_config:
3332
+ new_cloud_resource.memorystore_instance_config = memorystore_instance_config
3333
+ if not self.verify_gcp_cloud_resources(
3334
+ cloud_resource=new_cloud_resource,
3335
+ project_id=project_id,
3336
+ host_project_id=host_project_id,
3337
+ region=cloud.region,
3338
+ cloud_id=cloud_id,
3339
+ yes=False,
3340
+ ):
3341
+ raise ClickException(
3342
+ "Cloud edit failed because resource failed verification. Please check the verification results above, fix them, and try again."
3343
+ )
3344
+
3345
+ self.log.info(
3346
+ self.log.highlight(
3347
+ "Please make sure you checked the warnings from above verification results."
3348
+ )
3349
+ )
3350
+ self.log.close_block("Verify")
3351
+
3352
+ self.log.open_block(
3353
+ "Reminder", "Please read the following reminder carefully..."
3354
+ )
3355
+ self.log.info(
3356
+ self.log.highlight(
3357
+ "If there are running workloads utilizing the old resources, you may want to retain them. Please note that this edit will not automatically remove any old resources. If you wish to delete them, you'll need to handle it."
3358
+ )
3359
+ )
3360
+ self.log.info(
3361
+ self.log.highlight(
3362
+ "The cloud resources we are going to edit {} ({}): \n{}".format(
3363
+ cloud_name, cloud_id, "; \n".join(details_logs)
3364
+ )
3365
+ )
3366
+ )
3367
+ self.log.close_block("Reminder")
3368
+
3369
+ confirm(
3370
+ "Are you sure you want to edit these cloud resource? ", yes,
3371
+ )
3372
+
3373
+ # Execute edit cloud.
3374
+ self.log.open_block("Edit", "Start editing cloud...")
3375
+ try:
3376
+ self.api_client.edit_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_patch(
3377
+ cloud_id=cloud_id,
3378
+ editable_cloud_resource_gcp=EditableCloudResourceGCP(
3379
+ gcp_filestore_config=gcp_filestore_config,
3380
+ gcp_cloud_storage_bucket_id=gcp_cloud_storage_bucket_name,
3381
+ memorystore_instance_config=memorystore_instance_config,
3382
+ ),
3383
+ )
3384
+ except Exception as e: # noqa: BLE001
3385
+ self.log.error(str(e))
3386
+ raise ClickException(
3387
+ "Edit cloud resource failed! The backend server might be under maintainance right now, please reach out to support or your SA for assistance."
3388
+ )
3389
+
3390
+ # Hint customer rollback command.
3391
+ rollback_command = self._generate_rollback_command_for_gcp(
3392
+ cloud_id, cloud_resource, edit_details, gcp_filestore_config, gcp_utils,
3393
+ )
3394
+ self.log.info(
3395
+ self.log.highlight(
3396
+ f"Cloud {cloud_name}({cloud_id}) is successfully edited."
3397
+ )
3398
+ )
3399
+ if rollback_command:
3400
+ self.log.info(
3401
+ self.log.highlight(
3402
+ f"If you want to revert the edit, you can edit it back to the original cloud with: `{rollback_command}`"
3403
+ )
3404
+ )
3405
+ self.log.close_block("Edit")
3406
+
3407
+ def edit_cloud( # noqa: PLR0912,PLR0913
3408
+ self,
3409
+ *,
3410
+ cloud_name: Optional[str],
3411
+ cloud_id: Optional[str],
3412
+ aws_s3_id: Optional[str],
3413
+ aws_efs_id: Optional[str],
3414
+ aws_efs_mount_target_ip: Optional[str],
3415
+ memorydb_cluster_id: Optional[str],
3416
+ gcp_filestore_instance_id: Optional[str],
3417
+ gcp_filestore_location: Optional[str],
3418
+ gcp_cloud_storage_bucket_name: Optional[str],
3419
+ memorystore_instance_name: Optional[str],
3420
+ functional_verify: Optional[str],
3421
+ yes: bool = False,
3422
+ auto_add_user: Optional[bool] = None,
3423
+ ):
3424
+ """Edit aws cloud.
3425
+
3426
+ The editable fields are in EditableCloudResource (AWS) /EditableCloudResourceGCP (GCP).
3427
+ Steps:
3428
+ 1. Log the edits (from old to new values).
3429
+ 2. Static verify cloud resources with updated values.
3430
+ 3. Prompt the customer for confirmation based on verification results.
3431
+ 4. Update the cloud resource (calls backend API to modify the database).
3432
+ 5. Conduct a functional verification, if specified.
3433
+ """
3434
+ functions_to_verify = self._validate_functional_verification_args(
3435
+ functional_verify
3436
+ )
3437
+ cloud_id, cloud_name = get_cloud_id_and_name(
3438
+ self.api_client, cloud_id, cloud_name
3439
+ )
3440
+ cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(cloud_id).result
3441
+
3442
+ if not cloud.is_bring_your_own_resource:
3443
+ raise ClickException(
3444
+ f"Cloud {cloud_name}({cloud_id}) is not a cloud with customer defined resources, currently we don't support editing cloud resource values of managed clouds."
3445
+ )
3446
+
3447
+ cloud_resource = get_cloud_resource_by_cloud_id(
3448
+ cloud_id, cloud.provider, self.api_client
3449
+ )
3450
+ if cloud_resource is None:
3451
+ raise ClickException(
3452
+ f"Cloud {cloud_name}({cloud_id}) does not contain resource records."
3453
+ )
3454
+
3455
+ if auto_add_user is not None:
3456
+ self._update_auto_add_user_field(auto_add_user, cloud)
3457
+
3458
+ if any(
3459
+ [
3460
+ aws_s3_id,
3461
+ aws_efs_id,
3462
+ aws_efs_mount_target_ip,
3463
+ memorydb_cluster_id,
3464
+ gcp_filestore_instance_id,
3465
+ gcp_filestore_location,
3466
+ gcp_cloud_storage_bucket_name,
3467
+ memorystore_instance_name,
3468
+ ]
3469
+ ):
3470
+ if cloud.provider == "AWS":
3471
+ self.cloud_event_producer.init_trace_context(
3472
+ CloudAnalyticsEventCommandName.EDIT,
3473
+ CloudProviders.AWS,
3474
+ cloud_id=cloud_id,
3475
+ )
3476
+ self.cloud_event_producer.produce(
3477
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True
3478
+ )
3479
+ if (
3480
+ not any(
3481
+ [
3482
+ aws_s3_id,
3483
+ aws_efs_id,
3484
+ aws_efs_mount_target_ip,
3485
+ memorydb_cluster_id,
3486
+ ]
3487
+ )
3488
+ ) or any(
3489
+ [
3490
+ gcp_filestore_instance_id,
3491
+ gcp_filestore_location,
3492
+ gcp_cloud_storage_bucket_name,
3493
+ memorystore_instance_name,
3494
+ ]
3495
+ ):
3496
+ self.cloud_event_producer.produce(
3497
+ CloudAnalyticsEventName.RESOURCES_EDITED,
3498
+ succeeded=False,
3499
+ internal_error="specified resource and provider mismatch",
3500
+ )
3501
+ raise ClickException(
3502
+ "Specified resource and provider mismatch -- the cloud's provider is AWS, please make sure all the resource you want to edit is for AWS as well."
3503
+ )
3504
+ try:
3505
+ self._edit_aws_cloud(
3506
+ cloud_id=cloud_id,
3507
+ cloud_name=cloud_name,
3508
+ cloud=cloud,
3509
+ cloud_resource=cloud_resource,
3510
+ aws_s3_id=aws_s3_id,
3511
+ aws_efs_id=aws_efs_id,
3512
+ aws_efs_mount_target_ip=aws_efs_mount_target_ip,
3513
+ memorydb_cluster_id=memorydb_cluster_id,
3514
+ yes=yes,
3515
+ )
3516
+ except Exception as e: # noqa: BLE001
3517
+ self.cloud_event_producer.produce(
3518
+ CloudAnalyticsEventName.RESOURCES_EDITED,
3519
+ succeeded=False,
3520
+ logger=self.log,
3521
+ internal_error=str(e),
3522
+ )
3523
+ raise e
3524
+ elif cloud.provider == "GCP":
3525
+ self.cloud_event_producer.init_trace_context(
3526
+ CloudAnalyticsEventCommandName.EDIT,
3527
+ CloudProviders.GCP,
3528
+ cloud_id=cloud_id,
3529
+ )
3530
+ self.cloud_event_producer.produce(
3531
+ CloudAnalyticsEventName.COMMAND_START, succeeded=True
3532
+ )
3533
+ if (
3534
+ not any(
3535
+ [
3536
+ gcp_filestore_instance_id,
3537
+ gcp_filestore_location,
3538
+ gcp_cloud_storage_bucket_name,
3539
+ memorystore_instance_name,
3540
+ ]
3541
+ )
3542
+ ) or any(
3543
+ [
3544
+ aws_s3_id,
3545
+ aws_efs_id,
3546
+ aws_efs_mount_target_ip,
3547
+ memorydb_cluster_id,
3548
+ ]
3549
+ ):
3550
+ self.cloud_event_producer.produce(
3551
+ CloudAnalyticsEventName.RESOURCES_EDITED,
3552
+ succeeded=False,
3553
+ internal_error="specified resource and provider mismatch",
3554
+ )
3555
+ raise ClickException(
3556
+ "Specified resource and provider mismatch -- the cloud's provider is GCP, please make sure all the resource you want to edit is for GCP as well."
3557
+ )
3558
+ try:
3559
+ self._edit_gcp_cloud(
3560
+ cloud_id=cloud_id,
3561
+ cloud_name=cloud_name,
3562
+ cloud=cloud,
3563
+ cloud_resource=cloud_resource,
3564
+ gcp_filestore_instance_id=gcp_filestore_instance_id,
3565
+ gcp_filestore_location=gcp_filestore_location,
3566
+ gcp_cloud_storage_bucket_name=gcp_cloud_storage_bucket_name,
3567
+ memorystore_instance_name=memorystore_instance_name,
3568
+ yes=yes,
3569
+ )
3570
+ except Exception as e: # noqa: BLE001
3571
+ self.cloud_event_producer.produce(
3572
+ CloudAnalyticsEventName.RESOURCES_EDITED,
3573
+ succeeded=False,
3574
+ logger=self.log,
3575
+ internal_error=str(e),
3576
+ )
3577
+ raise e
3578
+ else:
3579
+ self.cloud_event_producer.produce(
3580
+ CloudAnalyticsEventName.RESOURCES_EDITED,
3581
+ succeeded=False,
3582
+ internal_error="invalid cloud provider",
3583
+ )
3584
+ raise ClickException(
3585
+ f"Unsupported cloud provider {cloud.provider} for cloud edit!"
3586
+ )
3587
+
3588
+ self.cloud_event_producer.produce(
3589
+ CloudAnalyticsEventName.RESOURCES_EDITED, succeeded=True
3590
+ )
3591
+ # Functional verify.
3592
+ if len(functions_to_verify) > 0:
3593
+ functional_verify_succeed = CloudFunctionalVerificationController(
3594
+ self.cloud_event_producer, self.log
3595
+ ).start_verification(
3596
+ cloud_id,
3597
+ self._get_cloud_provider_from_str(cloud.provider),
3598
+ functions_to_verify,
3599
+ yes,
3600
+ )
3601
+ if not functional_verify_succeed:
3602
+ raise ClickException(
3603
+ "Cloud functional verification failed. Please consider the following options:\n"
3604
+ "1. Create a new cloud (we recommend)\n"
3605
+ "2. Double-check the resources specified in the edit details, and the verification results, modify the resource if necessary, run `anyscale cloud verify (optional with functional-verify)` to verify again.\n"
3606
+ "3. Edit the resource back to original if you still want to use this cloud.\n"
3607
+ )
3608
+
3609
+ ### End of edit cloud ###