skypilot-nightly 1.0.0.dev20250627__py3-none-any.whl → 1.0.0.dev20250630__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 (173) hide show
  1. sky/__init__.py +2 -2
  2. sky/adaptors/kubernetes.py +14 -0
  3. sky/adaptors/nebius.py +2 -2
  4. sky/authentication.py +12 -5
  5. sky/backends/backend_utils.py +92 -26
  6. sky/check.py +5 -2
  7. sky/client/cli/command.py +39 -8
  8. sky/client/sdk.py +217 -167
  9. sky/client/service_account_auth.py +47 -0
  10. sky/clouds/aws.py +10 -4
  11. sky/clouds/azure.py +5 -2
  12. sky/clouds/cloud.py +5 -2
  13. sky/clouds/gcp.py +31 -18
  14. sky/clouds/kubernetes.py +54 -34
  15. sky/clouds/nebius.py +8 -2
  16. sky/clouds/ssh.py +5 -2
  17. sky/clouds/utils/aws_utils.py +10 -4
  18. sky/clouds/utils/gcp_utils.py +22 -7
  19. sky/clouds/utils/oci_utils.py +62 -14
  20. sky/dashboard/out/404.html +1 -1
  21. sky/dashboard/out/_next/static/NdypbqMxaYucRGfopkKXa/_buildManifest.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/1043-1b39779691bb4030.js +1 -0
  23. sky/dashboard/out/_next/static/chunks/{141-fa5a20cbf401b351.js → 1141-726e5a3f00b67185.js} +2 -2
  24. sky/dashboard/out/_next/static/chunks/1272-1ef0bf0237faccdb.js +1 -0
  25. sky/dashboard/out/_next/static/chunks/1664-d65361e92b85e786.js +1 -0
  26. sky/dashboard/out/_next/static/chunks/1691.44e378727a41f3b5.js +21 -0
  27. sky/dashboard/out/_next/static/chunks/1871-80dea41717729fa5.js +6 -0
  28. sky/dashboard/out/_next/static/chunks/2544.27f70672535675ed.js +1 -0
  29. sky/dashboard/out/_next/static/chunks/{875.52c962183328b3f2.js → 2875.c24c6d57dc82e436.js} +1 -1
  30. sky/dashboard/out/_next/static/chunks/3256.7257acd01b481bed.js +11 -0
  31. sky/dashboard/out/_next/static/chunks/3698-52ad1ca228faa776.js +1 -0
  32. sky/dashboard/out/_next/static/chunks/3785.b3cc2bc1d49d2c3c.js +1 -0
  33. sky/dashboard/out/_next/static/chunks/3937.d7f1c55d1916c7f2.js +1 -0
  34. sky/dashboard/out/_next/static/chunks/{947-6620842ef80ae879.js → 3947-b059261d6fa88a1f.js} +1 -1
  35. sky/dashboard/out/_next/static/chunks/{697.6460bf72e760addd.js → 4697.f5421144224da9fc.js} +1 -1
  36. sky/dashboard/out/_next/static/chunks/4725.4c849b1e05c8e9ad.js +1 -0
  37. sky/dashboard/out/_next/static/chunks/5230-df791914b54d91d9.js +1 -0
  38. sky/dashboard/out/_next/static/chunks/{491.b3d264269613fe09.js → 5491.918ffed0ba7a5294.js} +1 -1
  39. sky/dashboard/out/_next/static/chunks/5739-5ea3ffa10fc884f2.js +8 -0
  40. sky/dashboard/out/_next/static/chunks/616-162f3033ffcd3d31.js +39 -0
  41. sky/dashboard/out/_next/static/chunks/6601-fcfad0ddf92ec7ab.js +1 -0
  42. sky/dashboard/out/_next/static/chunks/6989-6ff4e45dfb49d11d.js +1 -0
  43. sky/dashboard/out/_next/static/chunks/6990-d0dc765474fa0eca.js +1 -0
  44. sky/dashboard/out/_next/static/chunks/8969-909d53833da080cb.js +1 -0
  45. sky/dashboard/out/_next/static/chunks/8982.a2e214068f30a857.js +1 -0
  46. sky/dashboard/out/_next/static/chunks/{25.76c246239df93d50.js → 9025.a7c44babfe56ce09.js} +2 -2
  47. sky/dashboard/out/_next/static/chunks/938-044ad21de8b4626b.js +1 -0
  48. sky/dashboard/out/_next/static/chunks/9470-21d059a1dfa03f61.js +1 -0
  49. sky/dashboard/out/_next/static/chunks/9984.739ae958a066298d.js +1 -0
  50. sky/dashboard/out/_next/static/chunks/fd9d1056-61f2257a9cd8b32b.js +1 -0
  51. sky/dashboard/out/_next/static/chunks/{framework-87d061ee6ed71b28.js → framework-efc06c2733009cd3.js} +1 -1
  52. sky/dashboard/out/_next/static/chunks/main-app-68c028b1bc5e1b72.js +1 -0
  53. sky/dashboard/out/_next/static/chunks/{main-e0e2335212e72357.js → main-c0a4f1ea606d48d2.js} +1 -1
  54. sky/dashboard/out/_next/static/chunks/pages/{_app-9a3ce3170d2edcec.js → _app-a37b06ddb64521fd.js} +2 -2
  55. sky/dashboard/out/_next/static/chunks/pages/_error-c72a1f77a3c0be1b.js +1 -0
  56. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-8135aba0712bda37.js +6 -0
  57. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-b8e1114e6d38218c.js +6 -0
  58. sky/dashboard/out/_next/static/chunks/pages/clusters-9744c271a1642f76.js +1 -0
  59. sky/dashboard/out/_next/static/chunks/pages/config-a2673b256b6d416f.js +1 -0
  60. sky/dashboard/out/_next/static/chunks/pages/index-927ddeebe57a8ac3.js +1 -0
  61. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-8b0809f59034d509.js +1 -0
  62. sky/dashboard/out/_next/static/chunks/pages/infra-ae9d2f705ce582c9.js +1 -0
  63. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-c4d5cfac7fbc0668.js +16 -0
  64. sky/dashboard/out/_next/static/chunks/pages/jobs-5bbdc71878f0a068.js +1 -0
  65. sky/dashboard/out/_next/static/chunks/pages/users-cd43fb3c122eedde.js +1 -0
  66. sky/dashboard/out/_next/static/chunks/pages/volumes-4ebf6484f7216387.js +1 -0
  67. sky/dashboard/out/_next/static/chunks/pages/workspace/new-5629d4e551dba1ee.js +1 -0
  68. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-7c0187f43757a548.js +1 -0
  69. sky/dashboard/out/_next/static/chunks/pages/workspaces-06bde99155fa6292.js +1 -0
  70. sky/dashboard/out/_next/static/chunks/webpack-d427db53e54de9ce.js +1 -0
  71. sky/dashboard/out/_next/static/css/0da6afe66176678a.css +3 -0
  72. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  73. sky/dashboard/out/clusters/[cluster].html +1 -1
  74. sky/dashboard/out/clusters.html +1 -1
  75. sky/dashboard/out/config.html +1 -1
  76. sky/dashboard/out/index.html +1 -1
  77. sky/dashboard/out/infra/[context].html +1 -1
  78. sky/dashboard/out/infra.html +1 -1
  79. sky/dashboard/out/jobs/[job].html +1 -1
  80. sky/dashboard/out/jobs.html +1 -1
  81. sky/dashboard/out/users.html +1 -1
  82. sky/dashboard/out/volumes.html +1 -1
  83. sky/dashboard/out/workspace/new.html +1 -1
  84. sky/dashboard/out/workspaces/[name].html +1 -1
  85. sky/dashboard/out/workspaces.html +1 -1
  86. sky/data/storage.py +8 -3
  87. sky/global_user_state.py +257 -9
  88. sky/jobs/client/sdk.py +20 -25
  89. sky/models.py +16 -0
  90. sky/optimizer.py +46 -0
  91. sky/provision/__init__.py +14 -6
  92. sky/provision/kubernetes/config.py +1 -1
  93. sky/provision/kubernetes/constants.py +9 -0
  94. sky/provision/kubernetes/instance.py +24 -18
  95. sky/provision/kubernetes/network.py +15 -9
  96. sky/provision/kubernetes/network_utils.py +42 -23
  97. sky/provision/kubernetes/utils.py +73 -35
  98. sky/provision/kubernetes/volume.py +77 -15
  99. sky/provision/nebius/utils.py +10 -4
  100. sky/resources.py +10 -4
  101. sky/serve/client/sdk.py +28 -34
  102. sky/server/common.py +51 -3
  103. sky/server/constants.py +3 -0
  104. sky/server/requests/executor.py +4 -0
  105. sky/server/requests/payloads.py +33 -0
  106. sky/server/requests/requests.py +19 -0
  107. sky/server/rest.py +6 -15
  108. sky/server/server.py +121 -6
  109. sky/skylet/constants.py +7 -0
  110. sky/skypilot_config.py +32 -4
  111. sky/task.py +12 -0
  112. sky/users/permission.py +29 -0
  113. sky/users/server.py +384 -5
  114. sky/users/token_service.py +196 -0
  115. sky/utils/common_utils.py +4 -5
  116. sky/utils/config_utils.py +41 -0
  117. sky/utils/controller_utils.py +5 -1
  118. sky/utils/log_utils.py +68 -0
  119. sky/utils/resource_checker.py +153 -0
  120. sky/utils/resources_utils.py +12 -4
  121. sky/utils/schemas.py +87 -60
  122. sky/utils/subprocess_utils.py +2 -6
  123. sky/volumes/server/core.py +103 -78
  124. sky/volumes/utils.py +22 -5
  125. sky/workspaces/core.py +9 -117
  126. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/METADATA +1 -1
  127. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/RECORD +133 -128
  128. sky/dashboard/out/_next/static/HudU4f4Xsy-cP51JvXSZ-/_buildManifest.js +0 -1
  129. sky/dashboard/out/_next/static/chunks/230-d6e363362017ff3a.js +0 -1
  130. sky/dashboard/out/_next/static/chunks/43-36177d00f6956ab2.js +0 -1
  131. sky/dashboard/out/_next/static/chunks/470-92dd1614396389be.js +0 -1
  132. sky/dashboard/out/_next/static/chunks/544.110e53813fb98e2e.js +0 -1
  133. sky/dashboard/out/_next/static/chunks/616-d6128fa9e7cae6e6.js +0 -39
  134. sky/dashboard/out/_next/static/chunks/645.961f08e39b8ce447.js +0 -1
  135. sky/dashboard/out/_next/static/chunks/664-047bc03493fda379.js +0 -1
  136. sky/dashboard/out/_next/static/chunks/690.55f9eed3be903f56.js +0 -16
  137. sky/dashboard/out/_next/static/chunks/785.dc2686c3c1235554.js +0 -1
  138. sky/dashboard/out/_next/static/chunks/798-c0525dc3f21e488d.js +0 -1
  139. sky/dashboard/out/_next/static/chunks/799-3625946b2ec2eb30.js +0 -8
  140. sky/dashboard/out/_next/static/chunks/871-3db673be3ee3750b.js +0 -6
  141. sky/dashboard/out/_next/static/chunks/937.3759f538f11a0953.js +0 -1
  142. sky/dashboard/out/_next/static/chunks/938-068520cc11738deb.js +0 -1
  143. sky/dashboard/out/_next/static/chunks/969-d3a0b53f728d280a.js +0 -1
  144. sky/dashboard/out/_next/static/chunks/973-81b2d057178adb76.js +0 -1
  145. sky/dashboard/out/_next/static/chunks/982.1b61658204416b0f.js +0 -1
  146. sky/dashboard/out/_next/static/chunks/984.e8bac186a24e5178.js +0 -1
  147. sky/dashboard/out/_next/static/chunks/989-db34c16ad7ea6155.js +0 -1
  148. sky/dashboard/out/_next/static/chunks/990-0ad5ea1699e03ee8.js +0 -1
  149. sky/dashboard/out/_next/static/chunks/fd9d1056-2821b0f0cabcd8bd.js +0 -1
  150. sky/dashboard/out/_next/static/chunks/main-app-241eb28595532291.js +0 -1
  151. sky/dashboard/out/_next/static/chunks/pages/_error-1be831200e60c5c0.js +0 -1
  152. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-aff040d7bc5d0086.js +0 -6
  153. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-8040f2483897ed0c.js +0 -6
  154. sky/dashboard/out/_next/static/chunks/pages/clusters-f119a5630a1efd61.js +0 -1
  155. sky/dashboard/out/_next/static/chunks/pages/config-6b255eae088da6a3.js +0 -1
  156. sky/dashboard/out/_next/static/chunks/pages/index-6b0d9e5031b70c58.js +0 -1
  157. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-b302aea4d65766bf.js +0 -1
  158. sky/dashboard/out/_next/static/chunks/pages/infra-ee8cc4d449945d19.js +0 -1
  159. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-e4b23128db0774cd.js +0 -16
  160. sky/dashboard/out/_next/static/chunks/pages/jobs-0a5695ff3075d94a.js +0 -1
  161. sky/dashboard/out/_next/static/chunks/pages/users-4978cbb093e141e7.js +0 -1
  162. sky/dashboard/out/_next/static/chunks/pages/volumes-476b670ef33d1ecd.js +0 -1
  163. sky/dashboard/out/_next/static/chunks/pages/workspace/new-5b59bce9eb208d84.js +0 -1
  164. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-cb7e720b739de53a.js +0 -1
  165. sky/dashboard/out/_next/static/chunks/pages/workspaces-50e230828730cfb3.js +0 -1
  166. sky/dashboard/out/_next/static/chunks/webpack-08fdb9e6070127fc.js +0 -1
  167. sky/dashboard/out/_next/static/css/52082cf558ec9705.css +0 -3
  168. /sky/dashboard/out/_next/static/{HudU4f4Xsy-cP51JvXSZ- → NdypbqMxaYucRGfopkKXa}/_ssgManifest.js +0 -0
  169. /sky/dashboard/out/_next/static/chunks/{804-4c9fc53aa74bc191.js → 804-9f5e98ce84d46bdd.js} +0 -0
  170. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/WHEEL +0 -0
  171. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/entry_points.txt +0 -0
  172. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/licenses/LICENSE +0 -0
  173. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/52082cf558ec9705.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/52082cf558ec9705.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-08fdb9e6070127fc.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-9a3ce3170d2edcec.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/jobs-0a5695ff3075d94a.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/jobs","query":{},"buildId":"HudU4f4Xsy-cP51JvXSZ-","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/0da6afe66176678a.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/0da6afe66176678a.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-d427db53e54de9ce.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-efc06c2733009cd3.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-c0a4f1ea606d48d2.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-a37b06ddb64521fd.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/jobs-5bbdc71878f0a068.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/jobs","query":{},"buildId":"NdypbqMxaYucRGfopkKXa","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/52082cf558ec9705.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/52082cf558ec9705.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-08fdb9e6070127fc.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-9a3ce3170d2edcec.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/users-4978cbb093e141e7.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/users","query":{},"buildId":"HudU4f4Xsy-cP51JvXSZ-","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/0da6afe66176678a.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/0da6afe66176678a.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-d427db53e54de9ce.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-efc06c2733009cd3.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-c0a4f1ea606d48d2.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-a37b06ddb64521fd.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/users-cd43fb3c122eedde.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/users","query":{},"buildId":"NdypbqMxaYucRGfopkKXa","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/52082cf558ec9705.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/52082cf558ec9705.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-08fdb9e6070127fc.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-9a3ce3170d2edcec.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/volumes-476b670ef33d1ecd.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/volumes","query":{},"buildId":"HudU4f4Xsy-cP51JvXSZ-","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/0da6afe66176678a.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/0da6afe66176678a.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-d427db53e54de9ce.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-efc06c2733009cd3.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-c0a4f1ea606d48d2.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-a37b06ddb64521fd.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/volumes-4ebf6484f7216387.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/volumes","query":{},"buildId":"NdypbqMxaYucRGfopkKXa","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/52082cf558ec9705.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/52082cf558ec9705.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-08fdb9e6070127fc.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-9a3ce3170d2edcec.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/workspace/new-5b59bce9eb208d84.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/workspace/new","query":{},"buildId":"HudU4f4Xsy-cP51JvXSZ-","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/0da6afe66176678a.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/0da6afe66176678a.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-d427db53e54de9ce.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-efc06c2733009cd3.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-c0a4f1ea606d48d2.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-a37b06ddb64521fd.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/workspace/new-5629d4e551dba1ee.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/workspace/new","query":{},"buildId":"NdypbqMxaYucRGfopkKXa","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/52082cf558ec9705.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/52082cf558ec9705.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-08fdb9e6070127fc.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-9a3ce3170d2edcec.js" defer=""></script><script src="/dashboard/_next/static/chunks/616-d6128fa9e7cae6e6.js" defer=""></script><script src="/dashboard/_next/static/chunks/230-d6e363362017ff3a.js" defer=""></script><script src="/dashboard/_next/static/chunks/799-3625946b2ec2eb30.js" defer=""></script><script src="/dashboard/_next/static/chunks/664-047bc03493fda379.js" defer=""></script><script src="/dashboard/_next/static/chunks/804-4c9fc53aa74bc191.js" defer=""></script><script src="/dashboard/_next/static/chunks/798-c0525dc3f21e488d.js" defer=""></script><script src="/dashboard/_next/static/chunks/947-6620842ef80ae879.js" defer=""></script><script src="/dashboard/_next/static/chunks/989-db34c16ad7ea6155.js" defer=""></script><script src="/dashboard/_next/static/chunks/470-92dd1614396389be.js" defer=""></script><script src="/dashboard/_next/static/chunks/990-0ad5ea1699e03ee8.js" defer=""></script><script src="/dashboard/_next/static/chunks/969-d3a0b53f728d280a.js" defer=""></script><script src="/dashboard/_next/static/chunks/43-36177d00f6956ab2.js" defer=""></script><script src="/dashboard/_next/static/chunks/973-81b2d057178adb76.js" defer=""></script><script src="/dashboard/_next/static/chunks/938-068520cc11738deb.js" defer=""></script><script src="/dashboard/_next/static/chunks/141-fa5a20cbf401b351.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/workspaces/%5Bname%5D-cb7e720b739de53a.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/workspaces/[name]","query":{},"buildId":"HudU4f4Xsy-cP51JvXSZ-","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/0da6afe66176678a.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/0da6afe66176678a.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-d427db53e54de9ce.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-efc06c2733009cd3.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-c0a4f1ea606d48d2.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-a37b06ddb64521fd.js" defer=""></script><script src="/dashboard/_next/static/chunks/616-162f3033ffcd3d31.js" defer=""></script><script src="/dashboard/_next/static/chunks/5230-df791914b54d91d9.js" defer=""></script><script src="/dashboard/_next/static/chunks/5739-5ea3ffa10fc884f2.js" defer=""></script><script src="/dashboard/_next/static/chunks/1664-d65361e92b85e786.js" defer=""></script><script src="/dashboard/_next/static/chunks/804-9f5e98ce84d46bdd.js" defer=""></script><script src="/dashboard/_next/static/chunks/1272-1ef0bf0237faccdb.js" defer=""></script><script src="/dashboard/_next/static/chunks/3947-b059261d6fa88a1f.js" defer=""></script><script src="/dashboard/_next/static/chunks/6989-6ff4e45dfb49d11d.js" defer=""></script><script src="/dashboard/_next/static/chunks/3698-52ad1ca228faa776.js" defer=""></script><script src="/dashboard/_next/static/chunks/9470-21d059a1dfa03f61.js" defer=""></script><script src="/dashboard/_next/static/chunks/6990-d0dc765474fa0eca.js" defer=""></script><script src="/dashboard/_next/static/chunks/8969-909d53833da080cb.js" defer=""></script><script src="/dashboard/_next/static/chunks/1043-1b39779691bb4030.js" defer=""></script><script src="/dashboard/_next/static/chunks/6601-fcfad0ddf92ec7ab.js" defer=""></script><script src="/dashboard/_next/static/chunks/938-044ad21de8b4626b.js" defer=""></script><script src="/dashboard/_next/static/chunks/1141-726e5a3f00b67185.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/workspaces/%5Bname%5D-7c0187f43757a548.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/workspaces/[name]","query":{},"buildId":"NdypbqMxaYucRGfopkKXa","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/52082cf558ec9705.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/52082cf558ec9705.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-08fdb9e6070127fc.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-9a3ce3170d2edcec.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/workspaces-50e230828730cfb3.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/HudU4f4Xsy-cP51JvXSZ-/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/workspaces","query":{},"buildId":"HudU4f4Xsy-cP51JvXSZ-","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/0da6afe66176678a.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/0da6afe66176678a.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-d427db53e54de9ce.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-efc06c2733009cd3.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-c0a4f1ea606d48d2.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-a37b06ddb64521fd.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/workspaces-06bde99155fa6292.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/NdypbqMxaYucRGfopkKXa/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/workspaces","query":{},"buildId":"NdypbqMxaYucRGfopkKXa","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
