@xen-orchestra/rest-api 0.18.0 → 0.19.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/dist/abstract-classes/base-controller.mjs +20 -0
- package/dist/abstract-classes/xo-controller.mjs +1 -1
- package/dist/alarms/alarm.controller.mjs +2 -1
- package/dist/backup-archives/backup-archive.controller.mjs +96 -0
- package/dist/backup-jobs/backup-job.controller.mjs +3 -1
- package/dist/backup-logs/backup-log.controller.mjs +2 -1
- package/dist/backup-repositories/backup-repositories.controller.mjs +2 -1
- package/dist/groups/group.controller.mjs +26 -1
- package/dist/hosts/host.controller.mjs +57 -2
- package/dist/index.mjs +3 -2
- package/dist/ioc/ioc.mjs +0 -5
- package/dist/messages/message.controller.mjs +2 -1
- package/dist/middlewares/authentication.middleware.mjs +44 -20
- package/dist/networks/network.controller.mjs +81 -2
- package/dist/open-api/oa-examples/backup-archive.oa-example.mjs +60 -0
- package/dist/open-api/oa-examples/pbd.oa-example.mjs +41 -0
- package/dist/open-api/oa-examples/user.oa-example.mjs +12 -0
- package/dist/open-api/routes/routes.js +2025 -565
- package/dist/pbds/pbd.controller.mjs +60 -0
- package/dist/pcis/pci.controller.mjs +2 -1
- package/dist/pgpus/pgpu.controller.mjs +2 -1
- package/dist/pifs/pif.controller.mjs +50 -1
- package/dist/pools/pool.controller.mjs +56 -3
- package/dist/proxies/proxy.controller.mjs +2 -1
- package/dist/restore-logs/restore-log.controller.mjs +3 -1
- package/dist/schedules/schedule.controller.mjs +2 -1
- package/dist/servers/server.controller.mjs +26 -2
- package/dist/sms/sm.controller.mjs +2 -1
- package/dist/srs/sr.controller.mjs +57 -2
- package/dist/tasks/task.controller.mjs +2 -1
- package/dist/users/user.controller.mjs +80 -31
- package/dist/users/user.middleware.mjs +11 -0
- package/dist/vbds/vbd.controller.mjs +50 -1
- package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +81 -2
- package/dist/vdis/vdi.controller.mjs +105 -2
- package/dist/vifs/vif.controller.mjs +50 -1
- package/dist/vm-controller/vm-controller.controller.mjs +81 -2
- package/dist/vm-snapshots/vm-snapshot.controller.mjs +57 -2
- package/dist/vm-templates/vm-template.controller.mjs +57 -2
- package/dist/vms/vm.controller.mjs +36 -13
- package/dist/xoa/xoa.controller.mjs +2 -1
- package/open-api/spec/swagger.json +7565 -2508
- package/package.json +3 -3
- package/tsoa.json +19 -0
- package/dist/tasks/task.service.mjs +0 -24
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as CM from 'complex-matcher';
|
|
1
2
|
import { Controller } from 'tsoa';
|
|
2
3
|
import { createGzip } from 'node:zlib';
|
|
3
4
|
import { pipeline } from 'node:stream/promises';
|
|
@@ -27,6 +28,25 @@ export class BaseController extends Controller {
|
|
|
27
28
|
return mappedObjects;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
31
|
+
async getTasksForObject(id, { filter, limit = Infinity } = {}) {
|
|
32
|
+
const tasks = {};
|
|
33
|
+
const object = await Promise.resolve(this.getObject(id));
|
|
34
|
+
const objectFilter = (task) => task.properties.objectId === object.id || task.properties.params?.id === object.id;
|
|
35
|
+
let userFilter = () => true;
|
|
36
|
+
if (filter !== undefined) {
|
|
37
|
+
userFilter = typeof filter === 'string' ? CM.parse(filter).createPredicate() : filter;
|
|
38
|
+
}
|
|
39
|
+
for await (const task of this.restApi.tasks.list({ filter: objectFilter })) {
|
|
40
|
+
if (limit === 0) {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
if (userFilter(task)) {
|
|
44
|
+
tasks[task.id] = task;
|
|
45
|
+
limit--;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return tasks;
|
|
49
|
+
}
|
|
30
50
|
/**
|
|
31
51
|
* statusCode must represent the status code in case of a synchronous request. Default 200
|
|
32
52
|
*/
|
|
@@ -16,7 +16,7 @@ let XoController = class XoController extends BaseController {
|
|
|
16
16
|
super(restApi);
|
|
17
17
|
}
|
|
18
18
|
async getObjects(opts = {}) {
|
|
19
|
-
let objects = await this.getAllCollectionObjects();
|
|
19
|
+
let objects = await this.getAllCollectionObjects(opts);
|
|
20
20
|
objects = limitAndFilterArray(objects, opts);
|
|
21
21
|
const objectById = {};
|
|
22
22
|
objects.forEach(obj => {
|
|
@@ -12,7 +12,7 @@ import { inject } from 'inversify';
|
|
|
12
12
|
import { noSuchObject } from 'xo-common/api-errors.js';
|
|
13
13
|
import { provide } from 'inversify-binding-decorators';
|
|
14
14
|
import { alarm, alarmIds, partialAlarms } from '../open-api/oa-examples/alarm.oa-example.mjs';
|
|
15
|
-
import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
15
|
+
import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
16
16
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
17
17
|
import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
|
|
18
18
|
import { AlarmService } from './alarm.service.mjs';
|
|
@@ -72,6 +72,7 @@ __decorate([
|
|
|
72
72
|
AlarmController = __decorate([
|
|
73
73
|
Route('alarms'),
|
|
74
74
|
Security('*'),
|
|
75
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
75
76
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
76
77
|
Tags('alarms'),
|
|
77
78
|
provide(AlarmController),
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
|
|
11
|
+
import { noSuchObject } from 'xo-common/api-errors.js';
|
|
12
|
+
import { provide } from 'inversify-binding-decorators';
|
|
13
|
+
import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
14
|
+
import { XoController } from '../abstract-classes/xo-controller.mjs';
|
|
15
|
+
import { backupArchive, backupArchiveIds, partialBackupArchives, } from '../open-api/oa-examples/backup-archive.oa-example.mjs';
|
|
16
|
+
// BR uuid/xo-vm-backups/VM uuid/(ISO 8601 compact).json
|
|
17
|
+
const BACKUP_ARCHIVE_ID_REGEX = /^([0-9a-fA-F-]{36})\/+xo-vm-backups\/+([0-9a-fA-F-]{36})\/+(\d{8}T\d{6}Z)\.json$/;
|
|
18
|
+
let BackupArchiveController = class BackupArchiveController extends XoController {
|
|
19
|
+
async getAllCollectionObjects({ backupRepositories = [], } = {}) {
|
|
20
|
+
const backupRepositoryIds = [];
|
|
21
|
+
if (backupRepositories.includes('*')) {
|
|
22
|
+
const allBackupRepositories = await this.restApi.xoApp.getAllRemotes();
|
|
23
|
+
allBackupRepositories.forEach(br => backupRepositoryIds.push(br.id));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
for (const brId of backupRepositories) {
|
|
27
|
+
const br = await this.restApi.xoApp.getRemote(brId);
|
|
28
|
+
backupRepositoryIds.push(br.id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const backupArchivesByRemote = await this.restApi.xoApp.listVmBackupsNg(backupRepositoryIds);
|
|
32
|
+
const vmBackupArchives = Object.values(backupArchivesByRemote)
|
|
33
|
+
.map(backupsByVm => Object.values(backupsByVm))
|
|
34
|
+
.flat(2);
|
|
35
|
+
return vmBackupArchives;
|
|
36
|
+
}
|
|
37
|
+
async getCollectionObject(id) {
|
|
38
|
+
const match = id.match(BACKUP_ARCHIVE_ID_REGEX);
|
|
39
|
+
if (match === null) {
|
|
40
|
+
throw noSuchObject(id, 'backup-archive');
|
|
41
|
+
}
|
|
42
|
+
const [, brId, vmId] = match;
|
|
43
|
+
const backupArchive = (await this.restApi.xoApp.listVmBackupsNg([brId]))[brId]?.[vmId]?.find(ba => ba.id === id);
|
|
44
|
+
if (backupArchive === undefined) {
|
|
45
|
+
throw noSuchObject(id, 'backup-archive');
|
|
46
|
+
}
|
|
47
|
+
return backupArchive;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* You can use the alias "*" in "backup-repository" to select all backup repositories.
|
|
52
|
+
*
|
|
53
|
+
* @example backupRepositories ["c4284e12-37c9-7967-b9e8-83ef229c3e03", "1af95910-01b4-4e87-9c2f-d895cafe0776"]
|
|
54
|
+
* @example fields "id,backupRepository,disks"
|
|
55
|
+
* @example filter "disks:length:>0"
|
|
56
|
+
* @example limit 42
|
|
57
|
+
*/
|
|
58
|
+
async getBackupArchives(req, backupRepositories, fields, ndjson, filter, limit) {
|
|
59
|
+
const backupArchives = await this.getObjects({ backupRepositories, filter, limit });
|
|
60
|
+
return this.sendObjects(Object.values(backupArchives), req);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* @example id "231264c3-af43-4ec0-a3be-394c5b1fdbfc//xo-vm-backups/6ef7c09e-677b-1e6f-0546-7ab30413c61c/20250801T080832Z.json"
|
|
64
|
+
*/
|
|
65
|
+
async getBackupArchive(id) {
|
|
66
|
+
const backupArchive = await this.getObject(id);
|
|
67
|
+
return backupArchive;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
__decorate([
|
|
71
|
+
Example(backupArchiveIds),
|
|
72
|
+
Example(partialBackupArchives),
|
|
73
|
+
Get(''),
|
|
74
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
75
|
+
__param(0, Request()),
|
|
76
|
+
__param(1, Query('backup-repository')),
|
|
77
|
+
__param(2, Query()),
|
|
78
|
+
__param(3, Query()),
|
|
79
|
+
__param(4, Query()),
|
|
80
|
+
__param(5, Query())
|
|
81
|
+
], BackupArchiveController.prototype, "getBackupArchives", null);
|
|
82
|
+
__decorate([
|
|
83
|
+
Example(backupArchive),
|
|
84
|
+
Get('{id}'),
|
|
85
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
86
|
+
__param(0, Path())
|
|
87
|
+
], BackupArchiveController.prototype, "getBackupArchive", null);
|
|
88
|
+
BackupArchiveController = __decorate([
|
|
89
|
+
Route('backup-archives'),
|
|
90
|
+
Security('*'),
|
|
91
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
92
|
+
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
93
|
+
Tags('backup-archives'),
|
|
94
|
+
provide(BackupArchiveController)
|
|
95
|
+
], BackupArchiveController);
|
|
96
|
+
export { BackupArchiveController };
|
|
@@ -15,7 +15,7 @@ import { Deprecated, Example, Get, Hidden, Middlewares, Path, Query, Request, Re
|
|
|
15
15
|
import { provide } from 'inversify-binding-decorators';
|
|
16
16
|
import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
|
|
17
17
|
import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
|
|
18
|
-
import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
18
|
+
import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
19
19
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
20
20
|
import { limitAndFilterArray } from '../helpers/utils.helper.mjs';
|
|
21
21
|
import { XoController } from '../abstract-classes/xo-controller.mjs';
|
|
@@ -72,6 +72,7 @@ __decorate([
|
|
|
72
72
|
BackupJobController = __decorate([
|
|
73
73
|
Route('backup-jobs'),
|
|
74
74
|
Security('*'),
|
|
75
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
75
76
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
76
77
|
Tags('backup-jobs'),
|
|
77
78
|
provide(BackupJobController)
|
|
@@ -271,6 +272,7 @@ __decorate([
|
|
|
271
272
|
DeprecatedBackupController = __decorate([
|
|
272
273
|
Route('backup'),
|
|
273
274
|
Security('*'),
|
|
275
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
274
276
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
275
277
|
Middlewares((_req, _res, next) => {
|
|
276
278
|
log.warn('You are calling a deprecated route. It will be removed in the futur. Please use `/rest/v0/backup-jobs` or `/rest/v0/backup-logs` instead');
|
|
@@ -14,7 +14,7 @@ import { provide } from 'inversify-binding-decorators';
|
|
|
14
14
|
import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
|
|
15
15
|
import { BackupLogService } from './backup-log.service.mjs';
|
|
16
16
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
17
|
-
import { unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
17
|
+
import { badRequestResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
18
18
|
import { XoController } from '../abstract-classes/xo-controller.mjs';
|
|
19
19
|
let BackupLogController = class BackupLogController extends XoController {
|
|
20
20
|
#backupLogService;
|
|
@@ -66,6 +66,7 @@ __decorate([
|
|
|
66
66
|
BackupLogController = __decorate([
|
|
67
67
|
Route('backup-logs'),
|
|
68
68
|
Security('*'),
|
|
69
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
69
70
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
70
71
|
Tags('backup-logs'),
|
|
71
72
|
provide(BackupLogController),
|
|
@@ -9,7 +9,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
9
9
|
};
|
|
10
10
|
import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
|
|
11
11
|
import { provide } from 'inversify-binding-decorators';
|
|
12
|
-
import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
12
|
+
import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
13
13
|
import { backupRepositoryIds, partialBackupRepositories, backupRepository, } from '../open-api/oa-examples/backup-repository.oa-example.mjs';
|
|
14
14
|
import { XoController } from '../abstract-classes/xo-controller.mjs';
|
|
15
15
|
let BackupRepositoryController = class BackupRepositoryController extends XoController {
|
|
@@ -54,6 +54,7 @@ __decorate([
|
|
|
54
54
|
BackupRepositoryController = __decorate([
|
|
55
55
|
Route('backup-repositories'),
|
|
56
56
|
Security('*'),
|
|
57
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
57
58
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
58
59
|
Tags('backup-repositories'),
|
|
59
60
|
provide(BackupRepositoryController)
|
|
@@ -12,13 +12,14 @@ import { inject } from 'inversify';
|
|
|
12
12
|
import { json } from 'express';
|
|
13
13
|
import { provide } from 'inversify-binding-decorators';
|
|
14
14
|
import { forbiddenOperation } from 'xo-common/api-errors.js';
|
|
15
|
-
import { createdResp, forbiddenOperationResp, invalidParameters, noContentResp, notFoundResp, resourceAlreadyExists, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
15
|
+
import { badRequestResp, createdResp, forbiddenOperationResp, invalidParameters, noContentResp, notFoundResp, resourceAlreadyExists, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
16
16
|
import { group, groupId, groupIds, partialGroups } from '../open-api/oa-examples/group.oa-example.mjs';
|
|
17
17
|
import { XoController } from '../abstract-classes/xo-controller.mjs';
|
|
18
18
|
import { UserService } from '../users/user.service.mjs';
|
|
19
19
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
20
20
|
import { limitAndFilterArray } from '../helpers/utils.helper.mjs';
|
|
21
21
|
import { partialUsers, userIds } from '../open-api/oa-examples/user.oa-example.mjs';
|
|
22
|
+
import { partialTasks, taskIds } from '../open-api/oa-examples/task.oa-example.mjs';
|
|
22
23
|
let GroupController = class GroupController extends XoController {
|
|
23
24
|
#userService;
|
|
24
25
|
constructor(restApi, userService) {
|
|
@@ -106,6 +107,16 @@ let GroupController = class GroupController extends XoController {
|
|
|
106
107
|
const users = await Promise.all(group.users.map(id => this.#userService.getUser(id)));
|
|
107
108
|
return this.sendObjects(limitAndFilterArray(users, { filter, limit }), req, 'users');
|
|
108
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* @example id "6c81b5e1-afc1-43ea-8f8d-939ceb5f3f90"
|
|
112
|
+
* @example fields "id,status,properties"
|
|
113
|
+
* @example filter "status:failure"
|
|
114
|
+
* @example limit 42
|
|
115
|
+
*/
|
|
116
|
+
async getGroupTasks(req, id, fields, ndjson, filter, limit) {
|
|
117
|
+
const tasks = await this.getTasksForObject(id, { filter, limit });
|
|
118
|
+
return this.sendObjects(Object.values(tasks), req, 'tasks');
|
|
119
|
+
}
|
|
109
120
|
};
|
|
110
121
|
__decorate([
|
|
111
122
|
Example(groupIds),
|
|
@@ -177,9 +188,23 @@ __decorate([
|
|
|
177
188
|
__param(4, Query()),
|
|
178
189
|
__param(5, Query())
|
|
179
190
|
], GroupController.prototype, "getGroupUsers", null);
|
|
191
|
+
__decorate([
|
|
192
|
+
Example(taskIds),
|
|
193
|
+
Example(partialTasks),
|
|
194
|
+
Get('{id}/tasks'),
|
|
195
|
+
Tags('tasks'),
|
|
196
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
197
|
+
__param(0, Request()),
|
|
198
|
+
__param(1, Path()),
|
|
199
|
+
__param(2, Query()),
|
|
200
|
+
__param(3, Query()),
|
|
201
|
+
__param(4, Query()),
|
|
202
|
+
__param(5, Query())
|
|
203
|
+
], GroupController.prototype, "getGroupTasks", null);
|
|
180
204
|
GroupController = __decorate([
|
|
181
205
|
Route('groups'),
|
|
182
206
|
Security('*'),
|
|
207
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
183
208
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
184
209
|
Tags('groups'),
|
|
185
210
|
provide(GroupController),
|
|
@@ -7,7 +7,7 @@ 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 { Example, Get, Path, Query, Request, Response, Route, Security, SuccessResponse, Tags } from 'tsoa';
|
|
10
|
+
import { Delete, Example, Get, Path, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags } from 'tsoa';
|
|
11
11
|
import { inject } from 'inversify';
|
|
12
12
|
import { pipeline } from 'node:stream/promises';
|
|
13
13
|
import { provide } from 'inversify-binding-decorators';
|
|
@@ -17,9 +17,10 @@ import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.m
|
|
|
17
17
|
import { host, hostIds, hostSmt, hostMissingPatches, hostStats, partialHosts, } from '../open-api/oa-examples/host.oa-example.mjs';
|
|
18
18
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
19
19
|
import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
|
|
20
|
-
import { featureUnauthorized, internalServerErrorResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
20
|
+
import { badRequestResp, featureUnauthorized, internalServerErrorResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
21
21
|
import { HostService } from './host.service.mjs';
|
|
22
22
|
import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
|
|
23
|
+
import { partialTasks, taskIds } from '../open-api/oa-examples/task.oa-example.mjs';
|
|
23
24
|
let HostController = class HostController extends XapiXoController {
|
|
24
25
|
#alarmService;
|
|
25
26
|
#hostService;
|
|
@@ -128,6 +129,32 @@ let HostController = class HostController extends XapiXoController {
|
|
|
128
129
|
const messages = this.getMessagesForObject(id, { filter, limit });
|
|
129
130
|
return this.sendObjects(Object.values(messages), req, 'messages');
|
|
130
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
|
|
134
|
+
* @example fields "id,status,properties"
|
|
135
|
+
* @example filter "status:failure"
|
|
136
|
+
* @example limit 42
|
|
137
|
+
*/
|
|
138
|
+
async getHostTasks(req, id, fields, ndjson, filter, limit) {
|
|
139
|
+
const tasks = await this.getTasksForObject(id, { filter, limit });
|
|
140
|
+
return this.sendObjects(Object.values(tasks), req, 'tasks');
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
|
|
144
|
+
* @example tag "from-rest-api"
|
|
145
|
+
*/
|
|
146
|
+
async putHostTag(id, tag) {
|
|
147
|
+
const host = this.getXapiObject(id);
|
|
148
|
+
await host.$call('add_tags', tag);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
|
|
152
|
+
* @example tag "from-rest-api"
|
|
153
|
+
*/
|
|
154
|
+
async deleteHostTag(id, tag) {
|
|
155
|
+
const host = this.getXapiObject(id);
|
|
156
|
+
await host.$call('remove_tags', tag);
|
|
157
|
+
}
|
|
131
158
|
};
|
|
132
159
|
__decorate([
|
|
133
160
|
Example(hostIds),
|
|
@@ -209,9 +236,37 @@ __decorate([
|
|
|
209
236
|
__param(4, Query()),
|
|
210
237
|
__param(5, Query())
|
|
211
238
|
], HostController.prototype, "getHostMessages", null);
|
|
239
|
+
__decorate([
|
|
240
|
+
Example(taskIds),
|
|
241
|
+
Example(partialTasks),
|
|
242
|
+
Get('{id}/tasks'),
|
|
243
|
+
Tags('tasks'),
|
|
244
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
245
|
+
__param(0, Request()),
|
|
246
|
+
__param(1, Path()),
|
|
247
|
+
__param(2, Query()),
|
|
248
|
+
__param(3, Query()),
|
|
249
|
+
__param(4, Query()),
|
|
250
|
+
__param(5, Query())
|
|
251
|
+
], HostController.prototype, "getHostTasks", null);
|
|
252
|
+
__decorate([
|
|
253
|
+
Put('{id}/tags/{tag}'),
|
|
254
|
+
SuccessResponse(noContentResp.status, noContentResp.description),
|
|
255
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
256
|
+
__param(0, Path()),
|
|
257
|
+
__param(1, Path())
|
|
258
|
+
], HostController.prototype, "putHostTag", null);
|
|
259
|
+
__decorate([
|
|
260
|
+
Delete('{id}/tags/{tag}'),
|
|
261
|
+
SuccessResponse(noContentResp.status, noContentResp.description),
|
|
262
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
263
|
+
__param(0, Path()),
|
|
264
|
+
__param(1, Path())
|
|
265
|
+
], HostController.prototype, "deleteHostTag", null);
|
|
212
266
|
HostController = __decorate([
|
|
213
267
|
Route('hosts'),
|
|
214
268
|
Security('*'),
|
|
269
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
215
270
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
216
271
|
Tags('hosts'),
|
|
217
272
|
provide(HostController),
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import genericErrorHandler from './middlewares/generic-error-handler.middleware.
|
|
|
4
4
|
import tsoaToXoErrorHandler from './middlewares/tsoa-to-xo-error.middleware.mjs';
|
|
5
5
|
import { RegisterRoutes } from './open-api/routes/routes.js';
|
|
6
6
|
import { setupContainer } from './ioc/ioc.mjs';
|
|
7
|
+
import { setupApiContext } from './middlewares/authentication.middleware.mjs';
|
|
7
8
|
// Avoid using "import from" to import a json file as this requires assert/with and will break compatibility with recent node versions
|
|
8
9
|
// https://github.com/nodejs/node/issues/51622
|
|
9
10
|
const require = createRequire(import.meta.url);
|
|
@@ -31,14 +32,14 @@ const SWAGGER_UI_OPTIONS = {
|
|
|
31
32
|
};
|
|
32
33
|
export default function setupRestApi(express, xoApp) {
|
|
33
34
|
setupContainer(xoApp);
|
|
35
|
+
express.use(BASE_URL, setupApiContext(xoApp));
|
|
34
36
|
RegisterRoutes(express);
|
|
35
37
|
express.get(`${BASE_URL}/docs/swagger.json`, (_req, res) => {
|
|
36
38
|
res.setHeader('Content-Type', 'application/json');
|
|
37
39
|
res.json(swaggerOpenApiSpec);
|
|
38
40
|
});
|
|
39
41
|
// do not register the doc at the root level, or it may lead to unwanted behaviour
|
|
40
|
-
|
|
41
|
-
// express.get('/rest/v0', (_req, res) => res.redirect('/rest/v0/docs'))
|
|
42
|
+
express.get('/rest/v0', (_req, res) => res.redirect('/rest/v0/docs'));
|
|
42
43
|
express.use(`${BASE_URL}/docs`, swaggerUi.serve, swaggerUi.setup(swaggerOpenApiSpec, SWAGGER_UI_OPTIONS));
|
|
43
44
|
express.use(BASE_URL, tsoaToXoErrorHandler);
|
|
44
45
|
express.use(BASE_URL, genericErrorHandler);
|
package/dist/ioc/ioc.mjs
CHANGED
|
@@ -8,7 +8,6 @@ import { HostService } from '../hosts/host.service.mjs';
|
|
|
8
8
|
import { PoolService } from '../pools/pool.service.mjs';
|
|
9
9
|
import { AlarmService } from '../alarms/alarm.service.mjs';
|
|
10
10
|
import { VdiService } from '../vdis/vdi.service.mjs';
|
|
11
|
-
import { TaskService } from '../tasks/task.service.mjs';
|
|
12
11
|
import { UserService } from '../users/user.service.mjs';
|
|
13
12
|
import { BackupJobService } from '../backup-jobs/backup-job.service.mjs';
|
|
14
13
|
import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
|
|
@@ -58,10 +57,6 @@ export function setupContainer(xoApp) {
|
|
|
58
57
|
return new AlarmService(restApi);
|
|
59
58
|
})
|
|
60
59
|
.inSingletonScope();
|
|
61
|
-
iocContainer.bind(TaskService).toDynamicValue(ctx => {
|
|
62
|
-
const restApi = ctx.container.get(RestApi);
|
|
63
|
-
return new TaskService(restApi);
|
|
64
|
-
});
|
|
65
60
|
iocContainer
|
|
66
61
|
.bind(VdiService)
|
|
67
62
|
.toDynamicValue(ctx => {
|
|
@@ -15,7 +15,7 @@ import { provide } from 'inversify-binding-decorators';
|
|
|
15
15
|
import { alarmPredicate } from '../alarms/alarm.service.mjs';
|
|
16
16
|
import { message, messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
|
|
17
17
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
18
|
-
import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
18
|
+
import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
19
19
|
import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
|
|
20
20
|
let MessageController = class MessageController extends XapiXoController {
|
|
21
21
|
constructor(restapi) {
|
|
@@ -76,6 +76,7 @@ __decorate([
|
|
|
76
76
|
MessageController = __decorate([
|
|
77
77
|
Route('messages'),
|
|
78
78
|
Security('*'),
|
|
79
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
79
80
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
80
81
|
Tags('messages'),
|
|
81
82
|
provide(MessageController),
|
|
@@ -1,34 +1,58 @@
|
|
|
1
1
|
import { createLogger } from '@xen-orchestra/log';
|
|
2
|
-
import {
|
|
2
|
+
import { unauthorized } from 'xo-common/api-errors.js';
|
|
3
3
|
import { iocContainer } from '../ioc/ioc.mjs';
|
|
4
4
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
5
|
-
|
|
5
|
+
import { ApiError } from '../helpers/error.helper.mjs';
|
|
6
6
|
const log = createLogger('xo:rest-api:authentication');
|
|
7
|
-
function getCredentials(securityName, req) {
|
|
8
|
-
const token = req.cookies.token ?? req.cookies.authenticationToken;
|
|
9
|
-
if (securityName === '*' || securityName === 'token') {
|
|
10
|
-
if (token !== undefined) {
|
|
11
|
-
return { token };
|
|
12
|
-
}
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
log.error(`${securityName} not implemented.`);
|
|
16
|
-
throw notImplemented();
|
|
17
|
-
}
|
|
18
7
|
// TODO: correctly handle ACL/Resource set users
|
|
19
8
|
// for now only support "xoa-admin"
|
|
9
|
+
// TSOA spec require this function to be async
|
|
20
10
|
export async function expressAuthentication(req, securityName) {
|
|
21
11
|
const restApi = iocContainer.get(RestApi);
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
throw
|
|
12
|
+
const user = restApi.getCurrentUser();
|
|
13
|
+
const authType = req.res.locals.authType;
|
|
14
|
+
if (securityName !== '*' && authType !== securityName) {
|
|
15
|
+
throw new ApiError(`invalid authentification. please use ${securityName} authentication`, 401);
|
|
26
16
|
}
|
|
27
|
-
const { user } = await restApi.authenticateUser(credentials, { ip });
|
|
28
17
|
if (user.permission !== 'admin') {
|
|
29
18
|
log.error(`The REST API can only be used by 'xoa-admin' users for now. Your permission: ${user.permission}`);
|
|
30
19
|
throw unauthorized();
|
|
31
20
|
}
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
return user;
|
|
22
|
+
}
|
|
23
|
+
export function setupApiContext(xoApp) {
|
|
24
|
+
return async (req, res, next) => {
|
|
25
|
+
const { cookies, headers, ip, query } = req;
|
|
26
|
+
const { authorization } = headers;
|
|
27
|
+
const token = cookies.authenticationToken ?? cookies.token;
|
|
28
|
+
const hasToken = Boolean(token);
|
|
29
|
+
const hasBasic = Boolean(authorization);
|
|
30
|
+
if (hasToken && hasBasic) {
|
|
31
|
+
return next(new ApiError('Having multiple authentication methods is not supported, please choose one', 400));
|
|
32
|
+
}
|
|
33
|
+
if (!hasToken && !hasBasic) {
|
|
34
|
+
return xoApp.runWithApiContext(undefined, next);
|
|
35
|
+
}
|
|
36
|
+
const credentials = {};
|
|
37
|
+
if (hasToken) {
|
|
38
|
+
Object.assign(credentials, { token });
|
|
39
|
+
res.locals.authType = 'token';
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const [, encoded] = authorization.split(' ');
|
|
43
|
+
if (encoded === undefined) {
|
|
44
|
+
return next(new ApiError('Malformed Authorization header', 400));
|
|
45
|
+
}
|
|
46
|
+
const [username, password] = Buffer.from(encoded, 'base64').toString().split(':');
|
|
47
|
+
Object.assign(credentials, { username, password, otp: query.otp });
|
|
48
|
+
res.locals.authType = 'basic';
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const { user } = await xoApp.authenticateUser(credentials, { ip });
|
|
52
|
+
return xoApp.runWithApiContext(user, next);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return next(error);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
34
58
|
}
|
|
@@ -7,16 +7,18 @@ 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 { Example, Get, Path, Query, Response, Request, Route, Security, Tags, Delete, SuccessResponse } from 'tsoa';
|
|
10
|
+
import { Example, Get, Path, Query, Response, Request, Route, Security, Tags, Delete, SuccessResponse, Put } from 'tsoa';
|
|
11
11
|
import { inject } from 'inversify';
|
|
12
12
|
import { provide } from 'inversify-binding-decorators';
|
|
13
13
|
import { AlarmService } from '../alarms/alarm.service.mjs';
|
|
14
14
|
import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
|
|
15
15
|
import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
|
|
16
16
|
import { network, networkIds, partialNetworks } from '../open-api/oa-examples/network.oa-example.mjs';
|
|
17
|
-
import { noContentResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
17
|
+
import { badRequestResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
18
18
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
19
19
|
import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
|
|
20
|
+
import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
|
|
21
|
+
import { partialTasks, taskIds } from '../open-api/oa-examples/task.oa-example.mjs';
|
|
20
22
|
let NetworkController = class NetworkController extends XapiXoController {
|
|
21
23
|
#alarmService;
|
|
22
24
|
constructor(restApi, alarmService) {
|
|
@@ -58,6 +60,42 @@ let NetworkController = class NetworkController extends XapiXoController {
|
|
|
58
60
|
});
|
|
59
61
|
return this.sendObjects(Object.values(alarms), req, 'alarms');
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
|
|
65
|
+
* @example fields "name,id,$object"
|
|
66
|
+
* @example filter "name:VM_STARTED"
|
|
67
|
+
* @example limit 42
|
|
68
|
+
*/
|
|
69
|
+
getNetworkMessages(req, id, fields, ndjson, filter, limit) {
|
|
70
|
+
const messages = this.getMessagesForObject(id, { filter, limit });
|
|
71
|
+
return this.sendObjects(Object.values(messages), req, 'messages');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
|
|
75
|
+
* @example fields "id,status,properties"
|
|
76
|
+
* @example filter "status:failure"
|
|
77
|
+
* @example limit 42
|
|
78
|
+
*/
|
|
79
|
+
async getNetworkTasks(req, id, fields, ndjson, filter, limit) {
|
|
80
|
+
const tasks = await this.getTasksForObject(id, { filter, limit });
|
|
81
|
+
return this.sendObjects(Object.values(tasks), req, 'tasks');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
|
|
85
|
+
* @example tag "from-rest-api"
|
|
86
|
+
*/
|
|
87
|
+
async putNetworkTag(id, tag) {
|
|
88
|
+
const network = this.getXapiObject(id);
|
|
89
|
+
await network.$call('add_tags', tag);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
|
|
93
|
+
* @example tag "from-rest-api"
|
|
94
|
+
*/
|
|
95
|
+
async deleteNetworkTag(id, tag) {
|
|
96
|
+
const network = this.getXapiObject(id);
|
|
97
|
+
await network.$call('remove_tags', tag);
|
|
98
|
+
}
|
|
61
99
|
};
|
|
62
100
|
__decorate([
|
|
63
101
|
Example(networkIds),
|
|
@@ -93,9 +131,50 @@ __decorate([
|
|
|
93
131
|
__param(4, Query()),
|
|
94
132
|
__param(5, Query())
|
|
95
133
|
], NetworkController.prototype, "getNetworkAlarms", null);
|
|
134
|
+
__decorate([
|
|
135
|
+
Example(messageIds),
|
|
136
|
+
Example(partialMessages),
|
|
137
|
+
Get('{id}/messages'),
|
|
138
|
+
Tags('messages'),
|
|
139
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
140
|
+
__param(0, Request()),
|
|
141
|
+
__param(1, Path()),
|
|
142
|
+
__param(2, Query()),
|
|
143
|
+
__param(3, Query()),
|
|
144
|
+
__param(4, Query()),
|
|
145
|
+
__param(5, Query())
|
|
146
|
+
], NetworkController.prototype, "getNetworkMessages", null);
|
|
147
|
+
__decorate([
|
|
148
|
+
Example(taskIds),
|
|
149
|
+
Example(partialTasks),
|
|
150
|
+
Get('{id}/tasks'),
|
|
151
|
+
Tags('tasks'),
|
|
152
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
153
|
+
__param(0, Request()),
|
|
154
|
+
__param(1, Path()),
|
|
155
|
+
__param(2, Query()),
|
|
156
|
+
__param(3, Query()),
|
|
157
|
+
__param(4, Query()),
|
|
158
|
+
__param(5, Query())
|
|
159
|
+
], NetworkController.prototype, "getNetworkTasks", null);
|
|
160
|
+
__decorate([
|
|
161
|
+
Put('{id}/tags/{tag}'),
|
|
162
|
+
SuccessResponse(noContentResp.status, noContentResp.description),
|
|
163
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
164
|
+
__param(0, Path()),
|
|
165
|
+
__param(1, Path())
|
|
166
|
+
], NetworkController.prototype, "putNetworkTag", null);
|
|
167
|
+
__decorate([
|
|
168
|
+
Delete('{id}/tags/{tag}'),
|
|
169
|
+
SuccessResponse(noContentResp.status, noContentResp.description),
|
|
170
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
171
|
+
__param(0, Path()),
|
|
172
|
+
__param(1, Path())
|
|
173
|
+
], NetworkController.prototype, "deleteNetworkTag", null);
|
|
96
174
|
NetworkController = __decorate([
|
|
97
175
|
Route('networks'),
|
|
98
176
|
Security('*'),
|
|
177
|
+
Response(badRequestResp.status, badRequestResp.description),
|
|
99
178
|
Response(unauthorizedResp.status, unauthorizedResp.description),
|
|
100
179
|
Tags('networks'),
|
|
101
180
|
provide(NetworkController),
|