skypilot-nightly 1.0.0.dev20250529__py3-none-any.whl → 1.0.0.dev20250530__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 (83) hide show
  1. sky/__init__.py +2 -2
  2. sky/adaptors/nebius.py +99 -16
  3. sky/authentication.py +54 -7
  4. sky/backends/backend_utils.py +35 -22
  5. sky/backends/cloud_vm_ray_backend.py +30 -15
  6. sky/check.py +1 -1
  7. sky/cli.py +7 -5
  8. sky/client/cli.py +7 -5
  9. sky/clouds/nebius.py +55 -14
  10. sky/dashboard/out/404.html +1 -1
  11. sky/dashboard/out/_next/static/Q32Bxr2Pby5tFDW-y5TNg/_buildManifest.js +1 -0
  12. sky/dashboard/out/_next/static/chunks/236-ca00738e2f58ea65.js +6 -0
  13. sky/dashboard/out/_next/static/chunks/37-64efcd0e9c54bff6.js +6 -0
  14. sky/dashboard/out/_next/static/chunks/{173-7db8607cefc20f70.js → 614-3d29f98e0634b179.js} +2 -2
  15. sky/dashboard/out/_next/static/chunks/682-f3f1443ed2fba42f.js +6 -0
  16. sky/dashboard/out/_next/static/chunks/798-c0525dc3f21e488d.js +1 -0
  17. sky/dashboard/out/_next/static/chunks/843-786c36624d5ff61f.js +11 -0
  18. sky/dashboard/out/_next/static/chunks/856-02e34c9fc5945066.js +1 -0
  19. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-42d3656aba9d2e78.js +6 -0
  20. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-20835df7b0c4599c.js +6 -0
  21. sky/dashboard/out/_next/static/chunks/pages/{clusters-943992b84fd6f4ee.js → clusters-f37ff20f0af29aae.js} +1 -1
  22. sky/dashboard/out/_next/static/chunks/pages/{config-7c48919fe030bc43.js → config-3c6a2dabf56e8cd6.js} +1 -1
  23. sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-909f1ceb0fcf1b99.js → [context]-342bc15bb78ab2e5.js} +1 -1
  24. sky/dashboard/out/_next/static/chunks/pages/{infra-d4c6875c88771e17.js → infra-7b4b8e7fa9fa0827.js} +1 -1
  25. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-258decb65e95f520.js +11 -0
  26. sky/dashboard/out/_next/static/chunks/pages/{jobs-a4efc09e61988f8d.js → jobs-78a6c5ba3e24c0cf.js} +1 -1
  27. sky/dashboard/out/_next/static/chunks/pages/{users-b2634885d67c49a6.js → users-89f9212b81d8897e.js} +1 -1
  28. sky/dashboard/out/_next/static/chunks/pages/workspace/{new-579b3203c7c19d84.js → new-198b6e00d7d724c5.js} +1 -1
  29. sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-9388e38fac73ee8f.js → [name]-2ce792183b03c341.js} +1 -1
  30. sky/dashboard/out/_next/static/chunks/pages/workspaces-17d41826537196e7.js +1 -0
  31. sky/dashboard/out/_next/static/chunks/webpack-f27c9a32aa3d9c6d.js +1 -0
  32. sky/dashboard/out/_next/static/css/5411b9fb0a783c1c.css +3 -0
  33. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  34. sky/dashboard/out/clusters/[cluster].html +1 -1
  35. sky/dashboard/out/clusters.html +1 -1
  36. sky/dashboard/out/config.html +1 -1
  37. sky/dashboard/out/index.html +1 -1
  38. sky/dashboard/out/infra/[context].html +1 -1
  39. sky/dashboard/out/infra.html +1 -1
  40. sky/dashboard/out/jobs/[job].html +1 -1
  41. sky/dashboard/out/jobs.html +1 -1
  42. sky/dashboard/out/users.html +1 -1
  43. sky/dashboard/out/workspace/new.html +1 -1
  44. sky/dashboard/out/workspaces/[name].html +1 -1
  45. sky/dashboard/out/workspaces.html +1 -1
  46. sky/exceptions.py +10 -0
  47. sky/global_user_state.py +149 -1
  48. sky/jobs/constants.py +1 -1
  49. sky/jobs/state.py +24 -5
  50. sky/jobs/utils.py +3 -1
  51. sky/provision/kubernetes/utils.py +2 -1
  52. sky/provision/provisioner.py +15 -10
  53. sky/serve/controller.py +10 -7
  54. sky/serve/replica_managers.py +22 -18
  55. sky/serve/service.py +5 -4
  56. sky/server/common.py +5 -2
  57. sky/server/stream_utils.py +21 -0
  58. sky/templates/kubernetes-ray.yml.j2 +19 -1
  59. sky/utils/common_utils.py +66 -0
  60. sky/utils/rich_utils.py +5 -0
  61. sky/utils/schemas.py +20 -1
  62. {skypilot_nightly-1.0.0.dev20250529.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/METADATA +1 -1
  63. {skypilot_nightly-1.0.0.dev20250529.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/RECORD +69 -70
  64. sky/dashboard/out/_next/static/HvNkg7hqKM1p0ptAcdDcF/_buildManifest.js +0 -1
  65. sky/dashboard/out/_next/static/chunks/236-90e5498a5b00ec29.js +0 -6
  66. sky/dashboard/out/_next/static/chunks/303-2c7b0f7af571710b.js +0 -6
  67. sky/dashboard/out/_next/static/chunks/320-afea3ddcc5bd1c6c.js +0 -6
  68. sky/dashboard/out/_next/static/chunks/578-9146658cead92981.js +0 -6
  69. sky/dashboard/out/_next/static/chunks/843-256ec920f6d5f41f.js +0 -11
  70. sky/dashboard/out/_next/static/chunks/856-59a1760784c9e770.js +0 -1
  71. sky/dashboard/out/_next/static/chunks/9f96d65d-5a3e4af68c26849e.js +0 -1
  72. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-159bffb2fa34ed54.js +0 -6
  73. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-9506c00257d10dbd.js +0 -1
  74. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-6b80e9e0c6aa16a1.js +0 -6
  75. sky/dashboard/out/_next/static/chunks/pages/workspaces-610c49ae3619ee85.js +0 -1
  76. sky/dashboard/out/_next/static/chunks/webpack-deda68c926e8d0bc.js +0 -1
  77. sky/dashboard/out/_next/static/css/ffd1cd601648c303.css +0 -3
  78. /sky/dashboard/out/_next/static/{HvNkg7hqKM1p0ptAcdDcF → Q32Bxr2Pby5tFDW-y5TNg}/_ssgManifest.js +0 -0
  79. /sky/dashboard/out/_next/static/chunks/pages/{_app-a631df412d8172de.js → _app-f19ea34b91c33950.js} +0 -0
  80. {skypilot_nightly-1.0.0.dev20250529.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/WHEEL +0 -0
  81. {skypilot_nightly-1.0.0.dev20250529.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/entry_points.txt +0 -0
  82. {skypilot_nightly-1.0.0.dev20250529.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/licenses/LICENSE +0 -0
  83. {skypilot_nightly-1.0.0.dev20250529.dist-info → skypilot_nightly-1.0.0.dev20250530.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"/><link rel="preload" href="/dashboard/skypilot.svg" as="image" fetchpriority="high"/><meta name="next-head-count" content="3"/><link rel="preload" href="/dashboard/_next/static/css/ffd1cd601648c303.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/ffd1cd601648c303.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-deda68c926e8d0bc.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-a631df412d8172de.js" defer=""></script><script src="/dashboard/_next/static/chunks/9f96d65d-5a3e4af68c26849e.js" defer=""></script><script src="/dashboard/_next/static/chunks/173-7db8607cefc20f70.js" defer=""></script><script src="/dashboard/_next/static/chunks/121-8f55ee3fa6301784.js" defer=""></script><script src="/dashboard/_next/static/chunks/320-afea3ddcc5bd1c6c.js" defer=""></script><script src="/dashboard/_next/static/chunks/470-4d003c441839094d.js" defer=""></script><script src="/dashboard/_next/static/chunks/293-351268365226d251.js" defer=""></script><script src="/dashboard/_next/static/chunks/856-59a1760784c9e770.js" defer=""></script><script src="/dashboard/_next/static/chunks/973-1a09cac61cfcc1e1.js" defer=""></script><script src="/dashboard/_next/static/chunks/236-90e5498a5b00ec29.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/workspaces-610c49ae3619ee85.js" defer=""></script><script src="/dashboard/_next/static/HvNkg7hqKM1p0ptAcdDcF/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/HvNkg7hqKM1p0ptAcdDcF/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div class="min-h-screen bg-gray-50"><div class="fixed top-0 left-0 right-0 z-50 shadow-sm"><div class="fixed top-0 left-0 right-0 bg-white z-30 h-14 px-4 border-b border-gray-200 shadow-sm"><div class="flex items-center h-full"><div class="flex items-center space-x-4 mr-6"><a class="flex items-center px-1 pt-1 h-full" href="/dashboard"><div class="h-20 w-20 flex items-center justify-center"><img alt="SkyPilot Logo" fetchpriority="high" width="80" height="80" decoding="async" data-nimg="1" class="w-full h-full object-contain" style="color:transparent" src="/dashboard/skypilot.svg"/></div></a></div><div class="flex items-center space-x-2 md:space-x-4 mr-6"><a class="inline-flex items-center border-b-2 border-transparent hover:text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/clusters"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect><rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect><line x1="6" x2="6.01" y1="6" y2="6"></line><line x1="6" x2="6.01" y1="18" y2="18"></line></svg><span>Clusters</span></a><a class="inline-flex items-center border-b-2 border-transparent hover:text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/jobs"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path><rect width="20" height="14" x="2" y="6" rx="2"></rect></svg><span>Jobs</span></a><div class="border-l border-gray-200 h-6 mx-1"></div><a class="inline-flex items-center border-b-2 border-transparent hover:text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/infra"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect><rect x="9" y="9" width="6" height="6"></rect><line x1="9" y1="1" x2="9" y2="4"></line><line x1="15" y1="1" x2="15" y2="4"></line><line x1="9" y1="20" x2="9" y2="23"></line><line x1="15" y1="20" x2="15" y2="23"></line><line x1="20" y1="9" x2="23" y2="9"></line><line x1="20" y1="14" x2="23" y2="14"></line><line x1="1" y1="9" x2="4" y2="9"></line><line x1="1" y1="14" x2="4" y2="14"></line></svg><span>Infra</span></a><a class="inline-flex items-center border-b-2 border-transparent text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/workspaces"><svg class="w-4 h-4" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path fill="none" d="M0 0h24v24H0z"></path><path d="M3 18.5V5a3 3 0 0 1 3-3h14a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5A3.5 3.5 0 0 1 3 18.5zM19 20v-3H6.5a1.5 1.5 0 0 0 0 3H19zM10 4H6a1 1 0 0 0-1 1v10.337A3.486 3.486 0 0 1 6.5 15H19V4h-2v8l-3.5-2-3.5 2V4z"></path></g></svg><span>Workspaces</span></a><a class="inline-flex items-center border-b-2 border-transparent hover:text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/users"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users w-4 h-4"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg><span>Users</span></a></div><div class="flex items-center space-x-1 ml-auto"><a href="https://skypilot.readthedocs.io/en/latest/" target="_blank" rel="noopener noreferrer" class="inline-flex items-center px-2 py-1 text-gray-600 hover:text-blue-600 transition-colors duration-150 cursor-pointer" title="Docs" tabindex="0"><span class="mr-1">Docs</span><svg class="w-3.5 h-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a><a href="https://github.com/skypilot-org/skypilot" target="_blank" rel="noopener noreferrer" class="inline-flex items-center justify-center p-2 rounded-full text-gray-600 hover:bg-gray-100 transition-colors duration-150 cursor-pointer" title="GitHub" tabindex="0"><svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path></svg></a><a href="https://slack.skypilot.co/" target="_blank" rel="noopener noreferrer" class="inline-flex items-center justify-center p-2 rounded-full text-gray-600 hover:bg-gray-100 transition-colors duration-150 cursor-pointer" title="Slack" tabindex="0"><svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path transform="scale(0.85) translate(1.8, 1.8)" d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"></path></svg></a><a href="https://github.com/skypilot-org/skypilot/issues/new" target="_blank" rel="noopener noreferrer" class="inline-flex items-center justify-center p-2 rounded-full text-gray-600 hover:bg-gray-100 transition-colors duration-150 cursor-pointer" title="Leave Feedback" tabindex="0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-square w-5 h-5"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg></a><div class="border-l border-gray-200 h-6"></div><a class="inline-flex items-center justify-center p-2 rounded-full transition-colors duration-150 cursor-pointer text-gray-600 hover:bg-gray-100" title="Configuration" tabindex="0" href="/dashboard/config"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-settings w-5 h-5"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path><circle cx="12" cy="12" r="3"></circle></svg></a></div></div></div></div><div class="transition-all duration-200 ease-in-out min-h-screen" style="padding-top:56px"><main class="p-6"><div class="flex justify-center items-center h-64"><style data-emotion="css z01bqi animation-61bdi0">.css-z01bqi{display:inline-block;color:#1976d2;-webkit-animation:animation-61bdi0 1.4s linear infinite;animation:animation-61bdi0 1.4s linear infinite;}@-webkit-keyframes animation-61bdi0{0%{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);}100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}@keyframes animation-61bdi0{0%{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);}100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}</style><span class="MuiCircularProgress-root MuiCircularProgress-indeterminate MuiCircularProgress-colorPrimary css-z01bqi" style="width:40px;height:40px" role="progressbar"><style data-emotion="css 13o7eu2">.css-13o7eu2{display:block;}</style><svg class="MuiCircularProgress-svg css-13o7eu2" viewBox="22 22 44 44"><style data-emotion="css 14891ef animation-1p2h4ri">.css-14891ef{stroke:currentColor;stroke-dasharray:80px,200px;stroke-dashoffset:0;-webkit-animation:animation-1p2h4ri 1.4s ease-in-out infinite;animation:animation-1p2h4ri 1.4s ease-in-out infinite;}@-webkit-keyframes animation-1p2h4ri{0%{stroke-dasharray:1px,200px;stroke-dashoffset:0;}50%{stroke-dasharray:100px,200px;stroke-dashoffset:-15px;}100%{stroke-dasharray:100px,200px;stroke-dashoffset:-125px;}}@keyframes animation-1p2h4ri{0%{stroke-dasharray:1px,200px;stroke-dashoffset:0;}50%{stroke-dasharray:100px,200px;stroke-dashoffset:-15px;}100%{stroke-dasharray:100px,200px;stroke-dashoffset:-125px;}}</style><circle class="MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate css-14891ef" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg></span><span class="ml-2 text-gray-500">Loading workspaces...</span></div></main></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/workspaces","query":{},"buildId":"HvNkg7hqKM1p0ptAcdDcF","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"/><title>Workspaces | SkyPilot Dashboard</title><link rel="preload" href="/dashboard/skypilot.svg" as="image" fetchpriority="high"/><meta name="next-head-count" content="4"/><link rel="preload" href="/dashboard/_next/static/css/5411b9fb0a783c1c.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/5411b9fb0a783c1c.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-f27c9a32aa3d9c6d.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-f19ea34b91c33950.js" defer=""></script><script src="/dashboard/_next/static/chunks/614-3d29f98e0634b179.js" defer=""></script><script src="/dashboard/_next/static/chunks/798-c0525dc3f21e488d.js" defer=""></script><script src="/dashboard/_next/static/chunks/121-8f55ee3fa6301784.js" defer=""></script><script src="/dashboard/_next/static/chunks/470-4d003c441839094d.js" defer=""></script><script src="/dashboard/_next/static/chunks/293-351268365226d251.js" defer=""></script><script src="/dashboard/_next/static/chunks/856-02e34c9fc5945066.js" defer=""></script><script src="/dashboard/_next/static/chunks/973-1a09cac61cfcc1e1.js" defer=""></script><script src="/dashboard/_next/static/chunks/236-ca00738e2f58ea65.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/workspaces-17d41826537196e7.js" defer=""></script><script src="/dashboard/_next/static/Q32Bxr2Pby5tFDW-y5TNg/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/Q32Bxr2Pby5tFDW-y5TNg/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div class="min-h-screen bg-gray-50"><div class="fixed top-0 left-0 right-0 z-50 shadow-sm"><div class="fixed top-0 left-0 right-0 bg-white z-30 h-14 px-4 border-b border-gray-200 shadow-sm"><div class="flex items-center h-full"><div class="flex items-center space-x-4 mr-6"><a class="flex items-center px-1 pt-1 h-full" href="/dashboard"><div class="h-20 w-20 flex items-center justify-center"><img alt="SkyPilot Logo" fetchpriority="high" width="80" height="80" decoding="async" data-nimg="1" class="w-full h-full object-contain" style="color:transparent" src="/dashboard/skypilot.svg"/></div></a></div><div class="flex items-center space-x-2 md:space-x-4 mr-6"><a class="inline-flex items-center border-b-2 border-transparent hover:text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/clusters"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect><rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect><line x1="6" x2="6.01" y1="6" y2="6"></line><line x1="6" x2="6.01" y1="18" y2="18"></line></svg><span>Clusters</span></a><a class="inline-flex items-center border-b-2 border-transparent hover:text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/jobs"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path><rect width="20" height="14" x="2" y="6" rx="2"></rect></svg><span>Jobs</span></a><div class="border-l border-gray-200 h-6 mx-1"></div><a class="inline-flex items-center border-b-2 border-transparent hover:text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/infra"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect><rect x="9" y="9" width="6" height="6"></rect><line x1="9" y1="1" x2="9" y2="4"></line><line x1="15" y1="1" x2="15" y2="4"></line><line x1="9" y1="20" x2="9" y2="23"></line><line x1="15" y1="20" x2="15" y2="23"></line><line x1="20" y1="9" x2="23" y2="9"></line><line x1="20" y1="14" x2="23" y2="14"></line><line x1="1" y1="9" x2="4" y2="9"></line><line x1="1" y1="14" x2="4" y2="14"></line></svg><span>Infra</span></a><a class="inline-flex items-center border-b-2 border-transparent text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/workspaces"><svg class="w-4 h-4" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path fill="none" d="M0 0h24v24H0z"></path><path d="M3 18.5V5a3 3 0 0 1 3-3h14a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5A3.5 3.5 0 0 1 3 18.5zM19 20v-3H6.5a1.5 1.5 0 0 0 0 3H19zM10 4H6a1 1 0 0 0-1 1v10.337A3.486 3.486 0 0 1 6.5 15H19V4h-2v8l-3.5-2-3.5 2V4z"></path></g></svg><span>Workspaces</span></a><a class="inline-flex items-center border-b-2 border-transparent hover:text-blue-600 px-1 pt-1 space-x-2" href="/dashboard/users"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users w-4 h-4"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg><span>Users</span></a></div><div class="flex items-center space-x-1 ml-auto"><a href="https://skypilot.readthedocs.io/en/latest/" target="_blank" rel="noopener noreferrer" class="inline-flex items-center px-2 py-1 text-gray-600 hover:text-blue-600 transition-colors duration-150 cursor-pointer" title="Docs" tabindex="0"><span class="mr-1">Docs</span><svg class="w-3.5 h-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a><a href="https://github.com/skypilot-org/skypilot" target="_blank" rel="noopener noreferrer" class="inline-flex items-center justify-center p-2 rounded-full text-gray-600 hover:bg-gray-100 transition-colors duration-150 cursor-pointer" title="GitHub" tabindex="0"><svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path></svg></a><a href="https://slack.skypilot.co/" target="_blank" rel="noopener noreferrer" class="inline-flex items-center justify-center p-2 rounded-full text-gray-600 hover:bg-gray-100 transition-colors duration-150 cursor-pointer" title="Slack" tabindex="0"><svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path transform="scale(0.85) translate(1.8, 1.8)" d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"></path></svg></a><a href="https://github.com/skypilot-org/skypilot/issues/new" target="_blank" rel="noopener noreferrer" class="inline-flex items-center justify-center p-2 rounded-full text-gray-600 hover:bg-gray-100 transition-colors duration-150 cursor-pointer" title="Leave Feedback" tabindex="0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-square w-5 h-5"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg></a><div class="border-l border-gray-200 h-6"></div><a class="inline-flex items-center justify-center p-2 rounded-full transition-colors duration-150 cursor-pointer text-gray-600 hover:bg-gray-100" title="Configuration" tabindex="0" href="/dashboard/config"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-settings w-5 h-5"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path><circle cx="12" cy="12" r="3"></circle></svg></a></div></div></div></div><div class="transition-all duration-200 ease-in-out min-h-screen" style="padding-top:56px"><main class="p-6"><div class="flex justify-center items-center h-64"><style data-emotion="css z01bqi animation-61bdi0">.css-z01bqi{display:inline-block;color:#1976d2;-webkit-animation:animation-61bdi0 1.4s linear infinite;animation:animation-61bdi0 1.4s linear infinite;}@-webkit-keyframes animation-61bdi0{0%{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);}100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}@keyframes animation-61bdi0{0%{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);}100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}</style><span class="MuiCircularProgress-root MuiCircularProgress-indeterminate MuiCircularProgress-colorPrimary css-z01bqi" style="width:40px;height:40px" role="progressbar"><style data-emotion="css 13o7eu2">.css-13o7eu2{display:block;}</style><svg class="MuiCircularProgress-svg css-13o7eu2" viewBox="22 22 44 44"><style data-emotion="css 14891ef animation-1p2h4ri">.css-14891ef{stroke:currentColor;stroke-dasharray:80px,200px;stroke-dashoffset:0;-webkit-animation:animation-1p2h4ri 1.4s ease-in-out infinite;animation:animation-1p2h4ri 1.4s ease-in-out infinite;}@-webkit-keyframes animation-1p2h4ri{0%{stroke-dasharray:1px,200px;stroke-dashoffset:0;}50%{stroke-dasharray:100px,200px;stroke-dashoffset:-15px;}100%{stroke-dasharray:100px,200px;stroke-dashoffset:-125px;}}@keyframes animation-1p2h4ri{0%{stroke-dasharray:1px,200px;stroke-dashoffset:0;}50%{stroke-dasharray:100px,200px;stroke-dashoffset:-15px;}100%{stroke-dasharray:100px,200px;stroke-dashoffset:-125px;}}</style><circle class="MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate css-14891ef" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg></span><span class="ml-2 text-gray-500">Loading workspaces...</span></div></main></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/workspaces","query":{},"buildId":"Q32Bxr2Pby5tFDW-y5TNg","assetPrefix":"/dashboard","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
sky/exceptions.py CHANGED
@@ -488,6 +488,16 @@ class ApiServerConnectionError(RuntimeError):
488
488
  f'Try: curl {server_url}/api/health')
489
489
 
490
490
 
491
+ class ApiServerAuthenticationError(RuntimeError):
492
+ """Raised when authentication is required for the API server."""
493
+
494
+ def __init__(self, server_url: str):
495
+ super().__init__(
496
+ f'Authentication required for SkyPilot API server at {server_url}. '
497
+ f'Please run:\n'
498
+ f' sky api login -e {server_url}')
499
+
500
+
491
501
  class APIVersionMismatchError(RuntimeError):
492
502
  """Raised when the API version mismatch."""
493
503
  pass
sky/global_user_state.py CHANGED
@@ -22,6 +22,7 @@ from sqlalchemy import orm
22
22
  from sqlalchemy.dialects import postgresql
23
23
  from sqlalchemy.dialects import sqlite
24
24
  from sqlalchemy.ext import declarative
25
+ import yaml
25
26
 
26
27
  from sky import models
27
28
  from sky import sky_logging
@@ -96,6 +97,12 @@ cluster_table = sqlalchemy.Table(
96
97
  sqlalchemy.Column('workspace',
97
98
  sqlalchemy.Text,
98
99
  server_default=constants.SKYPILOT_DEFAULT_WORKSPACE),
100
+ sqlalchemy.Column('last_creation_yaml',
101
+ sqlalchemy.Text,
102
+ server_default=None),
103
+ sqlalchemy.Column('last_creation_command',
104
+ sqlalchemy.Text,
105
+ server_default=None),
99
106
  )
100
107
 
101
108
  storage_table = sqlalchemy.Table(
@@ -133,6 +140,21 @@ cluster_history_table = sqlalchemy.Table(
133
140
  sqlalchemy.Column('user_hash', sqlalchemy.Text),
134
141
  )
135
142
 
143
+ ssh_key_table = sqlalchemy.Table(
144
+ 'ssh_key',
145
+ Base.metadata,
146
+ sqlalchemy.Column('user_hash', sqlalchemy.Text, primary_key=True),
147
+ sqlalchemy.Column('ssh_public_key', sqlalchemy.Text),
148
+ sqlalchemy.Column('ssh_private_key', sqlalchemy.Text),
149
+ )
150
+
151
+ cluster_yaml_table = sqlalchemy.Table(
152
+ 'cluster_yaml',
153
+ Base.metadata,
154
+ sqlalchemy.Column('cluster_name', sqlalchemy.Text, primary_key=True),
155
+ sqlalchemy.Column('yaml', sqlalchemy.Text),
156
+ )
157
+
136
158
 
137
159
  def _glob_to_similar(glob_pattern):
138
160
  """Converts a glob pattern to a PostgreSQL LIKE pattern."""
@@ -270,6 +292,19 @@ def create_table():
270
292
  default_statement='DEFAULT \'default\'',
271
293
  value_to_replace_existing_entries=constants.
272
294
  SKYPILOT_DEFAULT_WORKSPACE)
295
+ db_utils.add_column_to_table_sqlalchemy(
296
+ session,
297
+ 'clusters',
298
+ 'last_creation_yaml',
299
+ sqlalchemy.Text(),
300
+ default_statement='DEFAULT NULL',
301
+ )
302
+ db_utils.add_column_to_table_sqlalchemy(
303
+ session,
304
+ 'clusters',
305
+ 'last_creation_command',
306
+ sqlalchemy.Text(),
307
+ default_statement='DEFAULT NULL')
273
308
  session.commit()
274
309
 
275
310
 
@@ -318,7 +353,8 @@ def add_or_update_cluster(cluster_name: str,
318
353
  requested_resources: Optional[Set[Any]],
319
354
  ready: bool,
320
355
  is_launch: bool = True,
321
- config_hash: Optional[str] = None):
356
+ config_hash: Optional[str] = None,
357
+ task_config: Optional[Dict[str, Any]] = None):
322
358
  """Adds or updates cluster_name -> cluster_handle mapping.
323
359
 
324
360
  Args:
@@ -329,6 +365,8 @@ def add_or_update_cluster(cluster_name: str,
329
365
  be marked as INIT, otherwise it will be marked as UP.
330
366
  is_launch: if the cluster is firstly launched. If True, the launched_at
331
367
  and last_use will be updated. Otherwise, use the old value.
368
+ config_hash: Configuration hash for the cluster.
369
+ task_config: The config of the task being launched.
332
370
  """
333
371
  # TODO(zhwu): have to be imported here to avoid circular import.
334
372
  from sky import skypilot_config # pylint: disable=import-outside-toplevel
@@ -404,6 +442,13 @@ def add_or_update_cluster(cluster_name: str,
404
442
  conditional_values.update({
405
443
  'workspace': active_workspace,
406
444
  })
445
+ if (is_launch and not cluster_row or
446
+ cluster_row.status != status_lib.ClusterStatus.UP.value):
447
+ conditional_values.update({
448
+ 'last_creation_yaml': common_utils.dump_yaml_str(task_config)
449
+ if task_config else None,
450
+ 'last_creation_command': last_use,
451
+ })
407
452
 
408
453
  if (_SQLALCHEMY_ENGINE.dialect.name ==
409
454
  db_utils.SQLAlchemyDialect.SQLITE.value):
@@ -790,6 +835,8 @@ def get_cluster_from_name(
790
835
  'user_name': get_user(user_hash).name,
791
836
  'config_hash': row.config_hash,
792
837
  'workspace': row.workspace,
838
+ 'last_creation_yaml': row.last_creation_yaml,
839
+ 'last_creation_command': row.last_creation_command,
793
840
  }
794
841
 
795
842
  return record
@@ -822,6 +869,8 @@ def get_clusters() -> List[Dict[str, Any]]:
822
869
  'user_name': get_user(user_hash).name,
823
870
  'config_hash': row.config_hash,
824
871
  'workspace': row.workspace,
872
+ 'last_creation_yaml': row.last_creation_yaml,
873
+ 'last_creation_command': row.last_creation_command,
825
874
  }
826
875
 
827
876
  records.append(record)
@@ -1049,3 +1098,102 @@ def get_storage() -> List[Dict[str, Any]]:
1049
1098
  'status': status_lib.StorageStatus[row.status],
1050
1099
  })
1051
1100
  return records
1101
+
1102
+
1103
+ def get_ssh_keys(user_hash: str) -> Tuple[str, str, bool]:
1104
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1105
+ row = session.query(ssh_key_table).filter_by(
1106
+ user_hash=user_hash).first()
1107
+ if row:
1108
+ return row.ssh_public_key, row.ssh_private_key, True
1109
+ return '', '', False
1110
+
1111
+
1112
+ def set_ssh_keys(user_hash: str, ssh_public_key: str, ssh_private_key: str):
1113
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1114
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
1115
+ db_utils.SQLAlchemyDialect.SQLITE.value):
1116
+ insert_func = sqlite.insert
1117
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
1118
+ db_utils.SQLAlchemyDialect.POSTGRESQL.value):
1119
+ insert_func = postgresql.insert
1120
+ else:
1121
+ raise ValueError('Unsupported database dialect')
1122
+ insert_stmnt = insert_func(ssh_key_table).values(
1123
+ user_hash=user_hash,
1124
+ ssh_public_key=ssh_public_key,
1125
+ ssh_private_key=ssh_private_key)
1126
+ do_update_stmt = insert_stmnt.on_conflict_do_update(
1127
+ index_elements=[ssh_key_table.c.user_hash],
1128
+ set_={
1129
+ ssh_key_table.c.ssh_public_key: ssh_public_key,
1130
+ ssh_key_table.c.ssh_private_key: ssh_private_key
1131
+ })
1132
+ session.execute(do_update_stmt)
1133
+ session.commit()
1134
+
1135
+
1136
+ def get_cluster_yaml_str(cluster_yaml_path: Optional[str]) -> Optional[str]:
1137
+ """Get the cluster yaml from the database or the local file system.
1138
+ If the cluster yaml is not in the database, check if it exists on the
1139
+ local file system and migrate it to the database.
1140
+
1141
+ It is assumed that the cluster yaml file is named as <cluster_name>.yml.
1142
+ """
1143
+ if cluster_yaml_path is None:
1144
+ raise ValueError('Attempted to read a None YAML.')
1145
+ cluster_file_name = os.path.basename(cluster_yaml_path)
1146
+ cluster_name, _ = os.path.splitext(cluster_file_name)
1147
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1148
+ row = session.query(cluster_yaml_table).filter_by(
1149
+ cluster_name=cluster_name).first()
1150
+ if row is None:
1151
+ # If the cluster yaml is not in the database, check if it exists
1152
+ # on the local file system and migrate it to the database.
1153
+ # TODO(syang): remove this check once we have a way to migrate the
1154
+ # cluster from file to database. Remove on v0.12.0.
1155
+ if cluster_yaml_path is not None and os.path.exists(cluster_yaml_path):
1156
+ with open(cluster_yaml_path, 'r', encoding='utf-8') as f:
1157
+ yaml_str = f.read()
1158
+ set_cluster_yaml(cluster_name, yaml_str)
1159
+ return yaml_str
1160
+ return None
1161
+ return row.yaml
1162
+
1163
+
1164
+ def get_cluster_yaml_dict(cluster_yaml_path: Optional[str]) -> Dict[str, Any]:
1165
+ """Get the cluster yaml as a dictionary from the database.
1166
+
1167
+ It is assumed that the cluster yaml file is named as <cluster_name>.yml.
1168
+ """
1169
+ yaml_str = get_cluster_yaml_str(cluster_yaml_path)
1170
+ if yaml_str is None:
1171
+ raise ValueError(f'Cluster yaml {cluster_yaml_path} not found.')
1172
+ return yaml.safe_load(yaml_str)
1173
+
1174
+
1175
+ def set_cluster_yaml(cluster_name: str, yaml_str: str) -> None:
1176
+ """Set the cluster yaml in the database."""
1177
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1178
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
1179
+ db_utils.SQLAlchemyDialect.SQLITE.value):
1180
+ insert_func = sqlite.insert
1181
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
1182
+ db_utils.SQLAlchemyDialect.POSTGRESQL.value):
1183
+ insert_func = postgresql.insert
1184
+ else:
1185
+ raise ValueError('Unsupported database dialect')
1186
+ insert_stmnt = insert_func(cluster_yaml_table).values(
1187
+ cluster_name=cluster_name, yaml=yaml_str)
1188
+ do_update_stmt = insert_stmnt.on_conflict_do_update(
1189
+ index_elements=[cluster_yaml_table.c.cluster_name],
1190
+ set_={cluster_yaml_table.c.yaml: yaml_str})
1191
+ session.execute(do_update_stmt)
1192
+ session.commit()
1193
+
1194
+
1195
+ def remove_cluster_yaml(cluster_name: str):
1196
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1197
+ session.query(cluster_yaml_table).filter_by(
1198
+ cluster_name=cluster_name).delete()
1199
+ session.commit()
sky/jobs/constants.py CHANGED
@@ -47,7 +47,7 @@ JOBS_CLUSTER_NAME_PREFIX_LENGTH = 25
47
47
  # The version of the lib files that jobs/utils use. Whenever there is an API
