@xen-orchestra/rest-api 0.1.2 → 0.3.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.
Files changed (34) hide show
  1. package/README.md +2 -0
  2. package/dist/abstract-classes/base-controller.mjs +37 -0
  3. package/dist/abstract-classes/xapi-xo-controller.mjs +5 -10
  4. package/dist/abstract-classes/xo-controller.mjs +40 -0
  5. package/dist/helpers/object-wrapper.helper.mjs +2 -2
  6. package/dist/hosts/host.controller.mjs +75 -0
  7. package/dist/index.mjs +4 -3
  8. package/dist/middlewares/generic-error-handler.middleware.mjs +5 -1
  9. package/dist/open-api/common/response.common.mjs +21 -0
  10. package/dist/open-api/oa-examples/host.oa-example.mjs +777 -0
  11. package/dist/open-api/oa-examples/server.oa-example.mjs +28 -0
  12. package/dist/open-api/oa-examples/sr.oa-example.mjs +81 -0
  13. package/dist/open-api/oa-examples/task.oa-example.mjs +1 -0
  14. package/dist/open-api/oa-examples/vbd.oa-example.mjs +34 -0
  15. package/dist/open-api/oa-examples/vdi-snapshot.oa-example.mjs +42 -0
  16. package/dist/open-api/oa-examples/vdi.oa-example.mjs +84 -0
  17. package/dist/open-api/oa-examples/vm-controller.oa-example.mjs +71 -0
  18. package/dist/open-api/oa-examples/vm-snapshot.oa-example.mjs +178 -0
  19. package/dist/open-api/oa-examples/vm-template.oa-example.mjs +91 -0
  20. package/dist/open-api/oa-examples/vm.oa-example.mjs +144 -0
  21. package/dist/open-api/routes/routes.js +833 -23
  22. package/dist/rest-api/rest-api.mjs +9 -0
  23. package/dist/servers/server.controller.mjs +60 -0
  24. package/dist/srs/sr.controller.mjs +59 -0
  25. package/dist/vbds/vbd.controller.mjs +61 -0
  26. package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +59 -0
  27. package/dist/vdis/vdi.controller.mjs +59 -0
  28. package/dist/vm-controller/vm-controller.controller.mjs +60 -0
  29. package/dist/vm-snapshots/vm-snapshot.controller.mjs +60 -0
  30. package/dist/vm-templates/vm-template.controller.mjs +59 -0
  31. package/dist/vms/vm.controller.mjs +60 -4
  32. package/open-api/spec/swagger.json +13933 -384
  33. package/package.json +2 -2
  34. package/tsoa.json +3 -0
package/README.md CHANGED
@@ -28,6 +28,7 @@ The REST API is based on the `TSOA` framework and therefore we use decorators a
28
28
  @Routes('foo')
29
29
  @Security('*')
30
30
  @Response(401)
31
+ @Tags('foo')
31
32
  @provide(Foo)
32
33
  class Foo extends Controller {}
