skypilot-nightly 1.0.0.dev20250320__py3-none-any.whl → 1.0.0.dev20250322__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.
- sky/__init__.py +2 -2
- sky/adaptors/cloudflare.py +16 -5
- sky/adaptors/kubernetes.py +2 -1
- sky/adaptors/nebius.py +128 -6
- sky/backends/cloud_vm_ray_backend.py +3 -1
- sky/benchmark/benchmark_utils.py +3 -2
- sky/check.py +114 -114
- sky/cloud_stores.py +66 -0
- sky/clouds/aws.py +14 -7
- sky/clouds/azure.py +13 -6
- sky/clouds/cloud.py +34 -10
- sky/clouds/cudo.py +3 -2
- sky/clouds/do.py +3 -2
- sky/clouds/fluidstack.py +3 -2
- sky/clouds/gcp.py +8 -9
- sky/clouds/ibm.py +15 -6
- sky/clouds/kubernetes.py +3 -1
- sky/clouds/lambda_cloud.py +3 -1
- sky/clouds/nebius.py +59 -11
- sky/clouds/oci.py +15 -6
- sky/clouds/paperspace.py +3 -2
- sky/clouds/runpod.py +7 -1
- sky/clouds/scp.py +3 -1
- sky/clouds/service_catalog/kubernetes_catalog.py +3 -1
- sky/clouds/vast.py +3 -2
- sky/clouds/vsphere.py +3 -2
- sky/core.py +6 -4
- sky/data/data_transfer.py +75 -0
- sky/data/data_utils.py +34 -0
- sky/data/mounting_utils.py +18 -0
- sky/data/storage.py +540 -10
- sky/data/storage_utils.py +102 -84
- sky/exceptions.py +2 -0
- sky/global_user_state.py +12 -33
- sky/jobs/server/core.py +1 -1
- sky/jobs/utils.py +5 -0
- sky/optimizer.py +10 -5
- sky/resources.py +6 -1
- sky/setup_files/dependencies.py +3 -1
- sky/task.py +16 -5
- sky/utils/command_runner.py +2 -0
- sky/utils/controller_utils.py +8 -5
- sky/utils/kubernetes/gpu_labeler.py +4 -4
- sky/utils/kubernetes/kubernetes_deploy_utils.py +4 -3
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250322.dist-info}/METADATA +16 -7
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250322.dist-info}/RECORD +50 -50
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250322.dist-info}/WHEEL +1 -1
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250322.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250322.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250322.dist-info}/top_level.txt +0 -0
sky/cloud_stores.py
CHANGED
@@ -19,6 +19,7 @@ from sky.adaptors import aws
|
|
19
19
|
from sky.adaptors import azure
|
20
20
|
from sky.adaptors import cloudflare
|
21
21
|
from sky.adaptors import ibm
|
22
|
+
from sky.adaptors import nebius
|
22
23
|
from sky.adaptors import oci
|
23
24
|
from sky.clouds import gcp
|
24
25
|
from sky.data import data_utils
|
@@ -543,6 +544,70 @@ class OciCloudStorage(CloudStorage):
|
|
543
544
|
return download_via_ocicli
|
544
545
|
|
545
546
|
|
547
|
+
class NebiusCloudStorage(CloudStorage):
|
548
|
+
"""Nebius Cloud Storage."""
|
549
|
+
|
550
|
+
# List of commands to install AWS CLI
|
551
|
+
_GET_AWSCLI = [
|
552
|
+
'aws --version >/dev/null 2>&1 || '
|
553
|
+
f'{constants.SKY_UV_PIP_CMD} install awscli',
|
554
|
+
]
|
555
|
+
|
556
|
+
def is_directory(self, url: str) -> bool:
|
557
|
+
"""Returns whether nebius 'url' is a directory.
|
558
|
+
|
559
|
+
In cloud object stores, a "directory" refers to a regular object whose
|
560
|
+
name is a prefix of other objects.
|
561
|
+
"""
|
562
|
+
nebius_s3 = nebius.resource('s3')
|
563
|
+
bucket_name, path = data_utils.split_nebius_path(url)
|
564
|
+
bucket = nebius_s3.Bucket(bucket_name)
|
565
|
+
|
566
|
+
num_objects = 0
|
567
|
+
for obj in bucket.objects.filter(Prefix=path):
|
568
|
+
num_objects += 1
|
569
|
+
if obj.key == path:
|
570
|
+
return False
|
571
|
+
# If there are more than 1 object in filter, then it is a directory
|
572
|
+
if num_objects == 3:
|
573
|
+
return True
|
574
|
+
|
575
|
+
# A directory with few or no items
|
576
|
+
return True
|
577
|
+
|
578
|
+
def make_sync_dir_command(self, source: str, destination: str) -> str:
|
579
|
+
"""Downloads using AWS CLI."""
|
580
|
+
# AWS Sync by default uses 10 threads to upload files to the bucket.
|
581
|
+
# To increase parallelism, modify max_concurrent_requests in your
|
582
|
+
# aws config file (Default path: ~/.aws/config).
|
583
|
+
endpoint_url = nebius.create_endpoint()
|
584
|
+
assert 'nebius://' in source, 'nebius:// is not in source'
|
585
|
+
source = source.replace('nebius://', 's3://')
|
586
|
+
download_via_awscli = (f'{constants.SKY_REMOTE_PYTHON_ENV}/bin/aws s3 '
|
587
|
+
'sync --no-follow-symlinks '
|
588
|
+
f'{source} {destination} '
|
589
|
+
f'--endpoint {endpoint_url} '
|
590
|
+
f'--profile={nebius.NEBIUS_PROFILE_NAME}')
|
591
|
+
|
592
|
+
all_commands = list(self._GET_AWSCLI)
|
593
|
+
all_commands.append(download_via_awscli)
|
594
|
+
return ' && '.join(all_commands)
|
595
|
+
|
596
|
+
def make_sync_file_command(self, source: str, destination: str) -> str:
|
597
|
+
"""Downloads a file using AWS CLI."""
|
598
|
+
endpoint_url = nebius.create_endpoint()
|
599
|
+
assert 'nebius://' in source, 'nebius:// is not in source'
|
600
|
+
source = source.replace('nebius://', 's3://')
|
601
|
+
download_via_awscli = (f'{constants.SKY_REMOTE_PYTHON_ENV}/bin/aws s3 '
|
602
|
+
f'cp {source} {destination} '
|
603
|
+
f'--endpoint {endpoint_url} '
|
604
|
+
f'--profile={nebius.NEBIUS_PROFILE_NAME}')
|
605
|
+
|
606
|
+
all_commands = list(self._GET_AWSCLI)
|
607
|
+
all_commands.append(download_via_awscli)
|
608
|
+
return ' && '.join(all_commands)
|
609
|
+
|
610
|
+
|
546
611
|
def get_storage_from_path(url: str) -> CloudStorage:
|
547
612
|
"""Returns a CloudStorage by identifying the scheme:// in a URL."""
|
548
613
|
result = urllib.parse.urlsplit(url)
|
@@ -559,6 +624,7 @@ _REGISTRY = {
|
|
559
624
|
'r2': R2CloudStorage(),
|
560
625
|
'cos': IBMCosCloudStorage(),
|
561
626
|
'oci': OciCloudStorage(),
|
627
|
+
'nebius': NebiusCloudStorage(),
|
562
628
|
# TODO: This is a hack, as Azure URL starts with https://, we should
|
563
629
|
# refactor the registry to be able to take regex, so that Azure blob can
|
564
630
|
# be identified with `https://(.*?)\.blob\.core\.windows\.net`
|
sky/clouds/aws.py
CHANGED
@@ -558,11 +558,23 @@ class AWS(clouds.Cloud):
|
|
558
558
|
return resources_utils.FeasibleResources(_make(instance_list),
|
559
559
|
fuzzy_candidate_list, None)
|
560
560
|
|
561
|
+
@classmethod
|
562
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
563
|
+
"""Checks if the user has access credentials to this AWS's compute service."""
|
564
|
+
return cls._check_credentials()
|
565
|
+
|
566
|
+
@classmethod
|
567
|
+
def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
568
|
+
"""Checks if the user has access credentials to this AWS's storage service."""
|
569
|
+
# TODO(seungjin): Implement separate check for
|
570
|
+
# if the user has access to S3.
|
571
|
+
return cls._check_credentials()
|
572
|
+
|
561
573
|
@classmethod
|
562
574
|
@annotations.lru_cache(scope='global',
|
563
575
|
maxsize=1) # Cache since getting identity is slow.
|
564
|
-
def
|
565
|
-
"""Checks if the user has access credentials to
|
576
|
+
def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
|
577
|
+
"""Checks if the user has access credentials to AWS."""
|
566
578
|
|
567
579
|
dependency_installation_hints = (
|
568
580
|
'AWS dependencies are not installed. '
|
@@ -666,11 +678,6 @@ class AWS(clouds.Cloud):
|
|
666
678
|
f'{common_utils.format_exception(e, use_bracket=True)}')
|
667
679
|
return True, hints
|
668
680
|
|
669
|
-
@classmethod
|
670
|
-
def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
671
|
-
# TODO(seungjin): Check if the user has access to S3.
|
672
|
-
return cls.check_credentials()
|
673
|
-
|
674
681
|
@classmethod
|
675
682
|
def _current_identity_type(cls) -> Optional[AWSIdentityType]:
|
676
683
|
stdout = cls._aws_configure_list()
|
sky/clouds/azure.py
CHANGED
@@ -512,7 +512,19 @@ class Azure(clouds.Cloud):
|
|
512
512
|
fuzzy_candidate_list, None)
|
513
513
|
|
514
514
|
@classmethod
|
515
|
-
def
|
515
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
516
|
+
"""Checks if the user has access credentials to this cloud's compute service."""
|
517
|
+
return cls._check_credentials()
|
518
|
+
|
519
|
+
@classmethod
|
520
|
+
def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
521
|
+
"""Checks if the user has access credentials to this cloud's storage service."""
|
522
|
+
# TODO(seungjin): Implement separate check for
|
523
|
+
# if the user has access to Azure Blob Storage.
|
524
|
+
return cls._check_credentials()
|
525
|
+
|
526
|
+
@classmethod
|
527
|
+
def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
|
516
528
|
"""Checks if the user has access credentials to this cloud."""
|
517
529
|
help_str = (
|
518
530
|
' Run the following commands:'
|
@@ -574,11 +586,6 @@ class Azure(clouds.Cloud):
|
|
574
586
|
return service_catalog.instance_type_exists(instance_type,
|
575
587
|
clouds='azure')
|
576
588
|
|
577
|
-
@classmethod
|
578
|
-
def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
579
|
-
# TODO(seungjin): Check if the user has access to Azure Blob Storage.
|
580
|
-
return cls.check_credentials()
|
581
|
-
|
582
589
|
@classmethod
|
583
590
|
@annotations.lru_cache(scope='global',
|
584
591
|
maxsize=1) # Cache since getting identity is slow.
|
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,17 @@ class CloudImplementationFeatures(enum.Enum):
|
|
48
50
|
AUTO_TERMINATE = 'auto_terminate' # Pod/VM can stop or down itself
|
49
51
|
|
50
52
|
|
53
|
+
# Use str, enum.Enum to allow CloudCapability to be used as a string.
|
54
|
+
class CloudCapability(str, enum.Enum):
|
55
|
+
# Compute capability.
|
56
|
+
COMPUTE = 'compute'
|
57
|
+
# Storage capability.
|
58
|
+
STORAGE = 'storage'
|
59
|
+
|
60
|
+
|
61
|
+
ALL_CAPABILITIES = [CloudCapability.COMPUTE, CloudCapability.STORAGE]
|
62
|
+
|
63
|
+
|
51
64
|
class Region(collections.namedtuple('Region', ['name'])):
|
52
65
|
"""A region."""
|
53
66
|
name: str
|
@@ -435,25 +448,36 @@ class Cloud:
|
|
435
448
|
return {reservation: 0 for reservation in specific_reservations}
|
436
449
|
|
437
450
|
@classmethod
|
438
|
-
def check_credentials(
|
451
|
+
def check_credentials(
|
452
|
+
cls,
|
453
|
+
cloud_capability: CloudCapability) -> Tuple[bool, Optional[str]]:
|
439
454
|
"""Checks if the user has access credentials to this cloud.
|
440
455
|
|
441
456
|
Returns a boolean of whether the user can access this cloud, and a
|
442
457
|
string describing the reason if the user cannot access.
|
458
|
+
|
459
|
+
Raises NotSupportedError if the capability is
|
460
|
+
not supported by this cloud.
|
443
461
|
"""
|
444
|
-
|
462
|
+
if cloud_capability == CloudCapability.COMPUTE:
|
463
|
+
return cls._check_compute_credentials()
|
464
|
+
elif cloud_capability == CloudCapability.STORAGE:
|
465
|
+
return cls._check_storage_credentials()
|
466
|
+
assert_never(cloud_capability)
|
445
467
|
|
446
468
|
@classmethod
|
447
|
-
def
|
448
|
-
"""Checks if the user has access credentials to
|
469
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
470
|
+
"""Checks if the user has access credentials to
|
471
|
+
this cloud's compute service."""
|
472
|
+
raise exceptions.NotSupportedError(
|
473
|
+
f'{cls._REPR} does not support {CloudCapability.COMPUTE.value}.')
|
449
474
|
|
450
|
-
|
451
|
-
|
452
|
-
"""
|
453
|
-
|
454
|
-
# unless it overrides this method.
|
475
|
+
@classmethod
|
476
|
+
def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
477
|
+
"""Checks if the user has access credentials to
|
478
|
+
this cloud's storage service."""
|
455
479
|
raise exceptions.NotSupportedError(
|
456
|
-
f'{cls._REPR} does not support
|
480
|
+
f'{cls._REPR} does not support {CloudCapability.STORAGE.value}.')
|
457
481
|
|
458
482
|
# TODO(zhwu): Make the return type immutable.
|
459
483
|
@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
|
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
|
263
|
-
"""Verify that the user has valid credentials for
|
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
|
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
|
723
|
-
"""Checks if the user has
|
724
|
-
return cls._check_credentials(
|
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
|
735
|
-
"""Checks if the user has
|
736
|
-
return cls._check_credentials(
|
737
|
-
[
|
738
|
-
|
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
|
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
|
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()
|
sky/clouds/lambda_cloud.py
CHANGED
@@ -241,7 +241,9 @@ class Lambda(clouds.Cloud):
|
|
241
241
|
fuzzy_candidate_list, None)
|
242
242
|
|
243
243
|
@classmethod
|
244
|
-
def
|
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
@@ -1,5 +1,6 @@
|
|
1
1
|
""" Nebius Cloud. """
|
2
2
|
import logging
|
3
|
+
import os
|
3
4
|
import typing
|
4
5
|
from typing import Dict, Iterator, List, Optional, Tuple, Union
|
5
6
|
|
@@ -20,6 +21,22 @@ _CREDENTIAL_FILES = [
|
|
20
21
|
nebius.NEBIUS_CREDENTIALS_FILENAME
|
21
22
|
]
|
22
23
|
|
24
|
+
_INDENT_PREFIX = ' '
|
25
|
+
|
26
|
+
|
27
|
+
def nebius_profile_in_aws_cred() -> bool:
|
28
|
+
"""Checks if Nebius Object Storage profile is set in aws credentials."""
|
29
|
+
|
30
|
+
profile_path = os.path.expanduser('~/.aws/credentials')
|
31
|
+
nebius_profile_exists = False
|
32
|
+
if os.path.isfile(profile_path):
|
33
|
+
with open(profile_path, 'r', encoding='utf-8') as file:
|
34
|
+
for line in file:
|
35
|
+
if f'[{nebius.NEBIUS_PROFILE_NAME}]' in line:
|
36
|
+
nebius_profile_exists = True
|
37
|
+
|
38
|
+
return nebius_profile_exists
|
39
|
+
|
23
40
|
|
24
41
|
@registry.CLOUD_REGISTRY.register
|
25
42
|
class Nebius(clouds.Cloud):
|
@@ -250,17 +267,19 @@ class Nebius(clouds.Cloud):
|
|
250
267
|
fuzzy_candidate_list, None)
|
251
268
|
|
252
269
|
@classmethod
|
253
|
-
def
|
254
|
-
"""
|
270
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
271
|
+
"""Checks if the user has access credentials to
|
272
|
+
Nebius's compute service."""
|
255
273
|
logging.debug('Nebius cloud check credentials')
|
256
|
-
token_cred_msg = (
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
'
|
263
|
-
f'
|
274
|
+
token_cred_msg = (
|
275
|
+
f'{_INDENT_PREFIX}Credentials can be set up by running: \n'
|
276
|
+
f'{_INDENT_PREFIX} $ nebius iam get-access-token > {nebius.NEBIUS_IAM_TOKEN_PATH} \n' # pylint: disable=line-too-long
|
277
|
+
f'{_INDENT_PREFIX} or generate ~/.nebius/credentials.json')
|
278
|
+
|
279
|
+
tenant_msg = (f'{_INDENT_PREFIX}Copy your tenat ID from the web console and save it to file \n' # pylint: disable=line-too-long
|
280
|
+
f'{_INDENT_PREFIX} $ echo $NEBIUS_TENANT_ID_PATH > {nebius.NEBIUS_TENANT_ID_PATH} \n' # pylint: disable=line-too-long
|
281
|
+
f'{_INDENT_PREFIX} Or if you have 1 tenant you can run:\n' # pylint: disable=line-too-long
|
282
|
+
f'{_INDENT_PREFIX} $ nebius --format json iam whoami|jq -r \'.user_profile.tenants[0].tenant_id\' > {nebius.NEBIUS_TENANT_ID_PATH} \n') # pylint: disable=line-too-long
|
264
283
|
if not nebius.is_token_or_cred_file_exist():
|
265
284
|
return False, f'{token_cred_msg}'
|
266
285
|
sdk = nebius.sdk()
|
@@ -278,11 +297,40 @@ class Nebius(clouds.Cloud):
|
|
278
297
|
f'{tenant_msg}')
|
279
298
|
return True, None
|
280
299
|
|
300
|
+
@classmethod
|
301
|
+
def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
302
|
+
"""Checks if the user has access credentials to Nebius Object Storage.
|
303
|
+
|
304
|
+
Returns:
|
305
|
+
A tuple of a boolean value and a hint message where the bool
|
306
|
+
is True when credentials needed for Nebius Object Storage is set.
|
307
|
+
It is False when either of those are not set, which would hint
|
308
|
+
with a string on unset credential.
|
309
|
+
"""
|
310
|
+
hints = None
|
311
|
+
if not nebius_profile_in_aws_cred():
|
312
|
+
hints = (f'[{nebius.NEBIUS_PROFILE_NAME}] profile '
|
313
|
+
'is not set in ~/.aws/credentials.')
|
314
|
+
if hints:
|
315
|
+
hints += ' Run the following commands:'
|
316
|
+
if not nebius_profile_in_aws_cred():
|
317
|
+
hints += (
|
318
|
+
f'\n{_INDENT_PREFIX} $ pip install boto3'
|
319
|
+
f'\n{_INDENT_PREFIX} $ aws configure --profile nebius')
|
320
|
+
hints += (
|
321
|
+
f'\n{_INDENT_PREFIX}For more info: '
|
322
|
+
'https://docs.skypilot.co/en/latest/getting-started/installation.html#nebius' # pylint: disable=line-too-long
|
323
|
+
)
|
324
|
+
return (False, hints) if hints else (True, hints)
|
325
|
+
|
281
326
|
def get_credential_file_mounts(self) -> Dict[str, str]:
|
282
|
-
|
327
|
+
credential_file_mounts = {
|
283
328
|
f'~/.nebius/{filename}': f'~/.nebius/{filename}'
|
284
329
|
for filename in _CREDENTIAL_FILES
|
285
330
|
}
|
331
|
+
credential_file_mounts['~/.aws/credentials'] = '~/.aws/credentials'
|
332
|
+
|
333
|
+
return credential_file_mounts
|
286
334
|
|
287
335
|
@classmethod
|
288
336
|
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
|
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
|
253
|
-
"""
|
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
|
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
|
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
|
238
|
-
"""
|
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
|
258
|
-
"""Checks if the user has access credentials to
|
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
|
@@ -1134,9 +1136,9 @@ def local_down() -> None:
|
|
1134
1136
|
# Run sky check
|
1135
1137
|
with rich_utils.safe_status(
|
1136
1138
|
ux_utils.spinner_message('Running sky check...')):
|
1137
|
-
sky_check.
|
1138
|
-
|
1139
|
-
|
1139
|
+
sky_check.check_capability(sky_cloud.CloudCapability.COMPUTE,
|
1140
|
+
clouds=['kubernetes'],
|
1141
|
+
quiet=True)
|
1140
1142
|
logger.info(
|
1141
1143
|
ux_utils.finishing_message('Local cluster removed.',
|
1142
1144
|
log_path=log_path,
|