skypilot-nightly 1.0.0.dev20250509__py3-none-any.whl → 1.0.0.dev20251107__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.

Potentially problematic release.


This version of skypilot-nightly might be problematic. Click here for more details.

Files changed (512) hide show
  1. sky/__init__.py +22 -6
  2. sky/adaptors/aws.py +25 -7
  3. sky/adaptors/common.py +24 -1
  4. sky/adaptors/coreweave.py +278 -0
  5. sky/adaptors/do.py +8 -2
  6. sky/adaptors/hyperbolic.py +8 -0
  7. sky/adaptors/kubernetes.py +149 -18
  8. sky/adaptors/nebius.py +170 -17
  9. sky/adaptors/primeintellect.py +1 -0
  10. sky/adaptors/runpod.py +68 -0
  11. sky/adaptors/seeweb.py +167 -0
  12. sky/adaptors/shadeform.py +89 -0
  13. sky/admin_policy.py +187 -4
  14. sky/authentication.py +179 -225
  15. sky/backends/__init__.py +4 -2
  16. sky/backends/backend.py +22 -9
  17. sky/backends/backend_utils.py +1299 -380
  18. sky/backends/cloud_vm_ray_backend.py +1715 -518
  19. sky/backends/docker_utils.py +1 -1
  20. sky/backends/local_docker_backend.py +11 -6
  21. sky/backends/wheel_utils.py +37 -9
  22. sky/{clouds/service_catalog → catalog}/__init__.py +21 -19
  23. sky/{clouds/service_catalog → catalog}/aws_catalog.py +27 -8
  24. sky/{clouds/service_catalog → catalog}/azure_catalog.py +10 -7
  25. sky/{clouds/service_catalog → catalog}/common.py +89 -48
  26. sky/{clouds/service_catalog → catalog}/cudo_catalog.py +8 -5
  27. sky/{clouds/service_catalog → catalog}/data_fetchers/analyze.py +1 -1
  28. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_aws.py +30 -40
  29. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_cudo.py +38 -38
  30. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_gcp.py +42 -15
  31. sky/catalog/data_fetchers/fetch_hyperbolic.py +136 -0
  32. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_lambda_cloud.py +1 -0
  33. sky/catalog/data_fetchers/fetch_nebius.py +335 -0
  34. sky/catalog/data_fetchers/fetch_runpod.py +698 -0
  35. sky/catalog/data_fetchers/fetch_seeweb.py +329 -0
  36. sky/catalog/data_fetchers/fetch_shadeform.py +142 -0
  37. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vast.py +1 -1
  38. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vsphere.py +1 -1
  39. sky/{clouds/service_catalog → catalog}/do_catalog.py +5 -2
  40. sky/{clouds/service_catalog → catalog}/fluidstack_catalog.py +6 -3
  41. sky/{clouds/service_catalog → catalog}/gcp_catalog.py +41 -15
  42. sky/catalog/hyperbolic_catalog.py +136 -0
  43. sky/{clouds/service_catalog → catalog}/ibm_catalog.py +9 -6
  44. sky/{clouds/service_catalog → catalog}/kubernetes_catalog.py +36 -24
  45. sky/{clouds/service_catalog → catalog}/lambda_catalog.py +9 -6
  46. sky/{clouds/service_catalog → catalog}/nebius_catalog.py +9 -7
  47. sky/{clouds/service_catalog → catalog}/oci_catalog.py +9 -6
  48. sky/{clouds/service_catalog → catalog}/paperspace_catalog.py +5 -2
  49. sky/catalog/primeintellect_catalog.py +95 -0
  50. sky/{clouds/service_catalog → catalog}/runpod_catalog.py +11 -4
  51. sky/{clouds/service_catalog → catalog}/scp_catalog.py +9 -6
  52. sky/catalog/seeweb_catalog.py +184 -0
  53. sky/catalog/shadeform_catalog.py +165 -0
  54. sky/catalog/ssh_catalog.py +167 -0
  55. sky/{clouds/service_catalog → catalog}/vast_catalog.py +6 -3
  56. sky/{clouds/service_catalog → catalog}/vsphere_catalog.py +5 -2
  57. sky/check.py +491 -203
  58. sky/cli.py +5 -6005
  59. sky/client/{cli.py → cli/command.py} +2477 -1885
  60. sky/client/cli/deprecation_utils.py +99 -0
  61. sky/client/cli/flags.py +359 -0
  62. sky/client/cli/table_utils.py +320 -0
  63. sky/client/common.py +70 -32
  64. sky/client/oauth.py +82 -0
  65. sky/client/sdk.py +1203 -297
  66. sky/client/sdk_async.py +833 -0
  67. sky/client/service_account_auth.py +47 -0
  68. sky/cloud_stores.py +73 -0
  69. sky/clouds/__init__.py +13 -0
  70. sky/clouds/aws.py +358 -93
  71. sky/clouds/azure.py +105 -83
  72. sky/clouds/cloud.py +127 -36
  73. sky/clouds/cudo.py +68 -50
  74. sky/clouds/do.py +66 -48
  75. sky/clouds/fluidstack.py +63 -44
  76. sky/clouds/gcp.py +339 -110
  77. sky/clouds/hyperbolic.py +293 -0
  78. sky/clouds/ibm.py +70 -49
  79. sky/clouds/kubernetes.py +563 -162
  80. sky/clouds/lambda_cloud.py +74 -54
  81. sky/clouds/nebius.py +206 -80
  82. sky/clouds/oci.py +88 -66
  83. sky/clouds/paperspace.py +61 -44
  84. sky/clouds/primeintellect.py +317 -0
  85. sky/clouds/runpod.py +164 -74
  86. sky/clouds/scp.py +89 -83
  87. sky/clouds/seeweb.py +466 -0
  88. sky/clouds/shadeform.py +400 -0
  89. sky/clouds/ssh.py +263 -0
  90. sky/clouds/utils/aws_utils.py +10 -4
  91. sky/clouds/utils/gcp_utils.py +87 -11
  92. sky/clouds/utils/oci_utils.py +38 -14
  93. sky/clouds/utils/scp_utils.py +177 -124
  94. sky/clouds/vast.py +99 -77
  95. sky/clouds/vsphere.py +51 -40
  96. sky/core.py +349 -139
  97. sky/dag.py +15 -0
  98. sky/dashboard/out/404.html +1 -1
  99. sky/dashboard/out/_next/static/chunks/1141-e6aa9ab418717c59.js +11 -0
  100. sky/dashboard/out/_next/static/chunks/1272-1ef0bf0237faccdb.js +1 -0
  101. sky/dashboard/out/_next/static/chunks/1871-74503c8e80fd253b.js +6 -0
  102. sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
  103. sky/dashboard/out/_next/static/chunks/2350.fab69e61bac57b23.js +1 -0
  104. sky/dashboard/out/_next/static/chunks/2369.fc20f0c2c8ed9fe7.js +15 -0
  105. sky/dashboard/out/_next/static/chunks/2755.fff53c4a3fcae910.js +26 -0
  106. sky/dashboard/out/_next/static/chunks/3294.72362fa129305b19.js +1 -0
  107. sky/dashboard/out/_next/static/chunks/3785.ad6adaa2a0fa9768.js +1 -0
  108. sky/dashboard/out/_next/static/chunks/3850-ff4a9a69d978632b.js +1 -0
  109. sky/dashboard/out/_next/static/chunks/3937.210053269f121201.js +1 -0
  110. sky/dashboard/out/_next/static/chunks/4725.a830b5c9e7867c92.js +1 -0
  111. sky/dashboard/out/_next/static/chunks/4937.a2baa2df5572a276.js +15 -0
  112. sky/dashboard/out/_next/static/chunks/5739-d67458fcb1386c92.js +8 -0
  113. sky/dashboard/out/_next/static/chunks/6130-2be46d70a38f1e82.js +1 -0
  114. sky/dashboard/out/_next/static/chunks/616-3d59f75e2ccf9321.js +39 -0
  115. sky/dashboard/out/_next/static/chunks/6212-7bd06f60ba693125.js +13 -0
  116. sky/dashboard/out/_next/static/chunks/6601-06114c982db410b6.js +1 -0
  117. sky/dashboard/out/_next/static/chunks/6856-ef8ba11f96d8c4a3.js +1 -0
  118. sky/dashboard/out/_next/static/chunks/6989-01359c57e018caa4.js +1 -0
  119. sky/dashboard/out/_next/static/chunks/6990-32b6e2d3822301fa.js +1 -0
  120. sky/dashboard/out/_next/static/chunks/7359-c8d04e06886000b3.js +30 -0
  121. sky/dashboard/out/_next/static/chunks/7411-b15471acd2cba716.js +41 -0
  122. sky/dashboard/out/_next/static/chunks/7615-3301e838e5f25772.js +1 -0
  123. sky/dashboard/out/_next/static/chunks/8640.5b9475a2d18c5416.js +16 -0
  124. sky/dashboard/out/_next/static/chunks/8969-1e4613c651bf4051.js +1 -0
  125. sky/dashboard/out/_next/static/chunks/9025.fa408f3242e9028d.js +6 -0
  126. sky/dashboard/out/_next/static/chunks/9353-cff34f7e773b2e2b.js +1 -0
  127. sky/dashboard/out/_next/static/chunks/9360.7310982cf5a0dc79.js +31 -0
  128. sky/dashboard/out/_next/static/chunks/9847.3aaca6bb33455140.js +30 -0
  129. sky/dashboard/out/_next/static/chunks/fd9d1056-86323a29a8f7e46a.js +1 -0
  130. sky/dashboard/out/_next/static/chunks/framework-cf60a09ccd051a10.js +33 -0
  131. sky/dashboard/out/_next/static/chunks/main-app-587214043926b3cc.js +1 -0
  132. sky/dashboard/out/_next/static/chunks/main-f15ccb73239a3bf1.js +1 -0
  133. sky/dashboard/out/_next/static/chunks/pages/_app-bde01e4a2beec258.js +34 -0
  134. sky/dashboard/out/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js +1 -0
  135. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-c736ead69c2d86ec.js +16 -0
  136. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-a37d2063af475a1c.js +1 -0
  137. sky/dashboard/out/_next/static/chunks/pages/clusters-d44859594e6f8064.js +1 -0
  138. sky/dashboard/out/_next/static/chunks/pages/config-dfb9bf07b13045f4.js +1 -0
  139. sky/dashboard/out/_next/static/chunks/pages/index-444f1804401f04ea.js +1 -0
  140. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-c0b5935149902e6f.js +1 -0
  141. sky/dashboard/out/_next/static/chunks/pages/infra-aed0ea19df7cf961.js +1 -0
  142. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-5796e8d6aea291a0.js +16 -0
  143. sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-6edeb7d06032adfc.js +21 -0
  144. sky/dashboard/out/_next/static/chunks/pages/jobs-479dde13399cf270.js +1 -0
  145. sky/dashboard/out/_next/static/chunks/pages/users-5ab3b907622cf0fe.js +1 -0
  146. sky/dashboard/out/_next/static/chunks/pages/volumes-b84b948ff357c43e.js +1 -0
  147. sky/dashboard/out/_next/static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js +1 -0
  148. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-c5a3eeee1c218af1.js +1 -0
  149. sky/dashboard/out/_next/static/chunks/pages/workspaces-22b23febb3e89ce1.js +1 -0
  150. sky/dashboard/out/_next/static/chunks/webpack-2679be77fc08a2f8.js +1 -0
  151. sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
  152. sky/dashboard/out/_next/static/zB0ed6ge_W1MDszVHhijS/_buildManifest.js +1 -0
  153. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  154. sky/dashboard/out/clusters/[cluster].html +1 -1
  155. sky/dashboard/out/clusters.html +1 -1
  156. sky/dashboard/out/config.html +1 -0
  157. sky/dashboard/out/index.html +1 -1
  158. sky/dashboard/out/infra/[context].html +1 -0
  159. sky/dashboard/out/infra.html +1 -0
  160. sky/dashboard/out/jobs/[job].html +1 -1
  161. sky/dashboard/out/jobs/pools/[pool].html +1 -0
  162. sky/dashboard/out/jobs.html +1 -1
  163. sky/dashboard/out/users.html +1 -0
  164. sky/dashboard/out/volumes.html +1 -0
  165. sky/dashboard/out/workspace/new.html +1 -0
  166. sky/dashboard/out/workspaces/[name].html +1 -0
  167. sky/dashboard/out/workspaces.html +1 -0
  168. sky/data/data_utils.py +137 -1
  169. sky/data/mounting_utils.py +269 -84
  170. sky/data/storage.py +1451 -1807
  171. sky/data/storage_utils.py +43 -57
  172. sky/exceptions.py +132 -2
  173. sky/execution.py +206 -63
  174. sky/global_user_state.py +2374 -586
  175. sky/jobs/__init__.py +5 -0
  176. sky/jobs/client/sdk.py +242 -65
  177. sky/jobs/client/sdk_async.py +143 -0
  178. sky/jobs/constants.py +9 -8
  179. sky/jobs/controller.py +839 -277
  180. sky/jobs/file_content_utils.py +80 -0
  181. sky/jobs/log_gc.py +201 -0
  182. sky/jobs/recovery_strategy.py +398 -152
  183. sky/jobs/scheduler.py +315 -189
  184. sky/jobs/server/core.py +829 -255
  185. sky/jobs/server/server.py +156 -115
  186. sky/jobs/server/utils.py +136 -0
  187. sky/jobs/state.py +2092 -701
  188. sky/jobs/utils.py +1242 -160
  189. sky/logs/__init__.py +21 -0
  190. sky/logs/agent.py +108 -0
  191. sky/logs/aws.py +243 -0
  192. sky/logs/gcp.py +91 -0
  193. sky/metrics/__init__.py +0 -0
  194. sky/metrics/utils.py +443 -0
  195. sky/models.py +78 -1
  196. sky/optimizer.py +164 -70
  197. sky/provision/__init__.py +90 -4
  198. sky/provision/aws/config.py +147 -26
  199. sky/provision/aws/instance.py +135 -50
  200. sky/provision/azure/instance.py +10 -5
  201. sky/provision/common.py +13 -1
  202. sky/provision/cudo/cudo_machine_type.py +1 -1
  203. sky/provision/cudo/cudo_utils.py +14 -8
  204. sky/provision/cudo/cudo_wrapper.py +72 -71
  205. sky/provision/cudo/instance.py +10 -6
  206. sky/provision/do/instance.py +10 -6
  207. sky/provision/do/utils.py +4 -3
  208. sky/provision/docker_utils.py +114 -23
  209. sky/provision/fluidstack/instance.py +13 -8
  210. sky/provision/gcp/__init__.py +1 -0
  211. sky/provision/gcp/config.py +301 -19
  212. sky/provision/gcp/constants.py +218 -0
  213. sky/provision/gcp/instance.py +36 -8
  214. sky/provision/gcp/instance_utils.py +18 -4
  215. sky/provision/gcp/volume_utils.py +247 -0
  216. sky/provision/hyperbolic/__init__.py +12 -0
  217. sky/provision/hyperbolic/config.py +10 -0
  218. sky/provision/hyperbolic/instance.py +437 -0
  219. sky/provision/hyperbolic/utils.py +373 -0
  220. sky/provision/instance_setup.py +93 -14
  221. sky/provision/kubernetes/__init__.py +5 -0
  222. sky/provision/kubernetes/config.py +9 -52
  223. sky/provision/kubernetes/constants.py +17 -0
  224. sky/provision/kubernetes/instance.py +789 -247
  225. sky/provision/kubernetes/manifests/fusermount-server-daemonset.yaml +1 -2
  226. sky/provision/kubernetes/network.py +27 -17
  227. sky/provision/kubernetes/network_utils.py +40 -43
  228. sky/provision/kubernetes/utils.py +1192 -531
  229. sky/provision/kubernetes/volume.py +282 -0
  230. sky/provision/lambda_cloud/instance.py +22 -16
  231. sky/provision/nebius/constants.py +50 -0
  232. sky/provision/nebius/instance.py +19 -6
  233. sky/provision/nebius/utils.py +196 -91
  234. sky/provision/oci/instance.py +10 -5
  235. sky/provision/paperspace/instance.py +10 -7
  236. sky/provision/paperspace/utils.py +1 -1
  237. sky/provision/primeintellect/__init__.py +10 -0
  238. sky/provision/primeintellect/config.py +11 -0
  239. sky/provision/primeintellect/instance.py +454 -0
  240. sky/provision/primeintellect/utils.py +398 -0
  241. sky/provision/provisioner.py +110 -36
  242. sky/provision/runpod/__init__.py +5 -0
  243. sky/provision/runpod/instance.py +27 -6
  244. sky/provision/runpod/utils.py +51 -18
  245. sky/provision/runpod/volume.py +180 -0
  246. sky/provision/scp/__init__.py +15 -0
  247. sky/provision/scp/config.py +93 -0
  248. sky/provision/scp/instance.py +531 -0
  249. sky/provision/seeweb/__init__.py +11 -0
  250. sky/provision/seeweb/config.py +13 -0
  251. sky/provision/seeweb/instance.py +807 -0
  252. sky/provision/shadeform/__init__.py +11 -0
  253. sky/provision/shadeform/config.py +12 -0
  254. sky/provision/shadeform/instance.py +351 -0
  255. sky/provision/shadeform/shadeform_utils.py +83 -0
  256. sky/provision/ssh/__init__.py +18 -0
  257. sky/provision/vast/instance.py +13 -8
  258. sky/provision/vast/utils.py +10 -7
  259. sky/provision/vsphere/common/vim_utils.py +1 -2
  260. sky/provision/vsphere/instance.py +15 -10
  261. sky/provision/vsphere/vsphere_utils.py +9 -19
  262. sky/py.typed +0 -0
  263. sky/resources.py +844 -118
  264. sky/schemas/__init__.py +0 -0
  265. sky/schemas/api/__init__.py +0 -0
  266. sky/schemas/api/responses.py +225 -0
  267. sky/schemas/db/README +4 -0
  268. sky/schemas/db/env.py +90 -0
  269. sky/schemas/db/global_user_state/001_initial_schema.py +124 -0
  270. sky/schemas/db/global_user_state/002_add_workspace_to_cluster_history.py +35 -0
  271. sky/schemas/db/global_user_state/003_fix_initial_revision.py +61 -0
  272. sky/schemas/db/global_user_state/004_is_managed.py +34 -0
  273. sky/schemas/db/global_user_state/005_cluster_event.py +32 -0
  274. sky/schemas/db/global_user_state/006_provision_log.py +41 -0
  275. sky/schemas/db/global_user_state/007_cluster_event_request_id.py +34 -0
  276. sky/schemas/db/global_user_state/008_skylet_ssh_tunnel_metadata.py +34 -0
  277. sky/schemas/db/global_user_state/009_last_activity_and_launched_at.py +89 -0
  278. sky/schemas/db/global_user_state/010_save_ssh_key.py +66 -0
  279. sky/schemas/db/script.py.mako +28 -0
  280. sky/schemas/db/serve_state/001_initial_schema.py +67 -0
  281. sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
  282. sky/schemas/db/spot_jobs/001_initial_schema.py +97 -0
  283. sky/schemas/db/spot_jobs/002_cluster_pool.py +42 -0
  284. sky/schemas/db/spot_jobs/003_pool_hash.py +34 -0
  285. sky/schemas/db/spot_jobs/004_job_file_contents.py +42 -0
  286. sky/schemas/db/spot_jobs/005_logs_gc.py +38 -0
  287. sky/schemas/generated/__init__.py +0 -0
  288. sky/schemas/generated/autostopv1_pb2.py +36 -0
  289. sky/schemas/generated/autostopv1_pb2.pyi +43 -0
  290. sky/schemas/generated/autostopv1_pb2_grpc.py +146 -0
  291. sky/schemas/generated/jobsv1_pb2.py +86 -0
  292. sky/schemas/generated/jobsv1_pb2.pyi +254 -0
  293. sky/schemas/generated/jobsv1_pb2_grpc.py +542 -0
  294. sky/schemas/generated/managed_jobsv1_pb2.py +74 -0
  295. sky/schemas/generated/managed_jobsv1_pb2.pyi +278 -0
  296. sky/schemas/generated/managed_jobsv1_pb2_grpc.py +278 -0
  297. sky/schemas/generated/servev1_pb2.py +58 -0
  298. sky/schemas/generated/servev1_pb2.pyi +115 -0
  299. sky/schemas/generated/servev1_pb2_grpc.py +322 -0
  300. sky/serve/autoscalers.py +357 -5
  301. sky/serve/client/impl.py +310 -0
  302. sky/serve/client/sdk.py +47 -139
  303. sky/serve/client/sdk_async.py +130 -0
  304. sky/serve/constants.py +10 -8
  305. sky/serve/controller.py +64 -19
  306. sky/serve/load_balancer.py +106 -60
  307. sky/serve/load_balancing_policies.py +115 -1
  308. sky/serve/replica_managers.py +273 -162
  309. sky/serve/serve_rpc_utils.py +179 -0
  310. sky/serve/serve_state.py +554 -251
  311. sky/serve/serve_utils.py +733 -220
  312. sky/serve/server/core.py +66 -711
  313. sky/serve/server/impl.py +1093 -0
  314. sky/serve/server/server.py +21 -18
  315. sky/serve/service.py +133 -48
  316. sky/serve/service_spec.py +135 -16
  317. sky/serve/spot_placer.py +3 -0
  318. sky/server/auth/__init__.py +0 -0
  319. sky/server/auth/authn.py +50 -0
  320. sky/server/auth/loopback.py +38 -0
  321. sky/server/auth/oauth2_proxy.py +200 -0
  322. sky/server/common.py +475 -181
  323. sky/server/config.py +81 -23
  324. sky/server/constants.py +44 -6
  325. sky/server/daemons.py +229 -0
  326. sky/server/html/token_page.html +185 -0
  327. sky/server/metrics.py +160 -0
  328. sky/server/requests/executor.py +528 -138
  329. sky/server/requests/payloads.py +351 -17
  330. sky/server/requests/preconditions.py +21 -17
  331. sky/server/requests/process.py +112 -29
  332. sky/server/requests/request_names.py +120 -0
  333. sky/server/requests/requests.py +817 -224
  334. sky/server/requests/serializers/decoders.py +82 -31
  335. sky/server/requests/serializers/encoders.py +140 -22
  336. sky/server/requests/threads.py +106 -0
  337. sky/server/rest.py +417 -0
  338. sky/server/server.py +1290 -284
  339. sky/server/state.py +20 -0
  340. sky/server/stream_utils.py +345 -57
  341. sky/server/uvicorn.py +217 -3
  342. sky/server/versions.py +270 -0
  343. sky/setup_files/MANIFEST.in +5 -0
  344. sky/setup_files/alembic.ini +156 -0
  345. sky/setup_files/dependencies.py +136 -31
  346. sky/setup_files/setup.py +44 -42
  347. sky/sky_logging.py +102 -5
  348. sky/skylet/attempt_skylet.py +1 -0
  349. sky/skylet/autostop_lib.py +129 -8
  350. sky/skylet/configs.py +27 -20
  351. sky/skylet/constants.py +171 -19
  352. sky/skylet/events.py +105 -21
  353. sky/skylet/job_lib.py +335 -104
  354. sky/skylet/log_lib.py +297 -18
  355. sky/skylet/log_lib.pyi +44 -1
  356. sky/skylet/ray_patches/__init__.py +17 -3
  357. sky/skylet/ray_patches/autoscaler.py.diff +18 -0
  358. sky/skylet/ray_patches/cli.py.diff +19 -0
  359. sky/skylet/ray_patches/command_runner.py.diff +17 -0
  360. sky/skylet/ray_patches/log_monitor.py.diff +20 -0
  361. sky/skylet/ray_patches/resource_demand_scheduler.py.diff +32 -0
  362. sky/skylet/ray_patches/updater.py.diff +18 -0
  363. sky/skylet/ray_patches/worker.py.diff +41 -0
  364. sky/skylet/services.py +564 -0
  365. sky/skylet/skylet.py +63 -4
  366. sky/skylet/subprocess_daemon.py +103 -29
  367. sky/skypilot_config.py +506 -99
  368. sky/ssh_node_pools/__init__.py +1 -0
  369. sky/ssh_node_pools/core.py +135 -0
  370. sky/ssh_node_pools/server.py +233 -0
  371. sky/task.py +621 -137
  372. sky/templates/aws-ray.yml.j2 +10 -3
  373. sky/templates/azure-ray.yml.j2 +1 -1
  374. sky/templates/do-ray.yml.j2 +1 -1
  375. sky/templates/gcp-ray.yml.j2 +57 -0
  376. sky/templates/hyperbolic-ray.yml.j2 +67 -0
  377. sky/templates/jobs-controller.yaml.j2 +27 -24
  378. sky/templates/kubernetes-loadbalancer.yml.j2 +2 -0
  379. sky/templates/kubernetes-ray.yml.j2 +607 -51
  380. sky/templates/lambda-ray.yml.j2 +1 -1
  381. sky/templates/nebius-ray.yml.j2 +33 -12
  382. sky/templates/paperspace-ray.yml.j2 +1 -1
  383. sky/templates/primeintellect-ray.yml.j2 +71 -0
  384. sky/templates/runpod-ray.yml.j2 +9 -1
  385. sky/templates/scp-ray.yml.j2 +3 -50
  386. sky/templates/seeweb-ray.yml.j2 +108 -0
  387. sky/templates/shadeform-ray.yml.j2 +72 -0
  388. sky/templates/sky-serve-controller.yaml.j2 +22 -2
  389. sky/templates/websocket_proxy.py +178 -18
  390. sky/usage/usage_lib.py +18 -11
  391. sky/users/__init__.py +0 -0
  392. sky/users/model.conf +15 -0
  393. sky/users/permission.py +387 -0
  394. sky/users/rbac.py +121 -0
  395. sky/users/server.py +720 -0
  396. sky/users/token_service.py +218 -0
  397. sky/utils/accelerator_registry.py +34 -5
  398. sky/utils/admin_policy_utils.py +84 -38
  399. sky/utils/annotations.py +16 -5
  400. sky/utils/asyncio_utils.py +78 -0
  401. sky/utils/auth_utils.py +153 -0
  402. sky/utils/benchmark_utils.py +60 -0
  403. sky/utils/cli_utils/status_utils.py +159 -86
  404. sky/utils/cluster_utils.py +31 -9
  405. sky/utils/command_runner.py +354 -68
  406. sky/utils/command_runner.pyi +93 -3
  407. sky/utils/common.py +35 -8
  408. sky/utils/common_utils.py +310 -87
  409. sky/utils/config_utils.py +87 -5
  410. sky/utils/context.py +402 -0
  411. sky/utils/context_utils.py +222 -0
  412. sky/utils/controller_utils.py +264 -89
  413. sky/utils/dag_utils.py +31 -12
  414. sky/utils/db/__init__.py +0 -0
  415. sky/utils/db/db_utils.py +470 -0
  416. sky/utils/db/migration_utils.py +133 -0
  417. sky/utils/directory_utils.py +12 -0
  418. sky/utils/env_options.py +13 -0
  419. sky/utils/git.py +567 -0
  420. sky/utils/git_clone.sh +460 -0
  421. sky/utils/infra_utils.py +195 -0
  422. sky/utils/kubernetes/cleanup-tunnel.sh +62 -0
  423. sky/utils/kubernetes/config_map_utils.py +133 -0
  424. sky/utils/kubernetes/create_cluster.sh +13 -27
  425. sky/utils/kubernetes/delete_cluster.sh +10 -7
  426. sky/utils/kubernetes/deploy_remote_cluster.py +1299 -0
  427. sky/utils/kubernetes/exec_kubeconfig_converter.py +22 -31
  428. sky/utils/kubernetes/generate_kind_config.py +6 -66
  429. sky/utils/kubernetes/generate_kubeconfig.sh +4 -1
  430. sky/utils/kubernetes/gpu_labeler.py +5 -5
  431. sky/utils/kubernetes/kubernetes_deploy_utils.py +354 -47
  432. sky/utils/kubernetes/ssh-tunnel.sh +379 -0
  433. sky/utils/kubernetes/ssh_utils.py +221 -0
  434. sky/utils/kubernetes_enums.py +8 -15
  435. sky/utils/lock_events.py +94 -0
  436. sky/utils/locks.py +368 -0
  437. sky/utils/log_utils.py +300 -6
  438. sky/utils/perf_utils.py +22 -0
  439. sky/utils/resource_checker.py +298 -0
  440. sky/utils/resources_utils.py +249 -32
  441. sky/utils/rich_utils.py +213 -37
  442. sky/utils/schemas.py +905 -147
  443. sky/utils/serialize_utils.py +16 -0
  444. sky/utils/status_lib.py +10 -0
  445. sky/utils/subprocess_utils.py +38 -15
  446. sky/utils/tempstore.py +70 -0
  447. sky/utils/timeline.py +24 -52
  448. sky/utils/ux_utils.py +84 -15
  449. sky/utils/validator.py +11 -1
  450. sky/utils/volume.py +86 -0
  451. sky/utils/yaml_utils.py +111 -0
  452. sky/volumes/__init__.py +13 -0
  453. sky/volumes/client/__init__.py +0 -0
  454. sky/volumes/client/sdk.py +149 -0
  455. sky/volumes/server/__init__.py +0 -0
  456. sky/volumes/server/core.py +258 -0
  457. sky/volumes/server/server.py +122 -0
  458. sky/volumes/volume.py +212 -0
  459. sky/workspaces/__init__.py +0 -0
  460. sky/workspaces/core.py +655 -0
  461. sky/workspaces/server.py +101 -0
  462. sky/workspaces/utils.py +56 -0
  463. skypilot_nightly-1.0.0.dev20251107.dist-info/METADATA +675 -0
  464. skypilot_nightly-1.0.0.dev20251107.dist-info/RECORD +594 -0
  465. {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/WHEEL +1 -1
  466. sky/benchmark/benchmark_state.py +0 -256
  467. sky/benchmark/benchmark_utils.py +0 -641
  468. sky/clouds/service_catalog/constants.py +0 -7
  469. sky/dashboard/out/_next/static/LksQgChY5izXjokL3LcEu/_buildManifest.js +0 -1
  470. sky/dashboard/out/_next/static/chunks/236-f49500b82ad5392d.js +0 -6
  471. sky/dashboard/out/_next/static/chunks/312-c3c8845990db8ffc.js +0 -15
  472. sky/dashboard/out/_next/static/chunks/37-0a572fe0dbb89c4d.js +0 -6
  473. sky/dashboard/out/_next/static/chunks/678-206dddca808e6d16.js +0 -59
  474. sky/dashboard/out/_next/static/chunks/845-0f8017370869e269.js +0 -1
  475. sky/dashboard/out/_next/static/chunks/979-7bf73a4c7cea0f5c.js +0 -1
  476. sky/dashboard/out/_next/static/chunks/fd9d1056-2821b0f0cabcd8bd.js +0 -1
  477. sky/dashboard/out/_next/static/chunks/framework-87d061ee6ed71b28.js +0 -33
  478. sky/dashboard/out/_next/static/chunks/main-app-241eb28595532291.js +0 -1
  479. sky/dashboard/out/_next/static/chunks/main-e0e2335212e72357.js +0 -1
  480. sky/dashboard/out/_next/static/chunks/pages/_app-e6b013bc3f77ad60.js +0 -1
  481. sky/dashboard/out/_next/static/chunks/pages/_error-1be831200e60c5c0.js +0 -1
  482. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-e15db85d0ea1fbe1.js +0 -1
  483. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-f383db7389368ea7.js +0 -1
  484. sky/dashboard/out/_next/static/chunks/pages/clusters-a93b93e10b8b074e.js +0 -1
  485. sky/dashboard/out/_next/static/chunks/pages/index-f9f039532ca8cbc4.js +0 -1
  486. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-03f279c6741fb48b.js +0 -1
  487. sky/dashboard/out/_next/static/chunks/pages/jobs-a75029b67aab6a2e.js +0 -1
  488. sky/dashboard/out/_next/static/chunks/webpack-830f59b8404e96b8.js +0 -1
  489. sky/dashboard/out/_next/static/css/c6933bbb2ce7f4dd.css +0 -3
  490. sky/jobs/dashboard/dashboard.py +0 -223
  491. sky/jobs/dashboard/static/favicon.ico +0 -0
  492. sky/jobs/dashboard/templates/index.html +0 -831
  493. sky/jobs/server/dashboard_utils.py +0 -69
  494. sky/skylet/providers/scp/__init__.py +0 -2
  495. sky/skylet/providers/scp/config.py +0 -149
  496. sky/skylet/providers/scp/node_provider.py +0 -578
  497. sky/templates/kubernetes-ssh-jump.yml.j2 +0 -94
  498. sky/utils/db_utils.py +0 -100
  499. sky/utils/kubernetes/deploy_remote_cluster.sh +0 -308
  500. sky/utils/kubernetes/ssh_jump_lifecycle_manager.py +0 -191
  501. skypilot_nightly-1.0.0.dev20250509.dist-info/METADATA +0 -361
  502. skypilot_nightly-1.0.0.dev20250509.dist-info/RECORD +0 -396
  503. /sky/{clouds/service_catalog → catalog}/config.py +0 -0
  504. /sky/{benchmark → catalog/data_fetchers}/__init__.py +0 -0
  505. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_azure.py +0 -0
  506. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_fluidstack.py +0 -0
  507. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_ibm.py +0 -0
  508. /sky/{clouds/service_catalog/data_fetchers → client/cli}/__init__.py +0 -0
  509. /sky/dashboard/out/_next/static/{LksQgChY5izXjokL3LcEu → zB0ed6ge_W1MDszVHhijS}/_ssgManifest.js +0 -0
  510. {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/entry_points.txt +0 -0
  511. {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/licenses/LICENSE +0 -0
  512. {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/top_level.txt +0 -0
sky/check.py CHANGED
@@ -1,11 +1,9 @@
1
1
  """Credential checks: check cloud credentials and enable clouds."""
2
2
  import collections
3
- import itertools
4
3
  import os
5
4
  import traceback
6
5
  from types import ModuleType
7
- from typing import (Any, Callable, Dict, Iterable, List, Optional, Set, Tuple,
8
- Union)
6
+ from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
9
7
 
10
8
  import click
11
9
  import colorama
@@ -13,9 +11,13 @@ import colorama
13
11
  from sky import clouds as sky_clouds
14
12
  from sky import exceptions
15
13
  from sky import global_user_state
14
+ from sky import sky_logging
16
15
  from sky import skypilot_config
17
16
  from sky.adaptors import cloudflare
17
+ from sky.adaptors import coreweave
18
18
  from sky.clouds import cloud as sky_cloud
19
+ from sky.skylet import constants
20
+ from sky.utils import common_utils
19
21
  from sky.utils import registry
20
22
  from sky.utils import rich_utils
21
23
  from sky.utils import subprocess_utils
@@ -24,174 +26,289 @@ from sky.utils import ux_utils
24
26
  CHECK_MARK_EMOJI = '\U00002714' # Heavy check mark unicode
25
27
  PARTY_POPPER_EMOJI = '\U0001F389' # Party popper unicode
26
28
 
29
+ logger = sky_logging.init_logger(__name__)
30
+
31
+
32
+ def _get_workspace_allowed_clouds(workspace: str) -> List[str]:
33
+ # Use allowed_clouds from config if it exists, otherwise check all
34
+ # clouds. Also validate names with get_cloud_tuple.
35
+ config_allowed_cloud_names = skypilot_config.get_nested(
36
+ ('allowed_clouds',),
37
+ [repr(c) for c in registry.CLOUD_REGISTRY.values()] +
38
+ [cloudflare.NAME, coreweave.NAME])
39
+ # filter out the clouds that are disabled in the workspace config
40
+ workspace_disabled_clouds = []
41
+ for cloud in config_allowed_cloud_names:
42
+ cloud_config = skypilot_config.get_workspace_cloud(cloud.lower(),
43
+ workspace=workspace)
44
+ cloud_disabled = cloud_config.get('disabled', False)
45
+ if cloud_disabled:
46
+ workspace_disabled_clouds.append(cloud.lower())
47
+
48
+ config_allowed_cloud_names = [
49
+ c for c in config_allowed_cloud_names
50
+ if c.lower() not in workspace_disabled_clouds
51
+ ]
52
+ return config_allowed_cloud_names
53
+
27
54
 
28
55
  def check_capabilities(
29
56
  quiet: bool = False,
30
57
  verbose: bool = False,
31
58
  clouds: Optional[Iterable[str]] = None,
32
59
  capabilities: Optional[List[sky_cloud.CloudCapability]] = None,
33
- ) -> Dict[str, List[sky_cloud.CloudCapability]]:
60
+ workspace: Optional[str] = None,
61
+ ) -> Dict[str, Dict[str, List[sky_cloud.CloudCapability]]]:
62
+ # pylint: disable=import-outside-toplevel
63
+ from sky.workspaces import core
64
+
34
65
  echo = (lambda *_args, **_kwargs: None
35
66
  ) if quiet else lambda *args, **kwargs: click.echo(
36
67
  *args, **kwargs, color=True)
37
- echo('Checking credentials to enable clouds for SkyPilot.')
68
+ all_workspaces_results: Dict[str,
69
+ Dict[str,
70
+ List[sky_cloud.CloudCapability]]] = {}
71
+ available_workspaces = list(core.get_workspaces().keys())
72
+ hide_workspace_str = (available_workspaces == [
73
+ constants.SKYPILOT_DEFAULT_WORKSPACE
74
+ ])
75
+ initial_hint = 'Checking credentials to enable infra for SkyPilot.'
76
+ if len(available_workspaces) > 1:
77
+ initial_hint = (f'Checking credentials to enable infra for SkyPilot '
78
+ f'(Workspaces: {", ".join(available_workspaces)}).')
79
+ echo(initial_hint)
38
80
  if capabilities is None:
39
81
  capabilities = sky_cloud.ALL_CAPABILITIES
40
82
  assert capabilities is not None
41
- enabled_clouds: Dict[str, List[sky_cloud.CloudCapability]] = {}
42
- disabled_clouds: Dict[str, List[sky_cloud.CloudCapability]] = {}
43
-
44
- def check_one_cloud_one_capability(
45
- payload: Tuple[Tuple[str, Union[sky_clouds.Cloud, ModuleType]],
46
- sky_cloud.CloudCapability]
47
- ) -> Optional[Tuple[sky_cloud.CloudCapability, bool, Optional[str]]]:
48
- cloud_tuple, capability = payload
49
- _, cloud = cloud_tuple
50
- try:
51
- ok, reason = cloud.check_credentials(capability)
52
- except exceptions.NotSupportedError:
53
- return None
54
- except Exception: # pylint: disable=broad-except
55
- ok, reason = False, traceback.format_exc()
56
- return capability, ok, reason.strip() if reason else None
57
-
58
- def get_cloud_tuple(
59
- cloud_name: str) -> Tuple[str, Union[sky_clouds.Cloud, ModuleType]]:
60
- # Validates cloud_name and returns a tuple of the cloud's name and
61
- # the cloud object. Includes special handling for Cloudflare.
62
- if cloud_name.lower().startswith('cloudflare'):
63
- return cloudflare.NAME, cloudflare
64
- else:
65
- cloud_obj = registry.CLOUD_REGISTRY.from_str(cloud_name)
66
- assert cloud_obj is not None, f'Cloud {cloud_name!r} not found'
67
- return repr(cloud_obj), cloud_obj
68
83
 
69
- def get_all_clouds():
84
+ def get_all_clouds() -> Tuple[str, ...]:
70
85
  return tuple([repr(c) for c in registry.CLOUD_REGISTRY.values()] +
71
- [cloudflare.NAME])
72
-
73
- if clouds is not None:
74
- cloud_list = clouds
75
- else:
76
- cloud_list = get_all_clouds()
77
- clouds_to_check = [get_cloud_tuple(c) for c in cloud_list]
78
-
79
- # Use allowed_clouds from config if it exists, otherwise check all clouds.
80
- # Also validate names with get_cloud_tuple.
81
- config_allowed_cloud_names = sorted([
82
- get_cloud_tuple(c)[0] for c in skypilot_config.get_nested((
83
- 'allowed_clouds',), get_all_clouds())
84
- ])
85
- # Use disallowed_cloud_names for logging the clouds that will be disabled
86
- # because they are not included in allowed_clouds in config.yaml.
87
- disallowed_cloud_names = [
88
- c for c in get_all_clouds() if c not in config_allowed_cloud_names
89
- ]
90
- # Check only the clouds which are allowed in the config.
91
- clouds_to_check = [
92
- c for c in clouds_to_check if c[0] in config_allowed_cloud_names
93
- ]
94
-
95
- combinations = list(itertools.product(clouds_to_check, capabilities))
96
- with rich_utils.safe_status('Checking Cloud(s)...'):
97
- check_results = subprocess_utils.run_in_parallel(
98
- check_one_cloud_one_capability, combinations)
99
-
100
- check_results_dict: Dict[
101
- Tuple[str, Union[sky_clouds.Cloud, ModuleType]],
102
- List[Tuple[sky_cloud.CloudCapability, bool,
103
- Optional[str]]]] = collections.defaultdict(list)
104
- for combination, check_result in zip(combinations, check_results):
105
- if check_result is None:
106
- continue
107
- capability, ok, _ = check_result
108
- cloud_tuple, _ = combination
109
- cloud_repr = cloud_tuple[0]
110
- if ok:
111
- enabled_clouds.setdefault(cloud_repr, []).append(capability)
86
+ [cloudflare.NAME, coreweave.NAME])
87
+
88
+ def _execute_check_logic_for_workspace(
89
+ current_workspace_name: str,
90
+ hide_per_cloud_details: bool,
91
+ hide_workspace_str: bool,
92
+ ) -> Dict[str, List[sky_cloud.CloudCapability]]:
93
+ nonlocal echo, verbose, clouds, quiet
94
+
95
+ enabled_clouds: Dict[str, List[sky_cloud.CloudCapability]] = {}
96
+ disabled_clouds: Dict[str, List[sky_cloud.CloudCapability]] = {}
97
+
98
+ def check_one_cloud_one_capability(
99
+ payload: Tuple[Tuple[str, Union[sky_clouds.Cloud, ModuleType]],
100
+ sky_cloud.CloudCapability, bool]
101
+ ) -> Optional[Tuple[sky_cloud.CloudCapability, bool, Optional[Union[
102
+ str, Dict[str, str]]]]]:
103
+ cloud_tuple, capability, allowed = payload
104
+ if not allowed:
105
+ return (capability, False, f'{cloud_tuple[0]} is not included '
106
+ 'in allowed_clouds in ~/.sky/config.yaml')
107
+ with skypilot_config.local_active_workspace_ctx(
108
+ current_workspace_name):
109
+ # Have to override again for specific thread, as the
110
+ # local_active_workspace_ctx is thread-local.
111
+ _, cloud = cloud_tuple
112
+ try:
113
+ ok, reason = cloud.check_credentials(capability)
114
+ except exceptions.NotSupportedError:
115
+ return None
116
+ except Exception: # pylint: disable=broad-except
117
+ ok, reason = False, traceback.format_exc()
118
+ if not isinstance(reason, dict):
119
+ reason = reason.strip() if reason else None
120
+ return (capability, ok, reason)
121
+
122
+ def get_cloud_tuple(
123
+ cloud_name: str
124
+ ) -> Tuple[str, Union[sky_clouds.Cloud, ModuleType]]:
125
+ # Validates cloud_name and returns a tuple of the cloud's name and
126
+ # the cloud object. Includes special handling for Cloudflare and
127
+ # CoreWeave.
128
+ if cloud_name.lower().startswith('cloudflare'):
129
+ return cloudflare.NAME, cloudflare
130
+ elif cloud_name.lower().startswith('coreweave'):
131
+ return coreweave.NAME, coreweave
132
+ else:
133
+ cloud_obj = registry.CLOUD_REGISTRY.from_str(cloud_name)
134
+ assert cloud_obj is not None, f'Cloud {cloud_name!r} not found'
135
+ return repr(cloud_obj), cloud_obj
136
+
137
+ if clouds is not None:
138
+ cloud_list = clouds
139
+ check_explicit = True
112
140
  else:
113
- disabled_clouds.setdefault(cloud_repr, []).append(capability)
114
- check_results_dict[cloud_tuple].append(check_result)
115
-
116
- for cloud_tuple, check_result_list in sorted(check_results_dict.items(),
117
- key=lambda item: item[0][0]):
118
- _print_checked_cloud(echo, verbose, cloud_tuple, check_result_list)
119
-
120
- # Determine the set of enabled clouds: (previously enabled clouds + newly
121
- # enabled clouds - newly disabled clouds) intersected with
122
- # config_allowed_clouds, if specified in config.yaml.
123
- # This means that if a cloud is already enabled and is not included in
124
- # allowed_clouds in config.yaml, it will be disabled.
125
- all_enabled_clouds: Set[str] = set()
126
- for capability in capabilities:
127
- # Cloudflare is not a real cloud in registry.CLOUD_REGISTRY, and should
128
- # not be inserted into the DB (otherwise `sky launch` and other code
129
- # would error out when it's trying to look it up in the registry).
130
- enabled_clouds_set = {
131
- cloud for cloud, capabilities in enabled_clouds.items()
132
- if capability in capabilities and not cloud.startswith('Cloudflare')
133
- }
134
- disabled_clouds_set = {
135
- cloud for cloud, capabilities in disabled_clouds.items()
136
- if capability in capabilities and not cloud.startswith('Cloudflare')
137
- }
138
- config_allowed_clouds_set = {
139
- cloud for cloud in config_allowed_cloud_names
140
- if not cloud.startswith('Cloudflare')
141
- }
142
- previously_enabled_clouds_set = {
143
- repr(cloud)
144
- for cloud in global_user_state.get_cached_enabled_clouds(capability)
145
- }
146
- enabled_clouds_for_capability = (config_allowed_clouds_set & (
147
- (previously_enabled_clouds_set | enabled_clouds_set) -
148
- disabled_clouds_set))
149
- global_user_state.set_enabled_clouds(
150
- list(enabled_clouds_for_capability), capability)
151
- all_enabled_clouds = all_enabled_clouds.union(
152
- enabled_clouds_for_capability)
153
- disallowed_clouds_hint = None
154
- if disallowed_cloud_names:
155
- disallowed_clouds_hint = (
156
- '\nNote: The following clouds were disabled because they were not '
157
- 'included in allowed_clouds in ~/.sky/config.yaml: '
158
- f'{", ".join([c for c in disallowed_cloud_names])}')
159
- if not all_enabled_clouds:
141
+ cloud_list = get_all_clouds()
142
+ check_explicit = False
143
+
144
+ clouds_to_check = [get_cloud_tuple(c) for c in cloud_list]
145
+
146
+ # Use allowed_clouds from config if it exists, otherwise check all
147
+ # clouds. Also validate names with get_cloud_tuple.
148
+ config_allowed_cloud_names = sorted([
149
+ get_cloud_tuple(c)[0] for c in skypilot_config.get_nested((
150
+ 'allowed_clouds',), get_all_clouds())
151
+ ])
152
+
153
+ # filter out the clouds that are disabled in the workspace config
154
+ workspace_disabled_clouds = []
155
+ for cloud in config_allowed_cloud_names:
156
+ cloud_config = skypilot_config.get_workspace_cloud(
157
+ cloud, workspace=current_workspace_name)
158
+ cloud_disabled = cloud_config.get('disabled', False)
159
+ if cloud_disabled:
160
+ workspace_disabled_clouds.append(cloud)
161
+
162
+ config_allowed_cloud_names = [
163
+ c for c in config_allowed_cloud_names
164
+ if c not in workspace_disabled_clouds
165
+ ]
166
+ global_user_state.set_allowed_clouds(
167
+ [c for c in config_allowed_cloud_names], current_workspace_name)
168
+
169
+ # Use disallowed_cloud_names for logging the clouds that will be
170
+ # disabled because they are not included in allowed_clouds in
171
+ # config.yaml.
172
+ disallowed_cloud_names = [
173
+ c for c in get_all_clouds() if c not in config_allowed_cloud_names
174
+ ]
175
+
176
+ combinations = []
177
+ for c in clouds_to_check:
178
+ allowed = c[0] in config_allowed_cloud_names
179
+ if allowed or check_explicit:
180
+ for capability in capabilities:
181
+ combinations.append((c, capability, allowed))
182
+
183
+ cloud2ctx2text: Dict[str, Dict[str, str]] = {}
184
+
185
+ workspace_str = f' for workspace: {current_workspace_name!r}'
186
+ if hide_workspace_str:
187
+ workspace_str = ''
188
+ with rich_utils.safe_status(
189
+ ux_utils.spinner_message(
190
+ f'Checking infra choices{workspace_str}...')):
191
+ check_results = subprocess_utils.run_in_parallel(
192
+ check_one_cloud_one_capability, combinations)
193
+
194
+ check_results_dict: Dict[
195
+ Tuple[str, Union[sky_clouds.Cloud, ModuleType]],
196
+ List[Tuple[sky_cloud.CloudCapability, bool,
197
+ Optional[Union[str, Dict[str, str]]]]]] = (
198
+ collections.defaultdict(list))
199
+ for combination, check_result in zip(combinations, check_results):
200
+ if check_result is None:
201
+ continue
202
+ capability, ok, ctx2text = check_result
203
+ cloud_tuple, _, _ = combination
204
+ cloud_repr = cloud_tuple[0]
205
+ if isinstance(ctx2text, dict):
206
+ cloud2ctx2text[cloud_repr] = ctx2text
207
+ if ok:
208
+ enabled_clouds.setdefault(cloud_repr, []).append(capability)
209
+ else:
210
+ disabled_clouds.setdefault(cloud_repr, []).append(capability)
211
+ check_results_dict[cloud_tuple].append(check_result)
212
+
213
+ if not hide_per_cloud_details:
214
+ for cloud_tuple, check_result_list in sorted(
215
+ check_results_dict.items(), key=lambda item: item[0][0]):
216
+ _print_checked_cloud(echo, verbose, cloud_tuple,
217
+ check_result_list,
218
+ cloud2ctx2text.get(cloud_tuple[0], {}))
219
+
220
+ # Determine the set of enabled clouds: (previously enabled clouds +
221
+ # newly enabled clouds - newly disabled clouds) intersected with
222
+ # config_allowed_clouds, if specified in config.yaml.
223
+ # This means that if a cloud is already enabled and is not included in
224
+ # allowed_clouds in config.yaml, it will be disabled.
225
+ all_enabled_clouds: Set[str] = set()
226
+ for capability in capabilities:
227
+ # Cloudflare and CoreWeave are not real clouds in
228
+ # registry.CLOUD_REGISTRY, and should not be inserted into the DB
229
+ # (otherwise `sky launch` and other code would error out when it's
230
+ # trying to look it up in the registry).
231
+ enabled_clouds_set = {
232
+ cloud for cloud, capabilities in enabled_clouds.items()
233
+ if capability in capabilities and not cloud.startswith(
234
+ 'Cloudflare') and not cloud.startswith('CoreWeave')
235
+ }
236
+ disabled_clouds_set = {
237
+ cloud for cloud, capabilities in disabled_clouds.items()
238
+ if capability in capabilities and not cloud.startswith(
239
+ 'Cloudflare') and not cloud.startswith('CoreWeave')
240
+ }
241
+ config_allowed_clouds_set = {
242
+ cloud for cloud in config_allowed_cloud_names
243
+ if not cloud.startswith('Cloudflare') and
244
+ not cloud.startswith('CoreWeave')
245
+ }
246
+ previously_enabled_clouds_set = {
247
+ repr(cloud)
248
+ for cloud in global_user_state.get_cached_enabled_clouds(
249
+ capability, current_workspace_name)
250
+ }
251
+ enabled_clouds_for_capability = (config_allowed_clouds_set & (
252
+ (previously_enabled_clouds_set | enabled_clouds_set) -
253
+ disabled_clouds_set))
254
+
255
+ global_user_state.set_enabled_clouds(
256
+ list(enabled_clouds_for_capability), capability,
257
+ current_workspace_name)
258
+ all_enabled_clouds = all_enabled_clouds.union(
259
+ enabled_clouds_for_capability)
260
+
160
261
  echo(
161
- click.style(
162
- 'No cloud is enabled. SkyPilot will not be able to run any '
163
- 'task. Run `sky check` for more info.',
164
- fg='red',
165
- bold=True))
166
- if disallowed_clouds_hint:
167
- echo(click.style(disallowed_clouds_hint, dim=True))
168
- raise SystemExit()
262
+ _summary_message(enabled_clouds, cloud2ctx2text,
263
+ current_workspace_name, hide_workspace_str,
264
+ disallowed_cloud_names))
265
+
266
+ return enabled_clouds
267
+
268
+ # --- Main check_capabilities logic ---
269
+
270
+ if workspace is not None:
271
+ # Check only the specified workspace
272
+ if workspace not in available_workspaces:
273
+ with ux_utils.print_exception_no_traceback():
274
+ raise ValueError(
275
+ f'Workspace {workspace!r} not found in SkyPilot '
276
+ 'configuration. '
277
+ f'Available workspaces: {", ".join(available_workspaces)}')
278
+
279
+ # Always show details for single specified check (if verbose)
280
+ hide_per_cloud_details_flag = False
281
+ with skypilot_config.local_active_workspace_ctx(workspace):
282
+ enabled_ws_clouds = _execute_check_logic_for_workspace(
283
+ workspace, hide_per_cloud_details_flag, hide_workspace_str)
284
+ all_workspaces_results[workspace] = enabled_ws_clouds
169
285
  else:
170
- clouds_arg = (f' {" ".join(disabled_clouds).lower()}'
171
- if clouds is not None else '')
286
+ # Check all workspaces
287
+ workspaces_to_check = available_workspaces
288
+
289
+ hide_per_cloud_details_flag = (not verbose and clouds is None and
290
+ len(workspaces_to_check) > 1)
291
+
292
+ for ws_name in workspaces_to_check:
293
+ if not hide_workspace_str:
294
+ echo(f'\nChecking enabled infra for workspace: {ws_name!r}')
295
+ with skypilot_config.local_active_workspace_ctx(ws_name):
296
+ enabled_ws_clouds = _execute_check_logic_for_workspace(
297
+ ws_name, hide_per_cloud_details_flag, hide_workspace_str)
298
+ all_workspaces_results[ws_name] = enabled_ws_clouds
299
+
300
+ # Global "To enable a cloud..." message, printed once if relevant
301
+ if not quiet:
172
302
  echo(
173
303
  click.style(
174
304
  '\nTo enable a cloud, follow the hints above and rerun: ',
175
- dim=True) + click.style(f'sky check{clouds_arg}', bold=True) +
176
- '\n' + click.style(
305
+ dim=True) + click.style('sky check', bold=True) + '\n' +
306
+ click.style(
177
307
  'If any problems remain, refer to detailed docs at: '
178
308
  'https://docs.skypilot.co/en/latest/getting-started/installation.html', # pylint: disable=line-too-long
179
309
  dim=True))
180
310
 
181
- if disallowed_clouds_hint:
182
- echo(click.style(disallowed_clouds_hint, dim=True))
183
-
184
- # Pretty print for UX.
185
- if not quiet:
186
- enabled_clouds_str = '\n ' + '\n '.join([
187
- _format_enabled_cloud(cloud, capabilities)
188
- for cloud, capabilities in sorted(enabled_clouds.items(),
189
- key=lambda item: item[0])
190
- ])
191
- echo(f'\n{colorama.Fore.GREEN}{PARTY_POPPER_EMOJI} '
192
- f'Enabled clouds {PARTY_POPPER_EMOJI}'
193
- f'{colorama.Style.RESET_ALL}{enabled_clouds_str}')
194
- return enabled_clouds
311
+ return all_workspaces_results
195
312
 
196
313
 
197
314
  def check_capability(
@@ -199,12 +316,15 @@ def check_capability(
199
316
  quiet: bool = False,
200
317
  verbose: bool = False,
201
318
  clouds: Optional[Iterable[str]] = None,
202
- ) -> List[str]:
203
- clouds_with_capability = []
204
- enabled_clouds = check_capabilities(quiet, verbose, clouds, [capability])
205
- for cloud, capabilities in enabled_clouds.items():
206
- if capability in capabilities:
207
- clouds_with_capability.append(cloud)
319
+ workspace: Optional[str] = None,
320
+ ) -> Dict[str, List[str]]:
321
+ clouds_with_capability = collections.defaultdict(list)
322
+ workspace_enabled_clouds = check_capabilities(quiet, verbose, clouds,
323
+ [capability], workspace)
324
+ for workspace, enabled_clouds in workspace_enabled_clouds.items():
325
+ for cloud, capabilities in enabled_clouds.items():
326
+ if capability in capabilities:
327
+ clouds_with_capability[workspace].append(cloud)
208
328
  return clouds_with_capability
209
329
 
210
330
 
@@ -212,10 +332,31 @@ def check(
212
332
  quiet: bool = False,
213
333
  verbose: bool = False,
214
334
  clouds: Optional[Iterable[str]] = None,
215
- ) -> List[str]:
216
- return list(
217
- check_capabilities(quiet, verbose, clouds,
218
- sky_cloud.ALL_CAPABILITIES).keys())
335
+ workspace: Optional[str] = None,
336
+ ) -> Dict[str, List[str]]:
337
+ enabled_clouds_by_workspace: Dict[str,
338
+ List[str]] = collections.defaultdict(list)
339
+ capabilities_result = check_capabilities(quiet, verbose, clouds,
340
+ sky_cloud.ALL_CAPABILITIES,
341
+ workspace)
342
+ for ws_name, enabled_clouds_with_capabilities in capabilities_result.items(
343
+ ):
344
+ # For each workspace, get a list of cloud names that have any
345
+ # capabilities enabled.
346
+ # The inner dict enabled_clouds_with_capabilities maps cloud_name to
347
+ # List[CloudCapability].
348
+ # If the list of capabilities is non-empty, the cloud is considered
349
+ # enabled.
350
+ # We are interested in the keys (cloud names) of this dict if their
351
+ # value (list of capabilities) is not empty.
352
+ # However, check_capabilities already ensures that only clouds with
353
+ # *some* enabled capabilities (from the ones being checked, i.e.
354
+ # ALL_CAPABILITIES here) are included in its return value.
355
+ # So, the keys of enabled_clouds_with_capabilities are the enabled cloud
356
+ # names for that workspace.
357
+ enabled_clouds_by_workspace[ws_name] = list(
358
+ enabled_clouds_with_capabilities.keys())
359
+ return enabled_clouds_by_workspace
219
360
 
