skypilot-nightly 1.0.0.dev20250905__py3-none-any.whl → 1.0.0.dev20251210__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 (429) hide show
  1. sky/__init__.py +12 -2
  2. sky/adaptors/aws.py +27 -22
  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/ibm.py +5 -2
  8. sky/adaptors/kubernetes.py +64 -0
  9. sky/adaptors/nebius.py +3 -1
  10. sky/adaptors/primeintellect.py +1 -0
  11. sky/adaptors/seeweb.py +183 -0
  12. sky/adaptors/shadeform.py +89 -0
  13. sky/adaptors/slurm.py +478 -0
  14. sky/admin_policy.py +20 -0
  15. sky/authentication.py +157 -263
  16. sky/backends/__init__.py +3 -2
  17. sky/backends/backend.py +11 -3
  18. sky/backends/backend_utils.py +630 -185
  19. sky/backends/cloud_vm_ray_backend.py +1111 -928
  20. sky/backends/local_docker_backend.py +9 -5
  21. sky/backends/task_codegen.py +971 -0
  22. sky/backends/wheel_utils.py +18 -0
  23. sky/catalog/__init__.py +8 -3
  24. sky/catalog/aws_catalog.py +4 -0
  25. sky/catalog/common.py +19 -1
  26. sky/catalog/data_fetchers/fetch_aws.py +102 -80
  27. sky/catalog/data_fetchers/fetch_gcp.py +30 -3
  28. sky/catalog/data_fetchers/fetch_nebius.py +9 -6
  29. sky/catalog/data_fetchers/fetch_runpod.py +698 -0
  30. sky/catalog/data_fetchers/fetch_seeweb.py +329 -0
  31. sky/catalog/data_fetchers/fetch_shadeform.py +142 -0
  32. sky/catalog/kubernetes_catalog.py +36 -32
  33. sky/catalog/primeintellect_catalog.py +95 -0
  34. sky/catalog/runpod_catalog.py +5 -1
  35. sky/catalog/seeweb_catalog.py +184 -0
  36. sky/catalog/shadeform_catalog.py +165 -0
  37. sky/catalog/slurm_catalog.py +243 -0
  38. sky/check.py +87 -46
  39. sky/client/cli/command.py +1004 -434
  40. sky/client/cli/flags.py +4 -2
  41. sky/{volumes/utils.py → client/cli/table_utils.py} +111 -13
  42. sky/client/cli/utils.py +79 -0
  43. sky/client/common.py +12 -2
  44. sky/client/sdk.py +188 -65
  45. sky/client/sdk_async.py +34 -33
  46. sky/cloud_stores.py +82 -3
  47. sky/clouds/__init__.py +8 -0
  48. sky/clouds/aws.py +337 -129
  49. sky/clouds/azure.py +24 -18
  50. sky/clouds/cloud.py +47 -13
  51. sky/clouds/cudo.py +16 -13
  52. sky/clouds/do.py +9 -7
  53. sky/clouds/fluidstack.py +12 -5
  54. sky/clouds/gcp.py +14 -7
  55. sky/clouds/hyperbolic.py +12 -5
  56. sky/clouds/ibm.py +12 -5
  57. sky/clouds/kubernetes.py +80 -45
  58. sky/clouds/lambda_cloud.py +12 -5
  59. sky/clouds/nebius.py +23 -9
  60. sky/clouds/oci.py +19 -12
  61. sky/clouds/paperspace.py +4 -1
  62. sky/clouds/primeintellect.py +317 -0
  63. sky/clouds/runpod.py +85 -24
  64. sky/clouds/scp.py +12 -8
  65. sky/clouds/seeweb.py +477 -0
  66. sky/clouds/shadeform.py +400 -0
  67. sky/clouds/slurm.py +578 -0
  68. sky/clouds/ssh.py +6 -3
  69. sky/clouds/utils/scp_utils.py +61 -50
  70. sky/clouds/vast.py +43 -27
  71. sky/clouds/vsphere.py +14 -16
  72. sky/core.py +296 -195
  73. sky/dashboard/out/404.html +1 -1
  74. sky/dashboard/out/_next/static/KYAhEFa3FTfq4JyKVgo-s/_buildManifest.js +1 -0
  75. sky/dashboard/out/_next/static/chunks/1141-9c810f01ff4f398a.js +11 -0
  76. sky/dashboard/out/_next/static/chunks/1871-7e202677c42f43fe.js +6 -0
  77. sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
  78. sky/dashboard/out/_next/static/chunks/2369.fc20f0c2c8ed9fe7.js +15 -0
  79. sky/dashboard/out/_next/static/chunks/2755.edd818326d489a1d.js +26 -0
  80. sky/dashboard/out/_next/static/chunks/3294.ddda8c6c6f9f24dc.js +1 -0
  81. sky/dashboard/out/_next/static/chunks/3785.7e245f318f9d1121.js +1 -0
  82. sky/dashboard/out/_next/static/chunks/{6601-06114c982db410b6.js → 3800-b589397dc09c5b4e.js} +1 -1
  83. sky/dashboard/out/_next/static/chunks/3850-fd5696f3bbbaddae.js +1 -0
  84. sky/dashboard/out/_next/static/chunks/4725.172ede95d1b21022.js +1 -0
  85. sky/dashboard/out/_next/static/chunks/4937.a2baa2df5572a276.js +15 -0
  86. sky/dashboard/out/_next/static/chunks/6212-7bd06f60ba693125.js +13 -0
  87. sky/dashboard/out/_next/static/chunks/6856-da20c5fd999f319c.js +1 -0
  88. sky/dashboard/out/_next/static/chunks/6990-09cbf02d3cd518c3.js +1 -0
  89. sky/dashboard/out/_next/static/chunks/7359-c8d04e06886000b3.js +30 -0
  90. sky/dashboard/out/_next/static/chunks/7615-019513abc55b3b47.js +1 -0
  91. sky/dashboard/out/_next/static/chunks/8640.5b9475a2d18c5416.js +16 -0
  92. sky/dashboard/out/_next/static/chunks/8969-452f9d5cbdd2dc73.js +1 -0
  93. sky/dashboard/out/_next/static/chunks/9025.fa408f3242e9028d.js +6 -0
  94. sky/dashboard/out/_next/static/chunks/9353-8369df1cf105221c.js +1 -0
  95. sky/dashboard/out/_next/static/chunks/9360.a536cf6b1fa42355.js +31 -0
  96. sky/dashboard/out/_next/static/chunks/9847.3aaca6bb33455140.js +30 -0
  97. sky/dashboard/out/_next/static/chunks/pages/_app-68b647e26f9d2793.js +34 -0
  98. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-33f525539665fdfd.js +16 -0
  99. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-a7565f586ef86467.js +1 -0
  100. sky/dashboard/out/_next/static/chunks/pages/clusters-9e5d47818b9bdadd.js +1 -0
  101. sky/dashboard/out/_next/static/chunks/pages/{config-dfb9bf07b13045f4.js → config-718cdc365de82689.js} +1 -1
  102. sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-6563820e094f68ca.js → [context]-12c559ec4d81fdbd.js} +1 -1
  103. sky/dashboard/out/_next/static/chunks/pages/{infra-aabba60d57826e0f.js → infra-d187cd0413d72475.js} +1 -1
  104. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-895847b6cf200b04.js +16 -0
  105. sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-8d0f4655400b4eb9.js +21 -0
  106. sky/dashboard/out/_next/static/chunks/pages/jobs-e5a98f17f8513a96.js +1 -0
  107. sky/dashboard/out/_next/static/chunks/pages/plugins/[...slug]-4f46050ca065d8f8.js +1 -0
  108. sky/dashboard/out/_next/static/chunks/pages/users-2f7646eb77785a2c.js +1 -0
  109. sky/dashboard/out/_next/static/chunks/pages/volumes-ef19d49c6d0e8500.js +1 -0
  110. sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-af76bb06dbb3954f.js → [name]-96e0f298308da7e2.js} +1 -1
  111. sky/dashboard/out/_next/static/chunks/pages/{workspaces-7598c33a746cdc91.js → workspaces-cb4da3abe08ebf19.js} +1 -1
  112. sky/dashboard/out/_next/static/chunks/webpack-fba3de387ff6bb08.js +1 -0
  113. sky/dashboard/out/_next/static/css/c5a4cfd2600fc715.css +3 -0
  114. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  115. sky/dashboard/out/clusters/[cluster].html +1 -1
  116. sky/dashboard/out/clusters.html +1 -1
  117. sky/dashboard/out/config.html +1 -1
  118. sky/dashboard/out/index.html +1 -1
  119. sky/dashboard/out/infra/[context].html +1 -1
  120. sky/dashboard/out/infra.html +1 -1
  121. sky/dashboard/out/jobs/[job].html +1 -1
  122. sky/dashboard/out/jobs/pools/[pool].html +1 -1
  123. sky/dashboard/out/jobs.html +1 -1
  124. sky/dashboard/out/plugins/[...slug].html +1 -0
  125. sky/dashboard/out/users.html +1 -1
  126. sky/dashboard/out/volumes.html +1 -1
  127. sky/dashboard/out/workspace/new.html +1 -1
  128. sky/dashboard/out/workspaces/[name].html +1 -1
  129. sky/dashboard/out/workspaces.html +1 -1
  130. sky/data/data_utils.py +92 -1
  131. sky/data/mounting_utils.py +177 -30
  132. sky/data/storage.py +200 -19
  133. sky/data/storage_utils.py +10 -45
  134. sky/exceptions.py +18 -7
  135. sky/execution.py +74 -31
  136. sky/global_user_state.py +605 -191
  137. sky/jobs/__init__.py +2 -0
  138. sky/jobs/client/sdk.py +101 -4
  139. sky/jobs/client/sdk_async.py +31 -5
  140. sky/jobs/constants.py +15 -8
  141. sky/jobs/controller.py +726 -284
  142. sky/jobs/file_content_utils.py +128 -0
  143. sky/jobs/log_gc.py +193 -0
  144. sky/jobs/recovery_strategy.py +250 -100
  145. sky/jobs/scheduler.py +271 -173
  146. sky/jobs/server/core.py +367 -114
  147. sky/jobs/server/server.py +81 -35
  148. sky/jobs/server/utils.py +89 -35
  149. sky/jobs/state.py +1498 -620
  150. sky/jobs/utils.py +771 -306
  151. sky/logs/agent.py +40 -5
  152. sky/logs/aws.py +9 -19
  153. sky/metrics/utils.py +282 -39
  154. sky/models.py +2 -0
  155. sky/optimizer.py +7 -6
  156. sky/provision/__init__.py +38 -1
  157. sky/provision/aws/config.py +34 -13
  158. sky/provision/aws/instance.py +5 -2
  159. sky/provision/azure/instance.py +5 -3
  160. sky/provision/common.py +22 -0
  161. sky/provision/cudo/instance.py +4 -3
  162. sky/provision/do/instance.py +4 -3
  163. sky/provision/docker_utils.py +112 -28
  164. sky/provision/fluidstack/instance.py +6 -5
  165. sky/provision/gcp/config.py +6 -1
  166. sky/provision/gcp/instance.py +4 -2
  167. sky/provision/hyperbolic/instance.py +4 -2
  168. sky/provision/instance_setup.py +66 -20
  169. sky/provision/kubernetes/__init__.py +2 -0
  170. sky/provision/kubernetes/config.py +7 -44
  171. sky/provision/kubernetes/constants.py +0 -1
  172. sky/provision/kubernetes/instance.py +609 -213
  173. sky/provision/kubernetes/manifests/fusermount-server-daemonset.yaml +1 -2
  174. sky/provision/kubernetes/network.py +12 -8
  175. sky/provision/kubernetes/network_utils.py +8 -25
  176. sky/provision/kubernetes/utils.py +422 -422
  177. sky/provision/kubernetes/volume.py +150 -18
  178. sky/provision/lambda_cloud/instance.py +16 -13
  179. sky/provision/nebius/instance.py +6 -2
  180. sky/provision/nebius/utils.py +103 -86
  181. sky/provision/oci/instance.py +4 -2
  182. sky/provision/paperspace/instance.py +4 -3
  183. sky/provision/primeintellect/__init__.py +10 -0
  184. sky/provision/primeintellect/config.py +11 -0
  185. sky/provision/primeintellect/instance.py +454 -0
  186. sky/provision/primeintellect/utils.py +398 -0
  187. sky/provision/provisioner.py +45 -15
  188. sky/provision/runpod/__init__.py +2 -0
  189. sky/provision/runpod/instance.py +4 -3
  190. sky/provision/runpod/volume.py +69 -13
  191. sky/provision/scp/instance.py +307 -130
  192. sky/provision/seeweb/__init__.py +11 -0
  193. sky/provision/seeweb/config.py +13 -0
  194. sky/provision/seeweb/instance.py +812 -0
  195. sky/provision/shadeform/__init__.py +11 -0
  196. sky/provision/shadeform/config.py +12 -0
  197. sky/provision/shadeform/instance.py +351 -0
  198. sky/provision/shadeform/shadeform_utils.py +83 -0
  199. sky/provision/slurm/__init__.py +12 -0
  200. sky/provision/slurm/config.py +13 -0
  201. sky/provision/slurm/instance.py +572 -0
  202. sky/provision/slurm/utils.py +583 -0
  203. sky/provision/vast/instance.py +9 -4
  204. sky/provision/vast/utils.py +10 -6
  205. sky/provision/volume.py +164 -0
  206. sky/provision/vsphere/common/ssl_helper.py +1 -1
  207. sky/provision/vsphere/common/vapiconnect.py +2 -1
  208. sky/provision/vsphere/common/vim_utils.py +3 -2
  209. sky/provision/vsphere/instance.py +8 -6
  210. sky/provision/vsphere/vsphere_utils.py +8 -1
  211. sky/resources.py +11 -3
  212. sky/schemas/api/responses.py +107 -6
  213. sky/schemas/db/global_user_state/008_skylet_ssh_tunnel_metadata.py +34 -0
  214. sky/schemas/db/global_user_state/009_last_activity_and_launched_at.py +89 -0
  215. sky/schemas/db/global_user_state/010_save_ssh_key.py +66 -0
  216. sky/schemas/db/global_user_state/011_is_ephemeral.py +34 -0
  217. sky/schemas/db/kv_cache/001_initial_schema.py +29 -0
  218. sky/schemas/db/serve_state/002_yaml_content.py +34 -0
  219. sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
  220. sky/schemas/db/spot_jobs/002_cluster_pool.py +3 -3
  221. sky/schemas/db/spot_jobs/004_job_file_contents.py +42 -0
  222. sky/schemas/db/spot_jobs/005_logs_gc.py +38 -0
  223. sky/schemas/db/spot_jobs/006_controller_pid_started_at.py +34 -0
  224. sky/schemas/db/spot_jobs/007_config_file_content.py +34 -0
  225. sky/schemas/generated/jobsv1_pb2.py +86 -0
  226. sky/schemas/generated/jobsv1_pb2.pyi +254 -0
  227. sky/schemas/generated/jobsv1_pb2_grpc.py +542 -0
  228. sky/schemas/generated/managed_jobsv1_pb2.py +76 -0
  229. sky/schemas/generated/managed_jobsv1_pb2.pyi +278 -0
  230. sky/schemas/generated/managed_jobsv1_pb2_grpc.py +278 -0
  231. sky/schemas/generated/servev1_pb2.py +58 -0
  232. sky/schemas/generated/servev1_pb2.pyi +115 -0
  233. sky/schemas/generated/servev1_pb2_grpc.py +322 -0
  234. sky/serve/autoscalers.py +2 -0
  235. sky/serve/client/impl.py +55 -21
  236. sky/serve/constants.py +4 -3
  237. sky/serve/controller.py +17 -11
  238. sky/serve/load_balancing_policies.py +1 -1
  239. sky/serve/replica_managers.py +219 -142
  240. sky/serve/serve_rpc_utils.py +179 -0
  241. sky/serve/serve_state.py +63 -54
  242. sky/serve/serve_utils.py +145 -109
  243. sky/serve/server/core.py +46 -25
  244. sky/serve/server/impl.py +311 -162
  245. sky/serve/server/server.py +21 -19
  246. sky/serve/service.py +84 -68
  247. sky/serve/service_spec.py +45 -7
  248. sky/server/auth/loopback.py +38 -0
  249. sky/server/auth/oauth2_proxy.py +12 -7
  250. sky/server/common.py +47 -24
  251. sky/server/config.py +62 -28
  252. sky/server/constants.py +9 -1
  253. sky/server/daemons.py +109 -38
  254. sky/server/metrics.py +76 -96
  255. sky/server/middleware_utils.py +166 -0
  256. sky/server/plugins.py +222 -0
  257. sky/server/requests/executor.py +384 -145
  258. sky/server/requests/payloads.py +83 -19
  259. sky/server/requests/preconditions.py +15 -13
  260. sky/server/requests/request_names.py +123 -0
  261. sky/server/requests/requests.py +511 -157
  262. sky/server/requests/serializers/decoders.py +48 -17
  263. sky/server/requests/serializers/encoders.py +102 -20
  264. sky/server/requests/serializers/return_value_serializers.py +60 -0
  265. sky/server/requests/threads.py +117 -0
  266. sky/server/rest.py +116 -24
  267. sky/server/server.py +497 -179
  268. sky/server/server_utils.py +30 -0
  269. sky/server/stream_utils.py +219 -45
  270. sky/server/uvicorn.py +30 -19
  271. sky/setup_files/MANIFEST.in +6 -1
  272. sky/setup_files/alembic.ini +8 -0
  273. sky/setup_files/dependencies.py +64 -19
  274. sky/setup_files/setup.py +44 -44
  275. sky/sky_logging.py +13 -5
  276. sky/skylet/attempt_skylet.py +116 -24
  277. sky/skylet/configs.py +3 -1
  278. sky/skylet/constants.py +139 -29
  279. sky/skylet/events.py +74 -14
  280. sky/skylet/executor/__init__.py +1 -0
  281. sky/skylet/executor/slurm.py +189 -0
  282. sky/skylet/job_lib.py +143 -105
  283. sky/skylet/log_lib.py +252 -8
  284. sky/skylet/log_lib.pyi +47 -7
  285. sky/skylet/providers/ibm/node_provider.py +12 -8
  286. sky/skylet/providers/ibm/vpc_provider.py +13 -12
  287. sky/skylet/runtime_utils.py +21 -0
  288. sky/skylet/services.py +524 -0
  289. sky/skylet/skylet.py +27 -2
  290. sky/skylet/subprocess_daemon.py +104 -28
  291. sky/skypilot_config.py +99 -79
  292. sky/ssh_node_pools/constants.py +12 -0
  293. sky/ssh_node_pools/core.py +40 -3
  294. sky/ssh_node_pools/deploy/__init__.py +4 -0
  295. sky/ssh_node_pools/deploy/deploy.py +952 -0
  296. sky/ssh_node_pools/deploy/tunnel_utils.py +199 -0
  297. sky/ssh_node_pools/deploy/utils.py +173 -0
  298. sky/ssh_node_pools/server.py +20 -21
  299. sky/{utils/kubernetes/ssh_utils.py → ssh_node_pools/utils.py} +9 -6
  300. sky/task.py +221 -104
  301. sky/templates/aws-ray.yml.j2 +1 -0
  302. sky/templates/azure-ray.yml.j2 +1 -0
  303. sky/templates/cudo-ray.yml.j2 +1 -0
  304. sky/templates/do-ray.yml.j2 +1 -0
  305. sky/templates/fluidstack-ray.yml.j2 +1 -0
  306. sky/templates/gcp-ray.yml.j2 +1 -0
  307. sky/templates/hyperbolic-ray.yml.j2 +1 -0
  308. sky/templates/ibm-ray.yml.j2 +2 -1
  309. sky/templates/jobs-controller.yaml.j2 +3 -0
  310. sky/templates/kubernetes-ray.yml.j2 +204 -55
  311. sky/templates/lambda-ray.yml.j2 +1 -0
  312. sky/templates/nebius-ray.yml.j2 +3 -0
  313. sky/templates/oci-ray.yml.j2 +1 -0
  314. sky/templates/paperspace-ray.yml.j2 +1 -0
  315. sky/templates/primeintellect-ray.yml.j2 +72 -0
  316. sky/templates/runpod-ray.yml.j2 +1 -0
  317. sky/templates/scp-ray.yml.j2 +1 -0
  318. sky/templates/seeweb-ray.yml.j2 +171 -0
  319. sky/templates/shadeform-ray.yml.j2 +73 -0
  320. sky/templates/slurm-ray.yml.j2 +85 -0
  321. sky/templates/vast-ray.yml.j2 +2 -0
  322. sky/templates/vsphere-ray.yml.j2 +1 -0
  323. sky/templates/websocket_proxy.py +188 -43
  324. sky/usage/usage_lib.py +16 -4
  325. sky/users/model.conf +1 -1
  326. sky/users/permission.py +84 -44
  327. sky/users/rbac.py +31 -3
  328. sky/utils/accelerator_registry.py +6 -3
  329. sky/utils/admin_policy_utils.py +18 -5
  330. sky/utils/annotations.py +128 -6
  331. sky/utils/asyncio_utils.py +78 -0
  332. sky/utils/atomic.py +1 -1
  333. sky/utils/auth_utils.py +153 -0
  334. sky/utils/cli_utils/status_utils.py +12 -7
  335. sky/utils/cluster_utils.py +28 -6
  336. sky/utils/command_runner.py +283 -30
  337. sky/utils/command_runner.pyi +63 -7
  338. sky/utils/common.py +3 -1
  339. sky/utils/common_utils.py +55 -7
  340. sky/utils/config_utils.py +1 -14
  341. sky/utils/context.py +127 -40
  342. sky/utils/context_utils.py +73 -18
  343. sky/utils/controller_utils.py +229 -70
  344. sky/utils/db/db_utils.py +95 -18
  345. sky/utils/db/kv_cache.py +149 -0
  346. sky/utils/db/migration_utils.py +24 -7
  347. sky/utils/env_options.py +4 -0
  348. sky/utils/git.py +559 -1
  349. sky/utils/kubernetes/create_cluster.sh +15 -30
  350. sky/utils/kubernetes/delete_cluster.sh +10 -7
  351. sky/utils/kubernetes/generate_kind_config.py +6 -66
  352. sky/utils/kubernetes/gpu_labeler.py +13 -3
  353. sky/utils/kubernetes/k8s_gpu_labeler_job.yaml +2 -1
  354. sky/utils/kubernetes/k8s_gpu_labeler_setup.yaml +16 -16
  355. sky/utils/kubernetes/kubernetes_deploy_utils.py +187 -260
  356. sky/utils/kubernetes/rsync_helper.sh +11 -3
  357. sky/utils/kubernetes/ssh-tunnel.sh +7 -376
  358. sky/utils/kubernetes_enums.py +7 -15
  359. sky/utils/lock_events.py +4 -4
  360. sky/utils/locks.py +128 -31
  361. sky/utils/log_utils.py +0 -319
  362. sky/utils/resource_checker.py +13 -10
  363. sky/utils/resources_utils.py +53 -29
  364. sky/utils/rich_utils.py +8 -4
  365. sky/utils/schemas.py +138 -52
  366. sky/utils/subprocess_utils.py +17 -4
  367. sky/utils/thread_utils.py +91 -0
  368. sky/utils/timeline.py +2 -1
  369. sky/utils/ux_utils.py +35 -1
  370. sky/utils/volume.py +88 -4
  371. sky/utils/yaml_utils.py +9 -0
  372. sky/volumes/client/sdk.py +48 -10
  373. sky/volumes/server/core.py +59 -22
  374. sky/volumes/server/server.py +46 -17
  375. sky/volumes/volume.py +54 -42
  376. sky/workspaces/core.py +57 -21
  377. sky/workspaces/server.py +13 -12
  378. sky_templates/README.md +3 -0
  379. sky_templates/__init__.py +3 -0
  380. sky_templates/ray/__init__.py +0 -0
  381. sky_templates/ray/start_cluster +183 -0
  382. sky_templates/ray/stop_cluster +75 -0
  383. {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251210.dist-info}/METADATA +343 -65
  384. skypilot_nightly-1.0.0.dev20251210.dist-info/RECORD +629 -0
  385. skypilot_nightly-1.0.0.dev20251210.dist-info/top_level.txt +2 -0
  386. sky/client/cli/git.py +0 -549
  387. sky/dashboard/out/_next/static/chunks/1121-408ed10b2f9fce17.js +0 -1
  388. sky/dashboard/out/_next/static/chunks/1141-943efc7aff0f0c06.js +0 -1
  389. sky/dashboard/out/_next/static/chunks/1836-37fede578e2da5f8.js +0 -40
  390. sky/dashboard/out/_next/static/chunks/3015-86cabed5d4669ad0.js +0 -1
  391. sky/dashboard/out/_next/static/chunks/3294.c80326aec9bfed40.js +0 -6
  392. sky/dashboard/out/_next/static/chunks/3785.4872a2f3aa489880.js +0 -1
  393. sky/dashboard/out/_next/static/chunks/3850-ff4a9a69d978632b.js +0 -1
  394. sky/dashboard/out/_next/static/chunks/4045.b30465273dc5e468.js +0 -21
  395. sky/dashboard/out/_next/static/chunks/4676-9da7fdbde90b5549.js +0 -10
  396. sky/dashboard/out/_next/static/chunks/4725.10f7a9a5d3ea8208.js +0 -1
  397. sky/dashboard/out/_next/static/chunks/5339.3fda4a4010ff4e06.js +0 -51
  398. sky/dashboard/out/_next/static/chunks/6135-4b4d5e824b7f9d3c.js +0 -1
  399. sky/dashboard/out/_next/static/chunks/649.b9d7f7d10c1b8c53.js +0 -45
  400. sky/dashboard/out/_next/static/chunks/6856-dca7962af4814e1b.js +0 -1
  401. sky/dashboard/out/_next/static/chunks/6990-08b2a1cae076a943.js +0 -1
  402. sky/dashboard/out/_next/static/chunks/7325.b4bc99ce0892dcd5.js +0 -6
  403. sky/dashboard/out/_next/static/chunks/754-d0da8ab45f9509e9.js +0 -18
  404. sky/dashboard/out/_next/static/chunks/7669.1f5d9a402bf5cc42.js +0 -36
  405. sky/dashboard/out/_next/static/chunks/8969-0be3036bf86f8256.js +0 -1
  406. sky/dashboard/out/_next/static/chunks/9025.c12318fb6a1a9093.js +0 -6
  407. sky/dashboard/out/_next/static/chunks/9037-fa1737818d0a0969.js +0 -6
  408. sky/dashboard/out/_next/static/chunks/pages/_app-ce361c6959bc2001.js +0 -34
  409. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-1cbba24bd1bd35f8.js +0 -16
  410. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-0b4b35dc1dfe046c.js +0 -16
  411. sky/dashboard/out/_next/static/chunks/pages/clusters-469814d711d63b1b.js +0 -1
  412. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-dd64309c3fe67ed2.js +0 -11
  413. sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-07349868f7905d37.js +0 -16
  414. sky/dashboard/out/_next/static/chunks/pages/jobs-1f70d9faa564804f.js +0 -1
  415. sky/dashboard/out/_next/static/chunks/pages/users-018bf31cda52e11b.js +0 -1
  416. sky/dashboard/out/_next/static/chunks/pages/volumes-739726d6b823f532.js +0 -1
  417. sky/dashboard/out/_next/static/chunks/webpack-4fe903277b57b523.js +0 -1
  418. sky/dashboard/out/_next/static/css/4614e06482d7309e.css +0 -3
  419. sky/dashboard/out/_next/static/mS-4qZPSkRuA1u-g2wQhg/_buildManifest.js +0 -1
  420. sky/templates/kubernetes-ssh-jump.yml.j2 +0 -94
  421. sky/utils/kubernetes/cleanup-tunnel.sh +0 -62
  422. sky/utils/kubernetes/deploy_remote_cluster.py +0 -1299
  423. sky/utils/kubernetes/ssh_jump_lifecycle_manager.py +0 -191
  424. skypilot_nightly-1.0.0.dev20250905.dist-info/RECORD +0 -547
  425. skypilot_nightly-1.0.0.dev20250905.dist-info/top_level.txt +0 -1
  426. /sky/dashboard/out/_next/static/{mS-4qZPSkRuA1u-g2wQhg → KYAhEFa3FTfq4JyKVgo-s}/_ssgManifest.js +0 -0
  427. {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251210.dist-info}/WHEEL +0 -0
  428. {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251210.dist-info}/entry_points.txt +0 -0
  429. {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251210.dist-info}/licenses/LICENSE +0 -0