48
48
  # change for the jobs/utils, we need to bump this version and update
49
49
  # job.utils.ManagedJobCodeGen to handle the version update.
50
- MANAGED_JOBS_VERSION = 4
50
+ MANAGED_JOBS_VERSION = 5
51
51
 
52
52
  # The command for setting up the jobs dashboard on the controller. It firstly
53
53
  # checks if the systemd services are available, and if not (e.g., Kubernetes
sky/jobs/state.py CHANGED
@@ -121,7 +121,8 @@ def create_table(cursor, conn):
121
121
  env_file_path TEXT,
122
122
  user_hash TEXT,
123
123
  workspace TEXT DEFAULT NULL,
124
- priority INTEGER DEFAULT 500)""")
124
+ priority INTEGER DEFAULT 500,
125
+ entrypoint TEXT DEFAULT NULL)""")
125
126
 
126
127
  db_utils.add_column_to_table(cursor, conn, 'job_info', 'schedule_state',
127
128
  'TEXT')
@@ -151,6 +152,7 @@ def create_table(cursor, conn):
151
152
  'INTEGER',
152
153
  value_to_replace_existing_entries=500)
153
154
 
155
+ db_utils.add_column_to_table(cursor, conn, 'job_info', 'entrypoint', 'TEXT')
154
156
  conn.commit()
