@xen-orchestra/rest-api 0.32.0 → 0.34.0
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.
- package/README.md +36 -4
- package/dist/backup-jobs/backup-job.service.mjs +4 -0
- package/dist/backup-repositories/backup-repositories.controller.mjs +115 -6
- package/dist/backup-repositories/backup-repository.service.mjs +46 -0
- package/dist/ioc/ioc.mjs +8 -0
- package/dist/middlewares/acl.middleware.mjs +10 -1
- package/dist/middlewares/deprecated.middleware.mjs +10 -0
- package/dist/middlewares/generic-error-handler.middleware.mjs +1 -0
- package/dist/open-api/oa-examples/backup-repository.oa-example.mjs +1 -0
- package/dist/open-api/routes/routes.js +266 -20
- package/dist/pbds/pbd.controller.mjs +10 -0
- package/dist/pools/pool.controller.mjs +48 -0
- package/dist/srs/sr.controller.mjs +15 -0
- package/dist/vbds/vbd.controller.mjs +20 -1
- package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +27 -2
- package/dist/vdis/vdi.controller.mjs +26 -2
- package/dist/vifs/vif.controller.mjs +79 -6
- package/dist/vm-snapshots/vm-snapshot.controller.mjs +2 -1
- package/dist/vm-templates/vm-template.controller.mjs +2 -1
- package/dist/vms/vm.controller.mjs +70 -5
- package/dist/vms/vm.service.mjs +29 -11
- package/open-api/spec/swagger.json +1707 -68
- package/package.json +7 -7
|
@@ -7,13 +7,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
8
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
9
|
};
|
|
10
|
-
import { Body, Delete, Example, Extension, Get, Middlewares, Path, Post, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
|
|
10
|
+
import { Body, Delete, Example, Extension, Get, Middlewares, Patch, Path, Post, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
|
|
11
11
|
import { json } from 'express';
|
|
12
12
|
import { inject } from 'inversify';
|
|
13
13
|
import { incorrectState, invalidParameters } from 'xo-common/api-errors.js';
|
|
14
14
|
import { provide } from 'inversify-binding-decorators';
|
|
15
15
|
import { PassThrough } from 'node:stream';
|
|
16
|
-
import {
|
|
16
|
+
import { SUPPORTED_ACTIONS_BY_RESOURCE } from '@xen-orchestra/acl';
|
|
17
|
+
import { acl, actionsFromBody } from '../middlewares/acl.middleware.mjs';
|
|
17
18
|
import { asynchronousActionResp, badRequestResp, createdResp, forbiddenOperationResp, incorrectStateResp, internalServerErrorResp, invalidParameters as invalidParametersResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
18
19
|
import { BASE_URL } from '../index.mjs';
|
|
19
20
|
import { limitAndFilterArray, NDJSON_CONTENT_TYPE } from '../helpers/utils.helper.mjs';
|
|
@@ -27,7 +28,13 @@ import { BackupJobService } from '../backup-jobs/backup-job.service.mjs';
|
|
|
27
28
|
import { partialVmBackupJobs, vmBackupJobIds } from '../open-api/oa-examples/backup-job.oa-example.mjs';
|
|
28
29
|
import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
|
|
29
30
|
import { Task } from '@vates/task';
|
|
31
|
+
import { vmExportCompressDeprecated } from '../middlewares/deprecated.middleware.mjs';
|
|
30
32
|
const IGNORED_VDIS_TAG = '[NOSNAP]';
|
|
33
|
+
// `datasources` is managed through the dedicated `/vms/{id}/stats/data_source`
|
|
34
|
+
// endpoints, not as a direct VM property, so it cannot be updated via PATCH /vms.
|
|
35
|
+
const UPDATE_VM_ACTIONS = Object.keys(SUPPORTED_ACTIONS_BY_RESOURCE.vm.update)
|
|
36
|
+
.filter(action => action !== 'datasources')
|
|
37
|
+
.map(k => `update:${k}`);
|
|
31
38
|
let VmController = class VmController extends XapiXoController {
|
|
32
39
|
#vmService;
|
|
33
40
|
#backupJobService;
|
|
@@ -74,6 +81,29 @@ let VmController = class VmController extends XapiXoController {
|
|
|
74
81
|
getVm(id) {
|
|
75
82
|
return this.getObject(id);
|
|
76
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Partial update of a VM. Only the fields present in the body are modified;
|
|
86
|
+
* everything else is left untouched.
|
|
87
|
+
*
|
|
88
|
+
* Operations are applied sequentially: if one fails, previously applied
|
|
89
|
+
* changes are not rolled back.
|
|
90
|
+
*
|
|
91
|
+
* Required privilege per field provided in the body:
|
|
92
|
+
* - resource: vm, action: update:<field> (e.g. update:nameLabel, update:cpus, ...)
|
|
93
|
+
*
|
|
94
|
+
* Special fields:
|
|
95
|
+
* - `xenStoreData` keys are automatically prefixed with `vm-data/` when missing
|
|
96
|
+
*
|
|
97
|
+
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
|
|
98
|
+
* @example body {
|
|
99
|
+
* "nameLabel": "web-prod-01",
|
|
100
|
+
* "nameDescription": "Production web frontend — managed by n8n",
|
|
101
|
+
* "notes": "Docker containers: nginx, app-1, app-2"
|
|
102
|
+
* }
|
|
103
|
+
*/
|
|
104
|
+
async updateVm(id, body) {
|
|
105
|
+
await this.#vmService.updateVm(id, body);
|
|
106
|
+
}
|
|
77
107
|
/**
|
|
78
108
|
* Required privilege:
|
|
79
109
|
* - resource: vm, action: delete
|
|
@@ -418,6 +448,8 @@ let VmController = class VmController extends XapiXoController {
|
|
|
418
448
|
});
|
|
419
449
|
}
|
|
420
450
|
/**
|
|
451
|
+
* Required privilege:
|
|
452
|
+
* - resource: vm, action: clone
|
|
421
453
|
*
|
|
422
454
|
* - For fast clone on the same SR, omit `srId` and set `fast` to `true`.
|
|
423
455
|
* - For full copy on the same SR, omit `srId` and set `fast` to `false`.
|
|
@@ -601,6 +633,10 @@ let VmController = class VmController extends XapiXoController {
|
|
|
601
633
|
}
|
|
602
634
|
}
|
|
603
635
|
/**
|
|
636
|
+
* Required privileges:
|
|
637
|
+
* - resource: vm, action: migrate-send
|
|
638
|
+
* - resource: host, action: migrate-receive (on the destination host)
|
|
639
|
+
*
|
|
604
640
|
* VIF mapping is not allowed for intra-pool migration
|
|
605
641
|
*
|
|
606
642
|
* Networks and SRs must belong to the same pool as the destination host
|
|
@@ -620,6 +656,7 @@ let VmController = class VmController extends XapiXoController {
|
|
|
620
656
|
migrationNetworkId: migrationNetworkId,
|
|
621
657
|
sr: 'srId' in body ? body.srId : undefined,
|
|
622
658
|
});
|
|
659
|
+
return;
|
|
623
660
|
};
|
|
624
661
|
return this.createAction(action, {
|
|
625
662
|
sync,
|
|
@@ -644,7 +681,7 @@ __decorate([
|
|
|
644
681
|
__decorate([
|
|
645
682
|
Extension('x-mcp-exposure', 'deny'),
|
|
646
683
|
Get('{id}.{format}'),
|
|
647
|
-
Middlewares(acl({ resource: 'vm', action: 'export', objectId: 'params.id' })),
|
|
684
|
+
Middlewares([acl({ resource: 'vm', action: 'export', objectId: 'params.id' }), vmExportCompressDeprecated]),
|
|
648
685
|
SuccessResponse(200, 'Download started', 'application/octet-stream'),
|
|
649
686
|
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
650
687
|
Response(notFoundResp.status, notFoundResp.description),
|
|
@@ -663,6 +700,24 @@ __decorate([
|
|
|
663
700
|
Response(notFoundResp.status, notFoundResp.description),
|
|
664
701
|
__param(0, Path())
|
|
665
702
|
], VmController.prototype, "getVm", null);
|
|
703
|
+
__decorate([
|
|
704
|
+
Patch('{id}'),
|
|
705
|
+
Middlewares([
|
|
706
|
+
json(),
|
|
707
|
+
acl({
|
|
708
|
+
resource: 'vm',
|
|
709
|
+
actions: actionsFromBody(UPDATE_VM_ACTIONS),
|
|
710
|
+
objectId: 'params.id',
|
|
711
|
+
}),
|
|
712
|
+
]),
|
|
713
|
+
SuccessResponse(noContentResp.status, noContentResp.description),
|
|
714
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
715
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
716
|
+
Response(invalidParametersResp.status, invalidParametersResp.description),
|
|
717
|
+
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
718
|
+
__param(0, Path()),
|
|
719
|
+
__param(1, Body())
|
|
720
|
+
], VmController.prototype, "updateVm", null);
|
|
666
721
|
__decorate([
|
|
667
722
|
Extension('x-mcp-exposure', 'confirm'),
|
|
668
723
|
Delete('{id}'),
|
|
@@ -872,8 +927,9 @@ __decorate([
|
|
|
872
927
|
Example(taskLocation),
|
|
873
928
|
Extension('x-mcp-exposure', 'confirm'),
|
|
874
929
|
Post('{id}/actions/clone'),
|
|
875
|
-
Middlewares(json()),
|
|
930
|
+
Middlewares([json(), acl({ resource: 'vm', action: 'clone', objectId: 'params.id' })]),
|
|
876
931
|
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
|
|
932
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
877
933
|
Response(createdResp.status, createdResp.description),
|
|
878
934
|
Response(notFoundResp.status, notFoundResp.description),
|
|
879
935
|
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
@@ -994,8 +1050,17 @@ __decorate([
|
|
|
994
1050
|
Example(taskLocation),
|
|
995
1051
|
Extension('x-mcp-exposure', 'confirm'),
|
|
996
1052
|
Post('{id}/actions/migrate'),
|
|
997
|
-
Middlewares(
|
|
1053
|
+
Middlewares([
|
|
1054
|
+
json(),
|
|
1055
|
+
// Two separate checks allow independent control so a user can be allowed to migrate a VM away
|
|
1056
|
+
// without being allowed to place VMs on any specific host, and vice versa.
|
|
1057
|
+
acl([
|
|
1058
|
+
{ resource: 'vm', action: 'migrate-send', objectId: 'params.id' },
|
|
1059
|
+
{ resource: 'host', action: 'migrate-receive', objectId: 'body.hostId' },
|
|
1060
|
+
]),
|
|
1061
|
+
]),
|
|
998
1062
|
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
|
|
1063
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
999
1064
|
Response(noContentResp.status, noContentResp.description),
|
|
1000
1065
|
Response(notFoundResp.status, notFoundResp.description),
|
|
1001
1066
|
Response(invalidParametersResp.status, invalidParametersResp.description),
|
package/dist/vms/vm.service.mjs
CHANGED
|
@@ -120,7 +120,7 @@ export class VmService {
|
|
|
120
120
|
}
|
|
121
121
|
return vdis;
|
|
122
122
|
}
|
|
123
|
-
async export(id, vmType, { compress, format, response }) {
|
|
123
|
+
async export(id, vmType, { compress, format, response, }) {
|
|
124
124
|
const xapiVm = this.#restApi.getXapiObject(id, vmType);
|
|
125
125
|
let stream;
|
|
126
126
|
if (format === 'xva') {
|
|
@@ -146,6 +146,23 @@ export class VmService {
|
|
|
146
146
|
});
|
|
147
147
|
return alarms;
|
|
148
148
|
}
|
|
149
|
+
async updateVm(id, body) {
|
|
150
|
+
const { resourceSet, share, ...editProps } = body;
|
|
151
|
+
// Touch the object so 404 is raised before any side effect.
|
|
152
|
+
void this.#restApi.getObject(id, 'VM');
|
|
153
|
+
const xoApp = this.#restApi.xoApp;
|
|
154
|
+
if (resourceSet !== undefined) {
|
|
155
|
+
await xoApp.setVmResourceSet(id, resourceSet, true);
|
|
156
|
+
}
|
|
157
|
+
else if (share) {
|
|
158
|
+
// `share: false` is a no-op.
|
|
159
|
+
await xoApp.shareVmResourceSet(id);
|
|
160
|
+
}
|
|
161
|
+
if (Object.keys(editProps).length === 0) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await xoApp.getXapi(id).editVm(id, editProps);
|
|
165
|
+
}
|
|
149
166
|
#getDashboardQuickInfo(id) {
|
|
150
167
|
const { power_state, uuid, name_description, CPUs, mainIpAddress, os_version, memory, creation, $pool, virtualizationMode, tags, $container, startTime, pvDriversDetected, pvDriversVersion, pvDriversUpToDate, } = this.#restApi.getObject(id, 'VM');
|
|
151
168
|
return {
|
|
@@ -179,23 +196,24 @@ export class VmService {
|
|
|
179
196
|
}
|
|
180
197
|
#getLastReplication(id) {
|
|
181
198
|
const vm = this.#restApi.getObject(id, 'VM');
|
|
182
|
-
const
|
|
183
|
-
filter: obj => obj.other['xo:backup:vm'] === vm.id,
|
|
199
|
+
const snapshotReplicas = this.#restApi.getObjectsByType('VM-snapshot', {
|
|
200
|
+
filter: obj => obj.other['xo:backup:vm'] === vm.id && obj.$snapshot_of !== vm.id,
|
|
184
201
|
});
|
|
185
202
|
let lastTimestamp;
|
|
186
|
-
let
|
|
187
|
-
for (const id in
|
|
188
|
-
const
|
|
189
|
-
const timestamp = parseDateTime(
|
|
203
|
+
let lastReplicaId;
|
|
204
|
+
for (const id in snapshotReplicas) {
|
|
205
|
+
const snapshot = snapshotReplicas[id];
|
|
206
|
+
const timestamp = parseDateTime(snapshot.other['xo:backup:datetime']);
|
|
190
207
|
if (lastTimestamp === undefined || lastTimestamp < timestamp) {
|
|
191
208
|
lastTimestamp = timestamp;
|
|
192
|
-
|
|
209
|
+
lastReplicaId = snapshot.$snapshot_of;
|
|
193
210
|
}
|
|
194
211
|
}
|
|
195
|
-
if (
|
|
212
|
+
if (lastReplicaId === undefined) {
|
|
196
213
|
return {};
|
|
197
214
|
}
|
|
198
|
-
const
|
|
215
|
+
const replica = this.#restApi.getObject(lastReplicaId, 'VM');
|
|
216
|
+
const vdis = this.getVmVdis(replica.id, 'VM');
|
|
199
217
|
let sr = undefined;
|
|
200
218
|
for (const vdi of vdis) {
|
|
201
219
|
if (sr === undefined) {
|
|
@@ -209,7 +227,7 @@ export class VmService {
|
|
|
209
227
|
}
|
|
210
228
|
}
|
|
211
229
|
return {
|
|
212
|
-
id:
|
|
230
|
+
id: replica.id,
|
|
213
231
|
timestamp: lastTimestamp * 1000,
|
|
214
232
|
sr,
|
|
215
233
|
};
|