skypilot-nightly 1.0.0.dev20250603__py3-none-any.whl → 1.0.0.dev20250605__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 (142) hide show
  1. sky/__init__.py +3 -3
  2. sky/adaptors/kubernetes.py +8 -0
  3. sky/admin_policy.py +5 -0
  4. sky/backends/backend_utils.py +1 -0
  5. sky/backends/cloud_vm_ray_backend.py +8 -4
  6. sky/{clouds/service_catalog → catalog}/__init__.py +6 -17
  7. sky/{clouds/service_catalog → catalog}/aws_catalog.py +3 -3
  8. sky/{clouds/service_catalog → catalog}/azure_catalog.py +2 -2
  9. sky/{clouds/service_catalog → catalog}/common.py +2 -2
  10. sky/{clouds/service_catalog → catalog}/cudo_catalog.py +1 -1
  11. sky/{clouds/service_catalog → catalog}/data_fetchers/analyze.py +1 -1
  12. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_aws.py +1 -1
  13. sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vsphere.py +1 -1
  14. sky/{clouds/service_catalog → catalog}/do_catalog.py +1 -1
  15. sky/{clouds/service_catalog → catalog}/fluidstack_catalog.py +1 -1
  16. sky/{clouds/service_catalog → catalog}/gcp_catalog.py +2 -2
  17. sky/{clouds/service_catalog → catalog}/ibm_catalog.py +1 -1
  18. sky/{clouds/service_catalog → catalog}/kubernetes_catalog.py +2 -2
  19. sky/{clouds/service_catalog → catalog}/lambda_catalog.py +1 -1
  20. sky/{clouds/service_catalog → catalog}/nebius_catalog.py +1 -1
  21. sky/{clouds/service_catalog → catalog}/oci_catalog.py +1 -1
  22. sky/{clouds/service_catalog → catalog}/paperspace_catalog.py +1 -1
  23. sky/{clouds/service_catalog → catalog}/runpod_catalog.py +1 -1
  24. sky/{clouds/service_catalog → catalog}/scp_catalog.py +1 -1
  25. sky/{clouds/service_catalog → catalog}/ssh_catalog.py +3 -3
  26. sky/{clouds/service_catalog → catalog}/vast_catalog.py +1 -1
  27. sky/{clouds/service_catalog → catalog}/vsphere_catalog.py +1 -1
  28. sky/cli.py +16 -13
  29. sky/client/cli.py +16 -13
  30. sky/client/sdk.py +30 -12
  31. sky/clouds/aws.py +41 -40
  32. sky/clouds/azure.py +31 -34
  33. sky/clouds/cloud.py +8 -8
  34. sky/clouds/cudo.py +26 -26
  35. sky/clouds/do.py +24 -24
  36. sky/clouds/fluidstack.py +27 -29
  37. sky/clouds/gcp.py +42 -42
  38. sky/clouds/ibm.py +26 -26
  39. sky/clouds/kubernetes.py +24 -12
  40. sky/clouds/lambda_cloud.py +28 -30
  41. sky/clouds/nebius.py +26 -28
  42. sky/clouds/oci.py +32 -32
  43. sky/clouds/paperspace.py +24 -26
  44. sky/clouds/runpod.py +26 -28
  45. sky/clouds/scp.py +37 -36
  46. sky/clouds/utils/gcp_utils.py +3 -2
  47. sky/clouds/vast.py +27 -27
  48. sky/clouds/vsphere.py +12 -15
  49. sky/core.py +2 -2
  50. sky/dashboard/out/404.html +1 -1
  51. sky/dashboard/out/_next/static/chunks/614-635a84e87800f99e.js +66 -0
  52. sky/dashboard/out/_next/static/chunks/{856-f1b1f7f47edde2e8.js → 856-3a32da4b84176f6d.js} +1 -1
  53. sky/dashboard/out/_next/static/chunks/937.3759f538f11a0953.js +1 -0
  54. sky/dashboard/out/_next/static/chunks/pages/config-1a1eeb949dab8897.js +6 -0
  55. sky/dashboard/out/_next/static/chunks/pages/users-262aab38b9baaf3a.js +16 -0
  56. sky/dashboard/out/_next/static/chunks/pages/workspaces-384ea5fa0cea8f28.js +1 -0
  57. sky/dashboard/out/_next/static/chunks/{webpack-f27c9a32aa3d9c6d.js → webpack-65d465f948974c0d.js} +1 -1
  58. sky/dashboard/out/_next/static/css/667d941a2888ce6e.css +3 -0
  59. sky/dashboard/out/_next/static/qjhIe-yC6nHcLKBqpzO1M/_buildManifest.js +1 -0
  60. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  61. sky/dashboard/out/clusters/[cluster].html +1 -1
  62. sky/dashboard/out/clusters.html +1 -1
  63. sky/dashboard/out/config.html +1 -1
  64. sky/dashboard/out/index.html +1 -1
  65. sky/dashboard/out/infra/[context].html +1 -1
  66. sky/dashboard/out/infra.html +1 -1
  67. sky/dashboard/out/jobs/[job].html +1 -1
  68. sky/dashboard/out/jobs.html +1 -1
  69. sky/dashboard/out/users.html +1 -1
  70. sky/dashboard/out/workspace/new.html +1 -1
  71. sky/dashboard/out/workspaces/[name].html +1 -1
  72. sky/dashboard/out/workspaces.html +1 -1
  73. sky/data/storage_utils.py +5 -2
  74. sky/execution.py +44 -46
  75. sky/global_user_state.py +119 -86
  76. sky/jobs/client/sdk.py +4 -1
  77. sky/jobs/server/core.py +6 -2
  78. sky/models.py +1 -0
  79. sky/optimizer.py +1 -1
  80. sky/provision/cudo/cudo_machine_type.py +1 -1
  81. sky/provision/kubernetes/utils.py +35 -22
  82. sky/provision/vast/utils.py +1 -1
  83. sky/provision/vsphere/common/vim_utils.py +1 -2
  84. sky/provision/vsphere/instance.py +1 -1
  85. sky/provision/vsphere/vsphere_utils.py +7 -11
  86. sky/resources.py +24 -3
  87. sky/serve/server/core.py +1 -1
  88. sky/server/constants.py +3 -1
  89. sky/server/requests/executor.py +4 -1
  90. sky/server/requests/payloads.py +25 -0
  91. sky/server/requests/serializers/decoders.py +1 -1
  92. sky/server/server.py +33 -12
  93. sky/server/stream_utils.py +2 -38
  94. sky/setup_files/MANIFEST.in +1 -0
  95. sky/setup_files/dependencies.py +2 -0
  96. sky/skylet/constants.py +10 -4
  97. sky/skypilot_config.py +92 -39
  98. sky/templates/websocket_proxy.py +11 -1
  99. sky/usage/usage_lib.py +4 -3
  100. sky/users/__init__.py +0 -0
  101. sky/users/model.conf +15 -0
  102. sky/users/permission.py +178 -0
  103. sky/users/rbac.py +86 -0
  104. sky/users/server.py +66 -0
  105. sky/utils/accelerator_registry.py +3 -3
  106. sky/utils/kubernetes/deploy_remote_cluster.py +2 -1
  107. sky/utils/schemas.py +20 -10
  108. sky/workspaces/core.py +2 -2
  109. {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/METADATA +3 -1
  110. {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/RECORD +134 -130
  111. sky/clouds/service_catalog/constants.py +0 -8
  112. sky/dashboard/out/_next/static/chunks/614-3d29f98e0634b179.js +0 -66
  113. sky/dashboard/out/_next/static/chunks/937.f97f83652028e944.js +0 -1
  114. sky/dashboard/out/_next/static/chunks/pages/config-35383adcb0edb5e2.js +0 -6
  115. sky/dashboard/out/_next/static/chunks/pages/users-07b523ccb19317ad.js +0 -6
  116. sky/dashboard/out/_next/static/chunks/pages/workspaces-f54921ec9eb20965.js +0 -1
  117. sky/dashboard/out/_next/static/css/63d3995d8b528eb1.css +0 -3
  118. sky/dashboard/out/_next/static/zTAFq_Iv6_yxQj3fXvJWR/_buildManifest.js +0 -1
  119. /sky/{clouds/service_catalog → catalog}/config.py +0 -0
  120. /sky/{clouds/service_catalog → catalog}/data_fetchers/__init__.py +0 -0
  121. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_azure.py +0 -0
  122. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_cudo.py +0 -0
  123. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_fluidstack.py +0 -0
  124. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_gcp.py +0 -0
  125. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_ibm.py +0 -0
  126. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_lambda_cloud.py +0 -0
  127. /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vast.py +0 -0
  128. /sky/dashboard/out/_next/static/chunks/{121-8f55ee3fa6301784.js → 121-865d2bf8a3b84c6a.js} +0 -0
  129. /sky/dashboard/out/_next/static/chunks/{236-fef38aa6e5639300.js → 236-4c0dc6f63ccc6319.js} +0 -0
  130. /sky/dashboard/out/_next/static/chunks/{37-947904ccc5687bac.js → 37-beedd583fea84cc8.js} +0 -0
  131. /sky/dashboard/out/_next/static/chunks/{682-2be9b0f169727f2f.js → 682-6647f0417d5662f0.js} +0 -0
  132. /sky/dashboard/out/_next/static/chunks/{843-a097338acb89b7d7.js → 843-c296541442d4af88.js} +0 -0
  133. /sky/dashboard/out/_next/static/chunks/{969-d7b6fb7f602bfcb3.js → 969-c7abda31c10440ac.js} +0 -0
  134. /sky/dashboard/out/_next/static/chunks/pages/{_app-67925f5e6382e22f.js → _app-cb81dc4d27f4d009.js} +0 -0
  135. /sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/{[job]-158b70da336d8607.js → [job]-65d04d5d77cbb6b6.js} +0 -0
  136. /sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-62c9982dc3675725.js → [cluster]-beabbcd7606c1a23.js} +0 -0
  137. /sky/dashboard/out/_next/static/chunks/pages/jobs/{[job]-a62a3c65dc9bc57c.js → [job]-86c47edc500f15f9.js} +0 -0
  138. /sky/dashboard/out/_next/static/{zTAFq_Iv6_yxQj3fXvJWR → qjhIe-yC6nHcLKBqpzO1M}/_ssgManifest.js +0 -0
  139. {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/WHEEL +0 -0
  140. {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/entry_points.txt +0 -0
  141. {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/licenses/LICENSE +0 -0
  142. {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/top_level.txt +0 -0
@@ -1384,7 +1384,7 @@ def check_credentials(context: Optional[str],
1384
1384
  # Check if $KUBECONFIG envvar consists of multiple paths. We run this before
1385
1385
  # optional checks.
1386
1386
  try:
1387
- _ = _get_kubeconfig_path()
1387
+ _ = get_kubeconfig_paths()
1388
1388
  except ValueError as e:
1389
1389
  return False, f'{common_utils.format_exception(e, use_bracket=True)}'
1390
1390
 
@@ -1537,15 +1537,11 @@ def is_kubeconfig_exec_auth(
1537
1537
  raise ValueError(f'Kubernetes context {context!r} not found.')
1538
1538
  target_username = context_obj['context']['user']
1539
1539
 
1540
- # K8s api does not provide a mechanism to get the user details from the
1541
- # context. We need to load the kubeconfig file and parse it to get the
1542
- # user details.
1543
- kubeconfig_path = _get_kubeconfig_path()
1544
-
1545
- # Load the kubeconfig file as a dictionary
1546
- with open(kubeconfig_path, 'r', encoding='utf-8') as f:
1547
- kubeconfig = yaml.safe_load(f)
1540
+ # Load the kubeconfig for the context
1541
+ kubeconfig_text = _get_kubeconfig_text_for_context(context)
1542
+ kubeconfig = yaml.safe_load(kubeconfig_text)
1548
1543
 
1544
+ # Get the user details
1549
1545
  user_details = kubeconfig['users']
1550
1546
 
1551
1547
  # Find user matching the target username
@@ -1573,6 +1569,27 @@ def is_kubeconfig_exec_auth(
1573
1569
  return False, None
1574
1570
 
1575
1571
 
1572
+ def _get_kubeconfig_text_for_context(context: Optional[str] = None) -> str:
1573
+ """Get the kubeconfig text for the given context.
1574
+
1575
+ The kubeconfig might be multiple files, this function use kubectl to
1576
+ handle merging automatically.
1577
+ """
1578
+ command = 'kubectl config view --minify'
1579
+ if context is not None:
1580
+ command += f' --context={context}'
1581
+ proc = subprocess.run(command,
1582
+ shell=True,
1583
+ check=False,
1584
+ stdout=subprocess.PIPE,
1585
+ stderr=subprocess.PIPE)
1586
+ if proc.returncode != 0:
1587
+ raise RuntimeError(
1588
+ f'Failed to get kubeconfig text for context {context}: {proc.stderr.decode("utf-8")}'
1589
+ )
1590
+ return proc.stdout.decode('utf-8')
1591
+
1592
+
1576
1593
  @annotations.lru_cache(scope='request')
1577
1594
  def get_current_kube_config_context_name() -> Optional[str]:
1578
1595
  """Get the current kubernetes context from the kubeconfig file
@@ -3100,18 +3117,14 @@ def get_gpu_resource_key():
3100
3117
  return os.getenv('CUSTOM_GPU_RESOURCE_KEY', default=GPU_RESOURCE_KEY)
3101
3118
 
3102
3119
 
3103
- def _get_kubeconfig_path() -> str:
3104
- """Get the path to the kubeconfig file.
3120
+ def get_kubeconfig_paths() -> List[str]:
3121
+ """Get the path to the kubeconfig files.
3105
3122
  Parses `KUBECONFIG` env var if present, else uses the default path.
3106
- Currently, specifying multiple KUBECONFIG paths in the envvar is not
3107
- allowed, hence will raise a ValueError.
3108
3123
  """
3109
- kubeconfig_path = os.path.expanduser(
3110
- os.getenv(
3111
- 'KUBECONFIG', kubernetes.kubernetes.config.kube_config.
3112
- KUBE_CONFIG_DEFAULT_LOCATION))
3113
- if len(kubeconfig_path.split(os.pathsep)) > 1:
3114
- raise ValueError('SkyPilot currently only supports one '
3115
- 'config file path with $KUBECONFIG. Current '
3116
- f'path(s) are {kubeconfig_path}.')
3117
- return kubeconfig_path
3124
+ # We should always use the latest KUBECONFIG environment variable to
3125
+ # make sure env var overrides get respected.
3126
+ paths = os.getenv('KUBECONFIG', kubernetes.DEFAULT_KUBECONFIG_PATH)
3127
+ expanded = []
3128
+ for path in paths.split(kubernetes.ENV_KUBECONFIG_PATH_SEPARATOR):
3129
+ expanded.append(os.path.expanduser(path))
3130
+ return expanded
@@ -78,7 +78,7 @@ def launch(name: str, instance_type: str, region: str, disk_size: int,
78
78
  amount of memory.
79
79
 
80
80
  * Vast instance types are an invention for skypilot. Refer to
81
- service_catalog/vast_catalog.py for the current construction
81
+ catalog/vast_catalog.py for the current construction
82
82
  of the type.
83
83
 
84
84
  """
@@ -8,8 +8,7 @@ from typing import List
8
8
 
9
9
  from sky import sky_logging
10
10
  from sky.adaptors import vsphere as vsphere_adaptor
11
- from sky.clouds.service_catalog.data_fetchers.fetch_vsphere import (
12
- get_accelerators_from_csv)
11
+ from sky.catalog.data_fetchers.fetch_vsphere import get_accelerators_from_csv
13
12
 
14
13
  logger = sky_logging.init_logger(__name__)
15
14
  DISPLAY_CONTROLLER_CLASS_ID_PREFIXES = ['03']
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional
6
6
  from sky import sky_logging
7
7
  from sky.adaptors import common as adaptors_common
8
8
  from sky.adaptors import vsphere as vsphere_adaptor
9
- from sky.clouds.service_catalog.common import get_catalog_path
9
+ from sky.catalog.common import get_catalog_path
10
10
  from sky.provision import common
11
11
  from sky.provision.vsphere import vsphere_utils
12
12
  from sky.provision.vsphere.common import custom_script as custom_script_lib
@@ -10,18 +10,14 @@ from sky import exceptions
10
10
  from sky import sky_logging
11
11
  from sky.adaptors import common as adaptors_common
12
12
  from sky.adaptors import vsphere as vsphere_adaptor
13
- from sky.clouds.service_catalog import vsphere_catalog
14
- from sky.clouds.service_catalog.common import get_catalog_path
15
- from sky.clouds.service_catalog.data_fetchers.fetch_vsphere import (
16
- initialize_accelerators_csv)
17
- from sky.clouds.service_catalog.data_fetchers.fetch_vsphere import (
18
- initialize_hosts_csv)
19
- from sky.clouds.service_catalog.data_fetchers.fetch_vsphere import (
20
- initialize_images_csv)
21
- from sky.clouds.service_catalog.data_fetchers.fetch_vsphere import (
13
+ from sky.catalog import vsphere_catalog
14
+ from sky.catalog.common import get_catalog_path
15
+ from sky.catalog.data_fetchers.fetch_vsphere import initialize_accelerators_csv
16
+ from sky.catalog.data_fetchers.fetch_vsphere import initialize_hosts_csv
17
+ from sky.catalog.data_fetchers.fetch_vsphere import initialize_images_csv
18
+ from sky.catalog.data_fetchers.fetch_vsphere import (
22
19
  initialize_instance_image_mapping_csv)
23
- from sky.clouds.service_catalog.data_fetchers.fetch_vsphere import (
24
- initialize_vms_csv)
20
+ from sky.catalog.data_fetchers.fetch_vsphere import initialize_vms_csv
25
21
  from sky.provision.vsphere.common import vim_utils
26
22
  from sky.provision.vsphere.common.cls_api_client import ClsApiClient
27
23
  from sky.provision.vsphere.common.cls_api_helper import ClsApiHelper
sky/resources.py CHANGED
@@ -7,13 +7,13 @@ from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union
7
7
  import colorama
8
8
 
9
9
  import sky
10
+ from sky import catalog
10
11
  from sky import check as sky_check
11
12
  from sky import clouds
12
13
  from sky import exceptions
13
14
  from sky import sky_logging
14
15
  from sky import skypilot_config
15
16
  from sky.clouds import cloud as sky_cloud
16
- from sky.clouds import service_catalog
17
17
  from sky.provision import docker_utils
18
18
  from sky.provision.gcp import constants as gcp_constants
19
19
  from sky.provision.kubernetes import utils as kubernetes_utils
@@ -45,7 +45,9 @@ class AutostopConfig:
45
45
  # to be complete.
46
46
  enabled: bool
47
47
  # If enabled is False, these values are ignored.
48
- idle_minutes: int = 5
48
+ # Keep the default value to 0 to make the behavior consistent with the CLI
49
+ # flags.
50
+ idle_minutes: int = 0
49
51
  down: bool = False
50
52
 
51
53
  def to_yaml_config(self) -> Union[Literal[False], Dict[str, Any]]:
@@ -379,7 +381,7 @@ class Resources:
379
381
  # if it fails to fetch some account specific catalog information (e.g., AWS
380
382
  # zone mapping). It is fine to use the default catalog as this function is
381
383
  # only for display purposes.
382
- @service_catalog.fallback_to_default_catalog
384
+ @catalog.fallback_to_default_catalog
383
385
  def __repr__(self) -> str:
384
386
  """Returns a string representation for display.
385
387
 
@@ -883,6 +885,25 @@ class Resources:
883
885
  valid_volumes.append(volume)
884
886
  self._volumes = valid_volumes
885
887
 
888
+ def override_autostop_config(self,
889
+ down: bool = False,
890
+ idle_minutes: Optional[int] = None) -> None:
891
+ """Override autostop config to the resource.
892
+
893
+ Args:
894
+ down: If true, override the autostop config to use autodown.
895
+ idle_minutes: If not None, override the idle minutes to autostop or
896
+ autodown.
897
+ """
898
+ if not down and idle_minutes is None:
899
+ return
900
+ if self._autostop_config is None:
901
+ self._autostop_config = AutostopConfig(enabled=True,)
902
+ if down:
903
+ self._autostop_config.down = down
904
+ if idle_minutes is not None:
905
+ self._autostop_config.idle_minutes = idle_minutes
906
+
886
907
  def is_launchable(self) -> bool:
887
908
  """Returns whether the resource is launchable."""
888
909
  return self.cloud is not None and self._instance_type is not None
sky/serve/server/core.py CHANGED
@@ -17,7 +17,7 @@ from sky import sky_logging
17
17
  from sky import skypilot_config
18
18
  from sky import task as task_lib
19
19
  from sky.backends import backend_utils
20
- from sky.clouds.service_catalog import common as service_catalog_common
20
+ from sky.catalog import common as service_catalog_common
21
21
  from sky.serve import constants as serve_constants
22
22
  from sky.serve import serve_state
23
23
  from sky.serve import serve_utils
sky/server/constants.py CHANGED
@@ -7,7 +7,7 @@ from sky.skylet import constants
7
7
  # API server version, whenever there is a change in API server that requires a
8
8
  # restart of the local API server or error out when the client does not match
9
9
  # the server version.
10
- API_VERSION = '7'
10
+ API_VERSION = '9'
11
11
 
12
12
  # Prefix for API request names.
13
13
  REQUEST_NAME_PREFIX = 'sky.'
@@ -25,8 +25,10 @@ API_SERVER_REQUEST_DB_PATH = '~/.sky/api_server/requests.db'
25
25
  CLUSTER_REFRESH_DAEMON_INTERVAL_SECONDS = 60
26
26
 
27
27
  # Environment variable for a file path to the API cookie file.
28
+ # Keep in sync with websocket_proxy.py
28
29
  API_COOKIE_FILE_ENV_VAR = f'{constants.SKYPILOT_ENV_VAR_PREFIX}API_COOKIE_FILE'
29
30
  # Default file if unset.
31
+ # Keep in sync with websocket_proxy.py
30
32
  API_COOKIE_FILE_DEFAULT_LOCATION = '~/.sky/cookies.txt'
31
33
 
32
34
  # The path to the dashboard build output
@@ -239,8 +239,11 @@ def override_request_env_and_config(
239
239
  client_command=request_body.entrypoint_command,
240
240
  using_remote_api_server=request_body.using_remote_api_server)
241
241
  try:
242
+ logger.debug(
243
+ f'override path: {request_body.override_skypilot_config_path}')
242
244
  with skypilot_config.override_skypilot_config(
243
- request_body.override_skypilot_config):
245
+ request_body.override_skypilot_config,
246
+ request_body.override_skypilot_config_path):
244
247
  yield
245
248
  finally:
246
249
  # We need to call the save_timeline() since atexit will not be
@@ -82,6 +82,17 @@ def get_override_skypilot_config_from_client() -> Dict[str, Any]:
82
82
  return config
83
83
 
84
84
 
85
+ def get_override_skypilot_config_path_from_client() -> Optional[str]:
86
+ """Returns the override config path from the client."""
87
+ if annotations.is_on_api_server:
88
+ return None
89
+ # Currently, we don't need to check if the client-side config
90
+ # has been overridden because we only deal with cases where
91
+ # client has a project-level config/changed config and the
92
+ # api server has a different config.
93
+ return skypilot_config.loaded_config_path_serialized()
94
+
95
+
85
96
  class RequestBody(pydantic.BaseModel):
86
97
  """The request body for the SkyPilot API."""
87
98
  env_vars: Dict[str, str] = {}
@@ -89,6 +100,7 @@ class RequestBody(pydantic.BaseModel):
89
100
  entrypoint_command: str = ''
90
101
  using_remote_api_server: bool = False
91
102
  override_skypilot_config: Optional[Dict[str, Any]] = {}
103
+ override_skypilot_config_path: Optional[str] = None
92
104
 
93
105
  # Allow extra fields in the request body, which is useful for backward
94
106
  # compatibility, i.e., we can add new fields to the request body without
@@ -108,6 +120,9 @@ class RequestBody(pydantic.BaseModel):
108
120
  data['override_skypilot_config'] = data.get(
109
121
  'override_skypilot_config',
110
122
  get_override_skypilot_config_from_client())
123
+ data['override_skypilot_config_path'] = data.get(
124
+ 'override_skypilot_config_path',
125
+ get_override_skypilot_config_path_from_client())
111
126
  super().__init__(**data)
112
127
 
113
128
  def to_kwargs(self) -> Dict[str, Any]:
@@ -122,6 +137,7 @@ class RequestBody(pydantic.BaseModel):
122
137
  kwargs.pop('entrypoint_command')
123
138
  kwargs.pop('using_remote_api_server')
124
139
  kwargs.pop('override_skypilot_config')
140
+ kwargs.pop('override_skypilot_config_path')
125
141
  return kwargs
126
142
 
127
143
  @property
@@ -180,8 +196,10 @@ class LaunchBody(RequestBody):
180
196
  task: str
181
197
  cluster_name: str
182
198
  retry_until_up: bool = False
199
+ # TODO(aylei): remove this field in v0.12.0
183
200
  idle_minutes_to_autostop: Optional[int] = None
184
201
  dryrun: bool = False
202
+ # TODO(aylei): remove this field in v0.12.0
185
203
  down: bool = False
186
204
  backend: Optional[str] = None
187
205
  optimize_target: common_lib.OptimizeTarget = common_lib.OptimizeTarget.COST
@@ -315,6 +333,12 @@ class ClusterJobsDownloadLogsBody(RequestBody):
315
333
  local_dir: str = constants.SKY_LOGS_DIRECTORY
316
334
 
317
335
 
336
+ class UserUpdateBody(RequestBody):
337
+ """The request body for the user update endpoint."""
338
+ user_id: str
339
+ role: str
340
+
341
+
318
342
  class DownloadBody(RequestBody):
319
343
  """The request body for the download endpoint."""
320
344
  folder_paths: List[str]
@@ -359,6 +383,7 @@ class JobsQueueBody(RequestBody):
359
383
  refresh: bool = False
360
384
  skip_finished: bool = False
361
385
  all_users: bool = False
386
+ job_ids: Optional[List[int]] = None
362
387
 
363
388
 
364
389
  class JobsCancelBody(RequestBody):
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional, Tuple
6
6
 
7
7
  from sky import jobs as managed_jobs
8
8
  from sky import models
9
- from sky.clouds.service_catalog import common
9
+ from sky.catalog import common
10
10
  from sky.data import storage
11
11
  from sky.provision.kubernetes import utils as kubernetes_utils
12
12
  from sky.serve import serve_state
sky/server/server.py CHANGED
@@ -26,6 +26,7 @@ from fastapi.middleware import cors
26
26
  import starlette.middleware.base
27
27
 
28
28
  import sky
29
+ from sky import catalog
29
30
  from sky import check as sky_check
30
31
  from sky import clouds
31
32
  from sky import core
@@ -34,7 +35,6 @@ from sky import execution
34
35
  from sky import global_user_state
35
36
  from sky import models
36
37
  from sky import sky_logging
37
- from sky.clouds import service_catalog
38
38
  from sky.data import storage_utils
39
39
  from sky.jobs.server import server as jobs_rest
40
40
  from sky.provision.kubernetes import utils as kubernetes_utils
@@ -49,6 +49,7 @@ from sky.server.requests import preconditions
49
49
  from sky.server.requests import requests as requests_lib
50
50
  from sky.skylet import constants
51
51
  from sky.usage import usage_lib
52
+ from sky.users import server as users_rest
52
53
  from sky.utils import admin_policy_utils
53
54
  from sky.utils import common as common_lib
54
55
  from sky.utils import common_utils
@@ -100,6 +101,27 @@ logger = sky_logging.init_logger(__name__)
100
101
  # response will block other requests from being processed.
101
102
 
102
103
 
104
+ class RBACMiddleware(starlette.middleware.base.BaseHTTPMiddleware):
105
+ """Middleware to handle RBAC."""
106
+
107
+ async def dispatch(self, request: fastapi.Request, call_next):
108
+ if request.url.path.startswith('/dashboard/'):
109
+ return await call_next(request)
110
+
111
+ auth_user = _get_auth_user_header(request)
112
+ if auth_user is None:
113
+ return await call_next(request)
114
+
115
+ permission_service = users_rest.permission_service
116
+ # Check the role permission
117
+ if permission_service.check_permission(auth_user.id, request.url.path,
118
+ request.method):
119
+ return fastapi.responses.JSONResponse(
120
+ status_code=403, content={'detail': 'Forbidden'})
121
+
122
+ return await call_next(request)
123
+
124
+
103
125
  class RequestIDMiddleware(starlette.middleware.base.BaseHTTPMiddleware):
104
126
  """Middleware to add a request ID to each request."""
105
127
 
@@ -130,7 +152,10 @@ class AuthProxyMiddleware(starlette.middleware.base.BaseHTTPMiddleware):
130
152
 
131
153
  # Add user to database if auth_user is present
132
154
  if auth_user is not None:
133
- global_user_state.add_or_update_user(auth_user)
155
+ newly_added = global_user_state.add_or_update_user(auth_user)
156
+ if newly_added:
157
+ users_rest.permission_service.add_user_if_not_exists(
158
+ auth_user.id)
134
159
 
135
160
  body = await request.body()
136
161
  if auth_user and body:
@@ -244,11 +269,13 @@ class PathCleanMiddleware(starlette.middleware.base.BaseHTTPMiddleware):
244
269
  parent = pathlib.Path('/dashboard')
245
270
  request_path = pathlib.Path(posixpath.normpath(request.url.path))
246
271
  if not _is_relative_to(request_path, parent):
247
- raise fastapi.HTTPException(status_code=403, detail='Forbidden')
272
+ return fastapi.responses.JSONResponse(
273
+ status_code=403, content={'detail': 'Forbidden'})
248
274
  return await call_next(request)
249
275
 
250
276
 
251
277
  app = fastapi.FastAPI(prefix='/api/v1', debug=True, lifespan=lifespan)
278
+ app.add_middleware(RBACMiddleware)
252
279
  app.add_middleware(InternalDashboardPrefixMiddleware)
253
280
  app.add_middleware(PathCleanMiddleware)
254
281
  app.add_middleware(CacheControlStaticMiddleware)
@@ -266,6 +293,7 @@ app.add_middleware(AuthProxyMiddleware)
266
293
  app.add_middleware(RequestIDMiddleware)
267
294
  app.include_router(jobs_rest.router, prefix='/jobs', tags=['jobs'])
268
295
  app.include_router(serve_rest.router, prefix='/serve', tags=['serve'])
296
+ app.include_router(users_rest.router, prefix='/users', tags=['users'])
269
297
  app.include_router(workspaces_rest.router,
270
298
  prefix='/workspaces',
271
299
  tags=['workspaces'])
@@ -389,7 +417,7 @@ async def list_accelerators(
389
417
  request_id=request.state.request_id,
390
418
  request_name='list_accelerators',
391
419
  request_body=list_accelerator_counts_body,
392
- func=service_catalog.list_accelerators,
420
+ func=catalog.list_accelerators,
393
421
  schedule_type=requests_lib.ScheduleType.SHORT,
394
422
  )
395
423
 
@@ -404,7 +432,7 @@ async def list_accelerator_counts(
404
432
  request_id=request.state.request_id,
405
433
  request_name='list_accelerator_counts',
406
434
  request_body=list_accelerator_counts_body,
407
- func=service_catalog.list_accelerator_counts,
435
+ func=catalog.list_accelerator_counts,
408
436
  schedule_type=requests_lib.ScheduleType.SHORT,
409
437
  )
410
438
 
@@ -835,13 +863,6 @@ async def logs(
835
863
  )
836
864
 
837
865
 
838
- @app.get('/users')
839
- async def users() -> List[Dict[str, Any]]:
840
- """Gets all users."""
841
- user_list = global_user_state.get_all_users()
842
- return [user.to_dict() for user in user_list]
843
-
844
-
845
866
  @app.post('/download_logs')
846
867
  async def download_logs(
847
868
  request: fastapi.Request,
@@ -3,7 +3,7 @@
3
3
  import asyncio
4
4
  import collections
5
5
  import pathlib
6
- from typing import AsyncGenerator, Deque, List, Optional
6
+ from typing import AsyncGenerator, Deque, Optional
7
7
 
8
8
  import aiofiles
9
9
  import fastapi
@@ -15,12 +15,6 @@ from sky.utils import rich_utils
15
15
 
16
16
  logger = sky_logging.init_logger(__name__)
17
17
 
18
- # When streaming log lines, buffer the lines in memory and flush them in chunks
19
- # to improve log tailing throughput. Buffer size is the max size bytes of each
20
- # chunk and the timeout threshold for flushing the buffer to ensure
21
- # responsiveness.
22
- _BUFFER_SIZE = 8 * 1024 # 8KB
23
- _BUFFER_TIMEOUT = 0.02 # 20ms
24
18
  _HEARTBEAT_INTERVAL = 30
25
19
 
26
20
 
@@ -44,20 +38,6 @@ async def log_streamer(request_id: Optional[str],
44
38
  follow: bool = True) -> AsyncGenerator[str, None]:
45
39
  """Streams the logs of a request."""
46
40
 
47
- # Buffer the lines in memory and flush them in chunks to improve log tailing
48
- # throughput.
49
- buffer: List[str] = []
50
- buffer_bytes = 0
51
- last_flush_time = asyncio.get_event_loop().time()
52
-
53
- async def flush_buffer() -> AsyncGenerator[str, None]:
54
- nonlocal buffer, buffer_bytes, last_flush_time
55
- if buffer:
56
- yield ''.join(buffer)
57
- buffer.clear()
58
- buffer_bytes = 0
59
- last_flush_time = asyncio.get_event_loop().time()
60
-
61
41
  if request_id is not None:
62
42
  status_msg = rich_utils.EncodedStatusMessage(
63
43
  f'[dim]Checking request: {request_id}[/dim]')
@@ -119,14 +99,7 @@ async def log_streamer(request_id: Optional[str],
119
99
  # while keeps the loop tight to make log stream responsive.
120
100
  await asyncio.sleep(0)
121
101
  line: Optional[bytes] = await f.readline()
122
-
123
- current_time = asyncio.get_event_loop().time()
124
102
  if not line:
125
- if buffer and (current_time -
126
- last_flush_time) >= _BUFFER_TIMEOUT:
127
- async for chunk in flush_buffer():
128
- yield chunk
129
-
130
103
  if request_id is not None:
131
104
  request_task = requests_lib.get_request(request_id)
132
105
  if request_task.status > requests_lib.RequestStatus.RUNNING:
@@ -165,16 +138,7 @@ async def log_streamer(request_id: Optional[str],
165
138
  # sending invisible characters might be okay.
166
139
  if is_payload:
167
140
  continue
168
-
169
- # Add to buffer
170
- buffer.append(line_str)
171
- buffer_bytes += len(line_str.encode('utf-8'))
172
-
173
- # Check if we should flush the buffer
174
- if (buffer_bytes >= _BUFFER_SIZE or
175
- (current_time - last_flush_time) >= _BUFFER_TIMEOUT):
176
- async for chunk in flush_buffer():
177
- yield chunk
141
+ yield line_str
178
142
 
179
143
 
180
144
  def stream_response(
@@ -16,3 +16,4 @@ include sky/templates/*
16
16
  include sky/utils/kubernetes/*
17
17
  include sky/server/html/*
18
18
  recursive-include sky/dashboard/out *
19
+ include sky/users/*.conf
@@ -58,6 +58,8 @@ install_requires = [
58
58
  'setproctitle',
59
59
  'sqlalchemy',
60
60
  'psycopg2-binary',
61
+ 'casbin',
62
+ 'sqlalchemy_adapter',
61
63
  ]
62
64
 
63
65
  local_ray = [
sky/skylet/constants.py CHANGED
@@ -379,7 +379,7 @@ OVERRIDEABLE_CONFIG_KEYS_IN_TASK: List[Tuple[str, ...]] = [
379
379
  SKIPPED_CLIENT_OVERRIDE_KEYS: List[Tuple[str, ...]] = [('admin_policy',),
380
380
  ('api_server',),
381
381
  ('allowed_clouds',),
382
- ('workspaces',)]
382
+ ('workspaces',), ('db',)]
383
383
 
384
384
  # Constants for Azure blob storage
385
385
  WAIT_FOR_STORAGE_ACCOUNT_CREATION = 60
@@ -409,6 +409,12 @@ ENV_VAR_IS_SKYPILOT_SERVER = 'IS_SKYPILOT_SERVER'
409
409
 
410
410
  SKYPILOT_DEFAULT_WORKSPACE = 'default'
411
411
 
412
- # Experimental - may be deprecated in the future without notice.
413
- SKYPILOT_API_SERVER_DB_URL_ENV_VAR: str = (
414
- f'{SKYPILOT_ENV_VAR_PREFIX}API_SERVER_DB_URL')
412
+ # BEGIN constants used for service catalog.
413
+ HOSTED_CATALOG_DIR_URL = 'https://raw.githubusercontent.com/skypilot-org/skypilot-catalog/master/catalogs' # pylint: disable=line-too-long
414
+ HOSTED_CATALOG_DIR_URL_S3_MIRROR = 'https://skypilot-catalog.s3.us-east-1.amazonaws.com/catalogs' # pylint: disable=line-too-long
415
+ CATALOG_SCHEMA_VERSION = 'v7'
416
+ CATALOG_DIR = '~/.sky/catalogs'
417
+ ALL_CLOUDS = ('aws', 'azure', 'gcp', 'ibm', 'lambda', 'scp', 'oci',
418
+ 'kubernetes', 'runpod', 'vast', 'vsphere', 'cudo', 'fluidstack',
419
+ 'paperspace', 'do', 'nebius', 'ssh')
420
+ # END constants used for service catalog.