skypilot-nightly 1.0.0.dev20251009__py3-none-any.whl → 1.0.0.dev20251107__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.
Potentially problematic release.
This version of skypilot-nightly might be problematic. Click here for more details.
- sky/__init__.py +6 -2
- sky/adaptors/aws.py +25 -7
- sky/adaptors/coreweave.py +278 -0
- sky/adaptors/kubernetes.py +64 -0
- sky/adaptors/shadeform.py +89 -0
- sky/admin_policy.py +20 -0
- sky/authentication.py +59 -149
- sky/backends/backend_utils.py +104 -63
- sky/backends/cloud_vm_ray_backend.py +84 -39
- sky/catalog/data_fetchers/fetch_runpod.py +698 -0
- sky/catalog/data_fetchers/fetch_shadeform.py +142 -0
- sky/catalog/kubernetes_catalog.py +24 -28
- sky/catalog/runpod_catalog.py +5 -1
- sky/catalog/shadeform_catalog.py +165 -0
- sky/check.py +25 -13
- sky/client/cli/command.py +335 -86
- sky/client/cli/flags.py +4 -2
- sky/client/cli/table_utils.py +17 -9
- sky/client/sdk.py +59 -12
- sky/cloud_stores.py +73 -0
- sky/clouds/__init__.py +2 -0
- sky/clouds/aws.py +71 -16
- sky/clouds/azure.py +12 -5
- sky/clouds/cloud.py +19 -9
- sky/clouds/cudo.py +12 -5
- sky/clouds/do.py +4 -1
- sky/clouds/fluidstack.py +12 -5
- sky/clouds/gcp.py +12 -5
- sky/clouds/hyperbolic.py +12 -5
- sky/clouds/ibm.py +12 -5
- sky/clouds/kubernetes.py +62 -25
- sky/clouds/lambda_cloud.py +12 -5
- sky/clouds/nebius.py +12 -5
- sky/clouds/oci.py +12 -5
- sky/clouds/paperspace.py +4 -1
- sky/clouds/primeintellect.py +4 -1
- sky/clouds/runpod.py +12 -5
- sky/clouds/scp.py +12 -5
- sky/clouds/seeweb.py +4 -1
- sky/clouds/shadeform.py +400 -0
- sky/clouds/ssh.py +4 -2
- sky/clouds/vast.py +12 -5
- sky/clouds/vsphere.py +4 -1
- sky/core.py +12 -11
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/chunks/1141-e6aa9ab418717c59.js +11 -0
- sky/dashboard/out/_next/static/chunks/{1871-49141c317f3a9020.js → 1871-74503c8e80fd253b.js} +1 -1
- sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
- sky/dashboard/out/_next/static/chunks/2755.fff53c4a3fcae910.js +26 -0
- sky/dashboard/out/_next/static/chunks/3294.72362fa129305b19.js +1 -0
- sky/dashboard/out/_next/static/chunks/{3785.a19328ba41517b8b.js → 3785.ad6adaa2a0fa9768.js} +1 -1
- sky/dashboard/out/_next/static/chunks/{4725.10f7a9a5d3ea8208.js → 4725.a830b5c9e7867c92.js} +1 -1
- sky/dashboard/out/_next/static/chunks/6856-ef8ba11f96d8c4a3.js +1 -0
- sky/dashboard/out/_next/static/chunks/6990-32b6e2d3822301fa.js +1 -0
- sky/dashboard/out/_next/static/chunks/7615-3301e838e5f25772.js +1 -0
- sky/dashboard/out/_next/static/chunks/8969-1e4613c651bf4051.js +1 -0
- sky/dashboard/out/_next/static/chunks/9025.fa408f3242e9028d.js +6 -0
- sky/dashboard/out/_next/static/chunks/9353-cff34f7e773b2e2b.js +1 -0
- sky/dashboard/out/_next/static/chunks/9360.7310982cf5a0dc79.js +31 -0
- sky/dashboard/out/_next/static/chunks/pages/{_app-ce361c6959bc2001.js → _app-bde01e4a2beec258.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-c736ead69c2d86ec.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-477555ab7c0b13d8.js → [cluster]-a37d2063af475a1c.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{clusters-2f61f65487f6d8ff.js → clusters-d44859594e6f8064.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-553b8b5cb65e100b.js → [context]-c0b5935149902e6f.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{infra-910a22500c50596f.js → infra-aed0ea19df7cf961.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-5796e8d6aea291a0.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs/pools/{[pool]-bc979970c247d8f3.js → [pool]-6edeb7d06032adfc.js} +2 -2
- sky/dashboard/out/_next/static/chunks/pages/{jobs-a35a9dc3c5ccd657.js → jobs-479dde13399cf270.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{users-98d2ed979084162a.js → users-5ab3b907622cf0fe.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{volumes-835d14ba94808f79.js → volumes-b84b948ff357c43e.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-e8688c35c06f0ac5.js → [name]-c5a3eeee1c218af1.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{workspaces-69c80d677d3c2949.js → workspaces-22b23febb3e89ce1.js} +1 -1
- sky/dashboard/out/_next/static/chunks/webpack-2679be77fc08a2f8.js +1 -0
- sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
- sky/dashboard/out/_next/static/zB0ed6ge_W1MDszVHhijS/_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/pools/[pool].html +1 -1
- sky/dashboard/out/jobs.html +1 -1
- sky/dashboard/out/users.html +1 -1
- sky/dashboard/out/volumes.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/data_utils.py +92 -1
- sky/data/mounting_utils.py +143 -19
- sky/data/storage.py +168 -11
- sky/exceptions.py +13 -1
- sky/execution.py +13 -0
- sky/global_user_state.py +189 -113
- sky/jobs/client/sdk.py +32 -10
- sky/jobs/client/sdk_async.py +9 -3
- sky/jobs/constants.py +3 -1
- sky/jobs/controller.py +164 -192
- sky/jobs/file_content_utils.py +80 -0
- sky/jobs/log_gc.py +201 -0
- sky/jobs/recovery_strategy.py +59 -82
- sky/jobs/scheduler.py +20 -9
- sky/jobs/server/core.py +105 -23
- sky/jobs/server/server.py +40 -28
- sky/jobs/server/utils.py +32 -11
- sky/jobs/state.py +588 -110
- sky/jobs/utils.py +442 -209
- sky/logs/agent.py +1 -1
- sky/metrics/utils.py +45 -6
- sky/optimizer.py +1 -1
- sky/provision/__init__.py +7 -0
- sky/provision/aws/instance.py +2 -1
- sky/provision/azure/instance.py +2 -1
- sky/provision/common.py +2 -0
- sky/provision/cudo/instance.py +2 -1
- sky/provision/do/instance.py +2 -1
- sky/provision/fluidstack/instance.py +4 -3
- sky/provision/gcp/instance.py +2 -1
- sky/provision/hyperbolic/instance.py +2 -1
- sky/provision/instance_setup.py +10 -2
- sky/provision/kubernetes/constants.py +0 -1
- sky/provision/kubernetes/instance.py +222 -89
- sky/provision/kubernetes/network.py +12 -8
- sky/provision/kubernetes/utils.py +114 -53
- sky/provision/kubernetes/volume.py +5 -4
- sky/provision/lambda_cloud/instance.py +2 -1
- sky/provision/nebius/instance.py +2 -1
- sky/provision/oci/instance.py +2 -1
- sky/provision/paperspace/instance.py +2 -1
- sky/provision/provisioner.py +11 -2
- sky/provision/runpod/instance.py +2 -1
- sky/provision/scp/instance.py +2 -1
- sky/provision/seeweb/instance.py +3 -3
- sky/provision/shadeform/__init__.py +11 -0
- sky/provision/shadeform/config.py +12 -0
- sky/provision/shadeform/instance.py +351 -0
- sky/provision/shadeform/shadeform_utils.py +83 -0
- sky/provision/vast/instance.py +2 -1
- sky/provision/vsphere/instance.py +2 -1
- sky/resources.py +1 -1
- sky/schemas/api/responses.py +9 -5
- sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
- sky/schemas/db/spot_jobs/004_job_file_contents.py +42 -0
- sky/schemas/db/spot_jobs/005_logs_gc.py +38 -0
- sky/schemas/generated/jobsv1_pb2.py +52 -52
- sky/schemas/generated/jobsv1_pb2.pyi +4 -2
- sky/schemas/generated/managed_jobsv1_pb2.py +39 -35
- sky/schemas/generated/managed_jobsv1_pb2.pyi +21 -5
- sky/serve/client/impl.py +11 -3
- sky/serve/replica_managers.py +5 -2
- sky/serve/serve_utils.py +9 -2
- sky/serve/server/impl.py +7 -2
- sky/serve/server/server.py +18 -15
- sky/serve/service.py +2 -2
- sky/server/auth/oauth2_proxy.py +2 -5
- sky/server/common.py +31 -28
- sky/server/constants.py +5 -1
- sky/server/daemons.py +27 -19
- sky/server/requests/executor.py +138 -74
- sky/server/requests/payloads.py +9 -1
- sky/server/requests/preconditions.py +13 -10
- sky/server/requests/request_names.py +120 -0
- sky/server/requests/requests.py +485 -153
- sky/server/requests/serializers/decoders.py +26 -13
- sky/server/requests/serializers/encoders.py +56 -11
- sky/server/requests/threads.py +106 -0
- sky/server/rest.py +70 -18
- sky/server/server.py +283 -104
- sky/server/stream_utils.py +233 -59
- sky/server/uvicorn.py +18 -17
- sky/setup_files/alembic.ini +4 -0
- sky/setup_files/dependencies.py +32 -13
- sky/sky_logging.py +0 -2
- sky/skylet/constants.py +30 -7
- sky/skylet/events.py +7 -0
- sky/skylet/log_lib.py +8 -2
- sky/skylet/log_lib.pyi +1 -1
- sky/skylet/services.py +26 -13
- sky/skylet/subprocess_daemon.py +103 -29
- sky/skypilot_config.py +87 -75
- sky/ssh_node_pools/server.py +9 -8
- sky/task.py +67 -54
- sky/templates/kubernetes-ray.yml.j2 +8 -1
- sky/templates/nebius-ray.yml.j2 +1 -0
- sky/templates/shadeform-ray.yml.j2 +72 -0
- sky/templates/websocket_proxy.py +142 -12
- sky/users/permission.py +8 -1
- sky/utils/admin_policy_utils.py +16 -3
- sky/utils/asyncio_utils.py +78 -0
- sky/utils/auth_utils.py +153 -0
- sky/utils/cli_utils/status_utils.py +8 -2
- sky/utils/command_runner.py +11 -0
- sky/utils/common.py +3 -1
- sky/utils/common_utils.py +7 -4
- sky/utils/context.py +57 -51
- sky/utils/context_utils.py +30 -12
- sky/utils/controller_utils.py +35 -8
- sky/utils/db/db_utils.py +37 -10
- sky/utils/db/migration_utils.py +8 -4
- sky/utils/locks.py +24 -6
- sky/utils/resource_checker.py +4 -1
- sky/utils/resources_utils.py +53 -29
- sky/utils/schemas.py +23 -4
- sky/utils/subprocess_utils.py +17 -4
- sky/volumes/server/server.py +7 -6
- sky/workspaces/server.py +13 -12
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/METADATA +306 -55
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/RECORD +215 -195
- sky/dashboard/out/_next/static/chunks/1121-d0782b9251f0fcd3.js +0 -1
- sky/dashboard/out/_next/static/chunks/1141-3b40c39626f99c89.js +0 -11
- sky/dashboard/out/_next/static/chunks/2755.97300e1362fe7c98.js +0 -26
- sky/dashboard/out/_next/static/chunks/3015-8d748834fcc60b46.js +0 -1
- sky/dashboard/out/_next/static/chunks/3294.1fafbf42b3bcebff.js +0 -1
- sky/dashboard/out/_next/static/chunks/6135-4b4d5e824b7f9d3c.js +0 -1
- sky/dashboard/out/_next/static/chunks/6856-5fdc9b851a18acdb.js +0 -1
- sky/dashboard/out/_next/static/chunks/6990-f6818c84ed8f1c86.js +0 -1
- sky/dashboard/out/_next/static/chunks/8969-66237729cdf9749e.js +0 -1
- sky/dashboard/out/_next/static/chunks/9025.c12318fb6a1a9093.js +0 -6
- sky/dashboard/out/_next/static/chunks/9360.71e83b2ddc844ec2.js +0 -31
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-8f058b0346db2aff.js +0 -16
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-4f7079dcab6ed653.js +0 -16
- sky/dashboard/out/_next/static/chunks/webpack-6a5ddd0184bfa22c.js +0 -1
- sky/dashboard/out/_next/static/css/4614e06482d7309e.css +0 -3
- sky/dashboard/out/_next/static/hIViZcQBkn0HE8SpaSsUU/_buildManifest.js +0 -1
- /sky/dashboard/out/_next/static/{hIViZcQBkn0HE8SpaSsUU → zB0ed6ge_W1MDszVHhijS}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20251009.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/top_level.txt +0 -0
sky/clouds/shadeform.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
""" Shadeform Cloud. """
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import typing
|
|
6
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
|
|
7
|
+
|
|
8
|
+
from sky import catalog
|
|
9
|
+
from sky import clouds
|
|
10
|
+
from sky.adaptors import common as adaptors_common
|
|
11
|
+
from sky.catalog import shadeform_catalog
|
|
12
|
+
from sky.utils import registry
|
|
13
|
+
from sky.utils import resources_utils
|
|
14
|
+
from sky.utils import status_lib
|
|
15
|
+
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from sky import resources as resources_lib
|
|
18
|
+
from sky.utils import volume as volume_lib
|
|
19
|
+
else:
|
|
20
|
+
requests = adaptors_common.LazyImport('requests')
|
|
21
|
+
|
|
22
|
+
# Minimum set of files under ~/.shadeform that grant Shadeform access.
|
|
23
|
+
_CREDENTIAL_FILES = [
|
|
24
|
+
'api_key',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@registry.CLOUD_REGISTRY.register
|
|
29
|
+
class Shadeform(clouds.Cloud):
|
|
30
|
+
"""Shadeform GPU Cloud
|
|
31
|
+
|
|
32
|
+
Shadeform is a unified API for deploying and managing cloud GPUs across
|
|
33
|
+
multiple cloud providers.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Shadeform doesn't have explicit cluster name limits, but conservative
|
|
37
|
+
_MAX_CLUSTER_NAME_LEN_LIMIT = 120
|
|
38
|
+
|
|
39
|
+
# Features not currently supported by Shadeform
|
|
40
|
+
# yapf: disable
|
|
41
|
+
_CLOUD_UNSUPPORTED_FEATURES = {
|
|
42
|
+
clouds.CloudImplementationFeatures.STOP:
|
|
43
|
+
'Stopping instances not supported on Shadeform.',
|
|
44
|
+
clouds.CloudImplementationFeatures.MULTI_NODE:
|
|
45
|
+
'Multi-node clusters not supported on Shadeform.',
|
|
46
|
+
clouds.CloudImplementationFeatures.SPOT_INSTANCE:
|
|
47
|
+
'Spot instances not supported on Shadeform.',
|
|
48
|
+
clouds.CloudImplementationFeatures.CUSTOM_DISK_TIER:
|
|
49
|
+
'Custom disk tiers not supported on Shadeform.',
|
|
50
|
+
clouds.CloudImplementationFeatures.CUSTOM_NETWORK_TIER:
|
|
51
|
+
'Custom network tiers not supported on Shadeform.',
|
|
52
|
+
clouds.CloudImplementationFeatures.STORAGE_MOUNTING:
|
|
53
|
+
'Object storage mounting not supported on Shadeform.',
|
|
54
|
+
clouds.CloudImplementationFeatures.HOST_CONTROLLERS:
|
|
55
|
+
'Host controllers not supported on Shadeform.',
|
|
56
|
+
clouds.CloudImplementationFeatures.HIGH_AVAILABILITY_CONTROLLERS:
|
|
57
|
+
'High availability controllers not supported.',
|
|
58
|
+
clouds.CloudImplementationFeatures.CLONE_DISK_FROM_CLUSTER:
|
|
59
|
+
'Disk cloning not supported on Shadeform.',
|
|
60
|
+
clouds.CloudImplementationFeatures.IMAGE_ID:
|
|
61
|
+
'Custom image IDs not supported on Shadeform.',
|
|
62
|
+
clouds.CloudImplementationFeatures.DOCKER_IMAGE:
|
|
63
|
+
'Docker images not supported on Shadeform yet.',
|
|
64
|
+
clouds.CloudImplementationFeatures.CUSTOM_MULTI_NETWORK:
|
|
65
|
+
'Custom multiple network interfaces not supported.',
|
|
66
|
+
}
|
|
67
|
+
# yapf: enable
|
|
68
|
+
|
|
69
|
+
_regions: List[clouds.Region] = []
|
|
70
|
+
|
|
71
|
+
PROVISIONER_VERSION = clouds.ProvisionerVersion.SKYPILOT
|
|
72
|
+
STATUS_VERSION = clouds.StatusVersion.SKYPILOT
|
|
73
|
+
OPEN_PORTS_VERSION = clouds.OpenPortsVersion.LAUNCH_ONLY
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def _unsupported_features_for_resources(
|
|
77
|
+
cls,
|
|
78
|
+
resources: 'resources_lib.Resources',
|
|
79
|
+
region: Optional[str] = None,
|
|
80
|
+
) -> Dict[clouds.CloudImplementationFeatures, str]:
|
|
81
|
+
"""The features not supported based on the resources provided."""
|
|
82
|
+
del resources # unused
|
|
83
|
+
return cls._CLOUD_UNSUPPORTED_FEATURES
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def _max_cluster_name_length(cls) -> Optional[int]:
|
|
87
|
+
return cls._MAX_CLUSTER_NAME_LEN_LIMIT
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def regions_with_offering(
|
|
91
|
+
cls,
|
|
92
|
+
instance_type: str,
|
|
93
|
+
accelerators: Optional[Dict[str, int]],
|
|
94
|
+
use_spot: bool,
|
|
95
|
+
region: Optional[str],
|
|
96
|
+
zone: Optional[str],
|
|
97
|
+
resources: Optional['resources_lib.Resources'] = None,
|
|
98
|
+
) -> List[clouds.Region]:
|
|
99
|
+
"""Get regions that offer the requested instance type."""
|
|
100
|
+
assert zone is None, 'Shadeform does not support zones.'
|
|
101
|
+
del zone # unused
|
|
102
|
+
if use_spot:
|
|
103
|
+
return [] # No spot support
|
|
104
|
+
|
|
105
|
+
# IMPORTANT: instance_type here is the specific Shadeform instance type
|
|
106
|
+
# (like 'massedcompute_A6000_base'), NOT the accelerator name
|
|
107
|
+
# We only return regions where this exact instance type exists
|
|
108
|
+
regions = shadeform_catalog.get_region_zones_for_instance_type(
|
|
109
|
+
instance_type, use_spot)
|
|
110
|
+
|
|
111
|
+
if region is not None:
|
|
112
|
+
regions = [r for r in regions if r.name == region]
|
|
113
|
+
return regions
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def zones_provision_loop(
|
|
117
|
+
cls,
|
|
118
|
+
*,
|
|
119
|
+
region: str,
|
|
120
|
+
num_nodes: int,
|
|
121
|
+
instance_type: str,
|
|
122
|
+
accelerators: Optional[Dict[str, int]] = None,
|
|
123
|
+
use_spot: bool = False,
|
|
124
|
+
) -> Iterator[None]:
|
|
125
|
+
"""Iterate over zones for provisioning."""
|
|
126
|
+
del num_nodes # unused
|
|
127
|
+
if use_spot:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
regions = cls.regions_with_offering(instance_type, accelerators,
|
|
131
|
+
use_spot, region, None)
|
|
132
|
+
for r in regions:
|
|
133
|
+
assert r.zones is None, r
|
|
134
|
+
yield r.zones
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def get_vcpus_mem_from_instance_type(
|
|
138
|
+
cls,
|
|
139
|
+
instance_type: str,
|
|
140
|
+
) -> Tuple[Optional[float], Optional[float]]:
|
|
141
|
+
"""Get vCPUs and memory from instance type."""
|
|
142
|
+
return catalog.get_vcpus_mem_from_instance_type(instance_type,
|
|
143
|
+
clouds='shadeform')
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def get_accelerators_from_instance_type(
|
|
147
|
+
cls,
|
|
148
|
+
instance_type: str,
|
|
149
|
+
) -> Optional[Dict[str, Union[int, float]]]:
|
|
150
|
+
"""Get accelerator information from instance type."""
|
|
151
|
+
return catalog.get_accelerators_from_instance_type(instance_type,
|
|
152
|
+
clouds='shadeform')
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def get_default_instance_type(
|
|
156
|
+
cls,
|
|
157
|
+
cpus: Optional[str] = None,
|
|
158
|
+
memory: Optional[str] = None,
|
|
159
|
+
disk_tier: Optional[resources_utils.DiskTier] = None,
|
|
160
|
+
region: Optional[str] = None,
|
|
161
|
+
zone: Optional[str] = None,
|
|
162
|
+
) -> Optional[str]:
|
|
163
|
+
"""Get default instance type."""
|
|
164
|
+
del disk_tier # Not supported
|
|
165
|
+
return catalog.get_default_instance_type(cpus=cpus,
|
|
166
|
+
memory=memory,
|
|
167
|
+
disk_tier=None,
|
|
168
|
+
region=region,
|
|
169
|
+
zone=zone,
|
|
170
|
+
clouds='shadeform')
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def get_zone_shell_cmd(cls) -> Optional[str]:
|
|
174
|
+
"""Return shell command to get the zone of the instance."""
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def get_user_identities(cls) -> Optional[List[List[str]]]:
|
|
179
|
+
"""Get user identities for Shadeform."""
|
|
180
|
+
# No user identity support needed
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def instance_type_exists(self, instance_type: str) -> bool:
|
|
184
|
+
return catalog.instance_type_exists(instance_type, 'shadeform')
|
|
185
|
+
|
|
186
|
+
def instance_type_to_hourly_cost(self,
|
|
187
|
+
instance_type: str,
|
|
188
|
+
use_spot: bool,
|
|
189
|
+
region: Optional[str] = None,
|
|
190
|
+
zone: Optional[str] = None) -> float:
|
|
191
|
+
"""Get hourly cost for instance type."""
|
|
192
|
+
if use_spot:
|
|
193
|
+
raise ValueError('Spot instances are not supported on Shadeform')
|
|
194
|
+
return catalog.get_hourly_cost(instance_type,
|
|
195
|
+
use_spot=use_spot,
|
|
196
|
+
region=region,
|
|
197
|
+
zone=zone,
|
|
198
|
+
clouds='shadeform')
|
|
199
|
+
|
|
200
|
+
def accelerators_to_hourly_cost(self,
|
|
201
|
+
accelerators: Dict[str, int],
|
|
202
|
+
use_spot: bool,
|
|
203
|
+
region: Optional[str] = None,
|
|
204
|
+
zone: Optional[str] = None) -> float:
|
|
205
|
+
"""Get hourly cost for accelerators."""
|
|
206
|
+
return 0.0
|
|
207
|
+
|
|
208
|
+
def get_egress_cost(self, num_gigabytes: float) -> float:
|
|
209
|
+
"""Get egress cost."""
|
|
210
|
+
# No explicit egress pricing from Shadeform API
|
|
211
|
+
return 0.0
|
|
212
|
+
|
|
213
|
+
def __repr__(self):
|
|
214
|
+
return 'Shadeform'
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def get_current_user_identity(cls) -> Optional[str]:
|
|
218
|
+
"""Get current user identity."""
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
def make_deploy_resources_variables(
|
|
222
|
+
self,
|
|
223
|
+
resources: 'resources_lib.Resources',
|
|
224
|
+
cluster_name: resources_utils.ClusterName,
|
|
225
|
+
region: 'clouds.Region',
|
|
226
|
+
zones: Optional[List['clouds.Zone']],
|
|
227
|
+
num_nodes: int,
|
|
228
|
+
dryrun: bool = False,
|
|
229
|
+
volume_mounts: Optional[List['volume_lib.VolumeMount']] = None,
|
|
230
|
+
) -> Dict[str, Any]:
|
|
231
|
+
"""Make variables for deployment template."""
|
|
232
|
+
del zones, num_nodes, dryrun, volume_mounts # unused for Shadeform
|
|
233
|
+
|
|
234
|
+
# Get instance type
|
|
235
|
+
r = resources.copy(accelerators=None)
|
|
236
|
+
feasible_resources = self._get_feasible_launchable_resources(r)
|
|
237
|
+
instance_type = feasible_resources.resources_list[0].instance_type
|
|
238
|
+
|
|
239
|
+
resources_vars = {}
|
|
240
|
+
if instance_type is not None:
|
|
241
|
+
instance_type_split = instance_type.split('_')
|
|
242
|
+
cloud = instance_type_split[0]
|
|
243
|
+
resources_vars.update({
|
|
244
|
+
'instance_type': instance_type,
|
|
245
|
+
'region': region.name,
|
|
246
|
+
'cloud': cloud,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
# Add accelerator resources for Ray
|
|
250
|
+
accelerators = resources.accelerators
|
|
251
|
+
if accelerators is not None:
|
|
252
|
+
resources_vars['custom_resources'] = json.dumps(accelerators,
|
|
253
|
+
separators=(',',
|
|
254
|
+
':'))
|
|
255
|
+
|
|
256
|
+
return resources_vars
|
|
257
|
+
|
|
258
|
+
def get_credential_file_mounts(self) -> Dict[str, str]:
|
|
259
|
+
"""Get credential files that need to be mounted."""
|
|
260
|
+
return {
|
|
261
|
+
f'~/.shadeform/{f}': f'~/.shadeform/{f}' for f in _CREDENTIAL_FILES
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@classmethod
|
|
265
|
+
def get_current_user_identity_str(cls) -> Optional[str]:
|
|
266
|
+
"""Get current user identity string."""
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def check_credentials(
|
|
271
|
+
cls, cloud_capability: clouds.CloudCapability
|
|
272
|
+
) -> Tuple[bool, Optional[Union[str, Dict[str, str]]]]:
|
|
273
|
+
"""Check if Shadeform credentials are properly configured."""
|
|
274
|
+
del cloud_capability # unused for Shadeform
|
|
275
|
+
try:
|
|
276
|
+
api_key_path = os.path.expanduser('~/.shadeform/api_key')
|
|
277
|
+
if not os.path.exists(api_key_path):
|
|
278
|
+
return False, (f'Shadeform API key not found. '
|
|
279
|
+
f'Please save your API key to {api_key_path}')
|
|
280
|
+
|
|
281
|
+
# Try to read the API key
|
|
282
|
+
with open(api_key_path, 'r', encoding='utf-8') as f:
|
|
283
|
+
api_key = f.read().strip()
|
|
284
|
+
|
|
285
|
+
if not api_key:
|
|
286
|
+
return False, f'Shadeform API key is empty in {api_key_path}'
|
|
287
|
+
|
|
288
|
+
return True, None
|
|
289
|
+
|
|
290
|
+
except (OSError, IOError) as e:
|
|
291
|
+
return False, f'Error checking Shadeform credentials: {str(e)}'
|
|
292
|
+
|
|
293
|
+
def _get_feasible_launchable_resources(
|
|
294
|
+
self, resources: 'resources_lib.Resources'
|
|
295
|
+
) -> 'resources_utils.FeasibleResources':
|
|
296
|
+
"""Get feasible launchable resources."""
|
|
297
|
+
if resources.use_spot:
|
|
298
|
+
return resources_utils.FeasibleResources(
|
|
299
|
+
[], [], 'Spot instances are not supported on Shadeform.')
|
|
300
|
+
|
|
301
|
+
if resources.instance_type is not None:
|
|
302
|
+
# Instance type is already specified, validate it
|
|
303
|
+
assert resources.is_launchable(), resources
|
|
304
|
+
fuzzy_candidate_list = [resources.instance_type]
|
|
305
|
+
return resources_utils.FeasibleResources([resources],
|
|
306
|
+
fuzzy_candidate_list, None)
|
|
307
|
+
|
|
308
|
+
# Map accelerators to instance types
|
|
309
|
+
def _make_resources(instance_type_list):
|
|
310
|
+
resource_list = []
|
|
311
|
+
for instance_type in instance_type_list:
|
|
312
|
+
r = resources.copy(
|
|
313
|
+
cloud=Shadeform(),
|
|
314
|
+
instance_type=instance_type,
|
|
315
|
+
accelerators=resources.
|
|
316
|
+
accelerators, # Keep original accelerators
|
|
317
|
+
cpus=None,
|
|
318
|
+
memory=None,
|
|
319
|
+
)
|
|
320
|
+
resource_list.append(r)
|
|
321
|
+
return resource_list
|
|
322
|
+
|
|
323
|
+
# Handle accelerator requests
|
|
324
|
+
accelerators = resources.accelerators
|
|
325
|
+
if accelerators is not None:
|
|
326
|
+
# Get the first accelerator type and count
|
|
327
|
+
for accelerator_name, accelerator_count in accelerators.items():
|
|
328
|
+
# Get instance types that provide this accelerator
|
|
329
|
+
func = shadeform_catalog.get_instance_type_for_accelerator
|
|
330
|
+
instance_types, errors = func(accelerator_name,
|
|
331
|
+
accelerator_count,
|
|
332
|
+
use_spot=resources.use_spot)
|
|
333
|
+
|
|
334
|
+
if instance_types:
|
|
335
|
+
# Create separate resource objects for each instance type
|
|
336
|
+
# This is crucial: each resource will only be considered
|
|
337
|
+
# for regions where its specific instance type is available
|
|
338
|
+
all_resources = []
|
|
339
|
+
all_candidate_names = []
|
|
340
|
+
|
|
341
|
+
# Create one resource per instance type
|
|
342
|
+
for instance_type in instance_types:
|
|
343
|
+
resource = resources.copy(
|
|
344
|
+
cloud=Shadeform(),
|
|
345
|
+
instance_type=instance_type,
|
|
346
|
+
accelerators=resources.accelerators,
|
|
347
|
+
cpus=None,
|
|
348
|
+
memory=None,
|
|
349
|
+
)
|
|
350
|
+
all_resources.append(resource)
|
|
351
|
+
all_candidate_names.append(instance_type)
|
|
352
|
+
|
|
353
|
+
return resources_utils.FeasibleResources(
|
|
354
|
+
all_resources, all_candidate_names, None)
|
|
355
|
+
else:
|
|
356
|
+
error_msg = (f'No instances available for accelerator '
|
|
357
|
+
f'{accelerator_name}')
|
|
358
|
+
if errors:
|
|
359
|
+
error_msg += f': {"; ".join(errors)}'
|
|
360
|
+
return resources_utils.FeasibleResources([], [], error_msg)
|
|
361
|
+
|
|
362
|
+
# If accelerator not found in mapping, return error
|
|
363
|
+
return resources_utils.FeasibleResources(
|
|
364
|
+
[], [],
|
|
365
|
+
f'Accelerator {list(accelerators.keys())[0]} not supported.')
|
|
366
|
+
|
|
367
|
+
# No accelerators specified, return a default instance type
|
|
368
|
+
if accelerators is None:
|
|
369
|
+
# Return a default instance type
|
|
370
|
+
default_instance_type = Shadeform.get_default_instance_type(
|
|
371
|
+
cpus=resources.cpus,
|
|
372
|
+
memory=resources.memory,
|
|
373
|
+
disk_tier=resources.disk_tier,
|
|
374
|
+
region=resources.region,
|
|
375
|
+
zone=resources.zone)
|
|
376
|
+
if default_instance_type is None:
|
|
377
|
+
# TODO: Add hints to all return values in this method to help
|
|
378
|
+
# users understand why the resources are not launchable.
|
|
379
|
+
return resources_utils.FeasibleResources([], [], None)
|
|
380
|
+
else:
|
|
381
|
+
return resources_utils.FeasibleResources(
|
|
382
|
+
_make_resources([default_instance_type]), [], None)
|
|
383
|
+
|
|
384
|
+
@classmethod
|
|
385
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
|
386
|
+
"""Check compute credentials."""
|
|
387
|
+
success, msg = cls.check_credentials(clouds.CloudCapability.COMPUTE)
|
|
388
|
+
# Convert return type to match expected signature
|
|
389
|
+
if isinstance(msg, dict):
|
|
390
|
+
msg = str(msg)
|
|
391
|
+
return success, msg
|
|
392
|
+
|
|
393
|
+
@classmethod
|
|
394
|
+
def query_status(cls, name: str, tag_filters: Dict[str, str],
|
|
395
|
+
region: Optional[str], zone: Optional[str],
|
|
396
|
+
**kwargs) -> List[status_lib.ClusterStatus]:
|
|
397
|
+
"""Query cluster status."""
|
|
398
|
+
# For validation purposes, return empty list (no existing clusters)
|
|
399
|
+
# Actual status querying is handled by the provisioner
|
|
400
|
+
return []
|
sky/clouds/ssh.py
CHANGED
|
@@ -44,10 +44,12 @@ class SSH(kubernetes.Kubernetes):
|
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
46
46
|
def _unsupported_features_for_resources(
|
|
47
|
-
cls,
|
|
47
|
+
cls,
|
|
48
|
+
resources: 'resources_lib.Resources',
|
|
49
|
+
region: Optional[str] = None,
|
|
48
50
|
) -> Dict[kubernetes.clouds.CloudImplementationFeatures, str]:
|
|
49
51
|
# Inherit all Kubernetes unsupported features
|
|
50
|
-
return super()._unsupported_features_for_resources(resources)
|
|
52
|
+
return super()._unsupported_features_for_resources(resources, region)
|
|
51
53
|
|
|
52
54
|
@classmethod
|
|
53
55
|
def get_ssh_node_pool_contexts(cls) -> List[str]:
|
sky/clouds/vast.py
CHANGED
|
@@ -55,7 +55,9 @@ class Vast(clouds.Cloud):
|
|
|
55
55
|
|
|
56
56
|
@classmethod
|
|
57
57
|
def _unsupported_features_for_resources(
|
|
58
|
-
cls,
|
|
58
|
+
cls,
|
|
59
|
+
resources: 'resources_lib.Resources',
|
|
60
|
+
region: Optional[str] = None,
|
|
59
61
|
) -> Dict[clouds.CloudImplementationFeatures, str]:
|
|
60
62
|
"""The features not supported based on the resources provided.
|
|
61
63
|
|
|
@@ -74,10 +76,15 @@ class Vast(clouds.Cloud):
|
|
|
74
76
|
return cls._MAX_CLUSTER_NAME_LEN_LIMIT
|
|
75
77
|
|
|
76
78
|
@classmethod
|
|
77
|
-
def regions_with_offering(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
def regions_with_offering(
|
|
80
|
+
cls,
|
|
81
|
+
instance_type: str,
|
|
82
|
+
accelerators: Optional[Dict[str, int]],
|
|
83
|
+
use_spot: bool,
|
|
84
|
+
region: Optional[str],
|
|
85
|
+
zone: Optional[str],
|
|
86
|
+
resources: Optional['resources_lib.Resources'] = None,
|
|
87
|
+
) -> List[clouds.Region]:
|
|
81
88
|
assert zone is None, 'Vast does not support zones.'
|
|
82
89
|
del accelerators, zone # unused
|
|
83
90
|
regions = catalog.get_region_zones_for_instance_type(
|
sky/clouds/vsphere.py
CHANGED
|
@@ -73,7 +73,9 @@ class Vsphere(clouds.Cloud):
|
|
|
73
73
|
|
|
74
74
|
@classmethod
|
|
75
75
|
def _unsupported_features_for_resources(
|
|
76
|
-
cls,
|
|
76
|
+
cls,
|
|
77
|
+
resources: 'resources_lib.Resources',
|
|
78
|
+
region: Optional[str] = None,
|
|
77
79
|
) -> Dict[clouds.CloudImplementationFeatures, str]:
|
|
78
80
|
features = cls._CLOUD_UNSUPPORTED_FEATURES
|
|
79
81
|
return features
|
|
@@ -90,6 +92,7 @@ class Vsphere(clouds.Cloud):
|
|
|
90
92
|
use_spot: bool,
|
|
91
93
|
region: Optional[str],
|
|
92
94
|
zone: Optional[str],
|
|
95
|
+
resources: Optional['resources_lib.Resources'] = None,
|
|
93
96
|
) -> List[clouds.Region]:
|
|
94
97
|
del accelerators, zone # unused
|
|
95
98
|
regions = catalog.get_region_zones_for_instance_type(
|
sky/core.py
CHANGED
|
@@ -25,6 +25,7 @@ from sky.jobs.server import core as managed_jobs_core
|
|
|
25
25
|
from sky.provision.kubernetes import constants as kubernetes_constants
|
|
26
26
|
from sky.provision.kubernetes import utils as kubernetes_utils
|
|
27
27
|
from sky.schemas.api import responses
|
|
28
|
+
from sky.server.requests import request_names
|
|
28
29
|
from sky.skylet import autostop_lib
|
|
29
30
|
from sky.skylet import constants
|
|
30
31
|
from sky.skylet import job_lib
|
|
@@ -84,7 +85,9 @@ def optimize(
|
|
|
84
85
|
# but we do not apply the admin policy there. We should apply the admin
|
|
85
86
|
# policy in the optimizer, but that will require some refactoring.
|
|
86
87
|
with admin_policy_utils.apply_and_use_config_in_current_request(
|
|
87
|
-
dag,
|
|
88
|
+
dag,
|
|
89
|
+
request_name=request_names.AdminPolicyRequestName.OPTIMIZE,
|
|
90
|
+
request_options=request_options) as dag:
|
|
88
91
|
dag.resolve_and_validate_volumes()
|
|
89
92
|
return optimizer.Optimizer.optimize(dag=dag,
|
|
90
93
|
minimize=minimize,
|
|
@@ -99,6 +102,7 @@ def status(
|
|
|
99
102
|
all_users: bool = False,
|
|
100
103
|
include_credentials: bool = False,
|
|
101
104
|
summary_response: bool = False,
|
|
105
|
+
include_handle: bool = True,
|
|
102
106
|
) -> List[responses.StatusResponse]:
|
|
103
107
|
# NOTE(dev): Keep the docstring consistent between the Python API and CLI.
|
|
104
108
|
"""Gets cluster statuses.
|
|
@@ -179,7 +183,8 @@ def status(
|
|
|
179
183
|
cluster_names=cluster_names,
|
|
180
184
|
all_users=all_users,
|
|
181
185
|
include_credentials=include_credentials,
|
|
182
|
-
summary_response=summary_response
|
|
186
|
+
summary_response=summary_response,
|
|
187
|
+
include_handle=include_handle)
|
|
183
188
|
|
|
184
189
|
status_responses = []
|
|
185
190
|
for cluster in clusters:
|
|
@@ -414,22 +419,18 @@ def cost_report(
|
|
|
414
419
|
# compatibility
|
|
415
420
|
num_nodes = record.get('num_nodes', 1)
|
|
416
421
|
try:
|
|
417
|
-
resource_str_simple =
|
|
418
|
-
|
|
422
|
+
resource_str_simple, resource_str_full = (
|
|
423
|
+
resources_utils.format_resource(resources,
|
|
424
|
+
simplified_only=False))
|
|
419
425
|
record['resources_str'] = f'{num_nodes}x{resource_str_simple}'
|
|
420
|
-
|
|
421
|
-
resource_str_full = resources_utils.format_resource(
|
|
422
|
-
resources, simplify=False)
|
|
423
|
-
record[
|
|
424
|
-
'resources_str_full'] = f'{num_nodes}x{resource_str_full}'
|
|
426
|
+
record['resources_str_full'] = f'{num_nodes}x{resource_str_full}'
|
|
425
427
|
except Exception as e: # pylint: disable=broad-except
|
|
426
428
|
logger.debug(f'Failed to get resources_str for cluster '
|
|
427
429
|
f'{record["name"]}: {str(e)}')
|
|
428
430
|
for field in fields:
|
|
429
431
|
record[field] = None
|
|
430
432
|
record['resources_str'] = '-'
|
|
431
|
-
|
|
432
|
-
record['resources_str_full'] = '-'
|
|
433
|
+
record['resources_str_full'] = '-'
|
|
433
434
|
|
|
434
435
|
for report in cluster_reports:
|
|
435
436
|
_update_record_with_resources(report)
|
sky/dashboard/out/404.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/
|
|
1
|
+
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/0748ce22df867032.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/0748ce22df867032.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-2679be77fc08a2f8.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-cf60a09ccd051a10.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-f15ccb73239a3bf1.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-bde01e4a2beec258.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js" defer=""></script><script src="/dashboard/_next/static/zB0ed6ge_W1MDszVHhijS/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/zB0ed6ge_W1MDszVHhijS/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"zB0ed6ge_W1MDszVHhijS","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1141],{99333:function(e,s,t){t.d(s,{Z:function(){return a}});/**
|
|
2
|
+
* @license lucide-react v0.407.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/let a=(0,t(60998).Z)("Save",[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",key:"1c8476"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7",key:"1ydtos"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7",key:"t51u73"}]])},98418:function(e,s,t){t.d(s,{Z:function(){return a}});/**
|
|
7
|
+
* @license lucide-react v0.407.0 - ISC
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the ISC license.
|
|
10
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/let a=(0,t(60998).Z)("Trash",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}]])},1812:function(e,s,t){t.d(s,{X:function(){return n}});var a=t(85893),r=t(67294);let l=e=>{if(!(null==e?void 0:e.message))return"An unexpected error occurred.";let s=e.message;return s.includes("failed:")&&(s=s.split("failed:")[1].trim()),s},n=e=>{let{error:s,title:t="Error",onDismiss:n}=e,[c,i]=(0,r.useState)(!1);if((0,r.useEffect)(()=>{s&&i(!1)},[s]),!s||c)return null;let o="string"==typeof s?s:l(s);return(0,a.jsx)("div",{className:"bg-red-50 border border-red-200 rounded-md p-3 mb-4",children:(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsxs)("div",{className:"flex",children:[(0,a.jsx)("div",{className:"flex-shrink-0",children:(0,a.jsx)("svg",{className:"h-5 w-5 text-red-400",viewBox:"0 0 20 20",fill:"currentColor",children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z",clipRule:"evenodd"})})}),(0,a.jsx)("div",{className:"ml-3",children:(0,a.jsxs)("div",{className:"text-sm text-red-800",children:[(0,a.jsxs)("strong",{children:[t,":"]})," ",o]})})]}),(0,a.jsx)("button",{onClick:()=>{i(!0),n&&n()},className:"flex-shrink-0 ml-4 text-red-400 hover:text-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:ring-offset-red-50 rounded","aria-label":"Dismiss error",children:(0,a.jsx)("svg",{className:"h-4 w-4",viewBox:"0 0 20 20",fill:"currentColor",children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z",clipRule:"evenodd"})})})]})})}},69123:function(e,s,t){t.d(s,{g:function(){return n}});var a=t(85893),r=t(67294),l=t(32350);let n=r.forwardRef((e,s)=>{let{className:t,...r}=e;return(0,a.jsx)("textarea",{className:(0,l.cn)("flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",t),ref:s,...r})});n.displayName="Textarea"},11141:function(e,s,t){t.r(s),t.d(s,{WorkspaceEditor:function(){return _}});var a=t(85893),r=t(67294),l=t(11163),n=t(17324),c=t(23266),i=t(68969);t(9353);var o=t(41664),d=t.n(o),u=t(9008),x=t.n(u),m=t(37673),h=t(30803),f=t(69123),g=t(55739),p=t(70282),b=t(6021),j=t(13626),y=t(98418),N=t(99333),w=t(50326);let k=e=>{let{className:s="",variant:t="default",children:r,...l}=e;return(0,a.jsx)("div",{role:"alert",className:"".concat("relative w-full rounded-lg border p-4 flex items-start space-x-2"," ").concat({default:"bg-blue-50 border-blue-200 text-blue-800",destructive:"bg-red-50 border-red-200 text-red-800"}[t]," ").concat(s),...l,children:r})},v=e=>{let{className:s="",children:t,...r}=e;return(0,a.jsx)("div",{className:"text-sm leading-relaxed ".concat(s),...r,children:t})};var C=t(53850),L=t(1812),E=t(47615),S=t(1272),W=t(93225),A=t(53081),M=t(6378);let Z=e=>{let{message:s}=e;return s?(0,a.jsxs)(k,{className:"border-green-200 bg-green-50",children:[(0,a.jsx)(p.Z,{className:"h-4 w-4 text-green-600"}),(0,a.jsx)(v,{className:"text-green-800",children:s})]}):null},D=e=>{let{workspaceName:s,config:t,enabledClouds:r=[]}=e;if(!t)return null;let l="default"===s,n=0===Object.keys(t).length;if(l&&n)return(0,a.jsx)("div",{className:"text-sm text-gray-500 mb-3 italic p-3 bg-sky-50 rounded border border-sky-200",children:"Workspace 'default' can use all accessible infrastructure."});let c=[],i=[],o=[],d=new Set(r.map(e=>e.toLowerCase()));Object.entries(t).forEach(e=>{let[s,t]=e;if("private"===s||"allowed_users"===s)return;let r=W.Z2[s.toLowerCase()]||s.toUpperCase(),l=null==r?void 0:r.toLowerCase(),n=d.has(l)||Array.from(d).some(e=>e.startsWith(l+"/")),u=()=>"kubernetes"===s.toLowerCase()?Array.from(d).filter(e=>e.startsWith(l+"/")).map(e=>e.split("/")[1]):[];if((null==t?void 0:t.disabled)===!0)i.push(r);else if(t&&Object.keys(t).length>0){let e="";if("gcp"===s.toLowerCase()&&t.project_id)e=" (Project ID: ".concat(t.project_id,")");else if("aws"===s.toLowerCase()&&t.region)e=" (Region: ".concat(t.region,")");else if("kubernetes"===s.toLowerCase()){let s=u();s.length>0&&(e=" (Contexts: ".concat(s.join(", "),")"))}n?c.push((0,a.jsxs)("span",{className:"block",children:[r,e," is enabled."]},"".concat(s,"-enabled"))):o.push((0,a.jsxs)("span",{className:"block text-amber-700",children:[r,e," is configured but not currently available."]},"".concat(s,"-configured-not-enabled")))}else if(n){let e="";if("kubernetes"===s.toLowerCase()){let s=u();s.length>0&&(e=" (Contexts: ".concat(s.join(", "),")"))}c.push((0,a.jsxs)("span",{className:"block",children:[r,e," is enabled (using default settings)."]},"".concat(s,"-default-enabled")))}else o.push((0,a.jsxs)("span",{className:"block text-amber-700",children:[r," is configured but not currently available."]},"".concat(s,"-default-not-enabled")))});let u=[];if(i.length>0){let e=i.join(" and ");u.push((0,a.jsxs)("span",{className:"block",children:[e," ",1===i.length?"is":"are"," explicitly disabled."]},"disabled-clouds"))}return(u.push(...c),u.push(...o),u.length>0)?(0,a.jsx)("div",{className:"text-sm text-gray-700 mb-3 p-3 bg-sky-50 rounded border border-sky-200",children:u}):!l&&n?(0,a.jsx)("div",{className:"text-sm text-gray-500 mb-3 italic p-3 bg-sky-50 rounded border border-sky-200",children:"This workspace has no specific cloud resource configurations and can use all accessible infrastructure."}):null},R=e=>{let{isPrivate:s}=e;return s?(0,a.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-700 border border-gray-300",children:"Private"}):(0,a.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-700 border border-green-300",children:"Public"})},P=e=>{let{workspaceConfig:s,allUsers:t}=e;if(!s.private)return null;let r=s.allowed_users||[],l=(t||[]).filter(e=>"admin"===e.role).map(e=>e.username),n=[...new Set([...r,...l])];return 0===n.length?(0,a.jsxs)("div",{className:"mt-4",children:[(0,a.jsx)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:"Allowed Users (0)"}),(0,a.jsx)("div",{className:"text-amber-600 text-xs italic p-2 bg-amber-50 rounded border border-amber-200",children:"No users configured (workspace may be inaccessible)"})]}):(0,a.jsxs)("div",{className:"mt-4",children:[(0,a.jsxs)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:["Allowed Users (",n.length,")"]}),(0,a.jsx)("div",{className:"space-y-1 max-h-48 overflow-y-auto border border-gray-200 rounded",children:n.map(e=>{let s=l.includes(e);return(0,a.jsxs)("div",{className:"flex items-center justify-between text-xs p-2 bg-gray-50 hover:bg-gray-100 border-b border-gray-100 last:border-b-0",children:[(0,a.jsx)("span",{className:"font-medium text-gray-700",children:e}),s?(0,a.jsxs)("span",{className:"inline-flex items-center text-blue-600",children:[(0,a.jsx)(C.r7,{className:"w-3 h-3 mr-1"}),"Admin"]}):(0,a.jsxs)("span",{className:"inline-flex items-center text-gray-600",children:[(0,a.jsx)(b.Z,{className:"w-3 h-3 mr-1"}),"User"]})]},e)})})]})};function _(e){let{workspaceName:s,isNewWorkspace:t=!1}=e,o=(0,l.useRouter)(),[u,p]=(0,r.useState)({}),[b,k]=(0,r.useState)({}),[v,W]=(0,r.useState)(""),[_,z]=(0,r.useState)(!0),[O,U]=(0,r.useState)(!1),[F,T]=(0,r.useState)(!1),[Y,I]=(0,r.useState)(null),[J,V]=(0,r.useState)(null),[H,B]=(0,r.useState)(null),[X,G]=(0,r.useState)([]),[q,K]=(0,r.useState)({showDialog:!1,deleting:!1,error:null}),[Q,$]=(0,r.useState)({totalClusterCount:0,runningClusterCount:0,managedJobsCount:0,clouds:[]}),[ee,es]=(0,r.useState)(!1),et=(0,r.useCallback)(async()=>{z(!0),I(null);try{let e;let[t,a]=await Promise.all([(0,n.getWorkspaces)(),(0,A.R)()]),r=t[s]||{};p(r),k(r),G(a||[]),e=0===Object.keys(r).length?"".concat(s,":\n # Empty workspace configuration - uses all accessible infrastructure\n"):S.ZP.dump({[s]:r},{indent:2,lineWidth:-1,noRefs:!0,skipInvalid:!0,flowLevel:-1}),W(e)}catch(e){console.error("Error fetching workspace config:",e),I(e)}finally{z(!1)}},[s]),ea=(0,r.useCallback)(async()=>{if(!t){es(!0);try{let[e,t,a]=await Promise.all([M.dashboardCache.get(c.getClusters),M.dashboardCache.get(i.getManagedJobs,[{allUsers:!0,skipFinished:!0,workspaceMatch:s,fields:["workspace","status"]}]),M.dashboardCache.get(n.getEnabledClouds,[s,!0])]),r=e.filter(e=>(e.workspace||"default")===s),l=r.filter(e=>"RUNNING"===e.status||"LAUNCHING"===e.status),o={};e.forEach(e=>{o[e.cluster]=e.workspace||"default"});let d=t.jobs||[],u=new Set(E.statusGroups.active),x=0;d.forEach(e=>{e.workspace===s&&u.has(e.status)&&x++}),$({totalClusterCount:r.length,runningClusterCount:l.length,managedJobsCount:x,clouds:Array.isArray(a)?a:[]})}catch(e){console.error("Failed to fetch workspace stats:",e)}finally{es(!1)}}},[s,t]);(0,r.useEffect)(()=>{t?(z(!1),W("".concat(s,":\n # New workspace configuration\n # Leave empty to use all accessible infrastructure\n"))):(et(),ea())},[s,t,et,ea]),(0,r.useEffect)(()=>{T(JSON.stringify(u)!==JSON.stringify(b))},[u,b]);let er=e=>{W(e),B(null);try{let t=S.ZP.load(e)||{},a=Object.keys(t);if(0===a.length)p({});else if(1===a.length){let e=a[0];if(e!==s){B('Workspace name cannot be changed. Expected "'.concat(s,'" but found "').concat(e,'".'));return}let r=t[s]||{};p(r)}else B("Configuration must contain only one workspace. Found: ".concat(a.join(", ")))}catch(e){B("Invalid YAML: ".concat(e.message))}},el=async()=>{U(!0),I(null),V(null);try{if(H)throw Error("Please fix YAML errors before saving");let e=S.ZP.load(v)||{},a=Object.keys(e);if(a.length>0&&a[0]!==s)throw Error('Workspace name cannot be changed. Expected "'.concat(s,'".'));t?(await (0,n.MB)(s,u),V("Workspace created successfully!"),setTimeout(()=>{o.push("/workspaces/".concat(s))},1500)):(await (0,n.eA)(s,u),V("Workspace updated successfully!"),k(u),ea())}catch(e){console.error("Error saving workspace:",e),I(e)}finally{U(!1)}},en=async()=>{K(e=>({...e,deleting:!0,error:null}));try{await (0,n.zl)(s),V("Workspace deleted successfully!"),setTimeout(()=>{o.push("/workspaces")},1500)}catch(e){console.error("Error deleting workspace:",e),K(s=>({...s,deleting:!1,error:e}))}},ec=()=>{K({showDialog:!1,deleting:!1,error:null})},ei=async()=>{await Promise.all([et(),ea()])};if(!o.isReady)return(0,a.jsx)("div",{children:"Loading..."});let eo=t?"Create New Workspace | SkyPilot Dashboard":"Workspace: ".concat(s," | SkyPilot Dashboard");return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(x(),{children:(0,a.jsx)("title",{children:eo})}),(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("div",{className:"flex items-center justify-between mb-4 h-5",children:[(0,a.jsxs)("div",{className:"text-base flex items-center",children:[(0,a.jsx)(d(),{href:"/workspaces",className:"text-sky-blue hover:underline",children:"Workspaces"}),(0,a.jsx)("span",{className:"mx-2 text-gray-500",children:"›"}),(0,a.jsx)(d(),{href:t?"/workspace/new":"/workspaces/".concat(s),className:"text-sky-blue hover:underline",children:t?"New Workspace":s}),F&&(0,a.jsx)("span",{className:"ml-3 px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded",children:"Unsaved changes"})]}),(0,a.jsxs)("div",{className:"text-sm flex items-center",children:[(_||O||ee)&&(0,a.jsxs)("div",{className:"flex items-center mr-4",children:[(0,a.jsx)(g.Z,{size:15,className:"mt-0"}),(0,a.jsx)("span",{className:"ml-2 text-gray-500",children:O?"Saving...":"Loading..."})]}),(0,a.jsxs)("div",{className:"flex items-center space-x-4",children:[!t&&(0,a.jsxs)("button",{onClick:ei,disabled:_||O||ee,className:"text-sky-blue hover:text-sky-blue-bright font-medium inline-flex items-center",children:[(0,a.jsx)(j.Z,{className:"w-4 h-4 mr-1.5"}),"Refresh"]}),!t&&"default"!==s&&(0,a.jsxs)("button",{onClick:()=>K({...q,showDialog:!0}),disabled:q.deleting||O,className:"text-red-600 hover:text-red-700 font-medium inline-flex items-center",children:[(0,a.jsx)(y.Z,{className:"w-4 h-4 mr-1.5"}),"Delete"]})]})]})]}),_?(0,a.jsxs)("div",{className:"flex justify-center items-center py-12",children:[(0,a.jsx)(g.Z,{size:24,className:"mr-2"}),(0,a.jsx)("span",{className:"text-gray-500",children:"Loading workspace configuration..."})]}):(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsx)(L.X,{error:Y,title:"Error",onDismiss:()=>I(null)}),(0,a.jsx)(Z,{message:J}),(0,a.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-6",children:[!t&&(0,a.jsx)("div",{className:"lg:col-span-1",children:(0,a.jsxs)(m.Zb,{className:"h-full",children:[(0,a.jsx)(m.Ol,{children:(0,a.jsx)(m.ll,{className:"text-base font-normal",children:(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("span",{className:"font-semibold",children:"Workspace:"})," ",s]}),(0,a.jsx)(R,{isPrivate:!0===b.private})]})})}),(0,a.jsxs)(m.aY,{className:"text-sm pb-2 flex-1",children:[(0,a.jsxs)("div",{className:"py-2 flex items-center justify-between",children:[(0,a.jsxs)("div",{className:"flex items-center text-gray-600",children:[(0,a.jsx)(C.QT,{className:"w-4 h-4 mr-2 text-gray-500"}),(0,a.jsx)("span",{children:"Clusters (Running / Total)"})]}),(0,a.jsx)("span",{className:"font-normal text-gray-800",children:ee?"...":"".concat(Q.runningClusterCount," / ").concat(Q.totalClusterCount)})]}),(0,a.jsxs)("div",{className:"py-2 flex items-center justify-between border-t border-gray-100",children:[(0,a.jsxs)("div",{className:"flex items-center text-gray-600",children:[(0,a.jsx)(C.Vp,{className:"w-4 h-4 mr-2 text-gray-500"}),(0,a.jsx)("span",{children:"Managed Jobs"})]}),(0,a.jsx)("span",{className:"font-normal text-gray-800",children:ee?"...":Q.managedJobsCount})]})]}),(0,a.jsxs)("div",{className:"px-6 pb-6 text-sm pt-3",children:[(0,a.jsx)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:"Enabled Infra"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:ee?(0,a.jsx)("span",{className:"text-gray-500",children:"Loading..."}):Q.clouds.length>0?Q.clouds.map(e=>(0,a.jsxs)("div",{className:"flex items-center text-gray-700",children:[(0,a.jsx)(C.Ye,{className:"w-3.5 h-3.5 mr-1.5 text-green-500"}),(0,a.jsx)("span",{children:e})]},e)):(0,a.jsx)("span",{className:"text-gray-500 italic",children:"No enabled infrastructure"})}),(0,a.jsx)("div",{className:"mt-4",children:(0,a.jsx)(D,{workspaceName:s,config:b,enabledClouds:Q.clouds})}),(0,a.jsx)(P,{workspaceConfig:b,allUsers:X})]})]})}),(0,a.jsx)("div",{className:t?"lg:col-span-3":"lg:col-span-2",children:(0,a.jsxs)(m.Zb,{className:"h-full flex flex-col",children:[(0,a.jsx)(m.Ol,{children:(0,a.jsx)(m.ll,{className:"text-base font-normal",children:t?"New Workspace YAML":"Edit Workspace YAML"})}),(0,a.jsx)(m.aY,{className:"flex-1 flex flex-col",children:(0,a.jsxs)("div",{className:"space-y-4 flex-1 flex flex-col",children:[H&&(0,a.jsx)(L.X,{error:H,onDismiss:()=>B(null)}),(0,a.jsxs)("div",{className:"flex-1 flex flex-col",children:[(0,a.jsxs)("p",{className:"text-sm text-gray-600 mb-3",children:["Configure infra-specific settings for this workspace. Leave empty to use all accessible infrastructure. Refer to"," ",(0,a.jsx)("a",{href:"https://docs.skypilot.co/en/latest/admin/workspaces.html#configuration",target:"_blank",rel:"noopener noreferrer",className:"text-blue-600",children:"SkyPilot Docs"})," ","for more details."]}),(0,a.jsxs)("div",{className:"mb-4",children:[(0,a.jsx)("h4",{className:"text-sm font-medium text-gray-700 mb-2",children:"Example configuration:"}),(0,a.jsx)("div",{className:"p-3 bg-gray-50 border rounded-lg",children:(0,a.jsx)("pre",{className:"text-xs font-mono text-gray-600 whitespace-pre-wrap",children:"".concat(s||"my-workspace",":\n private: true\n allowed_users:\n - user1@mydomain.com\n - user2@mydomain.com\n gcp:\n project_id: xxx\n disabled: false\n kubernetes:\n allowed_contexts:\n - context-1")})})]}),(0,a.jsx)(f.g,{value:v,onChange:e=>er(e.target.value),className:"font-mono text-sm flex-1 resize-none",style:{minHeight:"350px"},spellCheck:!1,placeholder:"# Enter workspace configuration in YAML format"}),(0,a.jsx)("div",{className:"flex justify-end space-x-3 pt-3 border-gray-200",children:(0,a.jsxs)(h.z,{onClick:el,disabled:O||H||_,className:"inline-flex items-center bg-sky-600 hover:bg-sky-700 text-white",children:[(0,a.jsx)(N.Z,{className:"w-4 h-4 mr-1.5"}),O?"Applying...":"Apply"]})})]})]})})]})})]})]}),(0,a.jsx)(w.Vq,{open:q.showDialog,onOpenChange:ec,children:(0,a.jsxs)(w.cZ,{className:"sm:max-w-md",children:[(0,a.jsxs)(w.fK,{className:"",children:[(0,a.jsx)(w.$N,{children:"Delete Workspace"}),(0,a.jsxs)(w.Be,{children:['Are you sure you want to delete workspace "',s,'"? This action cannot be undone.']})]}),q.error&&(0,a.jsx)(L.X,{error:q.error,title:"Deletion Failed",onDismiss:()=>K(e=>({...e,error:null}))}),(0,a.jsxs)(w.cN,{className:"",children:[(0,a.jsx)(h.z,{variant:"outline",onClick:ec,disabled:q.deleting,children:"Cancel"}),(0,a.jsx)(h.z,{variant:"destructive",onClick:en,disabled:q.deleting,children:q.deleting?"Deleting...":"Delete"})]})]})})]})]})}},53081:function(e,s,t){t.d(s,{R:function(){return r}});var a=t(47145);async function r(){try{let e=await a.x.get("/users");if(!e.ok)throw Error("Failed to fetch users with status ".concat(e.status));return(await e.json()).map(e=>({userId:e.id,username:e.name,role:e.role,created_at:e.created_at}))||[]}catch(e){throw console.error("Failed to fetch users:",e),e}}}}]);
|