155
157
 
156
158
 
@@ -209,6 +211,7 @@ columns = [
209
211
  'user_hash',
210
212
  'workspace',
211
213
  'priority',
214
+ 'entrypoint',
212
215
  ]
213
216
 
214
217
 
@@ -412,14 +415,15 @@ class ManagedJobScheduleState(enum.Enum):
412
415
 
413
416
 
414
417
  # === Status transition functions ===
415
- def set_job_info(job_id: int, name: str, workspace: str):
418
+ def set_job_info(job_id: int, name: str, workspace: str, entrypoint: str):
416
419
  with db_utils.safe_cursor(_DB_PATH) as cursor:
417
420
  cursor.execute(
418
421
  """\
419
422
  INSERT INTO job_info
420
- (spot_job_id, name, schedule_state, workspace)
421
- VALUES (?, ?, ?, ?)""",
422
- (job_id, name, ManagedJobScheduleState.INACTIVE.value, workspace))
423
+ (spot_job_id, name, schedule_state, workspace, entrypoint)
424
+ VALUES (?, ?, ?, ?, ?)""",
425
+ (job_id, name, ManagedJobScheduleState.INACTIVE.value, workspace,
426
+ entrypoint))
423
427
 
424
428
 
425
429
  def set_pending(job_id: int, task_id: int, task_name: str, resources_str: str):
@@ -1008,6 +1012,21 @@ def get_managed_jobs(job_id: Optional[int] = None) -> List[Dict[str, Any]]:
1008
1012
  job_dict['schedule_state'])
1009
1013
  if job_dict['job_name'] is None:
1010
1014
  job_dict['job_name'] = job_dict['task_name']
1015
+
1016
+ # Add YAML content and command for managed jobs
1017
+ dag_yaml_path = job_dict.get('dag_yaml_path')
1018
+ if dag_yaml_path:
1019
+ try:
1020
+ with open(dag_yaml_path, 'r', encoding='utf-8') as f:
1021
+ job_dict['dag_yaml'] = f.read()
1022
+ except (FileNotFoundError, IOError, OSError):
1023
+ job_dict['dag_yaml'] = None
1024
+
1025
+ # Generate a command that could be used to launch this job
1026
+ # Format: sky jobs launch <yaml_path>
1027
+ else:
1028
+ job_dict['dag_yaml'] = None
1029
+
1011
1030
  jobs.append(job_dict)
1012
1031
  return jobs
1013
1032
 
sky/jobs/utils.py CHANGED
@@ -1388,13 +1388,15 @@ class ManagedJobCodeGen:
1388
1388
 
1389
1389
  @classmethod
1390
1390
  def set_pending(cls, job_id: int, managed_job_dag: 'dag_lib.Dag',
1391
- workspace) -> str:
1391
+ workspace: str, entrypoint: str) -> str:
1392
1392
  dag_name = managed_job_dag.name
1393
1393
  # Add the managed job to queue table.
1394
1394
  code = textwrap.dedent(f"""\
