skypilot-nightly 1.0.0.dev20250626__py3-none-any.whl → 1.0.0.dev20250628__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 (106) hide show
  1. sky/__init__.py +2 -2
  2. sky/adaptors/kubernetes.py +7 -0
  3. sky/adaptors/nebius.py +2 -2
  4. sky/admin_policy.py +27 -17
  5. sky/authentication.py +12 -5
  6. sky/backends/backend_utils.py +92 -26
  7. sky/check.py +5 -2
  8. sky/client/cli/command.py +38 -6
  9. sky/client/sdk.py +217 -167
  10. sky/client/service_account_auth.py +47 -0
  11. sky/clouds/aws.py +10 -4
  12. sky/clouds/azure.py +5 -2
  13. sky/clouds/cloud.py +5 -2
  14. sky/clouds/gcp.py +31 -18
  15. sky/clouds/kubernetes.py +54 -34
  16. sky/clouds/nebius.py +8 -2
  17. sky/clouds/ssh.py +5 -2
  18. sky/clouds/utils/aws_utils.py +10 -4
  19. sky/clouds/utils/gcp_utils.py +22 -7
  20. sky/clouds/utils/oci_utils.py +62 -14
  21. sky/dashboard/out/404.html +1 -1
  22. sky/dashboard/out/_next/static/{bs6UB9V4Jq10TIZ5x-kBK → ZYLkkWSYZjJhLVsObh20y}/_buildManifest.js +1 -1
  23. sky/dashboard/out/_next/static/chunks/43-f38a531f6692f281.js +1 -0
  24. sky/dashboard/out/_next/static/chunks/601-111d06d9ded11d00.js +1 -0
  25. sky/dashboard/out/_next/static/chunks/{616-d6128fa9e7cae6e6.js → 616-50a620ac4a23deb4.js} +1 -1
  26. sky/dashboard/out/_next/static/chunks/691.fd9292250ab089af.js +21 -0
  27. sky/dashboard/out/_next/static/chunks/{785.dc2686c3c1235554.js → 785.3446c12ffdf3d188.js} +1 -1
  28. sky/dashboard/out/_next/static/chunks/871-e547295e7e21399c.js +6 -0
  29. sky/dashboard/out/_next/static/chunks/937.72796f7afe54075b.js +1 -0
  30. sky/dashboard/out/_next/static/chunks/938-0a770415b5ce4649.js +1 -0
  31. sky/dashboard/out/_next/static/chunks/982.d7bd80ed18cad4cc.js +1 -0
  32. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-21080826c6095f21.js +6 -0
  33. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-77d4816945b04793.js +6 -0
  34. sky/dashboard/out/_next/static/chunks/pages/{clusters-f119a5630a1efd61.js → clusters-65b2c90320b8afb8.js} +1 -1
  35. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-64bdc0b2d3a44709.js +16 -0
  36. sky/dashboard/out/_next/static/chunks/pages/{jobs-0a5695ff3075d94a.js → jobs-df7407b5e37d3750.js} +1 -1
  37. sky/dashboard/out/_next/static/chunks/pages/{users-4978cbb093e141e7.js → users-d7684eaa04c4f58f.js} +1 -1
  38. sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-cb7e720b739de53a.js → [name]-04e1b3ad4207b1e9.js} +1 -1
  39. sky/dashboard/out/_next/static/chunks/pages/{workspaces-50e230828730cfb3.js → workspaces-c470366a6179f16e.js} +1 -1
  40. sky/dashboard/out/_next/static/chunks/{webpack-08fdb9e6070127fc.js → webpack-75a3310ef922a299.js} +1 -1
  41. sky/dashboard/out/_next/static/css/605ac87514049058.css +3 -0
  42. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  43. sky/dashboard/out/clusters/[cluster].html +1 -1
  44. sky/dashboard/out/clusters.html +1 -1
  45. sky/dashboard/out/config.html +1 -1
  46. sky/dashboard/out/index.html +1 -1
  47. sky/dashboard/out/infra/[context].html +1 -1
  48. sky/dashboard/out/infra.html +1 -1
  49. sky/dashboard/out/jobs/[job].html +1 -1
  50. sky/dashboard/out/jobs.html +1 -1
  51. sky/dashboard/out/users.html +1 -1
  52. sky/dashboard/out/volumes.html +1 -1
  53. sky/dashboard/out/workspace/new.html +1 -1
  54. sky/dashboard/out/workspaces/[name].html +1 -1
  55. sky/dashboard/out/workspaces.html +1 -1
  56. sky/data/storage.py +8 -3
  57. sky/global_user_state.py +257 -9
  58. sky/jobs/client/sdk.py +20 -25
  59. sky/models.py +16 -0
  60. sky/provision/kubernetes/config.py +1 -1
  61. sky/provision/kubernetes/instance.py +7 -4
  62. sky/provision/kubernetes/network.py +15 -9
  63. sky/provision/kubernetes/network_utils.py +42 -23
  64. sky/provision/kubernetes/utils.py +73 -35
  65. sky/provision/nebius/utils.py +10 -4
  66. sky/resources.py +10 -4
  67. sky/serve/client/sdk.py +28 -34
  68. sky/server/common.py +51 -3
  69. sky/server/constants.py +3 -0
  70. sky/server/requests/executor.py +4 -0
  71. sky/server/requests/payloads.py +33 -0
  72. sky/server/requests/requests.py +19 -0
  73. sky/server/rest.py +6 -15
  74. sky/server/server.py +121 -6
  75. sky/skylet/constants.py +6 -0
  76. sky/skypilot_config.py +32 -4
  77. sky/users/permission.py +29 -0
  78. sky/users/server.py +384 -5
  79. sky/users/token_service.py +196 -0
  80. sky/utils/common_utils.py +4 -5
  81. sky/utils/config_utils.py +41 -0
  82. sky/utils/controller_utils.py +5 -1
  83. sky/utils/resource_checker.py +153 -0
  84. sky/utils/resources_utils.py +12 -4
  85. sky/utils/schemas.py +87 -60
  86. sky/utils/subprocess_utils.py +2 -6
  87. sky/workspaces/core.py +9 -117
  88. {skypilot_nightly-1.0.0.dev20250626.dist-info → skypilot_nightly-1.0.0.dev20250628.dist-info}/METADATA +1 -1
  89. {skypilot_nightly-1.0.0.dev20250626.dist-info → skypilot_nightly-1.0.0.dev20250628.dist-info}/RECORD +95 -92
  90. sky/dashboard/out/_next/static/chunks/43-36177d00f6956ab2.js +0 -1
  91. sky/dashboard/out/_next/static/chunks/690.55f9eed3be903f56.js +0 -16
  92. sky/dashboard/out/_next/static/chunks/871-3db673be3ee3750b.js +0 -6
  93. sky/dashboard/out/_next/static/chunks/937.3759f538f11a0953.js +0 -1
  94. sky/dashboard/out/_next/static/chunks/938-068520cc11738deb.js +0 -1
  95. sky/dashboard/out/_next/static/chunks/973-81b2d057178adb76.js +0 -1
  96. sky/dashboard/out/_next/static/chunks/982.1b61658204416b0f.js +0 -1
  97. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-aff040d7bc5d0086.js +0 -6
  98. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-8040f2483897ed0c.js +0 -6
  99. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-e4b23128db0774cd.js +0 -16
  100. sky/dashboard/out/_next/static/css/52082cf558ec9705.css +0 -3
  101. /sky/dashboard/out/_next/static/{bs6UB9V4Jq10TIZ5x-kBK → ZYLkkWSYZjJhLVsObh20y}/_ssgManifest.js +0 -0
  102. /sky/dashboard/out/_next/static/chunks/pages/{_app-9a3ce3170d2edcec.js → _app-050a9e637b057b24.js} +0 -0
  103. {skypilot_nightly-1.0.0.dev20250626.dist-info → skypilot_nightly-1.0.0.dev20250628.dist-info}/WHEEL +0 -0
  104. {skypilot_nightly-1.0.0.dev20250626.dist-info → skypilot_nightly-1.0.0.dev20250628.dist-info}/entry_points.txt +0 -0
  105. {skypilot_nightly-1.0.0.dev20250626.dist-info → skypilot_nightly-1.0.0.dev20250628.dist-info}/licenses/LICENSE +0 -0
  106. {skypilot_nightly-1.0.0.dev20250626.dist-info → skypilot_nightly-1.0.0.dev20250628.dist-info}/top_level.txt +0 -0
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 = '512c53be5771c003cd9d94b5b89c23638a5e735e'
8
+ _SKYPILOT_COMMIT_SHA = 'ba333007314473d8662d29933dca71a5e3d1fbf1'
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.dev20250626'
38
+ __version__ = '1.0.0.dev20250628'
39
39
  __root_dir__ = os.path.dirname(os.path.abspath(__file__))