sky/usage/usage_lib.py CHANGED
@@ -14,6 +14,7 @@ from typing_extensions import ParamSpec
14
14
 
15
15
  import sky
16
16
  from sky import sky_logging
17
+ from sky import skypilot_config
17
18
  from sky.adaptors import common as adaptors_common
18
19
  from sky.usage import constants
19
20
  from sky.utils import common_utils
@@ -167,6 +168,7 @@ class UsageMessageToReport(MessageToReport):
167
168
  self.runtimes: Dict[str, float] = {} # update_runtime
168
169
  self.exception: Optional[str] = None # entrypoint_context
169
170
  self.stacktrace: Optional[str] = None # entrypoint_context
171
+ self.skypilot_config: Optional[Dict[str, Any]] = None
170
172
 
171
173
  # Whether API server is deployed remotely.
172
174
  self.using_remote_api_server: bool = (
@@ -177,6 +179,7 @@ class UsageMessageToReport(MessageToReport):
177
179
  self.client_entrypoint = common_utils.get_current_client_entrypoint(
178
180
  msg)
179
181
  self.entrypoint = msg
182
+ self.skypilot_config = dict(skypilot_config.to_dict())
180
183
 
181
184
  def set_internal(self):
182
185
  self.internal = True
@@ -313,21 +316,30 @@ class MessageCollection:
313
316
  """A collection of messages."""
314
317
 
315
318
  def __init__(self):
316
- self._messages = {
319
+ self._messages: Dict[MessageType, MessageToReport] = {
317
320
  MessageType.USAGE: UsageMessageToReport(),
318
321
  MessageType.HEARTBEAT: HeartbeatMessageToReport()
319
322
  }
320
323
 
321
324
  @property
322
325
  def usage(self) -> UsageMessageToReport:
323
- return self._messages[MessageType.USAGE]
326
+ msg = self._messages[MessageType.USAGE]
327
+ assert isinstance(msg, UsageMessageToReport)
328
+ return msg
324
329
 
325
330
  @property
326
331
  def heartbeat(self) -> HeartbeatMessageToReport:
327
- return self._messages[MessageType.HEARTBEAT]
332
+ msg = self._messages[MessageType.HEARTBEAT]
333
+ assert isinstance(msg, HeartbeatMessageToReport)
334
+ return msg
328
335
 
329
336
  def reset(self, message_type: MessageType):
330
- self._messages[message_type] = self._messages[message_type].__class__()
337
+ if message_type == MessageType.USAGE:
338
+ self._messages[message_type] = UsageMessageToReport()
339
+ elif message_type == MessageType.HEARTBEAT:
340
+ self._messages[message_type] = HeartbeatMessageToReport()
341
+ else:
342
+ raise ValueError(f'Unknown message type: {message_type}')
331
343
 
332
344
  def __getitem__(self, key):
333
345
  return self._messages[key]
sky/users/model.conf CHANGED
@@ -12,4 +12,4 @@ g = _, _
12
12
  e = some(where (p.eft == allow))
13
13
 
14
14
  [matchers]
15
- m = (g(r.sub, p.sub)|| p.sub == '*') && r.obj == p.obj && r.act == p.act
15
+ m = (g(r.sub, p.sub)|| p.sub == '*') && keyMatch2(r.obj, p.obj) && r.act == p.act
sky/users/permission.py CHANGED
@@ -3,7 +3,7 @@ import contextlib
3
3
  import hashlib
4
4
  import logging
5
5
  import os
6
- from typing import Generator, List
6
+ from typing import Generator, List, Optional
7
7
 
8
8
  import casbin
9
9
  import filelock
@@ -14,6 +14,7 @@ from sky import models
14
14
  from sky import sky_logging
15
15
  from sky.skylet import constants
16
16
  from sky.users import rbac
17
+ from sky.utils import annotations
17
18
  from sky.utils import common_utils
18
19
  from sky.utils.db import db_utils
19
20
 
@@ -27,14 +28,14 @@ logger = sky_logging.init_logger(__name__)
27
28
  POLICY_UPDATE_LOCK_PATH = os.path.expanduser('~/.sky/.policy_update.lock')
28
29
  POLICY_UPDATE_LOCK_TIMEOUT_SECONDS = 20
29
30
 
30
- _enforcer_instance = None
31
+ _enforcer_instance: Optional['PermissionService'] = None
31
32
 
32
33
 
33
34
  class PermissionService:
34
35
  """Permission service for SkyPilot API Server."""
35
36
 
36
37
  def __init__(self):
37
- self.enforcer = None
38
+ self.enforcer: Optional[casbin.Enforcer] = None
38
39
 
39
40
  def _lazy_initialize(self):
40
41
  if self.enforcer is not None:
@@ -42,7 +43,6 @@ class PermissionService:
42
43
  with _policy_lock():
43
44
  global _enforcer_instance
44
45
  if _enforcer_instance is None:
45
- _enforcer_instance = self
46
46
  engine = global_user_state.initialize_and_get_db()
47
47
  db_utils.add_all_tables_to_db_sqlalchemy(
48
48
  sqlalchemy_adapter.Base.metadata, engine)
@@ -52,11 +52,43 @@ class PermissionService:
52
52
  'model.conf')
53
53
  enforcer = casbin.Enforcer(model_path, adapter)
54
54
  self.enforcer = enforcer
55
+ # Only set the enforcer instance once the enforcer
56
+ # is successfully initialized, if we change it and then fail
57
+ # we will set it to None and all subsequent calls will fail.
58
+ _enforcer_instance = self
55
59
  self._maybe_initialize_policies()
56
60
  self._maybe_initialize_basic_auth_user()
57
61
  else:
62
+ assert _enforcer_instance is not None
58
63
  self.enforcer = _enforcer_instance.enforcer
59
64
 
65
+ def _ensure_enforcer(self) -> casbin.Enforcer:
66
+ """Ensure enforcer is initialized and return it."""
67
+ self._lazy_initialize()
68
+ assert self.enforcer is not None, (
69
+ 'Enforcer should be initialized after _lazy_initialize()')
70
+ return self.enforcer
71
+
72
+ def _get_plugin_rbac_rules(self):
73
+ """Get RBAC rules from loaded plugins.
74
+
75
+ Returns:
76
+ Dictionary of plugin RBAC rules, or empty dict if plugins module
77
+ is not available or no rules are defined.
78
+ """
79
+ try:
80
+ # pylint: disable=import-outside-toplevel
81
+ from sky.server import plugins as server_plugins
82
+ return server_plugins.get_plugin_rbac_rules()
83
+ except ImportError:
84
+ # Plugin module not available (e.g., not running as server)
85
+ logger.debug(
86
+ 'Plugin module not available, skipping plugin RBAC rules')
87
+ return {}
88
+ except Exception as e: # pylint: disable=broad-except
89
+ logger.warning(f'Failed to get plugin RBAC rules: {e}')
90
+ return {}
91
+
60
92
  def _maybe_initialize_basic_auth_user(self) -> None:
61
93
  """Initialize basic auth user if it is enabled."""
62
94
  basic_auth = os.environ.get(constants.SKYPILOT_INITIAL_BASIC_AUTH)
@@ -72,9 +104,9 @@ class PermissionService:
72
104
  return
73
105
  global_user_state.add_or_update_user(
74
106
  models.User(id=user_hash, name=username, password=password))
75
- self.enforcer.add_grouping_policy(user_hash,
76
- rbac.RoleName.ADMIN.value)
77
- self.enforcer.save_policy()
107
+ enforcer = self._ensure_enforcer()
108
+ enforcer.add_grouping_policy(user_hash, rbac.RoleName.ADMIN.value)
109
+ enforcer.save_policy()
78
110
  logger.info(f'Basic auth user {username} initialized')
79
111
 
80
112
  def _maybe_initialize_policies(self) -> None:
@@ -86,11 +118,15 @@ class PermissionService:
86
118
 
87
119
  # Check if policies are already initialized by looking for existing
88
120
  # permission policies in the enforcer
89
- existing_policies = self.enforcer.get_policy()
121
+ enforcer = self._ensure_enforcer()
122
+ existing_policies = enforcer.get_policy()
123
+
124
+ # Get plugin RBAC rules dynamically
125
+ plugin_rules = self._get_plugin_rbac_rules()
90
126
 
91
127
  # If we already have policies for the expected roles, skip
92
128
  # initialization
93
- role_permissions = rbac.get_role_permissions()
129
+ role_permissions = rbac.get_role_permissions(plugin_rules=plugin_rules)
94
130
  expected_policies = []
95
131
  for role, permissions in role_permissions.items():
96
132
  if permissions['permissions'] and 'blocklist' in permissions[
@@ -123,7 +159,7 @@ class PermissionService:
123
159
  logger.debug('Policies not found or incomplete, initializing...')
124
160
  # Only clear p policies (permission policies),
125
161
  # keep g policies (role policies)
126
- self.enforcer.remove_filtered_policy(0)
162
+ enforcer.remove_filtered_policy(0)
127
163
  for role, permissions in role_permissions.items():
128
164
  if permissions['permissions'] and 'blocklist' in permissions[
129
165
  'permissions']:
@@ -133,14 +169,14 @@ class PermissionService:
133
169
  method = item['method']
134
170
  logger.debug(f'Adding role policy: role={role}, '
135
171
  f'path={path}, method={method}')
136
- self.enforcer.add_policy(role, path, method)
172
+ enforcer.add_policy(role, path, method)
137
173
  policy_updated = True
138
174
 
139
175
  for workspace_name, users in workspace_policy_permissions.items():
140
176
  for user in users:
141
177
  logger.debug(f'Initializing workspace policy: user={user}, '
142
178
  f'workspace={workspace_name}')
143
- self.enforcer.add_policy(user, workspace_name, '*')
179
+ enforcer.add_policy(user, workspace_name, '*')
144
180
  policy_updated = True
145
181
  logger.debug('Policies initialized successfully')
146
182
  else:
@@ -153,7 +189,7 @@ class PermissionService:
153
189
  policy_updated = policy_updated or user_added
154
190
 
155
191
  if policy_updated:
156
- self.enforcer.save_policy()
192
+ enforcer.save_policy()
157
193
 
158
194
  def add_user_if_not_exists(self, user_id: str) -> None:
159
195
  """Add user role relationship."""
@@ -167,34 +203,35 @@ class PermissionService:
167
203
  Returns:
168
204
  True if the user was added, False otherwise.
169
205
  """
170
- user_roles = self.enforcer.get_roles_for_user(user_id)
206
+ enforcer = self._ensure_enforcer()
207
+ user_roles = enforcer.get_roles_for_user(user_id)
171
208
  if not user_roles:
172
- self.enforcer.add_grouping_policy(user_id, rbac.get_default_role())
209
+ enforcer.add_grouping_policy(user_id, rbac.get_default_role())
173
210
  return True
174
211
  return False
175
212
 
176
213
  def delete_user(self, user_id: str) -> None:
177
214
  """Delete user role relationship."""
178
- self._lazy_initialize()
179
215
  with _policy_lock():
180
216
  # Get current roles
181
217
  self._load_policy_no_lock()
182
218
  # Avoid calling get_user_roles, as it will require the lock.
183
- current_roles = self.enforcer.get_roles_for_user(user_id)
219
+ enforcer = self._ensure_enforcer()
220
+ current_roles = enforcer.get_roles_for_user(user_id)
184
221
  if not current_roles:
185
222
  logger.debug(f'User {user_id} has no roles')
186
223
  return
187
- self.enforcer.remove_grouping_policy(user_id, current_roles[0])
188
- self.enforcer.save_policy()
224
+ enforcer.remove_grouping_policy(user_id, current_roles[0])
225
+ enforcer.save_policy()
189
226
 
190
227
  def update_role(self, user_id: str, new_role: str) -> None:
191
228
  """Update user role relationship."""
192
- self._lazy_initialize()
193
229
  with _policy_lock():
194
230
  # Get current roles
195
231
  self._load_policy_no_lock()
196
232
  # Avoid calling get_user_roles, as it will require the lock.
197
- current_roles = self.enforcer.get_roles_for_user(user_id)
233
+ enforcer = self._ensure_enforcer()
234
+ current_roles = enforcer.get_roles_for_user(user_id)
198
235
  if not current_roles:
199
236
  logger.debug(f'User {user_id} has no roles')
200
237
  else:
@@ -203,11 +240,11 @@ class PermissionService:
203
240
  if current_role == new_role:
204
241
  logger.debug(f'User {user_id} already has role {new_role}')
205
242
  return
206
- self.enforcer.remove_grouping_policy(user_id, current_role)
243
+ enforcer.remove_grouping_policy(user_id, current_role)
207
244
 
208
245
  # Update user role
209
- self.enforcer.add_grouping_policy(user_id, new_role)
210
- self.enforcer.save_policy()
246
+ enforcer.add_grouping_policy(user_id, new_role)
247
+ enforcer.save_policy()
211
248
 
212
249
  def get_user_roles(self, user_id: str) -> List[str]:
213
250
  """Get all roles for a user.
@@ -222,15 +259,15 @@ class PermissionService:
222
259
  Returns:
223
260
  A list of role names that the user has.
224
261
  """
225
- self._lazy_initialize()
226
262
  self._load_policy_no_lock()
227
- return self.enforcer.get_roles_for_user(user_id)
263
+ enforcer = self._ensure_enforcer()
264
+ return enforcer.get_roles_for_user(user_id)
228
265
 
229
266
  def get_users_for_role(self, role: str) -> List[str]:
230
267
  """Get all users for a role."""
231
- self._lazy_initialize()
232
268
  self._load_policy_no_lock()
233
- return self.enforcer.get_users_for_role(role)
269
+ enforcer = self._ensure_enforcer()
270
+ return enforcer.get_users_for_role(role)
234
271
 
235
272
  def check_endpoint_permission(self, user_id: str, path: str,
236
273
  method: str) -> bool:
@@ -241,19 +278,22 @@ class PermissionService:
241
278
  # it is a hot path in every request. It is ok to have a stale policy,
242
279
  # as long as it is eventually consistent.
243
280
  # self._load_policy_no_lock()
244
- self._lazy_initialize()
245
- return self.enforcer.enforce(user_id, path, method)
281
+ enforcer = self._ensure_enforcer()
282
+ return enforcer.enforce(user_id, path, method)
246
283
 
247
284
  def _load_policy_no_lock(self):
248
285
  """Load policy from storage."""
249
- self.enforcer.load_policy()
286
+ enforcer = self._ensure_enforcer()
287
+ enforcer.load_policy()
250
288
 
251
289
  def load_policy(self):
252
290
  """Load policy from storage with lock."""
253
- self._lazy_initialize()
254
291
  with _policy_lock():
255
292
  self._load_policy_no_lock()
256
293
 
294
+ # Right now, not a lot of users are using multiple workspaces,
295
+ # so 5 should be more than enough.
296
+ @annotations.lru_cache(scope='request', maxsize=5)
257
297
  def check_workspace_permission(self, user_id: str,
258
298
  workspace_name: str) -> bool:
259
299
  """Check workspace permission.
@@ -266,7 +306,6 @@ class PermissionService:
266
306
  For public workspaces, the permission is granted via a wildcard policy
267
307
  ('*').
268
308
  """
269
- self._lazy_initialize()
270
309
  if os.getenv(constants.ENV_VAR_IS_SKYPILOT_SERVER) is None:
271
310
  # When it is not on API server, we allow all users to access all
272
311
  # workspaces, as the workspace check has been done on API server.
@@ -279,7 +318,8 @@ class PermissionService:
279
318
  # r.act == p.act
280
319
  # This means if there's a policy ('*', workspace_name, '*'), it will
281
320
  # match any user
282
- result = self.enforcer.enforce(user_id, workspace_name, '*')
321
+ enforcer = self._ensure_enforcer()
322
+ result = enforcer.enforce(user_id, workspace_name, '*')
283
323
  logger.debug(f'Workspace permission check: user={user_id}, '
284
324
  f'workspace={workspace_name}, result={result}')
285
325
  return result
@@ -323,13 +363,13 @@ class PermissionService:
323
363
  For public workspaces, this should be ['*'].
324
364
  For private workspaces, this should be specific user IDs.
325
365
  """
326
- self._lazy_initialize()
327
366
  with _policy_lock():
367
+ enforcer = self._ensure_enforcer()
328
368
  for user in users:
329
369
  logger.debug(f'Adding workspace policy: user={user}, '
330
370
  f'workspace={workspace_name}')
331
- self.enforcer.add_policy(user, workspace_name, '*')
332
- self.enforcer.save_policy()
371
+ enforcer.add_policy(user, workspace_name, '*')
372
+ enforcer.save_policy()
333
373
 
334
374
  def update_workspace_policy(self, workspace_name: str,
335
375
  users: List[str]) -> None:
@@ -341,24 +381,24 @@ class PermissionService:
341
381
  For public workspaces, this should be ['*'].
342
382
  For private workspaces, this should be specific user IDs.
343
383
  """
344
- self._lazy_initialize()
345
384
  with _policy_lock():
346
385
  self._load_policy_no_lock()
386
+ enforcer = self._ensure_enforcer()
347
387
  # Remove all existing policies for this workspace
348
- self.enforcer.remove_filtered_policy(1, workspace_name)
388
+ enforcer.remove_filtered_policy(1, workspace_name)
349
389
  # Add new policies
350
390
  for user in users:
351
391
  logger.debug(f'Updating workspace policy: user={user}, '
352
392
  f'workspace={workspace_name}')
353
- self.enforcer.add_policy(user, workspace_name, '*')
354
- self.enforcer.save_policy()
393
+ enforcer.add_policy(user, workspace_name, '*')
394
+ enforcer.save_policy()
355
395
 
356
396
  def remove_workspace_policy(self, workspace_name: str) -> None:
357
397
  """Remove workspace policy."""
358
- self._lazy_initialize()
359
398
  with _policy_lock():
360
- self.enforcer.remove_filtered_policy(1, workspace_name)
361
- self.enforcer.save_policy()
399
+ enforcer = self._ensure_enforcer()
400
+ enforcer.remove_filtered_policy(1, workspace_name)
401
+ enforcer.save_policy()
362
402
 
363
403
 
364
404
  @contextlib.contextmanager
sky/users/rbac.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """RBAC (Role-Based Access Control) functionality for SkyPilot API Server."""
2
2
 
3
3
  import enum
4
- from typing import Dict, List
4
+ from typing import Dict, List, Optional
5
5
 
6
6
  from sky import sky_logging
7
7
  from sky import skypilot_config
@@ -55,8 +55,13 @@ def get_default_role() -> str:
55
55
 
56
56
 
57
57
  def get_role_permissions(
58
+ plugin_rules: Optional[Dict[str, List[Dict[str, str]]]] = None
58
59
  ) -> Dict[str, Dict[str, Dict[str, List[Dict[str, str]]]]]:
59
- """Get all role permissions from config.
60
+ """Get all role permissions from config and plugins.
61
+
62
+ Args:
63
+ plugin_rules: Optional dictionary of plugin RBAC rules to merge.
64
+ Format: {'user': [{'path': '...', 'method': '...'}]}
60
65
 
61
66
  Returns:
62
67
  Dictionary containing all roles and their permissions configuration.
@@ -91,9 +96,32 @@ def get_role_permissions(
91
96
  if 'user' not in config_permissions:
92
97
  config_permissions['user'] = {
93
98
  'permissions': {
94
- 'blocklist': _DEFAULT_USER_BLOCKLIST
99
+ 'blocklist': _DEFAULT_USER_BLOCKLIST.copy()
95
100
  }
96
101
  }
102
+
103
+ # Merge plugin rules into the appropriate roles
104
+ if plugin_rules:
105
+ for role, rules in plugin_rules.items():
106
+ if role not in supported_roles:
107
+ logger.warning(f'Plugin specified invalid role: {role}')
108
+ continue
109
+ if role not in config_permissions:
110
+ config_permissions[role] = {'permissions': {'blocklist': []}}
111
+ if 'permissions' not in config_permissions[role]:
112
+ config_permissions[role]['permissions'] = {'blocklist': []}
113
+ if 'blocklist' not in config_permissions[role]['permissions']:
114
+ config_permissions[role]['permissions']['blocklist'] = []
115
+
116
+ # Merge plugin rules, avoiding duplicates
117
+ existing_rules = config_permissions[role]['permissions'][
118
+ 'blocklist']
119
+ for rule in rules:
120
+ if rule not in existing_rules:
121
+ existing_rules.append(rule)
122
+ logger.debug(f'Added plugin RBAC rule for {role}: '
123
+ f'{rule["method"]} {rule["path"]}')
124
+
97
125
  return config_permissions
98
126
 
99
127
 
@@ -3,6 +3,7 @@ import typing
3
3
  from typing import List, Optional
4
4
 
5
5
  from sky import catalog
6
+ from sky.catalog import common as catalog_common
6
7
  from sky.utils import rich_utils
7
8
  from sky.utils import ux_utils
8
9
 
@@ -34,8 +35,8 @@ if typing.TYPE_CHECKING:
34
35
 
35
36
  # Use a cached version of accelerators to cloud mapping, so that we don't have
36
37
  # to download and read the catalog file for every cloud locally.
37
- _accelerator_df = catalog.common.read_catalog('common/accelerators.csv')
38
- _memory_df = catalog.common.read_catalog('common/metadata.csv')
38
+ _accelerator_df = catalog_common.read_catalog('common/accelerators.csv')
39
+ _memory_df = catalog_common.read_catalog('common/metadata.csv')
39
40
 
40
41
  # List of non-GPU accelerators that are supported by our backend for job queue
41
42
  # scheduling.
@@ -107,10 +108,12 @@ def canonicalize_accelerator_name(accelerator: str,
107
108
  if not names and cloud_str in ['Kubernetes', None]:
108
109
  with rich_utils.safe_status(
109
110
  ux_utils.spinner_message('Listing accelerators on Kubernetes')):
111
+ # Only search for Kubernetes to reduce the lookup cost.
112
+ # For other clouds, the catalog has been searched in previous steps.
110
113
  searched = catalog.list_accelerators(
111
114
  name_filter=accelerator,
112
115
  case_sensitive=False,
113
- clouds=cloud_str,
116
+ clouds='Kubernetes',
114
117
  )
115
118
  names = list(searched.keys())
116
119
  if accelerator in names:
@@ -2,8 +2,9 @@
2
2
  import contextlib
3
3
  import copy
4
4
  import importlib
5
+ import typing
5
6
  from typing import Iterator, Optional, Tuple, Union
6
- import urllib.parse
7
+ from urllib import parse as urlparse
7
8
 
8
9
  import colorama
9
10
 
@@ -13,17 +14,21 @@ from sky import exceptions
13
14
  from sky import sky_logging
14
15
  from sky import skypilot_config
15
16
  from sky import task as task_lib
17
+ from sky.server.requests import request_names
16
18
  from sky.utils import common_utils
17
19
  from sky.utils import config_utils
18
20
  from sky.utils import ux_utils
19
21
 
20
22
  logger = sky_logging.init_logger(__name__)
21
23
 
24
+ if typing.TYPE_CHECKING:
25
+ from sky import models
26
+
22
27
 
23
28
  def _is_url(policy_string: str) -> bool:
24
29
  """Check if the policy string is a URL."""
25
30
  try:
26
- parsed = urllib.parse.urlparse(policy_string)
31
+ parsed = urlparse.urlparse(policy_string)
27
32
  return parsed.scheme in ('http', 'https')
28
33
  except Exception: # pylint: disable=broad-except
29
34
  return False
@@ -73,6 +78,7 @@ def _get_policy_impl(
73
78
  @contextlib.contextmanager
74
79
  def apply_and_use_config_in_current_request(
75
80
  entrypoint: Union['dag_lib.Dag', 'task_lib.Task'],
81
+ request_name: request_names.AdminPolicyRequestName,
76
82
  request_options: Optional[admin_policy.RequestOptions] = None,
77
83
  at_client_side: bool = False,
78
84
  ) -> Iterator['dag_lib.Dag']:
@@ -86,7 +92,8 @@ def apply_and_use_config_in_current_request(
86
92
  Refer to `apply()` for more details.
87
93
  """
88
94
  original_config = skypilot_config.to_dict()
89
- dag, mutated_config = apply(entrypoint, request_options, at_client_side)
95
+ dag, mutated_config = apply(entrypoint, request_name, request_options,
96
+ at_client_side)
90
97
  if mutated_config != original_config:
91
98
  with skypilot_config.replace_skypilot_config(mutated_config):
92
99
  yield dag
@@ -96,6 +103,7 @@ def apply_and_use_config_in_current_request(
96
103
 
97
104
  def apply(
98
105
  entrypoint: Union['dag_lib.Dag', 'task_lib.Task'],
106
+ request_name: request_names.AdminPolicyRequestName,
99
107
  request_options: Optional[admin_policy.RequestOptions] = None,
100
108
  at_client_side: bool = False,
101
109
  ) -> Tuple['dag_lib.Dag', config_utils.Config]:
@@ -126,9 +134,13 @@ def apply(
126
134
  if policy is None:
127
135
  return dag, skypilot_config.to_dict()
128
136
 
137
+ user = None
129
138
  if at_client_side:
130
139
  logger.info(f'Applying client admin policy: {policy}')
131
140
  else:
141
+ # When being called by the server, the middleware has set the
142
+ # current user and this information is available at this point.
143
+ user = common_utils.get_current_user()
132
144
  logger.info(f'Applying server admin policy: {policy}')
133
145
  config = copy.deepcopy(skypilot_config.to_dict())
134
146
  mutated_dag = dag_lib.Dag()
@@ -136,8 +148,9 @@ def apply(
136
148
 
137
149
  mutated_config = None
138
150
  for task in dag.tasks:
139
- user_request = admin_policy.UserRequest(task, config, request_options,
140
- at_client_side)
151
+ user_request = admin_policy.UserRequest(task, config, request_name,
152
+ request_options, at_client_side,
153
+ user)
141
154
  try:
142
155
  mutated_user_request = policy.apply(user_request)
143
156
  # Avoid duplicate exception wrapping.