skypilot-nightly 1.0.0.dev20250826__py3-none-any.whl → 1.0.0.dev20250828__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 +2 -2
- sky/admin_policy.py +11 -10
- sky/authentication.py +4 -10
- sky/backends/backend.py +3 -5
- sky/backends/backend_utils.py +41 -56
- sky/backends/cloud_vm_ray_backend.py +13 -24
- sky/backends/local_docker_backend.py +3 -8
- sky/client/cli/command.py +43 -10
- sky/client/common.py +41 -14
- sky/client/sdk.py +24 -9
- sky/client/sdk_async.py +6 -2
- sky/clouds/aws.py +1 -1
- sky/clouds/cloud.py +15 -0
- sky/clouds/kubernetes.py +27 -0
- sky/clouds/ssh.py +2 -3
- sky/core.py +1 -4
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/chunks/{webpack-6e76f636a048e145.js → webpack-6dae1cd599a34def.js} +1 -1
- 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/global_user_state.py +127 -23
- sky/jobs/client/sdk.py +5 -2
- sky/jobs/recovery_strategy.py +9 -4
- sky/logs/agent.py +2 -2
- sky/logs/aws.py +6 -3
- sky/provision/do/utils.py +2 -1
- sky/provision/kubernetes/config.py +2 -8
- sky/provision/kubernetes/instance.py +58 -8
- sky/provision/kubernetes/network_utils.py +3 -4
- sky/provision/kubernetes/utils.py +8 -7
- sky/provision/nebius/utils.py +51 -9
- sky/provision/vsphere/vsphere_utils.py +2 -8
- sky/schemas/api/responses.py +7 -0
- sky/serve/client/impl.py +5 -4
- sky/serve/replica_managers.py +4 -3
- sky/serve/serve_utils.py +4 -4
- sky/serve/server/impl.py +3 -2
- sky/serve/service_spec.py +2 -8
- sky/server/auth/authn.py +4 -0
- sky/server/auth/oauth2_proxy.py +10 -4
- sky/server/common.py +10 -3
- sky/server/daemons.py +10 -5
- sky/server/requests/executor.py +6 -1
- sky/server/requests/requests.py +21 -0
- sky/server/server.py +34 -33
- sky/server/uvicorn.py +33 -0
- sky/setup_files/dependencies.py +1 -0
- sky/sky_logging.py +4 -1
- sky/skylet/events.py +4 -5
- sky/skypilot_config.py +14 -12
- sky/ssh_node_pools/core.py +3 -1
- sky/task.py +4 -10
- sky/templates/nebius-ray.yml.j2 +4 -8
- sky/usage/usage_lib.py +3 -2
- sky/users/server.py +6 -6
- sky/utils/common_utils.py +0 -71
- sky/utils/controller_utils.py +4 -3
- sky/utils/dag_utils.py +4 -4
- sky/utils/kubernetes/config_map_utils.py +3 -3
- sky/utils/schemas.py +3 -0
- sky/utils/yaml_utils.py +102 -0
- sky/volumes/volume.py +8 -3
- {skypilot_nightly-1.0.0.dev20250826.dist-info → skypilot_nightly-1.0.0.dev20250828.dist-info}/METADATA +2 -1
- {skypilot_nightly-1.0.0.dev20250826.dist-info → skypilot_nightly-1.0.0.dev20250828.dist-info}/RECORD +83 -82
- /sky/dashboard/out/_next/static/{TPMkEeuj85tHTmIW7Gu3S → 9DW6d9jaP2kZt0NcgIfFa}/_buildManifest.js +0 -0
- /sky/dashboard/out/_next/static/{TPMkEeuj85tHTmIW7Gu3S → 9DW6d9jaP2kZt0NcgIfFa}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250826.dist-info → skypilot_nightly-1.0.0.dev20250828.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250826.dist-info → skypilot_nightly-1.0.0.dev20250828.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250826.dist-info → skypilot_nightly-1.0.0.dev20250828.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250826.dist-info → skypilot_nightly-1.0.0.dev20250828.dist-info}/top_level.txt +0 -0
sky/server/server.py
CHANGED
|
@@ -7,7 +7,6 @@ import contextlib
|
|
|
7
7
|
import datetime
|
|
8
8
|
import hashlib
|
|
9
9
|
import json
|
|
10
|
-
import logging
|
|
11
10
|
import multiprocessing
|
|
12
11
|
import os
|
|
13
12
|
import pathlib
|
|
@@ -24,7 +23,6 @@ import zipfile
|
|
|
24
23
|
import aiofiles
|
|
25
24
|
import fastapi
|
|
26
25
|
from fastapi.middleware import cors
|
|
27
|
-
from passlib.hash import apr_md5_crypt
|
|
28
26
|
import starlette.middleware.base
|
|
29
27
|
import uvloop
|
|
30
28
|
|
|
@@ -69,7 +67,6 @@ from sky.utils import common_utils
|
|
|
69
67
|
from sky.utils import context
|
|
70
68
|
from sky.utils import context_utils
|
|
71
69
|
from sky.utils import dag_utils
|
|
72
|
-
from sky.utils import env_options
|
|
73
70
|
from sky.utils import status_lib
|
|
74
71
|
from sky.utils import subprocess_utils
|
|
75
72
|
from sky.volumes.server import server as volumes_rest
|
|
@@ -85,31 +82,6 @@ P = ParamSpec('P')
|
|
|
85
82
|
|
|
86
83
|
_SERVER_USER_HASH_KEY = 'server_user_hash'
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
def _add_timestamp_prefix_for_server_logs() -> None:
|
|
90
|
-
server_logger = sky_logging.init_logger('sky.server')
|
|
91
|
-
# Clear existing handlers first to prevent duplicates
|
|
92
|
-
server_logger.handlers.clear()
|
|
93
|
-
# Disable propagation to avoid the root logger of SkyPilot being affected
|
|
94
|
-
server_logger.propagate = False
|
|
95
|
-
# Add date prefix to the log message printed by loggers under
|
|
96
|
-
# server.
|
|
97
|
-
stream_handler = logging.StreamHandler(sys.stdout)
|
|
98
|
-
if env_options.Options.SHOW_DEBUG_INFO.get():
|
|
99
|
-
stream_handler.setLevel(logging.DEBUG)
|
|
100
|
-
else:
|
|
101
|
-
stream_handler.setLevel(logging.INFO)
|
|
102
|
-
stream_handler.flush = sys.stdout.flush # type: ignore
|
|
103
|
-
stream_handler.setFormatter(sky_logging.FORMATTER)
|
|
104
|
-
server_logger.addHandler(stream_handler)
|
|
105
|
-
# Add date prefix to the log message printed by uvicorn.
|
|
106
|
-
for name in ['uvicorn', 'uvicorn.access']:
|
|
107
|
-
uvicorn_logger = logging.getLogger(name)
|
|
108
|
-
uvicorn_logger.handlers.clear()
|
|
109
|
-
uvicorn_logger.addHandler(stream_handler)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
_add_timestamp_prefix_for_server_logs()
|
|
113
85
|
logger = sky_logging.init_logger(__name__)
|
|
114
86
|
|
|
115
87
|
# TODO(zhwu): Streaming requests, such log tailing after sky launch or sky logs,
|
|
@@ -148,7 +120,7 @@ def _try_set_basic_auth_user(request: fastapi.Request):
|
|
|
148
120
|
username_encoded = username.encode('utf8')
|
|
149
121
|
db_username_encoded = user.name.encode('utf8')
|
|
150
122
|
if (username_encoded == db_username_encoded and
|
|
151
|
-
|
|
123
|
+
common.crypt_ctx.verify(password, user.password)):
|
|
152
124
|
request.state.auth_user = user
|
|
153
125
|
break
|
|
154
126
|
|
|
@@ -248,7 +220,7 @@ class BasicAuthMiddleware(starlette.middleware.base.BaseHTTPMiddleware):
|
|
|
248
220
|
username_encoded = username.encode('utf8')
|
|
249
221
|
db_username_encoded = user.name.encode('utf8')
|
|
250
222
|
if (username_encoded == db_username_encoded and
|
|
251
|
-
|
|
223
|
+
common.crypt_ctx.verify(password, user.password)):
|
|
252
224
|
valid_user = True
|
|
253
225
|
request.state.auth_user = user
|
|
254
226
|
await authn.override_user_info_in_request_body(request, user)
|
|
@@ -852,6 +824,15 @@ async def upload_zip_file(request: fastapi.Request, user_hash: str,
|
|
|
852
824
|
chunk_index: The chunk index, starting from 0.
|
|
853
825
|
total_chunks: The total number of chunks.
|
|
854
826
|
"""
|
|
827
|
+
# Field _body would be set if the request body has been received, fail fast
|
|
828
|
+
# to surface potential memory issues, i.e. catch the issue in our smoke
|
|
829
|
+
# test.
|
|
830
|
+
# pylint: disable=protected-access
|
|
831
|
+
if hasattr(request, '_body'):
|
|
832
|
+
raise fastapi.HTTPException(
|
|
833
|
+
status_code=500,
|
|
834
|
+
detail='Upload request body should not be received before streaming'
|
|
835
|
+
)
|
|
855
836
|
# Add the upload id to the cleanup list.
|
|
856
837
|
upload_ids_to_cleanup[(upload_id,
|
|
857
838
|
user_hash)] = (datetime.datetime.now() +
|
|
@@ -919,8 +900,9 @@ async def upload_zip_file(request: fastapi.Request, user_hash: str,
|
|
|
919
900
|
zip_file_path.rename(zip_file_path.with_suffix(''))
|
|
920
901
|
missing_chunks = get_missing_chunks(total_chunks)
|
|
921
902
|
if missing_chunks:
|
|
922
|
-
return payloads.UploadZipFileResponse(
|
|
923
|
-
|
|
903
|
+
return payloads.UploadZipFileResponse(
|
|
904
|
+
status=responses.UploadStatus.UPLOADING.value,
|
|
905
|
+
missing_chunks=missing_chunks)
|
|
924
906
|
zip_file_path = client_file_mounts_dir / f'{upload_id}.zip'
|
|
925
907
|
async with aiofiles.open(zip_file_path, 'wb') as zip_file:
|
|
926
908
|
for chunk in range(total_chunks):
|
|
@@ -937,7 +919,8 @@ async def upload_zip_file(request: fastapi.Request, user_hash: str,
|
|
|
937
919
|
unzip_file(zip_file_path, client_file_mounts_dir)
|
|
938
920
|
if total_chunks > 1:
|
|
939
921
|
shutil.rmtree(chunk_dir)
|
|
940
|
-
return payloads.UploadZipFileResponse(
|
|
922
|
+
return payloads.UploadZipFileResponse(
|
|
923
|
+
status=responses.UploadStatus.COMPLETED.value)
|
|
941
924
|
|
|
942
925
|
|
|
943
926
|
def _is_relative_to(path: pathlib.Path, parent: pathlib.Path) -> bool:
|
|
@@ -1420,6 +1403,9 @@ async def api_get(request_id: str) -> payloads.RequestPayload:
|
|
|
1420
1403
|
raise fastapi.HTTPException(
|
|
1421
1404
|
status_code=500, detail=request_task.encode().model_dump())
|
|
1422
1405
|
return request_task.encode()
|
|
1406
|
+
elif (request_task.status == requests_lib.RequestStatus.RUNNING and
|
|
1407
|
+
daemons.is_daemon_request_id(request_id)):
|
|
1408
|
+
return request_task.encode()
|
|
1423
1409
|
# yield control to allow other coroutines to run, sleep shortly
|
|
1424
1410
|
# to avoid storming the DB and CPU in the meantime
|
|
1425
1411
|
await asyncio.sleep(0.1)
|
|
@@ -1508,6 +1494,14 @@ async def stream(
|
|
|
1508
1494
|
if log_path == constants.API_SERVER_LOGS:
|
|
1509
1495
|
resolved_log_path = pathlib.Path(
|
|
1510
1496
|
constants.API_SERVER_LOGS).expanduser()
|
|
1497
|
+
if not resolved_log_path.exists():
|
|
1498
|
+
raise fastapi.HTTPException(
|
|
1499
|
+
status_code=404,
|
|
1500
|
+
detail='Server log file does not exist. The API server may '
|
|
1501
|
+
'have been started with `--foreground` - check the '
|
|
1502
|
+
'stdout of API server process, such as: '
|
|
1503
|
+
'`kubectl logs -n api-server-namespace '
|
|
1504
|
+
'api-server-pod-name`')
|
|
1511
1505
|
else:
|
|
1512
1506
|
# This should be a log path under ~/sky_logs.
|
|
1513
1507
|
resolved_logs_directory = pathlib.Path(
|
|
@@ -1786,6 +1780,11 @@ async def complete_volume_name(incomplete: str,) -> List[str]:
|
|
|
1786
1780
|
return global_user_state.get_volume_names_start_with(incomplete)
|
|
1787
1781
|
|
|
1788
1782
|
|
|
1783
|
+
@app.get('/api/completion/api_request')
|
|
1784
|
+
async def complete_api_request(incomplete: str,) -> List[str]:
|
|
1785
|
+
return requests_lib.get_api_request_ids_start_with(incomplete)
|
|
1786
|
+
|
|
1787
|
+
|
|
1789
1788
|
@app.get('/dashboard/{full_path:path}')
|
|
1790
1789
|
async def serve_dashboard(full_path: str):
|
|
1791
1790
|
"""Serves the Next.js dashboard application.
|
|
@@ -1859,6 +1858,8 @@ if __name__ == '__main__':
|
|
|
1859
1858
|
|
|
1860
1859
|
from sky.server import uvicorn as skyuvicorn
|
|
1861
1860
|
|
|
1861
|
+
skyuvicorn.add_timestamp_prefix_for_server_logs()
|
|
1862
|
+
|
|
1862
1863
|
# Initialize global user state db
|
|
1863
1864
|
global_user_state.initialize_and_get_db()
|
|
1864
1865
|
# Initialize request db
|
sky/server/uvicorn.py
CHANGED
|
@@ -4,8 +4,10 @@ This module is a wrapper around uvicorn to customize the behavior of the
|
|
|
4
4
|
server.
|
|
5
5
|
"""
|
|
6
6
|
import asyncio
|
|
7
|
+
import logging
|
|
7
8
|
import os
|
|
8
9
|
import signal
|
|
10
|
+
import sys
|
|
9
11
|
import threading
|
|
10
12
|
import time
|
|
11
13
|
from types import FrameType
|
|
@@ -21,6 +23,7 @@ from sky.server import state
|
|
|
21
23
|
from sky.server.requests import requests as requests_lib
|
|
22
24
|
from sky.skylet import constants
|
|
23
25
|
from sky.utils import context_utils
|
|
26
|
+
from sky.utils import env_options
|
|
24
27
|
from sky.utils import subprocess_utils
|
|
25
28
|
|
|
26
29
|
logger = sky_logging.init_logger(__name__)
|
|
@@ -47,6 +50,35 @@ _RETRIABLE_REQUEST_NAMES = [
|
|
|
47
50
|
]
|
|
48
51
|
|
|
49
52
|
|
|
53
|
+
def add_timestamp_prefix_for_server_logs() -> None:
|
|
54
|
+
"""Configure logging for API server.
|
|
55
|
+
|
|
56
|
+
Note: we only do this in the main API server process and uvicorn processes,
|
|
57
|
+
to avoid affecting executor logs (including in modules like
|
|
58
|
+
sky.server.requests) that may get sent to the client.
|
|
59
|
+
"""
|
|
60
|
+
server_logger = sky_logging.init_logger('sky.server')
|
|
61
|
+
# Clear existing handlers first to prevent duplicates
|
|
62
|
+
server_logger.handlers.clear()
|
|
63
|
+
# Disable propagation to avoid the root logger of SkyPilot being affected
|
|
64
|
+
server_logger.propagate = False
|
|
65
|
+
# Add date prefix to the log message printed by loggers under
|
|
66
|
+
# server.
|
|
67
|
+
stream_handler = logging.StreamHandler(sys.stdout)
|
|
68
|
+
if env_options.Options.SHOW_DEBUG_INFO.get():
|
|
69
|
+
stream_handler.setLevel(logging.DEBUG)
|
|
70
|
+
else:
|
|
71
|
+
stream_handler.setLevel(logging.INFO)
|
|
72
|
+
stream_handler.flush = sys.stdout.flush # type: ignore
|
|
73
|
+
stream_handler.setFormatter(sky_logging.FORMATTER)
|
|
74
|
+
server_logger.addHandler(stream_handler)
|
|
75
|
+
# Add date prefix to the log message printed by uvicorn.
|
|
76
|
+
for name in ['uvicorn', 'uvicorn.access']:
|
|
77
|
+
uvicorn_logger = logging.getLogger(name)
|
|
78
|
+
uvicorn_logger.handlers.clear()
|
|
79
|
+
uvicorn_logger.addHandler(stream_handler)
|
|
80
|
+
|
|
81
|
+
|
|
50
82
|
class Server(uvicorn.Server):
|
|
51
83
|
"""Server wrapper for uvicorn.
|
|
52
84
|
|
|
@@ -162,6 +194,7 @@ class Server(uvicorn.Server):
|
|
|
162
194
|
|
|
163
195
|
def run(self, *args, **kwargs):
|
|
164
196
|
"""Run the server process."""
|
|
197
|
+
add_timestamp_prefix_for_server_logs()
|
|
165
198
|
context_utils.hijack_sys_attrs()
|
|
166
199
|
# Use default loop policy of uvicorn (use uvloop if available).
|
|
167
200
|
self.config.setup_event_loop()
|
sky/setup_files/dependencies.py
CHANGED
sky/sky_logging.py
CHANGED
|
@@ -202,7 +202,10 @@ def set_sky_logging_levels(level: int):
|
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
def logging_enabled(logger: logging.Logger, level: int) -> bool:
|
|
205
|
-
return
|
|
205
|
+
# Note(cooperc): This may return true in a lot of cases where we won't
|
|
206
|
+
# actually log anything, since the log level is set on the handler in
|
|
207
|
+
# _setup_logger.
|
|
208
|
+
return logger.getEffectiveLevel() <= level
|
|
206
209
|
|
|
207
210
|
|
|
208
211
|
@contextlib.contextmanager
|
sky/skylet/events.py
CHANGED
|
@@ -7,7 +7,6 @@ import time
|
|
|
7
7
|
import traceback
|
|
8
8
|
|
|
9
9
|
import psutil
|
|
10
|
-
import yaml
|
|
11
10
|
|
|
12
11
|
from sky import clouds
|
|
13
12
|
from sky import sky_logging
|
|
@@ -21,9 +20,9 @@ from sky.skylet import constants
|
|
|
21
20
|
from sky.skylet import job_lib
|
|
22
21
|
from sky.usage import usage_lib
|
|
23
22
|
from sky.utils import cluster_utils
|
|
24
|
-
from sky.utils import common_utils
|
|
25
23
|
from sky.utils import registry
|
|
26
24
|
from sky.utils import ux_utils
|
|
25
|
+
from sky.utils import yaml_utils
|
|
27
26
|
|
|
28
27
|
# Seconds of sleep between the processing of skylet events.
|
|
29
28
|
EVENT_CHECKING_INTERVAL_SECONDS = 20
|
|
@@ -181,7 +180,7 @@ class AutostopEvent(SkyletEvent):
|
|
|
181
180
|
|
|
182
181
|
config_path = os.path.abspath(
|
|
183
182
|
os.path.expanduser(cluster_utils.SKY_CLUSTER_YAML_REMOTE_PATH))
|
|
184
|
-
config =
|
|
183
|
+
config = yaml_utils.read_yaml(config_path)
|
|
185
184
|
provider_name = cluster_utils.get_provider_name(config)
|
|
186
185
|
cloud = registry.CLOUD_REGISTRY.from_str(provider_name)
|
|
187
186
|
assert cloud is not None, f'Unknown cloud: {provider_name}'
|
|
@@ -309,7 +308,7 @@ class AutostopEvent(SkyletEvent):
|
|
|
309
308
|
else:
|
|
310
309
|
yaml_str = self._CATCH_NODES.sub(r'cache_stopped_nodes: true',
|
|
311
310
|
yaml_str)
|
|
312
|
-
config =
|
|
311
|
+
config = yaml_utils.safe_load(yaml_str)
|
|
313
312
|
# Set the private key with the existed key on the remote instance.
|
|
314
313
|
config['auth']['ssh_private_key'] = '~/ray_bootstrap_key.pem'
|
|
315
314
|
# NOTE: We must do this, otherwise with ssh_proxy_command still under
|
|
@@ -326,5 +325,5 @@ class AutostopEvent(SkyletEvent):
|
|
|
326
325
|
config['auth'].pop('ssh_proxy_command', None)
|
|
327
326
|
# Empty the file_mounts.
|
|
328
327
|
config['file_mounts'] = {}
|
|
329
|
-
|
|
328
|
+
yaml_utils.dump_yaml(yaml_path, config)
|
|
330
329
|
logger.debug('Replaced upscaling speed to 0.')
|
sky/skypilot_config.py
CHANGED
|
@@ -75,6 +75,7 @@ from sky.utils import config_utils
|
|
|
75
75
|
from sky.utils import context
|
|
76
76
|
from sky.utils import schemas
|
|
77
77
|
from sky.utils import ux_utils
|
|
78
|
+
from sky.utils import yaml_utils
|
|
78
79
|
from sky.utils.db import db_utils
|
|
79
80
|
from sky.utils.kubernetes import config_map_utils
|
|
80
81
|
|
|
@@ -493,7 +494,7 @@ def reload_config() -> None:
|
|
|
493
494
|
def parse_and_validate_config_file(config_path: str) -> config_utils.Config:
|
|
494
495
|
config = config_utils.Config()
|
|
495
496
|
try:
|
|
496
|
-
config_dict =
|
|
497
|
+
config_dict = yaml_utils.read_yaml(config_path)
|
|
497
498
|
config = config_utils.Config.from_dict(config_dict)
|
|
498
499
|
# pop the db url from the config, and set it to the env var.
|
|
499
500
|
# this is to avoid db url (considered a sensitive value)
|
|
@@ -503,7 +504,7 @@ def parse_and_validate_config_file(config_path: str) -> config_utils.Config:
|
|
|
503
504
|
os.environ[constants.ENV_VAR_DB_CONNECTION_URI] = db_url
|
|
504
505
|
if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
|
|
505
506
|
logger.debug(f'Config loaded from {config_path}:\n'
|
|
506
|
-
f'{
|
|
507
|
+
f'{yaml_utils.dump_yaml_str(dict(config))}')
|
|
507
508
|
except yaml.YAMLError as e:
|
|
508
509
|
logger.error(f'Error in loading config file ({config_path}):', e)
|
|
509
510
|
if config:
|
|
@@ -532,7 +533,7 @@ def _parse_dotlist(dotlist: List[str]) -> config_utils.Config:
|
|
|
532
533
|
if len(key) == 0 or len(value) == 0:
|
|
533
534
|
raise ValueError(f'Invalid config override: {arg}. '
|
|
534
535
|
'Please use the format: key=value')
|
|
535
|
-
value =
|
|
536
|
+
value = yaml_utils.safe_load(value)
|
|
536
537
|
nested_keys = tuple(key.split('.'))
|
|
537
538
|
config.set_nested(nested_keys, value)
|
|
538
539
|
return config
|
|
@@ -585,7 +586,8 @@ def _reload_config_as_server() -> None:
|
|
|
585
586
|
row = session.query(config_yaml_table).filter_by(
|
|
586
587
|
key=key).first()
|
|
587
588
|
if row:
|
|
588
|
-
db_config = config_utils.Config(
|
|
589
|
+
db_config = config_utils.Config(
|
|
590
|
+
yaml_utils.safe_load(row.value))
|
|
589
591
|
db_config.pop_nested(('db',), None)
|
|
590
592
|
return db_config
|
|
591
593
|
return None
|
|
@@ -598,7 +600,7 @@ def _reload_config_as_server() -> None:
|
|
|
598
600
|
sqlalchemy_engine.dispose()
|
|
599
601
|
if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
|
|
600
602
|
logger.debug(f'server config: \n'
|
|
601
|
-
f'{
|
|
603
|
+
f'{yaml_utils.dump_yaml_str(dict(server_config))}')
|
|
602
604
|
_set_loaded_config(server_config)
|
|
603
605
|
_set_loaded_config_path(server_config_path)
|
|
604
606
|
|
|
@@ -626,7 +628,7 @@ def _reload_config_as_client() -> None:
|
|
|
626
628
|
if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
|
|
627
629
|
logger.debug(
|
|
628
630
|
f'client config (before task and CLI overrides): \n'
|
|
629
|
-
f'{
|
|
631
|
+
f'{yaml_utils.dump_yaml_str(dict(overlaid_client_config))}')
|
|
630
632
|
_set_loaded_config(overlaid_client_config)
|
|
631
633
|
_set_loaded_config_path([user_config_path, project_config_path])
|
|
632
634
|
|
|
@@ -736,9 +738,9 @@ def override_skypilot_config(
|
|
|
736
738
|
'Failed to override the SkyPilot config on API '
|
|
737
739
|
'server with your local SkyPilot config:\n'
|
|
738
740
|
'=== SkyPilot config on API server ===\n'
|
|
739
|
-
f'{
|
|
741
|
+
f'{yaml_utils.dump_yaml_str(dict(original_config))}\n'
|
|
740
742
|
'=== Your local SkyPilot config ===\n'
|
|
741
|
-
f'{
|
|
743
|
+
f'{yaml_utils.dump_yaml_str(dict(override_configs))}\n'
|
|
742
744
|
f'Details: {e}') from e
|
|
743
745
|
finally:
|
|
744
746
|
_set_loaded_config(original_config)
|
|
@@ -765,7 +767,7 @@ def replace_skypilot_config(new_configs: config_utils.Config) -> Iterator[None]:
|
|
|
765
767
|
mode='w',
|
|
766
768
|
prefix='mutated-skypilot-config-',
|
|
767
769
|
suffix='.yaml') as temp_file:
|
|
768
|
-
|
|
770
|
+
yaml_utils.dump_yaml(temp_file.name, dict(**new_configs))
|
|
769
771
|
# Modify the env var of current process or context so that the
|
|
770
772
|
# new config will be used by spawned sub-processes.
|
|
771
773
|
# Note that this code modifies os.environ directly because it
|
|
@@ -829,7 +831,7 @@ def apply_cli_config(cli_config: Optional[List[str]]) -> Dict[str, Any]:
|
|
|
829
831
|
parsed_config = _compose_cli_config(cli_config)
|
|
830
832
|
if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
|
|
831
833
|
logger.debug(f'applying following CLI overrides: \n'
|
|
832
|
-
f'{
|
|
834
|
+
f'{yaml_utils.dump_yaml_str(dict(parsed_config))}')
|
|
833
835
|
_set_loaded_config(
|
|
834
836
|
overlay_skypilot_config(original_config=_get_loaded_config(),
|
|
835
837
|
override_configs=parsed_config))
|
|
@@ -873,7 +875,7 @@ def update_api_server_config_no_lock(config: config_utils.Config) -> None:
|
|
|
873
875
|
def _set_config_yaml_to_db(key: str,
|
|
874
876
|
config: config_utils.Config):
|
|
875
877
|
assert sqlalchemy_engine is not None
|
|
876
|
-
config_str =
|
|
878
|
+
config_str = yaml_utils.dump_yaml_str(dict(config))
|
|
877
879
|
with orm.Session(sqlalchemy_engine) as session:
|
|
878
880
|
if (sqlalchemy_engine.dialect.name ==
|
|
879
881
|
db_utils.SQLAlchemyDialect.SQLITE.value):
|
|
@@ -899,7 +901,7 @@ def update_api_server_config_no_lock(config: config_utils.Config) -> None:
|
|
|
899
901
|
|
|
900
902
|
if not db_updated:
|
|
901
903
|
# save to the local file (PVC in Kubernetes, local file otherwise)
|
|
902
|
-
|
|
904
|
+
yaml_utils.dump_yaml(global_config_path, dict(config))
|
|
903
905
|
|
|
904
906
|
if config_map_utils.is_running_in_kubernetes():
|
|
905
907
|
# In Kubernetes, sync the PVC config to ConfigMap for user
|
sky/ssh_node_pools/core.py
CHANGED
|
@@ -5,6 +5,8 @@ from typing import Any, Dict, List
|
|
|
5
5
|
|
|
6
6
|
import yaml
|
|
7
7
|
|
|
8
|
+
from sky.utils import yaml_utils
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
class SSHNodePoolManager:
|
|
10
12
|
"""Manager for SSH Node Pool configurations."""
|
|
@@ -21,7 +23,7 @@ class SSHNodePoolManager:
|
|
|
21
23
|
|
|
22
24
|
try:
|
|
23
25
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
24
|
-
return
|
|
26
|
+
return yaml_utils.safe_load(f) or {}
|
|
25
27
|
except Exception as e:
|
|
26
28
|
raise RuntimeError(
|
|
27
29
|
f'Failed to read SSH Node Pool config: {e}') from e
|
sky/task.py
CHANGED
|
@@ -4,7 +4,6 @@ import inspect
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
-
import typing
|
|
8
7
|
from typing import (Any, Callable, Dict, Iterable, List, Optional, Set, Tuple,
|
|
9
8
|
Union)
|
|
10
9
|
|
|
@@ -15,7 +14,6 @@ from sky import dag as dag_lib
|
|
|
15
14
|
from sky import exceptions
|
|
16
15
|
from sky import resources as resources_lib
|
|
17
16
|
from sky import sky_logging
|
|
18
|
-
from sky.adaptors import common as adaptors_common
|
|
19
17
|
from sky.data import data_utils
|
|
20
18
|
from sky.data import storage as storage_lib
|
|
21
19
|
from sky.provision import docker_utils
|
|
@@ -26,11 +24,7 @@ from sky.utils import registry
|
|
|
26
24
|
from sky.utils import schemas
|
|
27
25
|
from sky.utils import ux_utils
|
|
28
26
|
from sky.utils import volume as volume_lib
|
|
29
|
-
|
|
30
|
-
if typing.TYPE_CHECKING:
|
|
31
|
-
import yaml
|
|
32
|
-
else:
|
|
33
|
-
yaml = adaptors_common.LazyImport('yaml')
|
|
27
|
+
from sky.utils import yaml_utils
|
|
34
28
|
|
|
35
29
|
logger = sky_logging.init_logger(__name__)
|
|
36
30
|
|
|
@@ -570,7 +564,7 @@ class Task:
|
|
|
570
564
|
secrets_overrides: Optional[List[Tuple[str, str]]] = None,
|
|
571
565
|
) -> 'Task':
|
|
572
566
|
user_specified_yaml = config.pop('_user_specified_yaml',
|
|
573
|
-
|
|
567
|
+
yaml_utils.dump_yaml_str(config))
|
|
574
568
|
# More robust handling for 'envs': explicitly convert keys and values to
|
|
575
569
|
# str, since users may pass '123' as keys/values which will get parsed
|
|
576
570
|
# as int causing validate_schema() to fail.
|
|
@@ -836,7 +830,7 @@ class Task:
|
|
|
836
830
|
# https://github.com/yaml/pyyaml/issues/165#issuecomment-430074049
|
|
837
831
|
# to raise errors on duplicate keys.
|
|
838
832
|
user_specified_yaml = f.read()
|
|
839
|
-
config =
|
|
833
|
+
config = yaml_utils.safe_load(user_specified_yaml)
|
|
840
834
|
|
|
841
835
|
if isinstance(config, str):
|
|
842
836
|
with ux_utils.print_exception_no_traceback():
|
|
@@ -1611,7 +1605,7 @@ class Task:
|
|
|
1611
1605
|
if use_user_specified_yaml:
|
|
1612
1606
|
if self._user_specified_yaml is None:
|
|
1613
1607
|
return self._to_yaml_config(redact_secrets=True)
|
|
1614
|
-
config =
|
|
1608
|
+
config = yaml_utils.safe_load(self._user_specified_yaml)
|
|
1615
1609
|
if config.get('secrets') is not None:
|
|
1616
1610
|
config['secrets'] = {k: '<redacted>' for k in config['secrets']}
|
|
1617
1611
|
return config
|
sky/templates/nebius-ray.yml.j2
CHANGED
|
@@ -56,15 +56,11 @@ available_node_types:
|
|
|
56
56
|
filesystem_mount_path: {{ fs.filesystem_mount_path }}
|
|
57
57
|
{%- endfor %}
|
|
58
58
|
UserData: |
|
|
59
|
-
runcmd:
|
|
60
|
-
- sudo sed -i 's/^#\?AllowTcpForwarding.*/AllowTcpForwarding yes/' /etc/ssh/sshd_config
|
|
61
|
-
- systemctl restart sshd
|
|
62
|
-
|
|
63
59
|
{# Two available OS images:
|
|
64
|
-
1.
|
|
65
|
-
2.
|
|
66
|
-
To optimize deployment speed, Docker is only installed when using
|
|
67
|
-
{%- if docker_image is not none and image_id
|
|
60
|
+
1. ubuntu24.04-driverless - requires Docker installation
|
|
61
|
+
2. ubuntu24.04-cuda12 - comes with Docker pre-installed
|
|
62
|
+
To optimize deployment speed, Docker is only installed when using ubuntu24.04-driverless #}
|
|
63
|
+
{%- if docker_image is not none and image_id.endswith('-driverless') %}
|
|
68
64
|
apt:
|
|
69
65
|
sources:
|
|
70
66
|
docker.list:
|
sky/usage/usage_lib.py
CHANGED
|
@@ -19,6 +19,7 @@ from sky.usage import constants
|
|
|
19
19
|
from sky.utils import common_utils
|
|
20
20
|
from sky.utils import env_options
|
|
21
21
|
from sky.utils import ux_utils
|
|
22
|
+
from sky.utils import yaml_utils
|
|
22
23
|
|
|
23
24
|
if typing.TYPE_CHECKING:
|
|
24
25
|
import inspect
|
|
@@ -402,7 +403,7 @@ def _clean_yaml(yaml_info: Dict[str, Optional[str]]):
|
|
|
402
403
|
contents = inspect.getsource(contents)
|
|
403
404
|
|
|
404
405
|
if type(contents) in constants.USAGE_MESSAGE_REDACT_TYPES:
|
|
405
|
-
lines =
|
|
406
|
+
lines = yaml_utils.dump_yaml_str({
|
|
406
407
|
redact_type: contents
|
|
407
408
|
}).strip().split('\n')
|
|
408
409
|
message = (f'{len(lines)} lines {redact_type.upper()}'
|
|
@@ -431,7 +432,7 @@ def prepare_json_from_yaml_config(
|
|
|
431
432
|
with open(yaml_config_or_path, 'r', encoding='utf-8') as f:
|
|
432
433
|
lines = f.readlines()
|
|
433
434
|
comment_lines = [line for line in lines if line.startswith('#')]
|
|
434
|
-
yaml_info =
|
|
435
|
+
yaml_info = yaml_utils.read_yaml_all(yaml_config_or_path)
|
|
435
436
|
|
|
436
437
|
for i in range(len(yaml_info)):
|
|
437
438
|
if yaml_info[i] is None:
|
sky/users/server.py
CHANGED
|
@@ -10,11 +10,11 @@ from typing import Any, Dict, Generator, List
|
|
|
10
10
|
|
|
11
11
|
import fastapi
|
|
12
12
|
import filelock
|
|
13
|
-
from passlib.hash import apr_md5_crypt
|
|
14
13
|
|
|
15
14
|
from sky import global_user_state
|
|
16
15
|
from sky import models
|
|
17
16
|
from sky import sky_logging
|
|
17
|
+
from sky.server import common as server_common
|
|
18
18
|
from sky.server.requests import payloads
|
|
19
19
|
from sky.skylet import constants
|
|
20
20
|
from sky.users import permission
|
|
@@ -86,7 +86,7 @@ async def user_create(user_create_body: payloads.UserCreateBody) -> None:
|
|
|
86
86
|
role = rbac.get_default_role()
|
|
87
87
|
|
|
88
88
|
# Create user
|
|
89
|
-
password_hash =
|
|
89
|
+
password_hash = server_common.crypt_ctx.hash(password)
|
|
90
90
|
user_hash = hashlib.md5(
|
|
91
91
|
username.encode()).hexdigest()[:common_utils.USER_HASH_LENGTH]
|
|
92
92
|
with _user_lock(user_hash):
|
|
@@ -146,7 +146,7 @@ async def user_update(request: fastapi.Request,
|
|
|
146
146
|
|
|
147
147
|
with _user_lock(user_info.id):
|
|
148
148
|
if password:
|
|
149
|
-
password_hash =
|
|
149
|
+
password_hash = server_common.crypt_ctx.hash(password)
|
|
150
150
|
global_user_state.add_or_update_user(
|
|
151
151
|
models.User(id=user_info.id,
|
|
152
152
|
name=user_info.name,
|
|
@@ -271,13 +271,13 @@ async def user_import(
|
|
|
271
271
|
creation_errors.append(f'{username}: User already exists')
|
|
272
272
|
continue
|
|
273
273
|
|
|
274
|
-
# Check if password is already hashed
|
|
275
|
-
if
|
|
274
|
+
# Check if password is already hashed
|
|
275
|
+
if server_common.crypt_ctx.identify(password) is not None:
|
|
276
276
|
# Password is already hashed, use it directly
|
|
277
277
|
password_hash = password
|
|
278
278
|
else:
|
|
279
279
|
# Password is plain text, hash it
|
|
280
|
-
password_hash =
|
|
280
|
+
password_hash = server_common.crypt_ctx.hash(password)
|
|
281
281
|
|
|
282
282
|
user_hash = hashlib.md5(
|
|
283
283
|
username.encode()).hexdigest()[:common_utils.USER_HASH_LENGTH]
|
sky/utils/common_utils.py
CHANGED
|
@@ -6,7 +6,6 @@ import functools
|
|
|
6
6
|
import getpass
|
|
7
7
|
import hashlib
|
|
8
8
|
import inspect
|
|
9
|
-
import io
|
|
10
9
|
import os
|
|
11
10
|
import platform
|
|
12
11
|
import random
|
|
@@ -34,11 +33,9 @@ from sky.utils import validator
|
|
|
34
33
|
if typing.TYPE_CHECKING:
|
|
35
34
|
import jinja2
|
|
36
35
|
import psutil
|
|
37
|
-
import yaml
|
|
38
36
|
else:
|
|
39
37
|
jinja2 = adaptors_common.LazyImport('jinja2')
|
|
40
38
|
psutil = adaptors_common.LazyImport('psutil')
|
|
41
|
-
yaml = adaptors_common.LazyImport('yaml')
|
|
42
39
|
|
|
43
40
|
USER_HASH_FILE = os.path.expanduser('~/.sky/user_hash')
|
|
44
41
|
USER_HASH_LENGTH = 8
|
|
@@ -573,74 +570,6 @@ def user_and_hostname_hash() -> str:
|
|
|
573
570
|
return f'{getpass.getuser()}-{hostname_hash}'
|
|
574
571
|
|
|
575
572
|
|
|
576
|
-
def read_yaml(path: Optional[str]) -> Dict[str, Any]:
|
|
577
|
-
if path is None:
|
|
578
|
-
raise ValueError('Attempted to read a None YAML.')
|
|
579
|
-
with open(path, 'r', encoding='utf-8') as f:
|
|
580
|
-
config = yaml.safe_load(f)
|
|
581
|
-
return config
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
def read_yaml_all_str(yaml_str: str) -> List[Dict[str, Any]]:
|
|
585
|
-
stream = io.StringIO(yaml_str)
|
|
586
|
-
config = yaml.safe_load_all(stream)
|
|
587
|
-
configs = list(config)
|
|
588
|
-
if not configs:
|
|
589
|
-
# Empty YAML file.
|
|
590
|
-
return [{}]
|
|
591
|
-
return configs
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
def read_yaml_all(path: str) -> List[Dict[str, Any]]:
|
|
595
|
-
with open(path, 'r', encoding='utf-8') as f:
|
|
596
|
-
return read_yaml_all_str(f.read())
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
def dump_yaml(path: str,
|
|
600
|
-
config: Union[List[Dict[str, Any]], Dict[str, Any]],
|
|
601
|
-
blank: bool = False) -> None:
|
|
602
|
-
"""Dumps a YAML file.
|
|
603
|
-
|
|
604
|
-
Args:
|
|
605
|
-
path: the path to the YAML file.
|
|
606
|
-
config: the configuration to dump.
|
|
607
|
-
"""
|
|
608
|
-
with open(path, 'w', encoding='utf-8') as f:
|
|
609
|
-
contents = dump_yaml_str(config)
|
|
610
|
-
if blank and isinstance(config, dict) and len(config) == 0:
|
|
611
|
-
# when dumping to yaml, an empty dict will go in as {}.
|
|
612
|
-
contents = ''
|
|
613
|
-
f.write(contents)
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
def dump_yaml_str(config: Union[List[Dict[str, Any]], Dict[str, Any]]) -> str:
|
|
617
|
-
"""Dumps a YAML string.
|
|
618
|
-
|
|
619
|
-
Args:
|
|
620
|
-
config: the configuration to dump.
|
|
621
|
-
|
|
622
|
-
Returns:
|
|
623
|
-
The YAML string.
|
|
624
|
-
"""
|
|
625
|
-
|
|
626
|
-
# https://github.com/yaml/pyyaml/issues/127
|
|
627
|
-
class LineBreakDumper(yaml.SafeDumper):
|
|
628
|
-
|
|
629
|
-
def write_line_break(self, data=None):
|
|
630
|
-
super().write_line_break(data)
|
|
631
|
-
if len(self.indents) == 1:
|
|
632
|
-
super().write_line_break()
|
|
633
|
-
|
|
634
|
-
if isinstance(config, list):
|
|
635
|
-
dump_func = yaml.dump_all # type: ignore
|
|
636
|
-
else:
|
|
637
|
-
dump_func = yaml.dump # type: ignore
|
|
638
|
-
return dump_func(config,
|
|
639
|
-
Dumper=LineBreakDumper,
|
|
640
|
-
sort_keys=False,
|
|
641
|
-
default_flow_style=False)
|
|
642
|
-
|
|
643
|
-
|
|
644
573
|
def make_decorator(cls, name_or_fn: Union[str, Callable],
|
|
645
574
|
**ctx_kwargs) -> Callable:
|
|
646
575
|
"""Make the cls a decorator.
|