40
40
 
41
41
 
@@ -211,6 +211,13 @@ def api_client(context: Optional[str] = None):
211
211
  return kubernetes.client.ApiClient()
212
212
 
213
213
 
214
+ @_api_logging_decorator('urllib3', logging.ERROR)
215
+ @annotations.lru_cache(scope='request')
216
+ def custom_resources_api(context: Optional[str] = None):
217
+ _load_config(context)
218
+ return kubernetes.client.CustomObjectsApi()
219
+
220
+
214
221
  @_api_logging_decorator('urllib3', logging.ERROR)
215
222
  @annotations.lru_cache(scope='request')
216
223
  def watch(context: Optional[str] = None):
sky/adaptors/nebius.py CHANGED
@@ -120,8 +120,8 @@ def get_tenant_id():
120
120
  'tenant_id', None)
121
121
  if tenant_id_in_ws_config is not None:
122
122
  return tenant_id_in_ws_config
123
- tenant_id_in_config = skypilot_config.get_nested(('nebius', 'tenant_id'),
124
- None)
123
+ tenant_id_in_config = skypilot_config.get_effective_region_config(
124
+ cloud='nebius', region=None, keys=('tenant_id',), default_value=None)
125
125
  if tenant_id_in_config is not None:
126
126
  return tenant_id_in_config
