skypilot-nightly 1.0.0.dev20250320__py3-none-any.whl → 1.0.0.dev20250321__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.
Files changed (49) hide show
  1. sky/__init__.py +2 -2
  2. sky/adaptors/cloudflare.py +16 -4
  3. sky/adaptors/kubernetes.py +2 -1
  4. sky/adaptors/nebius.py +128 -6
  5. sky/backends/cloud_vm_ray_backend.py +3 -1
  6. sky/benchmark/benchmark_utils.py +3 -2
  7. sky/check.py +18 -87
  8. sky/cloud_stores.py +66 -0
  9. sky/clouds/aws.py +14 -7
  10. sky/clouds/azure.py +13 -6
  11. sky/clouds/cloud.py +33 -10
  12. sky/clouds/cudo.py +3 -2
  13. sky/clouds/do.py +3 -2
  14. sky/clouds/fluidstack.py +3 -2
  15. sky/clouds/gcp.py +8 -9
  16. sky/clouds/ibm.py +15 -6
  17. sky/clouds/kubernetes.py +3 -1
  18. sky/clouds/lambda_cloud.py +3 -1
  19. sky/clouds/nebius.py +7 -3
  20. sky/clouds/oci.py +15 -6
  21. sky/clouds/paperspace.py +3 -2
  22. sky/clouds/runpod.py +7 -1
  23. sky/clouds/scp.py +3 -1
  24. sky/clouds/service_catalog/kubernetes_catalog.py +3 -1
  25. sky/clouds/vast.py +3 -2
  26. sky/clouds/vsphere.py +3 -2
  27. sky/core.py +4 -2
  28. sky/data/data_transfer.py +75 -0
  29. sky/data/data_utils.py +34 -0
  30. sky/data/mounting_utils.py +18 -0
  31. sky/data/storage.py +537 -9
  32. sky/data/storage_utils.py +102 -84
  33. sky/exceptions.py +2 -0
  34. sky/global_user_state.py +12 -33
  35. sky/jobs/server/core.py +1 -1
  36. sky/jobs/utils.py +5 -0
  37. sky/optimizer.py +7 -2
  38. sky/resources.py +6 -1
  39. sky/setup_files/dependencies.py +3 -1
  40. sky/task.py +16 -5
  41. sky/utils/command_runner.py +2 -0
  42. sky/utils/controller_utils.py +8 -5
  43. sky/utils/kubernetes/kubernetes_deploy_utils.py +2 -1
  44. {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/METADATA +11 -1
  45. {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/RECORD +49 -49
  46. {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/WHEEL +1 -1
  47. {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/entry_points.txt +0 -0
  48. {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/licenses/LICENSE +0 -0
  49. {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/top_level.txt +0 -0
sky/clouds/cloud.py CHANGED
@@ -13,6 +13,8 @@ import math
13
13
  import typing
14
14
  from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union
15
15
 
16
+ from typing_extensions import assert_never
17
+
16
18
  from sky import exceptions
17
19
  from sky import skypilot_config
18
20
  from sky.clouds import service_catalog
@@ -48,6 +50,16 @@ class CloudImplementationFeatures(enum.Enum):
48
50
  AUTO_TERMINATE = 'auto_terminate' # Pod/VM can stop or down itself
49
51
 
50
52
 
53
+ class CloudCapability(enum.Enum):
54
+ # Compute capability.
55
+ COMPUTE = 'compute'
56
+ # Storage capability.
57
+ STORAGE = 'storage'
58
+
59
+
60
+ ALL_CAPABILITIES = [CloudCapability.COMPUTE, CloudCapability.STORAGE]
61
+
62
+
51
63
  class Region(collections.namedtuple('Region', ['name'])):
52
64
  """A region."""
53
65
  name: str
@@ -435,25 +447,36 @@ class Cloud:
435
447
  return {reservation: 0 for reservation in specific_reservations}
436
448
 
437
449
  @classmethod
438
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
450
+ def check_credentials(
451
+ cls,
452
+ cloud_capability: CloudCapability) -> Tuple[bool, Optional[str]]:
439
453
  """Checks if the user has access credentials to this cloud.
440
454
 
441
455
  Returns a boolean of whether the user can access this cloud, and a
442
456
  string describing the reason if the user cannot access.
457
+
458
+ Raises NotSupportedError if the capability is
459
+ not supported by this cloud.
443
460
  """
444
- raise NotImplementedError
461
+ if cloud_capability == CloudCapability.COMPUTE:
462
+ return cls._check_compute_credentials()
463
+ elif cloud_capability == CloudCapability.STORAGE:
464
+ return cls._check_storage_credentials()
465
+ assert_never(cloud_capability)
445
466
 
446
467
  @classmethod
447
- def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
448
- """Checks if the user has access credentials to this cloud's storage.
468
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
469
+ """Checks if the user has access credentials to
470
+ this cloud's compute service."""
471
+ raise exceptions.NotSupportedError(
472
+ f'{cls._REPR} does not support {CloudCapability.COMPUTE.value}.')
449
473
 
450
- Returns a boolean of whether the user can access this cloud's storage,
451
- and a string describing the reason if the user cannot access.
452
- """
453
- # A given cloud does not support storage
454
- # unless it overrides this method.
474
+ @classmethod
475
+ def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
476
+ """Checks if the user has access credentials to
477
+ this cloud's storage service."""
455
478
  raise exceptions.NotSupportedError(
456
- f'{cls._REPR} does not support storage.')
479
+ f'{cls._REPR} does not support {CloudCapability.STORAGE.value}.')
457
480
 
458
481
  # TODO(zhwu): Make the return type immutable.
459
482
  @classmethod
sky/clouds/cudo.py CHANGED
@@ -267,8 +267,9 @@ class Cudo(clouds.Cloud):
267
267
  fuzzy_candidate_list, None)
268
268
 
269
269
  @classmethod
270
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
271
-
270
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
271
+ """Checks if the user has access credentials to
272
+ Cudo's compute service."""
272
273
  try:
273
274
  # pylint: disable=import-outside-toplevel,unused-import
274
275
  from cudo_compute import cudo_api
sky/clouds/do.py CHANGED
@@ -259,8 +259,9 @@ class DO(clouds.Cloud):
259
259
  fuzzy_candidate_list, None)
260
260
 
261
261
  @classmethod
262
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
263
- """Verify that the user has valid credentials for DO."""
262
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
263
+ """Verify that the user has valid credentials for
264
+ DO's compute service."""
264
265
 
265
266
  try:
266
267
  do.exceptions()
sky/clouds/fluidstack.py CHANGED
@@ -255,8 +255,9 @@ class Fluidstack(clouds.Cloud):
255
255
  fuzzy_candidate_list, None)
256
256
 
257
257
  @classmethod
258
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
259
-
258
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
259
+ """Checks if the user has access credentials to
260
+ FluidStack's compute service."""
260
261
  try:
261
262
  assert os.path.exists(
262
263
  os.path.expanduser(fluidstack_utils.FLUIDSTACK_API_KEY_PATH))
sky/clouds/gcp.py CHANGED
@@ -719,9 +719,9 @@ class GCP(clouds.Cloud):
719
719
  return DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH
720
720
 
721
721
  @classmethod
722
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
723
- """Checks if the user has compute access credentials to this cloud."""
724
- return cls._check_credentials( # Check APIs.
722
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
723
+ """Checks if the user has access credentials to this cloud's compute service."""
724
+ return cls._check_credentials(
725
725
  [
726
726
  ('compute', 'Compute Engine'),
727
727
  ('cloudresourcemanager', 'Cloud Resource Manager'),
@@ -731,12 +731,11 @@ class GCP(clouds.Cloud):
731
731
  gcp_utils.get_minimal_compute_permissions())
732
732
 
733
733
  @classmethod
734
- def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
735
- """Checks if the user has compute access credentials to this cloud."""
736
- return cls._check_credentials( # Check APIs.
737
- [
738
- ('storage', 'Cloud Storage'),
739
- ], gcp_utils.get_minimal_storage_permissions())
734
+ def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
735
+ """Checks if the user has access credentials to this cloud's storage service."""
736
+ return cls._check_credentials(
737
+ [('storage', 'Cloud Storage')],
738
+ gcp_utils.get_minimal_storage_permissions())
740
739
 
741
740
  @classmethod
742
741
  def _check_credentials(
sky/clouds/ibm.py CHANGED
@@ -395,7 +395,21 @@ class IBM(clouds.Cloud):
395
395
  return image_size
396
396
 
397
397
  @classmethod
398
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
398
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
399
+ """Checks if the user has access credentials to
400
+ IBM's compute service."""
401
+ return cls._check_credentials()
402
+
403
+ @classmethod
404
+ def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
405
+ """Checks if the user has access credentials to
406
+ IBM's storage service."""
407
+ # TODO(seungjin): Implement separate check for
408
+ # if the user has access to IBM COS.
409
+ return cls._check_credentials()
410
+
411
+ @classmethod
412
+ def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
399
413
  """Checks if the user has access credentials to this cloud."""
400
414
 
401
415
  required_fields = ['iam_api_key', 'resource_group_id']
@@ -433,11 +447,6 @@ class IBM(clouds.Cloud):
433
447
  except Exception as e:
434
448
  return (False, f'{str(e)}' + help_str)
435
449
 
436
- @classmethod
437
- def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
438
- # TODO(seungjin): Check if the user has access to IBM COS.
439
- return cls.check_credentials()
440
-
441
450
  def get_credential_file_mounts(self) -> Dict[str, str]:
442
451
  """Returns a {remote:local} credential path mapping
443
452
  written to the cluster's file_mounts segment
sky/clouds/kubernetes.py CHANGED
@@ -655,7 +655,9 @@ class Kubernetes(clouds.Cloud):
655
655
  [], None)
656
656
 
657
657
  @classmethod
658
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
658
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
659
+ """Checks if the user has access credentials to
660
+ Kubernetes."""
659
661
  # Test using python API
660
662
  try:
661
663
  existing_allowed_contexts = cls.existing_allowed_contexts()
@@ -241,7 +241,9 @@ class Lambda(clouds.Cloud):
241
241
  fuzzy_candidate_list, None)
242
242
 
243
243
  @classmethod
244
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
244
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
245
+ """Checks if the user has access credentials to
246
+ Lambda's compute service."""
245
247
  try:
246
248
  lambda_utils.LambdaCloudClient().list_instances()
247
249
  except (AssertionError, KeyError, lambda_utils.LambdaCloudError):
sky/clouds/nebius.py CHANGED
@@ -250,8 +250,9 @@ class Nebius(clouds.Cloud):
250
250
  fuzzy_candidate_list, None)
251
251
 
252
252
  @classmethod
253
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
254
- """ Verify that the user has valid credentials for Nebius. """
253
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
254
+ """Checks if the user has access credentials to
255
+ Nebius's compute service."""
255
256
  logging.debug('Nebius cloud check credentials')
256
257
  token_cred_msg = (' Credentials can be set up by running: \n'\
257
258
  f' $ nebius iam get-access-token > {nebius.NEBIUS_IAM_TOKEN_PATH} \n'\
@@ -279,10 +280,13 @@ class Nebius(clouds.Cloud):
279
280
  return True, None
280
281
 
281
282
  def get_credential_file_mounts(self) -> Dict[str, str]:
282
- return {
283
+ credential_file_mounts = {
283
284
  f'~/.nebius/{filename}': f'~/.nebius/{filename}'
284
285
  for filename in _CREDENTIAL_FILES
285
286
  }
287
+ credential_file_mounts['~/.aws/credentials'] = '~/.aws/credentials'
288
+
289
+ return credential_file_mounts
286
290
 
287
291
  @classmethod
288
292
  def get_current_user_identity(cls) -> Optional[List[str]]:
sky/clouds/oci.py CHANGED
@@ -396,7 +396,21 @@ class OCI(clouds.Cloud):
396
396
  fuzzy_candidate_list, None)
397
397
 
398
398
  @classmethod
399
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
399
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
400
+ """Checks if the user has access credentials to
401
+ OCI's compute service."""
402
+ return cls._check_credentials()
403
+
404
+ @classmethod
405
+ def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
406
+ """Checks if the user has access credentials to
407
+ OCI's storage service."""
408
+ # TODO(seungjin): Implement separate check for
409
+ # if the user has access to OCI Object Storage.
410
+ return cls._check_credentials()
411
+
412
+ @classmethod
413
+ def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
400
414
  """Checks if the user has access credentials to this cloud."""
401
415
 
402
416
  short_credential_help_str = (
@@ -456,11 +470,6 @@ class OCI(clouds.Cloud):
456
470
  f'{cls._INDENT_PREFIX}Error details: '
457
471
  f'{common_utils.format_exception(e, use_bracket=True)}')
458
472
 
459
- @classmethod
460
- def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
461
- # TODO(seungjin): Check if the user has access to OCI Object Storage.
462
- return cls.check_credentials()
463
-
464
473
  @classmethod
465
474
  def check_disk_tier(
466
475
  cls, instance_type: Optional[str],
sky/clouds/paperspace.py CHANGED
@@ -249,8 +249,9 @@ class Paperspace(clouds.Cloud):
249
249
  fuzzy_candidate_list, None)
250
250
 
251
251
  @classmethod
252
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
253
- """Verify that the user has valid credentials for Paperspace."""
252
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
253
+ """Checks if the user has access credentials to
254
+ Paperspace's compute service."""
254
255
  try:
255
256
  # attempt to make a CURL request for listing instances
256
257
  utils.PaperspaceCloudClient().list_instances()
sky/clouds/runpod.py CHANGED
@@ -248,7 +248,13 @@ class RunPod(clouds.Cloud):
248
248
  fuzzy_candidate_list, None)
249
249
 
250
250
  @classmethod
251
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
251
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
252
+ """Checks if the user has access credentials to
253
+ RunPod's compute service."""
254
+ return cls._check_credentials()
255
+
256
+ @classmethod
257
+ def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
252
258
  """ Verify that the user has valid credentials for RunPod. """
253
259
  try:
254
260
  import runpod # pylint: disable=import-outside-toplevel
sky/clouds/scp.py CHANGED
@@ -312,7 +312,9 @@ class SCP(clouds.Cloud):
312
312
  fuzzy_candidate_list, None)
313
313
 
314
314
  @classmethod
315
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
315
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
316
+ """Checks if the user has access credentials to
317
+ SCP's compute service."""
316
318
  try:
317
319
  scp_utils.SCPClient().list_instances()
318
320
  except (AssertionError, KeyError, scp_utils.SCPClientError,
@@ -12,6 +12,7 @@ from sky import clouds as sky_clouds
12
12
  from sky import sky_logging
13
13
  from sky.adaptors import common as adaptors_common
14
14
  from sky.adaptors import kubernetes
15
+ from sky.clouds import cloud
15
16
  from sky.clouds.service_catalog import CloudFilter
16
17
  from sky.clouds.service_catalog import common
17
18
  from sky.provision.kubernetes import utils as kubernetes_utils
@@ -132,7 +133,8 @@ def _list_accelerators(
132
133
 
133
134
  # First check if Kubernetes is enabled. This ensures k8s python client is
134
135
  # installed. Do not put any k8s-specific logic before this check.
135
- enabled_clouds = sky_check.get_cached_enabled_clouds_or_refresh()
136
+ enabled_clouds = sky_check.get_cached_enabled_clouds_or_refresh(
137
+ cloud.CloudCapability.COMPUTE)
136
138
  if not sky_clouds.cloud_in_iterable(sky_clouds.Kubernetes(),
137
139
  enabled_clouds):
138
140
  return {}, {}, {}
sky/clouds/vast.py CHANGED
@@ -234,8 +234,9 @@ class Vast(clouds.Cloud):
234
234
  fuzzy_candidate_list, None)
235
235
 
236
236
  @classmethod
237
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
238
- """ Verify that the user has valid credentials for Vast. """
237
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
238
+ """Checks if the user has valid credentials for
239
+ Vast's compute service. """
239
240
  try:
240
241
  import vastai_sdk as _vast # pylint: disable=import-outside-toplevel
241
242
  vast = _vast.VastAI()
sky/clouds/vsphere.py CHANGED
@@ -254,8 +254,9 @@ class Vsphere(clouds.Cloud):
254
254
  fuzzy_candidate_list, None)
255
255
 
256
256
  @classmethod
257
- def check_credentials(cls) -> Tuple[bool, Optional[str]]:
258
- """Checks if the user has access credentials to this cloud."""
257
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
258
+ """Checks if the user has access credentials to
259
+ vSphere's compute service."""
259
260
 
260
261
  try:
261
262
  # pylint: disable=import-outside-toplevel,unused-import
sky/core.py CHANGED
@@ -19,6 +19,7 @@ from sky import optimizer
19
19
  from sky import sky_logging
20
20
  from sky import task as task_lib
21
21
  from sky.backends import backend_utils
22
+ from sky.clouds import cloud as sky_cloud
22
23
  from sky.clouds import service_catalog
23
24
  from sky.jobs.server import core as managed_jobs_core
24
25
  from sky.provision.kubernetes import constants as kubernetes_constants
@@ -1001,7 +1002,8 @@ def storage_delete(name: str) -> None:
1001
1002
  # ===================
1002
1003
  @usage_lib.entrypoint
1003
1004
  def enabled_clouds() -> List[clouds.Cloud]:
1004
- return global_user_state.get_cached_enabled_clouds()
1005
+ return global_user_state.get_cached_enabled_clouds(
1006
+ sky_cloud.CloudCapability.COMPUTE)
1005
1007
 
1006
1008
 
1007
1009
  @usage_lib.entrypoint
@@ -1136,7 +1138,7 @@ def local_down() -> None:
1136
1138
  ux_utils.spinner_message('Running sky check...')):
1137
1139
  sky_check.check(clouds=['kubernetes'],
1138
1140
  quiet=True,
1139
- capability=sky_check.CloudCapability.COMPUTE)
1141
+ capability=sky_cloud.CloudCapability.COMPUTE)
1140
1142
  logger.info(
1141
1143
  ux_utils.finishing_message('Local cluster removed.',
1142
1144
  log_path=log_path,
sky/data/data_transfer.py CHANGED
@@ -14,6 +14,7 @@ Currently implemented:
14
14
  TODO:
15
15
  - All combinations of Azure Transfer
16
16
  - All combinations of R2 Transfer
17
+ - All combinations of Nebius Transfer
17
18
  - GCS -> S3
18
19
  """
19
20
  import json
@@ -135,6 +136,35 @@ def s3_to_r2(s3_bucket_name: str, r2_bucket_name: str) -> None:
135
136
  'a local source for the storage object.')
136
137
 
137
138
 
139
+ def s3_to_nebius(s3_bucket_name: str, nebius_bucket_name: str) -> None:
140
+ """Creates a one-time transfer from Amazon S3 to Nebius Object Storage.
141
+
142
+ it will block until the transfer is complete.
143
+
144
+ Args:
145
+ s3_bucket_name: str; Name of the Amazon S3 Bucket
146
+ nebius_bucket_name: str; Name of the Cloudflare R2 Bucket
147
+ """
148
+ raise NotImplementedError('Moving data directly from clouds to Nebius is '
149
+ 'currently not supported. Please specify '
150
+ 'a local source for the storage object.')
151
+
152
+
153
+ def r2_to_nebius(r2_bucket_name: str, nebius_bucket_name: str) -> None:
154
+ """Creates a one-time transfer from Cloudflare R2 Bucket to
155
+ Nebius Object Storage.
156
+
157
+ it will block until the transfer is complete.
158
+
159
+ Args:
160
+ r2_bucket_name: str; Name of the Amazon S3 Bucket
161
+ nebius_bucket_name: str; Name of the Cloudflare R2 Bucket
162
+ """
163
+ raise NotImplementedError('Moving data directly from clouds to Nebius is '
164
+ 'currently not supported. Please specify '
165
+ 'a local source for the storage object.')
166
+
167
+
138
168
  def gcs_to_s3(gs_bucket_name: str, s3_bucket_name: str) -> None:
139
169
  """Creates a one-time transfer from Google Cloud Storage to Amazon S3.
140
170
 
@@ -160,6 +190,19 @@ def gcs_to_r2(gs_bucket_name: str, r2_bucket_name: str) -> None:
160
190
  'a local source for the storage object.')
161
191
 
162
192
 
193
+ def gcs_to_nebius(gs_bucket_name: str, nebius_bucket_name: str) -> None:
194
+ """Creates a one-time transfer from Google Cloud Storage to
195
+ Nebius Object Storage.
196
+
197
+ Args:
198
+ gs_bucket_name: str; Name of the Google Cloud Storage Bucket
199
+ nebius_bucket_name: str; Name of the Nebius Bucket
200
+ """
201
+ raise NotImplementedError('Moving data directly from clouds to Nebius is '
202
+ 'currently not supported. Please specify '
203
+ 'a local source for the storage object.')
204
+
205
+
163
206
  def r2_to_gcs(r2_bucket_name: str, gs_bucket_name: str) -> None:
164
207
  """Creates a one-time transfer from Cloudflare R2 to Google Cloud Storage.
165
208
 
@@ -175,6 +218,22 @@ def r2_to_gcs(r2_bucket_name: str, gs_bucket_name: str) -> None:
175
218
  'a local source for the storage object.')
176
219
 
177
220
 
221
+ def nebius_to_gcs(nebius_bucket_name: str, gs_bucket_name: str) -> None:
222
+ """Creates a one-time transfer from Nebius Object Storage
223
+ to Google Cloud Storage.
224
+
225
+ Can be viewed from: https://console.cloud.google.com/transfer/cloud
226
+ it will block until the transfer is complete.
227
+
228
+ Args:
229
+ nebius_bucket_name: str; Name of the Nebius Object Storage
230
+ gs_bucket_name: str; Name of the Google Cloud Storage Bucket
231
+ """
232
+ raise NotImplementedError('Moving data directly from Nebius to clouds is '
233
+ 'currently not supported. Please specify '
234
+ 'a local source for the storage object.')
235
+
236
+
178
237
  def r2_to_s3(r2_bucket_name: str, s3_bucket_name: str) -> None:
179
238
  """Creates a one-time transfer from Amazon S3 to Google Cloud Storage.
180
239
 
@@ -190,6 +249,22 @@ def r2_to_s3(r2_bucket_name: str, s3_bucket_name: str) -> None:
190
249
  'a local source for the storage object.')
191
250
 
192
251
 
252
+ def nebius_to_s3(nebius_bucket_name: str, s3_bucket_name: str) -> None:
253
+ """Creates a one-time transfer from Nebius Object Storage
254
+ to Amazon S3 Bucket.
255
+
256
+ Can be viewed from: https://console.cloud.google.com/transfer/cloud
257
+ it will block until the transfer is complete.
258
+
259
+ Args:
260
+ nebius_bucket_name: str; Name of the Nebius Object Storage\
261
+ s3_bucket_name: str; Name of the Amazon S3 Bucket
262
+ """
263
+ raise NotImplementedError('Moving data directly from R2 to clouds is '
264
+ 'currently not supported. Please specify '
265
+ 'a local source for the storage object.')
266
+
267
+
193
268
  def _add_bucket_iam_member(bucket_name: str, role: str, member: str) -> None:
194
269
  storage_client = gcp.storage_client()
195
270
  bucket = storage_client.bucket(bucket_name)
sky/data/data_utils.py CHANGED
@@ -20,6 +20,7 @@ from sky.adaptors import azure
20
20
  from sky.adaptors import cloudflare
21
21
  from sky.adaptors import gcp
22
22
  from sky.adaptors import ibm
23
+ from sky.adaptors import nebius
23
24
  from sky.skylet import log_lib
24
25
  from sky.utils import common_utils
25
26
  from sky.utils import ux_utils
@@ -94,6 +95,18 @@ def split_r2_path(r2_path: str) -> Tuple[str, str]:
94
95
  return bucket, key
95
96
 
96
97
 
98
+ def split_nebius_path(nebius_path: str) -> Tuple[str, str]:
99
+ """Splits Nebius Path into Bucket name and Relative Path to Bucket
100
+
101
+ Args:
102
+ nebius_path: str; Nebius Path, e.g. nebius://imagenet/train/
103
+ """
104
+ path_parts = nebius_path.replace('nebius://', '').split('/')
105
+ bucket = path_parts.pop(0)
106
+ key = '/'.join(path_parts)
107
+ return bucket, key
108
+
109
+
97
110
  def split_cos_path(s3_path: str) -> Tuple[str, str, str]:
98
111
  """returns extracted region, bucket name and bucket path to data
99
112
  from the specified cos bucket's url.
@@ -307,6 +320,16 @@ def create_r2_client(region: str = 'auto') -> Client:
307
320
  return cloudflare.client('s3', region)
308
321
 
309
322
 
323
+ def create_nebius_client(region: Optional[str]) -> Client:
324
+ """Helper method that connects to Boto3 client for Nebius Object Storage
325
+
326
+ Args:
327
+ region: str; Region for Nebius Object Storage
328
+ """
329
+ region = region if region is not None else nebius.DEFAULT_REGION
330
+ return nebius.client('s3', region)
331
+
332
+
310
333
  def verify_r2_bucket(name: str) -> bool:
311
334
  """Helper method that checks if the R2 bucket exists
312
335
 
@@ -318,6 +341,17 @@ def verify_r2_bucket(name: str) -> bool:
318
341
  return bucket in r2.buckets.all()
319
342
 
320
343
 
344
+ def verify_nebius_bucket(name: str) -> bool:
345
+ """Helper method that checks if the Nebius bucket exists
346
+
347
+ Args:
348
+ name: str; Name of Nebius Object Storage (without nebius:// prefix)
349
+ """
350
+ nebius_s = nebius.resource('s3')
351
+ bucket = nebius_s.Bucket(name)
352
+ return bucket in nebius_s.buckets.all()
353
+
354
+
321
355
  def verify_ibm_cos_bucket(name: str) -> bool:
322
356
  """Helper method that checks if the cos bucket exists
323
357
 
@@ -54,6 +54,24 @@ def get_s3_mount_cmd(bucket_name: str,
54
54
  return mount_cmd
55
55
 
56
56
 
57
+ def get_nebius_mount_cmd(nebius_profile_name: str,
58
+ endpoint_url: str,
59
+ bucket_name: str,
60
+ mount_path: str,
61
+ _bucket_sub_path: Optional[str] = None) -> str:
62
+ """Returns a command to install Nebius mount utility goofys."""
63
+ if _bucket_sub_path is None:
64
+ _bucket_sub_path = ''
65
+ else:
66
+ _bucket_sub_path = f':{_bucket_sub_path}'
67
+ mount_cmd = (f'AWS_PROFILE={nebius_profile_name} goofys -o allow_other '
68
+ f'--stat-cache-ttl {_STAT_CACHE_TTL} '
69
+ f'--type-cache-ttl {_TYPE_CACHE_TTL} '
70
+ f'--endpoint {endpoint_url} '
71
+ f'{bucket_name}{_bucket_sub_path} {mount_path}')
72
+ return mount_cmd
73
+
74
+
57
75
  def get_gcs_mount_install_cmd() -> str:
58
76
  """Returns a command to install GCS mount utility gcsfuse."""
59
77
  install_cmd = ('ARCH=$(uname -m) && '