33
34
  ```
@@ -47,6 +48,7 @@ class Foo extends Controller {
47
48
  @Example(['foo', 'bar'])
48
49
  @Get('{id}')
49
50
  @Security('*')
51
+ @SuccessResponse(202)
50
52
  @Response(404)
51
53
  getFoo(@Path() id: string) {
52
54
  return this.getFoo(id)
@@ -0,0 +1,37 @@
1
+ import { Controller } from 'tsoa';
2
+ import { BASE_URL } from '../index.mjs';
3
+ import { makeObjectMapper } from '../helpers/object-wrapper.helper.mjs';
4
+ const noop = () => { };
5
+ export class BaseController extends Controller {
6
+ restApi;
7
+ constructor(restApi) {
8
+ super();
9
+ this.restApi = restApi;
10
+ }
11
+ sendObjects(objects, req) {
12
+ const mapper = makeObjectMapper(req);
13
+ const mappedObjects = objects.map(mapper);
14
+ return mappedObjects;
15
+ }
16
+ /**
17
+ * statusCode must represent the status code in case of a synchronous request. Default 200
18
+ */
19
+ async createAction(cb, { statusCode = 200, sync = false, taskProperties, }) {
20
+ taskProperties.name = 'REST API: ' + taskProperties.name;
21
+ taskProperties.type = 'xo:rest-api:action';
22
+ const task = this.restApi.tasks.create(taskProperties);
23
+ const pResult = task.run(cb);
24
+ if (sync) {
25
+ this.setStatus(statusCode);
26
+ return pResult;
27
+ }
28
+ else {
29
+ pResult.catch(noop);
30
+ const location = `${BASE_URL}/tasks/${task.id}`;
31
+ this.setStatus(202);
32
+ this.setHeader('Location', location);
33
+ this.setHeader('Content-Type', 'text/plain');
34
+ return location;
35
+ }
36
+ }
37
+ }
@@ -1,13 +1,10 @@
1
1
  import * as CM from 'complex-matcher';
2
- import { Controller } from 'tsoa';
3
- import { makeObjectMapper } from '../helpers/object-wrapper.helper.mjs';
4
- export class XapiXoController extends Controller {
2
+ import { BaseController } from './base-controller.mjs';
3
+ export class XapiXoController extends BaseController {
5
4
  #type;
6
- restApi;
7
5
  constructor(type, restApi) {
8
- super();
6
+ super(restApi);
9
7
  this.#type = type;
10
- this.restApi = restApi;
11
8
  }
12
9
  getObjects({ filter, limit } = {}) {
13
10
  if (filter !== undefined) {
@@ -18,9 +15,7 @@ export class XapiXoController extends Controller {
18
15
  getObject(id) {
19
16
  return this.restApi.getObject(id, this.#type);
20
17
  }
21
- sendObjects(objects, req) {
22
- const mapper = makeObjectMapper(req);
23
- const mappedObjects = objects.map(mapper);
24
- return mappedObjects;
18
+ getXapiObject(maybeId) {
19
+ return this.restApi.getXapiObject(maybeId, this.#type);
25
20
  }
26
21
  }
@@ -0,0 +1,40 @@
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 * as CM from 'complex-matcher';
11
+ import { inject } from 'inversify';
12
+ import { BaseController } from './base-controller.mjs';
13
+ import { RestApi } from '../rest-api/rest-api.mjs';
14
+ let XoController = class XoController extends BaseController {
15
+ constructor(restApi) {
16
+ super(restApi);
17
+ }
18
+ async getObjects({ filter, limit = Infinity } = {}) {
19
+ let objects = await this.getAllCollectionObjects();
20
+ if (filter !== undefined) {
21
+ const predicate = CM.parse(filter).createPredicate();
22
+ objects = objects.filter(predicate);
23
+ }
24
+ if (limit < objects.length) {
25
+ objects.length = limit;
26
+ }
27
+ const objectById = {};
28
+ objects.forEach(obj => {
29
+ objectById[obj.id] = obj;
30
+ });
31
+ return objectById;
32
+ }
33
+ async getObject(id) {
34
+ return this.getCollectionObject(id);
35
+ }
36
+ };
37
+ XoController = __decorate([
38
+ __param(0, inject(RestApi))
39
+ ], XoController);
40
+ export { XoController };
@@ -2,9 +2,9 @@ import path from 'node:path';
2
2
  import pick from 'lodash/pick.js';
3
3
  const { join } = path.posix;
4
4
  export function makeObjectMapper(req, path = req.path) {
5
- const makeUrl = ({ id }) => join(baseUrl, path, typeof id === 'number' ? String(id) : id);
5
+ const makeUrl = ({ id }) => join(path, typeof id === 'number' ? String(id) : id);
6
6
  let objectMapper;
7
- const { query, baseUrl } = req;
7
+ const { query } = req;
8
8
  const { fields } = query;
9
9
  if (fields === '*') {
10
10
  objectMapper = object => ({
@@ -0,0 +1,75 @@
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 { inject } from 'inversify';
12
+ import { provide } from 'inversify-binding-decorators';
13
+ import { host, hostIds, hostStats, partialHosts } from '../open-api/oa-examples/host.oa-example.mjs';
14
+ import { RestApi } from '../rest-api/rest-api.mjs';
15
+ import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
16
+ import { internalServerErrorResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
17
+ let HostController = class HostController extends XapiXoController {
18
+ constructor(restApi) {
19
+ super('host', restApi);
20
+ }
21
+ /**
22
+ * @example fields "id,name_label,productBrand"
23
+ * @example filter "productBrand:XCP-ng"
24
+ * @example limit 42
25
+ */
26
+ getHosts(req, fields, filter, limit) {
27
+ return this.sendObjects(Object.values(this.getObjects({ filter, limit })), req);
28
+ }
29
+ /**
30
+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
31
+ */
32
+ getHost(id) {
33
+ return this.getObject(id);
34
+ }
35
+ /**
36
+ * Host must be running
37
+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
38
+ */
39
+ getHostStats(id, granularity) {
40
+ return this.restApi.xoApp.getXapiHostStats(id, granularity);
41
+ }
42
+ };
43
+ __decorate([
44
+ Example(hostIds),
45
+ Example(partialHosts),
46
+ Get(''),
47
+ __param(0, Request()),
48
+ __param(1, Query()),
49
+ __param(2, Query()),
50
+ __param(3, Query())
51
+ ], HostController.prototype, "getHosts", null);
52
+ __decorate([
53
+ Example(host),
54
+ Get('{id}'),
55
+ Response(notFoundResp.status, notFoundResp.description),
56
+ __param(0, Path())
57
+ ], HostController.prototype, "getHost", null);
58
+ __decorate([
59
+ Example(hostStats),
60
+ Get('{id}/stats'),
61
+ Response(notFoundResp.status, notFoundResp.description),
62
+ Response(422, 'Invalid granularity'),
63
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
64
+ __param(0, Path()),
65
+ __param(1, Query())
66
+ ], HostController.prototype, "getHostStats", null);
67
+ HostController = __decorate([
68
+ Route('hosts'),
69
+ Security('*'),
70
+ Response(unauthorizedResp.status, unauthorizedResp.description),
71
+ Tags('hosts'),
72
+ provide(HostController),
73
+ __param(0, inject(RestApi))
74
+ ], HostController);
75
+ export { HostController };
package/dist/index.mjs CHANGED
@@ -8,13 +8,14 @@ import { setupContainer } from './ioc/ioc.mjs';
8
8
  // https://github.com/nodejs/node/issues/51622
9
9
  const require = createRequire(import.meta.url);
10
10
  const swaggerOpenApiSpec = require('../open-api/spec/swagger.json');
11
+ export const BASE_URL = '/rest/v0';
11
12
  export default function setupRestApi(express, xoApp) {
12
13
  setupContainer(xoApp);
13
14
  RegisterRoutes(express);
14
15
  // do not register the doc at the root level, or it may lead to unwated behaviour
15
16
  // uncomment when all endpoints are migrated to this API
16
17
  // express.get('/rest/v0', (_req, res) => res.redirect('/rest/v0/docs'))
17
- express.use('/rest/v0/docs', swaggerUi.serve, swaggerUi.setup(swaggerOpenApiSpec));
18
- express.use('/rest/v0', tsoaToXoErrorHandler);
19
- express.use('/rest/v0', genericErrorHandler);
18
+ express.use(`${BASE_URL}/docs`, swaggerUi.serve, swaggerUi.setup(swaggerOpenApiSpec));
19
+ express.use(BASE_URL, tsoaToXoErrorHandler);
20
+ express.use(BASE_URL, genericErrorHandler);
20
21
  }
@@ -9,6 +9,7 @@ export default function genericErrorHandler(error, req, res, _next) {
9
9
  res.status(500).json({ error });
10
10
  return;
11
11
  }
12
+ const responseError = { error: error.message };
12
13
  if (noSuchObject.is(error)) {
13
14
  res.status(404);
14
15
  }
@@ -25,9 +26,12 @@ export default function genericErrorHandler(error, req, res, _next) {
25
26
  res.status(501);
26
27
  }
27
28
  else {
29
+ if (error.name === 'XapiError') {
30
+ responseError.info = 'This is a XenServer/XCP-ng error, not an XO error';
31
+ }
28
32
  res.status(500);
29
33
  log.error(error);
30
34
  }
31
35
  log.info(`[${req.method}] ${req.path} (${res.statusCode})`);
32
- res.json({ error: error.message });
36
+ res.json(responseError);
33
37
  }
@@ -0,0 +1,21 @@
1
+ export const actionAsyncroneResp = {
2
+ status: 202,
3
+ description: 'Action executed asynchronously',
4
+ produce: 'text/plain',
5
+ };
6
+ export const unauthorizedResp = {
7
+ status: 401,
8
+ description: 'Authentication required',
9
+ };
10
+ export const notFoundResp = {
11
+ status: 404,
12
+ description: 'Resource not found',
13
+ };
14
+ export const noContentResp = {
15
+ status: 204,
16
+ description: 'No content',
17
+ };
18
+ export const internalServerErrorResp = {
19
+ status: 500,
20
+ description: 'Internal server error, XenServer/XCP-ng error',
21
+ };