127
127
  try:
sky/admin_policy.py CHANGED
@@ -2,7 +2,7 @@
2
2
  import abc
3
3
  import dataclasses
4
4
  import typing
5
- from typing import Any, Dict, Optional
5
+ from typing import Optional
6
6
 
7
7
  import colorama
8
8
  import pydantic
@@ -44,8 +44,10 @@ class RequestOptions(pydantic.BaseModel):
44
44
 
45
45
  class _UserRequestBody(pydantic.BaseModel):
46
46
  """Auxiliary model to validate and serialize a user request."""
47
- task: Dict[str, Any]
48
- skypilot_config: Dict[str, Any]
47
+ # We have to use serialized YAML string, instead of a dict, because dict
48
+ # will be converted to JSON string, which will lose the None key.
49
+ task: str
50
+ skypilot_config: str
49
51
  request_options: Optional[RequestOptions] = None
50
52
  at_client_side: bool = False
51
53
 
@@ -78,25 +80,31 @@ class UserRequest:
78
80
 
79
81
  def encode(self) -> str:
80
82
  return _UserRequestBody(
81
- task=self.task.to_yaml_config(),
82
- skypilot_config=dict(self.skypilot_config),
83
+ task=common_utils.dump_yaml_str(self.task.to_yaml_config()),
84
+ skypilot_config=common_utils.dump_yaml_str(
85
+ dict(self.skypilot_config)),
83
86
  request_options=self.request_options,
84
- at_client_side=self.at_client_side).model_dump_json()
87
+ at_client_side=self.at_client_side,
88
+ ).model_dump_json()
85
89
 
86
90
  @classmethod
87
91
  def decode(cls, body: str) -> 'UserRequest':
88
92
  user_request_body = _UserRequestBody.model_validate_json(body)
89
- return cls(task=sky.Task.from_yaml_config(user_request_body.task),
90
- skypilot_config=config_utils.Config.from_dict(
91
- user_request_body.skypilot_config),
92
- request_options=user_request_body.request_options,
93
- at_client_side=user_request_body.at_client_side)
93
+ return cls(
94
+ task=sky.Task.from_yaml_config(
95
+ common_utils.read_yaml_all_str(user_request_body.task)[0]),
96
+ skypilot_config=config_utils.Config.from_dict(
97
+ common_utils.read_yaml_all_str(
98
+ user_request_body.skypilot_config)[0]),
99
+ request_options=user_request_body.request_options,
100
+ at_client_side=user_request_body.at_client_side,
101
+ )
94
102
 
95
103
 
96
104
  class _MutatedUserRequestBody(pydantic.BaseModel):
97
105
  """Auxiliary model to validate and serialize a user request."""
98
- task: Dict[str, Any]
99
- skypilot_config: Dict[str, Any]
106
+ task: str
107
+ skypilot_config: str
100
108
 
