@xen-orchestra/rest-api 0.30.1 → 0.31.1
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/helpers/stream.helper.mjs +17 -2
- package/dist/hosts/host.controller.mjs +29 -2
- package/dist/index.mjs +6 -0
- package/dist/ioc/ioc.mjs +16 -0
- package/dist/licenses/license.service.mjs +23 -0
- package/dist/open-api/routes/routes.js +71 -15
- package/dist/open-api/schema/build-openapi-schema.mjs +29 -0
- package/dist/router/external-router.mjs +260 -0
- package/dist/router/types.mjs +7 -0
- package/dist/srs/sr.controller.mjs +24 -3
- package/dist/srs/sr.service.mjs +27 -0
- package/dist/vifs/vif.controller.mjs +8 -1
- package/dist/vms/vm.controller.mjs +59 -0
- package/open-api/spec/swagger.json +229 -25
- package/package.json +9 -6
- package/turbo.json +8 -0
|
@@ -1,5 +1,20 @@
|
|
|
1
|
-
export function* makeNdJsonStream(
|
|
2
|
-
for (const object of
|
|
1
|
+
export async function* makeNdJsonStream(iterable) {
|
|
2
|
+
for await (const object of iterable) {
|
|
3
3
|
yield JSON.stringify(object) + '\n';
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
|
+
export async function* makeJsonStream(iterable) {
|
|
7
|
+
yield '[';
|
|
8
|
+
let first = true;
|
|
9
|
+
for await (const object of iterable) {
|
|
10
|
+
if (first) {
|
|
11
|
+
first = false;
|
|
12
|
+
yield '\n';
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
yield ',\n';
|
|
16
|
+
}
|
|
17
|
+
yield JSON.stringify(object, null, 2);
|
|
18
|
+
}
|
|
19
|
+
yield '\n]\n';
|
|
20
|
+
}
|
|
@@ -211,6 +211,9 @@ let HostController = class HostController extends XapiXoController {
|
|
|
211
211
|
await host.$call('remove_tags', tag);
|
|
212
212
|
}
|
|
213
213
|
/**
|
|
214
|
+
* Required privilege:
|
|
215
|
+
* - resource: pif, action: update:management
|
|
216
|
+
*
|
|
214
217
|
* Reconfigure the management interface of the host to use the given PIF.
|
|
215
218
|
*
|
|
216
219
|
* The target PIF must already have an IP address configured.
|
|
@@ -240,6 +243,10 @@ let HostController = class HostController extends XapiXoController {
|
|
|
240
243
|
});
|
|
241
244
|
}
|
|
242
245
|
/**
|
|
246
|
+
* Required privileges:
|
|
247
|
+
* - resource: host, action: disable
|
|
248
|
+
* - resource: host, action: evacuate (if `evacuate: true`)
|
|
249
|
+
*
|
|
243
250
|
* Disable a host.
|
|
244
251
|
*
|
|
245
252
|
* Set `evacuate` to `true` to also evacuate all running VMs to other hosts in the pool.
|
|
@@ -285,6 +292,9 @@ let HostController = class HostController extends XapiXoController {
|
|
|
285
292
|
});
|
|
286
293
|
}
|
|
287
294
|
/**
|
|
295
|
+
* Required privilege:
|
|
296
|
+
* - resource: host, action: enable
|
|
297
|
+
*
|
|
288
298
|
* Enable a host, taking it out of disabled state.
|
|
289
299
|
*
|
|
290
300
|
* @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
|
|
@@ -438,9 +448,10 @@ __decorate([
|
|
|
438
448
|
__decorate([
|
|
439
449
|
Example(taskLocation),
|
|
440
450
|
Post('{id}/actions/management_reconfigure'),
|
|
441
|
-
Middlewares(json()),
|
|
451
|
+
Middlewares([json(), acl({ resource: 'pif', action: 'update:management', objectId: 'body.pif' })]),
|
|
442
452
|
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
|
|
443
453
|
Response(noContentResp.status, noContentResp.description),
|
|
454
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
444
455
|
Response(notFoundResp.status, notFoundResp.description),
|
|
445
456
|
Response(badRequestResp.status, badRequestResp.description),
|
|
446
457
|
Response(invalidParametersResp.status, invalidParametersResp.description),
|
|
@@ -452,9 +463,23 @@ __decorate([
|
|
|
452
463
|
__decorate([
|
|
453
464
|
Example(taskLocation),
|
|
454
465
|
Post('{id}/actions/disable'),
|
|
455
|
-
Middlewares(
|
|
466
|
+
Middlewares([
|
|
467
|
+
json(),
|
|
468
|
+
acl({
|
|
469
|
+
resource: 'host',
|
|
470
|
+
actions: ({ req }) => {
|
|
471
|
+
const actions = ['disable'];
|
|
472
|
+
if (req.body?.evacuate) {
|
|
473
|
+
actions.push('evacuate');
|
|
474
|
+
}
|
|
475
|
+
return actions;
|
|
476
|
+
},
|
|
477
|
+
objectId: 'params.id',
|
|
478
|
+
}),
|
|
479
|
+
]),
|
|
456
480
|
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
|
|
457
481
|
Response(noContentResp.status, noContentResp.description),
|
|
482
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
458
483
|
Response(notFoundResp.status, notFoundResp.description),
|
|
459
484
|
Response(invalidParametersResp.status, invalidParametersResp.description),
|
|
460
485
|
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
@@ -465,8 +490,10 @@ __decorate([
|
|
|
465
490
|
__decorate([
|
|
466
491
|
Example(taskLocation),
|
|
467
492
|
Post('{id}/actions/enable'),
|
|
493
|
+
Middlewares(acl({ resource: 'host', action: 'enable', objectId: 'params.id' })),
|
|
468
494
|
SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
|
|
469
495
|
Response(noContentResp.status, noContentResp.description),
|
|
496
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
470
497
|
Response(notFoundResp.status, notFoundResp.description),
|
|
471
498
|
Response(internalServerErrorResp.status, internalServerErrorResp.description),
|
|
472
499
|
__param(0, Path()),
|
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { RegisterRoutes } from './open-api/routes/routes.js';
|
|
|
6
6
|
import { setupContainer } from './ioc/ioc.mjs';
|
|
7
7
|
import { setupApiContext } from './middlewares/authentication.middleware.mjs';
|
|
8
8
|
import { logMiddleware } from './middlewares/log.middleware.mjs';
|
|
9
|
+
import { createExternalRouter, sendObjects } from './router/external-router.mjs';
|
|
10
|
+
export { sendObjects };
|
|
9
11
|
// Avoid using "import from" to import a json file as this requires assert/with and will break compatibility with recent node versions
|
|
10
12
|
// https://github.com/nodejs/node/issues/51622
|
|
11
13
|
const require = createRequire(import.meta.url);
|
|
@@ -34,9 +36,12 @@ const SWAGGER_UI_OPTIONS = {
|
|
|
34
36
|
};
|
|
35
37
|
export default function setupRestApi(express, xoApp) {
|
|
36
38
|
setupContainer(xoApp);
|
|
39
|
+
// Create dynamic router so it can be used by plugin to register rest routes
|
|
40
|
+
const { mountExternalRoute, externalRouter } = createExternalRouter(swaggerOpenApiSpec);
|
|
37
41
|
express.use(BASE_URL, setupApiContext(xoApp));
|
|
38
42
|
express.use(BASE_URL, logMiddleware);
|
|
39
43
|
RegisterRoutes(express);
|
|
44
|
+
express.use(BASE_URL, externalRouter);
|
|
40
45
|
express.get(`${BASE_URL}/docs/swagger.json`, (_req, res) => {
|
|
41
46
|
res.setHeader('Content-Type', 'application/json');
|
|
42
47
|
res.json(swaggerOpenApiSpec);
|
|
@@ -46,4 +51,5 @@ export default function setupRestApi(express, xoApp) {
|
|
|
46
51
|
express.use(`${BASE_URL}/docs`, swaggerUi.serveFiles(undefined, SWAGGER_UI_OPTIONS), swaggerUi.setup(null, SWAGGER_UI_OPTIONS));
|
|
47
52
|
express.use(BASE_URL, tsoaToXoErrorHandler);
|
|
48
53
|
express.use(BASE_URL, genericErrorHandler);
|
|
54
|
+
return { mountExternalRoute };
|
|
49
55
|
}
|
package/dist/ioc/ioc.mjs
CHANGED
|
@@ -14,6 +14,8 @@ import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
|
|
|
14
14
|
import { EventService } from '../events/event.service.mjs';
|
|
15
15
|
import { NetworkService } from '../networks/network.service.mjs';
|
|
16
16
|
import { BackupArchiveService } from '../backup-archives/backup-archive.service.mjs';
|
|
17
|
+
import { SrService } from '../srs/sr.service.mjs';
|
|
18
|
+
import { LicenseService } from '../licenses/license.service.mjs';
|
|
17
19
|
const iocContainer = new Container();
|
|
18
20
|
export function setupContainer(xoApp) {
|
|
19
21
|
decorate(injectable(), Controller);
|
|
@@ -109,5 +111,19 @@ export function setupContainer(xoApp) {
|
|
|
109
111
|
return new BackupArchiveService(restApi);
|
|
110
112
|
})
|
|
111
113
|
.inSingletonScope();
|
|
114
|
+
iocContainer
|
|
115
|
+
.bind(SrService)
|
|
116
|
+
.toDynamicValue(ctx => {
|
|
117
|
+
const restApi = ctx.container.get(RestApi);
|
|
118
|
+
return new SrService(restApi);
|
|
119
|
+
})
|
|
120
|
+
.inSingletonScope();
|
|
121
|
+
iocContainer
|
|
122
|
+
.bind(LicenseService)
|
|
123
|
+
.toDynamicValue(ctx => {
|
|
124
|
+
const restApi = ctx.container.get(RestApi);
|
|
125
|
+
return new LicenseService(restApi);
|
|
126
|
+
})
|
|
127
|
+
.inSingletonScope();
|
|
112
128
|
}
|
|
113
129
|
export { iocContainer };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createLogger } from '@xen-orchestra/log';
|
|
2
|
+
const log = createLogger('xo:rest-api:license-service');
|
|
3
|
+
export class LicenseService {
|
|
4
|
+
#restApi;
|
|
5
|
+
constructor(restApi) {
|
|
6
|
+
this.#restApi = restApi;
|
|
7
|
+
}
|
|
8
|
+
async getXostorLicenses(srId) {
|
|
9
|
+
const xapiSr = this.#restApi.getXapiObject(srId, 'SR');
|
|
10
|
+
const xapi = xapiSr.$xapi;
|
|
11
|
+
const licenses = await this.#restApi.xoApp.getLicenses({ productType: 'xostor' });
|
|
12
|
+
const result = [];
|
|
13
|
+
for (const host of Object.values(xapi.objects.indexes.type.host)) {
|
|
14
|
+
const license = licenses.find(l => l.boundObjectId === host.uuid);
|
|
15
|
+
if (license === undefined) {
|
|
16
|
+
log.warn('no xostor license found for host', { hostId: host.uuid });
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
result.push({ licenseId: license.id, boundObjectId: host.uuid });
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
}
|