dstack 0.19.25__py3-none-any.whl → 0.19.26__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/commands/__init__.py +2 -2
- dstack/_internal/cli/commands/apply.py +3 -61
- dstack/_internal/cli/commands/attach.py +1 -1
- dstack/_internal/cli/commands/completion.py +1 -1
- dstack/_internal/cli/commands/delete.py +2 -2
- dstack/_internal/cli/commands/fleet.py +1 -1
- dstack/_internal/cli/commands/gateway.py +2 -2
- dstack/_internal/cli/commands/init.py +56 -24
- dstack/_internal/cli/commands/logs.py +1 -1
- dstack/_internal/cli/commands/metrics.py +1 -1
- dstack/_internal/cli/commands/offer.py +45 -7
- dstack/_internal/cli/commands/project.py +2 -2
- dstack/_internal/cli/commands/secrets.py +2 -2
- dstack/_internal/cli/commands/server.py +1 -1
- dstack/_internal/cli/commands/stop.py +1 -1
- dstack/_internal/cli/commands/volume.py +1 -1
- dstack/_internal/cli/main.py +2 -2
- dstack/_internal/cli/services/completion.py +2 -2
- dstack/_internal/cli/services/configurators/__init__.py +6 -2
- dstack/_internal/cli/services/configurators/base.py +6 -7
- dstack/_internal/cli/services/configurators/fleet.py +1 -3
- dstack/_internal/cli/services/configurators/gateway.py +2 -4
- dstack/_internal/cli/services/configurators/run.py +195 -55
- dstack/_internal/cli/services/configurators/volume.py +2 -4
- dstack/_internal/cli/services/profile.py +1 -1
- dstack/_internal/cli/services/repos.py +51 -47
- dstack/_internal/core/backends/aws/configurator.py +11 -7
- dstack/_internal/core/backends/azure/configurator.py +11 -7
- dstack/_internal/core/backends/base/configurator.py +25 -13
- dstack/_internal/core/backends/cloudrift/configurator.py +13 -7
- dstack/_internal/core/backends/cudo/configurator.py +11 -7
- dstack/_internal/core/backends/datacrunch/compute.py +5 -1
- dstack/_internal/core/backends/datacrunch/configurator.py +13 -7
- dstack/_internal/core/backends/gcp/configurator.py +11 -7
- dstack/_internal/core/backends/hotaisle/configurator.py +13 -7
- dstack/_internal/core/backends/kubernetes/configurator.py +13 -7
- dstack/_internal/core/backends/lambdalabs/configurator.py +11 -7
- dstack/_internal/core/backends/nebius/compute.py +1 -1
- dstack/_internal/core/backends/nebius/configurator.py +11 -7
- dstack/_internal/core/backends/nebius/resources.py +21 -11
- dstack/_internal/core/backends/oci/configurator.py +11 -7
- dstack/_internal/core/backends/runpod/configurator.py +11 -7
- dstack/_internal/core/backends/template/configurator.py.jinja +11 -7
- dstack/_internal/core/backends/tensordock/configurator.py +13 -7
- dstack/_internal/core/backends/vastai/configurator.py +11 -7
- dstack/_internal/core/backends/vultr/configurator.py +11 -4
- dstack/_internal/core/compatibility/gpus.py +13 -0
- dstack/_internal/core/compatibility/runs.py +1 -0
- dstack/_internal/core/models/common.py +3 -3
- dstack/_internal/core/models/configurations.py +172 -27
- dstack/_internal/core/models/files.py +1 -1
- dstack/_internal/core/models/fleets.py +5 -1
- dstack/_internal/core/models/profiles.py +41 -11
- dstack/_internal/core/models/resources.py +46 -42
- dstack/_internal/core/models/runs.py +4 -0
- dstack/_internal/core/services/configs/__init__.py +2 -2
- dstack/_internal/core/services/profiles.py +2 -2
- dstack/_internal/core/services/repos.py +5 -3
- dstack/_internal/core/services/ssh/ports.py +1 -1
- dstack/_internal/proxy/lib/deps.py +6 -2
- dstack/_internal/server/app.py +22 -17
- dstack/_internal/server/background/tasks/process_gateways.py +4 -1
- dstack/_internal/server/background/tasks/process_instances.py +10 -2
- dstack/_internal/server/background/tasks/process_probes.py +1 -1
- dstack/_internal/server/background/tasks/process_running_jobs.py +10 -4
- dstack/_internal/server/background/tasks/process_runs.py +1 -1
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +54 -43
- dstack/_internal/server/background/tasks/process_terminating_jobs.py +2 -2
- dstack/_internal/server/background/tasks/process_volumes.py +1 -1
- dstack/_internal/server/db.py +8 -4
- dstack/_internal/server/models.py +1 -0
- dstack/_internal/server/routers/gpus.py +1 -6
- dstack/_internal/server/schemas/runner.py +10 -0
- dstack/_internal/server/services/backends/__init__.py +14 -8
- dstack/_internal/server/services/backends/handlers.py +6 -1
- dstack/_internal/server/services/docker.py +5 -5
- dstack/_internal/server/services/fleets.py +14 -13
- dstack/_internal/server/services/gateways/__init__.py +2 -0
- dstack/_internal/server/services/gateways/client.py +5 -2
- dstack/_internal/server/services/gateways/connection.py +1 -1
- dstack/_internal/server/services/gpus.py +50 -49
- dstack/_internal/server/services/instances.py +41 -1
- dstack/_internal/server/services/jobs/__init__.py +15 -4
- dstack/_internal/server/services/jobs/configurators/base.py +7 -11
- dstack/_internal/server/services/jobs/configurators/dev.py +5 -0
- dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +3 -3
- dstack/_internal/server/services/jobs/configurators/extensions/vscode.py +3 -3
- dstack/_internal/server/services/jobs/configurators/service.py +1 -0
- dstack/_internal/server/services/jobs/configurators/task.py +3 -0
- dstack/_internal/server/services/locking.py +5 -5
- dstack/_internal/server/services/logging.py +10 -2
- dstack/_internal/server/services/logs/__init__.py +8 -6
- dstack/_internal/server/services/logs/aws.py +330 -327
- dstack/_internal/server/services/logs/filelog.py +7 -6
- dstack/_internal/server/services/logs/gcp.py +141 -139
- dstack/_internal/server/services/plugins.py +1 -1
- dstack/_internal/server/services/projects.py +2 -5
- dstack/_internal/server/services/proxy/repo.py +5 -1
- dstack/_internal/server/services/requirements/__init__.py +0 -0
- dstack/_internal/server/services/requirements/combine.py +259 -0
- dstack/_internal/server/services/runner/client.py +7 -0
- dstack/_internal/server/services/runs.py +1 -1
- dstack/_internal/server/services/services/__init__.py +8 -2
- dstack/_internal/server/services/services/autoscalers.py +2 -0
- dstack/_internal/server/services/ssh.py +2 -1
- dstack/_internal/server/services/storage/__init__.py +5 -6
- dstack/_internal/server/services/storage/gcs.py +49 -49
- dstack/_internal/server/services/storage/s3.py +52 -52
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/testing/common.py +1 -1
- dstack/_internal/server/utils/logging.py +3 -3
- dstack/_internal/server/utils/provisioning.py +3 -3
- dstack/_internal/utils/json_schema.py +3 -1
- dstack/_internal/utils/typing.py +14 -0
- dstack/api/_public/repos.py +21 -2
- dstack/api/_public/runs.py +5 -7
- dstack/api/server/__init__.py +17 -19
- dstack/api/server/_gpus.py +2 -1
- dstack/api/server/_group.py +4 -3
- dstack/api/server/_repos.py +20 -3
- dstack/plugins/builtin/rest_plugin/_plugin.py +1 -0
- dstack/version.py +1 -1
- {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/METADATA +1 -1
- {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/RECORD +127 -124
- dstack/api/huggingface/__init__.py +0 -73
- {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/WHEEL +0 -0
- {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -26,6 +26,7 @@ from dstack._internal.server.schemas.runner import (
|
|
|
26
26
|
ShimVolumeInfo,
|
|
27
27
|
SubmitBody,
|
|
28
28
|
TaskInfoResponse,
|
|
29
|
+
TaskListResponse,
|
|
29
30
|
TaskSubmitRequest,
|
|
30
31
|
TaskTerminateRequest,
|
|
31
32
|
)
|
|
@@ -245,6 +246,12 @@ class ShimClient:
|
|
|
245
246
|
self._raise_for_status(resp)
|
|
246
247
|
return self._response(InstanceHealthResponse, resp)
|
|
247
248
|
|
|
249
|
+
def list_tasks(self) -> TaskListResponse:
|
|
250
|
+
if not self.is_api_v2_supported():
|
|
251
|
+
raise ShimAPIVersionError()
|
|
252
|
+
resp = self._request("GET", "/api/tasks", raise_for_status=True)
|
|
253
|
+
return self._response(TaskListResponse, resp)
|
|
254
|
+
|
|
248
255
|
def get_task(self, task_id: "_TaskID") -> TaskInfoResponse:
|
|
249
256
|
if not self.is_api_v2_supported():
|
|
250
257
|
raise ShimAPIVersionError()
|
|
@@ -529,7 +529,7 @@ async def submit_run(
|
|
|
529
529
|
initial_status = RunStatus.PENDING
|
|
530
530
|
initial_replicas = 0
|
|
531
531
|
elif run_spec.configuration.type == "service":
|
|
532
|
-
initial_replicas = run_spec.configuration.replicas.min
|
|
532
|
+
initial_replicas = run_spec.configuration.replicas.min or 0
|
|
533
533
|
|
|
534
534
|
run_model = RunModel(
|
|
535
535
|
id=uuid.uuid4(),
|
|
@@ -5,7 +5,6 @@ Application logic related to `type: service` runs.
|
|
|
5
5
|
import uuid
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from typing import Optional
|
|
8
|
-
from urllib.parse import urlparse
|
|
9
8
|
|
|
10
9
|
import httpx
|
|
11
10
|
from sqlalchemy import select
|
|
@@ -73,6 +72,8 @@ async def register_service(session: AsyncSession, run_model: RunModel, run_spec:
|
|
|
73
72
|
async def _register_service_in_gateway(
|
|
74
73
|
session: AsyncSession, run_model: RunModel, run_spec: RunSpec, gateway: GatewayModel
|
|
75
74
|
) -> ServiceSpec:
|
|
75
|
+
assert run_spec.configuration.type == "service"
|
|
76
|
+
|
|
76
77
|
if gateway.gateway_compute is None:
|
|
77
78
|
raise ServerClientError("Gateway has no instance associated with it")
|
|
78
79
|
|
|
@@ -100,6 +101,9 @@ async def _register_service_in_gateway(
|
|
|
100
101
|
model_url=f"{gateway_protocol}://gateway.{wildcard_domain}",
|
|
101
102
|
)
|
|
102
103
|
|
|
104
|
+
domain = service_spec.get_domain()
|
|
105
|
+
assert domain is not None
|
|
106
|
+
|
|
103
107
|
conn = await get_or_add_gateway_connection(session, gateway.id)
|
|
104
108
|
try:
|
|
105
109
|
logger.debug("%s: registering service as %s", fmt(run_model), service_spec.url)
|
|
@@ -107,7 +111,7 @@ async def _register_service_in_gateway(
|
|
|
107
111
|
await client.register_service(
|
|
108
112
|
project=run_model.project.name,
|
|
109
113
|
run_name=run_model.run_name,
|
|
110
|
-
domain=
|
|
114
|
+
domain=domain,
|
|
111
115
|
service_https=service_https,
|
|
112
116
|
gateway_https=gateway_https,
|
|
113
117
|
auth=run_spec.configuration.auth,
|
|
@@ -127,6 +131,7 @@ async def _register_service_in_gateway(
|
|
|
127
131
|
|
|
128
132
|
|
|
129
133
|
def _register_service_in_server(run_model: RunModel, run_spec: RunSpec) -> ServiceSpec:
|
|
134
|
+
assert run_spec.configuration.type == "service"
|
|
130
135
|
if run_spec.configuration.https != SERVICE_HTTPS_DEFAULT:
|
|
131
136
|
# Note: if the user sets `https: <default-value>`, it will be ignored silently
|
|
132
137
|
# TODO: in 0.19, make `https` Optional to be able to tell if it was set or omitted
|
|
@@ -270,6 +275,7 @@ async def unregister_replica(session: AsyncSession, job_model: JobModel):
|
|
|
270
275
|
|
|
271
276
|
|
|
272
277
|
def _get_service_https(run_spec: RunSpec, configuration: GatewayConfiguration) -> bool:
|
|
278
|
+
assert run_spec.configuration.type == "service"
|
|
273
279
|
if not run_spec.configuration.https:
|
|
274
280
|
return False
|
|
275
281
|
if configuration.certificate is not None and configuration.certificate.type == "acm":
|
|
@@ -120,6 +120,8 @@ class RPSAutoscaler(BaseServiceScaler):
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
def get_service_scaler(conf: ServiceConfiguration) -> BaseServiceScaler:
|
|
123
|
+
assert conf.replicas.min is not None
|
|
124
|
+
assert conf.replicas.max is not None
|
|
123
125
|
if conf.scaling is None:
|
|
124
126
|
return ManualScaler(
|
|
125
127
|
min_replicas=conf.replicas.min,
|
|
@@ -20,10 +20,11 @@ def container_ssh_tunnel(
|
|
|
20
20
|
"""
|
|
21
21
|
Build SSHTunnel for connecting to the container running the specified job.
|
|
22
22
|
"""
|
|
23
|
-
|
|
24
23
|
jpd: JobProvisioningData = JobProvisioningData.__response__.parse_raw(
|
|
25
24
|
job.job_provisioning_data
|
|
26
25
|
)
|
|
26
|
+
assert jpd.hostname is not None
|
|
27
|
+
assert jpd.ssh_port is not None
|
|
27
28
|
if not jpd.dockerized:
|
|
28
29
|
ssh_destination = f"{jpd.username}@{jpd.hostname}"
|
|
29
30
|
ssh_port = jpd.ssh_port
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
3
|
from dstack._internal.server import settings
|
|
4
|
+
from dstack._internal.server.services.storage import gcs, s3
|
|
4
5
|
from dstack._internal.server.services.storage.base import BaseStorage
|
|
5
|
-
from dstack._internal.server.services.storage.gcs import GCS_AVAILABLE, GCSStorage
|
|
6
|
-
from dstack._internal.server.services.storage.s3 import BOTO_AVAILABLE, S3Storage
|
|
7
6
|
|
|
8
7
|
_default_storage = None
|
|
9
8
|
|
|
@@ -20,16 +19,16 @@ def init_default_storage():
|
|
|
20
19
|
)
|
|
21
20
|
|
|
22
21
|
if settings.SERVER_S3_BUCKET:
|
|
23
|
-
if not BOTO_AVAILABLE:
|
|
22
|
+
if not s3.BOTO_AVAILABLE:
|
|
24
23
|
raise ValueError("AWS dependencies are not installed")
|
|
25
|
-
_default_storage = S3Storage(
|
|
24
|
+
_default_storage = s3.S3Storage(
|
|
26
25
|
bucket=settings.SERVER_S3_BUCKET,
|
|
27
26
|
region=settings.SERVER_S3_BUCKET_REGION,
|
|
28
27
|
)
|
|
29
28
|
elif settings.SERVER_GCS_BUCKET:
|
|
30
|
-
if not GCS_AVAILABLE:
|
|
29
|
+
if not gcs.GCS_AVAILABLE:
|
|
31
30
|
raise ValueError("GCS dependencies are not installed")
|
|
32
|
-
_default_storage = GCSStorage(
|
|
31
|
+
_default_storage = gcs.GCSStorage(
|
|
33
32
|
bucket=settings.SERVER_GCS_BUCKET,
|
|
34
33
|
)
|
|
35
34
|
|
|
@@ -8,59 +8,59 @@ try:
|
|
|
8
8
|
from google.cloud.exceptions import NotFound
|
|
9
9
|
except ImportError:
|
|
10
10
|
GCS_AVAILABLE = False
|
|
11
|
+
else:
|
|
11
12
|
|
|
13
|
+
class GCSStorage(BaseStorage):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
bucket: str,
|
|
17
|
+
):
|
|
18
|
+
self._client = storage.Client()
|
|
19
|
+
self._bucket = self._client.bucket(bucket)
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
def upload_code(
|
|
22
|
+
self,
|
|
23
|
+
project_id: str,
|
|
24
|
+
repo_id: str,
|
|
25
|
+
code_hash: str,
|
|
26
|
+
blob: bytes,
|
|
27
|
+
):
|
|
28
|
+
key = self._get_code_key(project_id, repo_id, code_hash)
|
|
29
|
+
self._upload(key, blob)
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
self._upload(key, blob)
|
|
31
|
+
def get_code(
|
|
32
|
+
self,
|
|
33
|
+
project_id: str,
|
|
34
|
+
repo_id: str,
|
|
35
|
+
code_hash: str,
|
|
36
|
+
) -> Optional[bytes]:
|
|
37
|
+
key = self._get_code_key(project_id, repo_id, code_hash)
|
|
38
|
+
return self._get(key)
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
def upload_archive(
|
|
41
|
+
self,
|
|
42
|
+
user_id: str,
|
|
43
|
+
archive_hash: str,
|
|
44
|
+
blob: bytes,
|
|
45
|
+
):
|
|
46
|
+
key = self._get_archive_key(user_id, archive_hash)
|
|
47
|
+
self._upload(key, blob)
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
self._upload(key, blob)
|
|
49
|
+
def get_archive(
|
|
50
|
+
self,
|
|
51
|
+
user_id: str,
|
|
52
|
+
archive_hash: str,
|
|
53
|
+
) -> Optional[bytes]:
|
|
54
|
+
key = self._get_archive_key(user_id, archive_hash)
|
|
55
|
+
return self._get(key)
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
archive_hash: str,
|
|
53
|
-
) -> Optional[bytes]:
|
|
54
|
-
key = self._get_archive_key(user_id, archive_hash)
|
|
55
|
-
return self._get(key)
|
|
57
|
+
def _upload(self, key: str, blob: bytes):
|
|
58
|
+
blob_obj = self._bucket.blob(key)
|
|
59
|
+
blob_obj.upload_from_string(blob)
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
blob = self._bucket.blob(key)
|
|
64
|
-
except NotFound:
|
|
65
|
-
return None
|
|
66
|
-
return blob.download_as_bytes()
|
|
61
|
+
def _get(self, key: str) -> Optional[bytes]:
|
|
62
|
+
try:
|
|
63
|
+
blob = self._bucket.blob(key)
|
|
64
|
+
except NotFound:
|
|
65
|
+
return None
|
|
66
|
+
return blob.download_as_bytes()
|
|
@@ -8,62 +8,62 @@ try:
|
|
|
8
8
|
from boto3 import Session
|
|
9
9
|
except ImportError:
|
|
10
10
|
BOTO_AVAILABLE = False
|
|
11
|
+
else:
|
|
11
12
|
|
|
13
|
+
class S3Storage(BaseStorage):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
bucket: str,
|
|
17
|
+
region: Optional[str] = None,
|
|
18
|
+
):
|
|
19
|
+
self._session = Session()
|
|
20
|
+
self._client = self._session.client("s3", region_name=region)
|
|
21
|
+
self.bucket = bucket
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
def upload_code(
|
|
24
|
+
self,
|
|
25
|
+
project_id: str,
|
|
26
|
+
repo_id: str,
|
|
27
|
+
code_hash: str,
|
|
28
|
+
blob: bytes,
|
|
29
|
+
):
|
|
30
|
+
key = self._get_code_key(project_id, repo_id, code_hash)
|
|
31
|
+
self._upload(key, blob)
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
self._upload(key, blob)
|
|
33
|
+
def get_code(
|
|
34
|
+
self,
|
|
35
|
+
project_id: str,
|
|
36
|
+
repo_id: str,
|
|
37
|
+
code_hash: str,
|
|
38
|
+
) -> Optional[bytes]:
|
|
39
|
+
key = self._get_code_key(project_id, repo_id, code_hash)
|
|
40
|
+
return self._get(key)
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
def upload_archive(
|
|
43
|
+
self,
|
|
44
|
+
user_id: str,
|
|
45
|
+
archive_hash: str,
|
|
46
|
+
blob: bytes,
|
|
47
|
+
):
|
|
48
|
+
key = self._get_archive_key(user_id, archive_hash)
|
|
49
|
+
self._upload(key, blob)
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self._upload(key, blob)
|
|
51
|
+
def get_archive(
|
|
52
|
+
self,
|
|
53
|
+
user_id: str,
|
|
54
|
+
archive_hash: str,
|
|
55
|
+
) -> Optional[bytes]:
|
|
56
|
+
key = self._get_archive_key(user_id, archive_hash)
|
|
57
|
+
return self._get(key)
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
user_id: str,
|
|
54
|
-
archive_hash: str,
|
|
55
|
-
) -> Optional[bytes]:
|
|
56
|
-
key = self._get_archive_key(user_id, archive_hash)
|
|
57
|
-
return self._get(key)
|
|
59
|
+
def _upload(self, key: str, blob: bytes):
|
|
60
|
+
self._client.put_object(Bucket=self.bucket, Key=key, Body=blob)
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return None
|
|
68
|
-
raise e
|
|
69
|
-
return response["Body"].read()
|
|
62
|
+
def _get(self, key: str) -> Optional[bytes]:
|
|
63
|
+
try:
|
|
64
|
+
response = self._client.get_object(Bucket=self.bucket, Key=key)
|
|
65
|
+
except botocore.exceptions.ClientError as e:
|
|
66
|
+
if e.response["Error"]["Code"] == "NoSuchKey":
|
|
67
|
+
return None
|
|
68
|
+
raise e
|
|
69
|
+
return response["Body"].read()
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>dstack</title><meta name="description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
|
|
2
2
|
"/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"><meta name="og:title" content="dstack"><meta name="og:type" content="article"><meta name="og:image" content="/splash_thumbnail.png"><meta name="og:description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
|
|
3
|
-
"><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-d151b300fcac3933213d.js"></script><link href="/main-aec4762350e34d6fbff9.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div><script src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="1c028b8e-609e-40e3-b792-f3939a461850" data-project-name="dstack" data-project-color="rgba(0, 0, 0, 0.87)" data-font-size-lg="0.78rem" data-button-hide="true" data-modal-image="/logo-notext.svg" data-modal-z-index="1100" data-modal-title="Ask me anything" data-project-logo="/assets/images/kapa.svg" data-modal-disclaimer="This is a custom LLM for dstack with access to Documentation, API references and GitHub issues. This feature is experimental - Give it a try!" data-user-analytics-fingerprint-enabled="true" async></script></body></html>
|
|
3
|
+
"><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-d151b300fcac3933213d.js"></script><link href="/main-aec4762350e34d6fbff9.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div><script async src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="11a9339d-20ce-4ddb-9ba3-1b6e29afe8eb" data-project-name="dstack" data-project-color="rgba(0, 0, 0, 0.87)" data-font-size-lg="0.78rem" data-button-hide="true" data-modal-image="/logo-notext.svg" data-modal-z-index="1100" data-modal-title="Ask me anything" data-project-logo="/assets/images/kapa.svg" data-modal-disclaimer="This is a custom LLM for dstack with access to Documentation, API references and GitHub issues. This feature is experimental - Give it a try!" data-user-analytics-fingerprint-enabled="true"></script></body></html>
|
|
@@ -31,15 +31,15 @@ def configure_logging():
|
|
|
31
31
|
rename_fields={"name": "logger", "asctime": "timestamp", "levelname": "level"},
|
|
32
32
|
),
|
|
33
33
|
}
|
|
34
|
-
handlers = {
|
|
34
|
+
handlers: dict[str, logging.Handler] = {
|
|
35
35
|
"rich": DstackRichHandler(console=console),
|
|
36
36
|
"standard": logging.StreamHandler(stream=sys.stdout),
|
|
37
37
|
"json": logging.StreamHandler(stream=sys.stdout),
|
|
38
38
|
}
|
|
39
39
|
if settings.LOG_FORMAT not in formatters:
|
|
40
40
|
raise ValueError(f"Invalid settings.LOG_FORMAT: {settings.LOG_FORMAT}")
|
|
41
|
-
formatter = formatters
|
|
42
|
-
handler = handlers
|
|
41
|
+
formatter = formatters[settings.LOG_FORMAT]
|
|
42
|
+
handler = handlers[settings.LOG_FORMAT]
|
|
43
43
|
handler.setFormatter(formatter)
|
|
44
44
|
handler.addFilter(AsyncioCancelledErrorFilter())
|
|
45
45
|
root_logger = logging.getLogger(None)
|
|
@@ -312,10 +312,10 @@ def get_paramiko_connection(
|
|
|
312
312
|
with proxy_ctx as proxy_client, paramiko.SSHClient() as client:
|
|
313
313
|
proxy_channel: Optional[paramiko.Channel] = None
|
|
314
314
|
if proxy_client is not None:
|
|
315
|
+
transport = proxy_client.get_transport()
|
|
316
|
+
assert transport is not None
|
|
315
317
|
try:
|
|
316
|
-
proxy_channel =
|
|
317
|
-
"direct-tcpip", (host, port), ("", 0)
|
|
318
|
-
)
|
|
318
|
+
proxy_channel = transport.open_channel("direct-tcpip", (host, port), ("", 0))
|
|
319
319
|
except (paramiko.SSHException, OSError) as e:
|
|
320
320
|
raise ProvisioningError(f"Proxy channel failed: {e}") from e
|
|
321
321
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
@@ -3,7 +3,9 @@ def add_extra_schema_types(schema_property: dict, extra_types: list[dict]):
|
|
|
3
3
|
refs = [schema_property.pop("allOf")[0]]
|
|
4
4
|
elif "anyOf" in schema_property:
|
|
5
5
|
refs = schema_property.pop("anyOf")
|
|
6
|
-
|
|
6
|
+
elif "type" in schema_property:
|
|
7
7
|
refs = [{"type": schema_property.pop("type")}]
|
|
8
|
+
else:
|
|
9
|
+
refs = [{"$ref": schema_property.pop("$ref")}]
|
|
8
10
|
refs.extend(extra_types)
|
|
9
11
|
schema_property["anyOf"] = refs
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Any, Protocol, TypeVar, Union
|
|
2
|
+
|
|
3
|
+
_T_contra = TypeVar("_T_contra", contravariant=True)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SupportsDunderLT(Protocol[_T_contra]):
|
|
7
|
+
def __lt__(self, other: _T_contra, /) -> bool: ...
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SupportsDunderGT(Protocol[_T_contra]):
|
|
11
|
+
def __gt__(self, other: _T_contra, /) -> bool: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SupportsRichComparison = Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]
|
dstack/api/_public/repos.py
CHANGED
|
@@ -68,6 +68,7 @@ class RepoCollection:
|
|
|
68
68
|
"""
|
|
69
69
|
creds = None
|
|
70
70
|
if isinstance(repo, RemoteRepo):
|
|
71
|
+
assert repo.repo_url is not None
|
|
71
72
|
try:
|
|
72
73
|
creds = get_local_repo_credentials(
|
|
73
74
|
repo_url=repo.repo_url,
|
|
@@ -140,22 +141,40 @@ class RepoCollection:
|
|
|
140
141
|
def is_initialized(
|
|
141
142
|
self,
|
|
142
143
|
repo: Repo,
|
|
144
|
+
by_user: bool = False,
|
|
143
145
|
) -> bool:
|
|
144
146
|
"""
|
|
145
|
-
Checks if the
|
|
147
|
+
Checks if the repo is initialized in the project
|
|
146
148
|
|
|
147
149
|
Args:
|
|
148
150
|
repo: The repo to check.
|
|
151
|
+
by_user: Require the remote repo to be initialized by the user, that is, to have
|
|
152
|
+
the user's credentials. Ignored for other repo types.
|
|
149
153
|
|
|
150
154
|
Returns:
|
|
151
155
|
Whether the repo is initialized or not.
|
|
152
156
|
"""
|
|
157
|
+
if isinstance(repo, RemoteRepo) and by_user:
|
|
158
|
+
return self._is_initialized_by_user(repo)
|
|
153
159
|
try:
|
|
154
|
-
self._api_client.repos.get(self._project, repo.repo_id
|
|
160
|
+
self._api_client.repos.get(self._project, repo.repo_id)
|
|
155
161
|
return True
|
|
156
162
|
except ResourceNotExistsError:
|
|
157
163
|
return False
|
|
158
164
|
|
|
165
|
+
def _is_initialized_by_user(self, repo: RemoteRepo) -> bool:
|
|
166
|
+
try:
|
|
167
|
+
repo_head = self._api_client.repos.get_with_creds(self._project, repo.repo_id)
|
|
168
|
+
except ResourceNotExistsError:
|
|
169
|
+
return False
|
|
170
|
+
# This works because:
|
|
171
|
+
# - RepoCollection.init() always submits RemoteRepoCreds for remote repos, even if
|
|
172
|
+
# the repo is public
|
|
173
|
+
# - Server returns creds only if there is RepoCredsModel for the user (or legacy
|
|
174
|
+
# shared creds in RepoModel)
|
|
175
|
+
# TODO: add an API method with the same logic returning a bool value?
|
|
176
|
+
return repo_head.repo_creds is not None
|
|
177
|
+
|
|
159
178
|
|
|
160
179
|
def get_ssh_keypair(key_path: Optional[PathLike], dstack_key_path: Path) -> str:
|
|
161
180
|
"""Returns a path to the private key"""
|
dstack/api/_public/runs.py
CHANGED
|
@@ -23,7 +23,7 @@ from dstack._internal.core.models.configurations import (
|
|
|
23
23
|
PortMapping,
|
|
24
24
|
ServiceConfiguration,
|
|
25
25
|
)
|
|
26
|
-
from dstack._internal.core.models.files import FileArchiveMapping
|
|
26
|
+
from dstack._internal.core.models.files import FileArchiveMapping
|
|
27
27
|
from dstack._internal.core.models.profiles import (
|
|
28
28
|
CreationPolicy,
|
|
29
29
|
Profile,
|
|
@@ -436,7 +436,7 @@ class RunCollection:
|
|
|
436
436
|
) -> RunPlan:
|
|
437
437
|
"""
|
|
438
438
|
Get a run plan.
|
|
439
|
-
Use this method to see the run plan before applying the
|
|
439
|
+
Use this method to see the run plan before applying the configuration.
|
|
440
440
|
|
|
441
441
|
Args:
|
|
442
442
|
configuration (Union[Task, Service, DevEnvironment]): The run configuration.
|
|
@@ -499,7 +499,6 @@ class RunCollection:
|
|
|
499
499
|
|
|
500
500
|
self._validate_configuration_files(configuration, run_spec.configuration_path)
|
|
501
501
|
for file_mapping in configuration.files:
|
|
502
|
-
assert isinstance(file_mapping, FilePathMapping)
|
|
503
502
|
with tempfile.TemporaryFile("w+b") as fp:
|
|
504
503
|
try:
|
|
505
504
|
archive_hash = create_file_archive(file_mapping.local_path, fp)
|
|
@@ -691,11 +690,11 @@ class RunCollection:
|
|
|
691
690
|
spot_policy=spot_policy,
|
|
692
691
|
retry=None,
|
|
693
692
|
utilization_policy=utilization_policy,
|
|
694
|
-
max_duration=max_duration,
|
|
695
|
-
stop_duration=stop_duration,
|
|
693
|
+
max_duration=max_duration, # type: ignore[assignment]
|
|
694
|
+
stop_duration=stop_duration, # type: ignore[assignment]
|
|
696
695
|
max_price=max_price,
|
|
697
696
|
creation_policy=creation_policy,
|
|
698
|
-
idle_duration=idle_duration,
|
|
697
|
+
idle_duration=idle_duration, # type: ignore[assignment]
|
|
699
698
|
)
|
|
700
699
|
run_spec = RunSpec(
|
|
701
700
|
run_name=run_name,
|
|
@@ -812,7 +811,6 @@ class RunCollection:
|
|
|
812
811
|
if configuration_path is not None:
|
|
813
812
|
base_dir = Path(configuration_path).expanduser().resolve().parent
|
|
814
813
|
for file_mapping in configuration.files:
|
|
815
|
-
assert isinstance(file_mapping, FilePathMapping)
|
|
816
814
|
path = Path(file_mapping.local_path).expanduser()
|
|
817
815
|
if not path.is_absolute():
|
|
818
816
|
if base_dir is None:
|