101
109
 
102
110
  @dataclasses.dataclass
@@ -108,17 +116,19 @@ class MutatedUserRequest:
108
116
 
109
117
  def encode(self) -> str:
110
118
  return _MutatedUserRequestBody(
111
- task=self.task.to_yaml_config(),
112
- skypilot_config=dict(self.skypilot_config)).model_dump_json()
119
+ task=common_utils.dump_yaml_str(self.task.to_yaml_config()),
120
+ skypilot_config=common_utils.dump_yaml_str(
121
+ dict(self.skypilot_config),)).model_dump_json()
113
122
 
114
123
  @classmethod
115
124
  def decode(cls, mutated_user_request_body: str) -> 'MutatedUserRequest':
116
125
  mutated_user_request_body = _MutatedUserRequestBody.model_validate_json(
117
126
  mutated_user_request_body)
118
127
  return cls(task=sky.Task.from_yaml_config(
119
- mutated_user_request_body.task),
128
+ common_utils.read_yaml_all_str(mutated_user_request_body.task)[0]),
120
129
  skypilot_config=config_utils.Config.from_dict(
121
- mutated_user_request_body.skypilot_config))
130
+ common_utils.read_yaml_all_str(
131
+ mutated_user_request_body.skypilot_config)[0],))
122
132
 
123
133
 
124
134
  class PolicyInterface:
sky/authentication.py CHANGED
@@ -419,12 +419,17 @@ def setup_ibm_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
419
419
 
420
420
 
421
421
  def setup_kubernetes_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
422
+ context = kubernetes_utils.get_context_from_config(config['provider'])
423
+
422
424
  # Default ssh session is established with kubectl port-forwarding with
423
425
  # ClusterIP service.
424
426
  nodeport_mode = kubernetes_enums.KubernetesNetworkingMode.NODEPORT
425
427
  port_forward_mode = kubernetes_enums.KubernetesNetworkingMode.PORTFORWARD
426
- network_mode_str = skypilot_config.get_nested(('kubernetes', 'networking'),
427
- port_forward_mode.value)
428
+ network_mode_str = skypilot_config.get_effective_region_config(
429
+ cloud='kubernetes',
430
+ region=context,
431
+ keys=('networking',),
432
+ default_value=port_forward_mode.value)
428
433
  try:
429
434
  network_mode = kubernetes_enums.KubernetesNetworkingMode.from_str(
430
435
  network_mode_str)
@@ -439,7 +444,6 @@ def setup_kubernetes_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
439
444
  # Add the user's public key to the SkyPilot cluster.
440
445
  secret_name = clouds.Kubernetes.SKY_SSH_KEY_SECRET_NAME
441
446
  secret_field_name = clouds.Kubernetes().ssh_key_secret_field_name
442
- context = kubernetes_utils.get_context_from_config(config['provider'])
443
447
  namespace = kubernetes_utils.get_namespace_from_config(config['provider'])
444
448
  k8s = kubernetes.kubernetes
445
449
  with open(public_key_path, 'r', encoding='utf-8') as f:
@@ -454,8 +458,11 @@ def setup_kubernetes_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
454
458
  'parent': 'skypilot'
455
459
  }
456
460
  }
457
- custom_metadata = skypilot_config.get_nested(
458
- ('kubernetes', 'custom_metadata'), {})
461
+ custom_metadata = skypilot_config.get_effective_region_config(
462
+ cloud='kubernetes',
463
+ region=context,
464
+ keys=('custom_metadata',),
465
+ default_value={})
459
466
  config_utils.merge_k8s_configs(secret_metadata, custom_metadata)
460
467
 