220
361
 
221
362
  def get_cached_enabled_clouds_or_refresh(
@@ -233,18 +374,30 @@ def get_cached_enabled_clouds_or_refresh(
233
374
  exceptions.NoCloudAccessError: if no public cloud is enabled and
234
375
  raise_if_no_cloud_access is set to True.
235
376
  """
377
+ active_workspace = skypilot_config.get_active_workspace()
378
+ allowed_clouds_changed = False
379
+ cached_allowed_clouds = global_user_state.get_allowed_clouds(
380
+ active_workspace)
381
+ skypilot_config_allowed_clouds = _get_workspace_allowed_clouds(
382
+ active_workspace)
383
+ if sorted(cached_allowed_clouds) != sorted(skypilot_config_allowed_clouds):
384
+ allowed_clouds_changed = True
385
+
236
386
  cached_enabled_clouds = global_user_state.get_cached_enabled_clouds(
237
- capability)
238
- if not cached_enabled_clouds:
387
+ capability, active_workspace)
388
+ if not cached_enabled_clouds or allowed_clouds_changed:
239
389
  try:
240
- check_capability(sky_cloud.CloudCapability.COMPUTE, quiet=True)
390
+ check_capability(capability, quiet=True, workspace=active_workspace)
391
+ if allowed_clouds_changed:
392
+ global_user_state.set_allowed_clouds(
393
+ skypilot_config_allowed_clouds, active_workspace)
241
394
  except SystemExit:
242
395
  # If no cloud is enabled, check() will raise SystemExit.
243
396
  # Here we catch it and raise the exception later only if
244
397
  # raise_if_no_cloud_access is set to True.
245
398
  pass
246
399
  cached_enabled_clouds = global_user_state.get_cached_enabled_clouds(
247
- capability)
400
+ capability, active_workspace)
248
401
  if raise_if_no_cloud_access and not cached_enabled_clouds:
249
402
  with ux_utils.print_exception_no_traceback():
250
403
  raise exceptions.NoCloudAccessError(
@@ -274,7 +427,8 @@ def get_cloud_credential_file_mounts(
274
427
  cloud_file_mounts = cloud.get_credential_file_mounts()
275
428
  for remote_path, local_path in cloud_file_mounts.items():
276
429
  if os.path.exists(os.path.expanduser(local_path)):
277
- file_mounts[remote_path] = local_path
430
+ file_mounts[remote_path] = os.path.realpath(
431
+ os.path.expanduser(local_path))
278
432
  # Currently, get_cached_enabled_clouds_or_refresh() does not support r2 as
279
433
  # only clouds with computing instances are marked as enabled by skypilot.
280
434
  # This will be removed when cloudflare/r2 is added as a 'cloud'.
@@ -282,6 +436,12 @@ def get_cloud_credential_file_mounts(
282
436
  if r2_is_enabled:
283
437
  r2_credential_mounts = cloudflare.get_credential_file_mounts()
284
438
  file_mounts.update(r2_credential_mounts)
439
+
440
+ # Similarly, handle CoreWeave storage credentials
441
+ coreweave_is_enabled, _ = coreweave.check_storage_credentials()
442
+ if coreweave_is_enabled:
443
+ coreweave_credential_mounts = coreweave.get_credential_file_mounts()
444
+ file_mounts.update(coreweave_credential_mounts)
285
445
  return file_mounts
286
446
 
287
447
 
@@ -290,7 +450,8 @@ def _print_checked_cloud(
290
450
  verbose: bool,
291
451
  cloud_tuple: Tuple[str, Union[sky_clouds.Cloud, ModuleType]],
292
452
  cloud_capabilities: List[Tuple[sky_cloud.CloudCapability, bool,
293
- Optional[str]]],
453
+ Optional[Union[str, Dict[str, str]]]]],
454
+ ctx2text: Dict[str, str],
294
455
  ) -> None:
295
456
  """Prints whether a cloud is enabled, and the capabilities that are enabled.
296
457
  If any hints (for enabled capabilities) or
@@ -317,34 +478,146 @@ def _print_checked_cloud(
317
478
  for capability, ok, reason in cloud_capabilities:
318
479
  if ok:
319
480
  enabled_capabilities.append(capability)
481
+ # `dict` reasons for K8s and SSH will be printed in detail in
482
+ # _format_enabled_cloud. Skip here unless the cloud is disabled.
483
+ if not isinstance(reason, str):
484
+ if not ok and isinstance(cloud_tuple[1],
485
+ (sky_clouds.SSH, sky_clouds.Kubernetes)):
486
+ if reason is not None:
487
+ reason_str = _format_context_details(cloud_tuple[1],
488
+ show_details=True,
489
+ ctx2text=reason)
490
+ reason_str = '\n'.join(
491
+ ' ' + line for line in reason_str.splitlines())
492
+ reasons_to_capabilities.setdefault(reason_str,
493
+ []).append(capability)
494
+ continue
495
+ if ok:
320
496
  if reason is not None:
321
497
  hints_to_capabilities.setdefault(reason, []).append(capability)
322
498
  elif reason is not None:
323
499
  reasons_to_capabilities.setdefault(reason, []).append(capability)
500
+ style_str = f'{colorama.Style.DIM}'
324
501
  status_msg: str = 'disabled'
325
- styles: Dict[str, Any] = {'dim': True}
326
502
  capability_string: str = ''
503
+ detail_string: str = ''
327
504
  activated_account: Optional[str] = None
328
505
  if enabled_capabilities:
506
+ style_str = f'{colorama.Fore.GREEN}{colorama.Style.NORMAL}'
329
507
  status_msg = 'enabled'
330
- styles = {'fg': 'green', 'bold': False}
331
508
  capability_string = f'[{", ".join(enabled_capabilities)}]'
332
- if verbose and cloud is not cloudflare:
509
+ if verbose and cloud is not cloudflare and cloud is not coreweave:
333
510
  activated_account = cloud.get_active_user_identity_str()
334
-
511
+ if isinstance(cloud_tuple[1], (sky_clouds.SSH, sky_clouds.Kubernetes)):
512
+ detail_string = _format_context_details(cloud_tuple[1],
513
+ show_details=True,
514
+ ctx2text=ctx2text)
335
515
  echo(
336
- click.style(f' {cloud_repr}: {status_msg} {capability_string}',
337
- **styles))
516
+ click.style(
517
+ f'{style_str} {cloud_repr}: {status_msg} {capability_string}'
518
+ f'{colorama.Style.RESET_ALL}{detail_string}'))
338
519
  if activated_account is not None:
339
520
  echo(f' Activated account: {activated_account}')
340
- for reason, caps in hints_to_capabilities.items():
341
- echo(f' Hint [{", ".join(caps)}]: {_yellow_color(reason)}')
342
- for reason, caps in reasons_to_capabilities.items():
343
- echo(f' Reason [{", ".join(caps)}]: {reason}')
521
+ for reason, capabilities in hints_to_capabilities.items():
522
+ echo(f' Hint [{", ".join(capabilities)}]: {_yellow_color(reason)}')
523
+ for reason, capabilities in reasons_to_capabilities.items():
524
+ echo(f' Reason [{", ".join(capabilities)}]: {reason}')
525
+
526
+
527
+ def _green_color(str_to_format: str) -> str:
528
+ return f'{colorama.Fore.GREEN}{str_to_format}{colorama.Style.RESET_ALL}'
529
+
530
+
531
+ def _format_context_details(cloud: Union[str, sky_clouds.Cloud],
532
+ show_details: bool,
533
+ ctx2text: Optional[Dict[str, str]] = None) -> str:
534
+ if isinstance(cloud, str):
535
+ cloud_type = registry.CLOUD_REGISTRY.from_str(cloud)
536
+ assert cloud_type is not None
537
+ else:
538
+ cloud_type = cloud
539
+ if isinstance(cloud_type, sky_clouds.SSH):
540
+ # Get the cluster names by reading from the node pools file
541
+ contexts = sky_clouds.SSH.get_ssh_node_pool_contexts()
542
+ else:
543
+ assert isinstance(cloud_type, sky_clouds.Kubernetes)
544
+ contexts = sky_clouds.Kubernetes.existing_allowed_contexts()
545
+
546
+ filtered_contexts = []
547
+ for context in contexts:
548
+ if not show_details:
549
+ # Skip
550
+ if (ctx2text is None or context not in ctx2text or
551
+ 'disabled' in ctx2text[context]):
552
+ continue
553
+ filtered_contexts.append(context)
554
+
555
+ if not filtered_contexts:
556
+ return ''
557
+
558
+ def _red_color(str_to_format: str) -> str:
559
+ return (f'{colorama.Fore.LIGHTRED_EX}'
560
+ f'{str_to_format}'
561
+ f'{colorama.Style.RESET_ALL}')
562
+
563
+ def _dim_color(str_to_format: str) -> str:
564
+ return (f'{colorama.Style.DIM}'
565
+ f'{str_to_format}'
566
+ f'{colorama.Style.RESET_ALL}')
567
+
568
+ # For SSH, determine which contexts are disabled due to allowed_node_pools
569
+ disabled_due_to_allowed_node_pools = set()
570
+ if isinstance(cloud_type, sky_clouds.SSH):
571
+ # Get all node pool contexts from file
572
+ all_node_pool_contexts = sky_clouds.SSH.get_ssh_node_pool_contexts()
573
+ # Get allowed contexts (after filtering)
574
+ allowed_contexts = sky_clouds.SSH.existing_allowed_contexts()
575
+ # Contexts that exist in file but not in allowed list are disabled
576
+ # due to allowed_node_pools configuration
577
+ disabled_due_to_allowed_node_pools = (set(all_node_pool_contexts) -
578
+ set(allowed_contexts))
579
+
580
+ # Format the context info with consistent styling
581
+ contexts_formatted = []
582
+ for i, context in enumerate(filtered_contexts):
583
+ if isinstance(cloud_type, sky_clouds.SSH):
584
+ # TODO: This is a hack to remove the 'ssh-' prefix from the
585
+ # context name. Once we have a separate kubeconfig for SSH,
586
+ # this will not be required.
587
+ cleaned_context = common_utils.removeprefix(context, 'ssh-')
588
+ else:
589
+ cleaned_context = context
590
+ symbol = (ux_utils.INDENT_LAST_SYMBOL if i == len(filtered_contexts) -
591
+ 1 else ux_utils.INDENT_SYMBOL)
592
+ text_suffix = ''
593
+ if show_details:
594
+ if ctx2text is not None:
595
+ if context in ctx2text:
596
+ text_suffix = f': {ctx2text[context]}'
597
+ elif (isinstance(cloud_type, sky_clouds.SSH) and
598
+ context in disabled_due_to_allowed_node_pools):
599
+ # Context is disabled due to allowed_node_pools config
600
+ text_suffix = (': ' + _red_color('disabled. ') +
601
+ _dim_color('Reason: Not included in '
602
+ 'allowed_node_pools '
603
+ 'configuration.'))
604
+ else:
605
+ # Default case - not set up
606
+ text_suffix = (': ' + _red_color('disabled. ') +
607
+ _dim_color('Reason: Not set up. Use '
608
+ '`sky ssh up --infra '
609
+ f'{context.lstrip("ssh-")}` '
610
+ 'to set up.'))
611
+ contexts_formatted.append(
612
+ f'\n {symbol}{cleaned_context}{text_suffix}')
613
+ identity_str = ('SSH Node Pools' if isinstance(cloud_type, sky_clouds.SSH)
614
+ else 'Allowed contexts')
615
+ return f'\n {identity_str}:{"".join(contexts_formatted)}'
344
616
 
345
617
 
346
618
  def _format_enabled_cloud(cloud_name: str,
347
- capabilities: List[sky_cloud.CloudCapability]) -> str:
619
+ capabilities: List[sky_cloud.CloudCapability],
620
+ ctx2text: Optional[Dict[str, str]] = None) -> str:
348
621
  """Format the summary of enabled cloud and its enabled capabilities.
349
622
 
350
623
  Args:
@@ -355,34 +628,49 @@ def _format_enabled_cloud(cloud_name: str,
355
628
  A string of the formatted cloud and capabilities.
356
629
  """
357
630
  cloud_and_capabilities = f'{cloud_name} [{", ".join(capabilities)}]'
631
+ title = _green_color(cloud_and_capabilities)
358
632
 
359
- def _green_color(str_to_format: str) -> str:
360
- return (
361
- f'{colorama.Fore.GREEN}{str_to_format}{colorama.Style.RESET_ALL}')
362
-
363
- if cloud_name == repr(sky_clouds.Kubernetes()):
364
- # Get enabled contexts for Kubernetes
365
- existing_contexts = sky_clouds.Kubernetes.existing_allowed_contexts()
366
- if not existing_contexts:
367
- return _green_color(cloud_and_capabilities)
368
-
369
- # Check if allowed_contexts is explicitly set in config
370
- allowed_contexts = skypilot_config.get_nested(
371
- ('kubernetes', 'allowed_contexts'), None)
372
-
373
- # Format the context info with consistent styling
374
- if allowed_contexts is not None:
375
- contexts_formatted = []
376
- for i, context in enumerate(existing_contexts):
377
- symbol = (ux_utils.INDENT_LAST_SYMBOL
378
- if i == len(existing_contexts) -
379
- 1 else ux_utils.INDENT_SYMBOL)
380
- contexts_formatted.append(f'\n {symbol}{context}')
381
- context_info = f' Allowed contexts:{"".join(contexts_formatted)}'
382
- else:
383
- context_info = f' Active context: {existing_contexts[0]}'
384
-
385
- return (f'{_green_color(cloud_and_capabilities)}\n'
386
- f' {colorama.Style.DIM}{context_info}'
387
- f'{colorama.Style.RESET_ALL}')
633
+ if cloud_name in [repr(sky_clouds.Kubernetes()), repr(sky_clouds.SSH())]:
634
+ return (f'{title}' + _format_context_details(
635
+ cloud_name, show_details=False, ctx2text=ctx2text))
388
636
  return _green_color(cloud_and_capabilities)
637
+
638
+
639
+ def _summary_message(
640
+ enabled_clouds: Dict[str, List[sky_cloud.CloudCapability]],
641
+ cloud2ctx2text: Dict[str, Dict[str, str]],
642
+ current_workspace_name: str,
643
+ hide_workspace_str: bool,
644
+ disallowed_cloud_names: List[str],
645
+ ) -> str:
646
+ if not enabled_clouds:
647
+ enabled_clouds_str = '\n No infra to check/enabled.'
648
+ else:
649
+ enabled_clouds_str = '\n ' + '\n '.join([
650
+ _format_enabled_cloud(cloud, capabilities,
651
+ cloud2ctx2text.get(cloud, None))
652
+ for cloud, capabilities in sorted(enabled_clouds.items(),
653
+ key=lambda item: item[0])
654
+ ])
655
+
656
+ workspace_str = f' for workspace: {current_workspace_name!r}'
657
+ if hide_workspace_str:
658
+ workspace_str = ''
659
+
660
+ disallowed_clouds_hint = ''
661
+ if disallowed_cloud_names:
662
+ disable_for_workspace_hint = (
663
+ f' or disabled for this workspace {current_workspace_name!r}')
664
+ if hide_workspace_str:
665
+ disable_for_workspace_hint = ''
666
+ disallowed_clouds_hint = (
667
+ '\nNote: The following clouds were disabled because they were not '
668
+ 'included in allowed_clouds in ~/.sky/config.yaml'
669
+ f'{disable_for_workspace_hint}: '
670
+ f'{", ".join([c for c in disallowed_cloud_names])}')
671
+
672
+ return (f'\n{colorama.Fore.GREEN}{PARTY_POPPER_EMOJI} '
673
+ f'Enabled infra{workspace_str} '
674
+ f'{PARTY_POPPER_EMOJI}'
675
+ f'{colorama.Style.RESET_ALL}{enabled_clouds_str}'
676
+ f'{disallowed_clouds_hint}')