sky/data/storage.py CHANGED
@@ -1802,7 +1802,8 @@ class S3Store(AbstractStore):
1802
1802
 
1803
1803
  # Add AWS tags configured in config.yaml to the bucket.
1804
1804
  # This is useful for cost tracking and external cleanup.
1805
- bucket_tags = skypilot_config.get_nested(('aws', 'labels'), {})
1805
+ bucket_tags = skypilot_config.get_effective_region_config(
1806
+ cloud='aws', region=None, keys=('labels',), default_value={})
1806
1807
  if bucket_tags:
1807
1808
  s3_client.put_bucket_tagging(
1808
1809
  Bucket=bucket_name,
@@ -2765,8 +2766,12 @@ class AzureBlobStore(AbstractStore):
2765
2766
  # Creates new resource group and storage account or use the
2766
2767
  # storage_account provided by the user through config.yaml
2767
2768
  else:
2768
- config_storage_account = skypilot_config.get_nested(
2769
- ('azure', 'storage_account'), None)
2769
+ config_storage_account = (
2770
+ skypilot_config.get_effective_region_config(
2771
+ cloud='azure',
2772
+ region=None,
2773
+ keys=('storage_account',),
2774
+ default_value=None))
2770
2775
  if config_storage_account is not None:
2771
2776
  # using user provided storage account from config.yaml
2772
2777
  storage_account_name = config_storage_account
sky/global_user_state.py CHANGED
@@ -65,6 +65,7 @@ user_table = sqlalchemy.Table(
65
65
  sqlalchemy.Column('id', sqlalchemy.Text, primary_key=True),
66
66
  sqlalchemy.Column('name', sqlalchemy.Text),
67
67
  sqlalchemy.Column('password', sqlalchemy.Text),
68
+ sqlalchemy.Column('created_at', sqlalchemy.Integer),
68
69
  )
69
70
 
70
71
  cluster_table = sqlalchemy.Table(
@@ -167,6 +168,21 @@ ssh_key_table = sqlalchemy.Table(
167
168
  sqlalchemy.Column('ssh_private_key', sqlalchemy.Text),
168
169
  )
169
170
 
171
+ service_account_token_table = sqlalchemy.Table(
172
+ 'service_account_tokens',
173
+ Base.metadata,
174
+ sqlalchemy.Column('token_id', sqlalchemy.Text, primary_key=True),
175
+ sqlalchemy.Column('token_name', sqlalchemy.Text),
176
+ sqlalchemy.Column('token_hash', sqlalchemy.Text),
177
+ sqlalchemy.Column('created_at', sqlalchemy.Integer),
178
+ sqlalchemy.Column('last_used_at', sqlalchemy.Integer, server_default=None),
179
+ sqlalchemy.Column('expires_at', sqlalchemy.Integer, server_default=None),
180
+ sqlalchemy.Column('creator_user_hash',
181
+ sqlalchemy.Text), # Who created this token
182
+ sqlalchemy.Column('service_account_user_id',
183
+ sqlalchemy.Text), # Service account's own user ID
184
+ )
185
+
170
186
  cluster_yaml_table = sqlalchemy.Table(
171
187
  'cluster_yaml',
172
188
  Base.metadata,
@@ -174,6 +190,15 @@ cluster_yaml_table = sqlalchemy.Table(
174
190
  sqlalchemy.Column('yaml', sqlalchemy.Text),
175
191
  )
176
192
 
193
+ system_config_table = sqlalchemy.Table(
194
+ 'system_config',
195
+ Base.metadata,
196
+ sqlalchemy.Column('config_key', sqlalchemy.Text, primary_key=True),
197
+ sqlalchemy.Column('config_value', sqlalchemy.Text),
198
+ sqlalchemy.Column('created_at', sqlalchemy.Integer),
199
+ sqlalchemy.Column('updated_at', sqlalchemy.Integer),
200
+ )
201
+
177
202
 
178
203
  def _glob_to_similar(glob_pattern):
179
204
  """Converts a glob pattern to a PostgreSQL LIKE pattern."""
@@ -331,6 +356,12 @@ def create_table():
331
356
  'password',
332
357
  sqlalchemy.Text(),
333
358
  default_statement='DEFAULT NULL')
359
+ db_utils.add_column_to_table_sqlalchemy(
360
+ session,
361
+ 'users',
362
+ 'created_at',
363
+ sqlalchemy.Integer(),
364
+ default_statement='DEFAULT NULL')
334
365
 
335
366
  db_utils.add_column_to_table_sqlalchemy(
336
367
  session,
@@ -383,7 +414,8 @@ def _init_db(func):
383
414
 
384
415
 
385
416
  @_init_db
386
- def add_or_update_user(user: models.User) -> bool:
417
+ def add_or_update_user(user: models.User,
418
+ allow_duplicate_name: bool = True) -> bool:
387
419
  """Store the mapping from user hash to user name for display purposes.
388
420
 
389
421
  Returns:
@@ -394,7 +426,18 @@ def add_or_update_user(user: models.User) -> bool:
394
426
  if user.name is None:
395
427
  return False
396
428
 
429
+ # Set created_at if not already set
430
+ created_at = user.created_at
431
+ if created_at is None:
432
+ created_at = int(time.time())
397
433
  with orm.Session(_SQLALCHEMY_ENGINE) as session:
434
+ # Check for duplicate names if not allowed (within the same transaction)
435
+ if not allow_duplicate_name:
436
+ existing_user = session.query(user_table).filter(
437
+ user_table.c.name == user.name).first()
438
+ if existing_user is not None:
439
+ return False
440
+
398
441
  if (_SQLALCHEMY_ENGINE.dialect.name ==
399
442
  db_utils.SQLAlchemyDialect.SQLITE.value):
400
443
  # For SQLite, use INSERT OR IGNORE followed by UPDATE to detect new
@@ -405,14 +448,15 @@ def add_or_update_user(user: models.User) -> bool:
405
448
  insert_stmnt = insert_func(user_table).prefix_with(
406
449
  'OR IGNORE').values(id=user.id,
407
450
  name=user.name,
408
- password=user.password)
451
+ password=user.password,
452
+ created_at=created_at)
409
453
  result = session.execute(insert_stmnt)
410
454
 
411
455
  # Check if the INSERT actually inserted a row
412
456
  was_inserted = result.rowcount > 0
413
457
 
414
458
  if not was_inserted:
415
- # User existed, so update it
459
+ # User existed, so update it (but don't update created_at)
416
460
  if user.password:
417
461
  session.query(user_table).filter_by(id=user.id).update({
418
462
  user_table.c.name: user.name,
@@ -430,8 +474,12 @@ def add_or_update_user(user: models.User) -> bool:
430
474
  # For PostgreSQL, use INSERT ... ON CONFLICT with RETURNING to
431
475
  # detect insert vs update
432
476
  insert_func = postgresql.insert
477
+
433
478
  insert_stmnt = insert_func(user_table).values(
434
- id=user.id, name=user.name, password=user.password)
479
+ id=user.id,
480
+ name=user.name,
481
+ password=user.password,
482
+ created_at=created_at)
435
483
 
436
484
  # Use a sentinel in the RETURNING clause to detect insert vs update
437
485
  if user.password:
@@ -464,7 +512,10 @@ def get_user(user_id: str) -> Optional[models.User]:
464
512
  row = session.query(user_table).filter_by(id=user_id).first()
465
513
  if row is None:
466
514
  return None
467
- return models.User(id=row.id, name=row.name, password=row.password)
515
+ return models.User(id=row.id,
516
+ name=row.name,
517
+ password=row.password,
518
+ created_at=row.created_at)
468
519
 
469
520
 
470
521
  def get_user_by_name(username: str) -> List[models.User]:
@@ -473,8 +524,10 @@ def get_user_by_name(username: str) -> List[models.User]:
473
524
  if len(rows) == 0:
474
525
  return []
475
526
  return [
476
- models.User(id=row.id, name=row.name, password=row.password)
477
- for row in rows
527
+ models.User(id=row.id,
528
+ name=row.name,
529
+ password=row.password,
530
+ created_at=row.created_at) for row in rows
478
531
  ]
479
532
 
480
533
 
@@ -490,8 +543,10 @@ def get_all_users() -> List[models.User]:
490
543
  with orm.Session(_SQLALCHEMY_ENGINE) as session:
491
544
  rows = session.query(user_table).all()
492
545
  return [
493
- models.User(id=row.id, name=row.name, password=row.password)
494
- for row in rows
546
+ models.User(id=row.id,
547
+ name=row.name,
548
+ password=row.password,
549
+ created_at=row.created_at) for row in rows
495
550
  ]
496
551
 
497
552
 
@@ -1592,6 +1647,137 @@ def set_ssh_keys(user_hash: str, ssh_public_key: str, ssh_private_key: str):
1592
1647
  session.commit()
1593
1648
 
1594
1649
 
1650
+ @_init_db
1651
+ def add_service_account_token(token_id: str,
1652
+ token_name: str,
1653
+ token_hash: str,
1654
+ creator_user_hash: str,
1655
+ service_account_user_id: str,
1656
+ expires_at: Optional[int] = None) -> None:
1657
+ """Add a service account token to the database."""
1658
+ assert _SQLALCHEMY_ENGINE is not None
1659
+ created_at = int(time.time())
1660
+
1661
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1662
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
1663
+ db_utils.SQLAlchemyDialect.SQLITE.value):
1664
+ insert_func = sqlite.insert
1665
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
1666
+ db_utils.SQLAlchemyDialect.POSTGRESQL.value):
1667
+ insert_func = postgresql.insert
1668
+ else:
1669
+ raise ValueError('Unsupported database dialect')
1670
+
1671
+ insert_stmnt = insert_func(service_account_token_table).values(
1672
+ token_id=token_id,
1673
+ token_name=token_name,
1674
+ token_hash=token_hash,
1675
+ created_at=created_at,
1676
+ expires_at=expires_at,
1677
+ creator_user_hash=creator_user_hash,
1678
+ service_account_user_id=service_account_user_id)
1679
+ session.execute(insert_stmnt)
1680
+ session.commit()
1681
+
1682
+
1683
+ @_init_db
1684
+ def get_service_account_token(token_id: str) -> Optional[Dict[str, Any]]:
1685
+ """Get a service account token by token_id."""
1686
+ assert _SQLALCHEMY_ENGINE is not None
1687
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1688
+ row = session.query(service_account_token_table).filter_by(
1689
+ token_id=token_id).first()
1690
+ if row is None:
1691
+ return None
1692
+ return {
1693
+ 'token_id': row.token_id,
1694
+ 'token_name': row.token_name,
1695
+ 'token_hash': row.token_hash,
1696
+ 'created_at': row.created_at,
1697
+ 'last_used_at': row.last_used_at,
1698
+ 'expires_at': row.expires_at,
1699
+ 'creator_user_hash': row.creator_user_hash,
1700
+ 'service_account_user_id': row.service_account_user_id,
1701
+ }
1702
+
1703
+
1704
+ @_init_db
1705
+ def get_user_service_account_tokens(user_hash: str) -> List[Dict[str, Any]]:
1706
+ """Get all service account tokens for a user (as creator)."""
1707
+ assert _SQLALCHEMY_ENGINE is not None
1708
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1709
+ rows = session.query(service_account_token_table).filter_by(
1710
+ creator_user_hash=user_hash).all()
1711
+ return [{
1712
+ 'token_id': row.token_id,
1713
+ 'token_name': row.token_name,
1714
+ 'token_hash': row.token_hash,
1715
+ 'created_at': row.created_at,
1716
+ 'last_used_at': row.last_used_at,
1717
+ 'expires_at': row.expires_at,
1718
+ 'creator_user_hash': row.creator_user_hash,
1719
+ 'service_account_user_id': row.service_account_user_id,
1720
+ } for row in rows]
1721
+
1722
+
1723
+ @_init_db
1724
+ def update_service_account_token_last_used(token_id: str) -> None:
1725
+ """Update the last_used_at timestamp for a service account token."""
1726
+ assert _SQLALCHEMY_ENGINE is not None
1727
+ last_used_at = int(time.time())
1728
+
1729
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1730
+ session.query(service_account_token_table).filter_by(
1731
+ token_id=token_id).update(
1732
+ {service_account_token_table.c.last_used_at: last_used_at})
1733
+ session.commit()
1734
+
1735
+
1736
+ @_init_db
1737
+ def delete_service_account_token(token_id: str) -> bool:
1738
+ """Delete a service account token.
1739
+
1740
+ Returns:
1741
+ True if token was found and deleted.
1742
+ """
1743
+ assert _SQLALCHEMY_ENGINE is not None
1744
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1745
+ result = session.query(service_account_token_table).filter_by(
1746
+ token_id=token_id).delete()
1747
+ session.commit()
1748
+ return result > 0
1749
+
1750
+
1751
+ @_init_db
1752
+ def rotate_service_account_token(token_id: str,
1753
+ new_token_hash: str,
1754
+ new_expires_at: Optional[int] = None) -> None:
1755
+ """Rotate a service account token by updating its hash and expiration.
1756
+
1757
+ Args:
1758
+ token_id: The token ID to rotate.
1759
+ new_token_hash: The new hashed token value.
1760
+ new_expires_at: New expiration timestamp, or None for no expiration.
1761
+ """
1762
+ assert _SQLALCHEMY_ENGINE is not None
1763
+ current_time = int(time.time())
1764
+
1765
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1766
+ count = session.query(service_account_token_table).filter_by(
1767
+ token_id=token_id
1768
+ ).update({
1769
+ service_account_token_table.c.token_hash: new_token_hash,
1770
+ service_account_token_table.c.expires_at: new_expires_at,
1771
+ service_account_token_table.c.last_used_at: None, # Reset last used
1772
+ # Update creation time
1773
+ service_account_token_table.c.created_at: current_time,
1774
+ })
1775
+ session.commit()
1776
+
1777
+ if count == 0:
1778
+ raise ValueError(f'Service account token {token_id} not found.')
1779
+
1780
+
1595
1781
  @_init_db
1596
1782
  def get_cluster_yaml_str(cluster_yaml_path: Optional[str]) -> Optional[str]:
1597
1783
  """Get the cluster yaml from the database or the local file system.
@@ -1662,3 +1848,65 @@ def remove_cluster_yaml(cluster_name: str):
1662
1848
  session.query(cluster_yaml_table).filter_by(
1663
1849
  cluster_name=cluster_name).delete()
1664
1850
  session.commit()
1851
+
1852
+
1853
+ @_init_db
1854
+ def get_all_service_account_tokens() -> List[Dict[str, Any]]:
1855
+ """Get all service account tokens across all users (for admin access)."""
1856
+ assert _SQLALCHEMY_ENGINE is not None
1857
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1858
+ rows = session.query(service_account_token_table).all()
1859
+ return [{
1860
+ 'token_id': row.token_id,
1861
+ 'token_name': row.token_name,
1862
+ 'token_hash': row.token_hash,
1863
+ 'created_at': row.created_at,
1864
+ 'last_used_at': row.last_used_at,
1865
+ 'expires_at': row.expires_at,
1866
+ 'creator_user_hash': row.creator_user_hash,
1867
+ 'service_account_user_id': row.service_account_user_id,
1868
+ } for row in rows]
1869
+
1870
+
1871
+ @_init_db
1872
+ def get_system_config(config_key: str) -> Optional[str]:
1873
+ """Get a system configuration value by key."""
1874
+ assert _SQLALCHEMY_ENGINE is not None
1875
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1876
+ row = session.query(system_config_table).filter_by(
1877
+ config_key=config_key).first()
1878
+ if row is None:
1879
+ return None
1880
+ return row.config_value
1881
+
1882
+
1883
+ @_init_db
1884
+ def set_system_config(config_key: str, config_value: str) -> None:
1885
+ """Set a system configuration value."""
1886
+ assert _SQLALCHEMY_ENGINE is not None
1887
+ current_time = int(time.time())
1888
+
1889
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1890
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
1891
+ db_utils.SQLAlchemyDialect.SQLITE.value):
1892
+ insert_func = sqlite.insert
1893
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
1894
+ db_utils.SQLAlchemyDialect.POSTGRESQL.value):
1895
+ insert_func = postgresql.insert
1896
+ else:
1897
+ raise ValueError('Unsupported database dialect')
1898
+
1899
+ insert_stmnt = insert_func(system_config_table).values(
1900
+ config_key=config_key,
1901
+ config_value=config_value,
1902
+ created_at=current_time,
1903
+ updated_at=current_time)
1904
+
1905
+ upsert_stmnt = insert_stmnt.on_conflict_do_update(
1906
+ index_elements=[system_config_table.c.config_key],
1907
+ set_={
1908
+ system_config_table.c.config_value: config_value,
1909
+ system_config_table.c.updated_at: current_time,
1910
+ })
1911
+ session.execute(upsert_stmnt)
1912
+ session.commit()
sky/jobs/client/sdk.py CHANGED
@@ -82,12 +82,11 @@ def launch(
82
82
  task=dag_str,
83
83
  name=name,
84
84
  )
85
- response = rest.post(
86
- f'{server_common.get_server_url()}/jobs/launch',
85
+ response = server_common.make_authenticated_request(
86
+ 'POST',
87
+ '/jobs/launch',
87
88
  json=json.loads(body.model_dump_json()),
88
- timeout=(5, None),
89
- cookies=server_common.get_api_cookie_jar(),
90
- )
89
+ timeout=(5, None))
91
90
  return server_common.get_request_id(response)
92
91
 
93
92
 
@@ -142,12 +141,11 @@ def queue(refresh: bool,
142
141
  all_users=all_users,
143
142
  job_ids=job_ids,
144
143
  )
145
- response = rest.post(
146
- f'{server_common.get_server_url()}/jobs/queue',
144
+ response = server_common.make_authenticated_request(
145
+ 'POST',
146
+ '/jobs/queue',
147
147
  json=json.loads(body.model_dump_json()),
148
- timeout=(5, None),
149
- cookies=server_common.get_api_cookie_jar(),
150
- )
148
+ timeout=(5, None))
151
149
  return server_common.get_request_id(response=response)
152
150
 
153
151
 
@@ -182,12 +180,11 @@ def cancel(
182
180
  all=all,
183
181
  all_users=all_users,
184
182
  )
185
- response = rest.post(
186
- f'{server_common.get_server_url()}/jobs/cancel',
183
+ response = server_common.make_authenticated_request(
184
+ 'POST',
185
+ '/jobs/cancel',
187
186
  json=json.loads(body.model_dump_json()),
188
- timeout=(5, None),
189
- cookies=server_common.get_api_cookie_jar(),
190
- )
187
+ timeout=(5, None))
191
188
  return server_common.get_request_id(response=response)
192
189
 
193
190
 
@@ -233,13 +230,12 @@ def tail_logs(name: Optional[str] = None,
233
230
  refresh=refresh,
234
231
  tail=tail,
235
232
  )
236
- response = rest.post(
237
- f'{server_common.get_server_url()}/jobs/logs',
233
+ response = server_common.make_authenticated_request(
234
+ 'POST',
235
+ '/jobs/logs',
238
236
  json=json.loads(body.model_dump_json()),
239
237
  stream=True,
240
- timeout=(5, None),
241
- cookies=server_common.get_api_cookie_jar(),
242
- )
238
+ timeout=(5, None))
243
239
  request_id = server_common.get_request_id(response)
244
240
  # Log request is idempotent when tail is 0, thus can resume previous
245
241
  # streaming point on retry.
@@ -283,12 +279,11 @@ def download_logs(
283
279
  controller=controller,
284
280
  local_dir=local_dir,
285
281
  )
286
- response = rest.post(
287
- f'{server_common.get_server_url()}/jobs/download_logs',
282
+ response = server_common.make_authenticated_request(
283
+ 'POST',
284
+ '/jobs/download_logs',
288
285
  json=json.loads(body.model_dump_json()),
289
- timeout=(5, None),
290
- cookies=server_common.get_api_cookie_jar(),
291
- )
286
+ timeout=(5, None))
292
287
  job_id_remote_path_dict = sdk.stream_and_get(
293
288
  server_common.get_request_id(response))
294
289
  remote2local_path_dict = client_common.download_logs_from_api_server(
sky/models.py CHANGED
@@ -20,6 +20,18 @@ class User:
20
20
  # Display name of the user
21
21
  name: Optional[str] = None
22
22
  password: Optional[str] = None
23
+ created_at: Optional[int] = None
24
+
25
+ def __init__(
26
+ self,
27
+ id: str, # pylint: disable=redefined-builtin
28
+ name: Optional[str] = None,
29
+ password: Optional[str] = None,
30
+ created_at: Optional[int] = None):
31
+ self.id = id.strip().lower()
32
+ self.name = name
33
+ self.password = password
34
+ self.created_at = created_at
23
35
 
24
36
  def to_dict(self) -> Dict[str, Any]:
25
37
  return {'id': self.id, 'name': self.name}
@@ -37,6 +49,10 @@ class User:
37
49
  user_hash = common_utils.get_user_hash()
38
50
  return User(id=user_hash, name=user_name)
39
51
 
52
+ def is_service_account(self) -> bool:
53
+ """Check if the user is a service account."""
54
+ return self.id.lower().startswith('sa-')
55
+
40
56
 
41
57
  RealtimeGpuAvailability = collections.namedtuple(
42
58
  'RealtimeGpuAvailability', ['gpu', 'counts', 'capacity', 'available'])
sky/optimizer.py CHANGED
@@ -1252,6 +1252,52 @@ def _check_specified_clouds(dag: 'dag_lib.Dag') -> None:
1252
1252
  logger.warning(
1253
1253
  f'{colorama.Fore.YELLOW}{msg}{colorama.Style.RESET_ALL}')
1254
1254
 
1255
+ _check_specified_regions(task)
1256
+
1257
+
1258
+ def _check_specified_regions(task: task_lib.Task) -> None:
1259
+ """Check if specified regions (Kubernetes contexts) are enabled.
1260
+
1261
+ Args:
1262
+ task: The task to check.
1263
+ """
1264
+ # Only check for Kubernetes now
1265
+ if not all(
1266
+ isinstance(resources.cloud, clouds.Kubernetes)
1267
+ for resources in task.resources):
1268
+ return
1269
+ # Kubernetes region is a context if set
1270
+ for resources in task.resources:
1271
+ if resources.region is None:
1272
+ continue
1273
+ existing_contexts = clouds.Kubernetes.existing_allowed_contexts()
1274
+ region = resources.region
1275
+ task_name = f' {task.name!r}' if task.name is not None else ''
1276
+ msg = f'Task{task_name} requires '
1277
+ if region not in existing_contexts:
1278
+ infra_str = f'Kubernetes/{region}'
1279
+ logger.warning(f'{infra_str} is not enabled.')
1280
+ volume_mounts_str = ''
1281
+ if task.volume_mounts:
1282
+ if len(task.volume_mounts) > 1:
1283
+ volume_mounts_str += 'volumes '
1284
+ else:
1285
+ volume_mounts_str += 'volume '
1286
+ volume_mounts_str += ', '.join(
1287
+ [f'{v.volume_name}' for v in task.volume_mounts])
1288
+ volume_mounts_str += f' with infra {infra_str}'
1289
+ if volume_mounts_str:
1290
+ msg += volume_mounts_str
1291
+ else:
1292
+ msg += f'infra {infra_str}'
1293
+ msg += (
1294
+ f' which is not enabled. To enable access, change '
1295
+ f'the task infra requirement or run: {colorama.Style.BRIGHT}'
1296
+ f'sky check {colorama.Style.RESET_ALL}'
1297
+ f'to ensure the infra is enabled.')
1298
+ with ux_utils.print_exception_no_traceback():
1299
+ raise exceptions.ResourcesUnavailableError(msg)
1300
+
1255
1301
 
1256
1302
  def _fill_in_launchable_resources(
1257
1303
  task: task_lib.Task,