@backstage/plugin-scaffolder-backend 1.26.0-next.0 → 1.26.0-next.2

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 (105) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/alpha/package.json +1 -1
  3. package/dist/ScaffolderPlugin.cjs.js +168 -0
  4. package/dist/ScaffolderPlugin.cjs.js.map +1 -0
  5. package/dist/alpha.cjs.js +7 -196
  6. package/dist/alpha.cjs.js.map +1 -1
  7. package/dist/deprecated.cjs.js +15 -0
  8. package/dist/deprecated.cjs.js.map +1 -0
  9. package/dist/index.cjs.js +57 -134
  10. package/dist/index.cjs.js.map +1 -1
  11. package/dist/lib/templating/SecureTemplater.cjs.js +169 -0
  12. package/dist/lib/templating/SecureTemplater.cjs.js.map +1 -0
  13. package/dist/lib/templating/filters.cjs.js +26 -0
  14. package/dist/lib/templating/filters.cjs.js.map +1 -0
  15. package/dist/lib/templating/helpers.cjs.js +13 -0
  16. package/dist/lib/templating/helpers.cjs.js.map +1 -0
  17. package/dist/scaffolder/actions/TemplateActionRegistry.cjs.js +30 -0
  18. package/dist/scaffolder/actions/TemplateActionRegistry.cjs.js.map +1 -0
  19. package/dist/scaffolder/actions/builtin/catalog/fetch.cjs.js +93 -0
  20. package/dist/scaffolder/actions/builtin/catalog/fetch.cjs.js.map +1 -0
  21. package/dist/scaffolder/actions/builtin/catalog/fetch.examples.cjs.js +43 -0
  22. package/dist/scaffolder/actions/builtin/catalog/fetch.examples.cjs.js.map +1 -0
  23. package/dist/scaffolder/actions/builtin/catalog/register.cjs.js +142 -0
  24. package/dist/scaffolder/actions/builtin/catalog/register.cjs.js.map +1 -0
  25. package/dist/scaffolder/actions/builtin/catalog/register.examples.cjs.js +28 -0
  26. package/dist/scaffolder/actions/builtin/catalog/register.examples.cjs.js.map +1 -0
  27. package/dist/scaffolder/actions/builtin/catalog/write.cjs.js +74 -0
  28. package/dist/scaffolder/actions/builtin/catalog/write.cjs.js.map +1 -0
  29. package/dist/scaffolder/actions/builtin/catalog/write.examples.cjs.js +56 -0
  30. package/dist/scaffolder/actions/builtin/catalog/write.examples.cjs.js.map +1 -0
  31. package/dist/scaffolder/actions/builtin/createBuiltinActions.cjs.js +156 -0
  32. package/dist/scaffolder/actions/builtin/createBuiltinActions.cjs.js.map +1 -0
  33. package/dist/scaffolder/actions/builtin/debug/log.cjs.js +66 -0
  34. package/dist/scaffolder/actions/builtin/debug/log.cjs.js.map +1 -0
  35. package/dist/scaffolder/actions/builtin/debug/log.examples.cjs.js +58 -0
  36. package/dist/scaffolder/actions/builtin/debug/log.examples.cjs.js.map +1 -0
  37. package/dist/scaffolder/actions/builtin/debug/wait.cjs.js +66 -0
  38. package/dist/scaffolder/actions/builtin/debug/wait.cjs.js.map +1 -0
  39. package/dist/scaffolder/actions/builtin/debug/wait.examples.cjs.js +58 -0
  40. package/dist/scaffolder/actions/builtin/debug/wait.examples.cjs.js.map +1 -0
  41. package/dist/scaffolder/actions/builtin/fetch/plain.cjs.js +56 -0
  42. package/dist/scaffolder/actions/builtin/fetch/plain.cjs.js.map +1 -0
  43. package/dist/scaffolder/actions/builtin/fetch/plain.examples.cjs.js +44 -0
  44. package/dist/scaffolder/actions/builtin/fetch/plain.examples.cjs.js.map +1 -0
  45. package/dist/scaffolder/actions/builtin/fetch/plainFile.cjs.js +56 -0
  46. package/dist/scaffolder/actions/builtin/fetch/plainFile.cjs.js.map +1 -0
  47. package/dist/scaffolder/actions/builtin/fetch/plainFile.examples.cjs.js +29 -0
  48. package/dist/scaffolder/actions/builtin/fetch/plainFile.examples.cjs.js.map +1 -0
  49. package/dist/scaffolder/actions/builtin/fetch/template.cjs.js +241 -0
  50. package/dist/scaffolder/actions/builtin/fetch/template.cjs.js.map +1 -0
  51. package/dist/scaffolder/actions/builtin/fetch/template.examples.cjs.js +35 -0
  52. package/dist/scaffolder/actions/builtin/fetch/template.examples.cjs.js.map +1 -0
  53. package/dist/scaffolder/actions/builtin/fetch/templateFile.cjs.js +119 -0
  54. package/dist/scaffolder/actions/builtin/fetch/templateFile.cjs.js.map +1 -0
  55. package/dist/scaffolder/actions/builtin/fetch/templateFile.examples.cjs.js +34 -0
  56. package/dist/scaffolder/actions/builtin/fetch/templateFile.examples.cjs.js.map +1 -0
  57. package/dist/scaffolder/actions/builtin/filesystem/delete.cjs.js +54 -0
  58. package/dist/scaffolder/actions/builtin/filesystem/delete.cjs.js.map +1 -0
  59. package/dist/scaffolder/actions/builtin/filesystem/delete.examples.cjs.js +44 -0
  60. package/dist/scaffolder/actions/builtin/filesystem/delete.examples.cjs.js.map +1 -0
  61. package/dist/scaffolder/actions/builtin/filesystem/rename.cjs.js +83 -0
  62. package/dist/scaffolder/actions/builtin/filesystem/rename.cjs.js.map +1 -0
  63. package/dist/scaffolder/actions/builtin/filesystem/rename.examples.cjs.js +48 -0
  64. package/dist/scaffolder/actions/builtin/filesystem/rename.examples.cjs.js.map +1 -0
  65. package/dist/scaffolder/actions/deprecated.cjs.js +74 -0
  66. package/dist/scaffolder/actions/deprecated.cjs.js.map +1 -0
  67. package/dist/scaffolder/dryrun/DecoratedActionsRegistry.cjs.js +57 -0
  68. package/dist/scaffolder/dryrun/DecoratedActionsRegistry.cjs.js.map +1 -0
  69. package/dist/scaffolder/dryrun/createDryRunner.cjs.js +97 -0
  70. package/dist/scaffolder/dryrun/createDryRunner.cjs.js.map +1 -0
  71. package/dist/scaffolder/tasks/DatabaseTaskStore.cjs.js +430 -0
  72. package/dist/scaffolder/tasks/DatabaseTaskStore.cjs.js.map +1 -0
  73. package/dist/scaffolder/tasks/DatabaseWorkspaceProvider.cjs.js +22 -0
  74. package/dist/scaffolder/tasks/DatabaseWorkspaceProvider.cjs.js.map +1 -0
  75. package/dist/scaffolder/tasks/NunjucksWorkflowRunner.cjs.js +545 -0
  76. package/dist/scaffolder/tasks/NunjucksWorkflowRunner.cjs.js.map +1 -0
  77. package/dist/scaffolder/tasks/StorageTaskBroker.cjs.js +318 -0
  78. package/dist/scaffolder/tasks/StorageTaskBroker.cjs.js.map +1 -0
  79. package/dist/scaffolder/tasks/TaskWorker.cjs.js +110 -0
  80. package/dist/scaffolder/tasks/TaskWorker.cjs.js.map +1 -0
  81. package/dist/scaffolder/tasks/WorkspaceService.cjs.js +50 -0
  82. package/dist/scaffolder/tasks/WorkspaceService.cjs.js.map +1 -0
  83. package/dist/scaffolder/tasks/dbUtil.cjs.js +20 -0
  84. package/dist/scaffolder/tasks/dbUtil.cjs.js.map +1 -0
  85. package/dist/scaffolder/tasks/helper.cjs.js +46 -0
  86. package/dist/scaffolder/tasks/helper.cjs.js.map +1 -0
  87. package/dist/scaffolder/tasks/logger.cjs.js +156 -0
  88. package/dist/scaffolder/tasks/logger.cjs.js.map +1 -0
  89. package/dist/scaffolder/tasks/taskRecoveryHelper.cjs.js +18 -0
  90. package/dist/scaffolder/tasks/taskRecoveryHelper.cjs.js.map +1 -0
  91. package/dist/service/conditionExports.cjs.js +26 -0
  92. package/dist/service/conditionExports.cjs.js.map +1 -0
  93. package/dist/service/helpers.cjs.js +92 -0
  94. package/dist/service/helpers.cjs.js.map +1 -0
  95. package/dist/service/router.cjs.js +640 -0
  96. package/dist/service/router.cjs.js.map +1 -0
  97. package/dist/service/rules.cjs.js +97 -0
  98. package/dist/service/rules.cjs.js.map +1 -0
  99. package/dist/util/checkPermissions.cjs.js +25 -0
  100. package/dist/util/checkPermissions.cjs.js.map +1 -0
  101. package/dist/util/metrics.cjs.js +24 -0
  102. package/dist/util/metrics.cjs.js.map +1 -0
  103. package/package.json +30 -30
  104. package/dist/cjs/router-CC-UhVkG.cjs.js +0 -4101
  105. package/dist/cjs/router-CC-UhVkG.cjs.js.map +0 -1
