skypilot-nightly 1.0.0.dev20250319__py3-none-any.whl → 1.0.0.dev20250320__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 CHANGED
@@ -5,7 +5,7 @@ from typing import Optional
5
5
  import urllib.request
6
6
 
7
7
  # Replaced with the current commit when building the wheels.
8
- _SKYPILOT_COMMIT_SHA = '246e69ba16705c31b69143bfe76efcee17b6407f'
8
+ _SKYPILOT_COMMIT_SHA = 'a480f342522afcd17a3b30a20086f28333ddb7b5'
9
9
 
10
10
 
11
11
  def _get_git_commit():
@@ -35,7 +35,7 @@ def _get_git_commit():
35
35
 
36
36
 
37
37
  __commit__ = _get_git_commit()
38
- __version__ = '1.0.0.dev20250319'
38
+ __version__ = '1.0.0.dev20250320'
39
39
  __root_dir__ = os.path.dirname(os.path.abspath(__file__))
40
40
 
41
41
 
@@ -149,6 +149,10 @@ def create_endpoint():
149
149
 
150
150
 
151
151
  def check_credentials() -> Tuple[bool, Optional[str]]:
152
+ return check_storage_credentials()
153
+
154
+
155
+ def check_storage_credentials() -> Tuple[bool, Optional[str]]:
152
156
  """Checks if the user has access credentials to Cloudflare R2.
153
157
 
154
158
  Returns:
sky/check.py CHANGED
@@ -1,8 +1,9 @@
1
1
  """Credential checks: check cloud credentials and enable clouds."""
2
+ import enum
2
3
  import os
3
4
  import traceback
4
5
  from types import ModuleType
5
- from typing import Dict, Iterable, List, Optional, Tuple, Union
6
+ from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
6
7
 
7
8
  import click
8
9
  import colorama
@@ -20,44 +21,91 @@ CHECK_MARK_EMOJI = '\U00002714' # Heavy check mark unicode
20
21
  PARTY_POPPER_EMOJI = '\U0001F389' # Party popper unicode
21
22
 
22
23
 
23
- def check(
24
+ # Declaring CloudCapability as a subclass of str
25
+ # allows it to be JSON serializable.
26
+ class CloudCapability(str, enum.Enum):
27
+ # Compute capability.
28
+ COMPUTE = 'compute'
29
+ # Storage capability.
30
+ STORAGE = 'storage'
31
+
32
+
33
+ ALL_CAPABILITIES = [CloudCapability.COMPUTE, CloudCapability.STORAGE]
34
+
35
+
36
+ def check_capabilities(
24
37
  quiet: bool = False,
25
38
  verbose: bool = False,
26
39
  clouds: Optional[Iterable[str]] = None,
27
- ) -> List[str]:
40
+ capabilities: Optional[List[CloudCapability]] = None,
41
+ ) -> Dict[str, List[CloudCapability]]:
28
42
  echo = (lambda *_args, **_kwargs: None
29
43
  ) if quiet else lambda *args, **kwargs: click.echo(
30
44
  *args, **kwargs, color=True)
31
45
  echo('Checking credentials to enable clouds for SkyPilot.')
32
- enabled_clouds = []
33
- disabled_clouds = []
46
+ if capabilities is None:
47
+ capabilities = ALL_CAPABILITIES
48
+ assert capabilities is not None
49
+ enabled_clouds: Dict[str, List[CloudCapability]] = {}
50
+ disabled_clouds: Dict[str, List[CloudCapability]] = {}
51
+
52
+ def check_credentials(
53
+ cloud: Union[sky_clouds.Cloud, ModuleType],
54
+ capability: CloudCapability) -> Tuple[bool, Optional[str]]:
55
+ if capability == CloudCapability.COMPUTE:
56
+ return cloud.check_credentials()
57
+ elif capability == CloudCapability.STORAGE:
58
+ return cloud.check_storage_credentials()
59
+ else:
60
+ raise ValueError(f'Invalid capability: {capability}')
61
+
62
+ def get_cached_state(capability: CloudCapability) -> List[sky_clouds.Cloud]:
63
+ if capability == CloudCapability.COMPUTE:
64
+ return global_user_state.get_cached_enabled_clouds()
65
+ elif capability == CloudCapability.STORAGE:
66
+ return global_user_state.get_cached_enabled_storage_clouds()
67
+ else:
68
+ raise ValueError(f'Invalid capability: {capability}')
69
+
70
+ def set_cached_state(clouds: List[str],
71
+ capability: CloudCapability) -> None:
72
+ if capability == CloudCapability.COMPUTE:
73
+ global_user_state.set_enabled_clouds(clouds)
74
+ elif capability == CloudCapability.STORAGE:
75
+ global_user_state.set_enabled_storage_clouds(clouds)
76
+ else:
77
+ raise ValueError(f'Invalid capability: {capability}')
34
78
 
35
79
  def check_one_cloud(
36
80
  cloud_tuple: Tuple[str, Union[sky_clouds.Cloud,
37
81
  ModuleType]]) -> None:
38
82
  cloud_repr, cloud = cloud_tuple
39
- with rich_utils.safe_status(f'Checking {cloud_repr}...'):
40
- try:
41
- ok, reason = cloud.check_credentials()
42
- except Exception: # pylint: disable=broad-except
43
- # Catch all exceptions to prevent a single cloud from blocking
44
- # the check for other clouds.
45
- ok, reason = False, traceback.format_exc()
46
- status_msg = 'enabled' if ok else 'disabled'
47
- styles = {'fg': 'green', 'bold': False} if ok else {'dim': True}
48
- echo(' ' + click.style(f'{cloud_repr}: {status_msg}', **styles) +
49
- ' ' * 30)
50
- if ok:
51
- enabled_clouds.append(cloud_repr)
52
- if verbose and cloud is not cloudflare:
53
- activated_account = cloud.get_active_user_identity_str()
54
- if activated_account is not None:
55
- echo(f' Activated account: {activated_account}')
56
- if reason is not None:
57
- echo(f' Hint: {reason}')
58
- else:
59
- disabled_clouds.append(cloud_repr)
60
- echo(f' Reason: {reason}')
83
+ assert capabilities is not None
84
+ for capability in capabilities:
85
+ with rich_utils.safe_status(f'Checking {cloud_repr}...'):
86
+ try:
87
+ ok, reason = check_credentials(cloud, capability)
88
+ except exceptions.NotSupportedError:
89
+ continue
90
+ except Exception: # pylint: disable=broad-except
91
+ # Catch all exceptions to prevent a single cloud
92
+ # from blocking the check for other clouds.
93
+ ok, reason = False, traceback.format_exc()
94
+ status_msg = ('enabled' if ok else 'disabled')
95
+ styles = {'fg': 'green', 'bold': False} if ok else {'dim': True}
96
+ echo(' ' + click.style(f'{cloud_repr}: {status_msg}', **styles) +
97
+ ' ' * 30)
98
+ if ok:
99
+ enabled_clouds.setdefault(cloud_repr, []).append(capability)
100
+ if verbose and cloud is not cloudflare:
101
+ activated_account = cloud.get_active_user_identity_str()
102
+ if activated_account is not None:
103
+ echo(f' Activated account: {activated_account}')
104
+ if reason is not None:
105
+ echo(f' Hint: {reason}')
106
+ else:
107
+ disabled_clouds.setdefault(cloud_repr, []).append(capability)
108
+ echo(f' Reason: {reason}')
61
109
 
62
110
  def get_cloud_tuple(
63
111
  cloud_name: str) -> Tuple[str, Union[sky_clouds.Cloud, ModuleType]]:
@@ -99,33 +147,37 @@ def check(
99
147
  for cloud_tuple in sorted(clouds_to_check):
100
148
  check_one_cloud(cloud_tuple)
101
149
 
102
- # Cloudflare is not a real cloud in registry.CLOUD_REGISTRY, and should
103
- # not be inserted into the DB (otherwise `sky launch` and other code would
104
- # error out when it's trying to look it up in the registry).
105
- enabled_clouds_set = {
106
- cloud for cloud in enabled_clouds if not cloud.startswith('Cloudflare')
107
- }
108
- disabled_clouds_set = {
109
- cloud for cloud in disabled_clouds if not cloud.startswith('Cloudflare')
110
- }
111
- config_allowed_clouds_set = {
112
- cloud for cloud in config_allowed_cloud_names
113
- if not cloud.startswith('Cloudflare')
114
- }
115
- previously_enabled_clouds_set = {
116
- repr(cloud) for cloud in global_user_state.get_cached_enabled_clouds()
117
- }
118
-
119
150
  # Determine the set of enabled clouds: (previously enabled clouds + newly
120
151
  # enabled clouds - newly disabled clouds) intersected with
121
152
  # config_allowed_clouds, if specified in config.yaml.
122
153
  # This means that if a cloud is already enabled and is not included in
123
154
  # allowed_clouds in config.yaml, it will be disabled.
124
- all_enabled_clouds = (config_allowed_clouds_set & (
125
- (previously_enabled_clouds_set | enabled_clouds_set) -
126
- disabled_clouds_set))
127
- global_user_state.set_enabled_clouds(list(all_enabled_clouds))
128
-
155
+ all_enabled_clouds: Set[str] = set()
156
+ for capability in capabilities:
157
+ # Cloudflare is not a real cloud in registry.CLOUD_REGISTRY, and should
158
+ # not be inserted into the DB (otherwise `sky launch` and other code
159
+ # would error out when it's trying to look it up in the registry).
160
+ enabled_clouds_set = {
161
+ cloud for cloud, capabilities in enabled_clouds.items()
162
+ if capability in capabilities and not cloud.startswith('Cloudflare')
163
+ }
164
+ disabled_clouds_set = {
165
+ cloud for cloud, capabilities in disabled_clouds.items()
166
+ if capability in capabilities and not cloud.startswith('Cloudflare')
167
+ }
168
+ config_allowed_clouds_set = {
169
+ cloud for cloud in config_allowed_cloud_names
170
+ if not cloud.startswith('Cloudflare')
171
+ }
172
+ previously_enabled_clouds_set = {
173
+ repr(cloud) for cloud in get_cached_state(capability)
174
+ }
175
+ enabled_clouds_for_capability = (config_allowed_clouds_set & (
176
+ (previously_enabled_clouds_set | enabled_clouds_set) -
177
+ disabled_clouds_set))
178
+ set_cached_state(list(enabled_clouds_for_capability), capability)
179
+ all_enabled_clouds = all_enabled_clouds.union(
180
+ enabled_clouds_for_capability)
129
181
  disallowed_clouds_hint = None
130
182
  if disallowed_cloud_names:
131
183
  disallowed_clouds_hint = (
@@ -160,8 +212,7 @@ def check(
160
212
  # Pretty print for UX.
161
213
  if not quiet:
162
214
  enabled_clouds_str = '\n ' + '\n '.join([
163
- _format_enabled_cloud(cloud)
164
- for cloud in sorted(all_enabled_clouds)
215
+ _format_enabled_cloud(cloud) for cloud in sorted(enabled_clouds)
165
216
  ])
166
217
  echo(f'\n{colorama.Fore.GREEN}{PARTY_POPPER_EMOJI} '
167
218
  f'Enabled clouds {PARTY_POPPER_EMOJI}'
@@ -169,6 +220,23 @@ def check(
169
220
  return enabled_clouds
170
221
 
171
222
 
223
+ # 'sky check' command and associated '/check' server endpoint
224
+ # only checks compute capability for backward compatibility.
225
+ # This necessitates setting default capability to CloudCapability.COMPUTE.
226
+ def check(
227
+ quiet: bool = False,
228
+ verbose: bool = False,
229
+ clouds: Optional[Iterable[str]] = None,
230
+ capability: CloudCapability = CloudCapability.COMPUTE,
231
+ ) -> List[str]:
232
+ clouds_with_capability = []
233
+ enabled_clouds = check_capabilities(quiet, verbose, clouds, [capability])
234
+ for cloud, capabilities in enabled_clouds.items():
235
+ if capability in capabilities:
236
+ clouds_with_capability.append(cloud)
237
+ return clouds_with_capability
238
+
239
+
172
240
  def get_cached_enabled_clouds_or_refresh(
173
241
  raise_if_no_cloud_access: bool = False) -> List[sky_clouds.Cloud]:
174
242
  """Returns cached enabled clouds and if no cloud is enabled, refresh.
