skypilot-nightly 1.0.0.dev20250825__py3-none-any.whl → 1.0.0.dev20250827__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/authentication.py +3 -9
- sky/backends/backend_utils.py +30 -43
- sky/backends/cloud_vm_ray_backend.py +37 -4
- sky/client/cli/command.py +2 -1
- sky/client/common.py +41 -14
- sky/client/sdk.py +1 -1
- 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/dashboard/out/404.html +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 +103 -11
- sky/provision/kubernetes/config.py +2 -8
- sky/provision/kubernetes/instance.py +6 -0
- sky/provision/kubernetes/network_utils.py +3 -4
- sky/provision/kubernetes/utils.py +9 -5
- sky/provision/nebius/utils.py +15 -7
- sky/provision/vsphere/vsphere_utils.py +2 -8
- sky/schemas/api/responses.py +7 -0
- sky/serve/serve_utils.py +2 -2
- sky/serve/service_spec.py +2 -8
- sky/server/auth/authn.py +4 -0
- sky/server/common.py +7 -1
- sky/server/requests/executor.py +4 -0
- sky/server/rest.py +11 -8
- sky/server/server.py +18 -33
- sky/server/uvicorn.py +33 -0
- sky/setup_files/dependencies.py +1 -0
- sky/sky_logging.py +4 -1
- sky/skylet/events.py +2 -2
- sky/skypilot_config.py +4 -2
- sky/ssh_node_pools/core.py +3 -1
- sky/task.py +3 -9
- sky/users/server.py +6 -6
- sky/utils/common_utils.py +3 -2
- sky/utils/yaml_utils.py +35 -0
- sky/volumes/volume.py +8 -3
- {skypilot_nightly-1.0.0.dev20250825.dist-info → skypilot_nightly-1.0.0.dev20250827.dist-info}/METADATA +2 -1
- {skypilot_nightly-1.0.0.dev20250825.dist-info → skypilot_nightly-1.0.0.dev20250827.dist-info}/RECORD +61 -60
- /sky/dashboard/out/_next/static/{n7XGGtvnHqbVUS8eayoGG → -eL7Ky3bxVivzeLHNB9U6}/_buildManifest.js +0 -0
- /sky/dashboard/out/_next/static/{n7XGGtvnHqbVUS8eayoGG → -eL7Ky3bxVivzeLHNB9U6}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250825.dist-info → skypilot_nightly-1.0.0.dev20250827.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250825.dist-info → skypilot_nightly-1.0.0.dev20250827.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250825.dist-info → skypilot_nightly-1.0.0.dev20250827.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250825.dist-info → skypilot_nightly-1.0.0.dev20250827.dist-info}/top_level.txt +0 -0
sky/server/rest.py
CHANGED
|
@@ -48,6 +48,13 @@ _session.headers[constants.API_VERSION_HEADER] = str(constants.API_VERSION)
|
|
|
48
48
|
_session.headers[constants.VERSION_HEADER] = (
|
|
49
49
|
versions.get_local_readable_version())
|
|
50
50
|
|
|
51
|
+
# Enumerate error types that might be transient and can be addressed by
|
|
52
|
+
# retrying.
|
|
53
|
+
_transient_errors = [
|
|
54
|
+
requests.exceptions.RequestException,
|
|
55
|
+
ConnectionError,
|
|
56
|
+
]
|
|
57
|
+
|
|
51
58
|
|
|
52
59
|
class RetryContext:
|
|
53
60
|
|
|
@@ -87,14 +94,10 @@ def retry_transient_errors(max_retries: int = 3,
|
|
|
87
94
|
if isinstance(e, requests.exceptions.HTTPError):
|
|
88
95
|
# Only server error is considered as transient.
|
|
89
96
|
return e.response.status_code >= 500
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# all other errors might be transient since we only retry for 3 times
|
|
95
|
-
# by default. For permanent errors that we do not know now, we can
|
|
96
|
-
# exclude them here in the future.
|
|
97
|
-
return True
|
|
97
|
+
for error in _transient_errors:
|
|
98
|
+
if isinstance(e, error):
|
|
99
|
+
return True
|
|
100
|
+
return False
|
|
98
101
|
|
|
99
102
|
def decorator(func: F) -> F:
|
|
100
103
|
|
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:
|
|
@@ -1859,6 +1842,8 @@ if __name__ == '__main__':
|
|
|
1859
1842
|
|
|
1860
1843
|
from sky.server import uvicorn as skyuvicorn
|
|
1861
1844
|
|
|
1845
|
+
skyuvicorn.add_timestamp_prefix_for_server_logs()
|
|
1846
|
+
|
|
1862
1847
|
# Initialize global user state db
|
|
1863
1848
|
global_user_state.initialize_and_get_db()
|
|
1864
1849
|
# 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
|
|
@@ -24,6 +23,7 @@ from sky.utils import cluster_utils
|
|
|
24
23
|
from sky.utils import common_utils
|
|
25
24
|
from sky.utils import registry
|
|
26
25
|
from sky.utils import ux_utils
|
|
26
|
+
from sky.utils import yaml_utils
|
|
27
27
|
|
|
28
28
|
# Seconds of sleep between the processing of skylet events.
|
|
29
29
|
EVENT_CHECKING_INTERVAL_SECONDS = 20
|
|
@@ -309,7 +309,7 @@ class AutostopEvent(SkyletEvent):
|
|
|
309
309
|
else:
|
|
310
310
|
yaml_str = self._CATCH_NODES.sub(r'cache_stopped_nodes: true',
|
|
311
311
|
yaml_str)
|
|
312
|
-
config =
|
|
312
|
+
config = yaml_utils.safe_load(yaml_str)
|
|
313
313
|
# Set the private key with the existed key on the remote instance.
|
|
314
314
|
config['auth']['ssh_private_key'] = '~/ray_bootstrap_key.pem'
|
|
315
315
|
# NOTE: We must do this, otherwise with ssh_proxy_command still under
|
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
|
|
|
@@ -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
|
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
|
|
|
@@ -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/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
|
@@ -30,6 +30,7 @@ from sky.usage import constants as usage_constants
|
|
|
30
30
|
from sky.utils import annotations
|
|
31
31
|
from sky.utils import ux_utils
|
|
32
32
|
from sky.utils import validator
|
|
33
|
+
from sky.utils import yaml_utils
|
|
33
34
|
|
|
34
35
|
if typing.TYPE_CHECKING:
|
|
35
36
|
import jinja2
|
|
@@ -577,13 +578,13 @@ def read_yaml(path: Optional[str]) -> Dict[str, Any]:
|
|
|
577
578
|
if path is None:
|
|
578
579
|
raise ValueError('Attempted to read a None YAML.')
|
|
579
580
|
with open(path, 'r', encoding='utf-8') as f:
|
|
580
|
-
config =
|
|
581
|
+
config = yaml_utils.safe_load(f)
|
|
581
582
|
return config
|
|
582
583
|
|
|
583
584
|
|
|
584
585
|
def read_yaml_all_str(yaml_str: str) -> List[Dict[str, Any]]:
|
|
585
586
|
stream = io.StringIO(yaml_str)
|
|
586
|
-
config =
|
|
587
|
+
config = yaml_utils.safe_load_all(stream)
|
|
587
588
|
configs = list(config)
|
|
588
589
|
if not configs:
|
|
589
590
|
# Empty YAML file.
|
sky/utils/yaml_utils.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""YAML utilities."""
|
|
2
|
+
from typing import Any, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from sky.adaptors import common
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
import yaml
|
|
8
|
+
else:
|
|
9
|
+
yaml = common.LazyImport('yaml')
|
|
10
|
+
|
|
11
|
+
_csafe_loader_import_error = False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def safe_load(stream) -> Any:
|
|
15
|
+
global _csafe_loader_import_error
|
|
16
|
+
if _csafe_loader_import_error:
|
|
17
|
+
return yaml.load(stream, Loader=yaml.SafeLoader)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
return yaml.load(stream, Loader=yaml.CSafeLoader)
|
|
21
|
+
except ImportError:
|
|
22
|
+
_csafe_loader_import_error = True
|
|
23
|
+
return yaml.load(stream, Loader=yaml.SafeLoader)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def safe_load_all(stream) -> Any:
|
|
27
|
+
global _csafe_loader_import_error
|
|
28
|
+
if _csafe_loader_import_error:
|
|
29
|
+
return yaml.load_all(stream, Loader=yaml.SafeLoader)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
return yaml.load_all(stream, Loader=yaml.CSafeLoader)
|
|
33
|
+
except ImportError:
|
|
34
|
+
_csafe_loader_import_error = True
|
|
35
|
+
return yaml.load_all(stream, Loader=yaml.SafeLoader)
|
sky/volumes/volume.py
CHANGED
|
@@ -125,14 +125,19 @@ class Volume:
|
|
|
125
125
|
|
|
126
126
|
def _validate_config(self) -> None:
|
|
127
127
|
"""Validate the volume config."""
|
|
128
|
+
assert self.cloud is not None, 'Cloud must be specified'
|
|
129
|
+
cloud_obj = registry.CLOUD_REGISTRY.from_str(self.cloud)
|
|
130
|
+
assert cloud_obj is not None
|
|
131
|
+
|
|
132
|
+
valid, err_msg = cloud_obj.is_volume_name_valid(self.name)
|
|
133
|
+
if not valid:
|
|
134
|
+
raise ValueError(f'Invalid volume name: {err_msg}')
|
|
135
|
+
|
|
128
136
|
if not self.resource_name and not self.size:
|
|
129
137
|
raise ValueError('Size is required for new volumes. '
|
|
130
138
|
'Please specify the size in the YAML file or '
|
|
131
139
|
'use the --size flag.')
|
|
132
140
|
if self.labels:
|
|
133
|
-
assert self.cloud is not None, 'Cloud must be specified'
|
|
134
|
-
cloud_obj = registry.CLOUD_REGISTRY.from_str(self.cloud)
|
|
135
|
-
assert cloud_obj is not None
|
|
136
141
|
for key, value in self.labels.items():
|
|
137
142
|
valid, err_msg = cloud_obj.is_label_valid(key, value)
|
|
138
143
|
if not valid:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skypilot-nightly
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev20250827
|
|
4
4
|
Summary: SkyPilot: Run AI on Any Infra — Unified, Faster, Cheaper.
|
|
5
5
|
Author: SkyPilot Team
|
|
6
6
|
License: Apache 2.0
|
|
@@ -53,6 +53,7 @@ Requires-Dist: casbin
|
|
|
53
53
|
Requires-Dist: sqlalchemy_adapter
|
|
54
54
|
Requires-Dist: prometheus_client>=0.8.0
|
|
55
55
|
Requires-Dist: passlib
|
|
56
|
+
Requires-Dist: bcrypt
|
|
56
57
|
Requires-Dist: pyjwt
|
|
57
58
|
Requires-Dist: gitpython
|
|
58
59
|
Requires-Dist: types-paramiko
|