dstack 0.18.43__py3-none-any.whl → 0.19.0rc1__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.
- dstack/_internal/cli/commands/gateway.py +15 -3
- dstack/_internal/cli/commands/logs.py +0 -22
- dstack/_internal/cli/commands/stats.py +8 -17
- dstack/_internal/cli/main.py +1 -5
- dstack/_internal/cli/services/configurators/fleet.py +4 -39
- dstack/_internal/cli/services/configurators/run.py +22 -20
- dstack/_internal/cli/services/profile.py +34 -83
- dstack/_internal/cli/utils/gateway.py +1 -1
- dstack/_internal/cli/utils/run.py +11 -0
- dstack/_internal/core/backends/__init__.py +56 -39
- dstack/_internal/core/backends/aws/__init__.py +0 -25
- dstack/_internal/core/backends/aws/auth.py +1 -10
- dstack/_internal/core/backends/aws/backend.py +26 -0
- dstack/_internal/core/backends/aws/compute.py +21 -45
- dstack/_internal/{server/services/backends/configurators/aws.py → core/backends/aws/configurator.py} +46 -85
- dstack/_internal/core/backends/aws/models.py +135 -0
- dstack/_internal/core/backends/aws/resources.py +1 -1
- dstack/_internal/core/backends/azure/__init__.py +0 -20
- dstack/_internal/core/backends/azure/auth.py +2 -11
- dstack/_internal/core/backends/azure/backend.py +21 -0
- dstack/_internal/core/backends/azure/compute.py +14 -28
- dstack/_internal/{server/services/backends/configurators/azure.py → core/backends/azure/configurator.py} +141 -210
- dstack/_internal/core/backends/azure/models.py +89 -0
- dstack/_internal/core/backends/base/__init__.py +0 -12
- dstack/_internal/core/backends/base/backend.py +18 -0
- dstack/_internal/core/backends/base/compute.py +153 -33
- dstack/_internal/core/backends/base/configurator.py +105 -0
- dstack/_internal/core/backends/base/models.py +14 -0
- dstack/_internal/core/backends/configurators.py +138 -0
- dstack/_internal/core/backends/cudo/__init__.py +0 -15
- dstack/_internal/core/backends/cudo/backend.py +16 -0
- dstack/_internal/core/backends/cudo/compute.py +8 -26
- dstack/_internal/core/backends/cudo/configurator.py +72 -0
- dstack/_internal/core/backends/cudo/models.py +37 -0
- dstack/_internal/core/backends/datacrunch/__init__.py +0 -15
- dstack/_internal/core/backends/datacrunch/backend.py +16 -0
- dstack/_internal/core/backends/datacrunch/compute.py +8 -25
- dstack/_internal/core/backends/datacrunch/configurator.py +66 -0
- dstack/_internal/core/backends/datacrunch/models.py +38 -0
- dstack/_internal/core/{models/backends/dstack.py → backends/dstack/models.py} +7 -7
- dstack/_internal/core/backends/gcp/__init__.py +0 -16
- dstack/_internal/core/backends/gcp/auth.py +2 -11
- dstack/_internal/core/backends/gcp/backend.py +17 -0
- dstack/_internal/core/backends/gcp/compute.py +14 -44
- dstack/_internal/{server/services/backends/configurators/gcp.py → core/backends/gcp/configurator.py} +46 -103
- dstack/_internal/core/backends/gcp/models.py +125 -0
- dstack/_internal/core/backends/kubernetes/__init__.py +0 -15
- dstack/_internal/core/backends/kubernetes/backend.py +16 -0
- dstack/_internal/core/backends/kubernetes/compute.py +16 -5
- dstack/_internal/core/backends/kubernetes/configurator.py +55 -0
- dstack/_internal/core/backends/kubernetes/models.py +72 -0
- dstack/_internal/core/backends/lambdalabs/__init__.py +0 -16
- dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
- dstack/_internal/core/backends/lambdalabs/compute.py +7 -28
- dstack/_internal/core/backends/lambdalabs/configurator.py +82 -0
- dstack/_internal/core/backends/lambdalabs/models.py +37 -0
- dstack/_internal/core/backends/local/__init__.py +0 -13
- dstack/_internal/core/backends/local/backend.py +14 -0
- dstack/_internal/core/backends/local/compute.py +16 -2
- dstack/_internal/core/backends/models.py +128 -0
- dstack/_internal/core/backends/oci/__init__.py +0 -15
- dstack/_internal/core/backends/oci/auth.py +1 -5
- dstack/_internal/core/backends/oci/backend.py +16 -0
- dstack/_internal/core/backends/oci/compute.py +9 -23
- dstack/_internal/{server/services/backends/configurators/oci.py → core/backends/oci/configurator.py} +40 -85
- dstack/_internal/core/{models/backends/oci.py → backends/oci/models.py} +24 -25
- dstack/_internal/core/backends/oci/region.py +1 -1
- dstack/_internal/core/backends/runpod/__init__.py +0 -15
- dstack/_internal/core/backends/runpod/backend.py +16 -0
- dstack/_internal/core/backends/runpod/compute.py +28 -6
- dstack/_internal/core/backends/runpod/configurator.py +59 -0
- dstack/_internal/core/backends/runpod/models.py +54 -0
- dstack/_internal/core/backends/template/__init__.py +0 -0
- dstack/_internal/core/backends/tensordock/__init__.py +0 -15
- dstack/_internal/core/backends/tensordock/backend.py +16 -0
- dstack/_internal/core/backends/tensordock/compute.py +8 -27
- dstack/_internal/core/backends/tensordock/configurator.py +68 -0
- dstack/_internal/core/backends/tensordock/models.py +38 -0
- dstack/_internal/core/backends/vastai/__init__.py +0 -15
- dstack/_internal/core/backends/vastai/backend.py +16 -0
- dstack/_internal/core/backends/vastai/compute.py +2 -2
- dstack/_internal/core/backends/vastai/configurator.py +66 -0
- dstack/_internal/core/backends/vastai/models.py +37 -0
- dstack/_internal/core/backends/vultr/__init__.py +0 -15
- dstack/_internal/core/backends/vultr/backend.py +16 -0
- dstack/_internal/core/backends/vultr/compute.py +10 -24
- dstack/_internal/core/backends/vultr/configurator.py +64 -0
- dstack/_internal/core/backends/vultr/models.py +34 -0
- dstack/_internal/core/models/backends/__init__.py +0 -184
- dstack/_internal/core/models/backends/base.py +0 -19
- dstack/_internal/core/models/configurations.py +22 -16
- dstack/_internal/core/models/envs.py +4 -3
- dstack/_internal/core/models/fleets.py +17 -22
- dstack/_internal/core/models/gateways.py +3 -3
- dstack/_internal/core/models/instances.py +24 -0
- dstack/_internal/core/models/profiles.py +85 -45
- dstack/_internal/core/models/projects.py +1 -1
- dstack/_internal/core/models/repos/base.py +0 -5
- dstack/_internal/core/models/repos/local.py +3 -3
- dstack/_internal/core/models/repos/remote.py +26 -12
- dstack/_internal/core/models/repos/virtual.py +1 -1
- dstack/_internal/core/models/resources.py +45 -76
- dstack/_internal/core/models/runs.py +21 -19
- dstack/_internal/core/models/volumes.py +1 -3
- dstack/_internal/core/services/profiles.py +7 -16
- dstack/_internal/core/services/repos.py +0 -4
- dstack/_internal/server/app.py +11 -4
- dstack/_internal/server/background/__init__.py +10 -0
- dstack/_internal/server/background/tasks/process_gateways.py +4 -8
- dstack/_internal/server/background/tasks/process_instances.py +14 -9
- dstack/_internal/server/background/tasks/process_metrics.py +1 -1
- dstack/_internal/server/background/tasks/process_placement_groups.py +5 -1
- dstack/_internal/server/background/tasks/process_prometheus_metrics.py +135 -0
- dstack/_internal/server/background/tasks/process_running_jobs.py +80 -24
- dstack/_internal/server/background/tasks/process_runs.py +1 -0
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +20 -38
- dstack/_internal/server/background/tasks/process_volumes.py +5 -2
- dstack/_internal/server/migrations/versions/60e444118b6d_add_jobprometheusmetrics.py +40 -0
- dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
- dstack/_internal/server/migrations/versions/98d1b92988bc_add_jobterminationreason_terminated_due_.py +140 -0
- dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
- dstack/_internal/server/models.py +59 -9
- dstack/_internal/server/routers/backends.py +14 -23
- dstack/_internal/server/routers/instances.py +3 -4
- dstack/_internal/server/routers/metrics.py +31 -10
- dstack/_internal/server/routers/prometheus.py +36 -0
- dstack/_internal/server/routers/repos.py +1 -2
- dstack/_internal/server/routers/runs.py +13 -59
- dstack/_internal/server/schemas/gateways.py +14 -23
- dstack/_internal/server/schemas/projects.py +7 -2
- dstack/_internal/server/schemas/repos.py +2 -38
- dstack/_internal/server/schemas/runner.py +1 -0
- dstack/_internal/server/schemas/runs.py +1 -24
- dstack/_internal/server/security/permissions.py +1 -1
- dstack/_internal/server/services/backends/__init__.py +85 -158
- dstack/_internal/server/services/config.py +53 -567
- dstack/_internal/server/services/fleets.py +9 -103
- dstack/_internal/server/services/gateways/__init__.py +13 -4
- dstack/_internal/server/services/{pools.py → instances.py} +22 -329
- dstack/_internal/server/services/jobs/__init__.py +9 -6
- dstack/_internal/server/services/jobs/configurators/base.py +25 -1
- dstack/_internal/server/services/jobs/configurators/dev.py +9 -1
- dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
- dstack/_internal/server/services/metrics.py +131 -72
- dstack/_internal/server/services/offers.py +1 -1
- dstack/_internal/server/services/projects.py +23 -14
- dstack/_internal/server/services/prometheus.py +245 -0
- dstack/_internal/server/services/runner/client.py +14 -3
- dstack/_internal/server/services/runs.py +67 -31
- dstack/_internal/server/services/volumes.py +9 -4
- dstack/_internal/server/settings.py +3 -0
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/{main-fe8fd9db55df8d10e648.js → main-4fd5a4770eff59325ee3.js} +68 -15
- dstack/_internal/server/statics/{main-fe8fd9db55df8d10e648.js.map → main-4fd5a4770eff59325ee3.js.map} +1 -1
- dstack/_internal/server/statics/{main-7510e71dfa9749a4e70e.css → main-da9f8c06a69c20dac23e.css} +1 -1
- dstack/_internal/server/statics/static/media/entraID.d65d1f3e9486a8e56d24fc07b3230885.svg +9 -0
- dstack/_internal/server/testing/common.py +75 -32
- dstack/_internal/utils/json_schema.py +6 -0
- dstack/_internal/utils/ssh.py +2 -1
- dstack/api/__init__.py +4 -0
- dstack/api/_public/__init__.py +16 -20
- dstack/api/_public/backends.py +1 -1
- dstack/api/_public/repos.py +36 -36
- dstack/api/_public/runs.py +170 -83
- dstack/api/server/__init__.py +11 -13
- dstack/api/server/_backends.py +12 -16
- dstack/api/server/_fleets.py +15 -55
- dstack/api/server/_gateways.py +3 -14
- dstack/api/server/_repos.py +1 -4
- dstack/api/server/_runs.py +21 -96
- dstack/api/server/_volumes.py +10 -5
- dstack/api/utils.py +3 -0
- dstack/version.py +1 -1
- {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/METADATA +10 -1
- {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/RECORD +229 -206
- tests/_internal/cli/services/configurators/test_profile.py +6 -6
- tests/_internal/core/backends/aws/test_configurator.py +35 -0
- tests/_internal/core/backends/aws/test_resources.py +1 -1
- tests/_internal/core/backends/azure/test_configurator.py +61 -0
- tests/_internal/core/backends/cudo/__init__.py +0 -0
- tests/_internal/core/backends/cudo/test_configurator.py +37 -0
- tests/_internal/core/backends/datacrunch/__init__.py +0 -0
- tests/_internal/core/backends/datacrunch/test_configurator.py +17 -0
- tests/_internal/core/backends/gcp/test_configurator.py +42 -0
- tests/_internal/core/backends/kubernetes/test_configurator.py +43 -0
- tests/_internal/core/backends/lambdalabs/__init__.py +0 -0
- tests/_internal/core/backends/lambdalabs/test_configurator.py +38 -0
- tests/_internal/core/backends/oci/test_configurator.py +55 -0
- tests/_internal/core/backends/runpod/__init__.py +0 -0
- tests/_internal/core/backends/runpod/test_configurator.py +33 -0
- tests/_internal/core/backends/tensordock/__init__.py +0 -0
- tests/_internal/core/backends/tensordock/test_configurator.py +38 -0
- tests/_internal/core/backends/vastai/__init__.py +0 -0
- tests/_internal/core/backends/vastai/test_configurator.py +33 -0
- tests/_internal/core/backends/vultr/__init__.py +0 -0
- tests/_internal/core/backends/vultr/test_configurator.py +33 -0
- tests/_internal/server/background/tasks/test_process_gateways.py +4 -0
- tests/_internal/server/background/tasks/test_process_instances.py +49 -48
- tests/_internal/server/background/tasks/test_process_metrics.py +0 -3
- tests/_internal/server/background/tasks/test_process_placement_groups.py +2 -0
- tests/_internal/server/background/tasks/test_process_prometheus_metrics.py +186 -0
- tests/_internal/server/background/tasks/test_process_running_jobs.py +123 -19
- tests/_internal/server/background/tasks/test_process_runs.py +8 -22
- tests/_internal/server/background/tasks/test_process_submitted_jobs.py +3 -40
- tests/_internal/server/background/tasks/test_process_submitted_volumes.py +2 -0
- tests/_internal/server/background/tasks/test_process_terminating_jobs.py +10 -15
- tests/_internal/server/routers/test_backends.py +6 -764
- tests/_internal/server/routers/test_fleets.py +2 -26
- tests/_internal/server/routers/test_gateways.py +27 -3
- tests/_internal/server/routers/test_instances.py +0 -10
- tests/_internal/server/routers/test_metrics.py +42 -0
- tests/_internal/server/routers/test_projects.py +56 -0
- tests/_internal/server/routers/test_prometheus.py +333 -0
- tests/_internal/server/routers/test_repos.py +0 -15
- tests/_internal/server/routers/test_runs.py +83 -275
- tests/_internal/server/routers/test_volumes.py +2 -3
- tests/_internal/server/services/backends/__init__.py +0 -0
- tests/_internal/server/services/jobs/configurators/test_task.py +35 -0
- tests/_internal/server/services/test_config.py +7 -4
- tests/_internal/server/services/test_fleets.py +1 -4
- tests/_internal/server/services/{test_pools.py → test_instances.py} +11 -49
- tests/_internal/server/services/test_metrics.py +167 -0
- tests/_internal/server/services/test_repos.py +1 -14
- tests/_internal/server/services/test_runs.py +0 -4
- dstack/_internal/cli/commands/pool.py +0 -581
- dstack/_internal/cli/commands/run.py +0 -75
- dstack/_internal/core/backends/aws/config.py +0 -18
- dstack/_internal/core/backends/azure/config.py +0 -12
- dstack/_internal/core/backends/base/config.py +0 -5
- dstack/_internal/core/backends/cudo/config.py +0 -9
- dstack/_internal/core/backends/datacrunch/config.py +0 -9
- dstack/_internal/core/backends/gcp/config.py +0 -22
- dstack/_internal/core/backends/kubernetes/config.py +0 -6
- dstack/_internal/core/backends/lambdalabs/config.py +0 -9
- dstack/_internal/core/backends/nebius/__init__.py +0 -15
- dstack/_internal/core/backends/nebius/api_client.py +0 -319
- dstack/_internal/core/backends/nebius/compute.py +0 -220
- dstack/_internal/core/backends/nebius/config.py +0 -6
- dstack/_internal/core/backends/nebius/types.py +0 -37
- dstack/_internal/core/backends/oci/config.py +0 -6
- dstack/_internal/core/backends/runpod/config.py +0 -9
- dstack/_internal/core/backends/tensordock/config.py +0 -9
- dstack/_internal/core/backends/vastai/config.py +0 -6
- dstack/_internal/core/backends/vultr/config.py +0 -9
- dstack/_internal/core/models/backends/aws.py +0 -86
- dstack/_internal/core/models/backends/azure.py +0 -68
- dstack/_internal/core/models/backends/cudo.py +0 -43
- dstack/_internal/core/models/backends/datacrunch.py +0 -44
- dstack/_internal/core/models/backends/gcp.py +0 -67
- dstack/_internal/core/models/backends/kubernetes.py +0 -40
- dstack/_internal/core/models/backends/lambdalabs.py +0 -43
- dstack/_internal/core/models/backends/nebius.py +0 -54
- dstack/_internal/core/models/backends/runpod.py +0 -40
- dstack/_internal/core/models/backends/tensordock.py +0 -44
- dstack/_internal/core/models/backends/vastai.py +0 -43
- dstack/_internal/core/models/backends/vultr.py +0 -40
- dstack/_internal/core/models/pools.py +0 -43
- dstack/_internal/server/routers/pools.py +0 -142
- dstack/_internal/server/schemas/pools.py +0 -38
- dstack/_internal/server/services/backends/configurators/base.py +0 -72
- dstack/_internal/server/services/backends/configurators/cudo.py +0 -87
- dstack/_internal/server/services/backends/configurators/datacrunch.py +0 -79
- dstack/_internal/server/services/backends/configurators/kubernetes.py +0 -63
- dstack/_internal/server/services/backends/configurators/lambdalabs.py +0 -98
- dstack/_internal/server/services/backends/configurators/nebius.py +0 -85
- dstack/_internal/server/services/backends/configurators/runpod.py +0 -97
- dstack/_internal/server/services/backends/configurators/tensordock.py +0 -82
- dstack/_internal/server/services/backends/configurators/vastai.py +0 -80
- dstack/_internal/server/services/backends/configurators/vultr.py +0 -80
- dstack/api/_public/pools.py +0 -41
- dstack/api/_public/resources.py +0 -105
- dstack/api/server/_pools.py +0 -63
- tests/_internal/server/routers/test_pools.py +0 -612
- /dstack/_internal/{server/services/backends/configurators → core/backends/dstack}/__init__.py +0 -0
- {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/LICENSE.md +0 -0
- {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/WHEEL +0 -0
- {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/entry_points.txt +0 -0
- {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -1,581 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import getpass
|
|
3
|
-
import ipaddress
|
|
4
|
-
import time
|
|
5
|
-
import urllib.parse
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Optional, Sequence, Tuple
|
|
8
|
-
|
|
9
|
-
from rich.console import Group
|
|
10
|
-
from rich.live import Live
|
|
11
|
-
from rich.table import Table
|
|
12
|
-
|
|
13
|
-
from dstack._internal.cli.commands import APIBaseCommand
|
|
14
|
-
from dstack._internal.cli.services.args import cpu_spec, disk_spec, gpu_spec, memory_spec
|
|
15
|
-
from dstack._internal.cli.services.profile import (
|
|
16
|
-
apply_profile_args,
|
|
17
|
-
register_profile_args,
|
|
18
|
-
)
|
|
19
|
-
from dstack._internal.cli.utils.common import confirm_ask, console
|
|
20
|
-
from dstack._internal.core.errors import CLIError, ServerClientError
|
|
21
|
-
from dstack._internal.core.models.instances import (
|
|
22
|
-
InstanceAvailability,
|
|
23
|
-
InstanceOfferWithAvailability,
|
|
24
|
-
InstanceStatus,
|
|
25
|
-
SSHKey,
|
|
26
|
-
)
|
|
27
|
-
from dstack._internal.core.models.pools import Instance, Pool
|
|
28
|
-
from dstack._internal.core.models.profiles import Profile, SpotPolicy, parse_duration
|
|
29
|
-
from dstack._internal.core.models.resources import DEFAULT_CPU_COUNT, DEFAULT_MEMORY_SIZE
|
|
30
|
-
from dstack._internal.core.models.runs import Requirements, get_policy_map
|
|
31
|
-
from dstack._internal.utils.common import pretty_date
|
|
32
|
-
from dstack._internal.utils.logging import get_logger
|
|
33
|
-
from dstack._internal.utils.ssh import convert_ssh_key_to_pem, generate_public_key, pkey_from_str
|
|
34
|
-
from dstack.api._public.resources import Resources
|
|
35
|
-
from dstack.api.utils import load_profile
|
|
36
|
-
|
|
37
|
-
REFRESH_RATE_PER_SEC = 5
|
|
38
|
-
LIVE_PROVISION_INTERVAL_SECS = 10
|
|
39
|
-
|
|
40
|
-
logger = get_logger(__name__)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class PoolCommand(APIBaseCommand):
|
|
44
|
-
NAME = "pool"
|
|
45
|
-
DESCRIPTION = "Manage pools"
|
|
46
|
-
|
|
47
|
-
def _register(self) -> None:
|
|
48
|
-
super()._register()
|
|
49
|
-
self._parser.set_defaults(subfunc=self._list)
|
|
50
|
-
|
|
51
|
-
subparsers = self._parser.add_subparsers(dest="action")
|
|
52
|
-
|
|
53
|
-
# list pools
|
|
54
|
-
list_parser = subparsers.add_parser(
|
|
55
|
-
"list",
|
|
56
|
-
help="List pools",
|
|
57
|
-
description="List available pools",
|
|
58
|
-
formatter_class=self._parser.formatter_class,
|
|
59
|
-
)
|
|
60
|
-
list_parser.add_argument("-v", "--verbose", help="Show more information")
|
|
61
|
-
list_parser.set_defaults(subfunc=self._list)
|
|
62
|
-
|
|
63
|
-
# create pool
|
|
64
|
-
create_parser = subparsers.add_parser(
|
|
65
|
-
"create", help="Create pool", formatter_class=self._parser.formatter_class
|
|
66
|
-
)
|
|
67
|
-
create_parser.add_argument(
|
|
68
|
-
"-n", "--name", dest="pool_name", help="The name of the pool", required=True
|
|
69
|
-
)
|
|
70
|
-
create_parser.set_defaults(subfunc=self._create)
|
|
71
|
-
|
|
72
|
-
# delete pool
|
|
73
|
-
delete_parser = subparsers.add_parser(
|
|
74
|
-
"delete", help="Delete pool", formatter_class=self._parser.formatter_class
|
|
75
|
-
)
|
|
76
|
-
delete_parser.add_argument(
|
|
77
|
-
"-n", "--name", dest="pool_name", help="The name of the pool", required=True
|
|
78
|
-
)
|
|
79
|
-
# TODO: support --force
|
|
80
|
-
delete_parser.set_defaults(subfunc=self._delete)
|
|
81
|
-
|
|
82
|
-
# show pool instances
|
|
83
|
-
ps_parser = subparsers.add_parser(
|
|
84
|
-
"ps",
|
|
85
|
-
help="Show pool instances",
|
|
86
|
-
description="Show instances in the pool",
|
|
87
|
-
formatter_class=self._parser.formatter_class,
|
|
88
|
-
)
|
|
89
|
-
ps_parser.add_argument(
|
|
90
|
-
"--pool",
|
|
91
|
-
dest="pool_name",
|
|
92
|
-
help="The name of the pool. If not set, the default pool will be used",
|
|
93
|
-
)
|
|
94
|
-
ps_parser.add_argument(
|
|
95
|
-
"-w",
|
|
96
|
-
"--watch",
|
|
97
|
-
help="Watch instances in realtime",
|
|
98
|
-
action="store_true",
|
|
99
|
-
)
|
|
100
|
-
ps_parser.set_defaults(subfunc=self._ps)
|
|
101
|
-
|
|
102
|
-
# add instance
|
|
103
|
-
add_parser = subparsers.add_parser(
|
|
104
|
-
"add", help="Add instance to pool", formatter_class=self._parser.formatter_class
|
|
105
|
-
)
|
|
106
|
-
self._parser.add_argument(
|
|
107
|
-
"--max-offers",
|
|
108
|
-
help="Number of offers to show in the run plan",
|
|
109
|
-
type=int,
|
|
110
|
-
default=3,
|
|
111
|
-
)
|
|
112
|
-
add_parser.add_argument(
|
|
113
|
-
"-y", "--yes", help="Don't ask for confirmation", action="store_true"
|
|
114
|
-
)
|
|
115
|
-
register_profile_args(add_parser, pool_add=True)
|
|
116
|
-
register_resource_args(add_parser)
|
|
117
|
-
add_parser.set_defaults(subfunc=self._add)
|
|
118
|
-
|
|
119
|
-
# remove instance
|
|
120
|
-
remove_parser = subparsers.add_parser(
|
|
121
|
-
"rm",
|
|
122
|
-
help="Remove instance from the pool",
|
|
123
|
-
formatter_class=self._parser.formatter_class,
|
|
124
|
-
aliases=["remove"],
|
|
125
|
-
)
|
|
126
|
-
remove_parser.add_argument(
|
|
127
|
-
"instance_name",
|
|
128
|
-
help="The name of the instance",
|
|
129
|
-
)
|
|
130
|
-
remove_parser.add_argument(
|
|
131
|
-
"--pool",
|
|
132
|
-
dest="pool_name",
|
|
133
|
-
help="The name of the pool. If not set, the default pool will be used",
|
|
134
|
-
)
|
|
135
|
-
remove_parser.add_argument(
|
|
136
|
-
"--force",
|
|
137
|
-
action="store_true",
|
|
138
|
-
help="The name of the instance",
|
|
139
|
-
)
|
|
140
|
-
remove_parser.add_argument(
|
|
141
|
-
"-y", "--yes", help="Don't ask for confirmation", action="store_true"
|
|
142
|
-
)
|
|
143
|
-
remove_parser.set_defaults(subfunc=self._remove)
|
|
144
|
-
|
|
145
|
-
# pool set-default
|
|
146
|
-
set_default_parser = subparsers.add_parser(
|
|
147
|
-
"set-default",
|
|
148
|
-
help="Set the project's default pool",
|
|
149
|
-
formatter_class=self._parser.formatter_class,
|
|
150
|
-
)
|
|
151
|
-
set_default_parser.add_argument(
|
|
152
|
-
"--pool", dest="pool_name", help="The name of the pool", required=True
|
|
153
|
-
)
|
|
154
|
-
set_default_parser.set_defaults(subfunc=self._set_default)
|
|
155
|
-
|
|
156
|
-
# add-ssh
|
|
157
|
-
add_ssh = subparsers.add_parser(
|
|
158
|
-
"add-ssh",
|
|
159
|
-
help="Add remote instance to pool",
|
|
160
|
-
formatter_class=self._parser.formatter_class,
|
|
161
|
-
)
|
|
162
|
-
add_ssh.add_argument("destination")
|
|
163
|
-
add_ssh.add_argument(
|
|
164
|
-
"-i",
|
|
165
|
-
metavar="SSH_PRIVATE_KEY",
|
|
166
|
-
help="The private SSH key path for SSH",
|
|
167
|
-
type=Path,
|
|
168
|
-
dest="ssh_identity_file",
|
|
169
|
-
required=True,
|
|
170
|
-
)
|
|
171
|
-
add_ssh.add_argument("-p", help="SSH port to connect", dest="ssh_port", type=int)
|
|
172
|
-
add_ssh.add_argument("-l", help="User to login", dest="login_name")
|
|
173
|
-
add_ssh.add_argument("--region", help="Host region", dest="region")
|
|
174
|
-
add_ssh.add_argument("--pool", help="Pool name", dest="pool_name")
|
|
175
|
-
add_ssh.add_argument("--name", dest="instance_name", help="Set the name of the instance")
|
|
176
|
-
add_ssh.add_argument(
|
|
177
|
-
"--network",
|
|
178
|
-
dest="network",
|
|
179
|
-
help="Network address for multinode setup. Format <ip address>/<netmask>",
|
|
180
|
-
)
|
|
181
|
-
add_ssh.set_defaults(subfunc=self._add_ssh)
|
|
182
|
-
|
|
183
|
-
def _list(self, args: argparse.Namespace) -> None:
|
|
184
|
-
pools = self.api.client.pool.list(self.api.project)
|
|
185
|
-
print_pool_table(pools, verbose=getattr(args, "verbose", False))
|
|
186
|
-
|
|
187
|
-
def _create(self, args: argparse.Namespace) -> None:
|
|
188
|
-
self.api.client.pool.create(self.api.project, args.pool_name)
|
|
189
|
-
console.print(f"Pool {args.pool_name!r} created")
|
|
190
|
-
|
|
191
|
-
def _delete(self, args: argparse.Namespace) -> None:
|
|
192
|
-
# TODO(egor-s): ask for confirmation
|
|
193
|
-
with console.status("Removing pool..."):
|
|
194
|
-
self.api.client.pool.delete(self.api.project, args.pool_name, False)
|
|
195
|
-
console.print(f"Pool {args.pool_name!r} removed")
|
|
196
|
-
|
|
197
|
-
def _remove(self, args: argparse.Namespace) -> None:
|
|
198
|
-
pool = self.api.client.pool.show(self.api.project, args.pool_name)
|
|
199
|
-
pool.instances = [i for i in pool.instances if i.name == args.instance_name]
|
|
200
|
-
if not pool.instances:
|
|
201
|
-
raise CLIError(f"Instance {args.instance_name!r} not found in pool {pool.name!r}")
|
|
202
|
-
|
|
203
|
-
console.print(f" [bold]Pool name[/] {pool.name}\n")
|
|
204
|
-
print_instance_table(pool.instances)
|
|
205
|
-
|
|
206
|
-
if not args.force and any(i.status == InstanceStatus.BUSY for i in pool.instances):
|
|
207
|
-
# TODO(egor-s): implement this logic in the server too
|
|
208
|
-
raise CLIError("Can't remove busy instance. Use `--force` to remove anyway")
|
|
209
|
-
|
|
210
|
-
if not args.yes and not confirm_ask(f"Remove instance {args.instance_name!r}?"):
|
|
211
|
-
console.print("\nExiting...")
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
with console.status("Removing instance..."):
|
|
215
|
-
self.api.client.pool.remove(
|
|
216
|
-
self.api.project, pool.name, args.instance_name, args.force
|
|
217
|
-
)
|
|
218
|
-
console.print(f"Instance {args.instance_name!r} removed")
|
|
219
|
-
|
|
220
|
-
def _set_default(self, args: argparse.Namespace) -> None:
|
|
221
|
-
self.api.client.pool.set_default(self.api.project, args.pool_name)
|
|
222
|
-
|
|
223
|
-
def _ps(self, args: argparse.Namespace) -> None:
|
|
224
|
-
pool_name_template = " [bold]Pool name[/] {}\n"
|
|
225
|
-
if not args.watch:
|
|
226
|
-
resp = self.api.client.pool.show(self.api.project, args.pool_name)
|
|
227
|
-
console.print(pool_name_template.format(resp.name))
|
|
228
|
-
print_instance_table(resp.instances)
|
|
229
|
-
return
|
|
230
|
-
|
|
231
|
-
try:
|
|
232
|
-
with Live(console=console, refresh_per_second=REFRESH_RATE_PER_SEC) as live:
|
|
233
|
-
while True:
|
|
234
|
-
resp = self.api.client.pool.show(self.api.project, args.pool_name)
|
|
235
|
-
group = Group(
|
|
236
|
-
pool_name_template.format(resp.name), get_instance_table(resp.instances)
|
|
237
|
-
)
|
|
238
|
-
live.update(group)
|
|
239
|
-
time.sleep(LIVE_PROVISION_INTERVAL_SECS)
|
|
240
|
-
except KeyboardInterrupt:
|
|
241
|
-
pass
|
|
242
|
-
|
|
243
|
-
def _add(self, args: argparse.Namespace) -> None:
|
|
244
|
-
super()._command(args)
|
|
245
|
-
|
|
246
|
-
resources = Resources(
|
|
247
|
-
cpu=args.cpu,
|
|
248
|
-
memory=args.memory,
|
|
249
|
-
gpu=args.gpu,
|
|
250
|
-
shm_size=args.shared_memory,
|
|
251
|
-
disk=args.disk,
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
profile = load_profile(Path.cwd(), args.profile)
|
|
255
|
-
apply_profile_args(args, profile, pool_add=True)
|
|
256
|
-
|
|
257
|
-
spot = get_policy_map(profile.spot_policy, default=SpotPolicy.ONDEMAND)
|
|
258
|
-
|
|
259
|
-
requirements = Requirements(
|
|
260
|
-
resources=resources,
|
|
261
|
-
max_price=profile.max_price,
|
|
262
|
-
spot=spot,
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
with console.status("Getting instances..."):
|
|
266
|
-
pool_offers = self.api.runs.get_offers(profile, requirements)
|
|
267
|
-
|
|
268
|
-
profile.pool_name = pool_offers.pool_name
|
|
269
|
-
|
|
270
|
-
print_offers_table(
|
|
271
|
-
profile=profile,
|
|
272
|
-
requirements=requirements,
|
|
273
|
-
instance_offers=pool_offers.instances,
|
|
274
|
-
offers_limit=args.max_offers,
|
|
275
|
-
)
|
|
276
|
-
if not pool_offers.instances:
|
|
277
|
-
console.print("\nThere are no offers with these criteria. Exiting...")
|
|
278
|
-
return
|
|
279
|
-
|
|
280
|
-
if not args.yes and not confirm_ask("Continue?"):
|
|
281
|
-
console.print("\nExiting...")
|
|
282
|
-
return
|
|
283
|
-
|
|
284
|
-
try:
|
|
285
|
-
with console.status("Creating instance..."):
|
|
286
|
-
# TODO: Instance name is not passed, so --instance does not work.
|
|
287
|
-
# There is profile.instance_name but it makes sense for `dstack run` only.
|
|
288
|
-
instance = self.api.runs.create_instance(profile, requirements)
|
|
289
|
-
except ServerClientError as e:
|
|
290
|
-
raise CLIError(e.msg)
|
|
291
|
-
console.print()
|
|
292
|
-
print_instance_table([instance])
|
|
293
|
-
|
|
294
|
-
def _add_ssh(self, args: argparse.Namespace) -> None:
|
|
295
|
-
super()._command(args)
|
|
296
|
-
|
|
297
|
-
# validate network
|
|
298
|
-
if args.network is not None:
|
|
299
|
-
try:
|
|
300
|
-
network = ipaddress.IPv4Interface(args.network).network
|
|
301
|
-
except ValueError as e:
|
|
302
|
-
console.print(
|
|
303
|
-
f"[error]Can't parse network. The address must be in the format <network address>/<netmask>, example `10.0.0.0/24`. Error: {e}[/]"
|
|
304
|
-
)
|
|
305
|
-
return
|
|
306
|
-
if not network.is_private:
|
|
307
|
-
console.print(
|
|
308
|
-
f"[error]The network must be private network. The {network} is not private[/]"
|
|
309
|
-
)
|
|
310
|
-
return
|
|
311
|
-
|
|
312
|
-
ssh_keys = []
|
|
313
|
-
if args.ssh_identity_file:
|
|
314
|
-
try:
|
|
315
|
-
private_key = convert_ssh_key_to_pem(args.ssh_identity_file.read_text())
|
|
316
|
-
try:
|
|
317
|
-
pub_key = args.ssh_identity_file.with_suffix(".pub").read_text()
|
|
318
|
-
except FileNotFoundError:
|
|
319
|
-
pub_key = generate_public_key(pkey_from_str(private_key))
|
|
320
|
-
ssh_key = SSHKey(public=pub_key, private=private_key)
|
|
321
|
-
ssh_keys.append(ssh_key)
|
|
322
|
-
except OSError:
|
|
323
|
-
console.print("[error]Unable to read the public key.[/]")
|
|
324
|
-
return
|
|
325
|
-
except ValueError:
|
|
326
|
-
console.print("[error]Key type is not supported.[/]")
|
|
327
|
-
return
|
|
328
|
-
|
|
329
|
-
login, ssh_host, port = parse_destination(args.destination)
|
|
330
|
-
|
|
331
|
-
ssh_port = 22
|
|
332
|
-
if port is not None:
|
|
333
|
-
ssh_port = port
|
|
334
|
-
if args.ssh_port is not None:
|
|
335
|
-
ssh_port = args.ssh_port
|
|
336
|
-
|
|
337
|
-
ssh_user = args.login_name
|
|
338
|
-
if ssh_user is None:
|
|
339
|
-
ssh_user = login
|
|
340
|
-
if ssh_user is None:
|
|
341
|
-
try:
|
|
342
|
-
ssh_user = getpass.getuser()
|
|
343
|
-
except OSError:
|
|
344
|
-
console.print("[error]Set the user name with the `-l` parameter.[/]")
|
|
345
|
-
return
|
|
346
|
-
|
|
347
|
-
result = self.api.client.pool.add_remote(
|
|
348
|
-
project_name=self.api.project,
|
|
349
|
-
pool_name=args.pool_name,
|
|
350
|
-
instance_name=args.instance_name,
|
|
351
|
-
instance_network=args.network,
|
|
352
|
-
region=args.region,
|
|
353
|
-
host=ssh_host,
|
|
354
|
-
port=ssh_port,
|
|
355
|
-
ssh_user=ssh_user,
|
|
356
|
-
ssh_keys=ssh_keys,
|
|
357
|
-
)
|
|
358
|
-
if not result:
|
|
359
|
-
console.print(f"[error]Failed to add remote instance {args.instance_name!r}[/]")
|
|
360
|
-
return
|
|
361
|
-
console.print(
|
|
362
|
-
f"Remote instance [code]{result.name!r}[/] has been added with status [secondary]{result.status.upper()}[/]"
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
def _command(self, args: argparse.Namespace) -> None:
|
|
366
|
-
super()._command(args)
|
|
367
|
-
logger.warning(
|
|
368
|
-
"Pools are deprecated in favor of fleets and will be removed in 0.19.0. "
|
|
369
|
-
"Learn more about fleets at https://dstack.ai/docs/concepts/fleets/"
|
|
370
|
-
)
|
|
371
|
-
# TODO handle 404 and other errors
|
|
372
|
-
args.subfunc(args)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def print_pool_table(pools: Sequence[Pool], verbose: bool) -> None:
|
|
376
|
-
table = Table(box=None)
|
|
377
|
-
table.add_column("NAME")
|
|
378
|
-
table.add_column("DEFAULT")
|
|
379
|
-
table.add_column("INSTANCES")
|
|
380
|
-
if verbose:
|
|
381
|
-
table.add_column("CREATED")
|
|
382
|
-
|
|
383
|
-
sorted_pools = sorted(pools, key=lambda r: r.name)
|
|
384
|
-
for pool in sorted_pools:
|
|
385
|
-
default_mark = "default" if pool.default else ""
|
|
386
|
-
style = "success" if pool.total_instances == pool.available_instances else "error"
|
|
387
|
-
health = f"[{style}]{pool.available_instances}/{pool.total_instances}[/]"
|
|
388
|
-
row = [pool.name, default_mark, health]
|
|
389
|
-
if verbose:
|
|
390
|
-
row.append(pretty_date(pool.created_at))
|
|
391
|
-
table.add_row(*row)
|
|
392
|
-
|
|
393
|
-
console.print(table)
|
|
394
|
-
console.print()
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
def print_instance_table(instances: Sequence[Instance]) -> None:
|
|
398
|
-
console.print(get_instance_table(instances))
|
|
399
|
-
console.print()
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def get_instance_table(instances: Sequence[Instance]) -> Table:
|
|
403
|
-
table = Table(box=None)
|
|
404
|
-
table.add_column("INSTANCE", no_wrap=True)
|
|
405
|
-
table.add_column("BACKEND")
|
|
406
|
-
table.add_column("REGION")
|
|
407
|
-
table.add_column("RESOURCES")
|
|
408
|
-
table.add_column("SPOT")
|
|
409
|
-
table.add_column("PRICE")
|
|
410
|
-
table.add_column("STATUS")
|
|
411
|
-
table.add_column("CREATED")
|
|
412
|
-
|
|
413
|
-
for instance in instances:
|
|
414
|
-
resources = ""
|
|
415
|
-
spot = ""
|
|
416
|
-
if instance.instance_type is not None:
|
|
417
|
-
resources = instance.instance_type.resources.pretty_format()
|
|
418
|
-
spot = "yes" if instance.instance_type.resources.spot else "no"
|
|
419
|
-
|
|
420
|
-
status = instance.status.value
|
|
421
|
-
if instance.status in [InstanceStatus.IDLE, InstanceStatus.BUSY] and instance.unreachable:
|
|
422
|
-
status += "\n(unreachable)"
|
|
423
|
-
|
|
424
|
-
row = [
|
|
425
|
-
instance.name,
|
|
426
|
-
(instance.backend or "").replace("remote", "ssh"),
|
|
427
|
-
instance.region or "",
|
|
428
|
-
resources,
|
|
429
|
-
spot,
|
|
430
|
-
f"${instance.price:.4}" if instance.price is not None else "",
|
|
431
|
-
status,
|
|
432
|
-
pretty_date(instance.created),
|
|
433
|
-
]
|
|
434
|
-
table.add_row(*row)
|
|
435
|
-
|
|
436
|
-
return table
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
def print_offers_table(
|
|
440
|
-
profile: Profile,
|
|
441
|
-
requirements: Requirements,
|
|
442
|
-
instance_offers: Sequence[InstanceOfferWithAvailability],
|
|
443
|
-
offers_limit: int,
|
|
444
|
-
) -> None:
|
|
445
|
-
pretty_req = requirements.pretty_format(resources_only=True)
|
|
446
|
-
max_price = f"${requirements.max_price:g}" if requirements.max_price else "-"
|
|
447
|
-
termination_policy = profile.termination_policy
|
|
448
|
-
termination_idle_time = f"{parse_duration(profile.termination_idle_time)}s"
|
|
449
|
-
|
|
450
|
-
if requirements.spot is None:
|
|
451
|
-
spot_policy = "auto"
|
|
452
|
-
elif requirements.spot:
|
|
453
|
-
spot_policy = "spot"
|
|
454
|
-
else:
|
|
455
|
-
spot_policy = "on-demand"
|
|
456
|
-
|
|
457
|
-
def th(s: str) -> str:
|
|
458
|
-
return f"[bold]{s}[/bold]"
|
|
459
|
-
|
|
460
|
-
props = Table(box=None, show_header=False)
|
|
461
|
-
props.add_column(no_wrap=True) # key
|
|
462
|
-
props.add_column() # value
|
|
463
|
-
|
|
464
|
-
props.add_row(th("Pool"), profile.pool_name)
|
|
465
|
-
props.add_row(th("Min resources"), pretty_req)
|
|
466
|
-
props.add_row(th("Max price"), max_price)
|
|
467
|
-
props.add_row(th("Spot policy"), spot_policy)
|
|
468
|
-
props.add_row(th("Termination policy"), termination_policy)
|
|
469
|
-
props.add_row(th("Termination idle time"), termination_idle_time)
|
|
470
|
-
|
|
471
|
-
offers_table = Table(box=None)
|
|
472
|
-
offers_table.add_column("#")
|
|
473
|
-
offers_table.add_column("BACKEND")
|
|
474
|
-
offers_table.add_column("REGION")
|
|
475
|
-
offers_table.add_column("INSTANCE")
|
|
476
|
-
offers_table.add_column("RESOURCES")
|
|
477
|
-
offers_table.add_column("SPOT")
|
|
478
|
-
offers_table.add_column("PRICE")
|
|
479
|
-
offers_table.add_column()
|
|
480
|
-
|
|
481
|
-
print_offers = instance_offers[:offers_limit]
|
|
482
|
-
|
|
483
|
-
for index, offer in enumerate(print_offers, start=1):
|
|
484
|
-
resources = offer.instance.resources
|
|
485
|
-
|
|
486
|
-
availability = ""
|
|
487
|
-
if offer.availability in {
|
|
488
|
-
InstanceAvailability.NOT_AVAILABLE,
|
|
489
|
-
InstanceAvailability.NO_QUOTA,
|
|
490
|
-
}:
|
|
491
|
-
availability = offer.availability.value.replace("_", " ").title()
|
|
492
|
-
offers_table.add_row(
|
|
493
|
-
f"{index}",
|
|
494
|
-
offer.backend.replace("remote", "ssh"),
|
|
495
|
-
offer.region,
|
|
496
|
-
offer.instance.name,
|
|
497
|
-
resources.pretty_format(),
|
|
498
|
-
"yes" if resources.spot else "no",
|
|
499
|
-
f"${offer.price:g}",
|
|
500
|
-
availability,
|
|
501
|
-
style=None if index == 1 else "secondary",
|
|
502
|
-
)
|
|
503
|
-
if len(print_offers) > offers_limit:
|
|
504
|
-
offers_table.add_row("", "...", style="secondary")
|
|
505
|
-
|
|
506
|
-
console.print(props)
|
|
507
|
-
console.print()
|
|
508
|
-
if len(print_offers) > 0:
|
|
509
|
-
console.print(offers_table)
|
|
510
|
-
console.print()
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
def register_resource_args(parser: argparse.ArgumentParser) -> None:
|
|
514
|
-
resources_group = parser.add_argument_group("Resources")
|
|
515
|
-
resources_group.add_argument(
|
|
516
|
-
"--cpu",
|
|
517
|
-
help=f"Request the CPU count. Default: {DEFAULT_CPU_COUNT}",
|
|
518
|
-
dest="cpu",
|
|
519
|
-
metavar="SPEC",
|
|
520
|
-
default=DEFAULT_CPU_COUNT,
|
|
521
|
-
type=cpu_spec,
|
|
522
|
-
)
|
|
523
|
-
|
|
524
|
-
resources_group.add_argument(
|
|
525
|
-
"--memory",
|
|
526
|
-
help="Request the size of RAM. "
|
|
527
|
-
f"The format is [code]SIZE[/]:[code]MB|GB|TB[/]. Default: {DEFAULT_MEMORY_SIZE}",
|
|
528
|
-
dest="memory",
|
|
529
|
-
metavar="SIZE",
|
|
530
|
-
default=DEFAULT_MEMORY_SIZE,
|
|
531
|
-
type=memory_spec,
|
|
532
|
-
)
|
|
533
|
-
|
|
534
|
-
resources_group.add_argument(
|
|
535
|
-
"--shared-memory",
|
|
536
|
-
help="Request the size of Shared Memory. The format is [code]SIZE[/]:[code]MB|GB|TB[/].",
|
|
537
|
-
dest="shared_memory",
|
|
538
|
-
default=None,
|
|
539
|
-
metavar="SIZE",
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
resources_group.add_argument(
|
|
543
|
-
"--gpu",
|
|
544
|
-
help="Request GPU for the run. "
|
|
545
|
-
"The format is [code]NAME[/]:[code]COUNT[/]:[code]MEMORY[/] (all parts are optional)",
|
|
546
|
-
dest="gpu",
|
|
547
|
-
default=None,
|
|
548
|
-
metavar="SPEC",
|
|
549
|
-
type=gpu_spec,
|
|
550
|
-
)
|
|
551
|
-
|
|
552
|
-
resources_group.add_argument(
|
|
553
|
-
"--disk",
|
|
554
|
-
help="Request the size of disk for the run. Example [code]--disk 100GB..[/].",
|
|
555
|
-
dest="disk",
|
|
556
|
-
metavar="SIZE",
|
|
557
|
-
default=None,
|
|
558
|
-
type=disk_spec,
|
|
559
|
-
)
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
def parse_destination(destination: str) -> Tuple[Optional[str], str, Optional[int]]:
|
|
563
|
-
port = None
|
|
564
|
-
netloc = destination
|
|
565
|
-
|
|
566
|
-
if destination.startswith("ssh://"):
|
|
567
|
-
parse_result = urllib.parse.urlparse(destination)
|
|
568
|
-
netloc, _, netloc_port = parse_result.netloc.partition(":")
|
|
569
|
-
try:
|
|
570
|
-
port = int(netloc_port)
|
|
571
|
-
except ValueError:
|
|
572
|
-
pass
|
|
573
|
-
|
|
574
|
-
head, sep, tail = netloc.partition("@")
|
|
575
|
-
if sep == "@":
|
|
576
|
-
login = head
|
|
577
|
-
host = tail
|
|
578
|
-
else:
|
|
579
|
-
login = None
|
|
580
|
-
host = head
|
|
581
|
-
return login, host, port
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
from dstack._internal.cli.commands import APIBaseCommand
|
|
5
|
-
from dstack._internal.cli.services.configurators import get_run_configurator_class
|
|
6
|
-
from dstack._internal.cli.services.configurators.run import BaseRunConfigurator
|
|
7
|
-
from dstack._internal.core.models.configurations import RunConfigurationType
|
|
8
|
-
from dstack._internal.utils.logging import get_logger
|
|
9
|
-
from dstack.api.utils import load_configuration
|
|
10
|
-
|
|
11
|
-
logger = get_logger(__name__)
|
|
12
|
-
NOTSET = object()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class RunCommand(APIBaseCommand):
|
|
16
|
-
NAME = "run"
|
|
17
|
-
DESCRIPTION = "Run a configuration"
|
|
18
|
-
DEFAULT_HELP = False
|
|
19
|
-
|
|
20
|
-
def _register(self):
|
|
21
|
-
super()._register()
|
|
22
|
-
self._parser.add_argument(
|
|
23
|
-
"-h",
|
|
24
|
-
"--help",
|
|
25
|
-
nargs="?",
|
|
26
|
-
type=RunConfigurationType,
|
|
27
|
-
default=NOTSET,
|
|
28
|
-
help="Show this help message and exit. TYPE is one of [code]task[/], [code]dev-environment[/], [code]service[/]",
|
|
29
|
-
dest="help",
|
|
30
|
-
metavar="TYPE",
|
|
31
|
-
)
|
|
32
|
-
self._parser.add_argument("working_dir")
|
|
33
|
-
self._parser.add_argument(
|
|
34
|
-
"-f",
|
|
35
|
-
"--file",
|
|
36
|
-
type=Path,
|
|
37
|
-
metavar="FILE",
|
|
38
|
-
help="The path to the configuration file. Defaults to [code]$PWD/.dstack.yml[/]",
|
|
39
|
-
dest="configuration_file",
|
|
40
|
-
)
|
|
41
|
-
self._parser.add_argument(
|
|
42
|
-
"-y",
|
|
43
|
-
"--yes",
|
|
44
|
-
help="Do not ask for confirmation",
|
|
45
|
-
action="store_true",
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
def _command(self, args: argparse.Namespace):
|
|
49
|
-
if args.help is not NOTSET:
|
|
50
|
-
if args.help is not None:
|
|
51
|
-
configurator_class = get_run_configurator_class(RunConfigurationType(args.help))
|
|
52
|
-
else:
|
|
53
|
-
configurator_class = BaseRunConfigurator
|
|
54
|
-
configurator_class.register_args(self._parser)
|
|
55
|
-
self._parser.print_help()
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
super()._command(args)
|
|
59
|
-
|
|
60
|
-
logger.warning("[code]dstack run[/] is deprecated in favor of [code]dstack apply[/].")
|
|
61
|
-
|
|
62
|
-
configuration_path, configuration = load_configuration(
|
|
63
|
-
Path.cwd(), configuration_file=args.configuration_file
|
|
64
|
-
)
|
|
65
|
-
configurator_class = get_run_configurator_class(configuration.type)
|
|
66
|
-
configurator = configurator_class(api_client=self.api)
|
|
67
|
-
configurator_parser = configurator.get_parser()
|
|
68
|
-
known, unknown = configurator_parser.parse_known_args(args.unknown)
|
|
69
|
-
configurator.apply_configuration(
|
|
70
|
-
conf=configuration,
|
|
71
|
-
configuration_path=configuration_path,
|
|
72
|
-
command_args=args,
|
|
73
|
-
configurator_args=known,
|
|
74
|
-
unknown_args=unknown,
|
|
75
|
-
)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from dstack._internal.core.backends.base.config import BackendConfig
|
|
2
|
-
from dstack._internal.core.models.backends.aws import AnyAWSCreds, AWSStoredConfig
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class AWSConfig(AWSStoredConfig, BackendConfig):
|
|
6
|
-
creds: AnyAWSCreds
|
|
7
|
-
|
|
8
|
-
@property
|
|
9
|
-
def allocate_public_ips(self) -> bool:
|
|
10
|
-
if self.public_ips is not None:
|
|
11
|
-
return self.public_ips
|
|
12
|
-
return True
|
|
13
|
-
|
|
14
|
-
@property
|
|
15
|
-
def use_default_vpcs(self) -> bool:
|
|
16
|
-
if self.default_vpcs is not None:
|
|
17
|
-
return self.default_vpcs
|
|
18
|
-
return True
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
from dstack._internal.core.backends.base.config import BackendConfig
|
|
2
|
-
from dstack._internal.core.models.backends.azure import AnyAzureCreds, AzureStoredConfig
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class AzureConfig(AzureStoredConfig, BackendConfig):
|
|
6
|
-
creds: AnyAzureCreds
|
|
7
|
-
|
|
8
|
-
@property
|
|
9
|
-
def allocate_public_ips(self) -> bool:
|
|
10
|
-
if self.public_ips is not None:
|
|
11
|
-
return self.public_ips
|
|
12
|
-
return True
|