skypilot-nightly 1.0.0.dev20250513__py3-none-any.whl → 1.0.0.dev20250515__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.py +3 -2
- sky/backends/backend_utils.py +16 -17
- sky/backends/cloud_vm_ray_backend.py +47 -16
- sky/clouds/aws.py +11 -9
- sky/clouds/azure.py +16 -13
- sky/clouds/cloud.py +4 -3
- sky/clouds/cudo.py +3 -2
- sky/clouds/do.py +3 -2
- sky/clouds/fluidstack.py +3 -3
- sky/clouds/gcp.py +25 -9
- sky/clouds/ibm.py +12 -10
- sky/clouds/kubernetes.py +3 -2
- sky/clouds/lambda_cloud.py +6 -6
- sky/clouds/nebius.py +6 -5
- sky/clouds/oci.py +9 -7
- sky/clouds/paperspace.py +3 -2
- sky/clouds/runpod.py +9 -9
- sky/clouds/scp.py +5 -3
- sky/clouds/service_catalog/data_fetchers/fetch_gcp.py +33 -11
- sky/clouds/service_catalog/gcp_catalog.py +7 -1
- sky/clouds/vast.py +8 -7
- sky/clouds/vsphere.py +4 -2
- sky/core.py +18 -12
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/chunks/pages/index-6b0d9e5031b70c58.js +1 -0
- sky/dashboard/out/_next/static/{2dkponv64SfFShA8Rnw0D → jFI0Y-uJZ_XDK5IGJpKFU}/_buildManifest.js +1 -1
- 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/jobs/[job].html +1 -1
- sky/dashboard/out/jobs.html +1 -1
- sky/execution.py +33 -0
- sky/jobs/recovery_strategy.py +4 -1
- sky/jobs/server/core.py +6 -12
- sky/optimizer.py +19 -13
- sky/provision/kubernetes/utils.py +26 -1
- sky/resources.py +206 -43
- sky/serve/server/core.py +0 -5
- sky/serve/spot_placer.py +3 -0
- sky/server/server.py +51 -13
- sky/skylet/log_lib.py +12 -3
- sky/skylet/log_lib.pyi +5 -0
- sky/task.py +8 -6
- sky/templates/nebius-ray.yml.j2 +3 -1
- sky/utils/cli_utils/status_utils.py +6 -5
- sky/utils/controller_utils.py +39 -43
- sky/utils/dag_utils.py +4 -2
- sky/utils/resources_utils.py +3 -0
- sky/utils/schemas.py +33 -24
- {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250515.dist-info}/METADATA +1 -1
- {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250515.dist-info}/RECORD +58 -58
- {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250515.dist-info}/WHEEL +1 -1
- sky/dashboard/out/_next/static/chunks/pages/index-f9f039532ca8cbc4.js +0 -1
- /sky/dashboard/out/_next/static/{2dkponv64SfFShA8Rnw0D → jFI0Y-uJZ_XDK5IGJpKFU}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250515.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250515.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250515.dist-info}/top_level.txt +0 -0
sky/skylet/log_lib.py
CHANGED
@@ -62,6 +62,16 @@ class _ProcessingArgs:
|
|
62
62
|
self.streaming_prefix = streaming_prefix
|
63
63
|
|
64
64
|
|
65
|
+
def _get_context():
|
66
|
+
# TODO(aylei): remove this after we drop the backward-compatibility for
|
67
|
+
# 0.9.x in 0.12.0
|
68
|
+
# Keep backward-compatibility for the old version of SkyPilot runtimes.
|
69
|
+
if 'context' in globals():
|
70
|
+
return context.get()
|
71
|
+
else:
|
72
|
+
return None
|
73
|
+
|
74
|
+
|
65
75
|
def _handle_io_stream(io_stream, out_stream, args: _ProcessingArgs):
|
66
76
|
"""Process the stream of a process."""
|
67
77
|
out_io = io.TextIOWrapper(io_stream,
|
@@ -80,7 +90,7 @@ def _handle_io_stream(io_stream, out_stream, args: _ProcessingArgs):
|
|
80
90
|
with open(args.log_path, 'a', encoding='utf-8') as fout:
|
81
91
|
with line_processor:
|
82
92
|
while True:
|
83
|
-
ctx =
|
93
|
+
ctx = _get_context()
|
84
94
|
if ctx is not None and ctx.is_canceled():
|
85
95
|
return
|
86
96
|
line = out_io.readline()
|
@@ -139,7 +149,6 @@ def process_subprocess_stream(proc, stdout_stream_handler,
|
|
139
149
|
return stdout, stderr
|
140
150
|
|
141
151
|
|
142
|
-
@context_utils.cancellation_guard
|
143
152
|
def run_with_log(
|
144
153
|
cmd: Union[List[str], str],
|
145
154
|
log_path: str,
|
@@ -181,7 +190,7 @@ def run_with_log(
|
|
181
190
|
# Redirect stderr to stdout when using ray, to preserve the order of
|
182
191
|
# stdout and stderr.
|
183
192
|
stdout_arg = stderr_arg = None
|
184
|
-
ctx =
|
193
|
+
ctx = _get_context()
|
185
194
|
if process_stream or ctx is not None:
|
186
195
|
# Capture stdout/stderr of the subprocess if:
|
187
196
|
# 1. Post-processing is needed (process_stream=True)
|
sky/skylet/log_lib.pyi
CHANGED
@@ -11,6 +11,7 @@ from typing_extensions import Literal
|
|
11
11
|
from sky import sky_logging as sky_logging
|
12
12
|
from sky.skylet import constants as constants
|
13
13
|
from sky.skylet import job_lib as job_lib
|
14
|
+
from sky.utils import context
|
14
15
|
from sky.utils import log_utils as log_utils
|
15
16
|
|
16
17
|
SKY_LOG_WAITING_GAP_SECONDS: int = ...
|
@@ -41,6 +42,10 @@ class _ProcessingArgs:
|
|
41
42
|
...
|
42
43
|
|
43
44
|
|
45
|
+
def _get_context() -> Optional[context.Context]:
|
46
|
+
...
|
47
|
+
|
48
|
+
|
44
49
|
def _handle_io_stream(io_stream, out_stream, args: _ProcessingArgs):
|
45
50
|
...
|
46
51
|
|
sky/task.py
CHANGED
@@ -165,7 +165,8 @@ def _with_docker_login_config(
|
|
165
165
|
f'ignored.{colorama.Style.RESET_ALL}')
|
166
166
|
return resources
|
167
167
|
# Already checked in extract_docker_image
|
168
|
-
assert
|
168
|
+
assert resources.image_id is not None and len(
|
169
|
+
resources.image_id) == 1, resources.image_id
|
169
170
|
region = list(resources.image_id.keys())[0]
|
170
171
|
return resources.copy(image_id={region: 'docker:' + docker_image},
|
171
172
|
_docker_login_config=docker_login_config)
|
@@ -775,7 +776,7 @@ class Task:
|
|
775
776
|
for _, storage_obj in self.storage_mounts.items():
|
776
777
|
if storage_obj.mode in storage_lib.MOUNTABLE_STORAGE_MODES:
|
777
778
|
for r in self.resources:
|
778
|
-
r.
|
779
|
+
r.set_requires_fuse(True)
|
779
780
|
break
|
780
781
|
|
781
782
|
return self
|
@@ -931,7 +932,7 @@ class Task:
|
|
931
932
|
self.storage_mounts = {}
|
932
933
|
# Clear the requires_fuse flag if no storage mounts are set.
|
933
934
|
for r in self.resources:
|
934
|
-
r.
|
935
|
+
r.set_requires_fuse(False)
|
935
936
|
return self
|
936
937
|
for target, storage_obj in storage_mounts.items():
|
937
938
|
# TODO(zhwu): /home/username/sky_workdir as the target path need
|
@@ -956,7 +957,7 @@ class Task:
|
|
956
957
|
# If any storage is using MOUNT mode, we need to enable FUSE in
|
957
958
|
# the resources.
|
958
959
|
for r in self.resources:
|
959
|
-
r.
|
960
|
+
r.set_requires_fuse(True)
|
960
961
|
# Storage source validation is done in Storage object
|
961
962
|
self.storage_mounts = storage_mounts
|
962
963
|
return self
|
@@ -1234,13 +1235,14 @@ class Task:
|
|
1234
1235
|
|
1235
1236
|
add_if_not_none('name', self.name)
|
1236
1237
|
|
1237
|
-
tmp_resource_config
|
1238
|
+
tmp_resource_config: Union[Dict[str, Union[str, int]],
|
1239
|
+
Dict[str, List[Dict[str, Union[str, int]]]]]
|
1238
1240
|
if len(self.resources) > 1:
|
1239
1241
|
resource_list = []
|
1240
1242
|
for r in self.resources:
|
1241
1243
|
resource_list.append(r.to_yaml_config())
|
1242
1244
|
key = 'ordered' if isinstance(self.resources, list) else 'any_of'
|
1243
|
-
tmp_resource_config
|
1245
|
+
tmp_resource_config = {key: resource_list}
|
1244
1246
|
else:
|
1245
1247
|
tmp_resource_config = list(self.resources)[0].to_yaml_config()
|
1246
1248
|
|
sky/templates/nebius-ray.yml.j2
CHANGED
@@ -105,6 +105,7 @@ file_mounts: {
|
|
105
105
|
"{{sky_remote_path}}/{{sky_wheel_hash}}": "{{sky_local_path}}",
|
106
106
|
{%- for remote_path, local_path in credentials.items() %}
|
107
107
|
"{{remote_path}}": "{{local_path}}",
|
108
|
+
"~/.ssh/sky-cluster-key": "{{ssh_private_key}}",
|
108
109
|
{%- endfor %}
|
109
110
|
}
|
110
111
|
|
@@ -120,6 +121,7 @@ initialization_commands: []
|
|
120
121
|
# Increment the following for catching performance bugs easier:
|
121
122
|
# current num items (num SSH connections): 1
|
122
123
|
setup_commands:
|
124
|
+
# Add ~/.ssh/sky-cluster-key to SSH config to allow nodes within a cluster to connect to each other
|
123
125
|
# Disable `unattended-upgrades` to prevent apt-get from hanging. It should be called at the beginning before the process started to avoid being blocked. (This is a temporary fix.)
|
124
126
|
# Create ~/.ssh/config file in case the file does not exist in the image.
|
125
127
|
# Line 'rm ..': there is another installation of pip.
|
@@ -142,6 +144,6 @@ setup_commands:
|
|
142
144
|
{{ ray_skypilot_installation_commands }}
|
143
145
|
sudo bash -c 'rm -rf /etc/security/limits.d; echo "* soft nofile 1048576" >> /etc/security/limits.conf; echo "* hard nofile 1048576" >> /etc/security/limits.conf';
|
144
146
|
sudo grep -e '^DefaultTasksMax' /etc/systemd/system.conf || (sudo bash -c 'echo "DefaultTasksMax=infinity" >> /etc/systemd/system.conf'); sudo systemctl set-property user-$(id -u $(whoami)).slice TasksMax=infinity; sudo systemctl daemon-reload;
|
145
|
-
mkdir -p ~/.ssh; (grep -Pzo -q "Host \*\n StrictHostKeyChecking no" ~/.ssh/config) || printf "Host *\n StrictHostKeyChecking no\n" >> ~/.ssh/config;
|
147
|
+
mkdir -p ~/.ssh; (grep -Pzo -q "Host \*\n StrictHostKeyChecking no\n IdentityFile ~/.ssh/sky-cluster-key\n IdentityFile ~/.ssh/id_rsa" ~/.ssh/config) || printf "Host *\n StrictHostKeyChecking no\n IdentityFile ~/.ssh/sky-cluster-key\n IdentityFile ~/.ssh/id_rsa\n" >> ~/.ssh/config;
|
146
148
|
[ -f /etc/fuse.conf ] && sudo sed -i 's/#user_allow_other/user_allow_other/g' /etc/fuse.conf || (sudo sh -c 'echo "user_allow_other" > /etc/fuse.conf');
|
147
149
|
{{ ssh_max_sessions_config }}
|
@@ -201,12 +201,13 @@ def show_cost_report_table(cluster_records: List[_ClusterCostReportRecord],
|
|
201
201
|
controller = controller_utils.Controllers.from_name(controller_name)
|
202
202
|
if controller is None:
|
203
203
|
raise ValueError(f'Controller {controller_name} not found.')
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
204
|
+
controller_handle: backends.CloudVmRayResourceHandle = (
|
205
|
+
cluster_records[0]['handle'])
|
206
|
+
autostop_config = (
|
207
|
+
controller_handle.launched_resources.autostop_config)
|
208
|
+
if autostop_config is not None:
|
208
209
|
autostop_str = (f'{colorama.Style.DIM} (will be autostopped if '
|
209
|
-
f'idle for {
|
210
|
+
f'idle for {autostop_config.idle_minutes}min)'
|
210
211
|
f'{colorama.Style.RESET_ALL}')
|
211
212
|
click.echo(f'\n{colorama.Fore.CYAN}{colorama.Style.BRIGHT}'
|
212
213
|
f'{controller_name}{colorama.Style.RESET_ALL}'
|
sky/utils/controller_utils.py
CHANGED
@@ -6,7 +6,7 @@ import getpass
|
|
6
6
|
import os
|
7
7
|
import tempfile
|
8
8
|
import typing
|
9
|
-
from typing import Any, Dict, Iterable, List, Optional, Set
|
9
|
+
from typing import Any, Dict, Iterable, List, Optional, Set
|
10
10
|
import uuid
|
11
11
|
|
12
12
|
import colorama
|
@@ -517,6 +517,30 @@ def get_controller_resources(
|
|
517
517
|
if custom_controller_resources_config is not None:
|
518
518
|
controller_resources_config_copied.update(
|
519
519
|
custom_controller_resources_config)
|
520
|
+
# Compatibility with the old way of specifying the controller autostop
|
521
|
+
# config. TODO(cooperc): Remove this before 0.12.0.
|
522
|
+
custom_controller_autostop_config = skypilot_config.get_nested(
|
523
|
+
(controller.value.controller_type, 'controller', 'autostop'), None)
|
524
|
+
if custom_controller_autostop_config is not None:
|
525
|
+
logger.warning(
|
526
|
+
f'{colorama.Fore.YELLOW}Warning: Config value '
|
527
|
+
f'`{controller.value.controller_type}.controller.autostop` '
|
528
|
+
'is deprecated. Please use '
|
529
|
+
f'`{controller.value.controller_type}.controller.resources.'
|
530
|
+
f'autostop` instead.{colorama.Style.RESET_ALL}')
|
531
|
+
# Only set the autostop config if it is not already specified.
|
532
|
+
if controller_resources_config_copied.get('autostop') is None:
|
533
|
+
controller_resources_config_copied['autostop'] = (
|
534
|
+
custom_controller_autostop_config)
|
535
|
+
else:
|
536
|
+
logger.warning(f'{colorama.Fore.YELLOW}Ignoring the old '
|
537
|
+
'config, since it is already specified in '
|
538
|
+
f'resources.{colorama.Style.RESET_ALL}')
|
539
|
+
# Set the default autostop config for the controller, if not already
|
540
|
+
# specified.
|
541
|
+
if controller_resources_config_copied.get('autostop') is None:
|
542
|
+
controller_resources_config_copied['autostop'] = (
|
543
|
+
controller.value.default_autostop_config)
|
520
544
|
|
521
545
|
try:
|
522
546
|
controller_resources = resources.Resources.from_yaml_config(
|
@@ -547,7 +571,10 @@ def get_controller_resources(
|
|
547
571
|
if controller_record is not None:
|
548
572
|
handle = controller_record.get('handle', None)
|
549
573
|
if handle is not None:
|
550
|
-
|
574
|
+
# Use the existing resources, but override the autostop config with
|
575
|
+
# the one currently specified in the config.
|
576
|
+
controller_resources_to_use = handle.launched_resources.copy(
|
577
|
+
autostop=controller_resources_config_copied.get('autostop'))
|
551
578
|
|
552
579
|
# If the controller and replicas are from the same cloud (and region/zone),
|
553
580
|
# it should provide better connectivity. We will let the controller choose
|
@@ -608,8 +635,9 @@ def get_controller_resources(
|
|
608
635
|
controller_zone = controller_resources_to_use.zone
|
609
636
|
|
610
637
|
# Filter clouds if controller_resources_to_use.cloud is specified.
|
611
|
-
filtered_clouds =
|
612
|
-
|
638
|
+
filtered_clouds: Set[str] = {controller_cloud
|
639
|
+
} if controller_cloud is not None else set(
|
640
|
+
requested_clouds_with_region_zone.keys())
|
613
641
|
|
614
642
|
# Filter regions and zones and construct the result.
|
615
643
|
result: Set[resources.Resources] = set()
|
@@ -618,15 +646,17 @@ def get_controller_resources(
|
|
618
646
|
{None: {None}})
|
619
647
|
|
620
648
|
# Filter regions if controller_resources_to_use.region is specified.
|
621
|
-
filtered_regions = ({
|
622
|
-
|
649
|
+
filtered_regions: Set[Optional[str]] = ({
|
650
|
+
controller_region
|
651
|
+
} if controller_region is not None else set(regions.keys()))
|
623
652
|
|
624
653
|
for region in filtered_regions:
|
625
654
|
zones = regions.get(region, {None})
|
626
655
|
|
627
656
|
# Filter zones if controller_resources_to_use.zone is specified.
|
628
|
-
filtered_zones = ({
|
629
|
-
|
657
|
+
filtered_zones: Set[Optional[str]] = ({
|
658
|
+
controller_zone
|
659
|
+
} if controller_zone is not None else set(zones))
|
630
660
|
|
631
661
|
# Create combinations of cloud, region, and zone.
|
632
662
|
for zone in filtered_zones:
|
@@ -641,40 +671,6 @@ def get_controller_resources(
|
|
641
671
|
return result
|
642
672
|
|
643
673
|
|
644
|
-
def get_controller_autostop_config(
|
645
|
-
controller: Controllers) -> Tuple[Optional[int], bool]:
|
646
|
-
"""Get the autostop config for the controller.
|
647
|
-
|
648
|
-
Returns:
|
649
|
-
A tuple of (idle_minutes_to_autostop, down), which correspond to the
|
650
|
-
values passed to execution.launch().
|
651
|
-
"""
|
652
|
-
controller_autostop_config_copied: Dict[str, Any] = copy.copy(
|
653
|
-
controller.value.default_autostop_config)
|
654
|
-
if skypilot_config.loaded():
|
655
|
-
custom_controller_autostop_config = skypilot_config.get_nested(
|
656
|
-
(controller.value.controller_type, 'controller', 'autostop'), None)
|
657
|
-
if custom_controller_autostop_config is False:
|
658
|
-
# Disabled with `autostop: false` in config.
|
659
|
-
# To indicate autostop is disabled, we return None for
|
660
|
-
# idle_minutes_to_autostop.
|
661
|
-
return None, False
|
662
|
-
elif custom_controller_autostop_config is True:
|
663
|
-
# Enabled with default values. There is no change in behavior, but
|
664
|
-
# this is included by for completeness, since `False` is valid.
|
665
|
-
pass
|
666
|
-
elif custom_controller_autostop_config is not None:
|
667
|
-
# We have specific config values.
|
668
|
-
# Override the controller autostop config with the ones specified in
|
669
|
-
# the config.
|
670
|
-
assert isinstance(custom_controller_autostop_config, dict)
|
671
|
-
controller_autostop_config_copied.update(
|
672
|
-
custom_controller_autostop_config)
|
673
|
-
|
674
|
-
return (controller_autostop_config_copied['idle_minutes'],
|
675
|
-
controller_autostop_config_copied['down'])
|
676
|
-
|
677
|
-
|
678
674
|
def _setup_proxy_command_on_controller(
|
679
675
|
controller_launched_cloud: 'clouds.Cloud',
|
680
676
|
user_config: Dict[str, Any]) -> config_utils.Config:
|
@@ -703,7 +699,7 @@ def _setup_proxy_command_on_controller(
|
|
703
699
|
# NOTE: suppose that we have a controller in old VPC, then user
|
704
700
|
# changes 'vpc_name' in the config and does a 'job launch' /
|
705
701
|
# 'serve up'. In general, the old controller may not successfully
|
706
|
-
# launch the job in the new VPC. This happens if the two VPCs don
|
702
|
+
# launch the job in the new VPC. This happens if the two VPCs don't
|
707
703
|
# have peering set up. Like other places in the code, we assume
|
708
704
|
# properly setting up networking is user's responsibilities.
|
709
705
|
# TODO(zongheng): consider adding a basic check that checks
|
sky/utils/dag_utils.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Utilities for loading and dumping DAGs from/to YAML files."""
|
2
2
|
import copy
|
3
|
-
from typing import Any, Dict, List, Optional, Tuple
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
4
4
|
|
5
5
|
from sky import dag as dag_lib
|
6
6
|
from sky import sky_logging
|
@@ -195,7 +195,9 @@ def fill_default_config_in_dag_for_job_launch(dag: dag_lib.Dag) -> None:
|
|
195
195
|
assert default_strategy is not None
|
196
196
|
for resources in list(task_.resources):
|
197
197
|
original_job_recovery = resources.job_recovery
|
198
|
-
job_recovery = {
|
198
|
+
job_recovery: Dict[str, Optional[Union[str, int]]] = {
|
199
|
+
'strategy': default_strategy
|
200
|
+
}
|
199
201
|
if isinstance(original_job_recovery, str):
|
200
202
|
job_recovery['strategy'] = original_job_recovery
|
201
203
|
elif isinstance(original_job_recovery, dict):
|
sky/utils/resources_utils.py
CHANGED
@@ -140,10 +140,12 @@ def simplify_ports(ports: List[str]) -> List[str]:
|
|
140
140
|
def format_resource(resource: 'resources_lib.Resources',
|
141
141
|
simplify: bool = False) -> str:
|
142
142
|
if simplify:
|
143
|
+
resource = resource.assert_launchable()
|
143
144
|
cloud = resource.cloud
|
144
145
|
if resource.accelerators is None:
|
145
146
|
vcpu, _ = cloud.get_vcpus_mem_from_instance_type(
|
146
147
|
resource.instance_type)
|
148
|
+
assert vcpu is not None, 'vCPU must be specified'
|
147
149
|
hardware = f'vCPU={int(vcpu)}'
|
148
150
|
else:
|
149
151
|
hardware = f'{resource.accelerators}'
|
@@ -248,6 +250,7 @@ def make_launchables_for_valid_region_zones(
|
|
248
250
|
launchables = []
|
249
251
|
regions = launchable_resources.get_valid_regions_for_launchable()
|
250
252
|
for region in regions:
|
253
|
+
assert launchable_resources.cloud is not None, 'Cloud must be specified'
|
251
254
|
optimize_by_zone = (override_optimize_by_zone or
|
252
255
|
launchable_resources.cloud.optimize_by_zone())
|
253
256
|
# It is possible that we force the optimize_by_zone but some clouds
|
sky/utils/schemas.py
CHANGED
@@ -33,6 +33,37 @@ def _check_not_both_fields_present(field1: str, field2: str):
|
|
33
33
|
}
|
34
34
|
|
35
35
|
|
36
|
+
_AUTOSTOP_SCHEMA = {
|
37
|
+
'anyOf': [
|
38
|
+
{
|
39
|
+
# Use boolean to disable autostop completely, e.g.
|
40
|
+
# autostop: false
|
41
|
+
'type': 'boolean',
|
42
|
+
},
|
43
|
+
{
|
44
|
+
# Shorthand to set idle_minutes by directly specifying, e.g.
|
45
|
+
# autostop: 5
|
46
|
+
'type': 'integer',
|
47
|
+
'minimum': 0,
|
48
|
+
},
|
49
|
+
{
|
50
|
+
'type': 'object',
|
51
|
+
'required': [],
|
52
|
+
'additionalProperties': False,
|
53
|
+
'properties': {
|
54
|
+
'idle_minutes': {
|
55
|
+
'type': 'integer',
|
56
|
+
'minimum': 0,
|
57
|
+
},
|
58
|
+
'down': {
|
59
|
+
'type': 'boolean',
|
60
|
+
},
|
61
|
+
},
|
62
|
+
},
|
63
|
+
],
|
64
|
+
}
|
65
|
+
|
66
|
+
|
36
67
|
def _get_single_resources_schema():
|
37
68
|
"""Schema for a single resource in a resources list."""
|
38
69
|
# To avoid circular imports, only import when needed.
|
@@ -165,6 +196,7 @@ def _get_single_resources_schema():
|
|
165
196
|
'type': 'null',
|
166
197
|
}]
|
167
198
|
},
|
199
|
+
'autostop': _AUTOSTOP_SCHEMA,
|
168
200
|
# The following fields are for internal use only. Should not be
|
169
201
|
# specified in the task config.
|
170
202
|
'_docker_login_config': {
|
@@ -725,29 +757,6 @@ def get_config_schema():
|
|
725
757
|
if k != '$schema'
|
726
758
|
}
|
727
759
|
resources_schema['properties'].pop('ports')
|
728
|
-
autostop_schema = {
|
729
|
-
'anyOf': [
|
730
|
-
{
|
731
|
-
# Use boolean to disable autostop completely, e.g.
|
732
|
-
# autostop: false
|
733
|
-
'type': 'boolean',
|
734
|
-
},
|
735
|
-
{
|
736
|
-
'type': 'object',
|
737
|
-
'required': [],
|
738
|
-
'additionalProperties': False,
|
739
|
-
'properties': {
|
740
|
-
'idle_minutes': {
|
741
|
-
'type': 'integer',
|
742
|
-
'minimum': 0,
|
743
|
-
},
|
744
|
-
'down': {
|
745
|
-
'type': 'boolean',
|
746
|
-
},
|
747
|
-
},
|
748
|
-
},
|
749
|
-
],
|
750
|
-
}
|
751
760
|
controller_resources_schema = {
|
752
761
|
'type': 'object',
|
753
762
|
'required': [],
|
@@ -762,7 +771,7 @@ def get_config_schema():
|
|
762
771
|
'high_availability': {
|
763
772
|
'type': 'boolean',
|
764
773
|
},
|
765
|
-
'autostop':
|
774
|
+
'autostop': _AUTOSTOP_SCHEMA,
|
766
775
|
}
|
767
776
|
},
|
768
777
|
'bucket': {
|