@backstage/plugin-scaffolder-backend 0.15.19 → 0.15.22

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,68 @@
1
1
  # @backstage/plugin-scaffolder-backend
2
2
 
3
+ ## 0.15.22
4
+
5
+ ### Patch Changes
6
+
7
+ - b09dd8f43b: chore(deps): bump `@gitbeaker/node` from 34.6.0 to 35.1.0
8
+ - ac2f1eeec0: This change is for adding the option of inputs on the `github:actions:dispatch` Backstage Action. This will allow users to pass data from Backstage to the GitHub Action.
9
+ - 0d5e846a78: Expose a new option to provide additional template filters via `@backstage/scaffolder-backend`'s `createRouter()` function.
10
+ - Updated dependencies
11
+ - @backstage/plugin-catalog-backend@0.21.1
12
+ - @backstage/backend-common@0.10.5
13
+
14
+ ## 0.15.21
15
+
16
+ ### Patch Changes
17
+
18
+ - b05d303226: Added the ability to support supplying secrets when creating tasks in the `scaffolder-backend`.
19
+
20
+ **deprecation**: Deprecated `ctx.token` from actions in the `scaffolder-backend`. Please move to using `ctx.secrets.backstageToken` instead.
21
+
22
+ **deprecation**: Deprecated `task.token` in `TaskSpec` in the `scaffolder-backend`. Please move to using `task.secrets.backstageToken` instead.
23
+
24
+ - Updated dependencies
25
+ - @backstage/plugin-catalog-backend@0.21.0
26
+ - @backstage/integration@0.7.2
27
+ - @backstage/backend-common@0.10.4
28
+ - @backstage/config@0.1.13
29
+ - @backstage/catalog-model@0.9.10
30
+ - @backstage/catalog-client@0.5.5
31
+ - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.9
32
+ - @backstage/plugin-scaffolder-common@0.1.3
33
+
34
+ ## 0.15.21-next.0
35
+
36
+ ### Patch Changes
37
+
38
+ - Updated dependencies
39
+ - @backstage/plugin-catalog-backend@0.21.0-next.0
40
+ - @backstage/backend-common@0.10.4-next.0
41
+ - @backstage/config@0.1.13-next.0
42
+ - @backstage/catalog-model@0.9.10-next.0
43
+ - @backstage/catalog-client@0.5.5-next.0
44
+ - @backstage/integration@0.7.2-next.0
45
+ - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.9-next.0
46
+ - @backstage/plugin-scaffolder-common@0.1.3-next.0
47
+
48
+ ## 0.15.20
49
+
50
+ ### Patch Changes
51
+
52
+ - 9fbd3b90ae: fix: Register plugin to prioritise Component kind for entityRef
53
+ - 451ef0aa07: Fix token pass-through for software templates using beta 3 version
54
+ - 5333451def: Cleaned up API exports
55
+ - 3b4d8caff6: Allow a GitHubCredentialsProvider to be passed to the GitHub scaffolder tasks actions.
56
+ - Updated dependencies
57
+ - @backstage/config@0.1.12
58
+ - @backstage/integration@0.7.1
59
+ - @backstage/backend-common@0.10.3
60
+ - @backstage/plugin-catalog-backend@0.20.0
61
+ - @backstage/errors@0.2.0
62
+ - @backstage/catalog-client@0.5.4
63
+ - @backstage/catalog-model@0.9.9
64
+ - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.8
65
+
3
66
  ## 0.15.19
4
67
 
5
68
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -7,6 +7,7 @@ var catalogModel = require('@backstage/catalog-model');
7
7
  var fs = require('fs-extra');
8
8
  var yaml = require('yaml');
9
9
  var backendCommon = require('@backstage/backend-common');
10
+ var integration = require('@backstage/integration');
10
11
  var path = require('path');
11
12
  var globby = require('globby');
12
13
  var isbinaryfile = require('isbinaryfile');
@@ -16,7 +17,6 @@ var child_process = require('child_process');
16
17
  var stream = require('stream');
