@backstage/plugin-scaffolder-backend 1.22.0-next.1 → 1.22.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,87 @@
1
1
  # @backstage/plugin-scaffolder-backend
2
2
 
3
+ ## 1.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e9663a9: Move away from using `ctx.logStream`
8
+ - aa543c9: Migrate plugin to use the new auth services, add an optional service discovery to the router options and change the permissions type to be `PermissionsService`.
9
+ - e9663a9: Enable the redaction of secrets using the redacting logger and the secrets from the `TaskSpec`
10
+ - c6b132e: Introducing checkpoints for scaffolder task action idempotency
11
+
12
+ ### Patch Changes
13
+
14
+ - 984abfa: Fixing the lost of the initial state after a task recovery.
15
+ - 703ebc9: Fix support for unauthenticated requests to create scaffolder tasks
16
+ - f44589d: Introduced `createMockActionContext` to unify the way of creating scaffolder mock context.
17
+
18
+ It will help to maintain tests in a long run during structural changes of action context.
19
+
20
+ - 0fb419b: Updated dependency `uuid` to `^9.0.0`.
21
+ Updated dependency `@types/uuid` to `^9.0.0`.
22
+ - bbd1fe1: Made "checkpoint" on scaffolder action context non-optional
23
+ - Updated dependencies
24
+ - @backstage/plugin-scaffolder-node@0.4.0
25
+ - @backstage/plugin-scaffolder-backend-module-azure@0.1.6
26
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.4
27
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.4
28
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.4
29
+ - @backstage/backend-common@0.21.4
30
+ - @backstage/integration@1.9.1
31
+ - @backstage/plugin-auth-node@0.4.9
32
+ - @backstage/config@1.2.0
33
+ - @backstage/errors@1.2.4
34
+ - @backstage/backend-plugin-api@0.6.14
35
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.6
36
+ - @backstage/plugin-scaffolder-backend-module-github@0.2.4
37
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.0
38
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.1.4
39
+ - @backstage/plugin-permission-common@0.7.13
40
+ - @backstage/plugin-catalog-node@1.8.0
41
+ - @backstage/catalog-client@1.6.1
42
+ - @backstage/backend-tasks@0.5.19
43
+ - @backstage/plugin-permission-node@0.7.25
44
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11
45
+ - @backstage/catalog-model@1.4.5
46
+ - @backstage/types@1.1.1
47
+ - @backstage/plugin-scaffolder-common@1.5.1
48
+
49
+ ## 1.22.0-next.2
50
+
51
+ ### Minor Changes
52
+
53
+ - e9663a9: Move away from using `ctx.logStream`
54
+ - e9663a9: Enable the redaction of secrets using the redacting logger and the secrets from the `TaskSpec`
55
+
56
+ ### Patch Changes
57
+
58
+ - 703ebc9: Fix support for unauthenticated requests to create scaffolder tasks
59
+ - Updated dependencies
60
+ - @backstage/plugin-scaffolder-node@0.4.0-next.2
61
+ - @backstage/plugin-scaffolder-backend-module-azure@0.1.6-next.2
62
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.4-next.2
63
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.4-next.2
64
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.4-next.2
65
+ - @backstage/integration@1.9.1-next.2
66
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.0-next.2
67
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.1.4-next.2
68
+ - @backstage/catalog-client@1.6.1-next.1
69
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.6-next.2
70
+ - @backstage/plugin-scaffolder-backend-module-github@0.2.4-next.2
71
+ - @backstage/backend-common@0.21.4-next.2
72
+ - @backstage/plugin-auth-node@0.4.9-next.2
73
+ - @backstage/plugin-catalog-node@1.8.0-next.2
74
+ - @backstage/backend-plugin-api@0.6.14-next.2
75
+ - @backstage/backend-tasks@0.5.19-next.2
76
+ - @backstage/catalog-model@1.4.5-next.0
77
+ - @backstage/config@1.2.0-next.1
78
+ - @backstage/errors@1.2.4-next.0
79
+ - @backstage/types@1.1.1
80
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11-next.2
81
+ - @backstage/plugin-permission-common@0.7.13-next.1
82
+ - @backstage/plugin-permission-node@0.7.25-next.2
83
+ - @backstage/plugin-scaffolder-common@1.5.1-next.1
84
+
3
85
  ## 1.22.0-next.1
4
86
 
5
87
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-scaffolder-backend",
3
- "version": "1.22.0-next.1",
3
+ "version": "1.22.0",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/dist/alpha.cjs.js CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var alpha = require('@backstage/plugin-scaffolder-common/alpha');
6
6
  var pluginPermissionNode = require('@backstage/plugin-permission-node');
7
- var router = require('./cjs/router-1665319e.cjs.js');
7
+ var router = require('./cjs/router-77345506.cjs.js');
8
8
  var backendPluginApi = require('@backstage/backend-plugin-api');