461
468
  secret = k8s.client.V1Secret(
@@ -520,11 +520,51 @@ def get_expirable_clouds(
520
520
  expirable_clouds = []
521
521
  local_credentials_value = schemas.RemoteIdentityOptions.LOCAL_CREDENTIALS.value
522
522
  for cloud in enabled_clouds:
523
- remote_identities = skypilot_config.get_nested(
524
- (str(cloud).lower(), 'remote_identity'), None)
525
- if remote_identities is None:
526
- remote_identities = schemas.get_default_remote_identity(
527
- str(cloud).lower())
523
+ # Kubernetes config might have context-specific properties
524
+ if isinstance(cloud, clouds.Kubernetes):
525
+ # get all custom contexts
526
+ contexts = kubernetes_utils.get_custom_config_k8s_contexts()
527
+ # add remote_identity of each context if it exists
528
+ remote_identities = None
529
+ for context in contexts:
530
+ context_remote_identity = skypilot_config.get_effective_region_config(
531
+ cloud='kubernetes',
532
+ region=context,
533
+ keys=('remote_identity',),
534
+ default_value=None)
535
+ if context_remote_identity is not None:
536
+ if remote_identities is None:
537
+ remote_identities = []
538
+ if isinstance(context_remote_identity, str):
539
+ remote_identities.append(
540
+ {context: context_remote_identity})
541
+ elif isinstance(context_remote_identity, list):
542
+ remote_identities.extend(context_remote_identity)
543
+ # add global kubernetes remote identity if it exists, if not, add default
544
+ global_remote_identity = skypilot_config.get_effective_region_config(
545
+ cloud='kubernetes',
546
+ region=None,
547
+ keys=('remote_identity',),
548
+ default_value=None)
549
+ if global_remote_identity is not None:
550
+ if remote_identities is None:
551
+ remote_identities = []
552
+ if isinstance(global_remote_identity, str):
553
+ remote_identities.append({'*': global_remote_identity})
554
+ elif isinstance(global_remote_identity, list):
555
+ remote_identities.extend(global_remote_identity)
556
+ if remote_identities is None:
557
+ remote_identities = schemas.get_default_remote_identity(
558
+ str(cloud).lower())
559
+ else:
560
+ remote_identities = skypilot_config.get_effective_region_config(
561
+ cloud=str(cloud).lower(),
562
+ region=None,
563
+ keys=('remote_identity',),
564
+ default_value=None)
565
+ if remote_identities is None:
566
+ remote_identities = schemas.get_default_remote_identity(
567
+ str(cloud).lower())
528
568
 
529
569
  local_credential_expiring = cloud.can_credential_expire()
530
570
  if isinstance(remote_identities, str):
@@ -605,8 +645,11 @@ def write_cluster_config(
605
645
  config_dict = {}
606
646
 
607
647
  specific_reservations = set(
608
- skypilot_config.get_nested(
609
- (str(to_provision.cloud).lower(), 'specific_reservations'), set()))
648
+ skypilot_config.get_effective_region_config(
649
+ cloud=str(to_provision.cloud).lower(),
650
+ region=to_provision.region,
651
+ keys=('specific_reservations',),
652
+ default_value=set()))
610
653
 
611
654
  # Remote identity handling can have 4 cases:
612
655
  # 1. LOCAL_CREDENTIALS (default for most clouds): Upload local credentials
@@ -619,8 +662,11 @@ def write_cluster_config(
619
662
  # running required checks.
620
663
  assert cluster_name is not None
621
664
  excluded_clouds: Set[clouds.Cloud] = set()
622
- remote_identity_config = skypilot_config.get_nested(
623
- (str(cloud).lower(), 'remote_identity'), None)
665
+ remote_identity_config = skypilot_config.get_effective_region_config(
666
+ cloud=str(cloud).lower(),
667
+ region=region.name,
668
+ keys=('remote_identity',),
669
+ default_value=None)
624
670
  remote_identity = schemas.get_default_remote_identity(str(cloud).lower())
625
671
  if isinstance(remote_identity_config, str):
626
672
  remote_identity = remote_identity_config
@@ -649,15 +695,21 @@ def write_cluster_config(
649
695
  'is not supported by this cloud. Remove the config or set: '
650
696
  '`remote_identity: LOCAL_CREDENTIALS`.')
651
697
  if isinstance(cloud, clouds.Kubernetes):
652
- if skypilot_config.get_nested(
653
- ('kubernetes', 'allowed_contexts'), None) is None:
698
+ if skypilot_config.get_effective_region_config(
699
+ cloud='kubernetes',
700
+ region=None,
701
+ keys=('allowed_contexts',),
702
+ default_value=None) is None:
654
703
  excluded_clouds.add(cloud)
655
704
  else:
656
705
  excluded_clouds.add(cloud)
657
706
 
658
707
  for cloud_str, cloud_obj in registry.CLOUD_REGISTRY.items():
659
- remote_identity_config = skypilot_config.get_nested(
660
- (cloud_str.lower(), 'remote_identity'), None)
708
+ remote_identity_config = skypilot_config.get_effective_region_config(
709
+ cloud=cloud_str.lower(),
710
+ region=region.name,
711
+ keys=('remote_identity',),
712
+ default_value=None)
661
713
  if remote_identity_config:
662
714
  if (remote_identity_config ==
663
715
  schemas.RemoteIdentityOptions.NO_UPLOAD.value):
@@ -678,8 +730,11 @@ def write_cluster_config(
678
730
  yaml_path = _get_yaml_path_from_cluster_name(cluster_name)
679
731
 
680
732
  # Retrieve the ssh_proxy_command for the given cloud / region.
681
- ssh_proxy_command_config = skypilot_config.get_nested(
682
- (str(cloud).lower(), 'ssh_proxy_command'), None)
733
+ ssh_proxy_command_config = skypilot_config.get_effective_region_config(
734
+ cloud=str(cloud).lower(),
735
+ region=None,
736
+ keys=('ssh_proxy_command',),
737
+ default_value=None)
683
738
  if (isinstance(ssh_proxy_command_config, str) or
684
739
  ssh_proxy_command_config is None):
685
740
  ssh_proxy_command = ssh_proxy_command_config
@@ -705,7 +760,11 @@ def write_cluster_config(
705
760
  logger.debug(f'Using ssh_proxy_command: {ssh_proxy_command!r}')
706
761
 
707
762
  # User-supplied global instance tags from ~/.sky/config.yaml.
708
- labels = skypilot_config.get_nested((str(cloud).lower(), 'labels'), {})
763
+ labels = skypilot_config.get_effective_region_config(
764
+ cloud=str(cloud).lower(),
765
+ region=region.name,
766
+ keys=('labels',),
767
+ default_value={})
709
768
  # labels is a dict, which is guaranteed by the type check in
710
769
  # schemas.py
711
770
  assert isinstance(labels, dict), labels
@@ -762,12 +821,17 @@ def write_cluster_config(
762
821
  os.environ.get(constants.USER_ENV_VAR, '')),
763
822
 
764
823
  # Networking configs
765
- 'use_internal_ips': skypilot_config.get_nested(
766
- (str(cloud).lower(), 'use_internal_ips'), False),
824
+ 'use_internal_ips': skypilot_config.get_effective_region_config(
825
+ cloud=str(cloud).lower(),
826
+ region=region.name,
827
+ keys=('use_internal_ips',),
828
+ default_value=False),
767
829
  'ssh_proxy_command': ssh_proxy_command,
768
- 'vpc_name': skypilot_config.get_nested(
769
- (str(cloud).lower(), 'vpc_name'), None),
770
-
830
+ 'vpc_name': skypilot_config.get_effective_region_config(
831
+ cloud=str(cloud).lower(),
832
+ region=region.name,
833
+ keys=('vpc_name',),
834
+ default_value=None),
771
835
  # User-supplied labels.
772
836
  'labels': labels,
773
837
  # User-supplied remote_identity
@@ -847,8 +911,9 @@ def write_cluster_config(
847
911
  kubernetes_utils.combine_pod_config_fields(
848
912
  tmp_yaml_path,
849
913
  cluster_config_overrides=cluster_config_overrides,
850
- cloud=cloud)
851
- kubernetes_utils.combine_metadata_fields(tmp_yaml_path)
914
+ cloud=cloud,
915
+ context=region.name)
916
+ kubernetes_utils.combine_metadata_fields(tmp_yaml_path, region.name)
852
917
  yaml_obj = common_utils.read_yaml(tmp_yaml_path)
853
918
  pod_config: Dict[str, Any] = yaml_obj['available_node_types'][
854
919
  'ray_head_default']['node_config']
@@ -3095,7 +3160,8 @@ def get_endpoints(cluster: str,
3095
3160
  f'{_ENDPOINTS_RETRY_MESSAGE} ')
3096
3161
  if launched_resources.cloud.is_same_cloud(clouds.Kubernetes()):
3097
3162
  # Add Kubernetes specific debugging info
3098
- error_msg += (kubernetes_utils.get_endpoint_debug_message())
3163
+ error_msg += kubernetes_utils.get_endpoint_debug_message(
3164
+ launched_resources.region)
3099
3165
  logger.warning(error_msg)
3100
3166
  return {}
3101
3167
  return {port: port_details[port][0].url()}
@@ -3113,8 +3179,8 @@ def get_endpoints(cluster: str,
3113
3179
  f'{_ENDPOINTS_RETRY_MESSAGE} ')
3114
3180
  if launched_resources.cloud.is_same_cloud(clouds.Kubernetes()):
3115
3181
  # Add Kubernetes specific debugging info
3116
- error_msg += \
3117
- kubernetes_utils.get_endpoint_debug_message()
3182
+ error_msg += kubernetes_utils.get_endpoint_debug_message(
3183
+ launched_resources.region)
3118
3184
  logger.warning(error_msg)
3119
3185
  return {}
3120
3186
  return {
sky/check.py CHANGED
@@ -619,8 +619,11 @@ def _format_enabled_cloud(cloud_name: str,
619
619
  return _green_color(cloud_and_capabilities)
620
620
 
621
621
  # Check if allowed_contexts is explicitly set in config
622
- allowed_contexts = skypilot_config.get_nested(
623
- ('kubernetes', 'allowed_contexts'), None)
622
+ allowed_contexts = skypilot_config.get_effective_region_config(
623
+ cloud='kubernetes',
624
+ region=None,
625
+ keys=('allowed_contexts',),
626
+ default_value=None)
624
627
 
625
628
  # Format the context info with consistent styling
626
629
  if allowed_contexts is not None:
sky/client/cli/command.py CHANGED
@@ -3176,7 +3176,11 @@ def show_gpus(
3176
3176
  cloud_obj, clouds.Kubernetes) and not isinstance(cloud_obj, clouds.SSH)
3177
3177
  cloud_is_ssh = isinstance(cloud_obj, clouds.SSH)
3178
3178
  # TODO(romilb): We should move this to the backend.
3179
- kubernetes_autoscaling = kubernetes_utils.get_autoscaler_type() is not None
3179
+ kubernetes_autoscaling = skypilot_config.get_effective_region_config(
3180
+ cloud='kubernetes',
3181
+ region=region,
3182
+ keys=('autoscaler',),
3183
+ default_value=None) is not None
3180
3184
  kubernetes_is_enabled = clouds.Kubernetes.canonical_name() in enabled_clouds
3181
3185
  ssh_is_enabled = clouds.SSH.canonical_name() in enabled_clouds
3182
3186
  query_k8s_realtime_gpu = (kubernetes_is_enabled and
@@ -5358,14 +5362,42 @@ def api_status(request_ids: Optional[List[str]], all_status: bool,
5358
5362
  '-e',
5359
5363
  required=False,
5360
5364
  help='The SkyPilot API server endpoint.')
5361
- @click.option('--get-token',
5365
+ @click.option('--relogin',
5362
5366
  is_flag=True,
5363
5367
  default=False,
5364
- help='Force token-based login.')
5368
+ help='Force relogin with OAuth2 when enabled.')
5369
+ @click.option(
5370
+ '--service-account-token',
5371
+ '--token',
5372
+ '-t',
5373
+ required=False,
5374
+ help='Service account token for authentication (starts with ``sky_``).')
5365
5375
  @usage_lib.entrypoint
5366
- def api_login(endpoint: Optional[str], get_token: bool):
5367
- """Logs into a SkyPilot API server."""
5368
- sdk.api_login(endpoint, get_token)
5376
+ def api_login(endpoint: Optional[str], relogin: bool,
5377
+ service_account_token: Optional[str]):
5378
+ """Logs into a SkyPilot API server.
5379
+
5380
+ If your remote API server has enabled OAuth2 authentication, you can use
5381
+ one of the following methods to login:
5382
+
5383
+ 1. OAuth2 browser-based authentication (default)
5384
+
5385
+ 2. Service account token via ``--token`` flag
5386
+
5387
+ 3. Service account token in ``~/.sky/config.yaml``
5388
+
5389
+ Examples:
5390
+
5391
+ .. code-block:: bash
5392
+
5393
+ # OAuth2 browser login
5394
+ sky api login -e https://api.example.com
5395
+ \b
5396
+ # Service account token login
5397
+ sky api login -e https://api.example.com --token sky_abc123...
5398
+
5399
+ """
5400
+ sdk.api_login(endpoint, relogin, service_account_token)
5369
5401
 
5370
5402
 
5371
5403
  @api.command('info', cls=_DocumentedCodeCommand)