@@ -186,7 +254,7 @@ def get_cached_enabled_clouds_or_refresh(
186
254
  cached_enabled_clouds = global_user_state.get_cached_enabled_clouds()
187
255
  if not cached_enabled_clouds:
188
256
  try:
189
- check(quiet=True)
257
+ check(quiet=True, capability=CloudCapability.COMPUTE)
190
258
  except SystemExit:
191
259
  # If no cloud is enabled, check() will raise SystemExit.
192
260
  # Here we catch it and raise the exception later only if
@@ -201,6 +269,41 @@ def get_cached_enabled_clouds_or_refresh(
201
269
  return cached_enabled_clouds
202
270
 
203
271
 
272
+ def get_cached_enabled_storage_clouds_or_refresh(
273
+ raise_if_no_cloud_access: bool = False) -> List[sky_clouds.Cloud]:
274
+ """Returns cached enabled storage clouds and if no cloud is enabled,
275
+ refresh.
276
+
277
+ This function will perform a refresh if no public cloud is enabled.
278
+
279
+ Args:
280
+ raise_if_no_cloud_access: if True, raise an exception if no public
281
+ cloud is enabled.
282
+
283
+ Raises:
284
+ exceptions.NoCloudAccessError: if no public cloud is enabled and
285
+ raise_if_no_cloud_access is set to True.
286
+ """
287
+ cached_enabled_storage_clouds = (
288
+ global_user_state.get_cached_enabled_storage_clouds())
289
+ if not cached_enabled_storage_clouds:
290
+ try:
291
+ check(quiet=True, capability=CloudCapability.STORAGE)
292
+ except SystemExit:
293
+ # If no cloud is enabled, check() will raise SystemExit.
294
+ # Here we catch it and raise the exception later only if
295
+ # raise_if_no_cloud_access is set to True.
296
+ pass
297
+ cached_enabled_storage_clouds = (
298
+ global_user_state.get_cached_enabled_storage_clouds())
299
+ if raise_if_no_cloud_access and not cached_enabled_storage_clouds:
300
+ with ux_utils.print_exception_no_traceback():
301
+ raise exceptions.NoCloudAccessError(
302
+ 'Cloud access is not set up. Run: '
303
+ f'{colorama.Style.BRIGHT}sky check{colorama.Style.RESET_ALL}')
304
+ return cached_enabled_storage_clouds
305
+
306
+
204
307
  def get_cloud_credential_file_mounts(
205
308
  excluded_clouds: Optional[Iterable[sky_clouds.Cloud]]
206
309
  ) -> Dict[str, str]:
@@ -226,7 +329,7 @@ def get_cloud_credential_file_mounts(
226
329
  # Currently, get_cached_enabled_clouds_or_refresh() does not support r2 as
227
330
  # only clouds with computing instances are marked as enabled by skypilot.
228
331
  # This will be removed when cloudflare/r2 is added as a 'cloud'.
229
- r2_is_enabled, _ = cloudflare.check_credentials()
332
+ r2_is_enabled, _ = cloudflare.check_storage_credentials()
230
333
  if r2_is_enabled:
231
334
  r2_credential_mounts = cloudflare.get_credential_file_mounts()
232
335
  file_mounts.update(r2_credential_mounts)
sky/clouds/aws.py CHANGED
@@ -666,6 +666,11 @@ class AWS(clouds.Cloud):
666
666
  f'{common_utils.format_exception(e, use_bracket=True)}')
667
667
  return True, hints
668
668
 
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
+
669
674
  @classmethod
670
675
  def _current_identity_type(cls) -> Optional[AWSIdentityType]:
671
676
  stdout = cls._aws_configure_list()
sky/clouds/azure.py CHANGED
@@ -574,6 +574,11 @@ class Azure(clouds.Cloud):
574
574
  return service_catalog.instance_type_exists(instance_type,
575
575
  clouds='azure')
576
576
 
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
+
577
582
  @classmethod
578
583
  @annotations.lru_cache(scope='global',
579
584
  maxsize=1) # Cache since getting identity is slow.
sky/clouds/cloud.py CHANGED
@@ -443,6 +443,18 @@ class Cloud:
443
443
  """
444
444
  raise NotImplementedError
445
445
 
446
+ @classmethod
447
+ def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
448
+ """Checks if the user has access credentials to this cloud's storage.
449
+
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.
455
+ raise exceptions.NotSupportedError(
456
+ f'{cls._REPR} does not support storage.')
457
+
446
458
  # TODO(zhwu): Make the return type immutable.
447
459
  @classmethod
448
460
  def get_user_identities(cls) -> Optional[List[List[str]]]:
sky/clouds/gcp.py CHANGED
@@ -124,6 +124,7 @@ def _run_output(cmd):
124
124
 
125
125
 
126
126
  def is_api_disabled(endpoint: str, project_id: str) -> bool:
127
+ # requires serviceusage.services.list
127
128
  proc = subprocess.run((f'gcloud services list --project {project_id} '
128
129
  f' | grep {endpoint}.googleapis.com'),
129
130
  check=False,
@@ -719,6 +720,28 @@ class GCP(clouds.Cloud):
719
720
 
720
721
  @classmethod
721
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.
725
+ [
726
+ ('compute', 'Compute Engine'),
727
+ ('cloudresourcemanager', 'Cloud Resource Manager'),
728
+ ('iam', 'Identity and Access Management (IAM)'),
729
+ ('tpu', 'Cloud TPU'), # Keep as final element.
730
+ ],
731
+ gcp_utils.get_minimal_compute_permissions())
732
+
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())
740
+
741
+ @classmethod
742
+ def _check_credentials(
743
+ cls, apis: List[Tuple[str, str]],
744
+ gcp_minimal_permissions: List[str]) -> Tuple[bool, Optional[str]]:
722
745
  """Checks if the user has access credentials to this cloud."""
723
746
  try:
724
747
  # pylint: disable=import-outside-toplevel,unused-import
@@ -783,13 +806,37 @@ class GCP(clouds.Cloud):
783
806
  f'{cls._INDENT_PREFIX}Details: '
784
807
  f'{common_utils.format_exception(e, use_bracket=True)}')