1395
1395
  set_job_info_kwargs = {{'workspace': {workspace!r}}}
1396
1396
  if managed_job_version < 4:
1397
1397
  set_job_info_kwargs = {{}}
1398
+ if managed_job_version >= 5:
1399
+ set_job_info_kwargs['entrypoint'] = {entrypoint!r}
1398
1400
  managed_job_state.set_job_info(
1399
1401
  {job_id}, {dag_name!r}, **set_job_info_kwargs)
1400
1402
  """)
@@ -15,6 +15,7 @@ from urllib.parse import urlparse
15
15
  import sky
16
16
  from sky import clouds
17
17
  from sky import exceptions
18
+ from sky import global_user_state
18
19
  from sky import models
19
20
  from sky import sky_logging
20
21
  from sky import skypilot_config
@@ -2810,7 +2811,7 @@ def set_autodown_annotations(handle: 'backends.CloudVmRayResourceHandle',
2810
2811
  tags = {
2811
2812
  provision_constants.TAG_RAY_CLUSTER_NAME: handle.cluster_name_on_cloud,
2812
2813
  }
2813
- ray_config = common_utils.read_yaml(handle.cluster_yaml)
2814
+ ray_config = global_user_state.get_cluster_yaml_dict(handle.cluster_yaml)
2814
2815
  provider_config = ray_config['provider']
2815
2816
  namespace = get_namespace_from_config(provider_config)
2816
2817
  context = get_context_from_config(provider_config)
@@ -15,6 +15,7 @@ import colorama
15
15
  import sky
16
16
  from sky import clouds
17
17
  from sky import exceptions
18
+ from sky import global_user_state
18
19
  from sky import provision
19
20
  from sky import sky_logging
20
21
  from sky import skypilot_config
@@ -118,7 +119,7 @@ def bulk_provision(
118
119
  Cloud specific exceptions: If the provisioning process failed, cloud-
119
120
  specific exceptions will be raised by the cloud APIs.
120
121
  """
