dstack 0.19.17__py3-none-any.whl → 0.19.19__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/fleet.py +111 -1
- dstack/_internal/cli/services/profile.py +1 -1
- dstack/_internal/core/backends/aws/compute.py +237 -18
- dstack/_internal/core/backends/base/compute.py +20 -2
- dstack/_internal/core/backends/cudo/compute.py +23 -9
- dstack/_internal/core/backends/gcp/compute.py +13 -7
- dstack/_internal/core/backends/lambdalabs/compute.py +2 -1
- dstack/_internal/core/compatibility/fleets.py +12 -11
- dstack/_internal/core/compatibility/gateways.py +9 -8
- dstack/_internal/core/compatibility/logs.py +4 -3
- dstack/_internal/core/compatibility/runs.py +29 -21
- dstack/_internal/core/compatibility/volumes.py +11 -8
- dstack/_internal/core/errors.py +4 -0
- dstack/_internal/core/models/common.py +45 -2
- dstack/_internal/core/models/configurations.py +9 -1
- dstack/_internal/core/models/fleets.py +2 -1
- dstack/_internal/core/models/profiles.py +8 -5
- dstack/_internal/core/models/resources.py +15 -8
- dstack/_internal/core/models/runs.py +41 -138
- dstack/_internal/core/models/volumes.py +14 -0
- dstack/_internal/core/services/diff.py +56 -3
- dstack/_internal/core/services/ssh/attach.py +2 -0
- dstack/_internal/server/app.py +37 -9
- dstack/_internal/server/background/__init__.py +66 -40
- dstack/_internal/server/background/tasks/process_fleets.py +19 -3
- dstack/_internal/server/background/tasks/process_gateways.py +47 -29
- dstack/_internal/server/background/tasks/process_idle_volumes.py +139 -0
- dstack/_internal/server/background/tasks/process_instances.py +13 -2
- dstack/_internal/server/background/tasks/process_placement_groups.py +4 -2
- dstack/_internal/server/background/tasks/process_running_jobs.py +14 -3
- dstack/_internal/server/background/tasks/process_runs.py +8 -4
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +38 -7
- dstack/_internal/server/background/tasks/process_terminating_jobs.py +5 -3
- dstack/_internal/server/background/tasks/process_volumes.py +2 -2
- dstack/_internal/server/migrations/versions/35e90e1b0d3e_add_rolling_deployment_fields.py +6 -6
- dstack/_internal/server/migrations/versions/d5863798bf41_add_volumemodel_last_job_processed_at.py +40 -0
- dstack/_internal/server/models.py +1 -0
- dstack/_internal/server/routers/backends.py +23 -16
- dstack/_internal/server/routers/files.py +7 -6
- dstack/_internal/server/routers/fleets.py +47 -36
- dstack/_internal/server/routers/gateways.py +27 -18
- dstack/_internal/server/routers/instances.py +18 -13
- dstack/_internal/server/routers/logs.py +7 -3
- dstack/_internal/server/routers/metrics.py +14 -8
- dstack/_internal/server/routers/projects.py +33 -22
- dstack/_internal/server/routers/repos.py +7 -6
- dstack/_internal/server/routers/runs.py +49 -28
- dstack/_internal/server/routers/secrets.py +20 -15
- dstack/_internal/server/routers/server.py +7 -4
- dstack/_internal/server/routers/users.py +22 -19
- dstack/_internal/server/routers/volumes.py +34 -25
- dstack/_internal/server/schemas/logs.py +2 -2
- dstack/_internal/server/schemas/runs.py +17 -5
- dstack/_internal/server/services/fleets.py +358 -75
- dstack/_internal/server/services/gateways/__init__.py +17 -6
- dstack/_internal/server/services/gateways/client.py +5 -3
- dstack/_internal/server/services/instances.py +8 -0
- dstack/_internal/server/services/jobs/__init__.py +45 -0
- dstack/_internal/server/services/jobs/configurators/base.py +12 -1
- dstack/_internal/server/services/locking.py +104 -13
- dstack/_internal/server/services/logging.py +4 -2
- dstack/_internal/server/services/logs/__init__.py +15 -2
- dstack/_internal/server/services/logs/aws.py +2 -4
- dstack/_internal/server/services/logs/filelog.py +33 -27
- dstack/_internal/server/services/logs/gcp.py +3 -5
- dstack/_internal/server/services/proxy/repo.py +4 -1
- dstack/_internal/server/services/runs.py +139 -72
- dstack/_internal/server/services/services/__init__.py +2 -1
- dstack/_internal/server/services/users.py +3 -1
- dstack/_internal/server/services/volumes.py +15 -2
- dstack/_internal/server/settings.py +25 -6
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/{main-d151637af20f70b2e796.js → main-64f8273740c4b52c18f5.js} +71 -67
- dstack/_internal/server/statics/{main-d151637af20f70b2e796.js.map → main-64f8273740c4b52c18f5.js.map} +1 -1
- dstack/_internal/server/statics/{main-d48635d8fe670d53961c.css → main-d58fc0460cb0eae7cb5c.css} +1 -1
- dstack/_internal/server/testing/common.py +48 -8
- dstack/_internal/server/utils/routers.py +31 -8
- dstack/_internal/utils/json_utils.py +54 -0
- dstack/api/_public/runs.py +13 -2
- dstack/api/server/_runs.py +12 -2
- dstack/version.py +1 -1
- {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/METADATA +17 -14
- {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/RECORD +86 -83
- {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/WHEEL +0 -0
- {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -23,7 +23,10 @@ from dstack._internal.server.security.permissions import (
|
|
|
23
23
|
ProjectMemberOrPublicAccess,
|
|
24
24
|
)
|
|
25
25
|
from dstack._internal.server.services import projects
|
|
26
|
-
from dstack._internal.server.utils.routers import
|
|
26
|
+
from dstack._internal.server.utils.routers import (
|
|
27
|
+
CustomORJSONResponse,
|
|
28
|
+
get_base_api_additional_responses,
|
|
29
|
+
)
|
|
27
30
|
|
|
28
31
|
router = APIRouter(
|
|
29
32
|
prefix="/api/projects",
|
|
@@ -32,30 +35,34 @@ router = APIRouter(
|
|
|
32
35
|
)
|
|
33
36
|
|
|
34
37
|
|
|
35
|
-
@router.post("/list")
|
|
38
|
+
@router.post("/list", response_model=List[Project])
|
|
36
39
|
async def list_projects(
|
|
37
40
|
session: AsyncSession = Depends(get_session),
|
|
38
41
|
user: UserModel = Depends(Authenticated()),
|
|
39
|
-
)
|
|
42
|
+
):
|
|
40
43
|
"""
|
|
41
44
|
Returns all projects visible to user sorted by descending `created_at`.
|
|
42
45
|
|
|
43
46
|
`members` and `backends` are always empty - call `/api/projects/{project_name}/get` to retrieve them.
|
|
44
47
|
"""
|
|
45
|
-
return
|
|
48
|
+
return CustomORJSONResponse(
|
|
49
|
+
await projects.list_user_accessible_projects(session=session, user=user)
|
|
50
|
+
)
|
|
46
51
|
|
|
47
52
|
|
|
48
|
-
@router.post("/create")
|
|
53
|
+
@router.post("/create", response_model=Project)
|
|
49
54
|
async def create_project(
|
|
50
55
|
body: CreateProjectRequest,
|
|
51
56
|
session: AsyncSession = Depends(get_session),
|
|
52
57
|
user: UserModel = Depends(Authenticated()),
|
|
53
|
-
)
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
):
|
|
59
|
+
return CustomORJSONResponse(
|
|
60
|
+
await projects.create_project(
|
|
61
|
+
session=session,
|
|
62
|
+
user=user,
|
|
63
|
+
project_name=body.project_name,
|
|
64
|
+
is_public=body.is_public,
|
|
65
|
+
)
|
|
59
66
|
)
|
|
60
67
|
|
|
61
68
|
|
|
@@ -72,23 +79,24 @@ async def delete_projects(
|
|
|
72
79
|
)
|
|
73
80
|
|
|
74
81
|
|
|
75
|
-
@router.post("/{project_name}/get")
|
|
82
|
+
@router.post("/{project_name}/get", response_model=Project)
|
|
76
83
|
async def get_project(
|
|
77
84
|
session: AsyncSession = Depends(get_session),
|
|
78
85
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMemberOrPublicAccess()),
|
|
79
|
-
)
|
|
86
|
+
):
|
|
80
87
|
_, project = user_project
|
|
81
|
-
return projects.project_model_to_project(project)
|
|
88
|
+
return CustomORJSONResponse(projects.project_model_to_project(project))
|
|
82
89
|
|
|
83
90
|
|
|
84
91
|
@router.post(
|
|
85
92
|
"/{project_name}/set_members",
|
|
93
|
+
response_model=Project,
|
|
86
94
|
)
|
|
87
95
|
async def set_project_members(
|
|
88
96
|
body: SetProjectMembersRequest,
|
|
89
97
|
session: AsyncSession = Depends(get_session),
|
|
90
98
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectManager()),
|
|
91
|
-
)
|
|
99
|
+
):
|
|
92
100
|
user, project = user_project
|
|
93
101
|
await projects.set_project_members(
|
|
94
102
|
session=session,
|
|
@@ -97,17 +105,18 @@ async def set_project_members(
|
|
|
97
105
|
members=body.members,
|
|
98
106
|
)
|
|
99
107
|
await session.refresh(project)
|
|
100
|
-
return projects.project_model_to_project(project)
|
|
108
|
+
return CustomORJSONResponse(projects.project_model_to_project(project))
|
|
101
109
|
|
|
102
110
|
|
|
103
111
|
@router.post(
|
|
104
112
|
"/{project_name}/add_members",
|
|
113
|
+
response_model=Project,
|
|
105
114
|
)
|
|
106
115
|
async def add_project_members(
|
|
107
116
|
body: AddProjectMemberRequest,
|
|
108
117
|
session: AsyncSession = Depends(get_session),
|
|
109
118
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectManagerOrPublicProject()),
|
|
110
|
-
)
|
|
119
|
+
):
|
|
111
120
|
user, project = user_project
|
|
112
121
|
await projects.add_project_members(
|
|
113
122
|
session=session,
|
|
@@ -116,17 +125,18 @@ async def add_project_members(
|
|
|
116
125
|
members=body.members,
|
|
117
126
|
)
|
|
118
127
|
await session.refresh(project)
|
|
119
|
-
return projects.project_model_to_project(project)
|
|
128
|
+
return CustomORJSONResponse(projects.project_model_to_project(project))
|
|
120
129
|
|
|
121
130
|
|
|
122
131
|
@router.post(
|
|
123
132
|
"/{project_name}/remove_members",
|
|
133
|
+
response_model=Project,
|
|
124
134
|
)
|
|
125
135
|
async def remove_project_members(
|
|
126
136
|
body: RemoveProjectMemberRequest,
|
|
127
137
|
session: AsyncSession = Depends(get_session),
|
|
128
138
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectManagerOrSelfLeave()),
|
|
129
|
-
)
|
|
139
|
+
):
|
|
130
140
|
user, project = user_project
|
|
131
141
|
await projects.remove_project_members(
|
|
132
142
|
session=session,
|
|
@@ -135,17 +145,18 @@ async def remove_project_members(
|
|
|
135
145
|
usernames=body.usernames,
|
|
136
146
|
)
|
|
137
147
|
await session.refresh(project)
|
|
138
|
-
return projects.project_model_to_project(project)
|
|
148
|
+
return CustomORJSONResponse(projects.project_model_to_project(project))
|
|
139
149
|
|
|
140
150
|
|
|
141
151
|
@router.post(
|
|
142
152
|
"/{project_name}/update",
|
|
153
|
+
response_model=Project,
|
|
143
154
|
)
|
|
144
155
|
async def update_project(
|
|
145
156
|
body: UpdateProjectRequest,
|
|
146
157
|
session: AsyncSession = Depends(get_session),
|
|
147
158
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
148
|
-
)
|
|
159
|
+
):
|
|
149
160
|
user, project = user_project
|
|
150
161
|
await projects.update_project(
|
|
151
162
|
session=session,
|
|
@@ -154,4 +165,4 @@ async def update_project(
|
|
|
154
165
|
is_public=body.is_public,
|
|
155
166
|
)
|
|
156
167
|
await session.refresh(project)
|
|
157
|
-
return projects.project_model_to_project(project)
|
|
168
|
+
return CustomORJSONResponse(projects.project_model_to_project(project))
|
|
@@ -16,6 +16,7 @@ from dstack._internal.server.security.permissions import ProjectMember
|
|
|
16
16
|
from dstack._internal.server.services import repos
|
|
17
17
|
from dstack._internal.server.settings import SERVER_CODE_UPLOAD_LIMIT
|
|
18
18
|
from dstack._internal.server.utils.routers import (
|
|
19
|
+
CustomORJSONResponse,
|
|
19
20
|
get_base_api_additional_responses,
|
|
20
21
|
get_request_size,
|
|
21
22
|
)
|
|
@@ -28,21 +29,21 @@ router = APIRouter(
|
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
@router.post("/list")
|
|
32
|
+
@router.post("/list", response_model=List[RepoHead])
|
|
32
33
|
async def list_repos(
|
|
33
34
|
session: AsyncSession = Depends(get_session),
|
|
34
35
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
35
|
-
)
|
|
36
|
+
):
|
|
36
37
|
_, project = user_project
|
|
37
|
-
return await repos.list_repos(session=session, project=project)
|
|
38
|
+
return CustomORJSONResponse(await repos.list_repos(session=session, project=project))
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
@router.post("/get")
|
|
41
|
+
@router.post("/get", response_model=RepoHeadWithCreds)
|
|
41
42
|
async def get_repo(
|
|
42
43
|
body: GetRepoRequest,
|
|
43
44
|
session: AsyncSession = Depends(get_session),
|
|
44
45
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
45
|
-
)
|
|
46
|
+
):
|
|
46
47
|
user, project = user_project
|
|
47
48
|
repo = await repos.get_repo(
|
|
48
49
|
session=session,
|
|
@@ -53,7 +54,7 @@ async def get_repo(
|
|
|
53
54
|
)
|
|
54
55
|
if repo is None:
|
|
55
56
|
raise ResourceNotExistsError()
|
|
56
|
-
return repo
|
|
57
|
+
return CustomORJSONResponse(repo)
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
@router.post("/init")
|
|
@@ -18,7 +18,10 @@ from dstack._internal.server.schemas.runs import (
|
|
|
18
18
|
)
|
|
19
19
|
from dstack._internal.server.security.permissions import Authenticated, ProjectMember
|
|
20
20
|
from dstack._internal.server.services import runs
|
|
21
|
-
from dstack._internal.server.utils.routers import
|
|
21
|
+
from dstack._internal.server.utils.routers import (
|
|
22
|
+
CustomORJSONResponse,
|
|
23
|
+
get_base_api_additional_responses,
|
|
24
|
+
)
|
|
22
25
|
|
|
23
26
|
root_router = APIRouter(
|
|
24
27
|
prefix="/api/runs",
|
|
@@ -32,12 +35,15 @@ project_router = APIRouter(
|
|
|
32
35
|
)
|
|
33
36
|
|
|
34
37
|
|
|
35
|
-
@root_router.post(
|
|
38
|
+
@root_router.post(
|
|
39
|
+
"/list",
|
|
40
|
+
response_model=List[Run],
|
|
41
|
+
)
|
|
36
42
|
async def list_runs(
|
|
37
43
|
body: ListRunsRequest,
|
|
38
44
|
session: AsyncSession = Depends(get_session),
|
|
39
45
|
user: UserModel = Depends(Authenticated()),
|
|
40
|
-
)
|
|
46
|
+
):
|
|
41
47
|
"""
|
|
42
48
|
Returns all runs visible to user sorted by descending `submitted_at`.
|
|
43
49
|
`project_name`, `repo_id`, `username`, and `only_active` can be specified as filters.
|
|
@@ -47,26 +53,33 @@ async def list_runs(
|
|
|
47
53
|
The results are paginated. To get the next page, pass `submitted_at` and `id` of
|
|
48
54
|
the last run from the previous page as `prev_submitted_at` and `prev_run_id`.
|
|
49
55
|
"""
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
return CustomORJSONResponse(
|
|
57
|
+
await runs.list_user_runs(
|
|
58
|
+
session=session,
|
|
59
|
+
user=user,
|
|
60
|
+
project_name=body.project_name,
|
|
61
|
+
repo_id=body.repo_id,
|
|
62
|
+
username=body.username,
|
|
63
|
+
only_active=body.only_active,
|
|
64
|
+
include_jobs=body.include_jobs,
|
|
65
|
+
job_submissions_limit=body.job_submissions_limit,
|
|
66
|
+
prev_submitted_at=body.prev_submitted_at,
|
|
67
|
+
prev_run_id=body.prev_run_id,
|
|
68
|
+
limit=body.limit,
|
|
69
|
+
ascending=body.ascending,
|
|
70
|
+
)
|
|
61
71
|
)
|
|
62
72
|
|
|
63
73
|
|
|
64
|
-
@project_router.post(
|
|
74
|
+
@project_router.post(
|
|
75
|
+
"/get",
|
|
76
|
+
response_model=Run,
|
|
77
|
+
)
|
|
65
78
|
async def get_run(
|
|
66
79
|
body: GetRunRequest,
|
|
67
80
|
session: AsyncSession = Depends(get_session),
|
|
68
81
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
69
|
-
)
|
|
82
|
+
):
|
|
70
83
|
"""
|
|
71
84
|
Returns a run given `run_name` or `id`.
|
|
72
85
|
If given `run_name`, does not return deleted runs.
|
|
@@ -81,15 +94,18 @@ async def get_run(
|
|
|
81
94
|
)
|
|
82
95
|
if run is None:
|
|
83
96
|
raise ResourceNotExistsError("Run not found")
|
|
84
|
-
return run
|
|
97
|
+
return CustomORJSONResponse(run)
|
|
85
98
|
|
|
86
99
|
|
|
87
|
-
@project_router.post(
|
|
100
|
+
@project_router.post(
|
|
101
|
+
"/get_plan",
|
|
102
|
+
response_model=RunPlan,
|
|
103
|
+
)
|
|
88
104
|
async def get_plan(
|
|
89
105
|
body: GetRunPlanRequest,
|
|
90
106
|
session: AsyncSession = Depends(get_session),
|
|
91
107
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
92
|
-
)
|
|
108
|
+
):
|
|
93
109
|
"""
|
|
94
110
|
Returns a run plan for the given run spec.
|
|
95
111
|
This is an optional step before calling `/apply`.
|
|
@@ -102,15 +118,18 @@ async def get_plan(
|
|
|
102
118
|
run_spec=body.run_spec,
|
|
103
119
|
max_offers=body.max_offers,
|
|
104
120
|
)
|
|
105
|
-
return run_plan
|
|
121
|
+
return CustomORJSONResponse(run_plan)
|
|
106
122
|
|
|
107
123
|
|
|
108
|
-
@project_router.post(
|
|
124
|
+
@project_router.post(
|
|
125
|
+
"/apply",
|
|
126
|
+
response_model=Run,
|
|
127
|
+
)
|
|
109
128
|
async def apply_plan(
|
|
110
129
|
body: ApplyRunPlanRequest,
|
|
111
130
|
session: AsyncSession = Depends(get_session),
|
|
112
131
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
113
|
-
)
|
|
132
|
+
):
|
|
114
133
|
"""
|
|
115
134
|
Creates a new run or updates an existing run.
|
|
116
135
|
Errors if the expected current resource from the plan does not match the current resource.
|
|
@@ -118,12 +137,14 @@ async def apply_plan(
|
|
|
118
137
|
If the existing run is active and cannot be updated, it must be stopped first.
|
|
119
138
|
"""
|
|
120
139
|
user, project = user_project
|
|
121
|
-
return
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
return CustomORJSONResponse(
|
|
141
|
+
await runs.apply_plan(
|
|
142
|
+
session=session,
|
|
143
|
+
user=user,
|
|
144
|
+
project=project,
|
|
145
|
+
plan=body.plan,
|
|
146
|
+
force=body.force,
|
|
147
|
+
)
|
|
127
148
|
)
|
|
128
149
|
|
|
129
150
|
|
|
@@ -14,6 +14,7 @@ from dstack._internal.server.schemas.secrets import (
|
|
|
14
14
|
)
|
|
15
15
|
from dstack._internal.server.security.permissions import ProjectAdmin
|
|
16
16
|
from dstack._internal.server.services import secrets as secrets_services
|
|
17
|
+
from dstack._internal.server.utils.routers import CustomORJSONResponse
|
|
17
18
|
|
|
18
19
|
router = APIRouter(
|
|
19
20
|
prefix="/api/project/{project_name}/secrets",
|
|
@@ -21,24 +22,26 @@ router = APIRouter(
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
@router.post("/list")
|
|
25
|
+
@router.post("/list", response_model=List[Secret])
|
|
25
26
|
async def list_secrets(
|
|
26
27
|
session: AsyncSession = Depends(get_session),
|
|
27
28
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
28
|
-
)
|
|
29
|
+
):
|
|
29
30
|
_, project = user_project
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
return CustomORJSONResponse(
|
|
32
|
+
await secrets_services.list_secrets(
|
|
33
|
+
session=session,
|
|
34
|
+
project=project,
|
|
35
|
+
)
|
|
33
36
|
)
|
|
34
37
|
|
|
35
38
|
|
|
36
|
-
@router.post("/get")
|
|
39
|
+
@router.post("/get", response_model=Secret)
|
|
37
40
|
async def get_secret(
|
|
38
41
|
body: GetSecretRequest,
|
|
39
42
|
session: AsyncSession = Depends(get_session),
|
|
40
43
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
41
|
-
)
|
|
44
|
+
):
|
|
42
45
|
_, project = user_project
|
|
43
46
|
secret = await secrets_services.get_secret(
|
|
44
47
|
session=session,
|
|
@@ -47,21 +50,23 @@ async def get_secret(
|
|
|
47
50
|
)
|
|
48
51
|
if secret is None:
|
|
49
52
|
raise ResourceNotExistsError()
|
|
50
|
-
return secret
|
|
53
|
+
return CustomORJSONResponse(secret)
|
|
51
54
|
|
|
52
55
|
|
|
53
|
-
@router.post("/create_or_update")
|
|
56
|
+
@router.post("/create_or_update", response_model=Secret)
|
|
54
57
|
async def create_or_update_secret(
|
|
55
58
|
body: CreateOrUpdateSecretRequest,
|
|
56
59
|
session: AsyncSession = Depends(get_session),
|
|
57
60
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
58
|
-
)
|
|
61
|
+
):
|
|
59
62
|
_, project = user_project
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
return CustomORJSONResponse(
|
|
64
|
+
await secrets_services.create_or_update_secret(
|
|
65
|
+
session=session,
|
|
66
|
+
project=project,
|
|
67
|
+
name=body.name,
|
|
68
|
+
value=body.value,
|
|
69
|
+
)
|
|
65
70
|
)
|
|
66
71
|
|
|
67
72
|
|
|
@@ -2,6 +2,7 @@ from fastapi import APIRouter
|
|
|
2
2
|
|
|
3
3
|
from dstack._internal import settings
|
|
4
4
|
from dstack._internal.core.models.server import ServerInfo
|
|
5
|
+
from dstack._internal.server.utils.routers import CustomORJSONResponse
|
|
5
6
|
|
|
6
7
|
router = APIRouter(
|
|
7
8
|
prefix="/api/server",
|
|
@@ -9,8 +10,10 @@ router = APIRouter(
|
|
|
9
10
|
)
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
@router.post("/get_info")
|
|
13
|
-
async def get_server_info()
|
|
14
|
-
return
|
|
15
|
-
|
|
13
|
+
@router.post("/get_info", response_model=ServerInfo)
|
|
14
|
+
async def get_server_info():
|
|
15
|
+
return CustomORJSONResponse(
|
|
16
|
+
ServerInfo(
|
|
17
|
+
server_version=settings.DSTACK_VERSION,
|
|
18
|
+
)
|
|
16
19
|
)
|
|
@@ -16,7 +16,10 @@ from dstack._internal.server.schemas.users import (
|
|
|
16
16
|
)
|
|
17
17
|
from dstack._internal.server.security.permissions import Authenticated, GlobalAdmin
|
|
18
18
|
from dstack._internal.server.services import users
|
|
19
|
-
from dstack._internal.server.utils.routers import
|
|
19
|
+
from dstack._internal.server.utils.routers import (
|
|
20
|
+
CustomORJSONResponse,
|
|
21
|
+
get_base_api_additional_responses,
|
|
22
|
+
)
|
|
20
23
|
|
|
21
24
|
router = APIRouter(
|
|
22
25
|
prefix="/api/users",
|
|
@@ -25,41 +28,41 @@ router = APIRouter(
|
|
|
25
28
|
)
|
|
26
29
|
|
|
27
30
|
|
|
28
|
-
@router.post("/list")
|
|
31
|
+
@router.post("/list", response_model=List[User])
|
|
29
32
|
async def list_users(
|
|
30
33
|
session: AsyncSession = Depends(get_session),
|
|
31
34
|
user: UserModel = Depends(Authenticated()),
|
|
32
|
-
)
|
|
33
|
-
return await users.list_users_for_user(session=session, user=user)
|
|
35
|
+
):
|
|
36
|
+
return CustomORJSONResponse(await users.list_users_for_user(session=session, user=user))
|
|
34
37
|
|
|
35
38
|
|
|
36
|
-
@router.post("/get_my_user")
|
|
39
|
+
@router.post("/get_my_user", response_model=User)
|
|
37
40
|
async def get_my_user(
|
|
38
41
|
user: UserModel = Depends(Authenticated()),
|
|
39
|
-
)
|
|
40
|
-
return users.user_model_to_user(user)
|
|
42
|
+
):
|
|
43
|
+
return CustomORJSONResponse(users.user_model_to_user(user))
|
|
41
44
|
|
|
42
45
|
|
|
43
|
-
@router.post("/get_user")
|
|
46
|
+
@router.post("/get_user", response_model=UserWithCreds)
|
|
44
47
|
async def get_user(
|
|
45
48
|
body: GetUserRequest,
|
|
46
49
|
session: AsyncSession = Depends(get_session),
|
|
47
50
|
user: UserModel = Depends(Authenticated()),
|
|
48
|
-
)
|
|
51
|
+
):
|
|
49
52
|
res = await users.get_user_with_creds_by_name(
|
|
50
53
|
session=session, current_user=user, username=body.username
|
|
51
54
|
)
|
|
52
55
|
if res is None:
|
|
53
56
|
raise ResourceNotExistsError()
|
|
54
|
-
return res
|
|
57
|
+
return CustomORJSONResponse(res)
|
|
55
58
|
|
|
56
59
|
|
|
57
|
-
@router.post("/create")
|
|
60
|
+
@router.post("/create", response_model=User)
|
|
58
61
|
async def create_user(
|
|
59
62
|
body: CreateUserRequest,
|
|
60
63
|
session: AsyncSession = Depends(get_session),
|
|
61
64
|
user: UserModel = Depends(GlobalAdmin()),
|
|
62
|
-
)
|
|
65
|
+
):
|
|
63
66
|
res = await users.create_user(
|
|
64
67
|
session=session,
|
|
65
68
|
username=body.username,
|
|
@@ -67,15 +70,15 @@ async def create_user(
|
|
|
67
70
|
email=body.email,
|
|
68
71
|
active=body.active,
|
|
69
72
|
)
|
|
70
|
-
return users.user_model_to_user(res)
|
|
73
|
+
return CustomORJSONResponse(users.user_model_to_user(res))
|
|
71
74
|
|
|
72
75
|
|
|
73
|
-
@router.post("/update")
|
|
76
|
+
@router.post("/update", response_model=User)
|
|
74
77
|
async def update_user(
|
|
75
78
|
body: UpdateUserRequest,
|
|
76
79
|
session: AsyncSession = Depends(get_session),
|
|
77
80
|
user: UserModel = Depends(GlobalAdmin()),
|
|
78
|
-
)
|
|
81
|
+
):
|
|
79
82
|
res = await users.update_user(
|
|
80
83
|
session=session,
|
|
81
84
|
username=body.username,
|
|
@@ -85,19 +88,19 @@ async def update_user(
|
|
|
85
88
|
)
|
|
86
89
|
if res is None:
|
|
87
90
|
raise ResourceNotExistsError()
|
|
88
|
-
return users.user_model_to_user(res)
|
|
91
|
+
return CustomORJSONResponse(users.user_model_to_user(res))
|
|
89
92
|
|
|
90
93
|
|
|
91
|
-
@router.post("/refresh_token")
|
|
94
|
+
@router.post("/refresh_token", response_model=UserWithCreds)
|
|
92
95
|
async def refresh_token(
|
|
93
96
|
body: RefreshTokenRequest,
|
|
94
97
|
session: AsyncSession = Depends(get_session),
|
|
95
98
|
user: UserModel = Depends(Authenticated()),
|
|
96
|
-
)
|
|
99
|
+
):
|
|
97
100
|
res = await users.refresh_user_token(session=session, user=user, username=body.username)
|
|
98
101
|
if res is None:
|
|
99
102
|
raise ResourceNotExistsError()
|
|
100
|
-
return users.user_model_to_user_with_creds(res)
|
|
103
|
+
return CustomORJSONResponse(users.user_model_to_user_with_creds(res))
|
|
101
104
|
|
|
102
105
|
|
|
103
106
|
@router.post("/delete")
|
|
@@ -15,7 +15,10 @@ from dstack._internal.server.schemas.volumes import (
|
|
|
15
15
|
ListVolumesRequest,
|
|
16
16
|
)
|
|
17
17
|
from dstack._internal.server.security.permissions import Authenticated, ProjectMember
|
|
18
|
-
from dstack._internal.server.utils.routers import
|
|
18
|
+
from dstack._internal.server.utils.routers import (
|
|
19
|
+
CustomORJSONResponse,
|
|
20
|
+
get_base_api_additional_responses,
|
|
21
|
+
)
|
|
19
22
|
|
|
20
23
|
root_router = APIRouter(
|
|
21
24
|
prefix="/api/volumes",
|
|
@@ -25,12 +28,12 @@ root_router = APIRouter(
|
|
|
25
28
|
project_router = APIRouter(prefix="/api/project/{project_name}/volumes", tags=["volumes"])
|
|
26
29
|
|
|
27
30
|
|
|
28
|
-
@root_router.post("/list")
|
|
31
|
+
@root_router.post("/list", response_model=List[Volume])
|
|
29
32
|
async def list_volumes(
|
|
30
33
|
body: ListVolumesRequest,
|
|
31
34
|
session: AsyncSession = Depends(get_session),
|
|
32
35
|
user: UserModel = Depends(Authenticated()),
|
|
33
|
-
)
|
|
36
|
+
):
|
|
34
37
|
"""
|
|
35
38
|
Returns all volumes visible to user sorted by descending `created_at`.
|
|
36
39
|
`project_name` and `only_active` can be specified as filters.
|
|
@@ -38,36 +41,40 @@ async def list_volumes(
|
|
|
38
41
|
The results are paginated. To get the next page, pass `created_at` and `id` of
|
|
39
42
|
the last fleet from the previous page as `prev_created_at` and `prev_id`.
|
|
40
43
|
"""
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
return CustomORJSONResponse(
|
|
45
|
+
await volumes_services.list_volumes(
|
|
46
|
+
session=session,
|
|
47
|
+
user=user,
|
|
48
|
+
project_name=body.project_name,
|
|
49
|
+
only_active=body.only_active,
|
|
50
|
+
prev_created_at=body.prev_created_at,
|
|
51
|
+
prev_id=body.prev_id,
|
|
52
|
+
limit=body.limit,
|
|
53
|
+
ascending=body.ascending,
|
|
54
|
+
)
|
|
50
55
|
)
|
|
51
56
|
|
|
52
57
|
|
|
53
|
-
@project_router.post("/list")
|
|
58
|
+
@project_router.post("/list", response_model=List[Volume])
|
|
54
59
|
async def list_project_volumes(
|
|
55
60
|
session: AsyncSession = Depends(get_session),
|
|
56
61
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
57
|
-
)
|
|
62
|
+
):
|
|
58
63
|
"""
|
|
59
64
|
Returns all volumes in the project.
|
|
60
65
|
"""
|
|
61
66
|
_, project = user_project
|
|
62
|
-
return
|
|
67
|
+
return CustomORJSONResponse(
|
|
68
|
+
await volumes_services.list_project_volumes(session=session, project=project)
|
|
69
|
+
)
|
|
63
70
|
|
|
64
71
|
|
|
65
|
-
@project_router.post("/get")
|
|
72
|
+
@project_router.post("/get", response_model=Volume)
|
|
66
73
|
async def get_volume(
|
|
67
74
|
body: GetVolumeRequest,
|
|
68
75
|
session: AsyncSession = Depends(get_session),
|
|
69
76
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
70
|
-
)
|
|
77
|
+
):
|
|
71
78
|
"""
|
|
72
79
|
Returns a volume given a volume name.
|
|
73
80
|
"""
|
|
@@ -77,24 +84,26 @@ async def get_volume(
|
|
|
77
84
|
)
|
|
78
85
|
if volume is None:
|
|
79
86
|
raise ResourceNotExistsError()
|
|
80
|
-
return volume
|
|
87
|
+
return CustomORJSONResponse(volume)
|
|
81
88
|
|
|
82
89
|
|
|
83
|
-
@project_router.post("/create")
|
|
90
|
+
@project_router.post("/create", response_model=Volume)
|
|
84
91
|
async def create_volume(
|
|
85
92
|
body: CreateVolumeRequest,
|
|
86
93
|
session: AsyncSession = Depends(get_session),
|
|
87
94
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
88
|
-
)
|
|
95
|
+
):
|
|
89
96
|
"""
|
|
90
97
|
Creates a volume given a volume configuration.
|
|
91
98
|
"""
|
|
92
99
|
user, project = user_project
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
return CustomORJSONResponse(
|
|
101
|
+
await volumes_services.create_volume(
|
|
102
|
+
session=session,
|
|
103
|
+
project=project,
|
|
104
|
+
user=user,
|
|
105
|
+
configuration=body.configuration,
|
|
106
|
+
)
|
|
98
107
|
)
|
|
99
108
|
|
|
100
109
|
|
|
@@ -9,8 +9,8 @@ from dstack._internal.core.models.common import CoreModel
|
|
|
9
9
|
class PollLogsRequest(CoreModel):
|
|
10
10
|
run_name: str
|
|
11
11
|
job_submission_id: UUID4
|
|
12
|
-
start_time: Optional[datetime]
|
|
13
|
-
end_time: Optional[datetime]
|
|
12
|
+
start_time: Optional[datetime] = None
|
|
13
|
+
end_time: Optional[datetime] = None
|
|
14
14
|
descending: bool = False
|
|
15
15
|
next_token: Optional[str] = None
|
|
16
16
|
limit: int = Field(100, ge=0, le=1000)
|