17
18
  var azureDevopsNodeApi = require('azure-devops-node-api');
18
19
  var fetch = require('node-fetch');
19
- var integration = require('@backstage/integration');
20
20
  var rest = require('@octokit/rest');
21
21
  var lodash = require('lodash');
22
22
  var octokitPluginCreatePullRequest = require('octokit-plugin-create-pull-request');
@@ -119,7 +119,7 @@ function createCatalogRegisterAction(options) {
119
119
  }
120
120
  },
121
121
  async handler(ctx) {
122
- var _a;
122
+ var _a, _b;
123
123
  const { input } = ctx;
124
124
  let catalogInfoUrl;
125
125
  if ("catalogInfoUrl" in input) {
@@ -139,16 +139,23 @@ function createCatalogRegisterAction(options) {
139
139
  await catalogClient.addLocation({
140
140
  type: "url",
141
141
  target: catalogInfoUrl
142
- }, ctx.token ? { token: ctx.token } : {});
142
+ }, ((_a = ctx.secrets) == null ? void 0 : _a.backstageToken) ? { token: ctx.secrets.backstageToken } : {});
143
143
  try {
144
144
  const result = await catalogClient.addLocation({
145
145
  dryRun: true,
146
146
  type: "url",
147
147
  target: catalogInfoUrl
148
- }, ctx.token ? { token: ctx.token } : {});
148
+ }, ((_b = ctx.secrets) == null ? void 0 : _b.backstageToken) ? { token: ctx.secrets.backstageToken } : {});
149
149
  if (result.entities.length > 0) {
150
150
  const { entities } = result;
151
- const entity = (_a = entities.find((e) => !e.metadata.name.startsWith("generated-"))) != null ? _a : entities[0];
151
+ let entity;
152
+ entity = entities.find((e) => !e.metadata.name.startsWith("generated-") && e.kind === "Component");
153
+ if (!entity) {
154
+ entity = entities.find((e) => !e.metadata.name.startsWith("generated-"));
155
+ }
156
+ if (!entity) {
157
+ entity = entities[0];
158
+ }
152
159
  ctx.output("entityRef", catalogModel.stringifyEntityRef(entity));
153
160
  }
154
161
  } catch (e) {
@@ -354,6 +361,12 @@ const { render, renderCompat } = (() => {
354
361
  });
355
362
  }
356
363
 
364
+ if (typeof additionalTemplateFilters !== 'undefined') {
365
+ for (const [filterName, filterFn] of Object.entries(additionalTemplateFilters)) {
366
+ env.addFilter(filterName, (...args) => JSON.parse(filterFn(...args)));
367
+ }
368
+ }
369
+
357
370
  let uninstallCompat = undefined;
358
371
 
