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.
- sky/__init__.py +6 -2
- sky/adaptors/aws.py +25 -7
- sky/adaptors/coreweave.py +278 -0
- sky/adaptors/kubernetes.py +64 -0
- sky/adaptors/shadeform.py +89 -0
- sky/admin_policy.py +20 -0
- sky/authentication.py +59 -149
- sky/backends/backend_utils.py +104 -63
- sky/backends/cloud_vm_ray_backend.py +84 -39
- sky/catalog/data_fetchers/fetch_runpod.py +698 -0
- sky/catalog/data_fetchers/fetch_shadeform.py +142 -0
- sky/catalog/kubernetes_catalog.py +24 -28
- sky/catalog/runpod_catalog.py +5 -1
- sky/catalog/shadeform_catalog.py +165 -0
- sky/check.py +25 -13
- sky/client/cli/command.py +335 -86
- sky/client/cli/flags.py +4 -2
- sky/client/cli/table_utils.py +17 -9
- sky/client/sdk.py +59 -12
- sky/cloud_stores.py +73 -0
- sky/clouds/__init__.py +2 -0
- sky/clouds/aws.py +71 -16
- sky/clouds/azure.py +12 -5
- sky/clouds/cloud.py +19 -9
- sky/clouds/cudo.py +12 -5
- sky/clouds/do.py +4 -1
- sky/clouds/fluidstack.py +12 -5
- sky/clouds/gcp.py +12 -5
- sky/clouds/hyperbolic.py +12 -5
- sky/clouds/ibm.py +12 -5
- sky/clouds/kubernetes.py +62 -25
- sky/clouds/lambda_cloud.py +12 -5
- sky/clouds/nebius.py +12 -5
- sky/clouds/oci.py +12 -5
- sky/clouds/paperspace.py +4 -1
- sky/clouds/primeintellect.py +4 -1
- sky/clouds/runpod.py +12 -5
- sky/clouds/scp.py +12 -5
- sky/clouds/seeweb.py +4 -1
- sky/clouds/shadeform.py +400 -0
- sky/clouds/ssh.py +4 -2
- sky/clouds/vast.py +12 -5
- sky/clouds/vsphere.py +4 -1
- sky/core.py +12 -11
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/chunks/1141-e6aa9ab418717c59.js +11 -0
- sky/dashboard/out/_next/static/chunks/{1871-49141c317f3a9020.js → 1871-74503c8e80fd253b.js} +1 -1
- sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
- sky/dashboard/out/_next/static/chunks/2755.fff53c4a3fcae910.js +26 -0
- sky/dashboard/out/_next/static/chunks/3294.72362fa129305b19.js +1 -0
- sky/dashboard/out/_next/static/chunks/{3785.a19328ba41517b8b.js → 3785.ad6adaa2a0fa9768.js} +1 -1
- sky/dashboard/out/_next/static/chunks/{4725.10f7a9a5d3ea8208.js → 4725.a830b5c9e7867c92.js} +1 -1
- sky/dashboard/out/_next/static/chunks/6856-ef8ba11f96d8c4a3.js +1 -0
- sky/dashboard/out/_next/static/chunks/6990-32b6e2d3822301fa.js +1 -0
- sky/dashboard/out/_next/static/chunks/7615-3301e838e5f25772.js +1 -0
- sky/dashboard/out/_next/static/chunks/8969-1e4613c651bf4051.js +1 -0
- sky/dashboard/out/_next/static/chunks/9025.fa408f3242e9028d.js +6 -0
- sky/dashboard/out/_next/static/chunks/9353-cff34f7e773b2e2b.js +1 -0
- sky/dashboard/out/_next/static/chunks/9360.7310982cf5a0dc79.js +31 -0
- sky/dashboard/out/_next/static/chunks/pages/{_app-ce361c6959bc2001.js → _app-bde01e4a2beec258.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-c736ead69c2d86ec.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-477555ab7c0b13d8.js → [cluster]-a37d2063af475a1c.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{clusters-2f61f65487f6d8ff.js → clusters-d44859594e6f8064.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-553b8b5cb65e100b.js → [context]-c0b5935149902e6f.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{infra-910a22500c50596f.js → infra-aed0ea19df7cf961.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-5796e8d6aea291a0.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs/pools/{[pool]-bc979970c247d8f3.js → [pool]-6edeb7d06032adfc.js} +2 -2
- sky/dashboard/out/_next/static/chunks/pages/{jobs-a35a9dc3c5ccd657.js → jobs-479dde13399cf270.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{users-98d2ed979084162a.js → users-5ab3b907622cf0fe.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{volumes-835d14ba94808f79.js → volumes-b84b948ff357c43e.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-e8688c35c06f0ac5.js → [name]-c5a3eeee1c218af1.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{workspaces-69c80d677d3c2949.js → workspaces-22b23febb3e89ce1.js} +1 -1
- sky/dashboard/out/_next/static/chunks/webpack-2679be77fc08a2f8.js +1 -0
- sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
- sky/dashboard/out/_next/static/zB0ed6ge_W1MDszVHhijS/_buildManifest.js +1 -0
- sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
- sky/dashboard/out/clusters/[cluster].html +1 -1
- sky/dashboard/out/clusters.html +1 -1
- sky/dashboard/out/config.html +1 -1
- sky/dashboard/out/index.html +1 -1
- sky/dashboard/out/infra/[context].html +1 -1
- sky/dashboard/out/infra.html +1 -1
- sky/dashboard/out/jobs/[job].html +1 -1
- sky/dashboard/out/jobs/pools/[pool].html +1 -1
- sky/dashboard/out/jobs.html +1 -1
- sky/dashboard/out/users.html +1 -1
- sky/dashboard/out/volumes.html +1 -1
- sky/dashboard/out/workspace/new.html +1 -1
- sky/dashboard/out/workspaces/[name].html +1 -1
- sky/dashboard/out/workspaces.html +1 -1
- sky/data/data_utils.py +92 -1
- sky/data/mounting_utils.py +143 -19
- sky/data/storage.py +168 -11
- sky/exceptions.py +13 -1
- sky/execution.py +13 -0
- sky/global_user_state.py +189 -113
- sky/jobs/client/sdk.py +32 -10
- sky/jobs/client/sdk_async.py +9 -3
- sky/jobs/constants.py +3 -1
- sky/jobs/controller.py +164 -192
- sky/jobs/file_content_utils.py +80 -0
- sky/jobs/log_gc.py +201 -0
- sky/jobs/recovery_strategy.py +59 -82
- sky/jobs/scheduler.py +20 -9
- sky/jobs/server/core.py +105 -23
- sky/jobs/server/server.py +40 -28
- sky/jobs/server/utils.py +32 -11
- sky/jobs/state.py +588 -110
- sky/jobs/utils.py +442 -209
- sky/logs/agent.py +1 -1
- sky/metrics/utils.py +45 -6
- sky/optimizer.py +1 -1
- sky/provision/__init__.py +7 -0
- sky/provision/aws/instance.py +2 -1
- sky/provision/azure/instance.py +2 -1
- sky/provision/common.py +2 -0
- sky/provision/cudo/instance.py +2 -1
- sky/provision/do/instance.py +2 -1
- sky/provision/fluidstack/instance.py +4 -3
- sky/provision/gcp/instance.py +2 -1
- sky/provision/hyperbolic/instance.py +2 -1
- sky/provision/instance_setup.py +10 -2
- sky/provision/kubernetes/constants.py +0 -1
- sky/provision/kubernetes/instance.py +222 -89
- sky/provision/kubernetes/network.py +12 -8
- sky/provision/kubernetes/utils.py +114 -53
- sky/provision/kubernetes/volume.py +5 -4
- sky/provision/lambda_cloud/instance.py +2 -1
- sky/provision/nebius/instance.py +2 -1
- sky/provision/oci/instance.py +2 -1
- sky/provision/paperspace/instance.py +2 -1
- sky/provision/provisioner.py +11 -2
- sky/provision/runpod/instance.py +2 -1
- sky/provision/scp/instance.py +2 -1
- sky/provision/seeweb/instance.py +3 -3
- sky/provision/shadeform/__init__.py +11 -0
- sky/provision/shadeform/config.py +12 -0
- sky/provision/shadeform/instance.py +351 -0
- sky/provision/shadeform/shadeform_utils.py +83 -0
- sky/provision/vast/instance.py +2 -1
- sky/provision/vsphere/instance.py +2 -1
- sky/resources.py +1 -1
- sky/schemas/api/responses.py +9 -5
- sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
- sky/schemas/db/spot_jobs/004_job_file_contents.py +42 -0
- sky/schemas/db/spot_jobs/005_logs_gc.py +38 -0
- sky/schemas/generated/jobsv1_pb2.py +52 -52
- sky/schemas/generated/jobsv1_pb2.pyi +4 -2
- sky/schemas/generated/managed_jobsv1_pb2.py +39 -35
- sky/schemas/generated/managed_jobsv1_pb2.pyi +21 -5
- sky/serve/client/impl.py +11 -3
- sky/serve/replica_managers.py +5 -2
- sky/serve/serve_utils.py +9 -2
- sky/serve/server/impl.py +7 -2
- sky/serve/server/server.py +18 -15
- sky/serve/service.py +2 -2
- sky/server/auth/oauth2_proxy.py +2 -5
- sky/server/common.py +31 -28
- sky/server/constants.py +5 -1
- sky/server/daemons.py +27 -19
- sky/server/requests/executor.py +138 -74
- sky/server/requests/payloads.py +9 -1
- sky/server/requests/preconditions.py +13 -10
- sky/server/requests/request_names.py +120 -0
- sky/server/requests/requests.py +485 -153
- sky/server/requests/serializers/decoders.py +26 -13
- sky/server/requests/serializers/encoders.py +56 -11
- sky/server/requests/threads.py +106 -0
- sky/server/rest.py +70 -18
- sky/server/server.py +283 -104
- sky/server/stream_utils.py +233 -59
- sky/server/uvicorn.py +18 -17
- sky/setup_files/alembic.ini +4 -0
- sky/setup_files/dependencies.py +32 -13
- sky/sky_logging.py +0 -2
- sky/skylet/constants.py +30 -7
- sky/skylet/events.py +7 -0
- sky/skylet/log_lib.py +8 -2
- sky/skylet/log_lib.pyi +1 -1
- sky/skylet/services.py +26 -13
- sky/skylet/subprocess_daemon.py +103 -29
- sky/skypilot_config.py +87 -75
- sky/ssh_node_pools/server.py +9 -8
- sky/task.py +67 -54
- sky/templates/kubernetes-ray.yml.j2 +8 -1
- sky/templates/nebius-ray.yml.j2 +1 -0
- sky/templates/shadeform-ray.yml.j2 +72 -0
- sky/templates/websocket_proxy.py +142 -12
- sky/users/permission.py +8 -1
- sky/utils/admin_policy_utils.py +16 -3
- sky/utils/asyncio_utils.py +78 -0
- sky/utils/auth_utils.py +153 -0
- sky/utils/cli_utils/status_utils.py +8 -2
- sky/utils/command_runner.py +11 -0
- sky/utils/common.py +3 -1
- sky/utils/common_utils.py +7 -4
- sky/utils/context.py +57 -51
- sky/utils/context_utils.py +30 -12
- sky/utils/controller_utils.py +35 -8
- sky/utils/db/db_utils.py +37 -10
- sky/utils/db/migration_utils.py +8 -4
- sky/utils/locks.py +24 -6
- sky/utils/resource_checker.py +4 -1
- sky/utils/resources_utils.py +53 -29
- sky/utils/schemas.py +23 -4
- sky/utils/subprocess_utils.py +17 -4
- sky/volumes/server/server.py +7 -6
- sky/workspaces/server.py +13 -12
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/METADATA +306 -55
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/RECORD +215 -195
- sky/dashboard/out/_next/static/chunks/1121-d0782b9251f0fcd3.js +0 -1
- sky/dashboard/out/_next/static/chunks/1141-3b40c39626f99c89.js +0 -11
- sky/dashboard/out/_next/static/chunks/2755.97300e1362fe7c98.js +0 -26
- sky/dashboard/out/_next/static/chunks/3015-8d748834fcc60b46.js +0 -1
- sky/dashboard/out/_next/static/chunks/3294.1fafbf42b3bcebff.js +0 -1
- sky/dashboard/out/_next/static/chunks/6135-4b4d5e824b7f9d3c.js +0 -1
- sky/dashboard/out/_next/static/chunks/6856-5fdc9b851a18acdb.js +0 -1
- sky/dashboard/out/_next/static/chunks/6990-f6818c84ed8f1c86.js +0 -1
- sky/dashboard/out/_next/static/chunks/8969-66237729cdf9749e.js +0 -1
- sky/dashboard/out/_next/static/chunks/9025.c12318fb6a1a9093.js +0 -6
- sky/dashboard/out/_next/static/chunks/9360.71e83b2ddc844ec2.js +0 -31
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-8f058b0346db2aff.js +0 -16
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-4f7079dcab6ed653.js +0 -16
- sky/dashboard/out/_next/static/chunks/webpack-6a5ddd0184bfa22c.js +0 -1
- sky/dashboard/out/_next/static/css/4614e06482d7309e.css +0 -3
- sky/dashboard/out/_next/static/hIViZcQBkn0HE8SpaSsUU/_buildManifest.js +0 -1
- /sky/dashboard/out/_next/static/{hIViZcQBkn0HE8SpaSsUU → zB0ed6ge_W1MDszVHhijS}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
|