9
9
  var backendCommon = require('@backstage/backend-common');
10
10
  var integration = require('@backstage/integration');
@@ -274,7 +274,7 @@ function createCatalogWriteAction() {
274
274
  examples: examples$8,
275
275
  supportsDryRun: true,
276
276
  async handler(ctx) {
277
- ctx.logStream.write(`Writing catalog-info.yaml`);
277
+ ctx.logger.info(`Writing catalog-info.yaml`);
278
278
  const { filePath, entity } = ctx.input;
279
279
  const path = filePath != null ? filePath : "catalog-info.yaml";
280
280
  await fs__default["default"].writeFile(
@@ -465,11 +465,11 @@ function createDebugLogAction() {
465
465
  var _a, _b;
466
466
  ctx.logger.info(JSON.stringify(ctx.input, null, 2));
467
467
  if ((_a = ctx.input) == null ? void 0 : _a.message) {
468
- ctx.logStream.write(ctx.input.message);
468
+ ctx.logger.info(ctx.input.message);
469
469
  }
470
470
  if ((_b = ctx.input) == null ? void 0 : _b.listWorkspace) {
471
471
  const files = await recursiveReadDir(ctx.workspacePath);
472
- ctx.logStream.write(
472
+ ctx.logger.info(
473
473
  `Workspace:
474
474
  ${files.map((f) => ` - ${path.relative(ctx.workspacePath, f)}`).join("\n")}`
475
475
  );
@@ -1615,6 +1615,7 @@ class DatabaseTaskStore {
1615
1615
  try {
1616
1616
  const spec = JSON.parse(result.spec);
1617
1617
  const secrets = result.secrets ? JSON.parse(result.secrets) : void 0;
1618
+ const state = result.state ? JSON.parse(result.state).state : void 0;
1618
1619
  return {
1619
1620
  id: result.id,
1620
1621
  spec,
@@ -1622,7 +1623,8 @@ class DatabaseTaskStore {
1622
1623
  lastHeartbeatAt: parseSqlDateToIsoString(result.last_heartbeat_at),
1623
1624
  createdAt: parseSqlDateToIsoString(result.created_at),
1624
1625
  createdBy: (_a = result.created_by) != null ? _a : void 0,
1625
- secrets
1626
+ secrets,
1627
+ state
1626
1628
  };
1627
1629
  } catch (error) {
1628
1630
  throw new Error(`Failed to parse spec of task '${taskId}', ${error}`);
@@ -1659,6 +1661,15 @@ class DatabaseTaskStore {
1659
1661
  if (updateCount < 1) {
1660
1662
  return void 0;
1661
1663
  }
1664
+ const getState = () => {
1665
+ try {
1666
+ return task.state ? JSON.parse(task.state).state : void 0;
1667
+ } catch (error) {
1668
+ throw new Error(
1669
+ `Failed to parse state of the task '${task.id}', ${error}`
1670
+ );
1671
+ }
1672
+ };
1662
1673
  const secrets = this.parseTaskSecrets(task);
1663
1674
  return {
1664
1675
  id: task.id,
@@ -1667,7 +1678,8 @@ class DatabaseTaskStore {
1667
1678
  lastHeartbeatAt: task.last_heartbeat_at,
1668
1679
  createdAt: task.created_at,
1669
1680
  createdBy: (_a = task.created_by) != null ? _a : void 0,
1670
- secrets
1681
+ secrets,
1682
+ state: getState()
1671
1683
  };
1672
1684
  });
1673
1685
  }
@@ -1843,7 +1855,7 @@ class DatabaseTaskStore {
1843
1855
  taskIdsToRecover.push(...result.map((i) => i.id));
1844
1856
  for (const { id, spec } of result) {
1845
1857
  const taskSpec = JSON.parse(spec);
1846
- await this.db("task_events").insert({
1858
+ await tx("task_events").insert({
1847
1859
  task_id: id,
1848
1860
  event_type: "recovered",
1849
1861
  body: JSON.stringify({
@@ -2077,7 +2089,8 @@ class StorageTaskBroker {
2077
2089
  taskId: pendingTask.id,
2078
2090
  spec: pendingTask.spec,
2079
2091
  secrets: pendingTask.secrets,
2080
- createdBy: pendingTask.createdBy
2092
+ createdBy: pendingTask.createdBy,
2093
+ state: pendingTask.state
2081
2094
  },
2082
2095
  this.storage,
2083
2096
  abortController.signal,
@@ -2269,6 +2282,137 @@ const scaffolderActionRules = {
2269
2282
  hasStringProperty
2270
2283
  };
2271
2284
 
2285
+ var __accessCheck = (obj, member, msg) => {
2286
+ if (!member.has(obj))
2287
+ throw TypeError("Cannot " + msg);
2288
+ };
2289
+ var __privateGet = (obj, member, getter) => {
2290
+ __accessCheck(obj, member, "read from private field");
2291
+ return getter ? getter.call(obj) : member.get(obj);
2292
+ };
2293
+ var __privateAdd = (obj, member, value) => {
2294
+ if (member.has(obj))
2295
+ throw TypeError("Cannot add the same private member more than once");
2296
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
2297
+ };
2298
+ var __privateSet = (obj, member, value, setter) => {
2299
+ __accessCheck(obj, member, "write to private field");
2300
+ setter ? setter.call(obj, value) : member.set(obj, value);
2301
+ return value;
2302
+ };
2303
+ var _winston, _addRedactions;
2304
+ const escapeRegExp = (text) => {
2305
+ return text.replace(/[.*+?^${}(\)|[\]\\]/g, "\\$&");
2306
+ };
2307
+ const _WinstonLogger = class _WinstonLogger {
2308
+ constructor(winston, addRedactions) {
2309
+ __privateAdd(this, _winston, void 0);
2310
+ __privateAdd(this, _addRedactions, void 0);
2311
+ __privateSet(this, _winston, winston);
2312
+ __privateSet(this, _addRedactions, addRedactions);
2313
+ }
2314
+ /**
2315
+ * Creates a {@link WinstonLogger} instance.
2316
+ */
2317
+ static create(options) {
2318
+ var _a;
2319
+ const redacter = _WinstonLogger.redacter();
2320
+ let logger = winston.createLogger({
2321
+ level: options.level,
2322
+ format: winston.format.combine(redacter.format, options.format),
2323
+ transports: (_a = options.transports) != null ? _a : new winston.transports.Console()
2324
+ });
2325
+ if (options.meta) {
2326
+ logger = logger.child(options.meta);
2327
+ }
2328
+ return new _WinstonLogger(logger, redacter.add);
2329
+ }
2330
+ /**
2331
+ * Creates a winston log formatter for redacting secrets.
2332
+ */
2333
+ static redacter() {
2334
+ const redactionSet = /* @__PURE__ */ new Set();
2335
+ let redactionPattern = void 0;
2336
+ return {
2337
+ format: winston.format((info) => {
2338
+ if (redactionPattern && typeof info.message === "string") {
2339
+ info.message = info.message.replace(redactionPattern, "[REDACTED]");
2340
+ }
2341
+ if (redactionPattern && typeof info.stack === "string") {
2342
+ info.stack = info.stack.replace(redactionPattern, "[REDACTED]");
2343
+ }
2344
+ return info;
2345
+ })(),
2346
+ add(newRedactions) {
2347
+ let added = 0;
2348
+ for (const redactionToTrim of newRedactions) {
2349
+ const redaction = redactionToTrim.trim();
2350
+ if (redaction.length <= 1) {
2351
+ continue;
2352
+ }
2353
+ if (!redactionSet.has(redaction)) {
2354
+ redactionSet.add(redaction);
2355
+ added += 1;
2356
+ }
2357
+ }
2358
+ if (added > 0) {
2359
+ const redactions = Array.from(redactionSet).map((r) => escapeRegExp(r)).join("|");
2360
+ redactionPattern = new RegExp(`(${redactions})`, "g");
2361
+ }
2362
+ }
2363
+ };
2364
+ }
2365
+ /**
2366
+ * Creates a pretty printed winston log formatter.
2367
+ */
2368
+ static colorFormat() {
2369
+ const colorizer = winston.format.colorize();
2370
+ return winston.format.combine(
2371
+ winston.format.timestamp(),
2372
+ winston.format.colorize({
2373
+ colors: {
2374
+ timestamp: "dim",
2375
+ prefix: "blue",
2376
+ field: "cyan",
2377
+ debug: "grey"
2378
+ }
2379
+ }),
2380
+ winston.format.printf((info) => {
2381
+ const { timestamp, level, message, plugin, service, ...fields } = info;
2382
+ const prefix = plugin || service;
2383
+ const timestampColor = colorizer.colorize("timestamp", timestamp);
2384
+ const prefixColor = colorizer.colorize("prefix", prefix);
2385
+ const extraFields = Object.entries(fields).map(
2386
+ ([key, value]) => `${colorizer.colorize("field", `${key}`)}=${value}`
2387
+ ).join(" ");
2388
+ return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
2389
+ })
2390
+ );
2391
+ }
2392
+ error(message, meta) {
2393
+ __privateGet(this, _winston).error(message, meta);
2394
+ }
2395
+ warn(message, meta) {
2396
+ __privateGet(this, _winston).warn(message, meta);
2397
+ }
2398
+ info(message, meta) {
2399
+ __privateGet(this, _winston).info(message, meta);
2400
+ }
2401
+ debug(message, meta) {
2402
+ __privateGet(this, _winston).debug(message, meta);
2403
+ }
2404
+ child(meta) {
2405
+ return new _WinstonLogger(__privateGet(this, _winston).child(meta));
2406
+ }
2407
+ addRedactions(redactions) {
2408
+ var _a;
2409
+ (_a = __privateGet(this, _addRedactions)) == null ? void 0 : _a.call(this, redactions);
2410
+ }
2411
+ };
2412
+ _winston = new WeakMap();
2413
+ _addRedactions = new WeakMap();
2414
+ let WinstonLogger = _WinstonLogger;
2415
+
2272
2416
  var __defProp$1 = Object.defineProperty;
2273
2417
  var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
2274
2418
  var __publicField$1 = (obj, key, value) => {
@@ -2282,23 +2426,33 @@ const createStepLogger = ({
2282
2426
  task,
2283
2427
  step
2284
2428
  }) => {
2285
- const metadata = { stepId: step.id };
2286
- const taskLogger = winston__namespace.createLogger({
2429
+ var _a;
2430
+ const stepLogStream = new stream.PassThrough();
2431
+ stepLogStream.on("data", async (data) => {
2432
+ const message = data.toString().trim();
2433
+ if ((message == null ? void 0 : message.length) > 1) {
2434
+ await task.emitLog(message, { stepId: step.id });
2435
+ }
2436
+ });
2437
+ const taskLogger = WinstonLogger.create({
2287
2438
  level: process.env.LOG_LEVEL || "info",
2288
2439
  format: winston__namespace.format.combine(
2289
2440
  winston__namespace.format.colorize(),
2290
2441
  winston__namespace.format.simple()
2291
2442
  ),
2292
- defaultMeta: {}
2443
+ transports: [
2444
+ new winston__namespace.transports.Console(),
2445
+ new winston__namespace.transports.Stream({ stream: stepLogStream })
2446
+ ]
2293
2447
  });
2448
+ taskLogger.addRedactions(Object.values((_a = task.secrets) != null ? _a : {}));
2294
2449
  const streamLogger = new stream.PassThrough();
2295
2450
  streamLogger.on("data", async (data) => {
2296
2451
  const message = data.toString().trim();
2297
2452
  if ((message == null ? void 0 : message.length) > 1) {
2298
- await task.emitLog(message, metadata);
2453
+ taskLogger.info(message);
2299
2454
  }
2300
2455
  });
2301
- taskLogger.add(new winston__namespace.transports.Stream({ stream: streamLogger }));
2302
2456
  return { taskLogger, streamLogger };
2303
2457
  };
2304
2458
  const isActionAuthorized = pluginPermissionNode.createConditionAuthorizer(
@@ -2472,7 +2626,8 @@ class NunjucksWorkflowRunner {
2472
2626
  await action.handler({
2473
2627
  input: iteration.input,
2474
2628
  secrets: (_f = task.secrets) != null ? _f : {},
2475
- logger: taskLogger,
2629
+ // TODO(blam): move to LoggerService and away from Winston
2630
+ logger: backendCommon.loggerToWinstonLogger(taskLogger),
2476
2631
  logStream: streamLogger,
2477
2632
  workspacePath,
2478
2633
  async checkpoint(keySuffix, fn) {
@@ -3218,12 +3373,12 @@ async function createRouter(options) {
3218
3373
  const { kind, namespace, name } = catalogModel.parseEntityRef(templateRef, {
3219
3374
  defaultKind: "template"
3220
3375
  });
3221
- const credentials = await httpAuth.credentials(req, { allow: ["user"] });
3376
+ const credentials = await httpAuth.credentials(req);
3222
3377
  const { token } = await auth.getPluginRequestToken({
3223
3378
  onBehalfOf: credentials,
3224
3379
  targetPluginId: "catalog"
3225
3380
  });
3226
- const userEntityRef = credentials.principal.userEntityRef;
3381
+ const userEntityRef = auth.isPrincipal(credentials, "user") ? credentials.principal.userEntityRef : void 0;
3227
3382
  const userEntity = userEntityRef ? await catalogClient.getEntityByRef(userEntityRef, { token }) : void 0;
3228
3383
  let auditLog = `Scaffolding task for ${templateRef}`;
3229
3384
  if (userEntityRef) {
@@ -3490,4 +3645,4 @@ exports.createRouter = createRouter;
3490
3645
  exports.createWaitAction = createWaitAction;
3491
3646
  exports.scaffolderActionRules = scaffolderActionRules;
3492
3647
  exports.scaffolderTemplateRules = scaffolderTemplateRules;
3493
- //# sourceMappingURL=router-1665319e.cjs.js.map
3648
+ //# sourceMappingURL=router-77345506.cjs.js.map