359
372
  function render(str, values) {
@@ -386,12 +399,16 @@ const { render, renderCompat } = (() => {
386
399
  `;
387
400
  class SecureTemplater {
388
401
  static async loadRenderer(options = {}) {
389
- const { parseRepoUrl, cookiecutterCompat } = options;
390
- let sandbox = void 0;
402
+ const { parseRepoUrl, cookiecutterCompat, additionalTemplateFilters } = options;
403
+ const sandbox = {};
391
404
  if (parseRepoUrl) {
392
- sandbox = {
393
- parseRepoUrl: (url) => JSON.stringify(parseRepoUrl(url))
394
- };
405
+ sandbox.parseRepoUrl = (url) => JSON.stringify(parseRepoUrl(url));
406
+ }
407
+ if (additionalTemplateFilters) {
408
+ sandbox.additionalTemplateFilters = Object.fromEntries(Object.entries(additionalTemplateFilters).filter(([_, filterFunction]) => !!filterFunction).map(([filterName, filterFunction]) => [
409
+ filterName,
410
+ (...args) => JSON.stringify(filterFunction(...args))
411
+ ]));
395
412
  }
396
413
  const vm = new vm2.VM({ sandbox });
397
414
  const nunjucksSource = await fs__default["default"].readFile(backendCommon.resolvePackagePath("@backstage/plugin-scaffolder-backend", "assets/nunjucks.js.txt"), "utf-8");
@@ -412,7 +429,7 @@ class SecureTemplater {
412
429
  }
413
430
 
414
431
  function createFetchTemplateAction(options) {
415
- const { reader, integrations } = options;
432
+ const { reader, integrations, additionalTemplateFilters } = options;
416
433
  return createTemplateAction({
417
434
  id: "fetch:template",
418
435
  description: "Downloads a skeleton, templates variables into file and directory names and content, and places the result in the workspace, or optionally in a subdirectory specified by the 'targetPath' input option.",
@@ -503,7 +520,8 @@ function createFetchTemplateAction(options) {
503
520
  };
504
521
  ctx.logger.info(`Processing ${allEntriesInTemplate.length} template files/directories with input values`, ctx.input.values);
505
522
  const renderTemplate = await SecureTemplater.loadRenderer({
506
- cookiecutterCompat: ctx.input.cookiecutterCompat
523
+ cookiecutterCompat: ctx.input.cookiecutterCompat,
524
+ additionalTemplateFilters
507
525
  });
508
526
  for (const location of allEntriesInTemplate) {
509
527
  let renderFilename;
@@ -1150,12 +1168,9 @@ function createPublishFileAction() {
1150
1168
  }
1151
1169
 
1152
1170
  class OctokitProvider {
1153
- constructor(integrations) {
1171
+ constructor(integrations, githubCredentialsProvider) {
1154
1172
  this.integrations = integrations;
1155
- this.credentialsProviders = new Map(integrations.github.list().map((integration$1) => {
1156
- const provider = integration.SingleInstanceGithubCredentialsProvider.create(integration$1.config);
1157
- return [integration$1.config.host, provider];
1158
- }));
1173
+ this.githubCredentialsProvider = githubCredentialsProvider || integration.DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
1159
1174
  }
1160
1175
  async getOctokit(repoUrl) {
1161
1176
  var _a;
@@ -1167,11 +1182,7 @@ class OctokitProvider {
1167
1182
  if (!integrationConfig) {
1168
1183
  throw new errors.InputError(`No integration for host ${host}`);
1169
1184
  }
1170
- const credentialsProvider = this.credentialsProviders.get(host);
1171
- if (!credentialsProvider) {
1172
- throw new errors.InputError(`No matching credentials for host ${host}, please check your integrations config`);
1173
- }
1174
- const { token } = await credentialsProvider.getCredentials({
1185
+ const { token } = await this.githubCredentialsProvider.getCredentials({
1175
1186
  url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
1176
1187
  });
1177
1188
  if (!token) {
@@ -1187,8 +1198,8 @@ class OctokitProvider {
1187
1198
  }
1188
1199
 
1189
1200
  function createPublishGithubAction(options) {
1190
- const { integrations, config } = options;
1191
- const octokitProvider = new OctokitProvider(integrations);
1201
+ const { integrations, config, githubCredentialsProvider } = options;
1202
+ const octokitProvider = new OctokitProvider(integrations, githubCredentialsProvider || integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations));
1192
1203
  return createTemplateAction({
1193
1204
  id: "publish:github",
1194
1205
  description: "Initializes a git repository of contents in workspace and publishes it to GitHub.",
@@ -1388,6 +1399,7 @@ class GithubResponseError extends errors.CustomErrorBase {
1388
1399
  }
1389
1400
  const defaultClientFactory = async ({
1390
1401
  integrations,
1402
+ githubCredentialsProvider,
1391
1403
  owner,
1392
1404
  repo,
1393
1405
  host = "github.com"
@@ -1397,10 +1409,7 @@ const defaultClientFactory = async ({
1397
1409
  if (!integrationConfig) {
1398
1410
  throw new errors.InputError(`No integration for host ${host}`);
1399
1411
  }
1400
- const credentialsProvider = integration.SingleInstanceGithubCredentialsProvider.create(integrationConfig);
1401
- if (!credentialsProvider) {
1402
- throw new errors.InputError(`No matching credentials for host ${host}, please check your integrations config`);
1403
- }
1412
+ const credentialsProvider = githubCredentialsProvider || integration.SingleInstanceGithubCredentialsProvider.create(integrationConfig);
1404
1413
  const { token } = await credentialsProvider.getCredentials({
1405
1414
  url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
1406
1415
  });
@@ -1415,6 +1424,7 @@ const defaultClientFactory = async ({
1415
1424
  };
1416
1425
  const createPublishGithubPullRequestAction = ({
1417
1426
  integrations,
1427
+ githubCredentialsProvider,
1418
1428
  clientFactory = defaultClientFactory
1419
1429
  }) => {
1420
1430
  return createTemplateAction({
@@ -1481,7 +1491,13 @@ const createPublishGithubPullRequestAction = ({
1481
1491
  if (!owner) {
1482
1492
  throw new errors.InputError(`No owner provided for host: ${host}, and repo ${repo}`);
1483
1493
  }
1484
- const client = await clientFactory({ integrations, host, owner, repo });
1494
+ const client = await clientFactory({
1495
+ integrations,
1496
+ githubCredentialsProvider,
1497
+ host,
1498
+ owner,
1499
+ repo
1500
+ });
1485
1501
  const fileRoot = sourcePath ? backendCommon.resolveSafeChildPath(ctx.workspacePath, sourcePath) : ctx.workspacePath;
1486
1502
  const localFilePaths = await globby__default["default"](["./**", "./**/.*", "!.git"], {
1487
1503
  cwd: fileRoot,
@@ -1743,8 +1759,8 @@ const createPublishGitlabMergeRequestAction = (options) => {
1743
1759
  };
1744
1760
 
1745
1761
  function createGithubActionsDispatchAction(options) {
1746
- const { integrations } = options;
1747
- const octokitProvider = new OctokitProvider(integrations);
1762
+ const { integrations, githubCredentialsProvider } = options;
1763
+ const octokitProvider = new OctokitProvider(integrations, githubCredentialsProvider || integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations));
1748
1764
  return createTemplateAction({
1749
1765
  id: "github:actions:dispatch",
1750
1766
  description: "Dispatches a GitHub Action workflow for a given branch or tag",
@@ -1767,19 +1783,25 @@ function createGithubActionsDispatchAction(options) {
1767
1783
  title: "Branch or Tag name",
1768
1784
  description: "The git branch or tag name used to dispatch the workflow",
1769
1785
  type: "string"
1786
+ },
1787
+ workflowInputs: {
1788
+ title: "Workflow Inputs",
1789
+ description: "Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10. ",
1790
+ type: "object"
1770
1791
  }
1771
1792
  }
1772
1793
  }
1773
1794
  },
1774
1795
  async handler(ctx) {
1775
- const { repoUrl, workflowId, branchOrTagName } = ctx.input;
1796
+ const { repoUrl, workflowId, branchOrTagName, workflowInputs } = ctx.input;
1776
1797
  ctx.logger.info(`Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`);
1777
1798
  const { client, owner, repo } = await octokitProvider.getOctokit(repoUrl);
1778
1799
  await client.rest.actions.createWorkflowDispatch({
1779
1800
  owner,
1780
1801
  repo,
1781
1802
  workflow_id: workflowId,
1782
- ref: branchOrTagName
1803
+ ref: branchOrTagName,
1804
+ inputs: workflowInputs
1783
1805
  });
1784
1806
  ctx.logger.info(`Workflow ${workflowId} dispatched successfully`);
1785
1807
  }
@@ -1787,8 +1809,8 @@ function createGithubActionsDispatchAction(options) {
1787
1809
  }
1788
1810
 
1789
1811
  function createGithubWebhookAction(options) {
1790
- const { integrations, defaultWebhookSecret } = options;
1791
- const octokitProvider = new OctokitProvider(integrations);
1812
+ const { integrations, defaultWebhookSecret, githubCredentialsProvider } = options;
1813
+ const octokitProvider = new OctokitProvider(integrations, githubCredentialsProvider != null ? githubCredentialsProvider : integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations));
1792
1814
  const eventNames = webhooks.emitterEventNames.filter((event) => !event.includes("."));
1793
1815
  return createTemplateAction({
1794
1816
  id: "github:webhook",
@@ -1887,7 +1909,15 @@ function createGithubWebhookAction(options) {
1887
1909
  }
1888
1910
 
1889
1911
  const createBuiltinActions = (options) => {
1890
- const { reader, integrations, containerRunner, catalogClient, config } = options;
1912
+ const {
1913
+ reader,
1914
+ integrations,
1915
+ containerRunner,
1916
+ catalogClient,
1917
+ config,
1918
+ additionalTemplateFilters
1919
+ } = options;
1920
+ const githubCredentialsProvider = integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
1891
1921
  const actions = [
1892
1922
  createFetchPlainAction({
1893
1923
  reader,
@@ -1895,14 +1925,17 @@ const createBuiltinActions = (options) => {
1895
1925
  }),
1896
1926
  createFetchTemplateAction({
1897
1927
  integrations,
1898
- reader
1928
+ reader,
1929
+ additionalTemplateFilters
1899
1930
  }),
1900
1931
  createPublishGithubAction({
1901
1932
  integrations,
1902
- config
1933
+ config,
1934
+ githubCredentialsProvider
1903
1935
  }),
1904
1936
  createPublishGithubPullRequestAction({
1905
- integrations
1937
+ integrations,
1938
+ githubCredentialsProvider
1906
1939
  }),
1907
1940
  createPublishGitlabAction({
1908
1941
  integrations,
@@ -1925,10 +1958,12 @@ const createBuiltinActions = (options) => {
1925
1958
  createFilesystemDeleteAction(),
1926
1959
  createFilesystemRenameAction(),
1927
1960
  createGithubActionsDispatchAction({
1928
- integrations
1961
+ integrations,
1962
+ githubCredentialsProvider
1929
1963
  }),
1930
1964
  createGithubWebhookAction({
1931
- integrations
1965
+ integrations,
1966
+ githubCredentialsProvider
1932
1967
  })
1933
1968
  ];
1934
1969
  if (containerRunner) {
@@ -2014,7 +2049,8 @@ class DatabaseTaskStore {
2014
2049
  }
2015
2050
  const updateCount = await tx("tasks").where({ id: task.id, status: "open" }).update({
2016
2051
  status: "processing",
2017
- last_heartbeat_at: this.db.fn.now()
2052
+ last_heartbeat_at: this.db.fn.now(),
2053
+ secrets: null
2018
2054
  });
2019
2055
  if (updateCount < 1) {
2020
2056
  return void 0;
@@ -2078,8 +2114,7 @@ class DatabaseTaskStore {
2078
2114
  id: taskId,
2079
2115
  status: oldStatus
2080
2116
  }).update({
2081
- status,
2082
- secrets: null
2117
+ status
2083
2118
  });
2084
2119
  if (updateCount !== 1) {
2085
2120
  throw new errors.ConflictError(`Failed to update status to '${status}' for taskId ${taskId}`);
@@ -2292,7 +2327,7 @@ class HandlebarsWorkflowRunner {
2292
2327
  this.handlebars.registerHelper("eq", (a, b) => a === b);
2293
2328
  }
2294
2329
  async execute(task) {
2295
- var _a, _b;
2330
+ var _a, _b, _c;
2296
2331
  if (!isValidTaskSpec$1(task.spec)) {
2297
2332
  throw new errors.InputError(`Task spec is not a valid v1beta2 task spec`);
2298
2333
  }
@@ -2396,6 +2431,7 @@ class HandlebarsWorkflowRunner {
2396
2431
  logStream: stream$1,
2397
2432
  input,
2398
2433
  token: (_b = task.secrets) == null ? void 0 : _b.token,
2434
+ secrets: (_c = task.secrets) != null ? _c : {},
2399
2435
  workspacePath,
2400
2436
  async createTemporaryDirectory() {
2401
2437
  const tmpDir = await fs__default["default"].mkdtemp(`${workspacePath}_step-${step.id}-`);
@@ -2521,7 +2557,7 @@ class NunjucksWorkflowRunner {
2521
2557
  });
2522
2558
  }
2523
2559
  async execute(task) {
2524
- var _a, _b;
2560
+ var _a, _b, _c, _d;
2525
2561
  if (!isValidTaskSpec(task.spec)) {
2526
2562
  throw new errors.InputError("Wrong template version executed with the workflow engine");
2527
2563
  }
@@ -2530,7 +2566,8 @@ class NunjucksWorkflowRunner {
2530
2566
  const renderTemplate = await SecureTemplater.loadRenderer({
2531
2567
  parseRepoUrl(url) {
2532
2568
  return parseRepoUrl(url, integrations);
2533
- }
2569
+ },
2570
+ additionalTemplateFilters: this.options.additionalTemplateFilters
2534
2571
  });
2535
2572
  try {
2536
2573
  await fs__default["default"].ensureDir(workspacePath);
@@ -2570,6 +2607,8 @@ class NunjucksWorkflowRunner {
2570
2607
  await action.handler({
2571
2608
  baseUrl: task.spec.baseUrl,
2572
2609
  input,
2610
+ token: (_c = task.secrets) == null ? void 0 : _c.token,
2611
+ secrets: (_d = task.secrets) != null ? _d : {},
2573
2612
  logger: taskLogger,
2574
2613
  logStream: streamLogger,
2575
2614
  workspacePath,
@@ -2619,7 +2658,8 @@ class TaskWorker {
2619
2658
  logger,
2620
2659
  actionRegistry,
2621
2660
  integrations,
2622
- workingDirectory
2661
+ workingDirectory,
2662
+ additionalTemplateFilters
2623
2663
  } = options;
2624
2664
  const legacyWorkflowRunner = new HandlebarsWorkflowRunner({
2625
2665
  logger,
@@ -2631,7 +2671,8 @@ class TaskWorker {
2631
2671
  actionRegistry,
2632
2672
  integrations,
2633
2673
  logger,
2634
- workingDirectory
2674
+ workingDirectory,
2675
+ additionalTemplateFilters
2635
2676
  });
2636
2677
  return new TaskWorker({
2637
2678
  taskBroker,
@@ -2728,7 +2769,8 @@ async function createRouter(options) {
2728
2769
  catalogClient,
2729
2770
  actions,
2730
2771
  containerRunner,
2731
- taskWorkers
2772
+ taskWorkers,
2773
+ additionalTemplateFilters
2732
2774
  } = options;
2733
2775
  const logger = parentLogger.child({ plugin: "scaffolder" });
2734
2776
  const workingDirectory = await getWorkingDirectory(config, logger);
@@ -2751,7 +2793,8 @@ async function createRouter(options) {
2751
2793
  actionRegistry,
2752
2794
  integrations,
2753
2795
  logger,
2754
- workingDirectory
2796
+ workingDirectory,
2797
+ additionalTemplateFilters
2755
2798
  });
2756
2799
  workers.push(worker);
2757
2800
  }
@@ -2760,7 +2803,8 @@ async function createRouter(options) {
2760
2803
  catalogClient,
2761
2804
  containerRunner,
2762
2805
  reader,
2763
- config
2806
+ config,
2807
+ additionalTemplateFilters
2764
2808
  });
2765
2809
  actionsToRegister.forEach((action) => actionRegistry.register(action));
2766
2810
  workers.forEach((worker) => worker.start());
@@ -2851,6 +2895,8 @@ async function createRouter(options) {
2851
2895
  throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
2852
2896
  }
2853
2897
  const result = await taskBroker.dispatch(taskSpec, {
2898
+ ...req.body.secrets,
2899
+ backstageToken: token,
2854
2900
  token
2855
2901
  });
2856
2902
  res.status(201).json({ id: result.taskId });