@backstage/plugin-scaffolder-backend 1.21.3 → 1.22.0-next.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.
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var backendCommon = require('@backstage/backend-common');
3
4
  var catalogModel = require('@backstage/catalog-model');
4
5
  var config = require('@backstage/config');
5
6
  var errors = require('@backstage/errors');
@@ -13,7 +14,6 @@ var zod = require('zod');
13
14
  var pluginScaffolderNode = require('@backstage/plugin-scaffolder-node');
14
15
  var yaml = require('yaml');
15
16
  var fs = require('fs-extra');
16
- var backendCommon = require('@backstage/backend-common');
17
17
  var path = require('path');
18
18
  var luxon = require('luxon');
19
19
  var globby = require('globby');
@@ -95,7 +95,7 @@ const examples$9 = [
95
95
 
96
96
  const id$4 = "catalog:register";
97
97
  function createCatalogRegisterAction(options) {
98
- const { catalogClient, integrations } = options;
98
+ const { catalogClient, integrations, auth } = options;
99
99
  return pluginScaffolderNode.createTemplateAction({
100
100
  id: id$4,
101
101
  description: "Registers entities from a catalog descriptor file in the workspace into the software catalog.",
@@ -175,13 +175,17 @@ function createCatalogRegisterAction(options) {
175
175
  });
176
176
  }
177
177
  ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`);
178
+ const { token } = (_b = await (auth == null ? void 0 : auth.getPluginRequestToken({
179
+ onBehalfOf: await ctx.getInitiatorCredentials(),
180
+ targetPluginId: "catalog"
181
+ }))) != null ? _b : { token: (_a = ctx.secrets) == null ? void 0 : _a.backstageToken };
178
182
  try {
179
183
  await catalogClient.addLocation(
180
184
  {
181
185
  type: "url",
182
186
  target: catalogInfoUrl
183
187
  },
184
- ((_a = ctx.secrets) == null ? void 0 : _a.backstageToken) ? { token: ctx.secrets.backstageToken } : {}
188
+ token ? { token } : {}
185
189
  );
186
190
  } catch (e) {
187
191
  if (!input.optional) {
@@ -195,7 +199,7 @@ function createCatalogRegisterAction(options) {
195
199
  type: "url",
196
200
  target: catalogInfoUrl
197
201
  },
198
- ((_b = ctx.secrets) == null ? void 0 : _b.backstageToken) ? { token: ctx.secrets.backstageToken } : {}
202
+ token ? { token } : {}
199
203
  );
200
204
  if (result.entities.length) {
201
205
  const { entities } = result;
@@ -316,7 +320,7 @@ const examples$7 = [
316
320
 
317
321
  const id$2 = "catalog:fetch";
318
322
  function createFetchCatalogEntityAction(options) {
319
- const { catalogClient } = options;
323
+ const { catalogClient, auth } = options;
320
324
  return pluginScaffolderNode.createTemplateAction({
321
325
  id: id$2,
322
326
  description: "Returns entity or entities from the catalog by entity reference(s)",
@@ -356,13 +360,17 @@ function createFetchCatalogEntityAction(options) {
356
360
  }
357
361
  throw new Error("Missing entity reference or references");
358
362
  }
363
+ const { token } = (_b = await (auth == null ? void 0 : auth.getPluginRequestToken({
364
+ onBehalfOf: await ctx.getInitiatorCredentials(),
365
+ targetPluginId: "catalog"
366
+ }))) != null ? _b : { token: (_a = ctx.secrets) == null ? void 0 : _a.backstageToken };
359
367
  if (entityRef) {
360
368
  const entity = await catalogClient.getEntityByRef(
361
369
  catalogModel.stringifyEntityRef(
362
370
  catalogModel.parseEntityRef(entityRef, { defaultKind, defaultNamespace })
363
371
  ),
364
372
  {
365
- token: (_a = ctx.secrets) == null ? void 0 : _a.backstageToken
373
+ token
366
374
  }
367
375
  );
368
376
  if (!entity && !optional) {
@@ -380,7 +388,7 @@ function createFetchCatalogEntityAction(options) {
380
388
  )
381
389
  },
382
390
  {
383
- token: (_b = ctx.secrets) == null ? void 0 : _b.backstageToken
391
+ token
384
392
  }
385
393
  );
386
394
  const finalEntities = entities.items.map((e, i) => {
@@ -1320,6 +1328,7 @@ const createBuiltinActions = (options) => {
1320
1328
  reader,
1321
1329
  integrations,
1322
1330
  catalogClient,
1331
+ auth,
1323
1332
  config,
1324
1333
  additionalTemplateFilters,
1325
1334
  additionalTemplateGlobals
@@ -1393,8 +1402,8 @@ const createBuiltinActions = (options) => {
1393
1402
  }),
1394
1403
  createDebugLogAction(),
1395
1404
  createWaitAction(),
1396
- createCatalogRegisterAction({ catalogClient, integrations }),
1397
- createFetchCatalogEntityAction({ catalogClient }),
1405
+ createCatalogRegisterAction({ catalogClient, integrations, auth }),
1406
+ createFetchCatalogEntityAction({ catalogClient, auth }),
1398
1407
  createCatalogWriteAction(),
1399
1408
  createFilesystemDeleteAction(),
1400
1409
  createFilesystemRenameAction(),
@@ -1742,6 +1751,18 @@ class DatabaseTaskStore {
1742
1751
  body: serializedBody
1743
1752
  });
1744
1753
  }
1754
+ async getTaskState({ taskId }) {
1755
+ const [result] = await this.db("tasks").where({ id: taskId }).select("state");
1756
+ return result.state ? JSON.parse(result.state) : void 0;
1757
+ }
1758
+ async saveTaskState(options) {
1759
+ if (options.state) {
1760
+ const serializedState = JSON.stringify({ state: options.state });
1761
+ await this.db("tasks").where({ id: options.taskId }).update({
1762
+ state: serializedState
1763
+ });
1764
+ }
1765
+ }
1745
1766
  async listEvents(options) {
1746
1767
  const { taskId, after } = options;
1747
1768
  const rawEvents = await this.db("task_events").where({
@@ -1881,16 +1902,17 @@ var __publicField$2 = (obj, key, value) => {
1881
1902
  };
1882
1903
  class TaskManager {
1883
1904
  // Runs heartbeat internally
1884
- constructor(task, storage, signal, logger) {
1905
+ constructor(task, storage, signal, logger, auth) {
1885
1906
  this.task = task;
1886
1907
  this.storage = storage;
1887
1908
  this.signal = signal;
1888
1909
  this.logger = logger;
1910
+ this.auth = auth;
1889
1911
  __publicField$2(this, "isDone", false);
1890
1912
  __publicField$2(this, "heartbeatTimeoutId");
1891
1913
  }
1892
- static create(task, storage, abortSignal, logger) {
1893
- const agent = new TaskManager(task, storage, abortSignal, logger);
1914
+ static create(task, storage, abortSignal, logger, auth) {
1915
+ const agent = new TaskManager(task, storage, abortSignal, logger, auth);
1894
1916
  agent.startTimeout();
1895
1917
  return agent;
1896
1918
  }
@@ -1918,6 +1940,23 @@ class TaskManager {
1918
1940
  body: { message, ...logMetadata }
1919
1941
  });
1920
1942
  }
1943
+ async getTaskState() {
1944
+ var _a, _b;
1945
+ return (_b = (_a = this.storage).getTaskState) == null ? void 0 : _b.call(_a, { taskId: this.task.taskId });
1946
+ }
1947
+ async updateCheckpoint(options) {
1948
+ var _a, _b;
1949
+ const { key, ...value } = options;
1950
+ if (this.task.state) {
1951
+ this.task.state.checkpoints[key] = value;
1952
+ } else {
1953
+ this.task.state = { checkpoints: { [key]: value } };
1954
+ }
1955
+ await ((_b = (_a = this.storage).saveTaskState) == null ? void 0 : _b.call(_a, {
1956
+ taskId: this.task.taskId,
1957
+ state: this.task.state
1958
+ }));
1959
+ }
1921
1960
  async complete(result, metadata) {
1922
1961
  await this.storage.completeTask({
1923
1962
  taskId: this.task.taskId,
@@ -1946,6 +1985,17 @@ class TaskManager {
1946
1985
  }
1947
1986
  }, 1e3);
1948
1987
  }
1988
+ async getInitiatorCredentials() {
1989
+ if (this.task.secrets && "__initiatorCredentials" in this.task.secrets) {
1990
+ return JSON.parse(this.task.secrets.__initiatorCredentials);
1991
+ }
1992
+ if (!this.auth) {
1993
+ throw new Error(
1994
+ "Failed to create none credentials in scaffolder task. The TaskManager has not been initialized with an auth service implementation"
1995
+ );
1996
+ }
1997
+ return this.auth.getNoneCredentials();
1998
+ }
1949
1999
  }
1950
2000
  function defer() {
1951
2001
  let resolve = () => {
@@ -1956,10 +2006,11 @@ function defer() {
1956
2006
  return { promise, resolve };
1957
2007
  }
1958
2008
  class StorageTaskBroker {
1959
- constructor(storage, logger, config) {
2009
+ constructor(storage, logger, config, auth) {
1960
2010
  this.storage = storage;
1961
2011
  this.logger = logger;
1962
2012
  this.config = config;
2013
+ this.auth = auth;
1963
2014
  __publicField$2(this, "deferredDispatch", defer());
1964
2015
  }
1965
2016
  async list(options) {
@@ -2030,7 +2081,8 @@ class StorageTaskBroker {
2030
2081
  },
2031
2082
  this.storage,
2032
2083
  abortController.signal,
2033
- this.logger
2084
+ this.logger,
2085
+ this.auth
2034
2086
  );
2035
2087
  }
2036
2088
  await this.waitForDispatch();
@@ -2311,7 +2363,7 @@ class NunjucksWorkflowRunner {
2311
2363
  });
2312
2364
  }
2313
2365
  async executeStep(task, step, context, renderTemplate, taskTrack, workspacePath, decision) {
2314
- var _a, _b, _c, _d, _e;
2366
+ var _a, _b, _c, _d, _e, _f;
2315
2367
  const stepTrack = await this.tracker.stepStart(task, step);
2316
2368
  if (task.cancelSignal.aborted) {
2317
2369
  throw new Error(`Step ${step.name} has been cancelled.`);
@@ -2406,6 +2458,7 @@ class NunjucksWorkflowRunner {
2406
2458
  }
2407
2459
  const tmpDirs = new Array();
2408
2460
  const stepOutput = {};
2461
+ const prevTaskState = await ((_e = task.getTaskState) == null ? void 0 : _e.call(task));
2409
2462
  for (const iteration of iterations) {
2410
2463
  if (iteration.each) {
2411
2464
  taskLogger.info(
@@ -2418,10 +2471,39 @@ class NunjucksWorkflowRunner {
2418
2471
  }
2419
2472
  await action.handler({
2420
2473
  input: iteration.input,
2421
- secrets: (_e = task.secrets) != null ? _e : {},
2474
+ secrets: (_f = task.secrets) != null ? _f : {},
2422
2475
  logger: taskLogger,
2423
2476
  logStream: streamLogger,
2424
2477
  workspacePath,
2478
+ async checkpoint(keySuffix, fn) {
2479
+ var _a2, _b2, _c2, _d2;
2480
+ const key = `v1.task.checkpoint.${keySuffix}`;
2481
+ try {
2482
+ let prevValue;
2483
+ if (prevTaskState) {
2484
+ const prevState = (_b2 = (_a2 = prevTaskState.state) == null ? void 0 : _a2.checkpoints) == null ? void 0 : _b2[key];
2485
+ if (prevState && prevState.status === "success") {
2486
+ prevValue = prevState.value;
2487
+ }
2488
+ }
2489
+ const value = prevValue ? prevValue : await fn();
2490
+ if (!prevValue) {
2491
+ (_c2 = task.updateCheckpoint) == null ? void 0 : _c2.call(task, {
2492
+ key,
2493
+ status: "success",
2494
+ value
2495
+ });
2496
+ }
2497
+ return value;
2498
+ } catch (err) {
2499
+ (_d2 = task.updateCheckpoint) == null ? void 0 : _d2.call(task, {
2500
+ key,
2501
+ status: "failed",
2502
+ reason: errors.stringifyError(err)
2503
+ });
2504
+ throw err;
2505
+ }
2506
+ },
2425
2507
  createTemporaryDirectory: async () => {
2426
2508
  const tmpDir = await fs__default["default"].mkdtemp(
2427
2509
  `${workspacePath}_step-${step.id}-`
@@ -2440,7 +2522,8 @@ class NunjucksWorkflowRunner {
2440
2522
  templateInfo: task.spec.templateInfo,
2441
2523
  user: task.spec.user,
2442
2524
  isDryRun: task.isDryRun,
2443
- signal: task.cancelSignal
2525
+ signal: task.cancelSignal,
2526
+ getInitiatorCredentials: task.getInitiatorCredentials
2444
2527
  });
2445
2528
  }
2446
2529
  for (const tmpDir of tmpDirs) {
@@ -2458,7 +2541,6 @@ class NunjucksWorkflowRunner {
2458
2541
  }
2459
2542
  }
2460
2543
  async execute(task) {
2461
- var _a;
2462
2544
  if (!isValidTaskSpec(task.spec)) {
2463
2545
  throw new errors.InputError(
2464
2546
  "Wrong template version executed with the workflow engine"
@@ -2486,7 +2568,7 @@ class NunjucksWorkflowRunner {
2486
2568
  };
2487
2569
  const [decision] = this.options.permissions && task.spec.steps.length ? await this.options.permissions.authorizeConditional(
2488
2570
  [{ permission: alpha.actionExecutePermission }],
2489
- { token: (_a = task.secrets) == null ? void 0 : _a.backstageToken }
2571
+ { credentials: await task.getInitiatorCredentials() }
2490
2572
  ) : [{ result: pluginPermissionCommon.AuthorizeResult.ALLOW }];
2491
2573
  for (const step of task.spec.steps) {
2492
2574
  await this.executeStep(
@@ -2809,6 +2891,7 @@ function createDryRunner(options) {
2809
2891
  }
2810
2892
  },
2811
2893
  secrets: input.secrets,
2894
+ getInitiatorCredentials: () => Promise.resolve(input.credentials),
2812
2895
  // No need to update this at the end of the run, so just hard-code it
2813
2896
  done: false,
2814
2897
  isDryRun: true,
@@ -2967,17 +3050,23 @@ async function createRouter(options) {
2967
3050
  additionalTemplateFilters,
2968
3051
  additionalTemplateGlobals,
2969
3052
  permissions,
2970
- permissionRules
3053
+ permissionRules,
3054
+ discovery = backendCommon.HostDiscovery.fromConfig(config),
3055
+ identity = buildDefaultIdentityClient(options)
2971
3056
  } = options;
3057
+ const { auth, httpAuth } = backendCommon.createLegacyAuthAdapters({
3058
+ ...options,
3059
+ identity,
3060
+ discovery
3061
+ });
2972
3062
  const concurrentTasksLimit = (_a = options.concurrentTasksLimit) != null ? _a : options.config.getOptionalNumber("scaffolder.concurrentTasksLimit");
2973
3063
  const logger = parentLogger.child({ plugin: "scaffolder" });
2974
- const identity = options.identity || buildDefaultIdentityClient(options);
2975
3064
  const workingDirectory = await getWorkingDirectory(config, logger);
2976
3065
  const integrations = integration.ScmIntegrations.fromConfig(config);
2977
3066
  let taskBroker;
2978
3067
  if (!options.taskBroker) {
2979
3068
  const databaseTaskStore = await DatabaseTaskStore.create({ database });
2980
- taskBroker = new StorageTaskBroker(databaseTaskStore, logger, config);
3069
+ taskBroker = new StorageTaskBroker(databaseTaskStore, logger, config, auth);
2981
3070
  if (scheduler && databaseTaskStore.listStaleTasks) {
2982
3071
  await scheduler.scheduleTask({
2983
3072
  id: "close_stale_tasks",
@@ -3031,7 +3120,8 @@ async function createRouter(options) {
3031
3120
  reader,
3032
3121
  config,
3033
3122
  additionalTemplateFilters,
3034
- additionalTemplateGlobals
3123
+ additionalTemplateGlobals,
3124
+ auth
3035
3125
  });
3036
3126
  actionsToRegister.forEach((action) => actionRegistry.register(action));
3037
3127
  const launchWorkers = () => workers.forEach((worker) => worker.start());
@@ -3085,11 +3175,16 @@ async function createRouter(options) {
3085
3175
  "/v2/templates/:namespace/:kind/:name/parameter-schema",
3086
3176
  async (req, res) => {
3087
3177
  var _a2, _b;
3088
- const userIdentity = await identity.getIdentity({
3089
- request: req
3178
+ const credentials = await httpAuth.credentials(req);
3179
+ const { token } = await auth.getPluginRequestToken({
3180
+ onBehalfOf: credentials,
3181
+ targetPluginId: "catalog"
3090
3182
  });
3091
- const token = userIdentity == null ? void 0 : userIdentity.token;
3092
- const template = await authorizeTemplate(req.params, token);
3183
+ const template = await authorizeTemplate(
3184
+ req.params,
3185
+ token,
3186
+ credentials
3187
+ );
3093
3188
  const parameters = [(_a2 = template.spec.parameters) != null ? _a2 : []].flat();
3094
3189
  const presentation = template.spec.presentation;
3095
3190
  res.json({
@@ -3123,11 +3218,12 @@ async function createRouter(options) {
3123
3218
  const { kind, namespace, name } = catalogModel.parseEntityRef(templateRef, {
3124
3219
  defaultKind: "template"
3125
3220
  });
3126
- const callerIdentity = await identity.getIdentity({
3127
- request: req
3221
+ const credentials = await httpAuth.credentials(req, { allow: ["user"] });
3222
+ const { token } = await auth.getPluginRequestToken({
3223
+ onBehalfOf: credentials,
3224
+ targetPluginId: "catalog"
3128
3225
  });
3129
- const token = callerIdentity == null ? void 0 : callerIdentity.token;
3130
- const userEntityRef = callerIdentity == null ? void 0 : callerIdentity.identity.userEntityRef;
3226
+ const userEntityRef = credentials.principal.userEntityRef;
3131
3227
  const userEntity = userEntityRef ? await catalogClient.getEntityByRef(userEntityRef, { token }) : void 0;
3132
3228
  let auditLog = `Scaffolding task for ${templateRef}`;
3133
3229
  if (userEntityRef) {
@@ -3137,7 +3233,8 @@ async function createRouter(options) {
3137
3233
  const values = req.body.values;
3138
3234
  const template = await authorizeTemplate(
3139
3235
  { kind, namespace, name },
3140
- token
3236
+ token,
3237
+ credentials
3141
3238
  );
3142
3239
  for (const parameters of [(_a2 = template.spec.parameters) != null ? _a2 : []].flat()) {
3143
3240
  const result2 = jsonschema.validate(values, parameters);
@@ -3177,7 +3274,8 @@ async function createRouter(options) {
3177
3274
  createdBy: userEntityRef,
3178
3275
  secrets: {
3179
3276
  ...req.body.secrets,
3180
- backstageToken: token
3277
+ backstageToken: token,
3278
+ initiatorCredentials: JSON.stringify(credentials)
3181
3279
  }
3182
3280
  });
3183
3281
  res.status(201).json({ id: result.taskId });
@@ -3272,7 +3370,7 @@ data: ${JSON.stringify(event)}
3272
3370
  clearTimeout(timeout);
3273
3371
  });
3274
3372
  }).post("/v2/dry-run", async (req, res) => {
3275
- var _a2, _b, _c, _d;
3373
+ var _a2, _b, _c;
3276
3374
  const bodySchema = zod.z.object({
3277
3375
  template: zod.z.unknown(),
3278
3376
  values: zod.z.record(zod.z.unknown()),
@@ -3288,10 +3386,12 @@ data: ${JSON.stringify(event)}
3288
3386
  if (!await pluginScaffolderCommon.templateEntityV1beta3Validator.check(template)) {
3289
3387
  throw new errors.InputError("Input template is not a template");
3290
3388
  }
3291
- const token = (_a2 = await identity.getIdentity({
3292
- request: req
3293
- })) == null ? void 0 : _a2.token;
3294
- for (const parameters of [(_b = template.spec.parameters) != null ? _b : []].flat()) {
3389
+ const credentials = await httpAuth.credentials(req);
3390
+ const { token } = await auth.getPluginRequestToken({
3391
+ onBehalfOf: credentials,
3392
+ targetPluginId: "catalog"
3393
+ });
3394
+ for (const parameters of [(_a2 = template.spec.parameters) != null ? _a2 : []].flat()) {
3295
3395
  const result2 = jsonschema.validate(body.values, parameters);
3296
3396
  if (!result2.valid) {
3297
3397
  res.status(400).json({ errors: result2.errors });
@@ -3310,17 +3410,18 @@ data: ${JSON.stringify(event)}
3310
3410
  spec: {
3311
3411
  apiVersion: template.apiVersion,
3312
3412
  steps,
3313
- output: (_c = template.spec.output) != null ? _c : {},
3413
+ output: (_b = template.spec.output) != null ? _b : {},
3314
3414
  parameters: body.values
3315
3415
  },
3316
- directoryContents: ((_d = body.directoryContents) != null ? _d : []).map((file) => ({
3416
+ directoryContents: ((_c = body.directoryContents) != null ? _c : []).map((file) => ({
3317
3417
  path: file.path,
3318
3418
  content: Buffer.from(file.base64Content, "base64")
3319
3419
  })),
3320
3420
  secrets: {
3321
3421
  ...body.secrets,
3322
3422
  ...token && { backstageToken: token }
3323
- }
3423
+ },
3424
+ credentials
3324
3425
  });
3325
3426
  res.status(200).json({
3326
3427
  ...result,
@@ -3335,7 +3436,7 @@ data: ${JSON.stringify(event)}
3335
3436
  const app = express__default["default"]();
3336
3437
  app.set("logger", logger);
3337
3438
  app.use("/", router);
3338
- async function authorizeTemplate(entityRef, token) {
3439
+ async function authorizeTemplate(entityRef, token, credentials) {
3339
3440
  const template = await findTemplate({
3340
3441
  catalogApi: catalogClient,
3341
3442
  entityRef,
@@ -3354,7 +3455,7 @@ data: ${JSON.stringify(event)}
3354
3455
  { permission: alpha.templateParameterReadPermission },
3355
3456
  { permission: alpha.templateStepReadPermission }
3356
3457
  ],
3357
- { token }
3458
+ { credentials }
3358
3459
  );
3359
3460
  if (Array.isArray(template.spec.parameters)) {
3360
3461
  template.spec.parameters = template.spec.parameters.filter(
@@ -3389,4 +3490,4 @@ exports.createRouter = createRouter;
3389
3490
  exports.createWaitAction = createWaitAction;
3390
3491
  exports.scaffolderActionRules = scaffolderActionRules;
3391
3492
  exports.scaffolderTemplateRules = scaffolderTemplateRules;
3392
- //# sourceMappingURL=router-f392ade6.cjs.js.map
3493
+ //# sourceMappingURL=router-1665319e.cjs.js.map