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