121
- original_config = common_utils.read_yaml(cluster_yaml)
122
+ original_config = global_user_state.get_cluster_yaml_dict(cluster_yaml)
122
123
  head_node_type = original_config['head_node_type']
123
124
  bootstrap_config = provision_common.ProvisionConfig(
124
125
  provider_config=original_config['provider'],
@@ -413,9 +414,11 @@ def wait_for_ssh(cluster_info: provision_common.ClusterInfo,
413
414
 
414
415
  def _post_provision_setup(
415
416
  cloud_name: str, cluster_name: resources_utils.ClusterName,
416
- cluster_yaml: str, provision_record: provision_common.ProvisionRecord,
417
+ handle_cluster_yaml: str,
418
+ provision_record: provision_common.ProvisionRecord,
417
419
  custom_resource: Optional[str]) -> provision_common.ClusterInfo:
418
- config_from_yaml = common_utils.read_yaml(cluster_yaml)
420
+ config_from_yaml = global_user_state.get_cluster_yaml_dict(
421
+ handle_cluster_yaml)
419
422
  provider_config = config_from_yaml.get('provider')
420
423
  cluster_info = provision.get_cluster_info(cloud_name,
421
424
  provision_record.region,
@@ -446,7 +449,7 @@ def _post_provision_setup(
446
449
  # TODO(suquark): Move wheel build here in future PRs.
447
450
  # We don't set docker_user here, as we are configuring the VM itself.
448
451
  ssh_credentials = backend_utils.ssh_credential_from_yaml(
449
- cluster_yaml, ssh_user=cluster_info.ssh_user)
452
+ handle_cluster_yaml, ssh_user=cluster_info.ssh_user)
450
453
  docker_config = config_from_yaml.get('docker', {})
451
454
 
452
455
  with rich_utils.safe_status(
@@ -657,7 +660,8 @@ def _post_provision_setup(
657
660
  @timeline.event
658
661
  def post_provision_runtime_setup(
659
662
  cloud_name: str, cluster_name: resources_utils.ClusterName,
660
- cluster_yaml: str, provision_record: provision_common.ProvisionRecord,
663
+ handle_cluster_yaml: str,
664
+ provision_record: provision_common.ProvisionRecord,
661
665
  custom_resource: Optional[str],
662
666
  log_dir: str) -> provision_common.ClusterInfo:
663
667
  """Run internal setup commands after provisioning and before user setup.
@@ -675,11 +679,12 @@ def post_provision_runtime_setup(
675
679
  with provision_logging.setup_provision_logging(log_dir):
676
680
  try:
677
681
  logger.debug(_TITLE.format('System Setup After Provision'))
678
- return _post_provision_setup(cloud_name,
679
- cluster_name,
680
- cluster_yaml=cluster_yaml,
681
- provision_record=provision_record,
682
- custom_resource=custom_resource)
682
+ return _post_provision_setup(
683
+ cloud_name,
684
+ cluster_name,
685
+ handle_cluster_yaml=handle_cluster_yaml,
686
+ provision_record=provision_record,
687
+ custom_resource=custom_resource)
683
688
  except Exception: # pylint: disable=broad-except
684
689
  logger.error(
685
690
  ux_utils.error_message(
sky/serve/controller.py CHANGED
@@ -42,12 +42,13 @@ class SkyServeController:
42
42
  """
43
43
 
44
44
  def __init__(self, service_name: str, service_spec: serve.SkyServiceSpec,
45
- task_yaml: str, host: str, port: int) -> None:
45
+ service_task_yaml: str, host: str, port: int) -> None:
46
46
  self._service_name = service_name
47
47
  self._replica_manager: replica_managers.ReplicaManager = (
48
- replica_managers.SkyPilotReplicaManager(service_name=service_name,
49
- spec=service_spec,
50
- task_yaml_path=task_yaml))
48
+ replica_managers.SkyPilotReplicaManager(
49
+ service_name=service_name,
50
+ spec=service_spec,
51
+ service_task_yaml_path=service_task_yaml))
51
52
  self._autoscaler: autoscalers.Autoscaler = (
52
53
  autoscalers.Autoscaler.from_spec(service_name, service_spec))
53
54
  self._host = host
@@ -240,7 +241,9 @@ class SkyServeController:
240
241
  # TODO(tian): Probably we should support service that will stop the VM in
241
242
  # specific time period.
242
243
  def run_controller(service_name: str, service_spec: serve.SkyServiceSpec,
243
- task_yaml: str, controller_host: str, controller_port: int):
244
- controller = SkyServeController(service_name, service_spec, task_yaml,
245
- controller_host, controller_port)
244
+ service_task_yaml: str, controller_host: str,
245
+ controller_port: int):
246
+ controller = SkyServeController(service_name, service_spec,
247
+ service_task_yaml, controller_host,
248
+ controller_port)
246
249
  controller.run()
@@ -58,7 +58,7 @@ _MAX_NUM_LAUNCH = psutil.cpu_count() * 2
58
58
  # TODO(tian): Combine this with
59
59
  # sky/spot/recovery_strategy.py::StrategyExecutor::launch
60
60
  def launch_cluster(replica_id: int,
61
- task_yaml_path: str,
61
+ service_task_yaml_path: str,
62
62
  cluster_name: str,
63
63
  resources_override: Optional[Dict[str, Any]] = None,
64
64
  retry_until_up: bool = True,
@@ -78,7 +78,8 @@ def launch_cluster(replica_id: int,
78
78
  f'{cluster_name} with resources override: '
79
79
  f'{resources_override}')
80
80
  try:
81
- config = common_utils.read_yaml(os.path.expanduser(task_yaml_path))
81
+ config = common_utils.read_yaml(
82
+ os.path.expanduser(service_task_yaml_path))
82
83
  task = sky.Task.from_yaml_config(config)
83
84
  if resources_override is not None:
84
85
  resources = task.resources
@@ -173,9 +174,9 @@ def terminate_cluster(cluster_name: str,
173
174
  time.sleep(gap_seconds)
174
175
 
175
176
 
176
- def _get_resources_ports(task_yaml: str) -> str:
177
+ def _get_resources_ports(service_task_yaml_path: str) -> str:
177
178
  """Get the resources ports used by the task."""
178
- task = sky.Task.from_yaml(task_yaml)
179
+ task = sky.Task.from_yaml(service_task_yaml_path)
179
180
  # Already checked all ports are valid in sky.serve.core.up
180
181
  assert task.resources, task
181
182
  assert task.service is not None, task
@@ -183,7 +184,7 @@ def _get_resources_ports(task_yaml: str) -> str:
183
184
  return task.service.ports
184
185
 
185
186
 
186
- def _should_use_spot(task_yaml: str,
187
+ def _should_use_spot(service_task_yaml_path: str,
187
188
  resource_override: Optional[Dict[str, Any]]) -> bool:
188
189
  """Get whether the task should use spot."""
189
190
  if resource_override is not None:
@@ -191,7 +192,7 @@ def _should_use_spot(task_yaml: str,
191
192
  if use_spot_override is not None:
192
193
  assert isinstance(use_spot_override, bool)
193
194
  return use_spot_override
194
- task = sky.Task.from_yaml(task_yaml)
195
+ task = sky.Task.from_yaml(service_task_yaml_path)
195
196
  spot_use_resources = [
196
197
  resources for resources in task.resources if resources.use_spot
197
198
  ]
@@ -634,10 +635,10 @@ class SkyPilotReplicaManager(ReplicaManager):
634
635
  """
635
636
 
636
637
  def __init__(self, service_name: str, spec: 'service_spec.SkyServiceSpec',
637
- task_yaml_path: str) -> None:
638
+ service_task_yaml_path: str) -> None:
638
639
  super().__init__(service_name, spec)
639
- self._task_yaml_path = task_yaml_path
640
- task = sky.Task.from_yaml(task_yaml_path)
640
+ self.service_task_yaml_path = service_task_yaml_path
641
+ task = sky.Task.from_yaml(service_task_yaml_path)
641
642
  self._spot_placer: Optional[spot_placer.SpotPlacer] = (
642
643
  spot_placer.SpotPlacer.from_task(spec, task))
643
644
  # TODO(tian): Store launch/down pid in the replica table, to make the
@@ -714,7 +715,8 @@ class SkyPilotReplicaManager(ReplicaManager):
714
715
  self._service_name, replica_id)
715
716
  log_file_name = serve_utils.generate_replica_launch_log_file_name(
716
717
  self._service_name, replica_id)
717
- use_spot = _should_use_spot(self._task_yaml_path, resources_override)
718
+ use_spot = _should_use_spot(self.service_task_yaml_path,
719
+ resources_override)
718
720
  retry_until_up = True
719
721
  location = None
720
722
  if use_spot and self._spot_placer is not None:
@@ -742,10 +744,10 @@ class SkyPilotReplicaManager(ReplicaManager):
742
744
  launch_cluster,
743
745
  log_file_name,
744
746
  ).run,
745
- args=(replica_id, self._task_yaml_path, cluster_name,
747
+ args=(replica_id, self.service_task_yaml_path, cluster_name,
746
748
  resources_override, retry_until_up),
747
749
  )
748
- replica_port = _get_resources_ports(self._task_yaml_path)
750
+ replica_port = _get_resources_ports(self.service_task_yaml_path)
749
751
 
750
752
  info = ReplicaInfo(replica_id, cluster_name, replica_port, use_spot,
751
753
  location, self.latest_version, resources_override)
@@ -1290,11 +1292,11 @@ class SkyPilotReplicaManager(ReplicaManager):
1290
1292
  logger.error(f'Invalid version: {version}, '
1291
1293
  f'latest version: {self.latest_version}')
1292
1294
  return
1293
- task_yaml_path = serve_utils.generate_task_yaml_file_name(
1295
+ service_task_yaml_path = serve_utils.generate_task_yaml_file_name(
1294
1296
  self._service_name, version)
1295
1297
  serve_state.add_or_update_version(self._service_name, version, spec)
1296
1298
  self.latest_version = version
1297
- self._task_yaml_path = task_yaml_path
1299
+ self.service_task_yaml_path = service_task_yaml_path
1298
1300
  self._update_mode = update_mode
1299
1301
 
1300
1302
  # Reuse all replicas that have the same config as the new version
@@ -1302,7 +1304,8 @@ class SkyPilotReplicaManager(ReplicaManager):
1302
1304
  # the latest version. This can significantly improve the speed
1303
1305
  # for updating an existing service with only config changes to the
1304
1306
  # service specs, e.g. scale down the service.
1305
- new_config = common_utils.read_yaml(os.path.expanduser(task_yaml_path))
1307
+ new_config = common_utils.read_yaml(
1308
+ os.path.expanduser(service_task_yaml_path))
1306
1309
  # Always create new replicas and scale down old ones when file_mounts
1307
1310
  # are not empty.
1308
1311
  if new_config.get('file_mounts', None) != {}:
@@ -1313,10 +1316,11 @@ class SkyPilotReplicaManager(ReplicaManager):
1313
1316
  for info in replica_infos:
1314
1317
  if info.version < version and not info.is_terminal:
1315
1318
  # Assume user does not change the yaml file on the controller.
1316
- old_task_yaml_path = serve_utils.generate_task_yaml_file_name(
1317
- self._service_name, info.version)
1319
+ old_service_task_yaml_path = (
1320
+ serve_utils.generate_task_yaml_file_name(
1321
+ self._service_name, info.version))
1318
1322
  old_config = common_utils.read_yaml(
1319
- os.path.expanduser(old_task_yaml_path))
1323
+ os.path.expanduser(old_service_task_yaml_path))
1320
1324
  for key in ['service']:
1321
1325
  old_config.pop(key)
1322
1326
  # Bump replica version if all fields except for service are