@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.
@@ -44,6 +44,9 @@ let PbdController = class PbdController extends XapiXoController {
44
44
  return this.getObject(id);
45
45
  }
46
46
  /**
47
+ * Required privilege:
48
+ * - resource: pbd, action: plug
49
+ *
47
50
  * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
48
51
  */
49
52
  async plugPbd(id, sync) {
@@ -62,6 +65,9 @@ let PbdController = class PbdController extends XapiXoController {
62
65
  });
63
66
  }
64
67
  /**
68
+ * Required privilege:
69
+ * - resource: pbd, action: unplug
70
+ *
65
71
  * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
66
72
  */
67
73
  async unplugPbd(id, sync) {
@@ -106,8 +112,10 @@ __decorate([
106
112
  Example(taskLocation),
107
113
  Extension('x-mcp-exposure', 'confirm'),
108
114
  Post('{id}/actions/plug'),
115
+ Middlewares(acl({ resource: 'pbd', action: 'plug', objectId: 'params.id' })),
109
116
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
110
117
  Response(noContentResp.status, noContentResp.description),
118
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
111
119
  Response(notFoundResp.status, notFoundResp.description),
112
120
  Response(invalidParametersResp.status, invalidParametersResp.description),
113
121
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
@@ -118,8 +126,10 @@ __decorate([
118
126
  Example(taskLocation),
119
127
  Extension('x-mcp-exposure', 'confirm'),
120
128
  Post('{id}/actions/unplug'),
129
+ Middlewares(acl({ resource: 'pbd', action: 'unplug', objectId: 'params.id' })),
121
130
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
122
131
  Response(noContentResp.status, noContentResp.description),
132
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
123
133
  Response(notFoundResp.status, notFoundResp.description),
124
134
  Response(invalidParametersResp.status, invalidParametersResp.description),
125
135
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
@@ -423,6 +423,33 @@ let PoolController = class PoolController extends XapiXoController {
423
423
  },
424
424
  });
425
425
  }
426
+ /**
427
+ * Required privileges:
428
+ * - resource: pool, action: add-host
429
+ * - resource: host, action: join-pool
430
+ *
431
+ * Add a host to the pool.
432
+ *
433
+ * @example id "355ee47d-ff4c-4924-3db2-fd86ae629676"
434
+ * @example body { "host": "c787b75c-3e0d-70fa-d0c3-cbfd382d7e33", "force": "false" }
435
+ */
436
+ addHost(id, body, sync) {
437
+ const poolId = id;
438
+ const action = async () => {
439
+ const { _auth: { user, password }, _url: { hostnameRaw }, } = this.restApi.xoApp.getXapi(poolId);
440
+ const hostXapi = this.restApi.xoApp.getXapi(body.host);
441
+ await hostXapi.joinPool(hostnameRaw, user, password, body.force);
442
+ };
443
+ return this.createAction(action, {
444
+ sync,
445
+ statusCode: noContentResp.status,
446
+ taskProperties: {
447
+ name: 'add host',
448
+ objectId: poolId,
449
+ params: body,
450
+ },
451
+ });
452
+ }
426
453
  };
427
454
  __decorate([
428
455
  Example(poolIds),
@@ -729,6 +756,27 @@ __decorate([
729
756
  __param(1, Body()),
730
757
  __param(2, Query())
731
758
  ], PoolController.prototype, "managementReconfigure", null);
759
+ __decorate([
760
+ Example(taskLocation),
761
+ Post('{id}/actions/add_host'),
762
+ Middlewares([
763
+ json(),
764
+ acl([
765
+ { resource: 'pool', action: 'add-host', objectId: 'params.id' },
766
+ { resource: 'host', action: 'join-pool', objectId: 'body.host' },
767
+ ]),
768
+ ]),
769
+ SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
770
+ Response(noContentResp.status, noContentResp.description),
771
+ Response(badRequestResp.status, badRequestResp.description),
772
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
773
+ Response(notFoundResp.status, notFoundResp.description),
774
+ Response(invalidParametersResp.status, invalidParametersResp.description),
775
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
776
+ __param(0, Path()),
777
+ __param(1, Body()),
778
+ __param(2, Query())
779
+ ], PoolController.prototype, "addHost", null);
732
780
  PoolController = __decorate([
733
781
  Route('pools'),
734
782
  Security('*'),
@@ -158,6 +158,9 @@ let SrController = class SrController extends XapiXoController {
158
158
  await sr.$call('remove_tags', tag);
159
159
  }
160
160
  /**
161
+ * Required privilege:
162
+ * - resource: sr, action: reclaim-space
163
+ *
161
164
  * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
162
165
  */
163
166
  async reclaimSpaceSr(id, sync) {
@@ -176,6 +179,9 @@ let SrController = class SrController extends XapiXoController {
176
179
  });
177
180
  }
178
181
  /**
182
+ * Required privilege:
183
+ * - resource: sr, action: scan
184
+ *
179
185
  * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
180
186
  */
181
187
  async scanSr(id, sync) {
@@ -194,6 +200,9 @@ let SrController = class SrController extends XapiXoController {
194
200
  });
195
201
  }
196
202
  /**
203
+ * Required privilege:
204
+ * - resource: sr, action: forget
205
+ *
197
206
  * @example id "c4284e12-37c9-7967-b9e8-83ef229c3e03"
198
207
  */
199
208
  async forgetSr(id, sync) {
@@ -329,8 +338,10 @@ __decorate([
329
338
  Example(taskLocation),
330
339
  Extension('x-mcp-exposure', 'confirm'),
331
340
  Post('{id}/actions/reclaim_space'),
341
+ Middlewares(acl({ resource: 'sr', action: 'reclaim-space', objectId: 'params.id' })),
332
342
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
333
343
  Response(noContentResp.status, noContentResp.description),
344
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
334
345
  Response(notFoundResp.status, notFoundResp.description),
335
346
  Response(invalidParametersResp.status, invalidParametersResp.description),
336
347
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
@@ -341,8 +352,10 @@ __decorate([
341
352
  Example(taskLocation),
342
353
  Extension('x-mcp-exposure', 'confirm'),
343
354
  Post('{id}/actions/scan'),
355
+ Middlewares(acl({ resource: 'sr', action: 'scan', objectId: 'params.id' })),
344
356
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
345
357
  Response(noContentResp.status, noContentResp.description),
358
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
346
359
  Response(notFoundResp.status, notFoundResp.description),
347
360
  Response(invalidParametersResp.status, invalidParametersResp.description),
348
361
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
@@ -353,8 +366,10 @@ __decorate([
353
366
  Example(taskLocation),
354
367
  Extension('x-mcp-exposure', 'confirm'),
355
368
  Post('{id}/actions/forget'),
369
+ Middlewares(acl({ resource: 'sr', action: 'forget', objectId: 'params.id' })),
356
370
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
357
371
  Response(noContentResp.status, noContentResp.description),
372
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
358
373
  Response(notFoundResp.status, notFoundResp.description),
359
374
  Response(invalidParametersResp.status, invalidParametersResp.description),
360
375
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
@@ -53,6 +53,9 @@ let VbdController = class VbdController extends XapiXoController {
53
53
  return this.getObject(id);
54
54
  }
55
55
  /**
56
+ * Required privilege:
57
+ * - resource: vbd, action: create
58
+ *
56
59
  * Create a VBD to attach a VDI to a VM
57
60
  *
58
61
  * @example body { "VM": "4fe90510-8da4-1530-38e2-a7876ef374c7", "VDI": "656052a2-2e3e-467b-88ba-63a9ea5e4a54", "bootable": false, "mode": "RW" }
@@ -76,6 +79,9 @@ let VbdController = class VbdController extends XapiXoController {
76
79
  return { id: vbdUuid };
77
80
  }
78
81
  /**
82
+ * Required privilege:
83
+ * - resource: vbd, action: delete
84
+ *
79
85
  * Delete a VBD
80
86
  *
81
87
  * Removes the virtual block device, detaching the VDI from the VM.
@@ -142,6 +148,9 @@ let VbdController = class VbdController extends XapiXoController {
142
148
  });
143
149
  }
144
150
  /**
151
+ * Required privilege:
152
+ * - resource: vbd, action: connect
153
+ *
145
154
  * Hotplug the VBD, dynamically attaching it to the running VM
146
155
  * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
147
156
  */
@@ -161,6 +170,9 @@ let VbdController = class VbdController extends XapiXoController {
161
170
  });
162
171
  }
163
172
  /**
173
+ * Required privilege:
174
+ * - resource: vbd, action: disconnect
175
+ *
164
176
  * Hot-unplug the VBD, dynamically detaching it from the running VM
165
177
  * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
166
178
  */
@@ -206,8 +218,9 @@ __decorate([
206
218
  Example(vbdId),
207
219
  Extension('x-mcp-exposure', 'confirm'),
208
220
  Post(''),
209
- Middlewares(json()),
221
+ Middlewares([json(), acl({ resource: 'vbd', action: 'create', object: ({ req }) => req.body })]),
210
222
  SuccessResponse(createdResp.status, createdResp.description),
223
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
211
224
  Response(notFoundResp.status, notFoundResp.description),
212
225
  Response(invalidParameters.status, invalidParameters.description),
213
226
  __param(0, Body())
@@ -215,7 +228,9 @@ __decorate([
215
228
  __decorate([
216
229
  Extension('x-mcp-exposure', 'confirm'),
217
230
  Delete('{id}'),
231
+ Middlewares(acl({ resource: 'vbd', action: 'delete', objectId: 'params.id' })),
218
232
  SuccessResponse(noContentResp.status, noContentResp.description),
233
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
219
234
  Response(notFoundResp.status, notFoundResp.description),
220
235
  __param(0, Path())
221
236
  ], VbdController.prototype, "deleteVbd", null);
@@ -270,8 +285,10 @@ __decorate([
270
285
  Example(taskLocation),
271
286
  Extension('x-mcp-exposure', 'confirm'),
272
287
  Post('{id}/actions/connect'),
288
+ Middlewares(acl({ resource: 'vbd', action: 'connect', objectId: 'params.id' })),
273
289
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
274
290
  Response(noContentResp.status, noContentResp.description),
291
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
275
292
  Response(notFoundResp.status, notFoundResp.description),
276
293
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
277
294
  __param(0, Path()),
@@ -281,8 +298,10 @@ __decorate([
281
298
  Example(taskLocation),
282
299
  Extension('x-mcp-exposure', 'confirm'),
283
300
  Post('{id}/actions/disconnect'),
301
+ Middlewares(acl({ resource: 'vbd', action: 'disconnect', objectId: 'params.id' })),
284
302
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
285
303
  Response(noContentResp.status, noContentResp.description),
304
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
286
305
  Response(notFoundResp.status, notFoundResp.description),
287
306
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
288
307
  __param(0, Path()),
@@ -7,11 +7,12 @@ 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 { Delete, Example, Extension, Get, Path, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
10
+ import { Delete, Example, Extension, Get, Middlewares, Path, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
12
  import { provide } from 'inversify-binding-decorators';
13
+ import { acl } from '../middlewares/acl.middleware.mjs';
13
14
  import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
14
- import { badRequestResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
15
+ import { badRequestResp, forbiddenOperationResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
15
16
  import { RestApi } from '../rest-api/rest-api.mjs';
16
17
  import { partialVdiSnapshots, vdiSnapshot, vdiSnapshotIds } from '../open-api/oa-examples/vdi-snapshot.oa-example.mjs';
17
18
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
@@ -43,6 +44,8 @@ let VdiSnapshotController = class VdiSnapshotController extends XapiXoController
43
44
  });
44
45
  }
45
46
  /**
47
+ * Required privilege:
48
+ * - resource: vdi-snapshot, action: export
46
49
  *
47
50
  * Export VDI-snapshot content
48
51
  *
@@ -59,6 +62,9 @@ let VdiSnapshotController = class VdiSnapshotController extends XapiXoController
59
62
  return stream;
60
63
  }
61
64
  /**
65
+ * Required privilege:
66
+ * - resource: vdi-snapshot, action: read
67
+ *
62
68
  * @example id "d2727772-735b-478f-b6f9-11e7db56dfd0"
63
69
  */
64
70
  getVdiSnapshot(id) {
@@ -85,6 +91,9 @@ let VdiSnapshotController = class VdiSnapshotController extends XapiXoController
85
91
  });
86
92
  }
87
93
  /**
94
+ * Required privilege:
95
+ * - resource: vdi-snapshot, action: delete
96
+ *
88
97
  * @example id "d2727772-735b-478f-b6f9-11e7db56dfd0"
89
98
  */
90
99
  async deleteVdiSnapshot(id) {
@@ -126,6 +135,9 @@ let VdiSnapshotController = class VdiSnapshotController extends XapiXoController
126
135
  });
127
136
  }
128
137
  /**
138
+ * Required privilege:
139
+ * - resource: vdi-snapshot, action: update:tags
140
+ *
129
141
  * @example id "d2727772-735b-478f-b6f9-11e7db56dfd0"
130
142
  * @example tag "from-rest-api"
131
143
  */
@@ -134,6 +146,9 @@ let VdiSnapshotController = class VdiSnapshotController extends XapiXoController
134
146
  await vdiSnapshot.$call('add_tags', tag);
135
147
  }
136
148
  /**
149
+ * Required privilege:
150
+ * - resource: vdi-snapshot, action: update:tags
151
+ *
137
152
  * @example id "d2727772-735b-478f-b6f9-11e7db56dfd0"
138
153
  * @example tag "from-rest-api"
139
154
  */
@@ -158,7 +173,9 @@ __decorate([
158
173
  __decorate([
159
174
  Extension('x-mcp-exposure', 'deny'),
160
175
  Get('{id}.{format}'),
176
+ Middlewares(acl({ resource: 'vdi-snapshot', action: 'export', objectId: 'params.id' })),
161
177
  SuccessResponse(200, 'Download started', 'application/octet-stream'),
178
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
162
179
  Response(notFoundResp.status, notFoundResp.description),
163
180
  Response(422, 'Invalid format'),
164
181
  __param(0, Request()),
@@ -169,6 +186,8 @@ __decorate([
169
186
  Example(vdiSnapshot),
170
187
  Extension('x-mcp-exposure', 'allow'),
171
188
  Get('{id}'),
189
+ Middlewares(acl({ resource: 'vdi-snapshot', action: 'read', objectId: 'params.id' })),
190
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
172
191
  Response(notFoundResp.status, notFoundResp.description),
173
192
  __param(0, Path())
174
193
  ], VdiSnapshotController.prototype, "getVdiSnapshot", null);
@@ -190,7 +209,9 @@ __decorate([
190
209
  __decorate([
191
210
  Extension('x-mcp-exposure', 'confirm'),
192
211
  Delete('{id}'),
212
+ Middlewares(acl({ resource: 'vdi-snapshot', action: 'delete', objectId: 'params.id' })),
193
213
  SuccessResponse(noContentResp.status, noContentResp.description),
214
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
194
215
  Response(notFoundResp.status, notFoundResp.description),
195
216
  __param(0, Path())
196
217
  ], VdiSnapshotController.prototype, "deleteVdiSnapshot", null);
@@ -229,7 +250,9 @@ __decorate([
229
250
  __decorate([
230
251
  Extension('x-mcp-exposure', 'confirm'),
231
252
  Put('{id}/tags/{tag}'),
253
+ Middlewares(acl({ resource: 'vdi-snapshot', action: 'update:tags', objectId: 'params.id' })),
232
254
  SuccessResponse(noContentResp.status, noContentResp.description),
255
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
233
256
  Response(notFoundResp.status, notFoundResp.description),
234
257
  __param(0, Path()),
235
258
  __param(1, Path())
@@ -237,7 +260,9 @@ __decorate([
237
260
  __decorate([
238
261
  Extension('x-mcp-exposure', 'confirm'),
239
262
  Delete('{id}/tags/{tag}'),
263
+ Middlewares(acl({ resource: 'vdi-snapshot', action: 'update:tags', objectId: 'params.id' })),
240
264
  SuccessResponse(noContentResp.status, noContentResp.description),
265
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
241
266
  Response(notFoundResp.status, notFoundResp.description),
242
267
  __param(0, Path()),
243
268
  __param(1, Path())
@@ -110,6 +110,10 @@ let VdiController = class VdiController extends XapiXoController {
110
110
  /**
111
111
  * Create an empty VDI.
112
112
  *
113
+ * Required privileges:
114
+ * - resource: sr, action: import:vdi (on the target SR)
115
+ * - resource: vdi, action: create
116
+ *
113
117
  * @example body { "srId": "c4284e12-37c9-7967-b9e8-83ef229c3e03", "virtual_size": 10737418240, "name_label": "test VDI" }
114
118
  */
115
119
  async createVdi(body) {
@@ -173,6 +177,10 @@ let VdiController = class VdiController extends XapiXoController {
173
177
  /**
174
178
  * Migrate a VDI to another SR.
175
179
  *
180
+ * Required privileges:
181
+ * - resource: vdi, action: migrate-send
182
+ * - resource: sr, action: migrate-receive (on the target SR)
183
+ *
176
184
  * Note: After migration, the VDI will have a new ID. The new ID is returned in the response.
177
185
  *
178
186
  * @example id "c77f9955-c1d2-4b39-aa1c-73cdb2dacb7e"
@@ -280,8 +288,15 @@ __decorate([
280
288
  Example(vdiId),
281
289
  Extension('x-mcp-exposure', 'confirm'),
282
290
  Post(''),
283
- Middlewares(json()),
291
+ Middlewares([
292
+ json(),
293
+ acl([
294
+ { resource: 'sr', action: 'import:vdi', objectId: 'body.srId' },
295
+ { resource: 'vdi', action: 'create', object: ({ req }) => req.body },
296
+ ]),
297
+ ]),
284
298
  SuccessResponse(createdResp.status, createdResp.description),
299
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
285
300
  Response(notFoundResp.status, notFoundResp.description),
286
301
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
287
302
  __param(0, Body())
@@ -332,8 +347,17 @@ __decorate([
332
347
  Example(vdiId),
333
348
  Extension('x-mcp-exposure', 'confirm'),
334
349
  Post('{id}/actions/migrate'),
335
- Middlewares(json()),
350
+ Middlewares([
351
+ json(),
352
+ // Two separate checks allow independent control: a user can be allowed to migrate a VDI away
353
+ // without being allowed to place VDIs on any specific SR, and vice versa.
354
+ acl([
355
+ { resource: 'vdi', action: 'migrate-send', objectId: 'params.id' },
356
+ { resource: 'sr', action: 'migrate-receive', objectId: 'body.srId' },
357
+ ]),
358
+ ]),
336
359
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
360
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
337
361
  Response(200, 'Ok'),
338
362
  Response(notFoundResp.status, notFoundResp.description),
339
363
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
@@ -7,10 +7,11 @@ 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, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
10
+ import { Body, Delete, Example, Extension, Get, Middlewares, Patch, Path, Post, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
12
  import { json } from 'express';
13
- import { acl } from '../middlewares/acl.middleware.mjs';
13
+ import { SUPPORTED_ACTIONS_BY_RESOURCE } from '@xen-orchestra/acl';
14
+ import { acl, actionsFromBody } from '../middlewares/acl.middleware.mjs';
14
15
  import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
15
16
  import { provide } from 'inversify-binding-decorators';
16
17
  import { RestApi } from '../rest-api/rest-api.mjs';
@@ -22,6 +23,7 @@ import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.m
22
23
  import { AlarmService } from '../alarms/alarm.service.mjs';
23
24
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
24
25
  import { taskIds, partialTasks, taskLocation } from '../open-api/oa-examples/task.oa-example.mjs';
26
+ const UPDATE_VIF_ACTIONS = Object.keys(SUPPORTED_ACTIONS_BY_RESOURCE.vif.update).map(k => `update:${k}`);
25
27
  let VifController = class VifController extends XapiXoController {
26
28
  #alarmService;
27
29
  constructor(restApi, alarmService) {
@@ -51,6 +53,39 @@ let VifController = class VifController extends XapiXoController {
51
53
  getVif(id) {
52
54
  return this.getObject(id);
53
55
  }
56
+ /**
57
+ * Partial update of a VIF: only the fields present in the body are modified.
58
+ * Setting the allowed IPs to a non-empty list switches the locking mode to `locked`.
59
+ * `rateLimit` is expressed in kB/s (kilobytes per second).
60
+ *
61
+ * Required privilege per field provided in the body:
62
+ * - resource: vif, action: update:<field>
63
+ *
64
+ * @example id "f028c5d4-578a-332c-394e-087aaca32dd3"
65
+ * @example body { "lockingMode": "locked", "allowedIpv4Addresses": ["192.168.0.42"], "rateLimit": 1000 }
66
+ */
67
+ async updateVif(id, body) {
68
+ const vifId = id;
69
+ const { allowedIpv4Addresses, allowedIpv6Addresses, lockingMode, rateLimit, txChecksumming } = body;
70
+ // resolve first so a missing VIF 404s before any side effect
71
+ const vif = this.getObject(vifId);
72
+ if (Object.keys(body).length === 0) {
73
+ return;
74
+ }
75
+ // keep XO's IP-pool accounting in sync when the allowed IPs change
76
+ if (allowedIpv4Addresses !== undefined || allowedIpv6Addresses !== undefined) {
77
+ const oldIpAddresses = vif.allowedIpv4Addresses.concat(vif.allowedIpv6Addresses);
78
+ const newIpAddresses = (allowedIpv4Addresses ?? vif.allowedIpv4Addresses).concat(allowedIpv6Addresses ?? vif.allowedIpv6Addresses);
79
+ await this.restApi.xoApp.allocIpAddresses(vifId, newIpAddresses.filter(address => !oldIpAddresses.includes(address)), oldIpAddresses.filter(address => !newIpAddresses.includes(address)));
80
+ }
81
+ await this.getXapi(vifId).editVif(vifId, {
82
+ ipv4Allowed: allowedIpv4Addresses,
83
+ ipv6Allowed: allowedIpv6Addresses,
84
+ lockingMode,
85
+ rateLimit,
86
+ txChecksumming,
87
+ });
88
+ }
54
89
  /**
55
90
  * Returns all alarms that match the following privilege:
56
91
  * - resource: alarm, action: read
@@ -106,12 +141,15 @@ let VifController = class VifController extends XapiXoController {
106
141
  });
107
142
  }
108
143
  /**
144
+ * Required privilege:
145
+ * - resource: vif, action: create
146
+ *
109
147
  * @example body {
110
148
  * "networkId": "6b6ca0f5-6611-0636-4b0a-1fb1c1e96414",
111
149
  * "vmId": "613f541c-4bed-fc77-7ca8-2db6b68f079c",
112
- * "other_config": {
113
- *"ethtool-tx": "false"
114
- * },
150
+ * "other_config": {
151
+ * "ethtool-tx": "false"
152
+ * },
115
153
  * "qos_algorithm_params": {
116
154
  * "kbps": "42"
117
155
  * },
@@ -137,6 +175,9 @@ let VifController = class VifController extends XapiXoController {
137
175
  return { id: xapiVif.uuid };
138
176
  }
139
177
  /**
178
+ * Required privilege:
179
+ * - resource: vif, action: delete
180
+ *
140
181
  * @example id "6b6ca0f5-6611-0636-4b0a-1fb1c1e96414"
141
182
  */
142
183
  async destroyVif(id) {
@@ -147,6 +188,9 @@ let VifController = class VifController extends XapiXoController {
147
188
  * Hotplug the VIF, dynamically attaching it to the running VM
148
189
  * Requires PV drivers to be installed on the VM
149
190
  *
191
+ * Required privilege:
192
+ * - resource: vif, action: connect
193
+ *
150
194
  * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
151
195
  */
152
196
  async connectVif(id, sync) {
@@ -168,6 +212,9 @@ let VifController = class VifController extends XapiXoController {
168
212
  * Hot-unplug the VIF, dynamically detaching it from the running VM
169
213
  * Requires PV drivers to be installed on the VM
170
214
  *
215
+ * Required privilege:
216
+ * - resource: vif, action: disconnect
217
+ *
171
218
  * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
172
219
  */
173
220
  async disconnectVif(id, sync) {
@@ -208,6 +255,25 @@ __decorate([
208
255
  Response(notFoundResp.status, notFoundResp.description),
209
256
  __param(0, Path())
210
257
  ], VifController.prototype, "getVif", null);
258
+ __decorate([
259
+ Extension('x-mcp-exposure', 'confirm'),
260
+ Patch('{id}'),
261
+ Middlewares([
262
+ json(),
263
+ acl({
264
+ resource: 'vif',
265
+ actions: actionsFromBody(UPDATE_VIF_ACTIONS),
266
+ objectId: 'params.id',
267
+ }),
268
+ ]),
269
+ SuccessResponse(noContentResp.status, noContentResp.description),
270
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
271
+ Response(notFoundResp.status, notFoundResp.description),
272
+ Response(invalidParametersResp.status, invalidParametersResp.description),
273
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
274
+ __param(0, Path()),
275
+ __param(1, Body())
276
+ ], VifController.prototype, "updateVif", null);
211
277
  __decorate([
212
278
  Example(genericAlarmsExample),
213
279
  Extension('x-mcp-exposure', 'allow'),
@@ -259,9 +325,10 @@ __decorate([
259
325
  Example(vifId),
260
326
  Extension('x-mcp-exposure', 'confirm'),
261
327
  Post(''),
262
- Middlewares(json()),
328
+ Middlewares([json(), acl({ resource: 'vif', action: 'create', object: ({ req }) => req.body })]),
263
329
  SuccessResponse(createdResp.status, createdResp.description),
264
330
  Response(notFoundResp.status, notFoundResp.description),
331
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
265
332
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
266
333
  Response(invalidParametersResp.status, invalidParametersResp.description),
267
334
  __param(0, Body())
@@ -270,6 +337,8 @@ __decorate([
270
337
  Extension('x-mcp-exposure', 'confirm'),
271
338
  Delete('{id}'),
272
339
  SuccessResponse(noContentResp.status, noContentResp.description),
340
+ Middlewares(acl({ resource: 'vif', action: 'delete', objectId: 'params.id' })),
341
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
273
342
  Response(notFoundResp.status, notFoundResp.description),
274
343
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
275
344
  __param(0, Path())
@@ -277,9 +346,11 @@ __decorate([
277
346
  __decorate([
278
347
  Example(taskLocation),
279
348
  Extension('x-mcp-exposure', 'confirm'),
349
+ Middlewares(acl({ resource: 'vif', action: 'connect', objectId: 'params.id' })),
280
350
  Post('{id}/actions/connect'),
281
351
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
282
352
  Response(noContentResp.status, noContentResp.description),
353
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
283
354
  Response(notFoundResp.status, notFoundResp.description),
284
355
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
285
356
  __param(0, Path()),
@@ -289,8 +360,10 @@ __decorate([
289
360
  Example(taskLocation),
290
361
  Extension('x-mcp-exposure', 'confirm'),
291
362
  Post('{id}/actions/disconnect'),
363
+ Middlewares(acl({ resource: 'vif', action: 'disconnect', objectId: 'params.id' })),
292
364
  SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description),
293
365
  Response(noContentResp.status, noContentResp.description),
366
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
294
367
  Response(notFoundResp.status, notFoundResp.description),
295
368
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
296
369
  __param(0, Path()),
@@ -21,6 +21,7 @@ import { provide } from 'inversify-binding-decorators';
21
21
  import { partialVmSnapshots, vmSnapshot, vmSnapshotIds, vmSnapshotVdis, } from '../open-api/oa-examples/vm-snapshot.oa-example.mjs';
22
22
  import { VmService } from '../vms/vm.service.mjs';
23
23
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
24
+ import { vmExportCompressDeprecated } from '../middlewares/deprecated.middleware.mjs';
24
25
  let VmSnapshotController = class VmSnapshotController extends XapiXoController {
25
26
  #alarmService;
26
27
  #vmService;
@@ -191,7 +192,7 @@ __decorate([
191
192
  __decorate([
192
193
  Extension('x-mcp-exposure', 'deny'),
193
194
  Get('{id}.{format}'),
194
- Middlewares(acl({ resource: 'vm-snapshot', action: 'export', objectId: 'params.id' })),
195
+ Middlewares([acl({ resource: 'vm-snapshot', action: 'export', objectId: 'params.id' }), vmExportCompressDeprecated]),
195
196
  SuccessResponse(200, 'Download started', 'application/octet-stream'),
196
197
  Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
197
198
  Response(notFoundResp.status, notFoundResp.description),
@@ -21,6 +21,7 @@ import { partialVmTemplates, vmTemplate, vmTemplateIds, vmTemplateVdis, } from '
21
21
  import { VmService } from '../vms/vm.service.mjs';
22
22
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
23
23
  import { partialTasks, taskIds } from '../open-api/oa-examples/task.oa-example.mjs';
24
+ import { vmExportCompressDeprecated } from '../middlewares/deprecated.middleware.mjs';
24
25
  let VmTemplateController = class VmTemplateController extends XapiXoController {
25
26
  #alarmService;
26
27
  #vmService;
@@ -191,7 +192,7 @@ __decorate([
191
192
  __decorate([
192
193
  Extension('x-mcp-exposure', 'deny'),
193
194
  Get('{id}.{format}'),
194
- Middlewares(acl({ resource: 'vm-template', action: 'export', objectId: 'params.id' })),
195
+ Middlewares([acl({ resource: 'vm-template', action: 'export', objectId: 'params.id' }), vmExportCompressDeprecated]),
195
196
  SuccessResponse(200, 'Download started', 'application/octet-stream'),
196
197
  Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
197
198
  Response(notFoundResp.status, notFoundResp.description),