@xen-orchestra/rest-api 0.30.1 → 0.31.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.
@@ -1,5 +1,20 @@
1
- export function* makeNdJsonStream(array) {
2
- for (const object of array) {
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(json()),
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
+ }