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.
- sky/__init__.py +3 -3
- sky/adaptors/kubernetes.py +8 -0
- sky/admin_policy.py +5 -0
- sky/backends/backend_utils.py +1 -0
- sky/backends/cloud_vm_ray_backend.py +8 -4
- sky/{clouds/service_catalog → catalog}/__init__.py +6 -17
- sky/{clouds/service_catalog → catalog}/aws_catalog.py +3 -3
- sky/{clouds/service_catalog → catalog}/azure_catalog.py +2 -2
- sky/{clouds/service_catalog → catalog}/common.py +2 -2
- sky/{clouds/service_catalog → catalog}/cudo_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/data_fetchers/analyze.py +1 -1
- sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_aws.py +1 -1
- sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vsphere.py +1 -1
- sky/{clouds/service_catalog → catalog}/do_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/fluidstack_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/gcp_catalog.py +2 -2
- sky/{clouds/service_catalog → catalog}/ibm_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/kubernetes_catalog.py +2 -2
- sky/{clouds/service_catalog → catalog}/lambda_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/nebius_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/oci_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/paperspace_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/runpod_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/scp_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/ssh_catalog.py +3 -3
- sky/{clouds/service_catalog → catalog}/vast_catalog.py +1 -1
- sky/{clouds/service_catalog → catalog}/vsphere_catalog.py +1 -1
- sky/cli.py +16 -13
- sky/client/cli.py +16 -13
- sky/client/sdk.py +30 -12
- sky/clouds/aws.py +41 -40
- sky/clouds/azure.py +31 -34
- sky/clouds/cloud.py +8 -8
- sky/clouds/cudo.py +26 -26
- sky/clouds/do.py +24 -24
- sky/clouds/fluidstack.py +27 -29
- sky/clouds/gcp.py +42 -42
- sky/clouds/ibm.py +26 -26
- sky/clouds/kubernetes.py +24 -12
- sky/clouds/lambda_cloud.py +28 -30
- sky/clouds/nebius.py +26 -28
- sky/clouds/oci.py +32 -32
- sky/clouds/paperspace.py +24 -26
- sky/clouds/runpod.py +26 -28
- sky/clouds/scp.py +37 -36
- sky/clouds/utils/gcp_utils.py +3 -2
- sky/clouds/vast.py +27 -27
- sky/clouds/vsphere.py +12 -15
- sky/core.py +2 -2
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/chunks/614-635a84e87800f99e.js +66 -0
- sky/dashboard/out/_next/static/chunks/{856-f1b1f7f47edde2e8.js → 856-3a32da4b84176f6d.js} +1 -1
- sky/dashboard/out/_next/static/chunks/937.3759f538f11a0953.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/config-1a1eeb949dab8897.js +6 -0
- sky/dashboard/out/_next/static/chunks/pages/users-262aab38b9baaf3a.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/workspaces-384ea5fa0cea8f28.js +1 -0
- sky/dashboard/out/_next/static/chunks/{webpack-f27c9a32aa3d9c6d.js → webpack-65d465f948974c0d.js} +1 -1
- sky/dashboard/out/_next/static/css/667d941a2888ce6e.css +3 -0
- sky/dashboard/out/_next/static/qjhIe-yC6nHcLKBqpzO1M/_buildManifest.js +1 -0
- sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
- sky/dashboard/out/clusters/[cluster].html +1 -1
- sky/dashboard/out/clusters.html +1 -1
- sky/dashboard/out/config.html +1 -1
- sky/dashboard/out/index.html +1 -1
- sky/dashboard/out/infra/[context].html +1 -1
- sky/dashboard/out/infra.html +1 -1
- sky/dashboard/out/jobs/[job].html +1 -1
- sky/dashboard/out/jobs.html +1 -1
- sky/dashboard/out/users.html +1 -1
- sky/dashboard/out/workspace/new.html +1 -1
- sky/dashboard/out/workspaces/[name].html +1 -1
- sky/dashboard/out/workspaces.html +1 -1
- sky/data/storage_utils.py +5 -2
- sky/execution.py +44 -46
- sky/global_user_state.py +119 -86
- sky/jobs/client/sdk.py +4 -1
- sky/jobs/server/core.py +6 -2
- sky/models.py +1 -0
- sky/optimizer.py +1 -1
- sky/provision/cudo/cudo_machine_type.py +1 -1
- sky/provision/kubernetes/utils.py +35 -22
- sky/provision/vast/utils.py +1 -1
- sky/provision/vsphere/common/vim_utils.py +1 -2
- sky/provision/vsphere/instance.py +1 -1
- sky/provision/vsphere/vsphere_utils.py +7 -11
- sky/resources.py +24 -3
- sky/serve/server/core.py +1 -1
- sky/server/constants.py +3 -1
- sky/server/requests/executor.py +4 -1
- sky/server/requests/payloads.py +25 -0
- sky/server/requests/serializers/decoders.py +1 -1
- sky/server/server.py +33 -12
- sky/server/stream_utils.py +2 -38
- sky/setup_files/MANIFEST.in +1 -0
- sky/setup_files/dependencies.py +2 -0
- sky/skylet/constants.py +10 -4
- sky/skypilot_config.py +92 -39
- sky/templates/websocket_proxy.py +11 -1
- sky/usage/usage_lib.py +4 -3
- sky/users/__init__.py +0 -0
- sky/users/model.conf +15 -0
- sky/users/permission.py +178 -0
- sky/users/rbac.py +86 -0
- sky/users/server.py +66 -0
- sky/utils/accelerator_registry.py +3 -3
- sky/utils/kubernetes/deploy_remote_cluster.py +2 -1
- sky/utils/schemas.py +20 -10
- sky/workspaces/core.py +2 -2
- {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/METADATA +3 -1
- {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/RECORD +134 -130
- sky/clouds/service_catalog/constants.py +0 -8
- sky/dashboard/out/_next/static/chunks/614-3d29f98e0634b179.js +0 -66
- sky/dashboard/out/_next/static/chunks/937.f97f83652028e944.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/config-35383adcb0edb5e2.js +0 -6
- sky/dashboard/out/_next/static/chunks/pages/users-07b523ccb19317ad.js +0 -6
- sky/dashboard/out/_next/static/chunks/pages/workspaces-f54921ec9eb20965.js +0 -1
- sky/dashboard/out/_next/static/css/63d3995d8b528eb1.css +0 -3
- sky/dashboard/out/_next/static/zTAFq_Iv6_yxQj3fXvJWR/_buildManifest.js +0 -1
- /sky/{clouds/service_catalog → catalog}/config.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/__init__.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_azure.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_cudo.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_fluidstack.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_gcp.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_ibm.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_lambda_cloud.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vast.py +0 -0
- /sky/dashboard/out/_next/static/chunks/{121-8f55ee3fa6301784.js → 121-865d2bf8a3b84c6a.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/{236-fef38aa6e5639300.js → 236-4c0dc6f63ccc6319.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/{37-947904ccc5687bac.js → 37-beedd583fea84cc8.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/{682-2be9b0f169727f2f.js → 682-6647f0417d5662f0.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/{843-a097338acb89b7d7.js → 843-c296541442d4af88.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/{969-d7b6fb7f602bfcb3.js → 969-c7abda31c10440ac.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/pages/{_app-67925f5e6382e22f.js → _app-cb81dc4d27f4d009.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/{[job]-158b70da336d8607.js → [job]-65d04d5d77cbb6b6.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-62c9982dc3675725.js → [cluster]-beabbcd7606c1a23.js} +0 -0
- /sky/dashboard/out/_next/static/chunks/pages/jobs/{[job]-a62a3c65dc9bc57c.js → [job]-86c47edc500f15f9.js} +0 -0
- /sky/dashboard/out/_next/static/{zTAFq_Iv6_yxQj3fXvJWR → qjhIe-yC6nHcLKBqpzO1M}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250603.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
_ =
|
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
|
-
#
|
1541
|
-
|
1542
|
-
|
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
|
3104
|
-
"""Get the path to the kubeconfig
|
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
|
-
|
3110
|
-
|
3111
|
-
|
3112
|
-
|
3113
|
-
|
3114
|
-
|
3115
|
-
|
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
|
sky/provision/vast/utils.py
CHANGED
@@ -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
|
-
|
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.
|
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.
|
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.
|
14
|
-
from sky.
|
15
|
-
from sky.
|
16
|
-
|
17
|
-
from sky.
|
18
|
-
|
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.
|
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
|
-
|
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
|
-
@
|
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.
|
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 = '
|
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
|
sky/server/requests/executor.py
CHANGED
@@ -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
|
sky/server/requests/payloads.py
CHANGED
@@ -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.
|
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
|
-
|
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=
|
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=
|
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,
|
sky/server/stream_utils.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
import asyncio
|
4
4
|
import collections
|
5
5
|
import pathlib
|
6
|
-
from typing import AsyncGenerator, Deque,
|
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(
|
sky/setup_files/MANIFEST.in
CHANGED
sky/setup_files/dependencies.py
CHANGED
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
|
-
#
|
413
|
-
|
414
|
-
|
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.
|