@@ -0,0 +1,640 @@
1
+ 'use strict';
2
+
3
+ var backendCommon = require('@backstage/backend-common');
4
+ var catalogModel = require('@backstage/catalog-model');
5
+ var config = require('@backstage/config');
6
+ var errors = require('@backstage/errors');
7
+ var integration = require('@backstage/integration');
8
+ var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
9
+ var alpha = require('@backstage/plugin-scaffolder-common/alpha');
10
+ var express = require('express');
11
+ var Router = require('express-promise-router');
12
+ var jsonschema = require('jsonschema');
13
+ var zod = require('zod');
14
+ require('@backstage/plugin-scaffolder-node');
15
+ require('../scaffolder/actions/builtin/catalog/register.examples.cjs.js');
16
+ require('fs-extra');
17
+ require('yaml');
18
+ require('@backstage/backend-plugin-api');
19
+ require('../scaffolder/actions/builtin/catalog/write.examples.cjs.js');
20
+ require('../scaffolder/actions/builtin/catalog/fetch.examples.cjs.js');
21
+ var createBuiltinActions = require('../scaffolder/actions/builtin/createBuiltinActions.cjs.js');
22
+ require('path');
23
+ require('../scaffolder/actions/builtin/debug/log.examples.cjs.js');
24
+ require('fs');
25
+ var luxon = require('luxon');
26
+ require('../scaffolder/actions/builtin/debug/wait.examples.cjs.js');
27
+ require('../scaffolder/actions/builtin/fetch/plain.examples.cjs.js');
28
+ require('../scaffolder/actions/builtin/fetch/plainFile.examples.cjs.js');
29
+ require('globby');
30
+ require('isbinaryfile');
31
+ require('isolated-vm');
32
+ require('lodash/get');
33
+ require('../scaffolder/actions/builtin/fetch/template.examples.cjs.js');
34
+ require('../scaffolder/actions/builtin/fetch/templateFile.examples.cjs.js');
35
+ require('../scaffolder/actions/builtin/filesystem/delete.examples.cjs.js');
36
+ require('../scaffolder/actions/builtin/filesystem/rename.examples.cjs.js');
37
+ require('@backstage/plugin-scaffolder-backend-module-github');
38
+ require('@backstage/plugin-scaffolder-backend-module-gitlab');
39
+ require('@backstage/plugin-scaffolder-backend-module-azure');
40
+ require('@backstage/plugin-scaffolder-backend-module-bitbucket');
41
+ require('@backstage/plugin-scaffolder-backend-module-bitbucket-cloud');
42
+ require('@backstage/plugin-scaffolder-backend-module-bitbucket-server');
43
+ require('@backstage/plugin-scaffolder-backend-module-gerrit');
44
+ var TemplateActionRegistry = require('../scaffolder/actions/TemplateActionRegistry.cjs.js');
45
+ var DatabaseTaskStore = require('../scaffolder/tasks/DatabaseTaskStore.cjs.js');
46
+ var StorageTaskBroker = require('../scaffolder/tasks/StorageTaskBroker.cjs.js');
47
+ var TaskWorker = require('../scaffolder/tasks/TaskWorker.cjs.js');
48
+ var createDryRunner = require('../scaffolder/dryrun/createDryRunner.cjs.js');
49
+ var helpers = require('./helpers.cjs.js');
50
+ var pluginPermissionNode = require('@backstage/plugin-permission-node');
51
+ var rules = require('./rules.cjs.js');
52
+ var checkPermissions = require('../util/checkPermissions.cjs.js');
53
+
54
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
55
+
56
+ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
57
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
58
+
59
+ function isTemplatePermissionRuleInput(permissionRule) {
60
+ return permissionRule.resourceType === alpha.RESOURCE_TYPE_SCAFFOLDER_TEMPLATE;
61
+ }
62
+ function isActionPermissionRuleInput(permissionRule) {
63
+ return permissionRule.resourceType === alpha.RESOURCE_TYPE_SCAFFOLDER_ACTION;
64
+ }
65
+ function isSupportedTemplate(entity) {
66
+ return entity.apiVersion === "scaffolder.backstage.io/v1beta3";
67
+ }
68
+ function buildDefaultIdentityClient(options) {
69
+ return {
70
+ getIdentity: async ({ request }) => {
71
+ const header = request.headers.authorization;
72
+ const { logger } = options;
73
+ if (!header) {
74
+ return void 0;
75
+ }
76
+ try {
77
+ const token = header.match(/^Bearer\s(\S+\.\S+\.\S+)$/i)?.[1];
78
+ if (!token) {
79
+ throw new TypeError("Expected Bearer with JWT");
80
+ }
81
+ const [_header, rawPayload, _signature] = token.split(".");
82
+ const payload = JSON.parse(
83
+ Buffer.from(rawPayload, "base64").toString()
84
+ );
85
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
86
+ throw new TypeError("Malformed JWT payload");
87
+ }
88
+ const sub = payload.sub;
89
+ if (typeof sub !== "string") {
90
+ throw new TypeError("Expected string sub claim");
91
+ }
92
+ if (sub === "backstage-server") {
93
+ return void 0;
94
+ }
95
+ catalogModel.parseEntityRef(sub);
96
+ return {
97
+ identity: {
98
+ userEntityRef: sub,
99
+ ownershipEntityRefs: [],
100
+ type: "user"
101
+ },
102
+ token
103
+ };
104
+ } catch (e) {
105
+ logger.error(`Invalid authorization header: ${errors.stringifyError(e)}`);
106
+ return void 0;
107
+ }
108
+ }
109
+ };
110
+ }
111
+ const readDuration = (config$1, key, defaultValue) => {
112
+ if (config$1.has(key)) {
113
+ return config.readDurationFromConfig(config$1, { key });
114
+ }
115
+ return defaultValue;
116
+ };
117
+ async function createRouter(options) {
118
+ const router = Router__default.default();
119
+ router.use(express__default.default.json({ limit: "10MB" }));
120
+ const {
121
+ logger: parentLogger,
122
+ config,
123
+ reader,
124
+ database,
125
+ catalogClient,
126
+ actions,
127
+ taskWorkers,
128
+ scheduler,
129
+ additionalTemplateFilters,
130
+ additionalTemplateGlobals,
131
+ additionalWorkspaceProviders,
132
+ permissions,
133
+ permissionRules,
134
+ discovery = backendCommon.HostDiscovery.fromConfig(config),
135
+ identity = buildDefaultIdentityClient(options),
136
+ autocompleteHandlers = {}
137
+ } = options;
138
+ const { auth, httpAuth } = backendCommon.createLegacyAuthAdapters({
139
+ ...options,
140
+ identity,
141
+ discovery
142
+ });
143
+ const concurrentTasksLimit = options.concurrentTasksLimit ?? options.config.getOptionalNumber("scaffolder.concurrentTasksLimit");
144
+ const logger = parentLogger.child({ plugin: "scaffolder" });
145
+ const workingDirectory = await helpers.getWorkingDirectory(config, logger);
146
+ const integrations = integration.ScmIntegrations.fromConfig(config);
147
+ let taskBroker;
148
+ if (!options.taskBroker) {
149
+ const databaseTaskStore = await DatabaseTaskStore.DatabaseTaskStore.create({ database });
150
+ taskBroker = new StorageTaskBroker.StorageTaskBroker(
151
+ databaseTaskStore,
152
+ logger,
153
+ config,
154
+ auth,
155
+ additionalWorkspaceProviders
156
+ );
157
+ if (scheduler && databaseTaskStore.listStaleTasks) {
158
+ await scheduler.scheduleTask({
159
+ id: "close_stale_tasks",
160
+ frequency: readDuration(
161
+ config,
162
+ "scaffolder.taskTimeoutJanitorFrequency",
163
+ {
164
+ minutes: 5
165
+ }
166
+ ),
167
+ timeout: { minutes: 15 },
168
+ fn: async () => {
169
+ const { tasks } = await databaseTaskStore.listStaleTasks({
170
+ timeoutS: luxon.Duration.fromObject(
171
+ readDuration(config, "scaffolder.taskTimeout", {
172
+ hours: 24
173
+ })
174
+ ).as("seconds")
175
+ });
176
+ for (const task of tasks) {
177
+ await databaseTaskStore.shutdownTask(task);
178
+ logger.info(`Successfully closed stale task ${task.taskId}`);
179
+ }
180
+ }
181
+ });
182
+ }
183
+ } else {
184
+ taskBroker = options.taskBroker;
185
+ }
186
+ const actionRegistry = new TemplateActionRegistry.TemplateActionRegistry();
187
+ const workers = [];
188
+ if (concurrentTasksLimit !== 0) {
189
+ for (let i = 0; i < (taskWorkers || 1); i++) {
190
+ const worker = await TaskWorker.TaskWorker.create({
191
+ taskBroker,
192
+ actionRegistry,
193
+ integrations,
194
+ logger,
195
+ workingDirectory,
196
+ additionalTemplateFilters,
197
+ additionalTemplateGlobals,
198
+ concurrentTasksLimit,
199
+ permissions
200
+ });
201
+ workers.push(worker);
202
+ }
203
+ }
204
+ const actionsToRegister = Array.isArray(actions) ? actions : createBuiltinActions.createBuiltinActions({
205
+ integrations,
206
+ catalogClient,
207
+ reader,
208
+ config,
209
+ additionalTemplateFilters,
210
+ additionalTemplateGlobals,
211
+ auth
212
+ });
213
+ actionsToRegister.forEach((action) => actionRegistry.register(action));
214
+ const launchWorkers = () => workers.forEach((worker) => worker.start());
215
+ const shutdownWorkers = () => {
216
+ workers.forEach((worker) => worker.stop());
217
+ };
218
+ if (options.lifecycle) {
219
+ options.lifecycle.addStartupHook(launchWorkers);
220
+ options.lifecycle.addShutdownHook(shutdownWorkers);
221
+ } else {
222
+ launchWorkers();
223
+ }
224
+ const dryRunner = createDryRunner.createDryRunner({
225
+ actionRegistry,
226
+ integrations,
227
+ logger,
228
+ workingDirectory,
229
+ additionalTemplateFilters,
230
+ additionalTemplateGlobals,
231
+ permissions
232
+ });
233
+ const templateRules = Object.values(
234
+ rules.scaffolderTemplateRules
235
+ );
236
+ const actionRules = Object.values(
237
+ rules.scaffolderActionRules
238
+ );
239
+ if (permissionRules) {
240
+ templateRules.push(
241
+ ...permissionRules.filter(isTemplatePermissionRuleInput)
242
+ );
243
+ actionRules.push(...permissionRules.filter(isActionPermissionRuleInput));
244
+ }
245
+ const isAuthorized = pluginPermissionNode.createConditionAuthorizer(Object.values(templateRules));
246
+ const permissionIntegrationRouter = pluginPermissionNode.createPermissionIntegrationRouter({
247
+ resources: [
248
+ {
249
+ resourceType: alpha.RESOURCE_TYPE_SCAFFOLDER_TEMPLATE,
250
+ permissions: alpha.scaffolderTemplatePermissions,
251
+ rules: templateRules
252
+ },
253
+ {
254
+ resourceType: alpha.RESOURCE_TYPE_SCAFFOLDER_ACTION,
255
+ permissions: alpha.scaffolderActionPermissions,
256
+ rules: actionRules
257
+ }
258
+ ],
259
+ permissions: alpha.scaffolderTaskPermissions
260
+ });
261
+ router.use(permissionIntegrationRouter);
262
+ router.get(
263
+ "/v2/templates/:namespace/:kind/:name/parameter-schema",
264
+ async (req, res) => {
265
+ const credentials = await httpAuth.credentials(req);
266
+ const { token } = await auth.getPluginRequestToken({
267
+ onBehalfOf: credentials,
268
+ targetPluginId: "catalog"
269
+ });
270
+ const template = await authorizeTemplate(
271
+ req.params,
272
+ token,
273
+ credentials
274
+ );
275
+ const parameters = [template.spec.parameters ?? []].flat();
276
+ const presentation = template.spec.presentation;
277
+ res.json({
278
+ title: template.metadata.title ?? template.metadata.name,
279
+ ...presentation ? { presentation } : {},
280
+ description: template.metadata.description,
281
+ "ui:options": template.metadata["ui:options"],
282
+ steps: parameters.map((schema) => ({
283
+ title: schema.title ?? "Please enter the following information",
284
+ description: schema.description,
285
+ schema
286
+ }))
287
+ });
288
+ }
289
+ ).get("/v2/actions", async (_req, res) => {
290
+ const actionsList = actionRegistry.list().map((action) => {
291
+ return {
292
+ id: action.id,
293
+ description: action.description,
294
+ examples: action.examples,
295
+ schema: action.schema
296
+ };
297
+ });
298
+ res.json(actionsList);
299
+ }).post("/v2/tasks", async (req, res) => {
300
+ const templateRef = req.body.templateRef;
301
+ const { kind, namespace, name } = catalogModel.parseEntityRef(templateRef, {
302
+ defaultKind: "template"
303
+ });
304
+ const credentials = await httpAuth.credentials(req);
305
+ await checkPermissions.checkPermission({
306
+ credentials,
307
+ permissions: [alpha.taskCreatePermission],
308
+ permissionService: permissions
309
+ });
310
+ const { token } = await auth.getPluginRequestToken({
311
+ onBehalfOf: credentials,
312
+ targetPluginId: "catalog"
313
+ });
314
+ const userEntityRef = auth.isPrincipal(credentials, "user") ? credentials.principal.userEntityRef : void 0;
315
+ const userEntity = userEntityRef ? await catalogClient.getEntityByRef(userEntityRef, { token }) : void 0;
316
+ let auditLog = `Scaffolding task for ${templateRef}`;
317
+ if (userEntityRef) {
318
+ auditLog += ` created by ${userEntityRef}`;
319
+ }
320
+ logger.info(auditLog);
321
+ const values = req.body.values;
322
+ const template = await authorizeTemplate(
323
+ { kind, namespace, name },
324
+ token,
325
+ credentials
326
+ );
327
+ for (const parameters of [template.spec.parameters ?? []].flat()) {
328
+ const result2 = jsonschema.validate(values, parameters);
329
+ if (!result2.valid) {
330
+ res.status(400).json({ errors: result2.errors });
331
+ return;
332
+ }
333
+ }
334
+ const baseUrl = helpers.getEntityBaseUrl(template);
335
+ const taskSpec = {
336
+ apiVersion: template.apiVersion,
337
+ steps: template.spec.steps.map((step, index) => ({
338
+ ...step,
339
+ id: step.id ?? `step-${index + 1}`,
340
+ name: step.name ?? step.action
341
+ })),
342
+ EXPERIMENTAL_recovery: template.spec.EXPERIMENTAL_recovery,
343
+ output: template.spec.output ?? {},
344
+ parameters: values,
345
+ user: {
346
+ entity: userEntity,
347
+ ref: userEntityRef
348
+ },
349
+ templateInfo: {
350
+ entityRef: catalogModel.stringifyEntityRef({ kind, name, namespace }),
351
+ baseUrl,
352
+ entity: {
353
+ metadata: template.metadata
354
+ }
355
+ }
356
+ };
357
+ const secrets = {
358
+ ...req.body.secrets,
359
+ backstageToken: token,
360
+ __initiatorCredentials: JSON.stringify(credentials)
361
+ };
362
+ const result = await taskBroker.dispatch({
363
+ spec: taskSpec,
364
+ createdBy: userEntityRef,
365
+ secrets
366
+ });
367
+ res.status(201).json({ id: result.taskId });
368
+ }).get("/v2/tasks", async (req, res) => {
369
+ const credentials = await httpAuth.credentials(req);
370
+ await checkPermissions.checkPermission({
371
+ credentials,
372
+ permissions: [alpha.taskReadPermission],
373
+ permissionService: permissions
374
+ });
375
+ if (!taskBroker.list) {
376
+ throw new Error(
377
+ "TaskBroker does not support listing tasks, please implement the list method on the TaskBroker."
378
+ );
379
+ }
380
+ const createdBy = helpers.parseStringsParam(req.query.createdBy, "createdBy");
381
+ const status = helpers.parseStringsParam(req.query.status, "status");
382
+ const order = helpers.parseStringsParam(req.query.order, "order")?.map((item) => {
383
+ const match = item.match(/^(asc|desc):(.+)$/);
384
+ if (!match) {
385
+ throw new errors.InputError(
386
+ `Invalid order parameter "${item}", expected "<asc or desc>:<field name>"`
387
+ );
388
+ }
389
+ return {
390
+ order: match[1],
391
+ field: match[2]
392
+ };
393
+ });
394
+ const limit = helpers.parseNumberParam(req.query.limit, "limit");
395
+ const offset = helpers.parseNumberParam(req.query.offset, "offset");
396
+ const tasks = await taskBroker.list({
397
+ filters: {
398
+ createdBy,
399
+ status: status ? status : void 0
400
+ },
401
+ order,
402
+ pagination: {
403
+ limit: limit ? limit[0] : void 0,
404
+ offset: offset ? offset[0] : void 0
405
+ }
406
+ });
407
+ res.status(200).json(tasks);
408
+ }).get("/v2/tasks/:taskId", async (req, res) => {
409
+ const credentials = await httpAuth.credentials(req);
410
+ await checkPermissions.checkPermission({
411
+ credentials,
412
+ permissions: [alpha.taskReadPermission],
413
+ permissionService: permissions
414
+ });
415
+ const { taskId } = req.params;
416
+ const task = await taskBroker.get(taskId);
417
+ if (!task) {
418
+ throw new errors.NotFoundError(`Task with id ${taskId} does not exist`);
419
+ }
420
+ delete task.secrets;
421
+ res.status(200).json(task);
422
+ }).post("/v2/tasks/:taskId/cancel", async (req, res) => {
423
+ const credentials = await httpAuth.credentials(req);
424
+ await checkPermissions.checkPermission({
425
+ credentials,
426
+ permissions: [alpha.taskCancelPermission, alpha.taskReadPermission],
427
+ permissionService: permissions
428
+ });
429
+ const { taskId } = req.params;
430
+ await taskBroker.cancel?.(taskId);
431
+ res.status(200).json({ status: "cancelled" });
432
+ }).post("/v2/tasks/:taskId/retry", async (req, res) => {
433
+ const credentials = await httpAuth.credentials(req);
434
+ await checkPermissions.checkPermission({
435
+ credentials,
436
+ permissions: [alpha.taskCreatePermission, alpha.taskReadPermission],
437
+ permissionService: permissions
438
+ });
439
+ const { taskId } = req.params;
440
+ await taskBroker.retry?.(taskId);
441
+ res.status(201).json({ id: taskId });
442
+ }).get("/v2/tasks/:taskId/eventstream", async (req, res) => {
443
+ const credentials = await httpAuth.credentials(req);
444
+ await checkPermissions.checkPermission({
445
+ credentials,
446
+ permissions: [alpha.taskReadPermission],
447
+ permissionService: permissions
448
+ });
449
+ const { taskId } = req.params;
450
+ const after = req.query.after !== void 0 ? Number(req.query.after) : void 0;
451
+ logger.debug(`Event stream observing taskId '${taskId}' opened`);
452
+ res.writeHead(200, {
453
+ Connection: "keep-alive",
454
+ "Cache-Control": "no-cache",
455
+ "Content-Type": "text/event-stream"
456
+ });
457
+ const subscription = taskBroker.event$({ taskId, after }).subscribe({
458
+ error: (error) => {
459
+ logger.error(
460
+ `Received error from event stream when observing taskId '${taskId}', ${error}`
461
+ );
462
+ res.end();
463
+ },
464
+ next: ({ events }) => {
465
+ let shouldUnsubscribe = false;
466
+ for (const event of events) {
467
+ res.write(
468
+ `event: ${event.type}
469
+ data: ${JSON.stringify(event)}
470
+
471
+ `
472
+ );
473
+ if (event.type === "completion" && !event.isTaskRecoverable) {
474
+ shouldUnsubscribe = true;
475
+ }
476
+ }
477
+ res.flush?.();
478
+ if (shouldUnsubscribe) {
479
+ subscription.unsubscribe();
480
+ res.end();
481
+ }
482
+ }
483
+ });
484
+ req.on("close", () => {
485
+ subscription.unsubscribe();
486
+ logger.debug(`Event stream observing taskId '${taskId}' closed`);
487
+ });
488
+ }).get("/v2/tasks/:taskId/events", async (req, res) => {
489
+ const credentials = await httpAuth.credentials(req);
490
+ await checkPermissions.checkPermission({
491
+ credentials,
492
+ permissions: [alpha.taskReadPermission],
493
+ permissionService: permissions
494
+ });
495
+ const { taskId } = req.params;
496
+ const after = Number(req.query.after) || void 0;
497
+ const timeout = setTimeout(() => {
498
+ res.json([]);
499
+ }, 3e4);
500
+ const subscription = taskBroker.event$({ taskId, after }).subscribe({
501
+ error: (error) => {
502
+ logger.error(
503
+ `Received error from event stream when observing taskId '${taskId}', ${error}`
504
+ );
505
+ },
506
+ next: ({ events }) => {
507
+ clearTimeout(timeout);
508
+ subscription.unsubscribe();
509
+ res.json(events);
510
+ }
511
+ });
512
+ req.on("close", () => {
513
+ subscription.unsubscribe();
514
+ clearTimeout(timeout);
515
+ });
516
+ }).post("/v2/dry-run", async (req, res) => {
517
+ const credentials = await httpAuth.credentials(req);
518
+ await checkPermissions.checkPermission({
519
+ credentials,
520
+ permissions: [alpha.taskCreatePermission],
521
+ permissionService: permissions
522
+ });
523
+ const bodySchema = zod.z.object({
524
+ template: zod.z.unknown(),
525
+ values: zod.z.record(zod.z.unknown()),
526
+ secrets: zod.z.record(zod.z.string()).optional(),
527
+ directoryContents: zod.z.array(
528
+ zod.z.object({ path: zod.z.string(), base64Content: zod.z.string() })
529
+ )
530
+ });
531
+ const body = await bodySchema.parseAsync(req.body).catch((e) => {
532
+ throw new errors.InputError(`Malformed request: ${e}`);
533
+ });
534
+ const template = body.template;
535
+ if (!await pluginScaffolderCommon.templateEntityV1beta3Validator.check(template)) {
536
+ throw new errors.InputError("Input template is not a template");
537
+ }
538
+ const { token } = await auth.getPluginRequestToken({
539
+ onBehalfOf: credentials,
540
+ targetPluginId: "catalog"
541
+ });
542
+ const userEntityRef = auth.isPrincipal(credentials, "user") ? credentials.principal.userEntityRef : void 0;
543
+ const userEntity = userEntityRef ? await catalogClient.getEntityByRef(userEntityRef, { token }) : void 0;
544
+ for (const parameters of [template.spec.parameters ?? []].flat()) {
545
+ const result2 = jsonschema.validate(body.values, parameters);
546
+ if (!result2.valid) {
547
+ res.status(400).json({ errors: result2.errors });
548
+ return;
549
+ }
550
+ }
551
+ const steps = template.spec.steps.map((step, index) => ({
552
+ ...step,
553
+ id: step.id ?? `step-${index + 1}`,
554
+ name: step.name ?? step.action
555
+ }));
556
+ const result = await dryRunner({
557
+ spec: {
558
+ apiVersion: template.apiVersion,
559
+ steps,
560
+ output: template.spec.output ?? {},
561
+ parameters: body.values,
562
+ user: {
563
+ entity: userEntity,
564
+ ref: userEntityRef
565
+ }
566
+ },
567
+ directoryContents: (body.directoryContents ?? []).map((file) => ({
568
+ path: file.path,
569
+ content: Buffer.from(file.base64Content, "base64")
570
+ })),
571
+ secrets: {
572
+ ...body.secrets,
573
+ ...token && { backstageToken: token }
574
+ },
575
+ credentials
576
+ });
577
+ res.status(200).json({
578
+ ...result,
579
+ steps,
580
+ directoryContents: result.directoryContents.map((file) => ({
581
+ path: file.path,
582
+ executable: file.executable,
583
+ base64Content: file.content.toString("base64")
584
+ }))
585
+ });
586
+ }).post("/v2/autocomplete/:provider/:resource", async (req, res) => {
587
+ const { token, context } = req.body;
588
+ const { provider, resource } = req.params;
589
+ if (!token) throw new errors.InputError("Missing token query parameter");
590
+ if (!autocompleteHandlers[provider]) {
591
+ throw new errors.InputError(`Unsupported provider: ${provider}`);
592
+ }
593
+ const { results } = await autocompleteHandlers[provider]({
594
+ resource,
595
+ token,
596
+ context
597
+ });
598
+ res.status(200).json({ results });
599
+ });
600
+ const app = express__default.default();
601
+ app.set("logger", logger);
602
+ app.use("/", router);
603
+ async function authorizeTemplate(entityRef, token, credentials) {
604
+ const template = await helpers.findTemplate({
605
+ catalogApi: catalogClient,
606
+ entityRef,
607
+ token
608
+ });
609
+ if (!isSupportedTemplate(template)) {
610
+ throw new errors.InputError(
611
+ `Unsupported apiVersion field in schema entity, ${template.apiVersion}`
612
+ );
613
+ }
614
+ if (!permissions) {
615
+ return template;
616
+ }
617
+ const [parameterDecision, stepDecision] = await permissions.authorizeConditional(
618
+ [
619
+ { permission: alpha.templateParameterReadPermission },
620
+ { permission: alpha.templateStepReadPermission }
621
+ ],
622
+ { credentials }
623
+ );
624
+ if (Array.isArray(template.spec.parameters)) {
625
+ template.spec.parameters = template.spec.parameters.filter(
626
+ (step) => isAuthorized(parameterDecision, step)
627
+ );
628
+ } else if (template.spec.parameters && !isAuthorized(parameterDecision, template.spec.parameters)) {
629
+ template.spec.parameters = void 0;
630
+ }
631
+ template.spec.steps = template.spec.steps.filter(
632
+ (step) => isAuthorized(stepDecision, step)
633
+ );
634
+ return template;
635
+ }
636
+ return app;
637
+ }
638
+
639
+ exports.createRouter = createRouter;
640
+ //# sourceMappingURL=router.cjs.js.map