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

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

Potentially problematic release.


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

Files changed (231) hide show
  1. sky/__init__.py +6 -2
  2. sky/adaptors/aws.py +25 -7
  3. sky/adaptors/coreweave.py +278 -0
  4. sky/adaptors/kubernetes.py +64 -0
  5. sky/adaptors/shadeform.py +89 -0
  6. sky/admin_policy.py +20 -0
  7. sky/authentication.py +59 -149
  8. sky/backends/backend_utils.py +104 -63
  9. sky/backends/cloud_vm_ray_backend.py +84 -39
  10. sky/catalog/data_fetchers/fetch_runpod.py +698 -0
  11. sky/catalog/data_fetchers/fetch_shadeform.py +142 -0
  12. sky/catalog/kubernetes_catalog.py +24 -28
  13. sky/catalog/runpod_catalog.py +5 -1
  14. sky/catalog/shadeform_catalog.py +165 -0
  15. sky/check.py +25 -13
  16. sky/client/cli/command.py +335 -86
  17. sky/client/cli/flags.py +4 -2
  18. sky/client/cli/table_utils.py +17 -9
  19. sky/client/sdk.py +59 -12
  20. sky/cloud_stores.py +73 -0
  21. sky/clouds/__init__.py +2 -0
  22. sky/clouds/aws.py +71 -16
  23. sky/clouds/azure.py +12 -5
  24. sky/clouds/cloud.py +19 -9
  25. sky/clouds/cudo.py +12 -5
  26. sky/clouds/do.py +4 -1
  27. sky/clouds/fluidstack.py +12 -5
  28. sky/clouds/gcp.py +12 -5
  29. sky/clouds/hyperbolic.py +12 -5
  30. sky/clouds/ibm.py +12 -5
  31. sky/clouds/kubernetes.py +62 -25
  32. sky/clouds/lambda_cloud.py +12 -5
  33. sky/clouds/nebius.py +12 -5
  34. sky/clouds/oci.py +12 -5
  35. sky/clouds/paperspace.py +4 -1
  36. sky/clouds/primeintellect.py +4 -1
  37. sky/clouds/runpod.py +12 -5
  38. sky/clouds/scp.py +12 -5
  39. sky/clouds/seeweb.py +4 -1
  40. sky/clouds/shadeform.py +400 -0
  41. sky/clouds/ssh.py +4 -2
  42. sky/clouds/vast.py +12 -5
  43. sky/clouds/vsphere.py +4 -1
  44. sky/core.py +12 -11
  45. sky/dashboard/out/404.html +1 -1
  46. sky/dashboard/out/_next/static/chunks/1141-e6aa9ab418717c59.js +11 -0
  47. sky/dashboard/out/_next/static/chunks/{1871-49141c317f3a9020.js → 1871-74503c8e80fd253b.js} +1 -1
  48. sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
  49. sky/dashboard/out/_next/static/chunks/2755.fff53c4a3fcae910.js +26 -0
  50. sky/dashboard/out/_next/static/chunks/3294.72362fa129305b19.js +1 -0
  51. sky/dashboard/out/_next/static/chunks/{3785.a19328ba41517b8b.js → 3785.ad6adaa2a0fa9768.js} +1 -1
  52. sky/dashboard/out/_next/static/chunks/{4725.10f7a9a5d3ea8208.js → 4725.a830b5c9e7867c92.js} +1 -1
  53. sky/dashboard/out/_next/static/chunks/6856-ef8ba11f96d8c4a3.js +1 -0
  54. sky/dashboard/out/_next/static/chunks/6990-32b6e2d3822301fa.js +1 -0
  55. sky/dashboard/out/_next/static/chunks/7615-3301e838e5f25772.js +1 -0
  56. sky/dashboard/out/_next/static/chunks/8969-1e4613c651bf4051.js +1 -0
  57. sky/dashboard/out/_next/static/chunks/9025.fa408f3242e9028d.js +6 -0
  58. sky/dashboard/out/_next/static/chunks/9353-cff34f7e773b2e2b.js +1 -0
  59. sky/dashboard/out/_next/static/chunks/9360.7310982cf5a0dc79.js +31 -0
  60. sky/dashboard/out/_next/static/chunks/pages/{_app-ce361c6959bc2001.js → _app-bde01e4a2beec258.js} +1 -1
  61. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-c736ead69c2d86ec.js +16 -0
  62. sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-477555ab7c0b13d8.js → [cluster]-a37d2063af475a1c.js} +1 -1
  63. sky/dashboard/out/_next/static/chunks/pages/{clusters-2f61f65487f6d8ff.js → clusters-d44859594e6f8064.js} +1 -1
  64. sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-553b8b5cb65e100b.js → [context]-c0b5935149902e6f.js} +1 -1
  65. sky/dashboard/out/_next/static/chunks/pages/{infra-910a22500c50596f.js → infra-aed0ea19df7cf961.js} +1 -1
  66. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-5796e8d6aea291a0.js +16 -0
  67. sky/dashboard/out/_next/static/chunks/pages/jobs/pools/{[pool]-bc979970c247d8f3.js → [pool]-6edeb7d06032adfc.js} +2 -2
  68. sky/dashboard/out/_next/static/chunks/pages/{jobs-a35a9dc3c5ccd657.js → jobs-479dde13399cf270.js} +1 -1
  69. sky/dashboard/out/_next/static/chunks/pages/{users-98d2ed979084162a.js → users-5ab3b907622cf0fe.js} +1 -1
  70. sky/dashboard/out/_next/static/chunks/pages/{volumes-835d14ba94808f79.js → volumes-b84b948ff357c43e.js} +1 -1
  71. sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-e8688c35c06f0ac5.js → [name]-c5a3eeee1c218af1.js} +1 -1
  72. sky/dashboard/out/_next/static/chunks/pages/{workspaces-69c80d677d3c2949.js → workspaces-22b23febb3e89ce1.js} +1 -1
  73. sky/dashboard/out/_next/static/chunks/webpack-2679be77fc08a2f8.js +1 -0
  74. sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
  75. sky/dashboard/out/_next/static/zB0ed6ge_W1MDszVHhijS/_buildManifest.js +1 -0
  76. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  77. sky/dashboard/out/clusters/[cluster].html +1 -1
  78. sky/dashboard/out/clusters.html +1 -1
  79. sky/dashboard/out/config.html +1 -1
  80. sky/dashboard/out/index.html +1 -1
  81. sky/dashboard/out/infra/[context].html +1 -1
  82. sky/dashboard/out/infra.html +1 -1
  83. sky/dashboard/out/jobs/[job].html +1 -1
  84. sky/dashboard/out/jobs/pools/[pool].html +1 -1
  85. sky/dashboard/out/jobs.html +1 -1
  86. sky/dashboard/out/users.html +1 -1
  87. sky/dashboard/out/volumes.html +1 -1
  88. sky/dashboard/out/workspace/new.html +1 -1
  89. sky/dashboard/out/workspaces/[name].html +1 -1
  90. sky/dashboard/out/workspaces.html +1 -1
  91. sky/data/data_utils.py +92 -1
  92. sky/data/mounting_utils.py +143 -19
  93. sky/data/storage.py +168 -11
  94. sky/exceptions.py +13 -1
  95. sky/execution.py +13 -0
  96. sky/global_user_state.py +189 -113
  97. sky/jobs/client/sdk.py +32 -10
  98. sky/jobs/client/sdk_async.py +9 -3
  99. sky/jobs/constants.py +3 -1
  100. sky/jobs/controller.py +164 -192
  101. sky/jobs/file_content_utils.py +80 -0
  102. sky/jobs/log_gc.py +201 -0
  103. sky/jobs/recovery_strategy.py +59 -82
  104. sky/jobs/scheduler.py +20 -9
  105. sky/jobs/server/core.py +105 -23
  106. sky/jobs/server/server.py +40 -28
  107. sky/jobs/server/utils.py +32 -11
  108. sky/jobs/state.py +588 -110
  109. sky/jobs/utils.py +442 -209
  110. sky/logs/agent.py +1 -1
  111. sky/metrics/utils.py +45 -6
  112. sky/optimizer.py +1 -1
  113. sky/provision/__init__.py +7 -0
  114. sky/provision/aws/instance.py +2 -1
  115. sky/provision/azure/instance.py +2 -1
  116. sky/provision/common.py +2 -0
  117. sky/provision/cudo/instance.py +2 -1
  118. sky/provision/do/instance.py +2 -1
  119. sky/provision/fluidstack/instance.py +4 -3
  120. sky/provision/gcp/instance.py +2 -1
  121. sky/provision/hyperbolic/instance.py +2 -1
  122. sky/provision/instance_setup.py +10 -2
  123. sky/provision/kubernetes/constants.py +0 -1
  124. sky/provision/kubernetes/instance.py +222 -89
  125. sky/provision/kubernetes/network.py +12 -8
  126. sky/provision/kubernetes/utils.py +114 -53
  127. sky/provision/kubernetes/volume.py +5 -4
  128. sky/provision/lambda_cloud/instance.py +2 -1
  129. sky/provision/nebius/instance.py +2 -1
  130. sky/provision/oci/instance.py +2 -1
  131. sky/provision/paperspace/instance.py +2 -1
  132. sky/provision/provisioner.py +11 -2
  133. sky/provision/runpod/instance.py +2 -1
  134. sky/provision/scp/instance.py +2 -1
  135. sky/provision/seeweb/instance.py +3 -3
  136. sky/provision/shadeform/__init__.py +11 -0
  137. sky/provision/shadeform/config.py +12 -0
  138. sky/provision/shadeform/instance.py +351 -0
  139. sky/provision/shadeform/shadeform_utils.py +83 -0
  140. sky/provision/vast/instance.py +2 -1
  141. sky/provision/vsphere/instance.py +2 -1
  142. sky/resources.py +1 -1
  143. sky/schemas/api/responses.py +9 -5
  144. sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
  145. sky/schemas/db/spot_jobs/004_job_file_contents.py +42 -0
  146. sky/schemas/db/spot_jobs/005_logs_gc.py +38 -0
  147. sky/schemas/generated/jobsv1_pb2.py +52 -52
  148. sky/schemas/generated/jobsv1_pb2.pyi +4 -2
  149. sky/schemas/generated/managed_jobsv1_pb2.py +39 -35
  150. sky/schemas/generated/managed_jobsv1_pb2.pyi +21 -5
  151. sky/serve/client/impl.py +11 -3
  152. sky/serve/replica_managers.py +5 -2
  153. sky/serve/serve_utils.py +9 -2
  154. sky/serve/server/impl.py +7 -2
  155. sky/serve/server/server.py +18 -15
  156. sky/serve/service.py +2 -2
  157. sky/server/auth/oauth2_proxy.py +2 -5
  158. sky/server/common.py +31 -28
  159. sky/server/constants.py +5 -1
  160. sky/server/daemons.py +27 -19
  161. sky/server/requests/executor.py +138 -74
  162. sky/server/requests/payloads.py +9 -1
  163. sky/server/requests/preconditions.py +13 -10
  164. sky/server/requests/request_names.py +120 -0
  165. sky/server/requests/requests.py +485 -153
  166. sky/server/requests/serializers/decoders.py +26 -13
  167. sky/server/requests/serializers/encoders.py +56 -11
  168. sky/server/requests/threads.py +106 -0
  169. sky/server/rest.py +70 -18
  170. sky/server/server.py +283 -104
  171. sky/server/stream_utils.py +233 -59
  172. sky/server/uvicorn.py +18 -17
  173. sky/setup_files/alembic.ini +4 -0
  174. sky/setup_files/dependencies.py +32 -13
  175. sky/sky_logging.py +0 -2
  176. sky/skylet/constants.py +30 -7
  177. sky/skylet/events.py +7 -0
  178. sky/skylet/log_lib.py +8 -2
  179. sky/skylet/log_lib.pyi +1 -1
  180. sky/skylet/services.py +26 -13
  181. sky/skylet/subprocess_daemon.py +103 -29
  182. sky/skypilot_config.py +87 -75
  183. sky/ssh_node_pools/server.py +9 -8
  184. sky/task.py +67 -54
  185. sky/templates/kubernetes-ray.yml.j2 +8 -1
  186. sky/templates/nebius-ray.yml.j2 +1 -0
  187. sky/templates/shadeform-ray.yml.j2 +72 -0
  188. sky/templates/websocket_proxy.py +142 -12
  189. sky/users/permission.py +8 -1
  190. sky/utils/admin_policy_utils.py +16 -3
  191. sky/utils/asyncio_utils.py +78 -0
  192. sky/utils/auth_utils.py +153 -0
  193. sky/utils/cli_utils/status_utils.py +8 -2
  194. sky/utils/command_runner.py +11 -0
  195. sky/utils/common.py +3 -1
  196. sky/utils/common_utils.py +7 -4
  197. sky/utils/context.py +57 -51
  198. sky/utils/context_utils.py +30 -12
  199. sky/utils/controller_utils.py +35 -8
  200. sky/utils/db/db_utils.py +37 -10
  201. sky/utils/db/migration_utils.py +8 -4
  202. sky/utils/locks.py +24 -6
  203. sky/utils/resource_checker.py +4 -1
  204. sky/utils/resources_utils.py +53 -29
  205. sky/utils/schemas.py +23 -4
  206. sky/utils/subprocess_utils.py +17 -4
  207. sky/volumes/server/server.py +7 -6
  208. sky/workspaces/server.py +13 -12
  209. {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/METADATA +306 -55
  210. {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/RECORD +215 -195
  211. sky/dashboard/out/_next/static/chunks/1121-d0782b9251f0fcd3.js +0 -1
  212. sky/dashboard/out/_next/static/chunks/1141-3b40c39626f99c89.js +0 -11
  213. sky/dashboard/out/_next/static/chunks/2755.97300e1362fe7c98.js +0 -26
  214. sky/dashboard/out/_next/static/chunks/3015-8d748834fcc60b46.js +0 -1
  215. sky/dashboard/out/_next/static/chunks/3294.1fafbf42b3bcebff.js +0 -1
  216. sky/dashboard/out/_next/static/chunks/6135-4b4d5e824b7f9d3c.js +0 -1
  217. sky/dashboard/out/_next/static/chunks/6856-5fdc9b851a18acdb.js +0 -1
  218. sky/dashboard/out/_next/static/chunks/6990-f6818c84ed8f1c86.js +0 -1
  219. sky/dashboard/out/_next/static/chunks/8969-66237729cdf9749e.js +0 -1
  220. sky/dashboard/out/_next/static/chunks/9025.c12318fb6a1a9093.js +0 -6
  221. sky/dashboard/out/_next/static/chunks/9360.71e83b2ddc844ec2.js +0 -31
  222. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-8f058b0346db2aff.js +0 -16
  223. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-4f7079dcab6ed653.js +0 -16
  224. sky/dashboard/out/_next/static/chunks/webpack-6a5ddd0184bfa22c.js +0 -1
  225. sky/dashboard/out/_next/static/css/4614e06482d7309e.css +0 -3
  226. sky/dashboard/out/_next/static/hIViZcQBkn0HE8SpaSsUU/_buildManifest.js +0 -1
  227. /sky/dashboard/out/_next/static/{hIViZcQBkn0HE8SpaSsUU → zB0ed6ge_W1MDszVHhijS}/_ssgManifest.js +0 -0
  228. {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/WHEEL +0 -0
  229. {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/entry_points.txt +0 -0
  230. {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/licenses/LICENSE +0 -0
  231. {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/top_level.txt +0 -0
sky/admin_policy.py CHANGED
@@ -9,7 +9,9 @@ import pydantic
9
9
 
10
10
  import sky
11
11
  from sky import exceptions
12
+ from sky import models
12
13
  from sky.adaptors import common as adaptors_common
14
+ from sky.server.requests import request_names
13
15
  from sky.utils import common_utils
14
16
  from sky.utils import config_utils
15
17
  from sky.utils import ux_utils
@@ -49,8 +51,10 @@ class _UserRequestBody(pydantic.BaseModel):
49
51
  # will be converted to JSON string, which will lose the None key.
50
52
  task: str
51
53
  skypilot_config: str
54
+ request_name: str
52
55
  request_options: Optional[RequestOptions] = None
53
56
  at_client_side: bool = False
57
+ user: str
54
58
 
55
59
 
56
60
  @dataclasses.dataclass
@@ -73,32 +77,48 @@ class UserRequest:
73
77
  skypilot_config: Global skypilot config to be used in this request.
74
78
  request_options: Request options. It is None for jobs and services.
75
79
  at_client_side: Is the request intercepted by the policy at client-side?
80
+ user: User who made the request.
81
+ Only available on the server side.
82
+ This value is None if at_client_side is True.
76
83
  """
77
84
  task: 'sky.Task'
78
85
  skypilot_config: 'sky.Config'
86
+ request_name: request_names.AdminPolicyRequestName
79
87
  request_options: Optional['RequestOptions'] = None
80
88
  at_client_side: bool = False
89
+ user: Optional['models.User'] = None
81
90
 
82
91
  def encode(self) -> str:
83
92
  return _UserRequestBody(
84
93
  task=yaml_utils.dump_yaml_str(self.task.to_yaml_config()),
85
94
  skypilot_config=yaml_utils.dump_yaml_str(dict(
86
95
  self.skypilot_config)),
96
+ request_name=self.request_name.value,
87
97
  request_options=self.request_options,
88
98
  at_client_side=self.at_client_side,
99
+ user=(yaml_utils.dump_yaml_str(self.user.to_dict())
100
+ if self.user is not None else ''),
89
101
  ).model_dump_json()
90
102
 
91
103
  @classmethod
92
104
  def decode(cls, body: str) -> 'UserRequest':
93
105
  user_request_body = _UserRequestBody.model_validate_json(body)
106
+ user_dict = yaml_utils.read_yaml_str(
107
+ user_request_body.user) if user_request_body.user != '' else None
108
+ user = models.User(
109
+ id=user_dict['id'],
110
+ name=user_dict['name']) if user_dict is not None else None
94
111
  return cls(
95
112
  task=sky.Task.from_yaml_config(
96
113
  yaml_utils.read_yaml_all_str(user_request_body.task)[0]),
97
114
  skypilot_config=config_utils.Config.from_dict(
98
115
  yaml_utils.read_yaml_all_str(
99
116
  user_request_body.skypilot_config)[0]),
117
+ request_name=request_names.AdminPolicyRequestName(
118
+ user_request_body.request_name),
100
119
  request_options=user_request_body.request_options,
101
120
  at_client_side=user_request_body.at_client_side,
121
+ user=user,
102
122
  )
103
123
 
104
124
 
sky/authentication.py CHANGED
@@ -19,31 +19,30 @@ controller. (Lambda cloud is an exception, due to the limitation of the cloud
19
19
  provider. See the comments in setup_lambda_authentication)
20
20
  """
21
21
  import copy
22
- import functools
23
22
  import os
24
23
  import re
25
24
  import socket
26
25
  import subprocess
27
26
  import sys
28
- from typing import Any, Dict, Tuple
27
+ from typing import Any, Dict
29
28
  import uuid
30
29
 
31
30
  import colorama
32
- import filelock
33
31
 
34
32
  from sky import clouds
35
33
  from sky import exceptions
36
- from sky import global_user_state
37
34
  from sky import sky_logging
38
35
  from sky.adaptors import gcp
39
36
  from sky.adaptors import ibm
40
37
  from sky.adaptors import runpod
41
38
  from sky.adaptors import seeweb as seeweb_adaptor
39
+ from sky.adaptors import shadeform as shadeform_adaptor
42
40
  from sky.adaptors import vast
43
41
  from sky.provision.fluidstack import fluidstack_utils
44
42
  from sky.provision.kubernetes import utils as kubernetes_utils
45
43
  from sky.provision.lambda_cloud import lambda_utils
46
44
  from sky.provision.primeintellect import utils as primeintellect_utils
45
+ from sky.utils import auth_utils
47
46
  from sky.utils import common_utils
48
47
  from sky.utils import subprocess_utils
49
48
  from sky.utils import ux_utils
@@ -56,140 +55,9 @@ logger = sky_logging.init_logger(__name__)
56
55
  # using Cloud Client Libraries for Python, where possible, for new code
57
56
  # development.
58
57
 
59
- MAX_TRIALS = 64
60
- # TODO(zhwu): Support user specified key pair.
61
- # We intentionally not have the ssh key pair to be stored in
62
- # ~/.sky/api_server/clients, i.e. sky.server.common.API_SERVER_CLIENT_DIR,
63
- # because ssh key pair need to persist across API server restarts, while
64
- # the former dir is empheral.
65
- _SSH_KEY_PATH_PREFIX = '~/.sky/clients/{user_hash}/ssh'
66
-
67
-
68
- def get_ssh_key_and_lock_path(user_hash: str) -> Tuple[str, str, str]:
69
- user_ssh_key_prefix = _SSH_KEY_PATH_PREFIX.format(user_hash=user_hash)
70
-
71
- os.makedirs(os.path.expanduser(user_ssh_key_prefix),
72
- exist_ok=True,
73
- mode=0o700)
74
- private_key_path = os.path.join(user_ssh_key_prefix, 'sky-key')
75
- public_key_path = os.path.join(user_ssh_key_prefix, 'sky-key.pub')
76
- lock_path = os.path.join(user_ssh_key_prefix, '.__internal-sky-key.lock')
77
- return private_key_path, public_key_path, lock_path
78
-
79
-
80
- def _generate_rsa_key_pair() -> Tuple[str, str]:
81
- # Keep the import of the cryptography local to avoid expensive
82
- # third-party imports when not needed.
83
- # pylint: disable=import-outside-toplevel
84
- from cryptography.hazmat.backends import default_backend
85
- from cryptography.hazmat.primitives import serialization
86
- from cryptography.hazmat.primitives.asymmetric import rsa
87
-
88
- key = rsa.generate_private_key(backend=default_backend(),
89
- public_exponent=65537,
90
- key_size=2048)
91
-
92
- private_key = key.private_bytes(
93
- encoding=serialization.Encoding.PEM,
94
- format=serialization.PrivateFormat.TraditionalOpenSSL,
95
- encryption_algorithm=serialization.NoEncryption()).decode(
96
- 'utf-8').strip()
97
-
98
- public_key = key.public_key().public_bytes(
99
- serialization.Encoding.OpenSSH,
100
- serialization.PublicFormat.OpenSSH).decode('utf-8').strip()
101
-
102
- return public_key, private_key
103
-
104
-
105
- def _save_key_pair(private_key_path: str, public_key_path: str,
106
- private_key: str, public_key: str) -> None:
107
- key_dir = os.path.dirname(private_key_path)
108
- os.makedirs(key_dir, exist_ok=True, mode=0o700)
109
-
110
- with open(
111
- private_key_path,
112
- 'w',
113
- encoding='utf-8',
114
- opener=functools.partial(os.open, mode=0o600),
115
- ) as f:
116
- f.write(private_key)
117
-
118
- with open(public_key_path,
119
- 'w',
120
- encoding='utf-8',
121
- opener=functools.partial(os.open, mode=0o644)) as f:
122
- f.write(public_key)
123
-
124
-
125
- def get_or_generate_keys() -> Tuple[str, str]:
126
- """Returns the absolute private and public key paths."""
127
- user_hash = common_utils.get_user_hash()
128
- private_key_path, public_key_path, lock_path = get_ssh_key_and_lock_path(
129
- user_hash)
130
- private_key_path = os.path.expanduser(private_key_path)
131
- public_key_path = os.path.expanduser(public_key_path)
132
- lock_path = os.path.expanduser(lock_path)
133
-
134
- lock_dir = os.path.dirname(lock_path)
135
- # We should have the folder ~/.sky/generated/ssh to have 0o700 permission,
136
- # as the ssh configs will be written to this folder as well in
137
- # backend_utils.SSHConfigHelper
138
- os.makedirs(lock_dir, exist_ok=True, mode=0o700)
139
- with filelock.FileLock(lock_path, timeout=10):
140
- if not os.path.exists(private_key_path):
141
- ssh_public_key, ssh_private_key, exists = (
142
- global_user_state.get_ssh_keys(user_hash))
143
- if not exists:
144
- ssh_public_key, ssh_private_key = _generate_rsa_key_pair()
145
- global_user_state.set_ssh_keys(user_hash, ssh_public_key,
146
- ssh_private_key)
147
- _save_key_pair(private_key_path, public_key_path, ssh_private_key,
148
- ssh_public_key)
149
- assert os.path.exists(public_key_path), (
150
- 'Private key found, but associated public key '
151
- f'{public_key_path} does not exist.')
152
- return private_key_path, public_key_path
153
-
154
-
155
- def create_ssh_key_files_from_db(private_key_path: str):
156
- # Assume private key path is in the format of
157
- # ~/.sky/clients/<user_hash>/ssh/sky-key
158
- separated_path = os.path.normpath(private_key_path).split(os.path.sep)
159
- assert separated_path[-1] == 'sky-key'
160
- assert separated_path[-2] == 'ssh'
161
- user_hash = separated_path[-3]
162
-
163
- private_key_path_generated, public_key_path, lock_path = (
164
- get_ssh_key_and_lock_path(user_hash))
165
- assert private_key_path == os.path.expanduser(private_key_path_generated), (
166
- f'Private key path {private_key_path} does not '
167
- 'match the generated path '
168
- f'{os.path.expanduser(private_key_path_generated)}')
169
- private_key_path = os.path.expanduser(private_key_path)
170
- public_key_path = os.path.expanduser(public_key_path)
171
- lock_path = os.path.expanduser(lock_path)
172
-
173
- lock_dir = os.path.dirname(lock_path)
174
- # We should have the folder ~/.sky/generated/ssh to have 0o700 permission,
175
- # as the ssh configs will be written to this folder as well in
176
- # backend_utils.SSHConfigHelper
177
- os.makedirs(lock_dir, exist_ok=True, mode=0o700)
178
- with filelock.FileLock(lock_path, timeout=10):
179
- if not os.path.exists(private_key_path):
180
- ssh_public_key, ssh_private_key, exists = (
181
- global_user_state.get_ssh_keys(user_hash))
182
- if not exists:
183
- raise RuntimeError(f'SSH keys not found for user {user_hash}')
184
- _save_key_pair(private_key_path, public_key_path, ssh_private_key,
185
- ssh_public_key)
186
- assert os.path.exists(public_key_path), (
187
- 'Private key found, but associated public key '
188
- f'{public_key_path} does not exist.')
189
-
190
58
 
191
59
  def configure_ssh_info(config: Dict[str, Any]) -> Dict[str, Any]:
192
- _, public_key_path = get_or_generate_keys()
60
+ _, public_key_path = auth_utils.get_or_generate_keys()
193
61
  with open(public_key_path, 'r', encoding='utf-8') as f:
194
62
  public_key = f.read().strip()
195
63
  config_str = yaml_utils.dump_yaml_str(config)
@@ -227,7 +95,7 @@ def parse_gcp_project_oslogin(project):
227
95
  # Retry for the GCP as sometimes there will be connection reset by peer error.
228
96
  @common_utils.retry
229
97
  def setup_gcp_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
230
- _, public_key_path = get_or_generate_keys()
98
+ _, public_key_path = auth_utils.get_or_generate_keys()
231
99
  config = copy.deepcopy(config)
232
100
 
233
101
  project_id = config['provider']['project_id']
@@ -352,11 +220,11 @@ def setup_gcp_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
352
220
 
353
221
  def setup_lambda_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
354
222
 
355
- get_or_generate_keys()
223
+ auth_utils.get_or_generate_keys()
356
224
 
357
225
  # Ensure ssh key is registered with Lambda Cloud
358
226
  lambda_client = lambda_utils.LambdaCloudClient()
359
- _, public_key_path = get_or_generate_keys()
227
+ _, public_key_path = auth_utils.get_or_generate_keys()
360
228
  with open(public_key_path, 'r', encoding='utf-8') as f:
361
229
  public_key = f.read().strip()
362
230
  prefix = f'sky-key-{common_utils.get_user_hash()}'
@@ -373,7 +241,7 @@ def setup_ibm_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
373
241
  and updates config file.
374
242
  keys default location: '~/.ssh/sky-key' and '~/.ssh/sky-key.pub'
375
243
  """
376
- private_key_path, _ = get_or_generate_keys()
244
+ private_key_path, _ = auth_utils.get_or_generate_keys()
377
245
 
378
246
  def _get_unique_key_name():
379
247
  suffix_len = 10
@@ -382,7 +250,7 @@ def setup_ibm_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
382
250
  client = ibm.client(region=config['provider']['region'])
383
251
  resource_group_id = config['provider']['resource_group_id']
384
252
 
385
- _, public_key_path = get_or_generate_keys()
253
+ _, public_key_path = auth_utils.get_or_generate_keys()
386
254
  with open(os.path.abspath(os.path.expanduser(public_key_path)),
387
255
  'r',
388
256
  encoding='utf-8') as file:
@@ -424,7 +292,7 @@ def setup_ibm_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
424
292
  def setup_kubernetes_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
425
293
  context = kubernetes_utils.get_context_from_config(config['provider'])
426
294
  namespace = kubernetes_utils.get_namespace_from_config(config['provider'])
427
- private_key_path, _ = get_or_generate_keys()
295
+ private_key_path, _ = auth_utils.get_or_generate_keys()
428
296
  # Using `kubectl port-forward` creates a direct tunnel to the pod and
429
297
  # does not require a ssh jump pod.
430
298
  kubernetes_utils.check_port_forward_mode_dependencies()
@@ -455,7 +323,7 @@ def setup_runpod_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
455
323
  - Generates a new SSH key pair if one does not exist.
456
324
  - Adds the public SSH key to the user's RunPod account.
457
325
  """
458
- _, public_key_path = get_or_generate_keys()
326
+ _, public_key_path = auth_utils.get_or_generate_keys()
459
327
  with open(public_key_path, 'r', encoding='UTF-8') as pub_key_file:
460
328
  public_key = pub_key_file.read().strip()
461
329
  runpod.runpod.cli.groups.ssh.functions.add_ssh_key(public_key)
@@ -468,7 +336,7 @@ def setup_vast_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
468
336
  - Generates a new SSH key pair if one does not exist.
469
337
  - Adds the public SSH key to the user's Vast account.
470
338
  """
471
- _, public_key_path = get_or_generate_keys()
339
+ _, public_key_path = auth_utils.get_or_generate_keys()
472
340
  with open(public_key_path, 'r', encoding='UTF-8') as pub_key_file:
473
341
  public_key = pub_key_file.read().strip()
474
342
  current_key_list = vast.vast().show_ssh_keys() # pylint: disable=assignment-from-no-return
@@ -482,7 +350,7 @@ def setup_vast_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
482
350
 
483
351
  def setup_fluidstack_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
484
352
 
485
- _, public_key_path = get_or_generate_keys()
353
+ _, public_key_path = auth_utils.get_or_generate_keys()
486
354
 
487
355
  client = fluidstack_utils.FluidstackClient()
488
356
  public_key = None
@@ -495,7 +363,7 @@ def setup_fluidstack_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
495
363
 
496
364
  def setup_hyperbolic_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
497
365
  """Sets up SSH authentication for Hyperbolic."""
498
- _, public_key_path = get_or_generate_keys()
366
+ _, public_key_path = auth_utils.get_or_generate_keys()
499
367
  with open(public_key_path, 'r', encoding='utf-8') as f:
500
368
  public_key = f.read().strip()
501
369
 
@@ -511,6 +379,48 @@ def setup_hyperbolic_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
511
379
  return configure_ssh_info(config)
512
380
 
513
381
 
382
+ def setup_shadeform_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
383
+ """Sets up SSH authentication for Shadeform.
384
+ - Generates a new SSH key pair if one does not exist.
385
+ - Adds the public SSH key to the user's Shadeform account.
386
+
387
+ Note: This assumes there is a Shadeform Python SDK available.
388
+ If no official SDK exists, this function would need to use direct API calls.
389
+ """
390
+
391
+ _, public_key_path = auth_utils.get_or_generate_keys()
392
+ ssh_key_id = None
393
+
394
+ with open(public_key_path, 'r', encoding='utf-8') as f:
395
+ public_key = f.read().strip()
396
+
397
+ try:
398
+ # Add SSH key to Shadeform using our utility functions
399
+ ssh_key_id = shadeform_adaptor.add_ssh_key_to_shadeform(public_key)
400
+
401
+ except ImportError as e:
402
+ # If required dependencies are missing
403
+ logger.warning(
404
+ f'Failed to add Shadeform SSH key due to missing dependencies: '
405
+ f'{e}. Manually configure SSH keys in your Shadeform account.')
406
+
407
+ except Exception as e:
408
+ logger.warning(f'Failed to set up Shadeform authentication: {e}')
409
+ raise exceptions.CloudUserIdentityError(
410
+ 'Failed to set up SSH authentication for Shadeform. '
411
+ f'Please ensure your Shadeform credentials are configured: {e}'
412
+ ) from e
413
+
414
+ if ssh_key_id is None:
415
+ raise Exception('Failed to add SSH key to Shadeform')
416
+
417
+ # Configure SSH info in the config
418
+ config['auth']['ssh_public_key'] = public_key_path
419
+ config['auth']['ssh_key_id'] = ssh_key_id
420
+
421
+ return configure_ssh_info(config)
422
+
423
+
514
424
  def setup_primeintellect_authentication(
515
425
  config: Dict[str, Any]) -> Dict[str, Any]:
516
426
  """Sets up SSH authentication for Prime Intellect.
@@ -518,7 +428,7 @@ def setup_primeintellect_authentication(
518
428
  - Adds the public SSH key to the user's Prime Intellect account.
519
429
  """
520
430
  # Ensure local SSH keypair exists and fetch public key content
521
- _, public_key_path = get_or_generate_keys()
431
+ _, public_key_path = auth_utils.get_or_generate_keys()
522
432
  with open(public_key_path, 'r', encoding='utf-8') as f:
523
433
  public_key = f.read().strip()
524
434
 
@@ -538,10 +448,10 @@ def setup_primeintellect_authentication(
538
448
  def setup_seeweb_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
539
449
  """Registers the public key with Seeweb and notes the remote name."""
540
450
  # 1. local key pair
541
- get_or_generate_keys()
451
+ auth_utils.get_or_generate_keys()
542
452
 
543
453
  # 2. public key
544
- _, public_key_path = get_or_generate_keys()
454
+ _, public_key_path = auth_utils.get_or_generate_keys()
545
455
  with open(public_key_path, 'r', encoding='utf-8') as f:
546
456
  public_key = f.read().strip()
547
457