skypilot-nightly 1.0.0.dev20250509__py3-none-any.whl → 1.0.0.dev20251107__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (512) hide show
  1. sky/__init__.py +22 -6
  2. sky/adaptors/aws.py +25 -7
  3. sky/adaptors/common.py +24 -1
  4. sky/adaptors/coreweave.py +278 -0
  5. sky/adaptors/do.py +8 -2
  6. sky/adaptors/hyperbolic.py +8 -0
  7. sky/adaptors/kubernetes.py +149 -18
  8. sky/adaptors/nebius.py +170 -17
  9. sky/adaptors/primeintellect.py +1 -0
  10. sky/adaptors/runpod.py +68 -0
  11. sky/adaptors/seeweb.py +167 -0
  12. sky/adaptors/shadeform.py +89 -0
  13. sky/admin_policy.py +187 -4
  14. sky/authentication.py +179 -225
  15. sky/backends/__init__.py +4 -2
  16. sky/backends/backend.py +22 -9
  17. sky/backends/backend_utils.py +1299 -380
  18. sky/backends/cloud_vm_ray_backend.py +1715 -518
  19. sky/backends/docker_utils.py +1 -1
  20. sky/backends/local_docker_backend.py +11 -6
  21. sky/backends/wheel_utils.py +37 -9
  22. sky/{clouds/service_catalog → catalog}/__init__.py +21 -19
  23. sky/{clouds/service_catalog → catalog}/aws_catalog.py +27 -8
  24. sky/{clouds/service_catalog → catalog}/azure_catalog.py +10 -7
  25. sky/{clouds/service_catalog → catalog}/common.py +89 -48
  26. sky/{clouds/service_catalog → catalog}/cudo_catalog.py +8 -5
  27. sky/{clouds/service_catalog → catalog}/data_fetchers/analyze.py +1 -1
  28. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_aws.py +30 -40
  29. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_cudo.py +38 -38
  30. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_gcp.py +42 -15
  31. sky/catalog/data_fetchers/fetch_hyperbolic.py +136 -0
  32. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_lambda_cloud.py +1 -0
  33. sky/catalog/data_fetchers/fetch_nebius.py +335 -0
  34. sky/catalog/data_fetchers/fetch_runpod.py +698 -0
  35. sky/catalog/data_fetchers/fetch_seeweb.py +329 -0
  36. sky/catalog/data_fetchers/fetch_shadeform.py +142 -0
  37. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vast.py +1 -1
  38. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vsphere.py +1 -1
  39. sky/{clouds/service_catalog → catalog}/do_catalog.py +5 -2
  40. sky/{clouds/service_catalog → catalog}/fluidstack_catalog.py +6 -3
  41. sky/{clouds/service_catalog → catalog}/gcp_catalog.py +41 -15
  42. sky/catalog/hyperbolic_catalog.py +136 -0
  43. sky/{clouds/service_catalog → catalog}/ibm_catalog.py +9 -6
  44. sky/{clouds/service_catalog → catalog}/kubernetes_catalog.py +36 -24
  45. sky/{clouds/service_catalog → catalog}/lambda_catalog.py +9 -6
  46. sky/{clouds/service_catalog → catalog}/nebius_catalog.py +9 -7
  47. sky/{clouds/service_catalog → catalog}/oci_catalog.py +9 -6
  48. sky/{clouds/service_catalog → catalog}/paperspace_catalog.py +5 -2
  49. sky/catalog/primeintellect_catalog.py +95 -0
  50. sky/{clouds/service_catalog → catalog}/runpod_catalog.py +11 -4
  51. sky/{clouds/service_catalog → catalog}/scp_catalog.py +9 -6
  52. sky/catalog/seeweb_catalog.py +184 -0
  53. sky/catalog/shadeform_catalog.py +165 -0
  54. sky/catalog/ssh_catalog.py +167 -0
  55. sky/{clouds/service_catalog → catalog}/vast_catalog.py +6 -3
  56. sky/{clouds/service_catalog → catalog}/vsphere_catalog.py +5 -2
  57. sky/check.py +491 -203
  58. sky/cli.py +5 -6005
  59. sky/client/{cli.py → cli/command.py} +2477 -1885
  60. sky/client/cli/deprecation_utils.py +99 -0
  61. sky/client/cli/flags.py +359 -0
  62. sky/client/cli/table_utils.py +320 -0
  63. sky/client/common.py +70 -32
  64. sky/client/oauth.py +82 -0
  65. sky/client/sdk.py +1203 -297
  66. sky/client/sdk_async.py +833 -0
  67. sky/client/service_account_auth.py +47 -0
  68. sky/cloud_stores.py +73 -0
  69. sky/clouds/__init__.py +13 -0
  70. sky/clouds/aws.py +358 -93
  71. sky/clouds/azure.py +105 -83
  72. sky/clouds/cloud.py +127 -36
  73. sky/clouds/cudo.py +68 -50
  74. sky/clouds/do.py +66 -48
  75. sky/clouds/fluidstack.py +63 -44
  76. sky/clouds/gcp.py +339 -110
  77. sky/clouds/hyperbolic.py +293 -0
  78. sky/clouds/ibm.py +70 -49
  79. sky/clouds/kubernetes.py +563 -162
  80. sky/clouds/lambda_cloud.py +74 -54
  81. sky/clouds/nebius.py +206 -80
  82. sky/clouds/oci.py +88 -66
  83. sky/clouds/paperspace.py +61 -44
  84. sky/clouds/primeintellect.py +317 -0
  85. sky/clouds/runpod.py +164 -74
  86. sky/clouds/scp.py +89 -83
  87. sky/clouds/seeweb.py +466 -0
  88. sky/clouds/shadeform.py +400 -0
  89. sky/clouds/ssh.py +263 -0
  90. sky/clouds/utils/aws_utils.py +10 -4
  91. sky/clouds/utils/gcp_utils.py +87 -11
  92. sky/clouds/utils/oci_utils.py +38 -14
  93. sky/clouds/utils/scp_utils.py +177 -124
  94. sky/clouds/vast.py +99 -77
  95. sky/clouds/vsphere.py +51 -40
  96. sky/core.py +349 -139
  97. sky/dag.py +15 -0
  98. sky/dashboard/out/404.html +1 -1
  99. sky/dashboard/out/_next/static/chunks/1141-e6aa9ab418717c59.js +11 -0
  100. sky/dashboard/out/_next/static/chunks/1272-1ef0bf0237faccdb.js +1 -0
  101. sky/dashboard/out/_next/static/chunks/1871-74503c8e80fd253b.js +6 -0
  102. sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
  103. sky/dashboard/out/_next/static/chunks/2350.fab69e61bac57b23.js +1 -0
  104. sky/dashboard/out/_next/static/chunks/2369.fc20f0c2c8ed9fe7.js +15 -0
  105. sky/dashboard/out/_next/static/chunks/2755.fff53c4a3fcae910.js +26 -0
  106. sky/dashboard/out/_next/static/chunks/3294.72362fa129305b19.js +1 -0
  107. sky/dashboard/out/_next/static/chunks/3785.ad6adaa2a0fa9768.js +1 -0
  108. sky/dashboard/out/_next/static/chunks/3850-ff4a9a69d978632b.js +1 -0
  109. sky/dashboard/out/_next/static/chunks/3937.210053269f121201.js +1 -0
  110. sky/dashboard/out/_next/static/chunks/4725.a830b5c9e7867c92.js +1 -0
  111. sky/dashboard/out/_next/static/chunks/4937.a2baa2df5572a276.js +15 -0
  112. sky/dashboard/out/_next/static/chunks/5739-d67458fcb1386c92.js +8 -0
  113. sky/dashboard/out/_next/static/chunks/6130-2be46d70a38f1e82.js +1 -0
  114. sky/dashboard/out/_next/static/chunks/616-3d59f75e2ccf9321.js +39 -0
  115. sky/dashboard/out/_next/static/chunks/6212-7bd06f60ba693125.js +13 -0
  116. sky/dashboard/out/_next/static/chunks/6601-06114c982db410b6.js +1 -0
  117. sky/dashboard/out/_next/static/chunks/6856-ef8ba11f96d8c4a3.js +1 -0
  118. sky/dashboard/out/_next/static/chunks/6989-01359c57e018caa4.js +1 -0
  119. sky/dashboard/out/_next/static/chunks/6990-32b6e2d3822301fa.js +1 -0
  120. sky/dashboard/out/_next/static/chunks/7359-c8d04e06886000b3.js +30 -0
  121. sky/dashboard/out/_next/static/chunks/7411-b15471acd2cba716.js +41 -0
  122. sky/dashboard/out/_next/static/chunks/7615-3301e838e5f25772.js +1 -0
  123. sky/dashboard/out/_next/static/chunks/8640.5b9475a2d18c5416.js +16 -0
  124. sky/dashboard/out/_next/static/chunks/8969-1e4613c651bf4051.js +1 -0
  125. sky/dashboard/out/_next/static/chunks/9025.fa408f3242e9028d.js +6 -0
  126. sky/dashboard/out/_next/static/chunks/9353-cff34f7e773b2e2b.js +1 -0
  127. sky/dashboard/out/_next/static/chunks/9360.7310982cf5a0dc79.js +31 -0
  128. sky/dashboard/out/_next/static/chunks/9847.3aaca6bb33455140.js +30 -0
  129. sky/dashboard/out/_next/static/chunks/fd9d1056-86323a29a8f7e46a.js +1 -0
  130. sky/dashboard/out/_next/static/chunks/framework-cf60a09ccd051a10.js +33 -0
  131. sky/dashboard/out/_next/static/chunks/main-app-587214043926b3cc.js +1 -0
  132. sky/dashboard/out/_next/static/chunks/main-f15ccb73239a3bf1.js +1 -0
  133. sky/dashboard/out/_next/static/chunks/pages/_app-bde01e4a2beec258.js +34 -0
  134. sky/dashboard/out/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js +1 -0
  135. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-c736ead69c2d86ec.js +16 -0
  136. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-a37d2063af475a1c.js +1 -0
  137. sky/dashboard/out/_next/static/chunks/pages/clusters-d44859594e6f8064.js +1 -0
  138. sky/dashboard/out/_next/static/chunks/pages/config-dfb9bf07b13045f4.js +1 -0
  139. sky/dashboard/out/_next/static/chunks/pages/index-444f1804401f04ea.js +1 -0
  140. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-c0b5935149902e6f.js +1 -0
  141. sky/dashboard/out/_next/static/chunks/pages/infra-aed0ea19df7cf961.js +1 -0
  142. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-5796e8d6aea291a0.js +16 -0
  143. sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-6edeb7d06032adfc.js +21 -0
  144. sky/dashboard/out/_next/static/chunks/pages/jobs-479dde13399cf270.js +1 -0
  145. sky/dashboard/out/_next/static/chunks/pages/users-5ab3b907622cf0fe.js +1 -0
  146. sky/dashboard/out/_next/static/chunks/pages/volumes-b84b948ff357c43e.js +1 -0
  147. sky/dashboard/out/_next/static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js +1 -0
  148. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-c5a3eeee1c218af1.js +1 -0
  149. sky/dashboard/out/_next/static/chunks/pages/workspaces-22b23febb3e89ce1.js +1 -0
  150. sky/dashboard/out/_next/static/chunks/webpack-2679be77fc08a2f8.js +1 -0
  151. sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
  152. sky/dashboard/out/_next/static/zB0ed6ge_W1MDszVHhijS/_buildManifest.js +1 -0
  153. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  154. sky/dashboard/out/clusters/[cluster].html +1 -1
  155. sky/dashboard/out/clusters.html +1 -1
  156. sky/dashboard/out/config.html +1 -0
  157. sky/dashboard/out/index.html +1 -1
  158. sky/dashboard/out/infra/[context].html +1 -0
  159. sky/dashboard/out/infra.html +1 -0
  160. sky/dashboard/out/jobs/[job].html +1 -1
  161. sky/dashboard/out/jobs/pools/[pool].html +1 -0
  162. sky/dashboard/out/jobs.html +1 -1
  163. sky/dashboard/out/users.html +1 -0
  164. sky/dashboard/out/volumes.html +1 -0
  165. sky/dashboard/out/workspace/new.html +1 -0
  166. sky/dashboard/out/workspaces/[name].html +1 -0
  167. sky/dashboard/out/workspaces.html +1 -0
  168. sky/data/data_utils.py +137 -1
  169. sky/data/mounting_utils.py +269 -84
  170. sky/data/storage.py +1451 -1807
  171. sky/data/storage_utils.py +43 -57
  172. sky/exceptions.py +132 -2
  173. sky/execution.py +206 -63
  174. sky/global_user_state.py +2374 -586
  175. sky/jobs/__init__.py +5 -0
  176. sky/jobs/client/sdk.py +242 -65
  177. sky/jobs/client/sdk_async.py +143 -0
  178. sky/jobs/constants.py +9 -8
  179. sky/jobs/controller.py +839 -277
  180. sky/jobs/file_content_utils.py +80 -0
  181. sky/jobs/log_gc.py +201 -0
  182. sky/jobs/recovery_strategy.py +398 -152
  183. sky/jobs/scheduler.py +315 -189
  184. sky/jobs/server/core.py +829 -255
  185. sky/jobs/server/server.py +156 -115
  186. sky/jobs/server/utils.py +136 -0
  187. sky/jobs/state.py +2092 -701
  188. sky/jobs/utils.py +1242 -160
  189. sky/logs/__init__.py +21 -0
  190. sky/logs/agent.py +108 -0
  191. sky/logs/aws.py +243 -0
  192. sky/logs/gcp.py +91 -0
  193. sky/metrics/__init__.py +0 -0
  194. sky/metrics/utils.py +443 -0
  195. sky/models.py +78 -1
  196. sky/optimizer.py +164 -70
  197. sky/provision/__init__.py +90 -4
  198. sky/provision/aws/config.py +147 -26
  199. sky/provision/aws/instance.py +135 -50
  200. sky/provision/azure/instance.py +10 -5
  201. sky/provision/common.py +13 -1
  202. sky/provision/cudo/cudo_machine_type.py +1 -1
  203. sky/provision/cudo/cudo_utils.py +14 -8
  204. sky/provision/cudo/cudo_wrapper.py +72 -71
  205. sky/provision/cudo/instance.py +10 -6
  206. sky/provision/do/instance.py +10 -6
  207. sky/provision/do/utils.py +4 -3
  208. sky/provision/docker_utils.py +114 -23
  209. sky/provision/fluidstack/instance.py +13 -8
  210. sky/provision/gcp/__init__.py +1 -0
  211. sky/provision/gcp/config.py +301 -19
  212. sky/provision/gcp/constants.py +218 -0
  213. sky/provision/gcp/instance.py +36 -8
  214. sky/provision/gcp/instance_utils.py +18 -4
  215. sky/provision/gcp/volume_utils.py +247 -0
  216. sky/provision/hyperbolic/__init__.py +12 -0
  217. sky/provision/hyperbolic/config.py +10 -0
  218. sky/provision/hyperbolic/instance.py +437 -0
  219. sky/provision/hyperbolic/utils.py +373 -0
  220. sky/provision/instance_setup.py +93 -14
  221. sky/provision/kubernetes/__init__.py +5 -0
  222. sky/provision/kubernetes/config.py +9 -52
  223. sky/provision/kubernetes/constants.py +17 -0
  224. sky/provision/kubernetes/instance.py +789 -247
  225. sky/provision/kubernetes/manifests/fusermount-server-daemonset.yaml +1 -2
  226. sky/provision/kubernetes/network.py +27 -17
  227. sky/provision/kubernetes/network_utils.py +40 -43
  228. sky/provision/kubernetes/utils.py +1192 -531
  229. sky/provision/kubernetes/volume.py +282 -0
  230. sky/provision/lambda_cloud/instance.py +22 -16
  231. sky/provision/nebius/constants.py +50 -0
  232. sky/provision/nebius/instance.py +19 -6
  233. sky/provision/nebius/utils.py +196 -91
  234. sky/provision/oci/instance.py +10 -5
  235. sky/provision/paperspace/instance.py +10 -7
  236. sky/provision/paperspace/utils.py +1 -1
  237. sky/provision/primeintellect/__init__.py +10 -0
  238. sky/provision/primeintellect/config.py +11 -0
  239. sky/provision/primeintellect/instance.py +454 -0
  240. sky/provision/primeintellect/utils.py +398 -0
  241. sky/provision/provisioner.py +110 -36
  242. sky/provision/runpod/__init__.py +5 -0
  243. sky/provision/runpod/instance.py +27 -6
  244. sky/provision/runpod/utils.py +51 -18
  245. sky/provision/runpod/volume.py +180 -0
  246. sky/provision/scp/__init__.py +15 -0
  247. sky/provision/scp/config.py +93 -0
  248. sky/provision/scp/instance.py +531 -0
  249. sky/provision/seeweb/__init__.py +11 -0
  250. sky/provision/seeweb/config.py +13 -0
  251. sky/provision/seeweb/instance.py +807 -0
  252. sky/provision/shadeform/__init__.py +11 -0
  253. sky/provision/shadeform/config.py +12 -0
  254. sky/provision/shadeform/instance.py +351 -0
  255. sky/provision/shadeform/shadeform_utils.py +83 -0
  256. sky/provision/ssh/__init__.py +18 -0
  257. sky/provision/vast/instance.py +13 -8
  258. sky/provision/vast/utils.py +10 -7
  259. sky/provision/vsphere/common/vim_utils.py +1 -2
  260. sky/provision/vsphere/instance.py +15 -10
  261. sky/provision/vsphere/vsphere_utils.py +9 -19
  262. sky/py.typed +0 -0
  263. sky/resources.py +844 -118
  264. sky/schemas/__init__.py +0 -0
  265. sky/schemas/api/__init__.py +0 -0
  266. sky/schemas/api/responses.py +225 -0
  267. sky/schemas/db/README +4 -0
  268. sky/schemas/db/env.py +90 -0
  269. sky/schemas/db/global_user_state/001_initial_schema.py +124 -0
  270. sky/schemas/db/global_user_state/002_add_workspace_to_cluster_history.py +35 -0
  271. sky/schemas/db/global_user_state/003_fix_initial_revision.py +61 -0
  272. sky/schemas/db/global_user_state/004_is_managed.py +34 -0
  273. sky/schemas/db/global_user_state/005_cluster_event.py +32 -0
  274. sky/schemas/db/global_user_state/006_provision_log.py +41 -0
  275. sky/schemas/db/global_user_state/007_cluster_event_request_id.py +34 -0
  276. sky/schemas/db/global_user_state/008_skylet_ssh_tunnel_metadata.py +34 -0
  277. sky/schemas/db/global_user_state/009_last_activity_and_launched_at.py +89 -0
  278. sky/schemas/db/global_user_state/010_save_ssh_key.py +66 -0
  279. sky/schemas/db/script.py.mako +28 -0
  280. sky/schemas/db/serve_state/001_initial_schema.py +67 -0
  281. sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
  282. sky/schemas/db/spot_jobs/001_initial_schema.py +97 -0
  283. sky/schemas/db/spot_jobs/002_cluster_pool.py +42 -0
  284. sky/schemas/db/spot_jobs/003_pool_hash.py +34 -0
  285. sky/schemas/db/spot_jobs/004_job_file_contents.py +42 -0
  286. sky/schemas/db/spot_jobs/005_logs_gc.py +38 -0
  287. sky/schemas/generated/__init__.py +0 -0
  288. sky/schemas/generated/autostopv1_pb2.py +36 -0
  289. sky/schemas/generated/autostopv1_pb2.pyi +43 -0
  290. sky/schemas/generated/autostopv1_pb2_grpc.py +146 -0
  291. sky/schemas/generated/jobsv1_pb2.py +86 -0
  292. sky/schemas/generated/jobsv1_pb2.pyi +254 -0
  293. sky/schemas/generated/jobsv1_pb2_grpc.py +542 -0
  294. sky/schemas/generated/managed_jobsv1_pb2.py +74 -0
  295. sky/schemas/generated/managed_jobsv1_pb2.pyi +278 -0
  296. sky/schemas/generated/managed_jobsv1_pb2_grpc.py +278 -0
  297. sky/schemas/generated/servev1_pb2.py +58 -0
  298. sky/schemas/generated/servev1_pb2.pyi +115 -0
  299. sky/schemas/generated/servev1_pb2_grpc.py +322 -0
  300. sky/serve/autoscalers.py +357 -5
  301. sky/serve/client/impl.py +310 -0
  302. sky/serve/client/sdk.py +47 -139
  303. sky/serve/client/sdk_async.py +130 -0
  304. sky/serve/constants.py +10 -8
  305. sky/serve/controller.py +64 -19
  306. sky/serve/load_balancer.py +106 -60
  307. sky/serve/load_balancing_policies.py +115 -1
  308. sky/serve/replica_managers.py +273 -162
  309. sky/serve/serve_rpc_utils.py +179 -0
  310. sky/serve/serve_state.py +554 -251
  311. sky/serve/serve_utils.py +733 -220
  312. sky/serve/server/core.py +66 -711
  313. sky/serve/server/impl.py +1093 -0
  314. sky/serve/server/server.py +21 -18
  315. sky/serve/service.py +133 -48
  316. sky/serve/service_spec.py +135 -16
  317. sky/serve/spot_placer.py +3 -0
  318. sky/server/auth/__init__.py +0 -0
  319. sky/server/auth/authn.py +50 -0
  320. sky/server/auth/loopback.py +38 -0
  321. sky/server/auth/oauth2_proxy.py +200 -0
  322. sky/server/common.py +475 -181
  323. sky/server/config.py +81 -23
  324. sky/server/constants.py +44 -6
  325. sky/server/daemons.py +229 -0
  326. sky/server/html/token_page.html +185 -0
  327. sky/server/metrics.py +160 -0
  328. sky/server/requests/executor.py +528 -138
  329. sky/server/requests/payloads.py +351 -17
  330. sky/server/requests/preconditions.py +21 -17
  331. sky/server/requests/process.py +112 -29
  332. sky/server/requests/request_names.py +120 -0
  333. sky/server/requests/requests.py +817 -224
  334. sky/server/requests/serializers/decoders.py +82 -31
  335. sky/server/requests/serializers/encoders.py +140 -22
  336. sky/server/requests/threads.py +106 -0
  337. sky/server/rest.py +417 -0
  338. sky/server/server.py +1290 -284
  339. sky/server/state.py +20 -0
  340. sky/server/stream_utils.py +345 -57
  341. sky/server/uvicorn.py +217 -3
  342. sky/server/versions.py +270 -0
  343. sky/setup_files/MANIFEST.in +5 -0
  344. sky/setup_files/alembic.ini +156 -0
  345. sky/setup_files/dependencies.py +136 -31
  346. sky/setup_files/setup.py +44 -42
  347. sky/sky_logging.py +102 -5
  348. sky/skylet/attempt_skylet.py +1 -0
  349. sky/skylet/autostop_lib.py +129 -8
  350. sky/skylet/configs.py +27 -20
  351. sky/skylet/constants.py +171 -19
  352. sky/skylet/events.py +105 -21
  353. sky/skylet/job_lib.py +335 -104
  354. sky/skylet/log_lib.py +297 -18
  355. sky/skylet/log_lib.pyi +44 -1
  356. sky/skylet/ray_patches/__init__.py +17 -3
  357. sky/skylet/ray_patches/autoscaler.py.diff +18 -0
  358. sky/skylet/ray_patches/cli.py.diff +19 -0
  359. sky/skylet/ray_patches/command_runner.py.diff +17 -0
  360. sky/skylet/ray_patches/log_monitor.py.diff +20 -0
  361. sky/skylet/ray_patches/resource_demand_scheduler.py.diff +32 -0
  362. sky/skylet/ray_patches/updater.py.diff +18 -0
  363. sky/skylet/ray_patches/worker.py.diff +41 -0
  364. sky/skylet/services.py +564 -0
  365. sky/skylet/skylet.py +63 -4
  366. sky/skylet/subprocess_daemon.py +103 -29
  367. sky/skypilot_config.py +506 -99
  368. sky/ssh_node_pools/__init__.py +1 -0
  369. sky/ssh_node_pools/core.py +135 -0
  370. sky/ssh_node_pools/server.py +233 -0
  371. sky/task.py +621 -137
  372. sky/templates/aws-ray.yml.j2 +10 -3
  373. sky/templates/azure-ray.yml.j2 +1 -1
  374. sky/templates/do-ray.yml.j2 +1 -1
  375. sky/templates/gcp-ray.yml.j2 +57 -0
  376. sky/templates/hyperbolic-ray.yml.j2 +67 -0
  377. sky/templates/jobs-controller.yaml.j2 +27 -24
  378. sky/templates/kubernetes-loadbalancer.yml.j2 +2 -0
  379. sky/templates/kubernetes-ray.yml.j2 +607 -51
  380. sky/templates/lambda-ray.yml.j2 +1 -1
  381. sky/templates/nebius-ray.yml.j2 +33 -12
  382. sky/templates/paperspace-ray.yml.j2 +1 -1
  383. sky/templates/primeintellect-ray.yml.j2 +71 -0
  384. sky/templates/runpod-ray.yml.j2 +9 -1
  385. sky/templates/scp-ray.yml.j2 +3 -50
  386. sky/templates/seeweb-ray.yml.j2 +108 -0
  387. sky/templates/shadeform-ray.yml.j2 +72 -0
  388. sky/templates/sky-serve-controller.yaml.j2 +22 -2
  389. sky/templates/websocket_proxy.py +178 -18
  390. sky/usage/usage_lib.py +18 -11
  391. sky/users/__init__.py +0 -0
  392. sky/users/model.conf +15 -0
  393. sky/users/permission.py +387 -0
  394. sky/users/rbac.py +121 -0
  395. sky/users/server.py +720 -0
  396. sky/users/token_service.py +218 -0
  397. sky/utils/accelerator_registry.py +34 -5
  398. sky/utils/admin_policy_utils.py +84 -38
  399. sky/utils/annotations.py +16 -5
  400. sky/utils/asyncio_utils.py +78 -0
  401. sky/utils/auth_utils.py +153 -0
  402. sky/utils/benchmark_utils.py +60 -0
  403. sky/utils/cli_utils/status_utils.py +159 -86
  404. sky/utils/cluster_utils.py +31 -9
  405. sky/utils/command_runner.py +354 -68
  406. sky/utils/command_runner.pyi +93 -3
  407. sky/utils/common.py +35 -8
  408. sky/utils/common_utils.py +310 -87
  409. sky/utils/config_utils.py +87 -5
  410. sky/utils/context.py +402 -0
  411. sky/utils/context_utils.py +222 -0
  412. sky/utils/controller_utils.py +264 -89
  413. sky/utils/dag_utils.py +31 -12
  414. sky/utils/db/__init__.py +0 -0
  415. sky/utils/db/db_utils.py +470 -0
  416. sky/utils/db/migration_utils.py +133 -0
  417. sky/utils/directory_utils.py +12 -0
  418. sky/utils/env_options.py +13 -0
  419. sky/utils/git.py +567 -0
  420. sky/utils/git_clone.sh +460 -0
  421. sky/utils/infra_utils.py +195 -0
  422. sky/utils/kubernetes/cleanup-tunnel.sh +62 -0
  423. sky/utils/kubernetes/config_map_utils.py +133 -0
  424. sky/utils/kubernetes/create_cluster.sh +13 -27
  425. sky/utils/kubernetes/delete_cluster.sh +10 -7
  426. sky/utils/kubernetes/deploy_remote_cluster.py +1299 -0
  427. sky/utils/kubernetes/exec_kubeconfig_converter.py +22 -31
  428. sky/utils/kubernetes/generate_kind_config.py +6 -66
  429. sky/utils/kubernetes/generate_kubeconfig.sh +4 -1
  430. sky/utils/kubernetes/gpu_labeler.py +5 -5
  431. sky/utils/kubernetes/kubernetes_deploy_utils.py +354 -47
  432. sky/utils/kubernetes/ssh-tunnel.sh +379 -0
  433. sky/utils/kubernetes/ssh_utils.py +221 -0
  434. sky/utils/kubernetes_enums.py +8 -15
  435. sky/utils/lock_events.py +94 -0
  436. sky/utils/locks.py +368 -0
  437. sky/utils/log_utils.py +300 -6
  438. sky/utils/perf_utils.py +22 -0
  439. sky/utils/resource_checker.py +298 -0
  440. sky/utils/resources_utils.py +249 -32
  441. sky/utils/rich_utils.py +213 -37
  442. sky/utils/schemas.py +905 -147
  443. sky/utils/serialize_utils.py +16 -0
  444. sky/utils/status_lib.py +10 -0
  445. sky/utils/subprocess_utils.py +38 -15
  446. sky/utils/tempstore.py +70 -0
  447. sky/utils/timeline.py +24 -52
  448. sky/utils/ux_utils.py +84 -15
  449. sky/utils/validator.py +11 -1
  450. sky/utils/volume.py +86 -0
  451. sky/utils/yaml_utils.py +111 -0
  452. sky/volumes/__init__.py +13 -0
  453. sky/volumes/client/__init__.py +0 -0
  454. sky/volumes/client/sdk.py +149 -0
  455. sky/volumes/server/__init__.py +0 -0
  456. sky/volumes/server/core.py +258 -0
  457. sky/volumes/server/server.py +122 -0
  458. sky/volumes/volume.py +212 -0
  459. sky/workspaces/__init__.py +0 -0
  460. sky/workspaces/core.py +655 -0
  461. sky/workspaces/server.py +101 -0
  462. sky/workspaces/utils.py +56 -0
  463. skypilot_nightly-1.0.0.dev20251107.dist-info/METADATA +675 -0
  464. skypilot_nightly-1.0.0.dev20251107.dist-info/RECORD +594 -0
  465. {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/WHEEL +1 -1
  466. sky/benchmark/benchmark_state.py +0 -256
  467. sky/benchmark/benchmark_utils.py +0 -641
  468. sky/clouds/service_catalog/constants.py +0 -7
  469. sky/dashboard/out/_next/static/LksQgChY5izXjokL3LcEu/_buildManifest.js +0 -1
  470. sky/dashboard/out/_next/static/chunks/236-f49500b82ad5392d.js +0 -6
  471. sky/dashboard/out/_next/static/chunks/312-c3c8845990db8ffc.js +0 -15
  472. sky/dashboard/out/_next/static/chunks/37-0a572fe0dbb89c4d.js +0 -6
  473. sky/dashboard/out/_next/static/chunks/678-206dddca808e6d16.js +0 -59
  474. sky/dashboard/out/_next/static/chunks/845-0f8017370869e269.js +0 -1
  475. sky/dashboard/out/_next/static/chunks/979-7bf73a4c7cea0f5c.js +0 -1
  476. sky/dashboard/out/_next/static/chunks/fd9d1056-2821b0f0cabcd8bd.js +0 -1
  477. sky/dashboard/out/_next/static/chunks/framework-87d061ee6ed71b28.js +0 -33
  478. sky/dashboard/out/_next/static/chunks/main-app-241eb28595532291.js +0 -1
  479. sky/dashboard/out/_next/static/chunks/main-e0e2335212e72357.js +0 -1
  480. sky/dashboard/out/_next/static/chunks/pages/_app-e6b013bc3f77ad60.js +0 -1
  481. sky/dashboard/out/_next/static/chunks/pages/_error-1be831200e60c5c0.js +0 -1
  482. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-e15db85d0ea1fbe1.js +0 -1
  483. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-f383db7389368ea7.js +0 -1
  484. sky/dashboard/out/_next/static/chunks/pages/clusters-a93b93e10b8b074e.js +0 -1
  485. sky/dashboard/out/_next/static/chunks/pages/index-f9f039532ca8cbc4.js +0 -1
  486. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-03f279c6741fb48b.js +0 -1
  487. sky/dashboard/out/_next/static/chunks/pages/jobs-a75029b67aab6a2e.js +0 -1
  488. sky/dashboard/out/_next/static/chunks/webpack-830f59b8404e96b8.js +0 -1
  489. sky/dashboard/out/_next/static/css/c6933bbb2ce7f4dd.css +0 -3
  490. sky/jobs/dashboard/dashboard.py +0 -223
  491. sky/jobs/dashboard/static/favicon.ico +0 -0
  492. sky/jobs/dashboard/templates/index.html +0 -831
  493. sky/jobs/server/dashboard_utils.py +0 -69
  494. sky/skylet/providers/scp/__init__.py +0 -2
  495. sky/skylet/providers/scp/config.py +0 -149
  496. sky/skylet/providers/scp/node_provider.py +0 -578
  497. sky/templates/kubernetes-ssh-jump.yml.j2 +0 -94
  498. sky/utils/db_utils.py +0 -100
  499. sky/utils/kubernetes/deploy_remote_cluster.sh +0 -308
  500. sky/utils/kubernetes/ssh_jump_lifecycle_manager.py +0 -191
  501. skypilot_nightly-1.0.0.dev20250509.dist-info/METADATA +0 -361
  502. skypilot_nightly-1.0.0.dev20250509.dist-info/RECORD +0 -396
  503. /sky/{clouds/service_catalog → catalog}/config.py +0 -0
  504. /sky/{benchmark → catalog/data_fetchers}/__init__.py +0 -0
  505. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_azure.py +0 -0
  506. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_fluidstack.py +0 -0
  507. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_ibm.py +0 -0
  508. /sky/{clouds/service_catalog/data_fetchers → client/cli}/__init__.py +0 -0
  509. /sky/dashboard/out/_next/static/{LksQgChY5izXjokL3LcEu → zB0ed6ge_W1MDszVHhijS}/_ssgManifest.js +0 -0
  510. {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/entry_points.txt +0 -0
  511. {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/licenses/LICENSE +0 -0
  512. {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/top_level.txt +0 -0
sky/adaptors/seeweb.py ADDED
@@ -0,0 +1,167 @@
1
+ """ Seeweb Adaptor """
2
+ import configparser
3
+ import pathlib
4
+ from typing import Optional
5
+
6
+ import pydantic
7
+ import requests # type: ignore
8
+
9
+ from sky.adaptors import common
10
+ from sky.utils import annotations
11
+
12
+
13
+ class SeewebError(Exception):
14
+ """Base exception for Seeweb adaptor errors."""
15
+
16
+
17
+ class SeewebCredentialsFileNotFound(SeewebError):
18
+ """Raised when the Seeweb credentials file is missing."""
19
+
20
+
21
+ class SeewebApiKeyMissing(SeewebError):
22
+ """Raised when the Seeweb API key is missing or empty."""
23
+
24
+
25
+ class SeewebAuthenticationError(SeewebError):
26
+ """Raised when authenticating with Seeweb API fails."""
27
+
28
+
29
+ _IMPORT_ERROR_MESSAGE = ('Failed to import dependencies for Seeweb.'
30
+ 'Try pip install "skypilot[seeweb]"')
31
+
32
+ ecsapi = common.LazyImport(
33
+ 'ecsapi',
34
+ import_error_message=_IMPORT_ERROR_MESSAGE,
35
+ )
36
+ boto3 = common.LazyImport('boto3', import_error_message=_IMPORT_ERROR_MESSAGE)
37
+ botocore = common.LazyImport('botocore',
38
+ import_error_message=_IMPORT_ERROR_MESSAGE)
39
+
40
+ _LAZY_MODULES = (ecsapi, boto3, botocore)
41
+
42
+
43
+ @common.load_lazy_modules(_LAZY_MODULES)
44
+ def check_compute_credentials() -> bool:
45
+ """Checks if the user has access credentials to Seeweb's compute service.
46
+
47
+ Returns True if credentials are valid; otherwise raises a SeewebError.
48
+ """
49
+ # Read API key from standard Seeweb configuration file
50
+ key_path = pathlib.Path('~/.seeweb_cloud/seeweb_keys').expanduser()
51
+ if not key_path.exists():
52
+ raise SeewebCredentialsFileNotFound(
53
+ 'Missing Seeweb API key file ~/.seeweb_cloud/seeweb_keys')
54
+
55
+ parser = configparser.ConfigParser()
56
+ parser.read(key_path)
57
+ try:
58
+ api_key = parser['DEFAULT']['api_key'].strip()
59
+ except KeyError as e:
60
+ raise SeewebApiKeyMissing(
61
+ 'Missing api_key in ~/.seeweb_cloud/seeweb_keys') from e
62
+ if not api_key:
63
+ raise SeewebApiKeyMissing(
64
+ 'Empty api_key in ~/.seeweb_cloud/seeweb_keys')
65
+
66
+ # Test connection by fetching servers list to validate the key
67
+ try:
68
+ seeweb_client = ecsapi.Api(token=api_key)
69
+ seeweb_client.fetch_servers()
70
+ except Exception as e: # pylint: disable=broad-except
71
+ raise SeewebAuthenticationError(
72
+ f'Unable to authenticate with Seeweb API: {e}') from e
73
+
74
+ return True
75
+
76
+
77
+ @common.load_lazy_modules(_LAZY_MODULES)
78
+ def check_storage_credentials() -> bool:
79
+ """Checks if the user has access credentials to Seeweb's storage service.
80
+
81
+ Mirrors compute credentials validation.
82
+ """
83
+ return check_compute_credentials()
84
+
85
+
86
+ @common.load_lazy_modules(_LAZY_MODULES)
87
+ @annotations.lru_cache(scope='global', maxsize=1)
88
+ def client():
89
+ """Returns an authenticated ecsapi.Api object."""
90
+ # Create authenticated client using the same credential pattern
91
+ key_path = pathlib.Path('~/.seeweb_cloud/seeweb_keys').expanduser()
92
+ if not key_path.exists():
93
+ raise SeewebCredentialsFileNotFound(
94
+ 'Missing Seeweb API key file ~/.seeweb_cloud/seeweb_keys')
95
+
96
+ parser = configparser.ConfigParser()
97
+ parser.read(key_path)
98
+ try:
99
+ api_key = parser['DEFAULT']['api_key'].strip()
100
+ except KeyError as e:
101
+ raise SeewebApiKeyMissing(
102
+ 'Missing api_key in ~/.seeweb_cloud/seeweb_keys') from e
103
+ if not api_key:
104
+ raise SeewebApiKeyMissing(
105
+ 'Empty api_key in ~/.seeweb_cloud/seeweb_keys')
106
+
107
+ api = ecsapi.Api(token=api_key)
108
+
109
+ # Monkey-patch fetch_servers to be tolerant to API schema mismatches.
110
+ orig_fetch_servers = api.fetch_servers
111
+ orig_delete_server = api.delete_server
112
+
113
+ def _tolerant_fetch_servers(
114
+ timeout: Optional[int] = None): # type: ignore[override]
115
+ try:
116
+ return orig_fetch_servers(timeout=timeout)
117
+ except pydantic.ValidationError:
118
+ # Fallback path: fetch raw JSON, drop snapshot fields, then validate
119
+ # pylint: disable=protected-access
120
+ base_url = api._Api__generate_base_url() # type: ignore
121
+ headers = api._Api__generate_authentication_headers(
122
+ ) # type: ignore
123
+ url = f'{base_url}/servers'
124
+ resp = requests.get(url, headers=headers, timeout=timeout or 15)
125
+ resp.raise_for_status()
126
+ data = resp.json()
127
+ try:
128
+ servers = data.get('server', [])
129
+ for s in servers:
130
+ s.pop('last_restored_snapshot', None)
131
+ except (KeyError, TypeError, ValueError):
132
+ pass
133
+ server_list_response_cls = ecsapi._server._ServerListResponse
134
+ servers_response = server_list_response_cls.model_validate(data)
135
+ return servers_response.server
136
+
137
+ api.fetch_servers = _tolerant_fetch_servers # type: ignore[assignment]
138
+
139
+ def _tolerant_delete_server(server_name: str,
140
+ timeout: Optional[int] = None):
141
+ try:
142
+ return orig_delete_server(server_name, timeout=timeout)
143
+ except pydantic.ValidationError:
144
+ # Fallback: perform raw DELETE and interpret not_found as success
145
+ # pylint: disable=protected-access
146
+ base_url = api._Api__generate_base_url() # type: ignore
147
+ headers = api._Api__generate_authentication_headers(
148
+ ) # type: ignore
149
+ url = f'{base_url}/servers/{server_name}'
150
+ resp = requests.delete(url, headers=headers, timeout=timeout or 15)
151
+ # Treat 404 as idempotent success
152
+ if resp.status_code == 404:
153
+ return None
154
+ # Some APIs return {status: 'not_found', message: ...}
155
+ try:
156
+ data = resp.json()
157
+ if isinstance(data, dict) and data.get('status') == 'not_found':
158
+ return None
159
+ except (ValueError, TypeError):
160
+ pass
161
+ # If not clearly not_found, re-raise original behavior
162
+ resp.raise_for_status()
163
+ # Best-effort: return None to indicate deletion requested
164
+ return None
165
+
166
+ api.delete_server = _tolerant_delete_server # type: ignore[assignment]
167
+ return api
@@ -0,0 +1,89 @@
1
+ """Shadeform cloud adaptor."""
2
+
3
+ import functools
4
+ import socket
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import requests
8
+
9
+ from sky import sky_logging
10
+ from sky.provision.shadeform import shadeform_utils
11
+ from sky.utils import common_utils
12
+
13
+ logger = sky_logging.init_logger(__name__)
14
+
15
+ _shadeform_sdk = None
16
+
17
+
18
+ def import_package(func):
19
+
20
+ @functools.wraps(func)
21
+ def wrapper(*args, **kwargs):
22
+ global _shadeform_sdk
23
+ if _shadeform_sdk is None:
24
+ try:
25
+ import shadeform as _shadeform # pylint: disable=import-outside-toplevel
26
+ _shadeform_sdk = _shadeform
27
+ except ImportError:
28
+ raise ImportError(
29
+ 'Failed to import dependencies for Shadeform. '
30
+ 'Try pip install "skypilot[shadeform]"') from None
31
+ return func(*args, **kwargs)
32
+
33
+ return wrapper
34
+
35
+
36
+ @import_package
37
+ def shadeform():
38
+ """Return the shadeform package."""
39
+ return _shadeform_sdk
40
+
41
+
42
+ def list_ssh_keys() -> List[Dict[str, Any]]:
43
+ """List all SSH keys in Shadeform account."""
44
+ try:
45
+ response = shadeform_utils.get_ssh_keys()
46
+ return response.get('ssh_keys', [])
47
+ except (ValueError, KeyError, requests.exceptions.RequestException) as e:
48
+ logger.warning(f'Failed to list SSH keys from Shadeform: {e}')
49
+ return []
50
+
51
+
52
+ def add_ssh_key_to_shadeform(public_key: str) -> Optional[str]:
53
+ """Add SSH key to Shadeform if it doesn't already exist.
54
+
55
+ Args:
56
+ public_key: The SSH public key string.
57
+
58
+ Returns:
59
+ The name of the key if added successfully, None otherwise.
60
+ """
61
+ try:
62
+ # Check if key already exists
63
+ existing_keys = list_ssh_keys()
64
+ key_exists = False
65
+ key_id = None
66
+ for key in existing_keys:
67
+ if key.get('public_key') == public_key:
68
+ key_exists = True
69
+ key_id = key.get('id')
70
+ break
71
+
72
+ if key_exists:
73
+ logger.info('SSH key already exists in Shadeform account')
74
+ return key_id
75
+
76
+ # Generate a unique key name
77
+ hostname = socket.gethostname()
78
+ key_name = f'skypilot-{hostname}-{common_utils.get_user_hash()[:8]}'
79
+
80
+ # Add the key
81
+ response = shadeform_utils.add_ssh_key(name=key_name,
82
+ public_key=public_key)
83
+ key_id = response['id']
84
+ logger.info(f'Added SSH key to Shadeform: {key_name, key_id}')
85
+ return key_id
86
+
87
+ except (ValueError, KeyError, requests.exceptions.RequestException) as e:
88
+ logger.warning(f'Failed to add SSH key to Shadeform: {e}')
89
+ return None
sky/admin_policy.py CHANGED
@@ -4,12 +4,26 @@ import dataclasses
4
4
  import typing
5
5
  from typing import Optional
6
6
 
7
+ import colorama
8
+ import pydantic
9
+
10
+ import sky
11
+ from sky import exceptions
12
+ from sky import models
13
+ from sky.adaptors import common as adaptors_common
14
+ from sky.server.requests import request_names
15
+ from sky.utils import common_utils
16
+ from sky.utils import config_utils
17
+ from sky.utils import ux_utils
18
+ from sky.utils import yaml_utils
19
+
7
20
  if typing.TYPE_CHECKING:
8
- import sky
21
+ import requests
22
+ else:
23
+ requests = adaptors_common.LazyImport('requests')
9
24
 
10
25
 
11
- @dataclasses.dataclass
12
- class RequestOptions:
26
+ class RequestOptions(pydantic.BaseModel):
13
27
  """Request options for admin policy.
14
28
 
15
29
  Args:
@@ -21,11 +35,28 @@ class RequestOptions:
21
35
  dryrun: Is the request a dryrun?
22
36
  """
23
37
  cluster_name: Optional[str]
38
+ # Keep these two fields for backward compatibility. The values are copied
39
+ # from task.resources.autostop_config, so that legacy admin policy plugins
40
+ # can still read the correct autostop config from request options before
41
+ # we drop the compatibility.
42
+ # TODO(aylei): remove these fields after 0.12.0
24
43
  idle_minutes_to_autostop: Optional[int]
25
44
  down: bool
26
45
  dryrun: bool
27
46
 
28
47
 
48
+ class _UserRequestBody(pydantic.BaseModel):
49
+ """Auxiliary model to validate and serialize a user request."""
50
+ # We have to use serialized YAML string, instead of a dict, because dict
51
+ # will be converted to JSON string, which will lose the None key.
52
+ task: str
53
+ skypilot_config: str
54
+ request_name: str
55
+ request_options: Optional[RequestOptions] = None
56
+ at_client_side: bool = False
57
+ user: str
58
+
59
+
29
60
  @dataclasses.dataclass
30
61
  class UserRequest:
31
62
  """A user request.
@@ -45,20 +76,101 @@ class UserRequest:
45
76
  task: User specified task.
46
77
  skypilot_config: Global skypilot config to be used in this request.
47
78
  request_options: Request options. It is None for jobs and services.
79
+ at_client_side: Is the request intercepted by the policy at client-side?
80
+ user: User who made the request.
81
+ Only available on the server side.
82
+ This value is None if at_client_side is True.
48
83
  """
49
84
  task: 'sky.Task'
50
85
  skypilot_config: 'sky.Config'
86
+ request_name: request_names.AdminPolicyRequestName
51
87
  request_options: Optional['RequestOptions'] = None
88
+ at_client_side: bool = False
89
+ user: Optional['models.User'] = None
90
+
91
+ def encode(self) -> str:
92
+ return _UserRequestBody(
93
+ task=yaml_utils.dump_yaml_str(self.task.to_yaml_config()),
94
+ skypilot_config=yaml_utils.dump_yaml_str(dict(
95
+ self.skypilot_config)),
96
+ request_name=self.request_name.value,
97
+ request_options=self.request_options,
98
+ at_client_side=self.at_client_side,
99
+ user=(yaml_utils.dump_yaml_str(self.user.to_dict())
100
+ if self.user is not None else ''),
101
+ ).model_dump_json()
102
+
103
+ @classmethod
104
+ def decode(cls, body: str) -> 'UserRequest':
105
+ user_request_body = _UserRequestBody.model_validate_json(body)
106
+ user_dict = yaml_utils.read_yaml_str(
107
+ user_request_body.user) if user_request_body.user != '' else None
108
+ user = models.User(
109
+ id=user_dict['id'],
110
+ name=user_dict['name']) if user_dict is not None else None
111
+ return cls(
112
+ task=sky.Task.from_yaml_config(
113
+ yaml_utils.read_yaml_all_str(user_request_body.task)[0]),
114
+ skypilot_config=config_utils.Config.from_dict(
115
+ yaml_utils.read_yaml_all_str(
116
+ user_request_body.skypilot_config)[0]),
117
+ request_name=request_names.AdminPolicyRequestName(
118
+ user_request_body.request_name),
119
+ request_options=user_request_body.request_options,
120
+ at_client_side=user_request_body.at_client_side,
121
+ user=user,
122
+ )
123
+
124
+
125
+ class _MutatedUserRequestBody(pydantic.BaseModel):
126
+ """Auxiliary model to validate and serialize a user request."""
127
+ task: str
128
+ skypilot_config: str
52
129
 
53
130
 
54
131
  @dataclasses.dataclass
55
132
  class MutatedUserRequest:
133
+ """Mutated user request."""
134
+
56
135
  task: 'sky.Task'
57
136
  skypilot_config: 'sky.Config'
58
137
 
138
+ def encode(self) -> str:
139
+ return _MutatedUserRequestBody(
140
+ task=yaml_utils.dump_yaml_str(self.task.to_yaml_config()),
141
+ skypilot_config=yaml_utils.dump_yaml_str(dict(
142
+ self.skypilot_config),)).model_dump_json()
143
+
144
+ @classmethod
145
+ def decode(cls, mutated_user_request_body: str,
146
+ original_request: UserRequest) -> 'MutatedUserRequest':
147
+ mutated_user_request_body = _MutatedUserRequestBody.model_validate_json(
148
+ mutated_user_request_body)
149
+ task = sky.Task.from_yaml_config(
150
+ yaml_utils.read_yaml_all_str(mutated_user_request_body.task)[0])
151
+ # Some internal Task fields are not serialized. We need to manually
152
+ # restore them from the original request.
153
+ task.managed_job_dag = original_request.task.managed_job_dag
154
+ task.service_name = original_request.task.service_name
155
+ return cls(task=task,
156
+ skypilot_config=config_utils.Config.from_dict(
157
+ yaml_utils.read_yaml_all_str(
158
+ mutated_user_request_body.skypilot_config)[0],))
159
+
160
+
161
+ class PolicyInterface:
162
+ """Interface for admin-defined policy for user requests."""
163
+
164
+ @abc.abstractmethod
165
+ def apply(self, user_request: UserRequest) -> MutatedUserRequest:
166
+ """Apply the admin policy to the user request."""
167
+
168
+ def __str__(self):
169
+ return f'{self.__class__.__name__}'
170
+
59
171
 
60
172
  # pylint: disable=line-too-long
61
- class AdminPolicy:
173
+ class AdminPolicy(PolicyInterface):
62
174
  """Abstract interface of an admin-defined policy for all user requests.
63
175
 
64
176
  Admins can implement a subclass of AdminPolicy with the following signature:
@@ -99,3 +211,74 @@ class AdminPolicy:
99
211
  """
100
212
  raise NotImplementedError(
101
213
  'Your policy must implement validate_and_mutate')
214
+
215
+ def apply(self, user_request: UserRequest) -> MutatedUserRequest:
216
+ return self.validate_and_mutate(user_request)
217
+
218
+
219
+ class PolicyTemplate(PolicyInterface):
220
+ """Admin policy template that can be instantiated to create a policy."""
221
+
222
+ @abc.abstractmethod
223
+ def validate_and_mutate(self,
224
+ user_request: UserRequest) -> MutatedUserRequest:
225
+ """Validates and mutates the user request and returns mutated request.
226
+
227
+ Args:
228
+ user_request: The user request to validate and mutate.
229
+ UserRequest contains (sky.Task, sky.Config)
230
+
231
+ Returns:
232
+ MutatedUserRequest: The mutated user request.
233
+
234
+ Raises:
235
+ Exception to throw if the user request failed the validation.
236
+ """
237
+ raise NotImplementedError(
238
+ 'Your policy must implement validate_and_mutate')
239
+
240
+ def apply(self, user_request: UserRequest) -> MutatedUserRequest:
241
+ return self.validate_and_mutate(user_request)
242
+
243
+
244
+ class RestfulAdminPolicy(PolicyTemplate):
245
+ """Admin policy that calls a RESTful API for validation."""
246
+
247
+ def __init__(self, policy_url: str):
248
+ super().__init__()
249
+ self.policy_url = policy_url
250
+
251
+ def validate_and_mutate(self,
252
+ user_request: UserRequest) -> MutatedUserRequest:
253
+ try:
254
+ response = requests.post(
255
+ self.policy_url,
256
+ json=user_request.encode(),
257
+ headers={'Content-Type': 'application/json'},
258
+ # TODO(aylei): make this configurable
259
+ timeout=30)
260
+ if response.status_code == 400:
261
+ raise exceptions.UserRequestRejectedByPolicy(
262
+ f'{colorama.Fore.RED}User request is rejected by admin '
263
+ f'policy {self.policy_url}{colorama.Fore.RESET}: '
264
+ f'{response.text}')
265
+ response.raise_for_status()
266
+ except requests.exceptions.RequestException as e:
267
+ with ux_utils.print_exception_no_traceback():
268
+ raise exceptions.RestfulPolicyError(
269
+ f'Failed to call admin policy URL '
270
+ f'{self.policy_url}: {e}') from None
271
+
272
+ try:
273
+ mutated_user_request = MutatedUserRequest.decode(
274
+ response.json(), user_request)
275
+ except Exception as e: # pylint: disable=broad-except
276
+ with ux_utils.print_exception_no_traceback():
277
+ raise exceptions.RestfulPolicyError(
278
+ f'Failed to decode response from admin policy URL '
279
+ f'{self.policy_url}: {common_utils.format_exception(e, use_bracket=True)}'
280
+ ) from None
281
+ return mutated_user_request
282
+
283
+ def __repr__(self):
284
+ return f'RestfulAdminPolicy(policy_url={self.policy_url})'