785
808
 
786
- # Check APIs.
787
- apis = (
788
- ('compute', 'Compute Engine'),
789
- ('cloudresourcemanager', 'Cloud Resource Manager'),
790
- ('iam', 'Identity and Access Management (IAM)'),
791
- ('tpu', 'Cloud TPU'), # Keep as final element.
792
- )
809
+ # pylint: disable=import-outside-toplevel,unused-import
810
+ import google.auth
811
+
812
+ # This takes user's credential info from "~/.config/gcloud/application_default_credentials.json". # pylint: disable=line-too-long
813
+ credentials, project = google.auth.default()
814
+ crm = gcp.build('cloudresourcemanager',
815
+ 'v1',
816
+ credentials=credentials,
817
+ cache_discovery=False)
818
+ permissions = {'permissions': gcp_minimal_permissions}
819
+ request = crm.projects().testIamPermissions(resource=project,
820
+ body=permissions)
821
+ try:
822
+ ret_permissions = request.execute().get('permissions', [])
823
+ except gcp.gcp_auth_refresh_error_exception() as e:
824
+ return False, common_utils.format_exception(e, use_bracket=True)
825
+
826
+ diffs = set(gcp_minimal_permissions).difference(set(ret_permissions))
827
+ if diffs:
828
+ identity_str = identity[0] if identity else None
829
+ return False, (
830
+ 'The following permissions are not enabled for the current '
831
+ f'GCP identity ({identity_str}):\n '
832
+ f'{diffs}\n '
833
+ 'For more details, visit: https://docs.skypilot.co/en/latest/cloud-setup/cloud-permissions/gcp.html') # pylint: disable=line-too-long
834
+
835
+ # This code must be executed after the iam check above,
836
+ # as the check below for api enablement itself needs:
837
+ # - serviceusage.services.enable
838
+ # - serviceusage.services.list
839
+ # iam permissions.
793
840
  enabled_api = False
794
841
  for endpoint, display_name in apis:
795
842
  if is_api_disabled(endpoint, project_id):
@@ -801,6 +848,7 @@ class GCP(clouds.Cloud):
801
848
  suffix = ' (free of charge)'
802
849
  print(f'\nEnabling {display_name} API{suffix}...')
803
850
  t1 = time.time()
851
+ # requires serviceusage.services.enable
804
852
  proc = subprocess.run(
805
853
  f'gcloud services enable {endpoint}.googleapis.com '
806
854
  f'--project {project_id}',
@@ -830,32 +878,6 @@ class GCP(clouds.Cloud):
830
878
  'effect. If any SkyPilot commands/calls failed, retry after '
831
879
  'some time.')
832
880
 
833
- # pylint: disable=import-outside-toplevel,unused-import
834
- import google.auth
835
-
836
- # This takes user's credential info from "~/.config/gcloud/application_default_credentials.json". # pylint: disable=line-too-long
837
- credentials, project = google.auth.default()
838
- crm = gcp.build('cloudresourcemanager',
839
- 'v1',
840
- credentials=credentials,
841
- cache_discovery=False)
842
- gcp_minimal_permissions = gcp_utils.get_minimal_permissions()
843
- permissions = {'permissions': gcp_minimal_permissions}
844
- request = crm.projects().testIamPermissions(resource=project,
845
- body=permissions)
846
- try:
847
- ret_permissions = request.execute().get('permissions', [])
848
- except gcp.gcp_auth_refresh_error_exception() as e:
849
- return False, common_utils.format_exception(e, use_bracket=True)
850
-
851
- diffs = set(gcp_minimal_permissions).difference(set(ret_permissions))
852
- if diffs:
853
- identity_str = identity[0] if identity else None
854
- return False, (
855
- 'The following permissions are not enabled for the current '
856
- f'GCP identity ({identity_str}):\n '
857
- f'{diffs}\n '
858
- 'For more details, visit: https://docs.skypilot.co/en/latest/cloud-setup/cloud-permissions/gcp.html') # pylint: disable=line-too-long
859
881
  return True, None
860
882
 
861
883
  def get_credential_file_mounts(self) -> Dict[str, str]:
sky/clouds/ibm.py CHANGED
@@ -433,6 +433,11 @@ class IBM(clouds.Cloud):
433
433
  except Exception as e:
434
434
  return (False, f'{str(e)}' + help_str)
435
435
 
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
+
436
441
  def get_credential_file_mounts(self) -> Dict[str, str]:
437
442
  """Returns a {remote:local} credential path mapping
438
443
  written to the cluster's file_mounts segment
sky/clouds/oci.py CHANGED
@@ -456,6 +456,11 @@ class OCI(clouds.Cloud):
456
456
  f'{cls._INDENT_PREFIX}Error details: '
457
457
  f'{common_utils.format_exception(e, use_bracket=True)}')
458
458
 
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
+
459
464
  @classmethod
460
465
  def check_disk_tier(
461
466
  cls, instance_type: Optional[str],
@@ -167,7 +167,7 @@ def _list_reservations_for_instance_type(
167
167
  return [GCPReservation.from_dict(r) for r in json.loads(stdout)]
168
168
 
169
169
 
170
- def get_minimal_permissions() -> List[str]:
170
+ def get_minimal_compute_permissions() -> List[str]:
171
171
  permissions = copy.copy(constants.VM_MINIMAL_PERMISSIONS)
172
172
  if skypilot_config.get_nested(('gcp', 'vpc_name'), None) is None:
173
173
  # If custom VPC is not specified, permissions to modify network are
@@ -179,4 +179,14 @@ def get_minimal_permissions() -> List[str]:
179
179
  skypilot_config.get_nested(('gcp', 'specific_reservations'), [])):
180
180
  permissions += constants.RESERVATION_PERMISSIONS
181
181
 
182
+ permissions += constants.GCP_MINIMAL_PERMISSIONS
183
+
184
+ return permissions
185
+
186
+
187
+ def get_minimal_storage_permissions() -> List[str]:
188
+ permissions = copy.copy(constants.STORAGE_MINIMAL_PERMISSIONS)
189
+
190
+ permissions += constants.GCP_MINIMAL_PERMISSIONS
191
+
182
192
  return permissions
sky/core.py CHANGED
@@ -1134,7 +1134,9 @@ def local_down() -> None:
1134
1134
  # Run sky check
1135
1135
  with rich_utils.safe_status(
1136
1136
  ux_utils.spinner_message('Running sky check...')):
1137
- sky_check.check(clouds=['kubernetes'], quiet=True)
1137
+ sky_check.check(clouds=['kubernetes'],
1138
+ quiet=True,
1139
+ capability=sky_check.CloudCapability.COMPUTE)
1138
1140
  logger.info(
1139
1141
  ux_utils.finishing_message('Local cluster removed.',
1140
1142
  log_path=log_path,
sky/data/storage.py CHANGED
@@ -82,20 +82,17 @@ def get_cached_enabled_storage_clouds_or_refresh(
82
82
  raise_if_no_cloud_access: bool = False) -> List[str]:
83
83
  # This is a temporary solution until https://github.com/skypilot-org/skypilot/issues/1943 # pylint: disable=line-too-long
84
84
  # is resolved by implementing separate 'enabled_storage_clouds'
85
- enabled_clouds = sky_check.get_cached_enabled_clouds_or_refresh()
85
+ enabled_clouds = sky_check.get_cached_enabled_storage_clouds_or_refresh()
86
86
  enabled_clouds = [str(cloud) for cloud in enabled_clouds]
87
87
 
88
- enabled_storage_clouds = [
89
- cloud for cloud in enabled_clouds if cloud in STORE_ENABLED_CLOUDS
90
- ]
91
- r2_is_enabled, _ = cloudflare.check_credentials()
88
+ r2_is_enabled, _ = cloudflare.check_storage_credentials()
92
89
  if r2_is_enabled:
93
- enabled_storage_clouds.append(cloudflare.NAME)
94
- if raise_if_no_cloud_access and not enabled_storage_clouds:
90
+ enabled_clouds.append(cloudflare.NAME)
91
+ if raise_if_no_cloud_access and not enabled_clouds:
95
92
  raise exceptions.NoCloudAccessError(
96
93
  'No cloud access available for storage. '
97
94
  'Please check your cloud credentials.')
98
- return enabled_storage_clouds
95
+ return enabled_clouds
99
96
 
100
97
 
101
98
  def _is_storage_cloud_enabled(cloud_name: str,
@@ -105,7 +102,8 @@ def _is_storage_cloud_enabled(cloud_name: str,
105
102
  return True
106
103
  if try_fix_with_sky_check:
107
104
  # TODO(zhwu): Only check the specified cloud to speed up.
108
- sky_check.check(quiet=True)
105
+ sky_check.check(quiet=True,
106
+ capability=sky_check.CloudCapability.STORAGE)
109
107
  return _is_storage_cloud_enabled(cloud_name,
110
108
  try_fix_with_sky_check=False)
111
109
  return False
sky/global_user_state.py CHANGED
@@ -31,6 +31,7 @@ if typing.TYPE_CHECKING:
31
31
  logger = sky_logging.init_logger(__name__)
32
32
 
33
33
  _ENABLED_CLOUDS_KEY = 'enabled_clouds'
34
+ _ENABLED_STORAGE_CLOUDS_KEY = 'enabled_storage_clouds'
34
35
 
35
36
  _DB_PATH = os.path.expanduser('~/.sky/state.db')
36
37
  pathlib.Path(_DB_PATH).parents[0].mkdir(parents=True, exist_ok=True)
@@ -817,12 +818,41 @@ def get_cached_enabled_clouds() -> List['clouds.Cloud']:
817
818
  return enabled_clouds
818
819
 
819
820
 
821
+ def get_cached_enabled_storage_clouds() -> List['clouds.Cloud']:
822
+ rows = _DB.cursor.execute('SELECT value FROM config WHERE key = ?',
823
+ (_ENABLED_STORAGE_CLOUDS_KEY,))
824
+ ret = []
825
+ for (value,) in rows:
826
+ ret = json.loads(value)
827
+ break
828
+ enabled_clouds: List['clouds.Cloud'] = []
829
+ for c in ret:
830
+ try:
831
+ cloud = registry.CLOUD_REGISTRY.from_str(c)
832
+ except ValueError:
833
+ # Handle the case for the clouds whose support has been removed from
834
+ # SkyPilot, e.g., 'local' was a cloud in the past and may be stored
835
+ # in the database for users before #3037. We should ignore removed
836
+ # clouds and continue.
837
+ continue
838
+ if cloud is not None:
839
+ enabled_clouds.append(cloud)
840
+ return enabled_clouds
841
+
842
+
820
843
  def set_enabled_clouds(enabled_clouds: List[str]) -> None:
821
844
  _DB.cursor.execute('INSERT OR REPLACE INTO config VALUES (?, ?)',
822
845
  (_ENABLED_CLOUDS_KEY, json.dumps(enabled_clouds)))
823
846
  _DB.conn.commit()
824
847
 
825
848
 
849
+ def set_enabled_storage_clouds(enabled_storage_clouds: List[str]) -> None:
850
+ _DB.cursor.execute(
851
+ 'INSERT OR REPLACE INTO config VALUES (?, ?)',
852
+ (_ENABLED_STORAGE_CLOUDS_KEY, json.dumps(enabled_storage_clouds)))
853
+ _DB.conn.commit()
854
+
855
+
826
856
  def add_or_update_storage(storage_name: str,
827
857
  storage_handle: 'Storage.StorageMetadata',
828
858
  storage_status: status_lib.StorageStatus):
sky/optimizer.py CHANGED
@@ -1225,7 +1225,8 @@ def _check_specified_clouds(dag: 'dag_lib.Dag') -> None:
1225
1225
  # Explicitly check again to update the enabled cloud list.
1226
1226
  sky_check.check(quiet=True,
1227
1227
  clouds=list(clouds_need_recheck -
1228
- global_disabled_clouds))
1228
+ global_disabled_clouds),
1229
+ capability=sky_check.CloudCapability.COMPUTE)
1229
1230
  enabled_clouds = sky_check.get_cached_enabled_clouds_or_refresh(
1230
1231
  raise_if_no_cloud_access=True)
1231
1232
  disabled_clouds = (clouds_need_recheck -
@@ -297,8 +297,8 @@ def _is_permission_satisfied(service_account, crm, iam, required_permissions,
297
297
  def _configure_iam_role(config: common.ProvisionConfig, crm, iam) -> dict:
298
298
  """Setup a gcp service account with IAM roles.
299
299
 
300
- Creates a gcp service acconut and binds IAM roles which allow it to control
301
- control storage/compute services. Specifically, the head node needs to have
300
+ Creates a gcp service account and binds IAM roles which allow it to control
301
+ storage/compute services. Specifically, the head node needs to have
302
302
  an IAM role that allows it to create further gce instances and store items
303
303
  in google cloud storage.
304
304
 
@@ -311,7 +311,7 @@ def _configure_iam_role(config: common.ProvisionConfig, crm, iam) -> dict:
311
311
  )
312
312
  service_account = _get_service_account(email, project_id, iam)
313
313
 
314
- permissions = gcp_utils.get_minimal_permissions()
314
+ permissions = gcp_utils.get_minimal_compute_permissions()
315
315
  roles = constants.DEFAULT_SERVICE_ACCOUNT_ROLES
