skypilot-nightly 1.0.0.dev20250417__py3-none-any.whl → 1.0.0.dev20250422__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. sky/__init__.py +2 -2
  2. sky/adaptors/aws.py +2 -13
  3. sky/backends/backend_utils.py +28 -0
  4. sky/backends/wheel_utils.py +9 -0
  5. sky/cli.py +93 -24
  6. sky/client/cli.py +93 -24
  7. sky/client/common.py +10 -3
  8. sky/client/sdk.py +6 -3
  9. sky/clouds/aws.py +5 -5
  10. sky/clouds/service_catalog/data_fetchers/fetch_gcp.py +9 -9
  11. sky/dashboard/out/404.html +1 -0
  12. sky/dashboard/out/_next/static/2GsKhI8XKYj9B2969iIDf/_buildManifest.js +1 -0
  13. sky/dashboard/out/_next/static/2GsKhI8XKYj9B2969iIDf/_ssgManifest.js +1 -0
  14. sky/dashboard/out/_next/static/chunks/236-d437cf66e68a6f64.js +6 -0
  15. sky/dashboard/out/_next/static/chunks/312-c3c8845990db8ffc.js +15 -0
  16. sky/dashboard/out/_next/static/chunks/37-72fdc8f71d6e4784.js +6 -0
  17. sky/dashboard/out/_next/static/chunks/678-206dddca808e6d16.js +59 -0
  18. sky/dashboard/out/_next/static/chunks/845-2ea1cc63ba1f4067.js +1 -0
  19. sky/dashboard/out/_next/static/chunks/979-7cd0778078b9cfad.js +1 -0
  20. sky/dashboard/out/_next/static/chunks/fd9d1056-2821b0f0cabcd8bd.js +1 -0
  21. sky/dashboard/out/_next/static/chunks/framework-87d061ee6ed71b28.js +33 -0
  22. sky/dashboard/out/_next/static/chunks/main-app-241eb28595532291.js +1 -0
  23. sky/dashboard/out/_next/static/chunks/main-e0e2335212e72357.js +1 -0
  24. sky/dashboard/out/_next/static/chunks/pages/_app-3001e84c61acddfb.js +1 -0
  25. sky/dashboard/out/_next/static/chunks/pages/_error-1be831200e60c5c0.js +1 -0
  26. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-b09f7fbf6d5d74f6.js +1 -0
  27. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-b57ec043f09c5813.js +1 -0
  28. sky/dashboard/out/_next/static/chunks/pages/clusters-a93b93e10b8b074e.js +1 -0
  29. sky/dashboard/out/_next/static/chunks/pages/index-f9f039532ca8cbc4.js +1 -0
  30. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-ef2e0e91a9222cac.js +1 -0
  31. sky/dashboard/out/_next/static/chunks/pages/jobs-a75029b67aab6a2e.js +1 -0
  32. sky/dashboard/out/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js +1 -0
  33. sky/dashboard/out/_next/static/chunks/webpack-830f59b8404e96b8.js +1 -0
  34. sky/dashboard/out/_next/static/css/f3538cd90cfca88c.css +3 -0
  35. sky/dashboard/out/clusters/[cluster]/[job].html +1 -0
  36. sky/dashboard/out/clusters/[cluster].html +1 -0
  37. sky/dashboard/out/clusters.html +1 -0
  38. sky/dashboard/out/favicon.ico +0 -0
  39. sky/dashboard/out/index.html +1 -0
  40. sky/dashboard/out/jobs/[job].html +1 -0
  41. sky/dashboard/out/jobs.html +1 -0
  42. sky/dashboard/out/skypilot.svg +15 -0
  43. sky/dashboard/out/videos/cursor-small.mp4 +0 -0
  44. sky/data/data_transfer.py +2 -1
  45. sky/data/storage.py +24 -14
  46. sky/optimizer.py +7 -9
  47. sky/provision/provisioner.py +2 -1
  48. sky/provision/runpod/utils.py +32 -6
  49. sky/resources.py +11 -2
  50. sky/serve/__init__.py +2 -0
  51. sky/serve/autoscalers.py +6 -2
  52. sky/serve/client/sdk.py +61 -0
  53. sky/serve/replica_managers.py +6 -8
  54. sky/serve/serve_utils.py +33 -1
  55. sky/serve/server/core.py +187 -5
  56. sky/serve/server/server.py +28 -0
  57. sky/server/common.py +19 -1
  58. sky/server/constants.py +6 -0
  59. sky/server/requests/executor.py +4 -0
  60. sky/server/requests/payloads.py +27 -15
  61. sky/server/server.py +43 -0
  62. sky/setup_files/MANIFEST.in +1 -0
  63. sky/sky_logging.py +10 -0
  64. sky/skypilot_config.py +58 -37
  65. sky/templates/kubernetes-ray.yml.j2 +6 -2
  66. sky/utils/config_utils.py +0 -1
  67. sky/utils/controller_utils.py +0 -1
  68. {skypilot_nightly-1.0.0.dev20250417.dist-info → skypilot_nightly-1.0.0.dev20250422.dist-info}/METADATA +1 -1
  69. {skypilot_nightly-1.0.0.dev20250417.dist-info → skypilot_nightly-1.0.0.dev20250422.dist-info}/RECORD +73 -40
  70. {skypilot_nightly-1.0.0.dev20250417.dist-info → skypilot_nightly-1.0.0.dev20250422.dist-info}/WHEEL +1 -1
  71. {skypilot_nightly-1.0.0.dev20250417.dist-info → skypilot_nightly-1.0.0.dev20250422.dist-info}/entry_points.txt +0 -0
  72. {skypilot_nightly-1.0.0.dev20250417.dist-info → skypilot_nightly-1.0.0.dev20250422.dist-info}/licenses/LICENSE +0 -0
  73. {skypilot_nightly-1.0.0.dev20250417.dist-info → skypilot_nightly-1.0.0.dev20250422.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@ with the backend functions. The benefit of having the default values in the
6
6
  payloads is that a user can find the default values in the Restful API docs.
7
7
  """
8
8
  import getpass
9
- import json
10
9
  import os
11
10
  import typing
12
11
  from typing import Any, Dict, List, Optional, Tuple, Union
@@ -32,6 +31,19 @@ else:
32
31
 
33
32
  logger = sky_logging.init_logger(__name__)
34
33
 
34
+ # These non-skypilot environment variables will be updated from the local
35
+ # environment on each request when running a local API server.
36
+ # We should avoid adding variables here, but we should include credential-
37
+ # related variables.
38
+ EXTERNAL_LOCAL_ENV_VARS = [
39
+ # Allow overriding the AWS authentication.
40
+ 'AWS_PROFILE',
41
+ 'AWS_ACCESS_KEY_ID',
42
+ 'AWS_SECRET_ACCESS_KEY',
43
+ # Allow overriding the GCP authentication.
44
+ 'GOOGLE_APPLICATION_CREDENTIALS',
45
+ ]
46
+
35
47
 
36
48
  @annotations.lru_cache(scope='global')
37
49
  def request_body_env_vars() -> dict:
@@ -39,6 +51,8 @@ def request_body_env_vars() -> dict:
39
51
  for env_var in os.environ:
40
52
  if env_var.startswith(constants.SKYPILOT_ENV_VAR_PREFIX):
41
53
  env_vars[env_var] = os.environ[env_var]
54
+ if common.is_api_server_local() and env_var in EXTERNAL_LOCAL_ENV_VARS:
55
+ env_vars[env_var] = os.environ[env_var]
42
56
  env_vars[constants.USER_ID_ENV_VAR] = common_utils.get_user_hash()
43
57
  env_vars[constants.USER_ENV_VAR] = os.getenv(constants.USER_ENV_VAR,
44
58
  getpass.getuser())
@@ -47,7 +61,7 @@ def request_body_env_vars() -> dict:
47
61
  # Remove the path to config file, as the config content is included in the
48
62
  # request body and will be merged with the config on the server side.
49
63
  env_vars.pop(skypilot_config.ENV_VAR_SKYPILOT_CONFIG, None)
50
- env_vars.pop(skypilot_config.ENV_VAR_USER_CONFIG, None)
64
+ env_vars.pop(skypilot_config.ENV_VAR_GLOBAL_CONFIG, None)
51
65
  env_vars.pop(skypilot_config.ENV_VAR_PROJECT_CONFIG, None)
52
66
  return env_vars
53
67
 
@@ -56,20 +70,9 @@ def get_override_skypilot_config_from_client() -> Dict[str, Any]:
56
70
  """Returns the override configs from the client."""
57
71
  config = skypilot_config.to_dict()
58
72
  # Remove the API server config, as we should not specify the SkyPilot
59
- # server endpoint on the server side. This avoids the warning below.
73
+ # server endpoint on the server side. This avoids the warning at
74
+ # server-side.
60
75
  config.pop_nested(('api_server',), default_value=None)
61
- ignored_key_values = {}
62
- for nested_key in constants.SKIPPED_CLIENT_OVERRIDE_KEYS:
63
- value = config.pop_nested(nested_key, default_value=None)
64
- if value is not None:
65
- ignored_key_values['.'.join(nested_key)] = value
66
- if ignored_key_values:
67
- logger.debug(f'The following keys ({json.dumps(ignored_key_values)}) '
68
- 'are specified in the client SkyPilot config at '
69
- f'{skypilot_config.loaded_config_path()!r}. '
70
- 'This will be ignored. If you want to specify it, '
71
- 'please modify it on server side or contact your '
72
- 'administrator.')
73
76
  return config
74
77
 
75
78
 
@@ -420,6 +423,15 @@ class ServeLogsBody(RequestBody):
420
423
  follow: bool = True
421
424
 
422
425
 
426
+ class ServeDownloadLogsBody(RequestBody):
427
+ """The request body for the serve download logs endpoint."""
428
+ service_name: str
429
+ local_dir: str
430
+ targets: Optional[Union[str, serve.ServiceComponent,
431
+ List[Union[str, serve.ServiceComponent]]]]
432
+ replica_ids: Optional[List[int]] = None
433
+
434
+
423
435
  class ServeStatusBody(RequestBody):
424
436
  """The request body for the serve status endpoint."""
425
437
  service_names: Optional[Union[str, List[str]]]
sky/server/server.py CHANGED
@@ -150,7 +150,21 @@ async def lifespan(app: fastapi.FastAPI): # pylint: disable=redefined-outer-nam
150
150
  # Shutdown: Add any cleanup code here if needed
151
151
 
152
152
 
153
+ # Add a new middleware class to handle /internal/dashboard prefix
154
+ class InternalDashboardPrefixMiddleware(
155
+ starlette.middleware.base.BaseHTTPMiddleware):
156
+ """Middleware to handle /internal/dashboard prefix in requests."""
157
+
158
+ async def dispatch(self, request: fastapi.Request, call_next):
159
+ path = request.url.path
160
+ if path.startswith('/internal/dashboard/'):
161
+ # Remove /internal/dashboard prefix and update request scope
162
+ request.scope['path'] = path.replace('/internal/dashboard/', '/', 1)
163
+ return await call_next(request)
164
+
165
+
153
166
  app = fastapi.FastAPI(prefix='/api/v1', debug=True, lifespan=lifespan)
167
+ app.add_middleware(InternalDashboardPrefixMiddleware)
154
168
  app.add_middleware(
155
169
  cors.CORSMiddleware,
156
170
  # TODO(zhwu): in production deployment, we should restrict the allowed
@@ -1101,6 +1115,35 @@ async def complete_storage_name(incomplete: str,) -> List[str]:
1101
1115
  return global_user_state.get_storage_names_start_with(incomplete)
1102
1116
 
1103
1117
 
1118
+ # Add a route to serve static files
1119
+ @app.get('/{full_path:path}')
1120
+ async def serve_static_or_dashboard(full_path: str):
1121
+ """Serves static files for any unmatched routes.
1122
+
1123
+ Handles the /dashboard prefix from Next.js configuration.
1124
+ """
1125
+ # Check if the path starts with 'dashboard/' and remove it if it does
1126
+ if full_path.startswith('dashboard/'):
1127
+ full_path = full_path[len('dashboard/'):]
1128
+
1129
+ # Try to serve the file directly from the out directory first
1130
+ file_path = os.path.join(server_constants.DASHBOARD_DIR, full_path)
1131
+ if os.path.isfile(file_path):
1132
+ return fastapi.responses.FileResponse(file_path)
1133
+
1134
+ # If file not found, serve the index.html for client-side routing.
1135
+ # For example, the non-matched arbitrary route (/ or /test) from
1136
+ # client will be redirected to the index.html.
1137
+ index_path = os.path.join(server_constants.DASHBOARD_DIR, 'index.html')
1138
+ try:
1139
+ with open(index_path, 'r', encoding='utf-8') as f:
1140
+ content = f.read()
1141
+ return fastapi.responses.HTMLResponse(content=content)
1142
+ except Exception as e:
1143
+ logger.error(f'Error serving dashboard: {e}')
1144
+ raise fastapi.HTTPException(status_code=500, detail=str(e))
1145
+
1146
+
1104
1147
  if __name__ == '__main__':
1105
1148
  import uvicorn
1106
1149
 
@@ -15,3 +15,4 @@ include sky/jobs/dashboard/static/*
15
15
  include sky/templates/*
16
16
  include sky/utils/kubernetes/*
17
17
  include sky/server/html/*
18
+ recursive-include sky/dashboard/out *
sky/sky_logging.py CHANGED
@@ -18,6 +18,12 @@ _FORMAT = '%(levelname).1s %(asctime)s %(filename)s:%(lineno)d] %(message)s'
18
18
  _DATE_FORMAT = '%m-%d %H:%M:%S'
19
19
  _SENSITIVE_LOGGER = ['sky.provisioner', 'sky.optimizer']
20
20
 
21
+ DEBUG = logging.DEBUG
22
+ INFO = logging.INFO
23
+ WARNING = logging.WARNING
24
+ ERROR = logging.ERROR
25
+ CRITICAL = logging.CRITICAL
26
+
21
27
 
22
28
  def _show_logging_prefix():
23
29
  return env_options.Options.SHOW_DEBUG_INFO.get(
@@ -127,6 +133,10 @@ def set_logging_level(logger: str, level: int):
127
133
  logger.setLevel(original_level)
128
134
 
129
135
 
136
+ def logging_enabled(logger: logging.Logger, level: int) -> bool:
137
+ return logger.level <= level
138
+
139
+
130
140
  @contextlib.contextmanager
131
141
  def silent():
132
142
  """Make all sky_logging.print() and logger.{info, warning...} silent.
sky/skypilot_config.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Immutable user configurations (EXPERIMENTAL).
2
2
 
3
- On module import, we attempt to parse the config located at _USER_CONFIG_PATH
3
+ On module import, we attempt to parse the config located at _GLOBAL_CONFIG_PATH
4
4
  (default: ~/.sky/config.yaml). Caller can then use
5
5
 
6
6
  >> skypilot_config.loaded()
@@ -50,8 +50,8 @@ then:
50
50
  """
51
51
  import contextlib
52
52
  import copy
53
+ import json
53
54
  import os
54
- import pprint
55
55
  import threading
56
56
  import typing
57
57
  from typing import Any, Dict, Iterator, List, Optional, Tuple
@@ -80,8 +80,8 @@ logger = sky_logging.init_logger(__name__)
80
80
  # path as the config file. Do not use any other config files.
81
81
  # This behavior is subject to change and should not be relied on by users.
82
82
  # Else,
83
- # (1) If env var {ENV_VAR_USER_CONFIG} exists, use its path as the user
84
- # config file. Else, use the default path {_USER_CONFIG_PATH}.
83
+ # (1) If env var {ENV_VAR_GLOBAL_CONFIG} exists, use its path as the user
84
+ # config file. Else, use the default path {_GLOBAL_CONFIG_PATH}.
85
85
  # (2) If env var {ENV_VAR_PROJECT_CONFIG} exists, use its path as the project
86
86
  # config file. Else, use the default path {_PROJECT_CONFIG_PATH}.
87
87
  # (3) Override any config keys in (1) with the ones in (2).
@@ -97,21 +97,16 @@ logger = sky_logging.init_logger(__name__)
97
97
  # use the same config file.
98
98
  ENV_VAR_SKYPILOT_CONFIG = f'{constants.SKYPILOT_ENV_VAR_PREFIX}CONFIG'
99
99
 
100
- # (Used by users) Environment variables for setting non-default user and
101
- # project config files on clients.
102
- ENV_VAR_USER_CONFIG = f'{constants.SKYPILOT_ENV_VAR_PREFIX}USER_CONFIG'
100
+ # Environment variables for setting non-default server and user
101
+ # config files.
102
+ ENV_VAR_GLOBAL_CONFIG = f'{constants.SKYPILOT_ENV_VAR_PREFIX}GLOBAL_CONFIG'
103
+ # Environment variables for setting non-default project config files.
103
104
  ENV_VAR_PROJECT_CONFIG = f'{constants.SKYPILOT_ENV_VAR_PREFIX}PROJECT_CONFIG'
104
105
 
105
- # (Used by server) Environment variable for setting the server config file.
106
- ENV_VAR_SERVER_CONFIG = f'{constants.SKYPILOT_ENV_VAR_PREFIX}SERVER_CONFIG'
107
-
108
106
  # Path to the client config files.
109
- _USER_CONFIG_PATH = '~/.sky/config.yaml'
107
+ _GLOBAL_CONFIG_PATH = '~/.sky/config.yaml'
110
108
  _PROJECT_CONFIG_PATH = '.sky.yaml'
111
109
 
112
- # Path to the server config file.
113
- _SERVER_CONFIG_PATH = _USER_CONFIG_PATH
114
-
115
110
  # The loaded config.
116
111
  _dict = config_utils.Config()
117
112
  _loaded_config_path: Optional[str] = None
@@ -121,24 +116,24 @@ _reload_config_lock = threading.Lock()
121
116
 
122
117
  def get_user_config_path() -> str:
123
118
  """Returns the path to the user config file."""
124
- return _USER_CONFIG_PATH
119
+ return _GLOBAL_CONFIG_PATH
125
120
 
126
121
 
127
122
  def get_user_config() -> config_utils.Config:
128
123
  """Returns the user config."""
129
124
  # find the user config file
130
- user_config_path = _get_config_file_path(ENV_VAR_USER_CONFIG)
125
+ user_config_path = _get_config_file_path(ENV_VAR_GLOBAL_CONFIG)
131
126
  if user_config_path:
132
127
  logger.debug('using user config file specified by '
133
- f'{ENV_VAR_USER_CONFIG}: {user_config_path}')
128
+ f'{ENV_VAR_GLOBAL_CONFIG}: {user_config_path}')
134
129
  user_config_path = os.path.expanduser(user_config_path)
135
130
  if not os.path.exists(user_config_path):
136
131
  with ux_utils.print_exception_no_traceback():
137
132
  raise FileNotFoundError(
138
133
  'Config file specified by env var '
139
- f'{ENV_VAR_USER_CONFIG} ({user_config_path!r}) '
134
+ f'{ENV_VAR_GLOBAL_CONFIG} ({user_config_path!r}) '
140
135
  'does not exist. Please double check the path or unset the '
141
- f'env var: unset {ENV_VAR_USER_CONFIG}')
136
+ f'env var: unset {ENV_VAR_GLOBAL_CONFIG}')
142
137
  else:
143
138
  user_config_path = get_user_config_path()
144
139
  logger.debug(f'using default user config file: {user_config_path}')
@@ -185,20 +180,20 @@ def _get_project_config() -> config_utils.Config:
185
180
  def get_server_config() -> config_utils.Config:
186
181
  """Returns the server config."""
187
182
  # find the server config file
188
- server_config_path = _get_config_file_path(ENV_VAR_SERVER_CONFIG)
183
+ server_config_path = _get_config_file_path(ENV_VAR_GLOBAL_CONFIG)
189
184
  if server_config_path:
190
185
  logger.debug('using server config file specified by '
191
- f'{ENV_VAR_SERVER_CONFIG}: {server_config_path}')
186
+ f'{ENV_VAR_GLOBAL_CONFIG}: {server_config_path}')
192
187
  server_config_path = os.path.expanduser(server_config_path)
193
188
  if not os.path.exists(server_config_path):
194
189
  with ux_utils.print_exception_no_traceback():
195
190
  raise FileNotFoundError(
196
191
  'Config file specified by env var '
197
- f'{ENV_VAR_SERVER_CONFIG} ({server_config_path!r}) '
192
+ f'{ENV_VAR_GLOBAL_CONFIG} ({server_config_path!r}) '
198
193
  'does not exist. Please double check the path or unset the '
199
- f'env var: unset {ENV_VAR_SERVER_CONFIG}')
194
+ f'env var: unset {ENV_VAR_GLOBAL_CONFIG}')
200
195
  else:
201
- server_config_path = _SERVER_CONFIG_PATH
196
+ server_config_path = _GLOBAL_CONFIG_PATH
202
197
  logger.debug(f'using default server config file: {server_config_path}')
203
198
  server_config_path = os.path.expanduser(server_config_path)
204
199
 
@@ -314,8 +309,9 @@ def _parse_config_file(config_path: str) -> config_utils.Config:
314
309
  try:
315
310
  config_dict = common_utils.read_yaml(config_path)
316
311
  config = config_utils.Config.from_dict(config_dict)
317
- logger.debug(
318
- f'Config loaded from {config_path}:\n{pprint.pformat(config)}')
312
+ if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
313
+ logger.debug(f'Config loaded from {config_path}:\n'
314
+ f'{common_utils.dump_yaml_str(dict(config))}')
319
315
  except yaml.YAMLError as e:
320
316
  logger.error(f'Error in loading config file ({config_path}):', e)
321
317
  if config:
@@ -359,7 +355,10 @@ def _reload_config_as_server() -> None:
359
355
  for override in overrides:
360
356
  overlaid_server_config = overlay_skypilot_config(
361
357
  original_config=overlaid_server_config, override_configs=override)
362
- logger.debug(f'final server config: {overlaid_server_config}')
358
+ if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
359
+ logger.debug(
360
+ f'server config: \n'
361
+ f'{common_utils.dump_yaml_str(dict(overlaid_server_config))}')
363
362
  _dict = overlaid_server_config
364
363
 
365
364
 
@@ -381,7 +380,10 @@ def _reload_config_as_client() -> None:
381
380
  for override in overrides:
382
381
  overlaid_client_config = overlay_skypilot_config(
383
382
  original_config=overlaid_client_config, override_configs=override)
384
- logger.debug(f'final client config: {overlaid_client_config}')
383
+ if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
384
+ logger.debug(
385
+ f'client config (before task and CLI overrides): \n'
386
+ f'{common_utils.dump_yaml_str(dict(overlaid_client_config))}')
385
387
  _dict = overlaid_client_config
386
388
 
387
389
 
@@ -414,10 +416,26 @@ def override_skypilot_config(
414
416
  yield
415
417
  return
416
418
  original_config = _dict
419
+ override_configs = config_utils.Config(override_configs)
420
+ disallowed_diff_keys = []
421
+ for key in constants.SKIPPED_CLIENT_OVERRIDE_KEYS:
422
+ value = override_configs.pop_nested(key, default_value=None)
423
+ if (value is not None and
424
+ value != original_config.get_nested(key, default_value=None)):
425
+ disallowed_diff_keys.append('.'.join(key))
426
+ # Only warn if there is a diff in disallowed override keys, as the client
427
+ # use the same config file when connecting to a local server.
428
+ if disallowed_diff_keys:
429
+ logger.warning(
430
+ f'The following keys ({json.dumps(disallowed_diff_keys)}) have '
431
+ 'different values in the client SkyPilot config with the server '
432
+ 'and will be ignored. Remove these keys to disable this warning. '
433
+ 'If you want to specify it, please modify it on server side or '
434
+ 'contact your administrator.')
417
435
  config = _dict.get_nested(
418
436
  keys=tuple(),
419
437
  default_value=None,
420
- override_configs=override_configs,
438
+ override_configs=dict(override_configs),
421
439
  allowed_override_keys=None,
422
440
  disallowed_override_keys=constants.SKIPPED_CLIENT_OVERRIDE_KEYS)
423
441
  try:
@@ -439,14 +457,14 @@ def override_skypilot_config(
439
457
  '=== SkyPilot config on API server ===\n'
440
458
  f'{common_utils.dump_yaml_str(dict(original_config))}\n'
441
459
  '=== Your local SkyPilot config ===\n'
442
- f'{common_utils.dump_yaml_str(override_configs)}\n'
460
+ f'{common_utils.dump_yaml_str(dict(override_configs))}\n'
443
461
  f'Details: {e}') from e
444
462
  finally:
445
463
  _dict = original_config
446
464
  _config_overridden = False
447
465
 
448
466
 
449
- def _compose_cli_config(cli_config: Optional[str],) -> config_utils.Config:
467
+ def _compose_cli_config(cli_config: Optional[List[str]]) -> config_utils.Config:
450
468
  """Composes the skypilot CLI config.
451
469
  CLI config can either be:
452
470
  - A path to a config file
@@ -457,18 +475,19 @@ def _compose_cli_config(cli_config: Optional[str],) -> config_utils.Config:
457
475
  return config_utils.Config()
458
476
 
459
477
  config_source = 'CLI'
460
- maybe_config_path = os.path.expanduser(cli_config)
461
478
  try:
479
+ maybe_config_path = os.path.expanduser(cli_config[0])
462
480
  if os.path.isfile(maybe_config_path):
481
+ if len(cli_config) != 1:
482
+ raise ValueError(
483
+ 'Cannot use multiple --config flags with a config file.')
463
484
  config_source = maybe_config_path
464
485
  # cli_config is a path to a config file
465
486
  parsed_config = OmegaConf.to_object(
466
487
  OmegaConf.load(maybe_config_path))
467
488
  else: # cli_config is a comma-separated list of key-value pairs
468
- variables: List[str] = []
469
- variables = cli_config.split(',')
470
489
  parsed_config = OmegaConf.to_object(
471
- OmegaConf.from_dotlist(variables))
490
+ OmegaConf.from_dotlist(cli_config))
472
491
  _validate_config(parsed_config, config_source)
