skypilot-nightly 1.0.0.dev20251027__py3-none-any.whl → 1.0.0.dev20251029__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/adaptors/coreweave.py +278 -0
- sky/backends/backend_utils.py +9 -6
- sky/backends/cloud_vm_ray_backend.py +2 -3
- sky/check.py +25 -13
- sky/client/cli/command.py +5 -1
- sky/cloud_stores.py +73 -0
- sky/core.py +7 -5
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/{YP5Vc3ROcDnTGta0XAhcs → DabuSAKsc_y0wyJxpTIdQ}/_buildManifest.js +1 -1
- sky/dashboard/out/_next/static/chunks/{1141-d5204f35a3388bf4.js → 1141-c3c10e2c6ed71a8f.js} +1 -1
- sky/dashboard/out/_next/static/chunks/2755.a239c652bf8684dd.js +26 -0
- sky/dashboard/out/_next/static/chunks/3294.87a13fba0058865b.js +1 -0
- sky/dashboard/out/_next/static/chunks/{3785.538eb23a098fc304.js → 3785.170be320e0060eaf.js} +1 -1
- sky/dashboard/out/_next/static/chunks/4282-49b2065b7336e496.js +1 -0
- sky/dashboard/out/_next/static/chunks/7615-80aa7b09f45a86d2.js +1 -0
- sky/dashboard/out/_next/static/chunks/8969-4ed9236db997b42b.js +1 -0
- sky/dashboard/out/_next/static/chunks/9360.10a3aac7aad5e3aa.js +31 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-ac4a217f17b087cb.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-fbf2907ce2bb67e2.js → [cluster]-1704039ccaf997cf.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{jobs-0dc34cf9a8710a9f.js → jobs-7eee823559e5cf9f.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{users-96d6b8bb2dec055f.js → users-2b172f13f8538a7a.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-fb1b4d3bfb047cad.js → [name]-bbfe5860c93470fd.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{workspaces-6fc994fa1ee6c6bf.js → workspaces-1891376c08050940.js} +1 -1
- sky/dashboard/out/_next/static/chunks/{webpack-585d805f693dbceb.js → webpack-485984ca04e021d0.js} +1 -1
- sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -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 +39 -0
- sky/data/storage.py +166 -9
- sky/global_user_state.py +14 -18
- sky/jobs/server/server.py +2 -2
- sky/jobs/utils.py +5 -6
- sky/optimizer.py +1 -1
- sky/provision/kubernetes/instance.py +88 -19
- sky/provision/kubernetes/volume.py +2 -2
- sky/schemas/api/responses.py +2 -5
- sky/serve/replica_managers.py +2 -2
- sky/serve/serve_utils.py +9 -2
- sky/server/requests/payloads.py +2 -0
- sky/server/requests/requests.py +137 -102
- sky/server/requests/serializers/decoders.py +0 -6
- sky/server/requests/serializers/encoders.py +33 -6
- sky/server/server.py +2 -1
- sky/server/stream_utils.py +56 -13
- sky/setup_files/dependencies.py +2 -0
- sky/task.py +10 -0
- sky/templates/nebius-ray.yml.j2 +1 -0
- sky/utils/cli_utils/status_utils.py +8 -2
- sky/utils/context_utils.py +13 -1
- sky/utils/resources_utils.py +53 -29
- {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/METADATA +52 -36
- {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/RECORD +73 -72
- sky/dashboard/out/_next/static/chunks/2755.227c84f5adf75c6b.js +0 -26
- sky/dashboard/out/_next/static/chunks/3015-2dcace420c8939f4.js +0 -1
- sky/dashboard/out/_next/static/chunks/3294.6d5054a953a818cb.js +0 -1
- sky/dashboard/out/_next/static/chunks/4282-d2f3ef2fbf78e347.js +0 -1
- sky/dashboard/out/_next/static/chunks/8969-0389e2cb52412db3.js +0 -1
- sky/dashboard/out/_next/static/chunks/9360.07d78b8552bc9d17.js +0 -31
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-c815b90e296b8075.js +0 -16
- sky/dashboard/out/_next/static/css/4c052b4444e52a58.css +0 -3
- /sky/dashboard/out/_next/static/{YP5Vc3ROcDnTGta0XAhcs → DabuSAKsc_y0wyJxpTIdQ}/_ssgManifest.js +0 -0
- /sky/dashboard/out/_next/static/chunks/pages/{_app-513d332313670f2a.js → _app-bde01e4a2beec258.js} +0 -0
- {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/top_level.txt +0 -0
sky/__init__.py
CHANGED
|
@@ -7,7 +7,7 @@ import urllib.request
|
|
|
7
7
|
from sky.utils import directory_utils
|
|
8
8
|
|
|
9
9
|
# Replaced with the current commit when building the wheels.
|
|
10
|
-
_SKYPILOT_COMMIT_SHA = '
|
|
10
|
+
_SKYPILOT_COMMIT_SHA = '501e7213a39f27c1a11db3aade5ef878b47bf066'
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def _get_git_commit():
|
|
@@ -37,7 +37,7 @@ def _get_git_commit():
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
__commit__ = _get_git_commit()
|
|
40
|
-
__version__ = '1.0.0.
|
|
40
|
+
__version__ = '1.0.0.dev20251029'
|
|
41
41
|
__root_dir__ = directory_utils.get_sky_dir()
|
|
42
42
|
|
|
43
43
|
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""CoreWeave cloud adaptor."""
|
|
2
|
+
|
|
3
|
+
import configparser
|
|
4
|
+
import contextlib
|
|
5
|
+
import os
|
|
6
|
+
import threading
|
|
7
|
+
from typing import Dict, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
from sky import exceptions
|
|
10
|
+
from sky import sky_logging
|
|
11
|
+
from sky.adaptors import common
|
|
12
|
+
from sky.clouds import cloud
|
|
13
|
+
from sky.utils import annotations
|
|
14
|
+
from sky.utils import ux_utils
|
|
15
|
+
|
|
16
|
+
logger = sky_logging.init_logger(__name__)
|
|
17
|
+
|
|
18
|
+
COREWEAVE_PROFILE_NAME = 'cw'
|
|
19
|
+
COREWEAVE_CREDENTIALS_PATH = '~/.coreweave/cw.credentials'
|
|
20
|
+
COREWEAVE_CONFIG_PATH = '~/.coreweave/cw.config'
|
|
21
|
+
NAME = 'CoreWeave'
|
|
22
|
+
DEFAULT_REGION = 'US-EAST-01A'
|
|
23
|
+
_DEFAULT_ENDPOINT = 'https://cwobject.com'
|
|
24
|
+
_INDENT_PREFIX = ' '
|
|
25
|
+
|
|
26
|
+
_IMPORT_ERROR_MESSAGE = ('Failed to import dependencies for CoreWeave.'
|
|
27
|
+
'Try pip install "skypilot[coreweave]"')
|
|
28
|
+
|
|
29
|
+
boto3 = common.LazyImport('boto3', import_error_message=_IMPORT_ERROR_MESSAGE)
|
|
30
|
+
botocore = common.LazyImport('botocore',
|
|
31
|
+
import_error_message=_IMPORT_ERROR_MESSAGE)
|
|
32
|
+
|
|
33
|
+
_LAZY_MODULES = (boto3, botocore)
|
|
34
|
+
_session_creation_lock = threading.RLock()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@contextlib.contextmanager
|
|
38
|
+
def _load_cw_credentials_env():
|
|
39
|
+
"""Context manager to temporarily change the AWS credentials file path."""
|
|
40
|
+
prev_credentials_path = os.environ.get('AWS_SHARED_CREDENTIALS_FILE')
|
|
41
|
+
prev_config_path = os.environ.get('AWS_CONFIG_FILE')
|
|
42
|
+
os.environ['AWS_SHARED_CREDENTIALS_FILE'] = COREWEAVE_CREDENTIALS_PATH
|
|
43
|
+
os.environ['AWS_CONFIG_FILE'] = COREWEAVE_CONFIG_PATH
|
|
44
|
+
try:
|
|
45
|
+
yield
|
|
46
|
+
finally:
|
|
47
|
+
if prev_credentials_path is None:
|
|
48
|
+
del os.environ['AWS_SHARED_CREDENTIALS_FILE']
|
|
49
|
+
else:
|
|
50
|
+
os.environ['AWS_SHARED_CREDENTIALS_FILE'] = prev_credentials_path
|
|
51
|
+
if prev_config_path is None:
|
|
52
|
+
del os.environ['AWS_CONFIG_FILE']
|
|
53
|
+
else:
|
|
54
|
+
os.environ['AWS_CONFIG_FILE'] = prev_config_path
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_coreweave_credentials(boto3_session):
|
|
58
|
+
"""Gets the CoreWeave credentials from the boto3 session object.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
boto3_session: The boto3 session object.
|
|
62
|
+
Returns:
|
|
63
|
+
botocore.credentials.ReadOnlyCredentials object with the CoreWeave
|
|
64
|
+
credentials.
|
|
65
|
+
"""
|
|
66
|
+
with _load_cw_credentials_env():
|
|
67
|
+
coreweave_credentials = boto3_session.get_credentials()
|
|
68
|
+
if coreweave_credentials is None:
|
|
69
|
+
with ux_utils.print_exception_no_traceback():
|
|
70
|
+
raise ValueError('CoreWeave credentials not found. Run '
|
|
71
|
+
'`sky check` to verify credentials are '
|
|
72
|
+
'correctly set up.')
|
|
73
|
+
return coreweave_credentials.get_frozen_credentials()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@annotations.lru_cache(scope='global')
|
|
77
|
+
def session():
|
|
78
|
+
"""Create an AWS session for CoreWeave."""
|
|
79
|
+
# Creating the session object is not thread-safe for boto3,
|
|
80
|
+
# so we add a reentrant lock to synchronize the session creation.
|
|
81
|
+
# Reference: https://github.com/boto/boto3/issues/1592
|
|
82
|
+
# However, the session object itself is thread-safe, so we are
|
|
83
|
+
# able to use lru_cache() to cache the session object.
|
|
84
|
+
with _session_creation_lock:
|
|
85
|
+
with _load_cw_credentials_env():
|
|
86
|
+
session_ = boto3.session.Session(
|
|
87
|
+
profile_name=COREWEAVE_PROFILE_NAME)
|
|
88
|
+
return session_
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@annotations.lru_cache(scope='global')
|
|
92
|
+
def resource(resource_name: str, **kwargs):
|
|
93
|
+
"""Create a CoreWeave resource.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
resource_name: CoreWeave resource name (e.g., 's3').
|
|
97
|
+
kwargs: Other options.
|
|
98
|
+
"""
|
|
99
|
+
# Need to use the resource retrieved from the per-thread session
|
|
100
|
+
# to avoid thread-safety issues (Directly creating the client
|
|
101
|
+
# with boto3.resource() is not thread-safe).
|
|
102
|
+
# Reference: https://stackoverflow.com/a/59635814
|
|
103
|
+
|
|
104
|
+
session_ = session()
|
|
105
|
+
coreweave_credentials = get_coreweave_credentials(session_)
|
|
106
|
+
endpoint = get_endpoint()
|
|
107
|
+
|
|
108
|
+
return session_.resource(
|
|
109
|
+
resource_name,
|
|
110
|
+
endpoint_url=endpoint,
|
|
111
|
+
aws_access_key_id=coreweave_credentials.access_key,
|
|
112
|
+
aws_secret_access_key=coreweave_credentials.secret_key,
|
|
113
|
+
region_name='auto',
|
|
114
|
+
config=botocore.config.Config(s3={'addressing_style': 'virtual'}),
|
|
115
|
+
**kwargs)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@annotations.lru_cache(scope='global')
|
|
119
|
+
def client(service_name: str):
|
|
120
|
+
"""Create CoreWeave client of a certain service.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
service_name: CoreWeave service name (e.g., 's3').
|
|
124
|
+
"""
|
|
125
|
+
# Need to use the client retrieved from the per-thread session
|
|
126
|
+
# to avoid thread-safety issues (Directly creating the client
|
|
127
|
+
# with boto3.client() is not thread-safe).
|
|
128
|
+
# Reference: https://stackoverflow.com/a/59635814
|
|
129
|
+
|
|
130
|
+
session_ = session()
|
|
131
|
+
coreweave_credentials = get_coreweave_credentials(session_)
|
|
132
|
+
endpoint = get_endpoint()
|
|
133
|
+
|
|
134
|
+
return session_.client(
|
|
135
|
+
service_name,
|
|
136
|
+
endpoint_url=endpoint,
|
|
137
|
+
aws_access_key_id=coreweave_credentials.access_key,
|
|
138
|
+
aws_secret_access_key=coreweave_credentials.secret_key,
|
|
139
|
+
region_name='auto',
|
|
140
|
+
config=botocore.config.Config(s3={'addressing_style': 'virtual'}),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@common.load_lazy_modules(_LAZY_MODULES)
|
|
145
|
+
def botocore_exceptions():
|
|
146
|
+
"""AWS botocore exception."""
|
|
147
|
+
# pylint: disable=import-outside-toplevel
|
|
148
|
+
from botocore import exceptions as boto_exceptions
|
|
149
|
+
return boto_exceptions
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_endpoint():
|
|
153
|
+
"""Parse the COREWEAVE_CONFIG_PATH to get the endpoint_url.
|
|
154
|
+
|
|
155
|
+
The config file is an AWS-style config file with format:
|
|
156
|
+
[profile cw]
|
|
157
|
+
endpoint_url = https://cwobject.com
|
|
158
|
+
s3 =
|
|
159
|
+
addressing_style = virtual
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
str: The endpoint URL from the config file, or the default endpoint
|
|
163
|
+
if the file doesn't exist or doesn't contain the endpoint_url.
|
|
164
|
+
"""
|
|
165
|
+
config_path = os.path.expanduser(COREWEAVE_CONFIG_PATH)
|
|
166
|
+
if not os.path.isfile(config_path):
|
|
167
|
+
return _DEFAULT_ENDPOINT
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
config = configparser.ConfigParser()
|
|
171
|
+
config.read(config_path)
|
|
172
|
+
|
|
173
|
+
# Try to get endpoint_url from [profile cw] section
|
|
174
|
+
profile_section = f'profile {COREWEAVE_PROFILE_NAME}'
|
|
175
|
+
if config.has_section(profile_section):
|
|
176
|
+
if config.has_option(profile_section, 'endpoint_url'):
|
|
177
|
+
endpoint = config.get(profile_section, 'endpoint_url')
|
|
178
|
+
return endpoint.strip()
|
|
179
|
+
except (configparser.Error, OSError) as e:
|
|
180
|
+
logger.warning(f'Failed to parse CoreWeave config file: {e}. '
|
|
181
|
+
f'Using default endpoint: {_DEFAULT_ENDPOINT}')
|
|
182
|
+
|
|
183
|
+
return _DEFAULT_ENDPOINT
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def check_credentials(
|
|
187
|
+
cloud_capability: cloud.CloudCapability) -> Tuple[bool, Optional[str]]:
|
|
188
|
+
if cloud_capability == cloud.CloudCapability.STORAGE:
|
|
189
|
+
return check_storage_credentials()
|
|
190
|
+
else:
|
|
191
|
+
raise exceptions.NotSupportedError(
|
|
192
|
+
f'{NAME} does not support {cloud_capability}.')
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def check_storage_credentials() -> Tuple[bool, Optional[str]]:
|
|
196
|
+
"""Checks if the user has access credentials to CoreWeave Object Storage.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
A tuple of a boolean value and a hint message where the bool
|
|
200
|
+
is True when both credentials needed for CoreWeave storage is set.
|
|
201
|
+
It is False when either of those are not set, which would hint with a
|
|
202
|
+
string on unset credential.
|
|
203
|
+
"""
|
|
204
|
+
hints = None
|
|
205
|
+
profile_in_cred = coreweave_profile_in_cred()
|
|
206
|
+
profile_in_config = coreweave_profile_in_config()
|
|
207
|
+
|
|
208
|
+
if not profile_in_cred:
|
|
209
|
+
hints = (f'[{COREWEAVE_PROFILE_NAME}] profile is not set in '
|
|
210
|
+
f'{COREWEAVE_CREDENTIALS_PATH}.')
|
|
211
|
+
if not profile_in_config:
|
|
212
|
+
if hints:
|
|
213
|
+
hints += ' Additionally, '
|
|
214
|
+
else:
|
|
215
|
+
hints = ''
|
|
216
|
+
hints += (f'[{COREWEAVE_PROFILE_NAME}] profile is not set in '
|
|
217
|
+
f'{COREWEAVE_CONFIG_PATH}.')
|
|
218
|
+
|
|
219
|
+
if hints:
|
|
220
|
+
hints += ' Run the following commands:'
|
|
221
|
+
if not profile_in_cred:
|
|
222
|
+
hints += f'\n{_INDENT_PREFIX} $ pip install boto3'
|
|
223
|
+
hints += (f'\n{_INDENT_PREFIX} $ AWS_SHARED_CREDENTIALS_FILE='
|
|
224
|
+
f'{COREWEAVE_CREDENTIALS_PATH} aws configure --profile '
|
|
225
|
+
f'{COREWEAVE_PROFILE_NAME}')
|
|
226
|
+
if not profile_in_config:
|
|
227
|
+
hints += (f'\n{_INDENT_PREFIX} $ AWS_CONFIG_FILE='
|
|
228
|
+
f'{COREWEAVE_CONFIG_PATH} aws configure set endpoint_url'
|
|
229
|
+
f' <ENDPOINT_URL> --profile '
|
|
230
|
+
f'{COREWEAVE_PROFILE_NAME}')
|
|
231
|
+
hints += (f'\n{_INDENT_PREFIX} $ AWS_CONFIG_FILE='
|
|
232
|
+
f'{COREWEAVE_CONFIG_PATH} aws configure set '
|
|
233
|
+
f's3.addressing_style virtual --profile '
|
|
234
|
+
f'{COREWEAVE_PROFILE_NAME}')
|
|
235
|
+
hints += f'\n{_INDENT_PREFIX}For more info: '
|
|
236
|
+
hints += 'https://docs.coreweave.com/docs/products/storage/object-storage/get-started-caios' # pylint: disable=line-too-long
|
|
237
|
+
|
|
238
|
+
return (False, hints) if hints else (True, hints)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def coreweave_profile_in_config() -> bool:
|
|
242
|
+
"""Checks if CoreWeave profile is set in config"""
|
|
243
|
+
conf_path = os.path.expanduser(COREWEAVE_CONFIG_PATH)
|
|
244
|
+
coreweave_profile_exists = False
|
|
245
|
+
if os.path.isfile(conf_path):
|
|
246
|
+
with open(conf_path, 'r', encoding='utf-8') as file:
|
|
247
|
+
for line in file:
|
|
248
|
+
if f'[profile {COREWEAVE_PROFILE_NAME}]' in line:
|
|
249
|
+
coreweave_profile_exists = True
|
|
250
|
+
break
|
|
251
|
+
return coreweave_profile_exists
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def coreweave_profile_in_cred() -> bool:
|
|
255
|
+
"""Checks if CoreWeave profile is set in credentials"""
|
|
256
|
+
cred_path = os.path.expanduser(COREWEAVE_CREDENTIALS_PATH)
|
|
257
|
+
coreweave_profile_exists = False
|
|
258
|
+
if os.path.isfile(cred_path):
|
|
259
|
+
with open(cred_path, 'r', encoding='utf-8') as file:
|
|
260
|
+
for line in file:
|
|
261
|
+
if f'[{COREWEAVE_PROFILE_NAME}]' in line:
|
|
262
|
+
coreweave_profile_exists = True
|
|
263
|
+
break
|
|
264
|
+
return coreweave_profile_exists
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def get_credential_file_mounts() -> Dict[str, str]:
|
|
268
|
+
"""Returns credential file mounts for CoreWeave.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Dict[str, str]: A dictionary mapping source paths to destination paths
|
|
272
|
+
for credential files.
|
|
273
|
+
"""
|
|
274
|
+
coreweave_credential_mounts = {
|
|
275
|
+
COREWEAVE_CREDENTIALS_PATH: COREWEAVE_CREDENTIALS_PATH,
|
|
276
|
+
COREWEAVE_CONFIG_PATH: COREWEAVE_CONFIG_PATH
|
|
277
|
+
}
|
|
278
|
+
return coreweave_credential_mounts
|
sky/backends/backend_utils.py
CHANGED
|
@@ -3157,6 +3157,7 @@ def get_clusters(
|
|
|
3157
3157
|
all_users: bool = True,
|
|
3158
3158
|
include_credentials: bool = False,
|
|
3159
3159
|
summary_response: bool = False,
|
|
3160
|
+
include_handle: bool = True,
|
|
3160
3161
|
# Internal only:
|
|
3161
3162
|
# pylint: disable=invalid-name
|
|
3162
3163
|
_include_is_managed: bool = False,
|
|
@@ -3240,13 +3241,13 @@ def get_clusters(
|
|
|
3240
3241
|
"""Add resource str to record"""
|
|
3241
3242
|
for record in _get_records_with_handle(records):
|
|
3242
3243
|
handle = record['handle']
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
handle,
|
|
3246
|
-
record[
|
|
3247
|
-
'resources_str_full'] = resources_utils.get_readable_resources_repr(
|
|
3248
|
-
handle, simplify=False)
|
|
3244
|
+
resource_str_simple, resource_str_full = (
|
|
3245
|
+
resources_utils.get_readable_resources_repr(
|
|
3246
|
+
handle, simplified_only=summary_response))
|
|
3247
|
+
record['resources_str'] = resource_str_simple
|
|
3249
3248
|
if not summary_response:
|
|
3249
|
+
assert resource_str_full is not None
|
|
3250
|
+
record['resources_str_full'] = resource_str_full
|
|
3250
3251
|
record['cluster_name_on_cloud'] = handle.cluster_name_on_cloud
|
|
3251
3252
|
|
|
3252
3253
|
def _update_records_with_credentials(
|
|
@@ -3313,6 +3314,8 @@ def get_clusters(
|
|
|
3313
3314
|
record['accelerators'] = (
|
|
3314
3315
|
f'{handle.launched_resources.accelerators}'
|
|
3315
3316
|
if handle.launched_resources.accelerators else None)
|
|
3317
|
+
if not include_handle:
|
|
3318
|
+
record.pop('handle', None)
|
|
3316
3319
|
|
|
3317
3320
|
# Add handle info to the records
|
|
3318
3321
|
_update_records_with_handle_info(records)
|
|
@@ -2369,9 +2369,8 @@ class RetryingVmProvisioner(object):
|
|
|
2369
2369
|
for (resource, exception) in resource_exceptions.items():
|
|
2370
2370
|
table.add_row([
|
|
2371
2371
|
resource.infra.formatted_str(),
|
|
2372
|
-
resources_utils.format_resource(
|
|
2373
|
-
|
|
2374
|
-
exception
|
|
2372
|
+
resources_utils.format_resource(
|
|
2373
|
+
resource, simplified_only=True)[0], exception
|
|
2375
2374
|
])
|
|
2376
2375
|
# Set the max width of REASON column to 80 to avoid the table
|
|
2377
2376
|
# being wrapped in a unreadable way.
|
sky/check.py
CHANGED
|
@@ -14,6 +14,7 @@ from sky import global_user_state
|
|
|
14
14
|
from sky import sky_logging
|
|
15
15
|
from sky import skypilot_config
|
|
16
16
|
from sky.adaptors import cloudflare
|
|
17
|
+
from sky.adaptors import coreweave
|
|
17
18
|
from sky.clouds import cloud as sky_cloud
|
|
18
19
|
from sky.skylet import constants
|
|
19
20
|
from sky.utils import common_utils
|
|
@@ -33,7 +34,8 @@ def _get_workspace_allowed_clouds(workspace: str) -> List[str]:
|
|
|
33
34
|
# clouds. Also validate names with get_cloud_tuple.
|
|
34
35
|
config_allowed_cloud_names = skypilot_config.get_nested(
|
|
35
36
|
('allowed_clouds',),
|
|
36
|
-
[repr(c) for c in registry.CLOUD_REGISTRY.values()] +
|
|
37
|
+
[repr(c) for c in registry.CLOUD_REGISTRY.values()] +
|
|
38
|
+
[cloudflare.NAME, coreweave.NAME])
|
|
37
39
|
# filter out the clouds that are disabled in the workspace config
|
|
38
40
|
workspace_disabled_clouds = []
|
|
39
41
|
for cloud in config_allowed_cloud_names:
|
|
@@ -81,7 +83,7 @@ def check_capabilities(
|
|
|
81
83
|
|
|
82
84
|
def get_all_clouds() -> Tuple[str, ...]:
|
|
83
85
|
return tuple([repr(c) for c in registry.CLOUD_REGISTRY.values()] +
|
|
84
|
-
[cloudflare.NAME])
|
|
86
|
+
[cloudflare.NAME, coreweave.NAME])
|
|
85
87
|
|
|
86
88
|
def _execute_check_logic_for_workspace(
|
|
87
89
|
current_workspace_name: str,
|
|
@@ -121,9 +123,12 @@ def check_capabilities(
|
|
|
121
123
|
cloud_name: str
|
|
122
124
|
) -> Tuple[str, Union[sky_clouds.Cloud, ModuleType]]:
|
|
123
125
|
# Validates cloud_name and returns a tuple of the cloud's name and
|
|
124
|
-
# the cloud object. Includes special handling for Cloudflare
|
|
126
|
+
# the cloud object. Includes special handling for Cloudflare and
|
|
127
|
+
# CoreWeave.
|
|
125
128
|
if cloud_name.lower().startswith('cloudflare'):
|
|
126
129
|
return cloudflare.NAME, cloudflare
|
|
130
|
+
elif cloud_name.lower().startswith('coreweave'):
|
|
131
|
+
return coreweave.NAME, coreweave
|
|
127
132
|
else:
|
|
128
133
|
cloud_obj = registry.CLOUD_REGISTRY.from_str(cloud_name)
|
|
129
134
|
assert cloud_obj is not None, f'Cloud {cloud_name!r} not found'
|
|
@@ -219,23 +224,24 @@ def check_capabilities(
|
|
|
219
224
|
# allowed_clouds in config.yaml, it will be disabled.
|
|
220
225
|
all_enabled_clouds: Set[str] = set()
|
|
221
226
|
for capability in capabilities:
|
|
222
|
-
# Cloudflare
|
|
223
|
-
# should not be inserted into the DB
|
|
224
|
-
# other code would error out when it's
|
|
225
|
-
# registry).
|
|
227
|
+
# Cloudflare and CoreWeave are not real clouds in
|
|
228
|
+
# registry.CLOUD_REGISTRY, and should not be inserted into the DB
|
|
229
|
+
# (otherwise `sky launch` and other code would error out when it's
|
|
230
|
+
# trying to look it up in the registry).
|
|
226
231
|
enabled_clouds_set = {
|
|
227
232
|
cloud for cloud, capabilities in enabled_clouds.items()
|
|
228
|
-
if capability in capabilities and
|
|
229
|
-
|
|
233
|
+
if capability in capabilities and not cloud.startswith(
|
|
234
|
+
'Cloudflare') and not cloud.startswith('CoreWeave')
|
|
230
235
|
}
|
|
231
236
|
disabled_clouds_set = {
|
|
232
237
|
cloud for cloud, capabilities in disabled_clouds.items()
|
|
233
|
-
if capability in capabilities and
|
|
234
|
-
|
|
238
|
+
if capability in capabilities and not cloud.startswith(
|
|
239
|
+
'Cloudflare') and not cloud.startswith('CoreWeave')
|
|
235
240
|
}
|
|
236
241
|
config_allowed_clouds_set = {
|
|
237
242
|
cloud for cloud in config_allowed_cloud_names
|
|
238
|
-
if not cloud.startswith('Cloudflare')
|
|
243
|
+
if not cloud.startswith('Cloudflare') and
|
|
244
|
+
not cloud.startswith('CoreWeave')
|
|
239
245
|
}
|
|
240
246
|
previously_enabled_clouds_set = {
|
|
241
247
|
repr(cloud)
|
|
@@ -430,6 +436,12 @@ def get_cloud_credential_file_mounts(
|
|
|
430
436
|
if r2_is_enabled:
|
|
431
437
|
r2_credential_mounts = cloudflare.get_credential_file_mounts()
|
|
432
438
|
file_mounts.update(r2_credential_mounts)
|
|
439
|
+
|
|
440
|
+
# Similarly, handle CoreWeave storage credentials
|
|
441
|
+
coreweave_is_enabled, _ = coreweave.check_storage_credentials()
|
|
442
|
+
if coreweave_is_enabled:
|
|
443
|
+
coreweave_credential_mounts = coreweave.get_credential_file_mounts()
|
|
444
|
+
file_mounts.update(coreweave_credential_mounts)
|
|
433
445
|
return file_mounts
|
|
434
446
|
|
|
435
447
|
|
|
@@ -494,7 +506,7 @@ def _print_checked_cloud(
|
|
|
494
506
|
style_str = f'{colorama.Fore.GREEN}{colorama.Style.NORMAL}'
|
|
495
507
|
status_msg = 'enabled'
|
|
496
508
|
capability_string = f'[{", ".join(enabled_capabilities)}]'
|
|
497
|
-
if verbose and cloud is not cloudflare:
|
|
509
|
+
if verbose and cloud is not cloudflare and cloud is not coreweave:
|
|
498
510
|
activated_account = cloud.get_active_user_identity_str()
|
|
499
511
|
if isinstance(cloud_tuple[1], (sky_clouds.SSH, sky_clouds.Kubernetes)):
|
|
500
512
|
detail_string = _format_context_details(cloud_tuple[1],
|
sky/client/cli/command.py
CHANGED
|
@@ -3452,7 +3452,7 @@ def _down_or_stop_clusters(
|
|
|
3452
3452
|
click.echo(f' {name} ({first})')
|
|
3453
3453
|
|
|
3454
3454
|
if failures:
|
|
3455
|
-
|
|
3455
|
+
click.echo('Cluster(s) failed. See details above.')
|
|
3456
3456
|
|
|
3457
3457
|
|
|
3458
3458
|
@cli.command(cls=_DocumentedCodeCommand)
|
|
@@ -4253,6 +4253,10 @@ def volumes():
|
|
|
4253
4253
|
pass
|
|
4254
4254
|
|
|
4255
4255
|
|
|
4256
|
+
# Add 'volume' as an alias for 'volumes'
|
|
4257
|
+
cli.add_command(volumes, name='volume')
|
|
4258
|
+
|
|
4259
|
+
|
|
4256
4260
|
@volumes.command('apply', cls=_DocumentedCodeCommand)
|
|
4257
4261
|
@flags.config_option(expose_value=False)
|
|
4258
4262
|
@click.argument('entrypoint',
|
sky/cloud_stores.py
CHANGED
|
@@ -18,6 +18,7 @@ from sky import sky_logging
|
|
|
18
18
|
from sky.adaptors import aws
|
|
19
19
|
from sky.adaptors import azure
|
|
20
20
|
from sky.adaptors import cloudflare
|
|
21
|
+
from sky.adaptors import coreweave
|
|
21
22
|
from sky.adaptors import ibm
|
|
22
23
|
from sky.adaptors import nebius
|
|
23
24
|
from sky.adaptors import oci
|
|
@@ -602,6 +603,77 @@ class NebiusCloudStorage(CloudStorage):
|
|
|
602
603
|
return ' && '.join(all_commands)
|
|
603
604
|
|
|
604
605
|
|
|
606
|
+
class CoreWeaveCloudStorage(CloudStorage):
|
|
607
|
+
"""CoreWeave Cloud Storage."""
|
|
608
|
+
|
|
609
|
+
# List of commands to install AWS CLI
|
|
610
|
+
_GET_AWSCLI = [
|
|
611
|
+
'aws --version >/dev/null 2>&1 || '
|
|
612
|
+
f'{constants.SKY_UV_PIP_CMD} install awscli',
|
|
613
|
+
]
|
|
614
|
+
|
|
615
|
+
def is_directory(self, url: str) -> bool:
|
|
616
|
+
"""Checks if the coreweave object is a directory.
|
|
617
|
+
|
|
618
|
+
In cloud object stores, a "directory" refers to a regular object whose
|
|
619
|
+
name is a prefix of other objects.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
url: coreweave object URL.
|
|
623
|
+
"""
|
|
624
|
+
cw = coreweave.resource('s3')
|
|
625
|
+
bucket_name, path = data_utils.split_coreweave_path(url)
|
|
626
|
+
bucket = cw.Bucket(bucket_name)
|
|
627
|
+
|
|
628
|
+
num_objects = 0
|
|
629
|
+
for obj in bucket.objects.filter(Prefix=path):
|
|
630
|
+
num_objects += 1
|
|
631
|
+
if obj.key == path:
|
|
632
|
+
return False
|
|
633
|
+
# If there are more than 1 object in filter, then it is a directory
|
|
634
|
+
if num_objects == 3:
|
|
635
|
+
return True
|
|
636
|
+
|
|
637
|
+
# A directory with few or no items
|
|
638
|
+
return True
|
|
639
|
+
|
|
640
|
+
def make_sync_dir_command(self, source: str, destination: str) -> str:
|
|
641
|
+
"""Downloads using AWS CLI."""
|
|
642
|
+
# AWS Sync by default uses 10 threads to upload files to the bucket.
|
|
643
|
+
# To increase parallelism, modify max_concurrent_requests in your
|
|
644
|
+
# aws config file (Default path: ~/.coreweave/cw.config).
|
|
645
|
+
assert 'cw://' in source, 'cw:// is not in source'
|
|
646
|
+
source = source.replace('cw://', 's3://')
|
|
647
|
+
download_via_awscli = (
|
|
648
|
+
'AWS_SHARED_CREDENTIALS_FILE='
|
|
649
|
+
f'{coreweave.COREWEAVE_CREDENTIALS_PATH} '
|
|
650
|
+
f'AWS_CONFIG_FILE={coreweave.COREWEAVE_CONFIG_PATH} '
|
|
651
|
+
f'{constants.SKY_REMOTE_PYTHON_ENV}/bin/aws s3 '
|
|
652
|
+
'sync --no-follow-symlinks '
|
|
653
|
+
f'{source} {destination} '
|
|
654
|
+
f'--profile={coreweave.COREWEAVE_PROFILE_NAME}')
|
|
655
|
+
|
|
656
|
+
all_commands = list(self._GET_AWSCLI)
|
|
657
|
+
all_commands.append(download_via_awscli)
|
|
658
|
+
return ' && '.join(all_commands)
|
|
659
|
+
|
|
660
|
+
def make_sync_file_command(self, source: str, destination: str) -> str:
|
|
661
|
+
"""Downloads a file using AWS CLI."""
|
|
662
|
+
assert 'cw://' in source, 'cw:// is not in source'
|
|
663
|
+
source = source.replace('cw://', 's3://')
|
|
664
|
+
download_via_awscli = (
|
|
665
|
+
'AWS_SHARED_CREDENTIALS_FILE='
|
|
666
|
+
f'{coreweave.COREWEAVE_CREDENTIALS_PATH} '
|
|
667
|
+
f'AWS_CONFIG_FILE={coreweave.COREWEAVE_CONFIG_PATH} '
|
|
668
|
+
f'{constants.SKY_REMOTE_PYTHON_ENV}/bin/aws s3 '
|
|
669
|
+
f'cp {source} {destination} '
|
|
670
|
+
f'--profile={coreweave.COREWEAVE_PROFILE_NAME}')
|
|
671
|
+
|
|
672
|
+
all_commands = list(self._GET_AWSCLI)
|
|
673
|
+
all_commands.append(download_via_awscli)
|
|
674
|
+
return ' && '.join(all_commands)
|
|
675
|
+
|
|
676
|
+
|
|
605
677
|
def get_storage_from_path(url: str) -> CloudStorage:
|
|
606
678
|
"""Returns a CloudStorage by identifying the scheme:// in a URL."""
|
|
607
679
|
result = urllib.parse.urlsplit(url)
|
|
@@ -619,6 +691,7 @@ _REGISTRY = {
|
|
|
619
691
|
'cos': IBMCosCloudStorage(),
|
|
620
692
|
'oci': OciCloudStorage(),
|
|
621
693
|
'nebius': NebiusCloudStorage(),
|
|
694
|
+
'cw': CoreWeaveCloudStorage(),
|
|
622
695
|
# TODO: This is a hack, as Azure URL starts with https://, we should
|
|
623
696
|
# refactor the registry to be able to take regex, so that Azure blob can
|
|
624
697
|
# be identified with `https://(.*?)\.blob\.core\.windows\.net`
|
sky/core.py
CHANGED
|
@@ -99,6 +99,7 @@ def status(
|
|
|
99
99
|
all_users: bool = False,
|
|
100
100
|
include_credentials: bool = False,
|
|
101
101
|
summary_response: bool = False,
|
|
102
|
+
include_handle: bool = True,
|
|
102
103
|
) -> List[responses.StatusResponse]:
|
|
103
104
|
# NOTE(dev): Keep the docstring consistent between the Python API and CLI.
|
|
104
105
|
"""Gets cluster statuses.
|
|
@@ -179,7 +180,8 @@ def status(
|
|
|
179
180
|
cluster_names=cluster_names,
|
|
180
181
|
all_users=all_users,
|
|
181
182
|
include_credentials=include_credentials,
|
|
182
|
-
summary_response=summary_response
|
|
183
|
+
summary_response=summary_response,
|
|
184
|
+
include_handle=include_handle)
|
|
183
185
|
|
|
184
186
|
status_responses = []
|
|
185
187
|
for cluster in clusters:
|
|
@@ -414,12 +416,12 @@ def cost_report(
|
|
|
414
416
|
# compatibility
|
|
415
417
|
num_nodes = record.get('num_nodes', 1)
|
|
416
418
|
try:
|
|
417
|
-
resource_str_simple =
|
|
418
|
-
|
|
419
|
+
resource_str_simple, resource_str_full = (
|
|
420
|
+
resources_utils.format_resource(
|
|
421
|
+
resources, simplified_only=abbreviate_response))
|
|
419
422
|
record['resources_str'] = f'{num_nodes}x{resource_str_simple}'
|
|
420
423
|
if not abbreviate_response:
|
|
421
|
-
resource_str_full
|
|
422
|
-
resources, simplify=False)
|
|
424
|
+
assert resource_str_full is not None
|
|
423
425
|
record[
|
|
424
426
|
'resources_str_full'] = f'{num_nodes}x{resource_str_full}'
|
|
425
427
|
except Exception as e: # pylint: disable=broad-except
|
sky/dashboard/out/404.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/
|
|
1
|
+
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/0748ce22df867032.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/0748ce22df867032.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-485984ca04e021d0.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-cf60a09ccd051a10.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-f15ccb73239a3bf1.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-bde01e4a2beec258.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js" defer=""></script><script src="/dashboard/_next/static/DabuSAKsc_y0wyJxpTIdQ/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/DabuSAKsc_y0wyJxpTIdQ/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"DabuSAKsc_y0wyJxpTIdQ","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
|
sky/dashboard/out/_next/static/{YP5Vc3ROcDnTGta0XAhcs → DabuSAKsc_y0wyJxpTIdQ}/_buildManifest.js
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
self.__BUILD_MANIFEST=function(s,c,e,
|
|
1
|
+
self.__BUILD_MANIFEST=function(s,c,a,e,t,f,b,u,n,o,j,r,i,k,h){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-444f1804401f04ea.js"],"/_error":["static/chunks/pages/_error-c66a4e8afc46f17b.js"],"/clusters":["static/chunks/pages/clusters-9e0df5442b04f7d0.js"],"/clusters/[cluster]":[s,c,a,f,b,j,o,e,t,u,r,n,i,k,h,"static/chunks/6856-5c94d394259cdb6e.js","static/chunks/1871-81adbc5f25dff78f.js","static/chunks/pages/clusters/[cluster]-1704039ccaf997cf.js"],"/clusters/[cluster]/[job]":[s,c,a,f,e,t,n,"static/chunks/pages/clusters/[cluster]/[job]-ac4a217f17b087cb.js"],"/config":["static/chunks/pages/config-dfb9bf07b13045f4.js"],"/infra":["static/chunks/pages/infra-c84a3b8a9d599b02.js"],"/infra/[context]":["static/chunks/pages/infra/[context]-9b6e47c2e8b485a2.js"],"/jobs":["static/chunks/pages/jobs-7eee823559e5cf9f.js"],"/jobs/pools/[pool]":[s,c,a,b,o,e,t,u,"static/chunks/pages/jobs/pools/[pool]-e020fd69dbe76cea.js"],"/jobs/[job]":[s,c,a,f,b,o,e,t,u,n,"static/chunks/pages/jobs/[job]-eb5822dac0c9509b.js"],"/users":["static/chunks/pages/users-2b172f13f8538a7a.js"],"/volumes":["static/chunks/pages/volumes-d2af9d22e87cc4ba.js"],"/workspace/new":["static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js"],"/workspaces":["static/chunks/pages/workspaces-1891376c08050940.js"],"/workspaces/[name]":[s,c,a,f,b,j,e,t,u,r,n,i,k,h,"static/chunks/1141-c3c10e2c6ed71a8f.js","static/chunks/pages/workspaces/[name]-bbfe5860c93470fd.js"],sortedPages:["/","/_app","/_error","/clusters","/clusters/[cluster]","/clusters/[cluster]/[job]","/config","/infra","/infra/[context]","/jobs","/jobs/pools/[pool]","/jobs/[job]","/users","/volumes","/workspace/new","/workspaces","/workspaces/[name]"]}}("static/chunks/616-3d59f75e2ccf9321.js","static/chunks/6130-2be46d70a38f1e82.js","static/chunks/5739-d67458fcb1386c92.js","static/chunks/6989-01359c57e018caa4.js","static/chunks/3850-ff4a9a69d978632b.js","static/chunks/7411-b15471acd2cba716.js","static/chunks/1272-1ef0bf0237faccdb.js","static/chunks/8969-4ed9236db997b42b.js","static/chunks/9353-cff34f7e773b2e2b.js","static/chunks/6212-7bd06f60ba693125.js","static/chunks/7359-c8d04e06886000b3.js","static/chunks/6990-f6818c84ed8f1c86.js","static/chunks/4282-49b2065b7336e496.js","static/chunks/6601-06114c982db410b6.js","static/chunks/7615-80aa7b09f45a86d2.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|