316
316
  if config.provider_config.get(constants.HAS_TPU_PROVIDER_FIELD, False):
317
317
  roles = (constants.DEFAULT_SERVICE_ACCOUNT_ROLES +
@@ -141,6 +141,11 @@ FIREWALL_RULES_TEMPLATE = [
141
141
  },
142
142
  ]
143
143
 
144
+ GCP_MINIMAL_PERMISSIONS = [
145
+ 'serviceusage.services.enable',
146
+ 'serviceusage.services.list',
147
+ ]
148
+
144
149
  # A list of permissions required to run SkyPilot on GCP.
145
150
  # Keep this in sync with https://docs.skypilot.co/en/latest/cloud-setup/cloud-permissions/gcp.html # pylint: disable=line-too-long
146
151
  VM_MINIMAL_PERMISSIONS = [
@@ -170,13 +175,22 @@ VM_MINIMAL_PERMISSIONS = [
170
175
  # Check: sky.provision.gcp.config::_is_permission_satisfied
171
176
  # 'iam.serviceAccounts.actAs',
172
177
  'iam.serviceAccounts.get',
173
- 'serviceusage.services.enable',
174
- 'serviceusage.services.list',
175
178
  'serviceusage.services.use',
176
179
  'resourcemanager.projects.get',
177
180
  'resourcemanager.projects.getIamPolicy',
178
181
  ]
179
182
 
183
+ STORAGE_MINIMAL_PERMISSIONS = [
184
+ 'storage.buckets.create',
185
+ 'storage.buckets.get',
186
+ 'storage.buckets.delete',
187
+ 'storage.objects.create',
188
+ 'storage.objects.update',
189
+ 'storage.objects.delete',
190
+ 'storage.objects.get',
191
+ 'storage.objects.list',
192
+ ]
193
+
180
194
  # Permissions implied by GCP built-in roles. We hardcode these here, as we
181
195
  # cannot get the permissions of built-in role from the GCP Python API.
182
196
  # The lists are not exhaustive, but should cover the permissions listed in
@@ -586,8 +586,11 @@ def open_ports(
586
586
  }
587
587
  handlers: List[Type[instance_utils.GCPInstance]] = [
588
588
  instance_utils.GCPComputeInstance,
589
- instance_utils.GCPTPUVMInstance,
590
589
  ]
590
+ use_tpu_vms = provider_config.get('_has_tpus', False)
591
+ if use_tpu_vms:
592
+ handlers.append(instance_utils.GCPTPUVMInstance)
593
+
591
594
  handler_to_instances = _filter_instances(handlers, project_id, zone,
592
595
  label_filters, lambda _: None)
593
596
  operations = collections.defaultdict(list)
@@ -663,18 +663,25 @@ class GKEAutoscaler(Autoscaler):
663
663
 
664
664
  # Check if any node pool with autoscaling enabled can
665
665
  # fit the instance type.
666
- for node_pool in cluster['nodePools']:
667
- logger.debug(f'checking if node pool {node_pool["name"]} '
666
+ node_pools = cluster.get('nodePools', [])
667
+ for node_pool in node_pools:
668
+ name = node_pool.get('name', '')
669
+ logger.debug(f'checking if node pool {name} '
668
670
  'has autoscaling enabled.')
669
- if (node_pool['autoscaling'] is not None and
670
- 'enabled' in node_pool['autoscaling'] and
671
- node_pool['autoscaling']['enabled']):
672
- logger.debug(
673
- f'node pool {node_pool["name"]} has autoscaling enabled. '
674
- 'Checking if it can create a node '
675
- f'satisfying {instance_type}')
676
- if cls._check_instance_fits_gke_autoscaler_node_pool(
677
- instance_type, node_pool):
671
+ autoscaling_enabled = (node_pool.get('autoscaling',
672
+ {}).get('enabled', False))
673
+ if autoscaling_enabled:
674
+ logger.debug(f'node pool {name} has autoscaling enabled. '
675
+ 'Checking if it can create a node '
676
+ f'satisfying {instance_type}')
677
+ try:
678
+ if cls._check_instance_fits_gke_autoscaler_node_pool(
679
+ instance_type, node_pool):
680
+ return True
681
+ except KeyError:
682
+ logger.debug('encountered KeyError while checking if '
683
+ f'node pool {name} can create a node '
684
+ f'satisfying {instance_type}.')
678
685
  return True
679
686
  return False
680
687
 
@@ -776,9 +783,9 @@ class GKEAutoscaler(Autoscaler):
776
783
  to fit the instance type.
777
784
  """
778
785
  for accelerator in node_pool_accelerators:
779
- node_accelerator_type = GKELabelFormatter. \
780
- get_accelerator_from_label_value(
781
- accelerator['acceleratorType'])
786
+ node_accelerator_type = (
787
+ GKELabelFormatter.get_accelerator_from_label_value(
788
+ accelerator['acceleratorType']))
782
789
  node_accelerator_count = accelerator['acceleratorCount']
783
790
  if node_accelerator_type == requested_gpu_type and int(
784
791
  node_accelerator_count) >= requested_gpu_count:
@@ -812,24 +819,22 @@ class GKEAutoscaler(Autoscaler):
812
819
  @classmethod
813
820
  def _tpu_chip_count_from_instance_type(cls, machine_type: str) -> int:
814
821
  """Infer the number of TPU chips from the instance type."""
815
- machine_type_parts = machine_type.split('-')
816
822
  # according to
817
823
  # https://cloud.google.com/kubernetes-engine/docs/concepts/tpus#machine_type
818
824
  # GKE TPU machine types have the format of
819
825
  # ct<version>-<type>-<node-chip-count>t
820
826
  logger.debug(
821
827
  f'inferring TPU chip count from machine type: {machine_type}')
822
- if (len(machine_type_parts) != 3 or
823
- not machine_type_parts[0].startswith('ct') or
824
- not machine_type_parts[2].endswith('t') or
825
- not machine_type_parts[2].strip('t').isdigit()):
828
+ pattern = r'ct[a-z0-9]+-[a-z]+-([0-9]+)t'
829
+ search = re.search(pattern, machine_type)
830
+ if search is None:
826
831
  logger.debug(f'machine type {machine_type} is not a '
827
832
  'valid TPU machine type format.')
828
833
  return 0
829
- num_tpu_chips = int(machine_type_parts[2].strip('t'))
834
+ num_tpu_chips = search.group(1)
830
835
  logger.debug(
831
836
  f'machine type {machine_type} has {num_tpu_chips} TPU chips.')
832
- return num_tpu_chips
837
+ return int(num_tpu_chips)
833
838
 
834
839
  @classmethod
835
840
  def _is_node_multi_host_tpu(cls, resource_labels: dict) -> bool:
@@ -1205,7 +1205,16 @@ class SkyPilotReplicaManager(ReplicaManager):
1205
1205
  for key in ['service']:
1206
1206
  old_config.pop(key)
1207
1207
  # Bump replica version if all fields except for service are
1208
- # the same. File mounts should both be empty, as update always
1208
+ # the same.
1209
+ # Here, we manually convert the any_of field to a set to avoid
1210
+ # only the difference in the random order of the any_of fields.
1211
+ old_config_any_of = old_config.get('resources',
1212
+ {}).pop('any_of', [])
1213
+ new_config_any_of = new_config.get('resources',
1214
+ {}).pop('any_of', [])
1215
+ if set(old_config_any_of) != set(new_config_any_of):
1216
+ continue
1217
+ # File mounts should both be empty, as update always
1209
1218
  # create new buckets if they are not empty.
1210
1219
  if (old_config == new_config and
1211
1220
  old_config.get('file_mounts', None) == {}):
@@ -215,7 +215,13 @@ def _get_cloud_dependencies_installation_commands(
215
215
  commands.append(f'echo -en "\\r{step_prefix}uv{empty_str}" &&'
216
216
  f'{constants.SKY_UV_INSTALL_CMD} >/dev/null 2>&1')
217
217
 
218
- for cloud in sky_check.get_cached_enabled_clouds_or_refresh():
218
+ enabled_compute_clouds = set(
219
+ sky_check.get_cached_enabled_clouds_or_refresh())
220
+ enabled_storage_clouds = set(
221
+ sky_check.get_cached_enabled_storage_clouds_or_refresh())
222
+ enabled_clouds = enabled_compute_clouds.union(enabled_storage_clouds)
223
+
224
+ for cloud in enabled_clouds:
219
225
  cloud_python_dependencies: List[str] = copy.deepcopy(
220
226
  dependencies.extras_require[cloud.canonical_name()])
221
227
 
@@ -167,7 +167,9 @@ def deploy_local_cluster(gpus: bool):
167
167
  f'\nError: {stderr}')
168
168
  # Run sky check
169
169
  with rich_utils.safe_status('[bold cyan]Running sky check...'):
170
- sky_check.check(clouds=['kubernetes'], quiet=True)
170
+ sky_check.check(clouds=['kubernetes'],
171
+ quiet=True,
172
+ capability=sky_check.CloudCapability.COMPUTE)
171
173
  if cluster_created:
172
174
  # Prepare completion message which shows CPU and GPU count
173
175
  # Get number of CPUs
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: skypilot-nightly
3
- Version: 1.0.0.dev20250319
3
+ Version: 1.0.0.dev20250320
4
4
  Summary: SkyPilot: An intercloud broker for the clouds
5
5
  Author: SkyPilot Team
6
6
  License: Apache 2.0
@@ -156,6 +156,7 @@ Dynamic: classifier
156
156
  Dynamic: description
157
157
  Dynamic: description-content-type
158
158
  Dynamic: license
159
+ Dynamic: license-file
159
160
  Dynamic: project-url
160
161
  Dynamic: provides-extra
161
162
  Dynamic: requires-dist
@@ -1,16 +1,16 @@
1
- sky/__init__.py,sha256=3eIvmaqr9j7Q14zbXB6K1AYrtAYYBeSZaufG8cPHilk,6428
1
+ sky/__init__.py,sha256=QfQeD1Wm9yNNJl0Tuk92yJ1R5u-p0hTxTZT9lSqpvtY,6428
2
2
  sky/admin_policy.py,sha256=hPo02f_A32gCqhUueF0QYy1fMSSKqRwYEg_9FxScN_s,3248
3
3
  sky/authentication.py,sha256=hCEqi77nprQEg3ktfRL51xiiw16zwZOmFEDB_Z7fWVU,22384
4
- sky/check.py,sha256=NDKx_Zm7YRxPjMv82wz3ESLnGIPljaACyqVdVNM0PzY,11258
4
+ sky/check.py,sha256=UgHpR6D0SAnbKeOeBXyX4Px6q01uEwvKD9MriKmOQOU,15944
5
5
  sky/cli.py,sha256=o1IHTY4YSJzsfkynTynaN1dB4RBPZIRZGWQ4rTnVVnQ,221956
6
6
  sky/cloud_stores.py,sha256=kEHXd2divyra-1c3EusHxKyM5yTQlTXc6cKVXofsefA,23978
7
- sky/core.py,sha256=MU9hcTdh8baMGrr2ZXmbxx12vNlhajrkeyg5QtV717c,47609
7
+ sky/core.py,sha256=WM0SVu0MygIhXSdu2rB7urryEBbJ8BqskBsRZqUJ0Bw,47711
8
8
  sky/dag.py,sha256=Yl7Ry26Vql5cv4YMz8g9kOUgtoCihJnw7c8NgZYakMY,3242
9
9
  sky/exceptions.py,sha256=cEZ5nm7RhTW22Npw-oYS5Wp9rtxoHxdPQHfkNa92wOo,16641
10
10
  sky/execution.py,sha256=9L8NFOXNphtabnsL7mHGPJeGdw4n6gIIUEOzjW7CEHw,28294
11
- sky/global_user_state.py,sha256=sUDdSsJeiJkbgmZNwy8YGFK0XeNh-RBr1VDUvbmjf0g,33246
11
+ sky/global_user_state.py,sha256=LgC5HJrNwJPJ3QWSiChN0eSQihlu9OaqcnQ8UalTDPg,34393
12
12
  sky/models.py,sha256=4xSW05BdDPEjW8Ubvj3VlVOVnzv0TbrolsFvR5R5v1U,638
13
- sky/optimizer.py,sha256=7FeTo0Bk4M7OnXugv-YdCj50PTL2R7NVGHMsr7DWBJ0,60457
13
+ sky/optimizer.py,sha256=qrY7JCPxcPC4JTcgYDKv4jMOUtAdCHJ4jMwHvn1QnXM,60527
14
14
  sky/resources.py,sha256=f2Qo_Wt0kFruKmYm6cgYbICH_wn0Zkb8uIv6LA82SRs,72153
15
15
  sky/sky_logging.py,sha256=pID2RINjH62n7SZpv70DuN8BSFYdCfTJ2ScGQpVmugg,5725
16
16
  sky/skypilot_config.py,sha256=bt1vSis2aKKdQfPz80-KcjM9vNIg_qYKLNXur782Poo,8693
@@ -18,7 +18,7 @@ sky/task.py,sha256=elzRNKy0twCOgz4VaCpd4k-EQZ3ZKy4N6nNg0yldnYo,54969
18
18
  sky/adaptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  sky/adaptors/aws.py,sha256=iH55Cm6eXWwufAG0Dgk7LTQcADawNa3ELrBH1m6yuSY,7617
20
20
  sky/adaptors/azure.py,sha256=r8xkjRgZZQfsSExNdguFa6c5fCLcUhZrFV8zs62VExo,21986
21
- sky/adaptors/cloudflare.py,sha256=kosJFf4nA6lDiV5iiYS3v7G_Ooi8MgRK1va1k2V1toE,7652
21
+ sky/adaptors/cloudflare.py,sha256=BeT2kq8oZapRUK1bEdO38Gx4amwQJgfjyFUqnhjXyTI,7756
22
22
  sky/adaptors/common.py,sha256=nJmuBYFokCH0vX2oFqdAJYS-84FnUSTmIPKjAi4gqzo,2877
23
23
  sky/adaptors/cudo.py,sha256=WGvIQrlzJkGDe02Ve7pygA56tHwUc4kwS3XHW8kMFAA,239
24
24
  sky/adaptors/do.py,sha256=dJ0BYbkQoUWVu6_9Pxq3fOu6PngjZyyCQzgjnODXLCA,777
@@ -47,18 +47,18 @@ sky/client/cli.py,sha256=o1IHTY4YSJzsfkynTynaN1dB4RBPZIRZGWQ4rTnVVnQ,221956
47
47
  sky/client/common.py,sha256=axDic7WOG1e78SdFm5XIwdhX7YNvf3g4k7INrsW3X4s,14611
48
48
  sky/client/sdk.py,sha256=DL-3aV-kbK3T70_qXrEiMlaUmbiHaJk513gLp2by-6Y,68352
49
49
  sky/clouds/__init__.py,sha256=OW6mJ-9hpJSBORCgt2LippLQEYZHNfnBW1mooRNNvxo,1416
50
- sky/clouds/aws.py,sha256=J8tczaTDL239UowN9tUlhI92SeHw01wtFucSckvG63w,54112
51
- sky/clouds/azure.py,sha256=bawEw6wOLAVyrjxMD-4UjLCuMj1H5_jH8qggpfZYS54,31703
52
- sky/clouds/cloud.py,sha256=Ej6WH6VElYdG3PG1-Sp6lFVsJ42uskV4dAg7kmoY4JA,35376
50
+ sky/clouds/aws.py,sha256=T-j6Fd4QUt5gYIsXRDt7vPLKKPpHSciDnib_9ra7-eg,54301
51
+ sky/clouds/azure.py,sha256=SC2x_I0ozh8q9YdyD6--veGIfixettC0I8Iqc9CMjDA,31908
52
+ sky/clouds/cloud.py,sha256=z9cDJcIWgIAQWIkszAirAGDLWkAmrK41SjpERc6ckyA,35894
53
53
  sky/clouds/cudo.py,sha256=femv17IUM1TOXuCAg6zljqyFcBGfofbXCNGckpXFHzc,13127
54
54
  sky/clouds/do.py,sha256=hmksx0XML0dVHUZBMV2Wr3a5VilOsYfxX2dSBV_XK5o,11487
55
55
  sky/clouds/fluidstack.py,sha256=Eb0nlfU_EwTtGtV0nPKS2ueBlB0nYiDAN9swA-jjQV0,12446
56
- sky/clouds/gcp.py,sha256=cvFSeX8RcyhX5HJb57YposUr9p1RaUPmpxvg_AI_D3c,55978
57
- sky/clouds/ibm.py,sha256=R4JR96YfXstZ2B_IgFNVEX2SBAq3q0lSWz4y7FoFoeE,21474
56
+ sky/clouds/gcp.py,sha256=QNbCES7CnFvKoHc-iBkpjFNVRjIWTRID1zkywhA9hNU,56938
57
+ sky/clouds/ibm.py,sha256=YRKzGja9Uad5N0f4lzlOPIF2BoPsB-TV8_Mu_fjVPws,21668
58
58
  sky/clouds/kubernetes.py,sha256=u8mRd75a0NS7-uHdGXk_cqqLc4Z2vU0CedwmLJpzmZ0,36081
59
59
  sky/clouds/lambda_cloud.py,sha256=ejqA_Wj5-325Y_QjQ__FY4HMO8sv_2tSRsufmaldcmI,12699
60
60
  sky/clouds/nebius.py,sha256=G3v73NZjLzGoCi0ZfHj6VkOt-fs1i6DDxCpNiE88BdA,12676
61
- sky/clouds/oci.py,sha256=irINbQsQ6YxRxGTMaCNsms3mZkIun2oJMMA1fMCRJyA,27072
61
+ sky/clouds/oci.py,sha256=oGsM8c6f-eXYBXzpd23SgEpJNBB6THylvjg9PdLdxsY,27277
62
62
  sky/clouds/paperspace.py,sha256=O7bH8YaHBLFuyj6rDz2bPDz_6OYWmNB9OLqnZH70yfY,10922
63
63
  sky/clouds/runpod.py,sha256=hzYB4td6qaged83xMAVKZ96bH40oZnrHXL7a_CKxXIw,11926
64
64
  sky/clouds/scp.py,sha256=bOl5icqVHXJFsr9sIKH2VHwBhupo66kn7SVCL0xajdc,15872
@@ -97,14 +97,14 @@ sky/clouds/service_catalog/data_fetchers/fetch_vsphere.py,sha256=Opp2r3KSzXPtwk3
97
97
  sky/clouds/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  sky/clouds/utils/aws_utils.py,sha256=W5BRC-2F_VY4BymRA1kS6-MufsI3V8cfY_hv--4gJBU,1986
99
99
  sky/clouds/utils/azure_utils.py,sha256=NToRBnhEyuUvb-nBnsKTxjhOBRkMcrelL8LK4w6s4t8,3555
100
- sky/clouds/utils/gcp_utils.py,sha256=h_ezG7uq2FogLJCPSu8GIEYLOEHmUtC_Xzt4fLOw19s,6853
100
+ sky/clouds/utils/gcp_utils.py,sha256=YtuS4EoAMvcRnGPgE_WLENPOPWIdvhp7dLceTw_zfas,7114
101
101
  sky/clouds/utils/oci_utils.py,sha256=KbmwTWlEO3stcjM4jN2agSFi2w9WNc2NYA9Gr5jMAvE,6385
102
102
  sky/clouds/utils/scp_utils.py,sha256=r4lhRLtNgoz5nmkfN2ctAXYugF_-Et8TYH6ZlbbFfo8,15791
103
103
  sky/data/__init__.py,sha256=Nhaf1NURisXpZuwWANa2IuCyppIuc720FRwqSE2oEwY,184
104
104
  sky/data/data_transfer.py,sha256=wixC4_3_JaeJFdGKOp-O5ulcsMugDSgrCR0SnPpugGc,8946
105
105
  sky/data/data_utils.py,sha256=HjcgMDuWRR_fNQ9gjuROi9GgPVvTGApiJwxGtdb2_UU,28860
106
106
  sky/data/mounting_utils.py,sha256=i79Y-DZXVR88fjG_MBPB8EgsZBnHdpf1LGnJSm_VhAg,16063
107
- sky/data/storage.py,sha256=vYn7V2vyh9mGoQpyfd7FyoPZQ6HPpl3yH2x62GgfQgw,207707
107
+ sky/data/storage.py,sha256=bENPzQGJCb_4wE6OpLryB8Ny-FVxA2vPcq9ZAFDmaro,207657
108
108
  sky/data/storage_utils.py,sha256=zB99nRTJjh8isU0UmqERmlwwRNgfig91IwrwVH8CcNw,12383
109
109
  sky/jobs/__init__.py,sha256=qoI53-xXE0-SOkrLWigvhgFXjk7dWE0OTqGPYIk-kmM,1458
110
110
  sky/jobs/constants.py,sha256=1XiIqdR5dEgGgepLKWkZCRT3MYSsMBR-dO7N4RTsjwg,3088
@@ -154,9 +154,9 @@ sky/provision/fluidstack/config.py,sha256=hDqesKEVjIhXLTWej3fDdpbHtKBXoybxFGgC6T
154
154
  sky/provision/fluidstack/fluidstack_utils.py,sha256=dYEHRRY86nCxQtZ_GIV6WhkNvvnkIuqDDKJqf1CouKY,5729
155
155
  sky/provision/fluidstack/instance.py,sha256=TCGLojd5mEuEaUQ1BnmRvXMOSSBjltyf7dhPG3OLdgQ,13787
156
156
  sky/provision/gcp/__init__.py,sha256=zlgjR2JoaGD7sStGStMRu9bJ62f-8NKEIyb-bFHBlzM,528
157
- sky/provision/gcp/config.py,sha256=rNpnRFNZqqvEHjzjSdwMoI7Fq7RW9w_dL2vIaubj3Dc,33319
158
- sky/provision/gcp/constants.py,sha256=9eLZatVSW2uGqxrvFGaWK2A_Ab0o_4S4GdgUuI7mfsk,7441
159
- sky/provision/gcp/instance.py,sha256=224_ZEPxa9E1qzOc-8aVfgeGu5xZEFOYj9Fe8sgqXWk,24940
157
+ sky/provision/gcp/config.py,sha256=kU357o4tCTuQ2e0Gind5q_tC0kFRW2tch-NQ2DnwN9Q,33319
158
+ sky/provision/gcp/constants.py,sha256=KbnAupsKCp_mNNOas62-yiCJcqYoaer7iMljFq-CPfk,7739
159
+ sky/provision/gcp/instance.py,sha256=47jDHLbIAI5M1MZIQTCiKGfwc9QzPOyjApkShqBRczE,25035
160
160
  sky/provision/gcp/instance_utils.py,sha256=T0AVT8lMn128snPp3MvqmhXOihlZSC8-c1QpgYT4_FA,71377
161
161
  sky/provision/gcp/mig_utils.py,sha256=oFpcFZoapHMILSE4iIm8V5bxP1RhbMHRF7cciqq8qAk,7883
162
162
  sky/provision/kubernetes/__init__.py,sha256=y6yVfii81WYG3ROxv4hiIj-ydinS5-xGxLvXnARVQoI,719
@@ -165,7 +165,7 @@ sky/provision/kubernetes/constants.py,sha256=dZCUV8FOO9Gct80sdqeubKnxeW3CGl-u5mx
165
165
  sky/provision/kubernetes/instance.py,sha256=oag17OtuiqU-1RjkgW9NvEpxSGUFIYdI7M61S-YmPu8,50503
166
166
  sky/provision/kubernetes/network.py,sha256=AtcOM8wPs_-UlQJhGEQGP6Lh4HIgdx63Y0iWEhP5jyc,12673
167
167
  sky/provision/kubernetes/network_utils.py,sha256=Bwy5ZQb62ejC7ZHM4htjzhs86UNACK7AXN-NfQ9IJrE,11454
168
- sky/provision/kubernetes/utils.py,sha256=puwjlWM4EMExa1jO0cxluzg8ZSF-QX4rgZGksZdxKiQ,124015
168
+ sky/provision/kubernetes/utils.py,sha256=Ugy_FaD6rBw5SsbYShc0nlmXZZrWgNaQwU45rtkILe0,124207
169
169
  sky/provision/kubernetes/manifests/smarter-device-manager-configmap.yaml,sha256=AMzYzlY0JIlfBWj5eX054Rc1XDW2thUcLSOGMJVhIdA,229
170
170
  sky/provision/kubernetes/manifests/smarter-device-manager-daemonset.yaml,sha256=RtTq4F1QUmR2Uunb6zuuRaPhV7hpesz4saHjn3Ncsb4,2010
171
171
  sky/provision/lambda_cloud/__init__.py,sha256=6EEvSgtUeEiup9ivIFevHmgv0GqleroO2X0K7TRa2nE,612
@@ -217,7 +217,7 @@ sky/serve/constants.py,sha256=H_cVwI49HkS4JRg2k6TWje1vJtfAAnOivRtVrh8qjwQ,4806
217
217
  sky/serve/controller.py,sha256=jtzWHsLHnVPQ727ZpDZTUpGTtIOssbnQpXeWOyAuW_s,11886
218
218
  sky/serve/load_balancer.py,sha256=2nkMPRvy-h7hJL4Qq__tkT8nIAVC_nmjyXf8mMGYEFk,13658
219
219
  sky/serve/load_balancing_policies.py,sha256=XVj76qBgqh7h6wfx53RKQFzBefDWTE4TCdCEtFLLtI4,5398
220
- sky/serve/replica_managers.py,sha256=oyo9EUOZvv9dmkKXPKpOhJhezrBMj1vo4CJ9nS2jH1M,57611
220
+ sky/serve/replica_managers.py,sha256=nf5T6npBbgeWbTVLFXw1IF1eEYbWn__vJdlr_CVWSJY,58160
221
221
  sky/serve/serve_state.py,sha256=Do-MITtijw1z8dDUyJZMAGnjZCgdUFtJ2nwhhcjpOGI,20056
222
222
  sky/serve/serve_utils.py,sha256=o8ZgAA72-1_PMfTftmF3SChi9NmbLap5A6AOnMGkvNU,41747
223
223
  sky/serve/service.py,sha256=9sx9oDgHoAZYAo6yTL01sUaa6d6cqpJaHc2cd-SCSGY,12519
@@ -316,7 +316,7 @@ sky/utils/common.py,sha256=P4oVXFATUYgkruHX92cN12SJBtfb8DiOOYZtbN1kvP0,1927
316
316
  sky/utils/common_utils.py,sha256=vsikxAnRZYTbn0OWENuMSv1JWYUr2hRnPxqWSct6N7I,31357
317
317
  sky/utils/config_utils.py,sha256=VQ2E3DQ2XysD-kul-diSrxn_pXWsDMfKAev91OiJQ1Q,9041
318
318
  sky/utils/control_master_utils.py,sha256=iD4M0onjYOdZ2RuxjwMBl4KhafHXJzuHjvqlBUnu-VE,1450
319
- sky/utils/controller_utils.py,sha256=KuOX7pnIuC1n_wmrDr27G-HtnJtYRTXXLB1DaO0Dlb4,49439
319
+ sky/utils/controller_utils.py,sha256=UTiKU7mzX1_MiYZk9JiUPDa-kLcRLgROQywzIdsYdcg,49672
320
320
  sky/utils/dag_utils.py,sha256=sAus0aL1wtuuFZSDnpO4LY-6WK4u5iJY952oWQzHo3Y,7532
321
321
  sky/utils/db_utils.py,sha256=K2-OHPg0FeHCarevMdWe0IWzm6wWumViEeYeJuGoFUE,3747
322
322
  sky/utils/env_options.py,sha256=aaD6GoYK0LaZIqjOEZ-R7eccQuiRriW3EuLWtOI5En8,1578
@@ -344,12 +344,12 @@ sky/utils/kubernetes/generate_kubeconfig.sh,sha256=MBvXJio0PeujZSCXiRKE_pa6HCTiU
344
344
  sky/utils/kubernetes/gpu_labeler.py,sha256=4px7FyfsukacPEvKwTLUNb3WwacMIUrHWjP93qTi3kE,6998
345
345
  sky/utils/kubernetes/k8s_gpu_labeler_job.yaml,sha256=k0TBoQ4zgf79-sVkixKSGYFHQ7ZWF5gdVIZPupCCo9A,1224
346
346
  sky/utils/kubernetes/k8s_gpu_labeler_setup.yaml,sha256=VLKT2KKimZu1GDg_4AIlIt488oMQvhRZWwsj9vBbPUg,3812
347
- sky/utils/kubernetes/kubernetes_deploy_utils.py,sha256=otzHzpliHDCpzYT-nU9Q0ZExbiFpDPWvhxwkvchZj7k,10073
347
+ sky/utils/kubernetes/kubernetes_deploy_utils.py,sha256=jVRySB1Dme9yI_hFqkgKhYco19fDpyDpHusa3gnl8UY,10167
348
348
  sky/utils/kubernetes/rsync_helper.sh,sha256=h4YwrPFf9727CACnMJvF3EyK_0OeOYKKt4su_daKekw,1256
349
349
  sky/utils/kubernetes/ssh_jump_lifecycle_manager.py,sha256=Kq1MDygF2IxFmu9FXpCxqucXLmeUrvs6OtRij6XTQbo,6554
350
- skypilot_nightly-1.0.0.dev20250319.dist-info/LICENSE,sha256=emRJAvE7ngL6x0RhQvlns5wJzGI3NEQ_WMjNmd9TZc4,12170
351
- skypilot_nightly-1.0.0.dev20250319.dist-info/METADATA,sha256=Iys5Rb5saDPHcYoCslzL2WR1YyxDv2fSA-knwQQb6jc,17919
352
- skypilot_nightly-1.0.0.dev20250319.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
353
- skypilot_nightly-1.0.0.dev20250319.dist-info/entry_points.txt,sha256=StA6HYpuHj-Y61L2Ze-hK2IcLWgLZcML5gJu8cs6nU4,36
354
- skypilot_nightly-1.0.0.dev20250319.dist-info/top_level.txt,sha256=qA8QuiNNb6Y1OF-pCUtPEr6sLEwy2xJX06Bd_CrtrHY,4
355
- skypilot_nightly-1.0.0.dev20250319.dist-info/RECORD,,
350
+ skypilot_nightly-1.0.0.dev20250320.dist-info/licenses/LICENSE,sha256=emRJAvE7ngL6x0RhQvlns5wJzGI3NEQ_WMjNmd9TZc4,12170
351
+ skypilot_nightly-1.0.0.dev20250320.dist-info/METADATA,sha256=8VHM4tb2L9CQ4Hd1deeEyY2O0qzxQOFgkJ4Qv_MUGBk,17941
352
+ skypilot_nightly-1.0.0.dev20250320.dist-info/WHEEL,sha256=tTnHoFhvKQHCh4jz3yCn0WPTYIy7wXx3CJtJ7SJGV7c,91
353
+ skypilot_nightly-1.0.0.dev20250320.dist-info/entry_points.txt,sha256=StA6HYpuHj-Y61L2Ze-hK2IcLWgLZcML5gJu8cs6nU4,36
354
+ skypilot_nightly-1.0.0.dev20250320.dist-info/top_level.txt,sha256=qA8QuiNNb6Y1OF-pCUtPEr6sLEwy2xJX06Bd_CrtrHY,4
355
+ skypilot_nightly-1.0.0.dev20250320.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (77.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5