dstack 0.19.33__py3-none-any.whl → 0.19.35__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dstack might be problematic. Click here for more details.
- dstack/_internal/cli/services/configurators/run.py +1 -1
- dstack/_internal/core/backends/base/compute.py +20 -1
- dstack/_internal/core/backends/base/models.py +10 -0
- dstack/_internal/core/backends/base/offers.py +4 -1
- dstack/_internal/core/backends/features.py +5 -0
- dstack/_internal/core/backends/gcp/compute.py +24 -9
- dstack/_internal/core/backends/gcp/models.py +4 -1
- dstack/_internal/core/backends/nebius/compute.py +28 -16
- dstack/_internal/core/backends/nebius/configurator.py +1 -1
- dstack/_internal/core/backends/nebius/models.py +4 -0
- dstack/_internal/core/backends/nebius/resources.py +41 -20
- dstack/_internal/core/backends/runpod/api_client.py +245 -59
- dstack/_internal/core/backends/runpod/compute.py +161 -14
- dstack/_internal/core/compatibility/runs.py +25 -4
- dstack/_internal/core/models/compute_groups.py +39 -0
- dstack/_internal/core/models/fleets.py +6 -1
- dstack/_internal/core/models/instances.py +2 -1
- dstack/_internal/core/models/profiles.py +3 -1
- dstack/_internal/core/models/runs.py +4 -0
- dstack/_internal/core/services/ssh/key_manager.py +56 -0
- dstack/_internal/server/app.py +14 -2
- dstack/_internal/server/background/__init__.py +7 -0
- dstack/_internal/server/background/tasks/process_compute_groups.py +164 -0
- dstack/_internal/server/background/tasks/process_instances.py +81 -49
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +179 -84
- dstack/_internal/server/migrations/env.py +20 -2
- dstack/_internal/server/migrations/versions/7d1ec2b920ac_add_computegroupmodel.py +93 -0
- dstack/_internal/server/models.py +42 -0
- dstack/_internal/server/routers/metrics.py +6 -2
- dstack/_internal/server/routers/runs.py +15 -6
- dstack/_internal/server/routers/users.py +7 -0
- dstack/_internal/server/services/compute_groups.py +22 -0
- dstack/_internal/server/services/fleets.py +1 -0
- dstack/_internal/server/services/jobs/__init__.py +31 -9
- dstack/_internal/server/services/jobs/configurators/base.py +3 -2
- dstack/_internal/server/services/offers.py +1 -0
- dstack/_internal/server/services/requirements/combine.py +1 -0
- dstack/_internal/server/services/runs.py +21 -3
- dstack/_internal/server/services/users.py +3 -3
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/{main-97c7e184573ca23f9fe4.js → main-e79754c136f1d8e4e7e6.js} +11 -11
- dstack/_internal/server/statics/{main-97c7e184573ca23f9fe4.js.map → main-e79754c136f1d8e4e7e6.js.map} +1 -1
- dstack/_internal/server/testing/common.py +55 -0
- dstack/_internal/server/utils/routers.py +18 -20
- dstack/_internal/settings.py +4 -1
- dstack/_internal/utils/version.py +22 -0
- dstack/api/_public/__init__.py +2 -2
- dstack/api/_public/runs.py +36 -39
- dstack/api/server/__init__.py +4 -0
- dstack/version.py +1 -1
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/METADATA +4 -4
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/RECORD +55 -50
- dstack/_internal/core/backends/nebius/fabrics.py +0 -49
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/WHEEL +0 -0
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -13,6 +13,7 @@ from dstack._internal.core.backends.base.compute import (
|
|
|
13
13
|
Compute,
|
|
14
14
|
ComputeWithCreateInstanceSupport,
|
|
15
15
|
ComputeWithGatewaySupport,
|
|
16
|
+
ComputeWithGroupProvisioningSupport,
|
|
16
17
|
ComputeWithMultinodeSupport,
|
|
17
18
|
ComputeWithPlacementGroupSupport,
|
|
18
19
|
ComputeWithPrivateGatewaySupport,
|
|
@@ -22,6 +23,10 @@ from dstack._internal.core.backends.base.compute import (
|
|
|
22
23
|
)
|
|
23
24
|
from dstack._internal.core.models.backends.base import BackendType
|
|
24
25
|
from dstack._internal.core.models.common import NetworkMode
|
|
26
|
+
from dstack._internal.core.models.compute_groups import (
|
|
27
|
+
ComputeGroupProvisioningData,
|
|
28
|
+
ComputeGroupStatus,
|
|
29
|
+
)
|
|
25
30
|
from dstack._internal.core.models.configurations import (
|
|
26
31
|
AnyRunConfiguration,
|
|
27
32
|
DevEnvironmentConfiguration,
|
|
@@ -83,6 +88,7 @@ from dstack._internal.core.models.volumes import (
|
|
|
83
88
|
)
|
|
84
89
|
from dstack._internal.server.models import (
|
|
85
90
|
BackendModel,
|
|
91
|
+
ComputeGroupModel,
|
|
86
92
|
DecryptedString,
|
|
87
93
|
FileArchiveModel,
|
|
88
94
|
FleetModel,
|
|
@@ -126,6 +132,8 @@ async def create_user(
|
|
|
126
132
|
global_role: GlobalRole = GlobalRole.ADMIN,
|
|
127
133
|
token: Optional[str] = None,
|
|
128
134
|
email: Optional[str] = None,
|
|
135
|
+
ssh_public_key: Optional[str] = None,
|
|
136
|
+
ssh_private_key: Optional[str] = None,
|
|
129
137
|
active: bool = True,
|
|
130
138
|
) -> UserModel:
|
|
131
139
|
if token is None:
|
|
@@ -137,6 +145,8 @@ async def create_user(
|
|
|
137
145
|
token=DecryptedString(plaintext=token),
|
|
138
146
|
token_hash=get_token_hash(token),
|
|
139
147
|
email=email,
|
|
148
|
+
ssh_public_key=ssh_public_key,
|
|
149
|
+
ssh_private_key=ssh_private_key,
|
|
140
150
|
active=active,
|
|
141
151
|
)
|
|
142
152
|
session.add(user)
|
|
@@ -349,6 +359,7 @@ async def create_job(
|
|
|
349
359
|
instance_assigned: bool = False,
|
|
350
360
|
disconnected_at: Optional[datetime] = None,
|
|
351
361
|
registered: bool = False,
|
|
362
|
+
waiting_master_job: Optional[bool] = None,
|
|
352
363
|
) -> JobModel:
|
|
353
364
|
if deployment_num is None:
|
|
354
365
|
deployment_num = run.deployment_num
|
|
@@ -380,6 +391,7 @@ async def create_job(
|
|
|
380
391
|
disconnected_at=disconnected_at,
|
|
381
392
|
probes=[],
|
|
382
393
|
registered=registered,
|
|
394
|
+
waiting_master_job=waiting_master_job,
|
|
383
395
|
)
|
|
384
396
|
session.add(job)
|
|
385
397
|
await session.commit()
|
|
@@ -451,6 +463,48 @@ def get_job_runtime_data(
|
|
|
451
463
|
)
|
|
452
464
|
|
|
453
465
|
|
|
466
|
+
def get_compute_group_provisioning_data(
|
|
467
|
+
compute_group_id: str = "test_compute_group",
|
|
468
|
+
compute_group_name: str = "test_compute_group",
|
|
469
|
+
backend: BackendType = BackendType.RUNPOD,
|
|
470
|
+
region: str = "US",
|
|
471
|
+
job_provisioning_datas: Optional[list[JobProvisioningData]] = None,
|
|
472
|
+
backend_data: Optional[str] = None,
|
|
473
|
+
) -> ComputeGroupProvisioningData:
|
|
474
|
+
if job_provisioning_datas is None:
|
|
475
|
+
job_provisioning_datas = []
|
|
476
|
+
return ComputeGroupProvisioningData(
|
|
477
|
+
compute_group_id=compute_group_id,
|
|
478
|
+
compute_group_name=compute_group_name,
|
|
479
|
+
backend=backend,
|
|
480
|
+
region=region,
|
|
481
|
+
job_provisioning_datas=job_provisioning_datas,
|
|
482
|
+
backend_data=backend_data,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
async def create_compute_group(
|
|
487
|
+
session: AsyncSession,
|
|
488
|
+
project: ProjectModel,
|
|
489
|
+
fleet: FleetModel,
|
|
490
|
+
status: ComputeGroupStatus = ComputeGroupStatus.RUNNING,
|
|
491
|
+
provisioning_data: Optional[ComputeGroupProvisioningData] = None,
|
|
492
|
+
last_processed_at: datetime = datetime(2023, 1, 2, 3, 4, tzinfo=timezone.utc),
|
|
493
|
+
):
|
|
494
|
+
if provisioning_data is None:
|
|
495
|
+
provisioning_data = get_compute_group_provisioning_data()
|
|
496
|
+
compute_group = ComputeGroupModel(
|
|
497
|
+
project=project,
|
|
498
|
+
fleet=fleet,
|
|
499
|
+
status=status,
|
|
500
|
+
provisioning_data=provisioning_data.json(),
|
|
501
|
+
last_processed_at=last_processed_at,
|
|
502
|
+
)
|
|
503
|
+
session.add(compute_group)
|
|
504
|
+
await session.commit()
|
|
505
|
+
return compute_group
|
|
506
|
+
|
|
507
|
+
|
|
454
508
|
async def create_probe(
|
|
455
509
|
session: AsyncSession,
|
|
456
510
|
job: JobModel,
|
|
@@ -1132,6 +1186,7 @@ class AsyncContextManager:
|
|
|
1132
1186
|
class ComputeMockSpec(
|
|
1133
1187
|
Compute,
|
|
1134
1188
|
ComputeWithCreateInstanceSupport,
|
|
1189
|
+
ComputeWithGroupProvisioningSupport,
|
|
1135
1190
|
ComputeWithPrivilegedSupport,
|
|
1136
1191
|
ComputeWithMultinodeSupport,
|
|
1137
1192
|
ComputeWithReservationSupport,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Optional
|
|
2
2
|
|
|
3
3
|
import orjson
|
|
4
|
+
import packaging.version
|
|
4
5
|
from fastapi import HTTPException, Request, Response, status
|
|
5
|
-
from packaging import version
|
|
6
6
|
|
|
7
7
|
from dstack._internal.core.errors import ServerClientError, ServerClientErrorCode
|
|
8
8
|
from dstack._internal.core.models.common import CoreModel
|
|
9
9
|
from dstack._internal.utils.json_utils import get_orjson_default_options, orjson_default
|
|
10
|
+
from dstack._internal.utils.version import parse_version
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class CustomORJSONResponse(Response):
|
|
@@ -122,8 +123,15 @@ def get_request_size(request: Request) -> int:
|
|
|
122
123
|
return int(request.headers["content-length"])
|
|
123
124
|
|
|
124
125
|
|
|
126
|
+
def get_client_version(request: Request) -> Optional[packaging.version.Version]:
|
|
127
|
+
version = request.headers.get("x-api-version")
|
|
128
|
+
if version is None:
|
|
129
|
+
return None
|
|
130
|
+
return parse_version(version)
|
|
131
|
+
|
|
132
|
+
|
|
125
133
|
def check_client_server_compatibility(
|
|
126
|
-
client_version: Optional[
|
|
134
|
+
client_version: Optional[packaging.version.Version],
|
|
127
135
|
server_version: Optional[str],
|
|
128
136
|
) -> Optional[CustomORJSONResponse]:
|
|
129
137
|
"""
|
|
@@ -132,28 +140,18 @@ def check_client_server_compatibility(
|
|
|
132
140
|
"""
|
|
133
141
|
if client_version is None or server_version is None:
|
|
134
142
|
return None
|
|
135
|
-
parsed_server_version =
|
|
136
|
-
|
|
137
|
-
if client_version == "latest":
|
|
143
|
+
parsed_server_version = parse_version(server_version)
|
|
144
|
+
if parsed_server_version is None:
|
|
138
145
|
return None
|
|
139
|
-
try:
|
|
140
|
-
parsed_client_version = version.parse(client_version)
|
|
141
|
-
except version.InvalidVersion:
|
|
142
|
-
return CustomORJSONResponse(
|
|
143
|
-
status_code=status.HTTP_400_BAD_REQUEST,
|
|
144
|
-
content={
|
|
145
|
-
"detail": get_server_client_error_details(
|
|
146
|
-
ServerClientError("Bad API version specified")
|
|
147
|
-
)
|
|
148
|
-
},
|
|
149
|
-
)
|
|
150
146
|
# We preserve full client backward compatibility across patch releases.
|
|
151
147
|
# Server is always partially backward-compatible (so no check).
|
|
152
|
-
if
|
|
153
|
-
|
|
154
|
-
or
|
|
148
|
+
if client_version > parsed_server_version and (
|
|
149
|
+
client_version.major > parsed_server_version.major
|
|
150
|
+
or client_version.minor > parsed_server_version.minor
|
|
155
151
|
):
|
|
156
|
-
return error_incompatible_versions(
|
|
152
|
+
return error_incompatible_versions(
|
|
153
|
+
str(client_version), server_version, ask_cli_update=False
|
|
154
|
+
)
|
|
157
155
|
return None
|
|
158
156
|
|
|
159
157
|
|
dstack/_internal/settings.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
from dstack import version
|
|
4
|
+
from dstack._internal.utils.version import parse_version
|
|
4
5
|
|
|
5
6
|
DSTACK_VERSION = os.getenv("DSTACK_VERSION", version.__version__)
|
|
6
|
-
if DSTACK_VERSION
|
|
7
|
+
if parse_version(DSTACK_VERSION) is None:
|
|
7
8
|
# The build backend (hatching) requires not None for versions,
|
|
8
9
|
# but the code currently treats None as dev version.
|
|
9
10
|
# TODO: update the code to treat 0.0.0 as dev version.
|
|
@@ -33,3 +34,5 @@ class FeatureFlags:
|
|
|
33
34
|
large features. This class may be empty if there are no such features in
|
|
34
35
|
development. Feature flags are environment variables of the form DSTACK_FF_*
|
|
35
36
|
"""
|
|
37
|
+
|
|
38
|
+
pass
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import packaging.version
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_version(version_string: str) -> Optional[packaging.version.Version]:
|
|
7
|
+
"""
|
|
8
|
+
Returns a `packaging.version.Version` instance or `None` if the version is dev/latest.
|
|
9
|
+
|
|
10
|
+
Values parsed as the dev/latest version:
|
|
11
|
+
* the "latest" literal
|
|
12
|
+
* any "0.0.0" release, e.g., "0.0.0", "0.0.0a1", "0.0.0.dev0"
|
|
13
|
+
"""
|
|
14
|
+
if version_string == "latest":
|
|
15
|
+
return None
|
|
16
|
+
try:
|
|
17
|
+
version = packaging.version.parse(version_string)
|
|
18
|
+
except packaging.version.InvalidVersion as e:
|
|
19
|
+
raise ValueError(f"Invalid version: {version_string}") from e
|
|
20
|
+
if version.release == (0, 0, 0):
|
|
21
|
+
return None
|
|
22
|
+
return version
|
dstack/api/_public/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ from dstack._internal.utils.logging import get_logger
|
|
|
6
6
|
from dstack._internal.utils.path import PathLike as PathLike
|
|
7
7
|
from dstack.api._public.backends import BackendCollection
|
|
8
8
|
from dstack.api._public.repos import RepoCollection
|
|
9
|
-
from dstack.api._public.runs import RunCollection
|
|
9
|
+
from dstack.api._public.runs import RunCollection
|
|
10
10
|
from dstack.api.server import APIClient
|
|
11
11
|
|
|
12
12
|
logger = get_logger(__name__)
|
|
@@ -42,7 +42,7 @@ class Client:
|
|
|
42
42
|
self._backends = BackendCollection(api_client, project_name)
|
|
43
43
|
self._runs = RunCollection(api_client, project_name, self)
|
|
44
44
|
if ssh_identity_file is not None:
|
|
45
|
-
|
|
45
|
+
logger.warning(
|
|
46
46
|
"[code]ssh_identity_file[/code] in [code]Client[/code] is deprecated and ignored; will be removed"
|
|
47
47
|
" since 0.19.40"
|
|
48
48
|
)
|
dstack/api/_public/runs.py
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import base64
|
|
2
|
-
import hashlib
|
|
3
|
-
import os
|
|
4
2
|
import queue
|
|
5
3
|
import tempfile
|
|
6
4
|
import threading
|
|
@@ -17,7 +15,6 @@ from urllib.parse import urlparse
|
|
|
17
15
|
from websocket import WebSocketApp
|
|
18
16
|
|
|
19
17
|
import dstack.api as api
|
|
20
|
-
from dstack._internal.cli.utils.common import warn
|
|
21
18
|
from dstack._internal.core.consts import DSTACK_RUNNER_HTTP_PORT, DSTACK_RUNNER_SSH_PORT
|
|
22
19
|
from dstack._internal.core.errors import ClientError, ConfigurationError, ResourceNotExistsError
|
|
23
20
|
from dstack._internal.core.models.backends.base import BackendType
|
|
@@ -48,10 +45,10 @@ from dstack._internal.core.models.runs import (
|
|
|
48
45
|
get_service_port,
|
|
49
46
|
)
|
|
50
47
|
from dstack._internal.core.models.runs import Run as RunModel
|
|
51
|
-
from dstack._internal.core.models.users import UserWithCreds
|
|
52
48
|
from dstack._internal.core.services.configs import ConfigManager
|
|
53
49
|
from dstack._internal.core.services.logs import URLReplacer
|
|
54
50
|
from dstack._internal.core.services.ssh.attach import SSHAttach
|
|
51
|
+
from dstack._internal.core.services.ssh.key_manager import UserSSHKeyManager
|
|
55
52
|
from dstack._internal.core.services.ssh.ports import PortsLock
|
|
56
53
|
from dstack._internal.server.schemas.logs import PollLogsRequest
|
|
57
54
|
from dstack._internal.utils.common import get_or_error, make_proxy_url
|
|
@@ -88,7 +85,7 @@ class Run(ABC):
|
|
|
88
85
|
self._ports_lock: Optional[PortsLock] = ports_lock
|
|
89
86
|
self._ssh_attach: Optional[SSHAttach] = None
|
|
90
87
|
if ssh_identity_file is not None:
|
|
91
|
-
|
|
88
|
+
logger.warning(
|
|
92
89
|
"[code]ssh_identity_file[/code] in [code]Run[/code] is deprecated and ignored; will be removed"
|
|
93
90
|
" since 0.19.40"
|
|
94
91
|
)
|
|
@@ -281,31 +278,20 @@ class Run(ABC):
|
|
|
281
278
|
dstack.api.PortUsedError: If ports are in use or the run is attached by another process.
|
|
282
279
|
"""
|
|
283
280
|
if not ssh_identity_file:
|
|
284
|
-
user = self._api_client.users.get_my_user()
|
|
285
|
-
run_ssh_key_pub = self._run.run_spec.ssh_key_pub
|
|
286
281
|
config_manager = ConfigManager()
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def key_opener(path, flags):
|
|
293
|
-
return os.open(path, flags, 0o600)
|
|
294
|
-
|
|
295
|
-
with open(ssh_identity_file, "wb", opener=key_opener) as f:
|
|
296
|
-
assert user.ssh_private_key
|
|
297
|
-
f.write(user.ssh_private_key.encode())
|
|
282
|
+
key_manager = UserSSHKeyManager(self._api_client, config_manager.dstack_ssh_dir)
|
|
283
|
+
if (
|
|
284
|
+
user_key := key_manager.get_user_key()
|
|
285
|
+
) and user_key.public_key == self._run.run_spec.ssh_key_pub:
|
|
286
|
+
ssh_identity_file = user_key.private_key_path
|
|
298
287
|
else:
|
|
299
288
|
if config_manager.dstack_key_path.exists():
|
|
300
289
|
# TODO: Remove since 0.19.40
|
|
301
|
-
|
|
302
|
-
f"Using legacy [code]{config_manager.dstack_key_path}[/code]."
|
|
303
|
-
" Future versions will use the user SSH key from the server.",
|
|
304
|
-
)
|
|
290
|
+
logger.debug(f"Using legacy [code]{config_manager.dstack_key_path}[/code].")
|
|
305
291
|
ssh_identity_file = config_manager.dstack_key_path
|
|
306
292
|
else:
|
|
307
293
|
raise ConfigurationError(
|
|
308
|
-
f"User SSH key
|
|
294
|
+
f"User SSH key doesn't match; default SSH key ({config_manager.dstack_key_path}) doesn't exist"
|
|
309
295
|
)
|
|
310
296
|
ssh_identity_file = str(ssh_identity_file)
|
|
311
297
|
|
|
@@ -504,15 +490,19 @@ class RunCollection:
|
|
|
504
490
|
ssh_key_pub = Path(ssh_identity_file).with_suffix(".pub").read_text()
|
|
505
491
|
else:
|
|
506
492
|
config_manager = ConfigManager()
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
493
|
+
key_manager = UserSSHKeyManager(self._api_client, config_manager.dstack_ssh_dir)
|
|
494
|
+
if key_manager.get_user_key():
|
|
495
|
+
ssh_key_pub = None # using the server-managed user key
|
|
496
|
+
else:
|
|
497
|
+
if not config_manager.dstack_key_path.exists():
|
|
498
|
+
generate_rsa_key_pair(private_key_path=config_manager.dstack_key_path)
|
|
499
|
+
logger.warning(
|
|
500
|
+
f"Using legacy [code]{config_manager.dstack_key_path.with_suffix('.pub')}[/code]."
|
|
501
|
+
" You will only be able to attach to the run from this client."
|
|
502
|
+
" Update the [code]dstack[/] server to [code]0.19.34[/]+ to switch to user keys"
|
|
503
|
+
" automatically replicated to all clients.",
|
|
504
|
+
)
|
|
505
|
+
ssh_key_pub = config_manager.dstack_key_path.with_suffix(".pub").read_text()
|
|
516
506
|
run_spec = RunSpec(
|
|
517
507
|
run_name=configuration.name,
|
|
518
508
|
repo_id=repo.repo_id,
|
|
@@ -760,12 +750,19 @@ class RunCollection:
|
|
|
760
750
|
idle_duration=idle_duration, # type: ignore[assignment]
|
|
761
751
|
)
|
|
762
752
|
config_manager = ConfigManager()
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
753
|
+
key_manager = UserSSHKeyManager(self._api_client, config_manager.dstack_ssh_dir)
|
|
754
|
+
if key_manager.get_user_key():
|
|
755
|
+
ssh_key_pub = None # using the server-managed user key
|
|
756
|
+
else:
|
|
757
|
+
if not config_manager.dstack_key_path.exists():
|
|
758
|
+
generate_rsa_key_pair(private_key_path=config_manager.dstack_key_path)
|
|
759
|
+
logger.warning(
|
|
760
|
+
f"Using legacy [code]{config_manager.dstack_key_path.with_suffix('.pub')}[/code]."
|
|
761
|
+
" You will only be able to attach to the run from this client."
|
|
762
|
+
" Update the [code]dstack[/] server to [code]0.19.34[/]+ to switch to user keys"
|
|
763
|
+
" automatically replicated to all clients.",
|
|
764
|
+
)
|
|
765
|
+
ssh_key_pub = config_manager.dstack_key_path.with_suffix(".pub").read_text()
|
|
769
766
|
run_spec = RunSpec(
|
|
770
767
|
run_name=run_name,
|
|
771
768
|
repo_id=repo.repo_id,
|
|
@@ -775,7 +772,7 @@ class RunCollection:
|
|
|
775
772
|
configuration_path=configuration_path,
|
|
776
773
|
configuration=configuration,
|
|
777
774
|
profile=profile,
|
|
778
|
-
ssh_key_pub=
|
|
775
|
+
ssh_key_pub=ssh_key_pub,
|
|
779
776
|
)
|
|
780
777
|
logger.debug("Getting run plan")
|
|
781
778
|
run_plan = self._api_client.runs.get_plan(self._project, run_spec)
|
dstack/api/server/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import hashlib
|
|
1
2
|
import os
|
|
2
3
|
import pprint
|
|
3
4
|
import time
|
|
@@ -121,6 +122,9 @@ class APIClient:
|
|
|
121
122
|
def files(self) -> FilesAPIClient:
|
|
122
123
|
return FilesAPIClient(self._request, self._logger)
|
|
123
124
|
|
|
125
|
+
def get_token_hash(self) -> str:
|
|
126
|
+
return hashlib.sha1(self._token.encode()).hexdigest()[:8]
|
|
127
|
+
|
|
124
128
|
def _request(
|
|
125
129
|
self,
|
|
126
130
|
path: str,
|
dstack/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dstack
|
|
3
|
-
Version: 0.19.
|
|
3
|
+
Version: 0.19.35
|
|
4
4
|
Summary: dstack is an open-source orchestration engine for running AI workloads on any cloud or on-premises.
|
|
5
5
|
Project-URL: Homepage, https://dstack.ai
|
|
6
6
|
Project-URL: Source, https://github.com/dstackai/dstack
|
|
@@ -22,7 +22,7 @@ Requires-Dist: cryptography
|
|
|
22
22
|
Requires-Dist: cursor
|
|
23
23
|
Requires-Dist: filelock
|
|
24
24
|
Requires-Dist: gitpython
|
|
25
|
-
Requires-Dist: gpuhunt==0.1.
|
|
25
|
+
Requires-Dist: gpuhunt==0.1.11
|
|
26
26
|
Requires-Dist: ignore-python>=0.2.0
|
|
27
27
|
Requires-Dist: jsonschema
|
|
28
28
|
Requires-Dist: orjson
|
|
@@ -73,7 +73,7 @@ Requires-Dist: grpcio>=1.50; extra == 'all'
|
|
|
73
73
|
Requires-Dist: httpx; extra == 'all'
|
|
74
74
|
Requires-Dist: jinja2; extra == 'all'
|
|
75
75
|
Requires-Dist: kubernetes; extra == 'all'
|
|
76
|
-
Requires-Dist: nebius
|
|
76
|
+
Requires-Dist: nebius<0.4,>=0.3.4; (python_version >= '3.10') and extra == 'all'
|
|
77
77
|
Requires-Dist: oci>=2.150.0; extra == 'all'
|
|
78
78
|
Requires-Dist: prometheus-client; extra == 'all'
|
|
79
79
|
Requires-Dist: pyopenssl>=23.2.0; extra == 'all'
|
|
@@ -259,7 +259,7 @@ Requires-Dist: fastapi; extra == 'nebius'
|
|
|
259
259
|
Requires-Dist: grpcio>=1.50; extra == 'nebius'
|
|
260
260
|
Requires-Dist: httpx; extra == 'nebius'
|
|
261
261
|
Requires-Dist: jinja2; extra == 'nebius'
|
|
262
|
-
Requires-Dist: nebius
|
|
262
|
+
Requires-Dist: nebius<0.4,>=0.3.4; (python_version >= '3.10') and extra == 'nebius'
|
|
263
263
|
Requires-Dist: prometheus-client; extra == 'nebius'
|
|
264
264
|
Requires-Dist: python-dxf==12.1.0; extra == 'nebius'
|
|
265
265
|
Requires-Dist: python-json-logger>=3.1.0; extra == 'nebius'
|