skypilot-nightly 1.0.0.dev20250523__py3-none-any.whl → 1.0.0.dev20250524__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 +2 -2
- sky/backends/backend_utils.py +62 -45
- sky/backends/cloud_vm_ray_backend.py +3 -1
- sky/check.py +332 -170
- sky/cli.py +44 -11
- sky/client/cli.py +44 -11
- sky/client/sdk.py +54 -10
- sky/clouds/gcp.py +19 -3
- sky/core.py +5 -2
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/aHej19bZyl4hoHgrzPCn7/_buildManifest.js +1 -0
- sky/dashboard/out/_next/static/chunks/480-ee58038f1a4afd5c.js +1 -0
- sky/dashboard/out/_next/static/chunks/488-50d843fdb5396d32.js +15 -0
- sky/dashboard/out/_next/static/chunks/498-d7722313e5e5b4e6.js +21 -0
- sky/dashboard/out/_next/static/chunks/573-f17bd89d9f9118b3.js +66 -0
- sky/dashboard/out/_next/static/chunks/578-7a4795009a56430c.js +6 -0
- sky/dashboard/out/_next/static/chunks/734-5f5ce8f347b7f417.js +1 -0
- sky/dashboard/out/_next/static/chunks/937.f97f83652028e944.js +1 -0
- sky/dashboard/out/_next/static/chunks/938-f347f6144075b0c8.js +1 -0
- sky/dashboard/out/_next/static/chunks/9f96d65d-5a3e4af68c26849e.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/_app-dec800f9ef1b10f4.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-37c042a356f8e608.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-9529d9e882a0e75c.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters-9e6d1ec6e1ac5b29.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/infra-e690d864aa00e2ea.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-db6558a5ec687011.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs-73d5e0c369d00346.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/users-2d319455c3f1c3e2.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/workspaces-02a7b60f2ead275f.js +1 -0
- sky/dashboard/out/_next/static/chunks/webpack-deda68c926e8d0bc.js +1 -0
- sky/dashboard/out/_next/static/css/d2cdba64c9202dd7.css +3 -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/index.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 -0
- sky/dashboard/out/workspaces.html +1 -0
- sky/data/storage.py +1 -1
- sky/global_user_state.py +42 -19
- sky/jobs/constants.py +1 -1
- sky/jobs/server/core.py +72 -56
- sky/jobs/state.py +26 -5
- sky/jobs/utils.py +65 -13
- sky/optimizer.py +6 -3
- sky/provision/fluidstack/instance.py +1 -0
- sky/serve/server/core.py +9 -6
- sky/server/html/token_page.html +6 -1
- sky/server/requests/executor.py +1 -0
- sky/server/requests/payloads.py +11 -0
- sky/server/server.py +68 -5
- sky/skylet/constants.py +4 -1
- sky/skypilot_config.py +83 -9
- sky/utils/cli_utils/status_utils.py +18 -8
- sky/utils/kubernetes/deploy_remote_cluster.py +150 -147
- sky/utils/log_utils.py +4 -0
- sky/utils/schemas.py +54 -0
- {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250524.dist-info}/METADATA +1 -1
- {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250524.dist-info}/RECORD +66 -59
- sky/dashboard/out/_next/static/ECKwDNS9v9y3_IKFZ2lpp/_buildManifest.js +0 -1
- sky/dashboard/out/_next/static/chunks/236-1a3a9440417720eb.js +0 -6
- sky/dashboard/out/_next/static/chunks/312-c3c8845990db8ffc.js +0 -15
- sky/dashboard/out/_next/static/chunks/37-d584022b0da4ac3b.js +0 -6
- sky/dashboard/out/_next/static/chunks/393-e1eaa440481337ec.js +0 -1
- sky/dashboard/out/_next/static/chunks/480-f28cd152a98997de.js +0 -1
- sky/dashboard/out/_next/static/chunks/582-683f4f27b81996dc.js +0 -59
- sky/dashboard/out/_next/static/chunks/pages/_app-8cfab319f9fb3ae8.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-33bc2bec322249b1.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-e2fc2dd1955e6c36.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters-3a748bd76e5c2984.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/infra-abf08c4384190a39.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-70756c2dad850a7e.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs-ecd804b9272f4a7c.js +0 -1
- sky/dashboard/out/_next/static/chunks/webpack-830f59b8404e96b8.js +0 -1
- sky/dashboard/out/_next/static/css/7e7ce4ff31d3977b.css +0 -3
- /sky/dashboard/out/_next/static/{ECKwDNS9v9y3_IKFZ2lpp → aHej19bZyl4hoHgrzPCn7}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250524.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250524.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250524.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250524.dist-info}/top_level.txt +0 -0
sky/check.py
CHANGED
@@ -15,6 +15,7 @@ from sky import global_user_state
|
|
15
15
|
from sky import skypilot_config
|
16
16
|
from sky.adaptors import cloudflare
|
17
17
|
from sky.clouds import cloud as sky_cloud
|
18
|
+
from sky.skylet import constants
|
18
19
|
from sky.utils import registry
|
19
20
|
from sky.utils import rich_utils
|
20
21
|
from sky.utils import subprocess_utils
|
@@ -29,178 +30,250 @@ def check_capabilities(
|
|
29
30
|
verbose: bool = False,
|
30
31
|
clouds: Optional[Iterable[str]] = None,
|
31
32
|
capabilities: Optional[List[sky_cloud.CloudCapability]] = None,
|
32
|
-
|
33
|
+
workspace: Optional[str] = None,
|
34
|
+
) -> Dict[str, Dict[str, List[sky_cloud.CloudCapability]]]:
|
33
35
|
echo = (lambda *_args, **_kwargs: None
|
34
36
|
) if quiet else lambda *args, **kwargs: click.echo(
|
35
37
|
*args, **kwargs, color=True)
|
36
|
-
|
38
|
+
all_workspaces_results: Dict[str,
|
39
|
+
Dict[str,
|
40
|
+
List[sky_cloud.CloudCapability]]] = {}
|
41
|
+
available_workspaces = list(skypilot_config.get_workspaces().keys())
|
42
|
+
hide_workspace_str = (available_workspaces == [
|
43
|
+
constants.SKYPILOT_DEFAULT_WORKSPACE
|
44
|
+
])
|
45
|
+
initial_hint = 'Checking credentials to enable infra for SkyPilot.'
|
46
|
+
if len(available_workspaces) > 1:
|
47
|
+
initial_hint = (f'Checking credentials to enable infra for SkyPilot '
|
48
|
+
f'(Workspaces: {", ".join(available_workspaces)}).')
|
49
|
+
echo(initial_hint)
|
37
50
|
if capabilities is None:
|
38
51
|
capabilities = sky_cloud.ALL_CAPABILITIES
|
39
52
|
assert capabilities is not None
|
40
|
-
enabled_clouds: Dict[str, List[sky_cloud.CloudCapability]] = {}
|
41
|
-
disabled_clouds: Dict[str, List[sky_cloud.CloudCapability]] = {}
|
42
|
-
|
43
|
-
def check_one_cloud_one_capability(
|
44
|
-
payload: Tuple[Tuple[str, Union[sky_clouds.Cloud, ModuleType]],
|
45
|
-
sky_cloud.CloudCapability]
|
46
|
-
) -> Optional[Tuple[sky_cloud.CloudCapability, bool, Optional[Union[
|
47
|
-
str, Dict[str, str]]]]]:
|
48
|
-
cloud_tuple, capability = payload
|
49
|
-
_, cloud = cloud_tuple
|
50
|
-
try:
|
51
|
-
ok, reason = cloud.check_credentials(capability)
|
52
|
-
except exceptions.NotSupportedError:
|
53
|
-
return None
|
54
|
-
except Exception: # pylint: disable=broad-except
|
55
|
-
ok, reason = False, traceback.format_exc()
|
56
|
-
if not isinstance(reason, dict):
|
57
|
-
reason = reason.strip() if reason else None
|
58
|
-
return (capability, ok, reason)
|
59
|
-
|
60
|
-
def get_cloud_tuple(
|
61
|
-
cloud_name: str) -> Tuple[str, Union[sky_clouds.Cloud, ModuleType]]:
|
62
|
-
# Validates cloud_name and returns a tuple of the cloud's name and
|
63
|
-
# the cloud object. Includes special handling for Cloudflare.
|
64
|
-
if cloud_name.lower().startswith('cloudflare'):
|
65
|
-
return cloudflare.NAME, cloudflare
|
66
|
-
else:
|
67
|
-
cloud_obj = registry.CLOUD_REGISTRY.from_str(cloud_name)
|
68
|
-
assert cloud_obj is not None, f'Cloud {cloud_name!r} not found'
|
69
|
-
return repr(cloud_obj), cloud_obj
|
70
53
|
|
71
|
-
def get_all_clouds():
|
54
|
+
def get_all_clouds() -> Tuple[str, ...]:
|
72
55
|
return tuple([repr(c) for c in registry.CLOUD_REGISTRY.values()] +
|
73
56
|
[cloudflare.NAME])
|
74
57
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
58
|
+
def _execute_check_logic_for_workspace(
|
59
|
+
current_workspace_name: str,
|
60
|
+
hide_per_cloud_details: bool,
|
61
|
+
hide_workspace_str: bool,
|
62
|
+
) -> Dict[str, List[sky_cloud.CloudCapability]]:
|
63
|
+
nonlocal echo, verbose, clouds, quiet
|
64
|
+
|
65
|
+
enabled_clouds: Dict[str, List[sky_cloud.CloudCapability]] = {}
|
66
|
+
disabled_clouds: Dict[str, List[sky_cloud.CloudCapability]] = {}
|
67
|
+
|
68
|
+
def check_one_cloud_one_capability(
|
69
|
+
payload: Tuple[Tuple[str, Union[sky_clouds.Cloud, ModuleType]],
|
70
|
+
sky_cloud.CloudCapability]
|
71
|
+
) -> Optional[Tuple[sky_cloud.CloudCapability, bool, Optional[Union[
|
72
|
+
str, Dict[str, str]]]]]:
|
73
|
+
with skypilot_config.local_active_workspace_ctx(
|
74
|
+
current_workspace_name):
|
75
|
+
# Have to override again for specific thread, as the
|
76
|
+
# local_active_workspace_ctx is thread-local.
|
77
|
+
cloud_tuple, capability = payload
|
78
|
+
_, cloud = cloud_tuple
|
79
|
+
try:
|
80
|
+
ok, reason = cloud.check_credentials(capability)
|
81
|
+
except exceptions.NotSupportedError:
|
82
|
+
return None
|
83
|
+
except Exception: # pylint: disable=broad-except
|
84
|
+
ok, reason = False, traceback.format_exc()
|
85
|
+
if not isinstance(reason, dict):
|
86
|
+
reason = reason.strip() if reason else None
|
87
|
+
return (capability, ok, reason)
|
88
|
+
|
89
|
+
def get_cloud_tuple(
|
90
|
+
cloud_name: str
|
91
|
+
) -> Tuple[str, Union[sky_clouds.Cloud, ModuleType]]:
|
92
|
+
# Validates cloud_name and returns a tuple of the cloud's name and
|
93
|
+
# the cloud object. Includes special handling for Cloudflare.
|
94
|
+
if cloud_name.lower().startswith('cloudflare'):
|
95
|
+
return cloudflare.NAME, cloudflare
|
96
|
+
else:
|
97
|
+
cloud_obj = registry.CLOUD_REGISTRY.from_str(cloud_name)
|
98
|
+
assert cloud_obj is not None, f'Cloud {cloud_name!r} not found'
|
99
|
+
return repr(cloud_obj), cloud_obj
|
100
|
+
|
101
|
+
if clouds is not None:
|
102
|
+
cloud_list = clouds
|
119
103
|
else:
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
104
|
+
cloud_list = get_all_clouds()
|
105
|
+
|
106
|
+
clouds_to_check = [get_cloud_tuple(c) for c in cloud_list]
|
107
|
+
|
108
|
+
# Use allowed_clouds from config if it exists, otherwise check all
|
109
|
+
# clouds. Also validate names with get_cloud_tuple.
|
110
|
+
config_allowed_cloud_names = sorted([
|
111
|
+
get_cloud_tuple(c)[0] for c in skypilot_config.get_nested((
|
112
|
+
'allowed_clouds',), get_all_clouds())
|
113
|
+
])
|
114
|
+
|
115
|
+
# filter out the clouds that are disabled in the workspace config
|
116
|
+
workspace_disabled_clouds = []
|
117
|
+
for cloud in config_allowed_cloud_names:
|
118
|
+
cloud_config = skypilot_config.get_workspace_cloud(
|
119
|
+
cloud, workspace=current_workspace_name)
|
120
|
+
cloud_disabled = cloud_config.get('disabled', False)
|
121
|
+
if cloud_disabled:
|
122
|
+
workspace_disabled_clouds.append(cloud)
|
123
|
+
|
124
|
+
config_allowed_cloud_names = [
|
125
|
+
c for c in config_allowed_cloud_names
|
126
|
+
if c not in workspace_disabled_clouds
|
127
|
+
]
|
128
|
+
|
129
|
+
# Use disallowed_cloud_names for logging the clouds that will be
|
130
|
+
# disabled because they are not included in allowed_clouds in
|
131
|
+
# config.yaml.
|
132
|
+
disallowed_cloud_names = [
|
133
|
+
c for c in get_all_clouds() if c not in config_allowed_cloud_names
|
134
|
+
]
|
135
|
+
# Check only the clouds which are allowed in the config.
|
136
|
+
clouds_to_check = [
|
137
|
+
c for c in clouds_to_check if c[0] in config_allowed_cloud_names
|
138
|
+
]
|
139
|
+
|
140
|
+
combinations = list(itertools.product(clouds_to_check, capabilities))
|
141
|
+
|
142
|
+
cloud2ctx2text: Dict[str, Dict[str, str]] = {}
|
143
|
+
if not combinations:
|
144
|
+
echo(
|
145
|
+
_summary_message(enabled_clouds, cloud2ctx2text,
|
146
|
+
current_workspace_name, hide_workspace_str,
|
147
|
+
disallowed_cloud_names))
|
148
|
+
|
149
|
+
return {}
|
150
|
+
|
151
|
+
workspace_str = f' for workspace: {current_workspace_name!r}'
|
152
|
+
if hide_workspace_str:
|
153
|
+
workspace_str = ''
|
154
|
+
with rich_utils.safe_status(
|
155
|
+
ux_utils.spinner_message(
|
156
|
+
f'Checking infra choices{workspace_str}...')):
|
157
|
+
check_results = subprocess_utils.run_in_parallel(
|
158
|
+
check_one_cloud_one_capability, combinations)
|
159
|
+
|
160
|
+
check_results_dict: Dict[
|
161
|
+
Tuple[str, Union[sky_clouds.Cloud, ModuleType]],
|
162
|
+
List[Tuple[sky_cloud.CloudCapability, bool,
|
163
|
+
Optional[Union[str, Dict[str, str]]]]]] = (
|
164
|
+
collections.defaultdict(list))
|
165
|
+
for combination, check_result in zip(combinations, check_results):
|
166
|
+
if check_result is None:
|
167
|
+
continue
|
168
|
+
capability, ok, ctx2text = check_result
|
169
|
+
cloud_tuple, _ = combination
|
170
|
+
cloud_repr = cloud_tuple[0]
|
171
|
+
if isinstance(ctx2text, dict):
|
172
|
+
cloud2ctx2text[cloud_repr] = ctx2text
|
173
|
+
if ok:
|
174
|
+
enabled_clouds.setdefault(cloud_repr, []).append(capability)
|
175
|
+
else:
|
176
|
+
disabled_clouds.setdefault(cloud_repr, []).append(capability)
|
177
|
+
check_results_dict[cloud_tuple].append(check_result)
|
178
|
+
|
179
|
+
if not hide_per_cloud_details:
|
180
|
+
for cloud_tuple, check_result_list in sorted(
|
181
|
+
check_results_dict.items(), key=lambda item: item[0][0]):
|
182
|
+
_print_checked_cloud(echo, verbose, cloud_tuple,
|
183
|
+
check_result_list,
|
184
|
+
cloud2ctx2text.get(cloud_tuple[0], {}))
|
185
|
+
|
186
|
+
# Determine the set of enabled clouds: (previously enabled clouds +
|
187
|
+
# newly enabled clouds - newly disabled clouds) intersected with
|
188
|
+
# config_allowed_clouds, if specified in config.yaml.
|
189
|
+
# This means that if a cloud is already enabled and is not included in
|
190
|
+
# allowed_clouds in config.yaml, it will be disabled.
|
191
|
+
all_enabled_clouds: Set[str] = set()
|
192
|
+
for capability in capabilities:
|
193
|
+
# Cloudflare is not a real cloud in registry.CLOUD_REGISTRY, and
|
194
|
+
# should not be inserted into the DB (otherwise `sky launch` and
|
195
|
+
# other code would error out when it's trying to look it up in the
|
196
|
+
# registry).
|
197
|
+
enabled_clouds_set = {
|
198
|
+
cloud for cloud, capabilities in enabled_clouds.items()
|
199
|
+
if capability in capabilities and
|
200
|
+
not cloud.startswith('Cloudflare')
|
201
|
+
}
|
202
|
+
disabled_clouds_set = {
|
203
|
+
cloud for cloud, capabilities in disabled_clouds.items()
|
204
|
+
if capability in capabilities and
|
205
|
+
not cloud.startswith('Cloudflare')
|
206
|
+
}
|
207
|
+
config_allowed_clouds_set = {
|
208
|
+
cloud for cloud in config_allowed_cloud_names
|
209
|
+
if not cloud.startswith('Cloudflare')
|
210
|
+
}
|
211
|
+
previously_enabled_clouds_set = {
|
212
|
+
repr(cloud)
|
213
|
+
for cloud in global_user_state.get_cached_enabled_clouds(
|
214
|
+
capability, current_workspace_name)
|
215
|
+
}
|
216
|
+
enabled_clouds_for_capability = (config_allowed_clouds_set & (
|
217
|
+
(previously_enabled_clouds_set | enabled_clouds_set) -
|
218
|
+
disabled_clouds_set))
|
219
|
+
|
220
|
+
global_user_state.set_enabled_clouds(
|
221
|
+
list(enabled_clouds_for_capability), capability,
|
222
|
+
current_workspace_name)
|
223
|
+
all_enabled_clouds = all_enabled_clouds.union(
|
224
|
+
enabled_clouds_for_capability)
|
225
|
+
|
168
226
|
echo(
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
227
|
+
_summary_message(enabled_clouds, cloud2ctx2text,
|
228
|
+
current_workspace_name, hide_workspace_str,
|
229
|
+
disallowed_cloud_names))
|
230
|
+
|
231
|
+
return enabled_clouds
|
232
|
+
|
233
|
+
# --- Main check_capabilities logic ---
|
234
|
+
|
235
|
+
if workspace is not None:
|
236
|
+
# Check only the specified workspace
|
237
|
+
if workspace not in available_workspaces:
|
238
|
+
with ux_utils.print_exception_no_traceback():
|
239
|
+
raise ValueError(
|
240
|
+
f'Workspace {workspace!r} not found in SkyPilot '
|
241
|
+
'configuration. '
|
242
|
+
f'Available workspaces: {", ".join(available_workspaces)}')
|
243
|
+
|
244
|
+
# Always show details for single specified check (if verbose)
|
245
|
+
hide_per_cloud_details_flag = False
|
246
|
+
with skypilot_config.local_active_workspace_ctx(workspace):
|
247
|
+
enabled_ws_clouds = _execute_check_logic_for_workspace(
|
248
|
+
workspace, hide_per_cloud_details_flag, hide_workspace_str)
|
249
|
+
all_workspaces_results[workspace] = enabled_ws_clouds
|
177
250
|
else:
|
178
|
-
|
179
|
-
|
251
|
+
# Check all workspaces
|
252
|
+
workspaces_to_check = available_workspaces
|
253
|
+
|
254
|
+
hide_per_cloud_details_flag = (not verbose and
|
255
|
+
len(workspaces_to_check) > 1)
|
256
|
+
|
257
|
+
for ws_name in workspaces_to_check:
|
258
|
+
if not hide_workspace_str:
|
259
|
+
echo(f'\nChecking enabled infra for workspace: {ws_name!r}')
|
260
|
+
with skypilot_config.local_active_workspace_ctx(ws_name):
|
261
|
+
enabled_ws_clouds = _execute_check_logic_for_workspace(
|
262
|
+
ws_name, hide_per_cloud_details_flag, hide_workspace_str)
|
263
|
+
all_workspaces_results[ws_name] = enabled_ws_clouds
|
264
|
+
|
265
|
+
# Global "To enable a cloud..." message, printed once if relevant
|
266
|
+
if not quiet:
|
180
267
|
echo(
|
181
268
|
click.style(
|
182
269
|
'\nTo enable a cloud, follow the hints above and rerun: ',
|
183
|
-
dim=True) + click.style(
|
184
|
-
|
270
|
+
dim=True) + click.style('sky check', bold=True) + '\n' +
|
271
|
+
click.style(
|
185
272
|
'If any problems remain, refer to detailed docs at: '
|
186
273
|
'https://docs.skypilot.co/en/latest/getting-started/installation.html', # pylint: disable=line-too-long
|
187
274
|
dim=True))
|
188
275
|
|
189
|
-
|
190
|
-
echo(click.style(disallowed_clouds_hint, dim=True))
|
191
|
-
|
192
|
-
# Pretty print for UX.
|
193
|
-
if not quiet:
|
194
|
-
enabled_clouds_str = '\n ' + '\n '.join([
|
195
|
-
_format_enabled_cloud(cloud, capabilities,
|
196
|
-
cloud2ctx2text.get(cloud, None))
|
197
|
-
for cloud, capabilities in sorted(enabled_clouds.items(),
|
198
|
-
key=lambda item: item[0])
|
199
|
-
])
|
200
|
-
echo(f'\n{colorama.Fore.GREEN}{PARTY_POPPER_EMOJI} '
|
201
|
-
f'Enabled infra {PARTY_POPPER_EMOJI}'
|
202
|
-
f'{colorama.Style.RESET_ALL}{enabled_clouds_str}')
|
203
|
-
return enabled_clouds
|
276
|
+
return all_workspaces_results
|
204
277
|
|
205
278
|
|
206
279
|
def check_capability(
|
@@ -208,12 +281,15 @@ def check_capability(
|
|
208
281
|
quiet: bool = False,
|
209
282
|
verbose: bool = False,
|
210
283
|
clouds: Optional[Iterable[str]] = None,
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
284
|
+
workspace: Optional[str] = None,
|
285
|
+
) -> Dict[str, List[str]]:
|
286
|
+
clouds_with_capability = collections.defaultdict(list)
|
287
|
+
workspace_enabled_clouds = check_capabilities(quiet, verbose, clouds,
|
288
|
+
[capability], workspace)
|
289
|
+
for workspace, enabled_clouds in workspace_enabled_clouds.items():
|
290
|
+
for cloud, capabilities in enabled_clouds.items():
|
291
|
+
if capability in capabilities:
|
292
|
+
clouds_with_capability[workspace].append(cloud)
|
217
293
|
return clouds_with_capability
|
218
294
|
|
219
295
|
|
@@ -221,10 +297,31 @@ def check(
|
|
221
297
|
quiet: bool = False,
|
222
298
|
verbose: bool = False,
|
223
299
|
clouds: Optional[Iterable[str]] = None,
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
300
|
+
workspace: Optional[str] = None,
|
301
|
+
) -> Dict[str, List[str]]:
|
302
|
+
enabled_clouds_by_workspace: Dict[str,
|
303
|
+
List[str]] = collections.defaultdict(list)
|
304
|
+
capabilities_result = check_capabilities(quiet, verbose, clouds,
|
305
|
+
sky_cloud.ALL_CAPABILITIES,
|
306
|
+
workspace)
|
307
|
+
for ws_name, enabled_clouds_with_capabilities in capabilities_result.items(
|
308
|
+
):
|
309
|
+
# For each workspace, get a list of cloud names that have any
|
310
|
+
# capabilities enabled.
|
311
|
+
# The inner dict enabled_clouds_with_capabilities maps cloud_name to
|
312
|
+
# List[CloudCapability].
|
313
|
+
# If the list of capabilities is non-empty, the cloud is considered
|
314
|
+
# enabled.
|
315
|
+
# We are interested in the keys (cloud names) of this dict if their
|
316
|
+
# value (list of capabilities) is not empty.
|
317
|
+
# However, check_capabilities already ensures that only clouds with
|
318
|
+
# *some* enabled capabilities (from the ones being checked, i.e.
|
319
|
+
# ALL_CAPABILITIES here) are included in its return value.
|
320
|
+
# So, the keys of enabled_clouds_with_capabilities are the enabled cloud
|
321
|
+
# names for that workspace.
|
322
|
+
enabled_clouds_by_workspace[ws_name] = list(
|
323
|
+
enabled_clouds_with_capabilities.keys())
|
324
|
+
return enabled_clouds_by_workspace
|
228
325
|
|
229
326
|
|
230
327
|
def get_cached_enabled_clouds_or_refresh(
|
@@ -243,17 +340,17 @@ def get_cached_enabled_clouds_or_refresh(
|
|
243
340
|
raise_if_no_cloud_access is set to True.
|
244
341
|
"""
|
245
342
|
cached_enabled_clouds = global_user_state.get_cached_enabled_clouds(
|
246
|
-
capability)
|
343
|
+
capability, skypilot_config.get_active_workspace())
|
247
344
|
if not cached_enabled_clouds:
|
248
345
|
try:
|
249
|
-
check_capability(
|
346
|
+
check_capability(capability, quiet=True)
|
250
347
|
except SystemExit:
|
251
348
|
# If no cloud is enabled, check() will raise SystemExit.
|
252
349
|
# Here we catch it and raise the exception later only if
|
253
350
|
# raise_if_no_cloud_access is set to True.
|
254
351
|
pass
|
255
352
|
cached_enabled_clouds = global_user_state.get_cached_enabled_clouds(
|
256
|
-
capability)
|
353
|
+
capability, skypilot_config.get_active_workspace())
|
257
354
|
if raise_if_no_cloud_access and not cached_enabled_clouds:
|
258
355
|
with ux_utils.print_exception_no_traceback():
|
259
356
|
raise exceptions.NoCloudAccessError(
|
@@ -357,10 +454,10 @@ def _print_checked_cloud(
|
|
357
454
|
f'{colorama.Style.RESET_ALL}{detail_string}'))
|
358
455
|
if activated_account is not None:
|
359
456
|
echo(f' Activated account: {activated_account}')
|
360
|
-
for reason,
|
361
|
-
echo(f' Hint [{", ".join(
|
362
|
-
for reason,
|
363
|
-
echo(f' Reason [{", ".join(
|
457
|
+
for reason, capabilities in hints_to_capabilities.items():
|
458
|
+
echo(f' Hint [{", ".join(capabilities)}]: {_yellow_color(reason)}')
|
459
|
+
for reason, capabilities in reasons_to_capabilities.items():
|
460
|
+
echo(f' Reason [{", ".join(capabilities)}]: {reason}')
|
364
461
|
|
365
462
|
|
366
463
|
def _green_color(str_to_format: str) -> str:
|
@@ -450,4 +547,69 @@ def _format_enabled_cloud(cloud_name: str,
|
|
450
547
|
return (f'{title}' + _format_context_details(
|
451
548
|
cloud_name, show_details=False, ctx2text=ctx2text))
|
452
549
|
|
453
|
-
|
550
|
+
if cloud_name == repr(sky_clouds.Kubernetes()):
|
551
|
+
# Get enabled contexts for Kubernetes
|
552
|
+
existing_contexts = sky_clouds.Kubernetes.existing_allowed_contexts()
|
553
|
+
if not existing_contexts:
|
554
|
+
return _green_color(cloud_and_capabilities)
|
555
|
+
|
556
|
+
# Check if allowed_contexts is explicitly set in config
|
557
|
+
allowed_contexts = skypilot_config.get_nested(
|
558
|
+
('kubernetes', 'allowed_contexts'), None)
|
559
|
+
|
560
|
+
# Format the context info with consistent styling
|
561
|
+
if allowed_contexts is not None:
|
562
|
+
contexts_formatted = []
|
563
|
+
for i, context in enumerate(existing_contexts):
|
564
|
+
symbol = (ux_utils.INDENT_LAST_SYMBOL
|
565
|
+
if i == len(existing_contexts) -
|
566
|
+
1 else ux_utils.INDENT_SYMBOL)
|
567
|
+
contexts_formatted.append(f'\n {symbol}{context}')
|
568
|
+
context_info = f' Allowed contexts:{"".join(contexts_formatted)}'
|
569
|
+
else:
|
570
|
+
context_info = f' Active context: {existing_contexts[0]}'
|
571
|
+
|
572
|
+
return (f'{_green_color(cloud_and_capabilities)}\n'
|
573
|
+
f' {colorama.Style.DIM}{context_info}'
|
574
|
+
f'{colorama.Style.RESET_ALL}')
|
575
|
+
return _green_color(cloud_and_capabilities)
|
576
|
+
|
577
|
+
|
578
|
+
def _summary_message(
|
579
|
+
enabled_clouds: Dict[str, List[sky_cloud.CloudCapability]],
|
580
|
+
cloud2ctx2text: Dict[str, Dict[str, str]],
|
581
|
+
current_workspace_name: str,
|
582
|
+
hide_workspace_str: bool,
|
583
|
+
disallowed_cloud_names: List[str],
|
584
|
+
) -> str:
|
585
|
+
if not enabled_clouds:
|
586
|
+
enabled_clouds_str = '\n No infra to check/enabled.'
|
587
|
+
else:
|
588
|
+
enabled_clouds_str = '\n ' + '\n '.join([
|
589
|
+
_format_enabled_cloud(cloud, capabilities,
|
590
|
+
cloud2ctx2text.get(cloud, None))
|
591
|
+
for cloud, capabilities in sorted(enabled_clouds.items(),
|
592
|
+
key=lambda item: item[0])
|
593
|
+
])
|
594
|
+
|
595
|
+
workspace_str = f' for workspace: {current_workspace_name!r}'
|
596
|
+
if hide_workspace_str:
|
597
|
+
workspace_str = ''
|
598
|
+
|
599
|
+
disallowed_clouds_hint = ''
|
600
|
+
if disallowed_cloud_names:
|
601
|
+
disable_for_workspace_hint = (
|
602
|
+
f' or disabled for this workspace {current_workspace_name!r}')
|
603
|
+
if hide_workspace_str:
|
604
|
+
disable_for_workspace_hint = ''
|
605
|
+
disallowed_clouds_hint = (
|
606
|
+
'\nNote: The following clouds were disabled because they were not '
|
607
|
+
'included in allowed_clouds in ~/.sky/config.yaml'
|
608
|
+
f'{disable_for_workspace_hint}: '
|
609
|
+
f'{", ".join([c for c in disallowed_cloud_names])}')
|
610
|
+
|
611
|
+
return (f'\n{colorama.Fore.GREEN}{PARTY_POPPER_EMOJI} '
|
612
|
+
f'Enabled infra{workspace_str} '
|
613
|
+
f'{PARTY_POPPER_EMOJI}'
|
614
|
+
f'{colorama.Style.RESET_ALL}{enabled_clouds_str}'
|
615
|
+
f'{disallowed_clouds_hint}')
|