473
492
  except ValueError as e:
474
493
  raise ValueError(f'Invalid config override: {cli_config}. '
@@ -479,7 +498,7 @@ def _compose_cli_config(cli_config: Optional[str],) -> config_utils.Config:
479
498
  return parsed_config
480
499
 
481
500
 
482
- def apply_cli_config(cli_config: Optional[str]) -> Dict[str, Any]:
501
+ def apply_cli_config(cli_config: Optional[List[str]]) -> Dict[str, Any]:
483
502
  """Applies the CLI provided config.
484
503
  SAFETY:
485
504
  This function directly modifies the global _dict variable.
@@ -491,7 +510,9 @@ def apply_cli_config(cli_config: Optional[str]) -> Dict[str, Any]:
491
510
  """
492
511
  global _dict
493
512
  parsed_config = _compose_cli_config(cli_config)
494
- logger.debug(f'applying following CLI overrides: {parsed_config}')
513
+ if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
514
+ logger.debug(f'applying following CLI overrides: \n'
515
+ f'{common_utils.dump_yaml_str(dict(parsed_config))}')
495
516
  _dict = overlay_skypilot_config(original_config=_dict,
496
517
  override_configs=parsed_config)
497
518
  return parsed_config
@@ -384,8 +384,12 @@ available_node_types:
384
384
  set +e
385
385
 
386
386
  if [ ! -z "$MISSING_PACKAGES" ]; then
387
- echo "Installing missing packages: $MISSING_PACKAGES";
388
- DEBIAN_FRONTEND=noninteractive $(prefix_cmd) apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" $MISSING_PACKAGES;
387
+ # Install missing packages individually to avoid failure installation breaks the whole install process,
388
+ # e.g. fuse3 is not available on some distributions.
389
+ echo "Installing missing packages individually: $MISSING_PACKAGES";
390
+ for pkg in $MISSING_PACKAGES; do
391
+ DEBIAN_FRONTEND=noninteractive $(prefix_cmd) apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" $pkg || echo "Optional package $pkg installation failed, skip.";
392
+ done
389
393
  fi;
390
394
 
391
395
  {% if k8s_fuse_device_required %}
sky/utils/config_utils.py CHANGED
@@ -171,7 +171,6 @@ def _get_nested(configs: Optional[Dict[str, Any]],
171
171
  curr = value
172
172
  else:
173
173
  return default_value
174
- logger.debug(f'Config: {".".join(keys)} -> {curr}')
175
174
  return curr
176
175
 
177
176
 
@@ -393,7 +393,6 @@ def download_and_stream_latest_job_log(
393
393
  f'Failed to stream the logs for the user program at '
394
394
  f'{log_file}: {common_utils.format_exception(e)}',
395
395
  exc_info=True)
396
- # Return the log_file anyway.
397
396
 
398
397
  return log_file
399
398
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skypilot-nightly
3
- Version: 1.0.0.dev20250417
3
+ Version: 1.0.0.dev20250422
4
4
  Summary: SkyPilot: An intercloud broker for the clouds
5
5
  Author: SkyPilot Team
6
6
  License: Apache 2.0