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.

Files changed (81) hide show
  1. sky/__init__.py +2 -2
  2. sky/adaptors/coreweave.py +278 -0
  3. sky/backends/backend_utils.py +9 -6
  4. sky/backends/cloud_vm_ray_backend.py +2 -3
  5. sky/check.py +25 -13
  6. sky/client/cli/command.py +5 -1
  7. sky/cloud_stores.py +73 -0
  8. sky/core.py +7 -5
  9. sky/dashboard/out/404.html +1 -1
  10. sky/dashboard/out/_next/static/{YP5Vc3ROcDnTGta0XAhcs → DabuSAKsc_y0wyJxpTIdQ}/_buildManifest.js +1 -1
  11. sky/dashboard/out/_next/static/chunks/{1141-d5204f35a3388bf4.js → 1141-c3c10e2c6ed71a8f.js} +1 -1
  12. sky/dashboard/out/_next/static/chunks/2755.a239c652bf8684dd.js +26 -0
  13. sky/dashboard/out/_next/static/chunks/3294.87a13fba0058865b.js +1 -0
  14. sky/dashboard/out/_next/static/chunks/{3785.538eb23a098fc304.js → 3785.170be320e0060eaf.js} +1 -1
  15. sky/dashboard/out/_next/static/chunks/4282-49b2065b7336e496.js +1 -0
  16. sky/dashboard/out/_next/static/chunks/7615-80aa7b09f45a86d2.js +1 -0
  17. sky/dashboard/out/_next/static/chunks/8969-4ed9236db997b42b.js +1 -0
  18. sky/dashboard/out/_next/static/chunks/9360.10a3aac7aad5e3aa.js +31 -0
  19. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-ac4a217f17b087cb.js +16 -0
  20. sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-fbf2907ce2bb67e2.js → [cluster]-1704039ccaf997cf.js} +1 -1
  21. sky/dashboard/out/_next/static/chunks/pages/{jobs-0dc34cf9a8710a9f.js → jobs-7eee823559e5cf9f.js} +1 -1
  22. sky/dashboard/out/_next/static/chunks/pages/{users-96d6b8bb2dec055f.js → users-2b172f13f8538a7a.js} +1 -1
  23. sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-fb1b4d3bfb047cad.js → [name]-bbfe5860c93470fd.js} +1 -1
  24. sky/dashboard/out/_next/static/chunks/pages/{workspaces-6fc994fa1ee6c6bf.js → workspaces-1891376c08050940.js} +1 -1
  25. sky/dashboard/out/_next/static/chunks/{webpack-585d805f693dbceb.js → webpack-485984ca04e021d0.js} +1 -1
  26. sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
  27. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  28. sky/dashboard/out/clusters/[cluster].html +1 -1
  29. sky/dashboard/out/clusters.html +1 -1
  30. sky/dashboard/out/config.html +1 -1
  31. sky/dashboard/out/index.html +1 -1
  32. sky/dashboard/out/infra/[context].html +1 -1
  33. sky/dashboard/out/infra.html +1 -1
  34. sky/dashboard/out/jobs/[job].html +1 -1
  35. sky/dashboard/out/jobs/pools/[pool].html +1 -1
  36. sky/dashboard/out/jobs.html +1 -1
  37. sky/dashboard/out/users.html +1 -1
  38. sky/dashboard/out/volumes.html +1 -1
  39. sky/dashboard/out/workspace/new.html +1 -1
  40. sky/dashboard/out/workspaces/[name].html +1 -1
  41. sky/dashboard/out/workspaces.html +1 -1
  42. sky/data/data_utils.py +92 -1
  43. sky/data/mounting_utils.py +39 -0
  44. sky/data/storage.py +166 -9
  45. sky/global_user_state.py +14 -18
  46. sky/jobs/server/server.py +2 -2
  47. sky/jobs/utils.py +5 -6
  48. sky/optimizer.py +1 -1
  49. sky/provision/kubernetes/instance.py +88 -19
  50. sky/provision/kubernetes/volume.py +2 -2
  51. sky/schemas/api/responses.py +2 -5
  52. sky/serve/replica_managers.py +2 -2
  53. sky/serve/serve_utils.py +9 -2
  54. sky/server/requests/payloads.py +2 -0
  55. sky/server/requests/requests.py +137 -102
  56. sky/server/requests/serializers/decoders.py +0 -6
  57. sky/server/requests/serializers/encoders.py +33 -6
  58. sky/server/server.py +2 -1
  59. sky/server/stream_utils.py +56 -13
  60. sky/setup_files/dependencies.py +2 -0
  61. sky/task.py +10 -0
  62. sky/templates/nebius-ray.yml.j2 +1 -0
  63. sky/utils/cli_utils/status_utils.py +8 -2
  64. sky/utils/context_utils.py +13 -1
  65. sky/utils/resources_utils.py +53 -29
  66. {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/METADATA +52 -36
  67. {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/RECORD +73 -72
  68. sky/dashboard/out/_next/static/chunks/2755.227c84f5adf75c6b.js +0 -26
  69. sky/dashboard/out/_next/static/chunks/3015-2dcace420c8939f4.js +0 -1
  70. sky/dashboard/out/_next/static/chunks/3294.6d5054a953a818cb.js +0 -1
  71. sky/dashboard/out/_next/static/chunks/4282-d2f3ef2fbf78e347.js +0 -1
  72. sky/dashboard/out/_next/static/chunks/8969-0389e2cb52412db3.js +0 -1
  73. sky/dashboard/out/_next/static/chunks/9360.07d78b8552bc9d17.js +0 -31
  74. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-c815b90e296b8075.js +0 -16
  75. sky/dashboard/out/_next/static/css/4c052b4444e52a58.css +0 -3
  76. /sky/dashboard/out/_next/static/{YP5Vc3ROcDnTGta0XAhcs → DabuSAKsc_y0wyJxpTIdQ}/_ssgManifest.js +0 -0
  77. /sky/dashboard/out/_next/static/chunks/pages/{_app-513d332313670f2a.js → _app-bde01e4a2beec258.js} +0 -0
  78. {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/WHEEL +0 -0
  79. {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/entry_points.txt +0 -0
  80. {skypilot_nightly-1.0.0.dev20251027.dist-info → skypilot_nightly-1.0.0.dev20251029.dist-info}/licenses/LICENSE +0 -0
  81. {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 = 'b7eec54b8f52d7f72ff5b3908ba7f2d66b8bca6a'
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.dev20251027'
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
@@ -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
- record[
3244
- 'resources_str'] = resources_utils.get_readable_resources_repr(
3245
- handle, simplify=True)
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(resource,
2373
- simplify=True),
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()] + [cloudflare.NAME])
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 is not a real cloud in registry.CLOUD_REGISTRY, and
223
- # should not be inserted into the DB (otherwise `sky launch` and
224
- # other code would error out when it's trying to look it up in the
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
- not cloud.startswith('Cloudflare')
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
- not cloud.startswith('Cloudflare')
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
- raise click.ClickException('Cluster(s) failed. See details above.')
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 = resources_utils.format_resource(resources,
418
- simplify=True)
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 = resources_utils.format_resource(
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
@@ -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/4c052b4444e52a58.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/4c052b4444e52a58.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-585d805f693dbceb.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-513d332313670f2a.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js" defer=""></script><script src="/dashboard/_next/static/YP5Vc3ROcDnTGta0XAhcs/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/YP5Vc3ROcDnTGta0XAhcs/_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":"YP5Vc3ROcDnTGta0XAhcs","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
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>
@@ -1 +1 @@
1
- self.__BUILD_MANIFEST=function(s,c,e,a,t,f,b,u,n,o,j,r,i,k,d){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,e,f,b,j,o,a,t,u,r,n,i,k,d,"static/chunks/6856-5c94d394259cdb6e.js","static/chunks/1871-81adbc5f25dff78f.js","static/chunks/pages/clusters/[cluster]-fbf2907ce2bb67e2.js"],"/clusters/[cluster]/[job]":[s,c,e,f,a,t,n,"static/chunks/pages/clusters/[cluster]/[job]-c815b90e296b8075.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-0dc34cf9a8710a9f.js"],"/jobs/pools/[pool]":[s,c,e,b,o,a,t,u,"static/chunks/pages/jobs/pools/[pool]-e020fd69dbe76cea.js"],"/jobs/[job]":[s,c,e,f,b,o,a,t,u,n,"static/chunks/pages/jobs/[job]-eb5822dac0c9509b.js"],"/users":["static/chunks/pages/users-96d6b8bb2dec055f.js"],"/volumes":["static/chunks/pages/volumes-d2af9d22e87cc4ba.js"],"/workspace/new":["static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js"],"/workspaces":["static/chunks/pages/workspaces-6fc994fa1ee6c6bf.js"],"/workspaces/[name]":[s,c,e,f,b,j,a,t,u,r,n,i,k,d,"static/chunks/1141-d5204f35a3388bf4.js","static/chunks/pages/workspaces/[name]-fb1b4d3bfb047cad.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-0389e2cb52412db3.js","static/chunks/9353-cff34f7e773b2e2b.js","static/chunks/6212-7bd06f60ba693125.js","static/chunks/7359-c8d04e06886000b3.js","static/chunks/6990-f6818c84ed8f1c86.js","static/chunks/4282-d2f3ef2fbf78e347.js","static/chunks/6601-06114c982db410b6.js","static/chunks/3015-2dcace420c8939f4.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
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();