@backstage/plugin-scaffolder-backend 1.2.0-next.0 → 1.3.0-next.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,74 @@
1
1
  # @backstage/plugin-scaffolder-backend
2
2
 
3
+ ## 1.3.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 72dfcbc8bf: A new scaffolder action has been added: `gerrit:publish`
8
+
9
+ ### Patch Changes
10
+
11
+ - 6901f6be4a: Adds more of an explanation when the `publish:github` scaffolder action fails to create a repository.
12
+ - Updated dependencies
13
+ - @backstage/backend-common@0.13.6-next.0
14
+ - @backstage/integration@1.2.1-next.0
15
+ - @backstage/plugin-catalog-backend@1.2.0-next.0
16
+
17
+ ## 1.2.0
18
+
19
+ ### Minor Changes
20
+
21
+ - 9818112d12: Update the `github:publish` action to allow passing required status check
22
+ contexts before merging to the main branch.
23
+ - f8baf7df44: Added the ability to reference the user in the `template.yaml` manifest
24
+ - 8d5a2238a9: Split `publish:bitbucket` into `publish:bitbucketCloud` and `publish:bitbucketServer`.
25
+
26
+ In order to migrate from the deprecated action, you need to replace the use of action
27
+ `publish:bitbucket` in your templates with the use of either `publish:bitbucketCloud`
28
+ or `publish:bitbucketServer` - depending on which destination SCM provider you use.
29
+
30
+ Additionally, these actions will not utilize `integrations.bitbucket` anymore,
31
+ but `integrations.bitbucketCloud` or `integrations.bitbucketServer` respectively.
32
+ You may or may not have migrated to these already.
33
+
34
+ As described in a previous changeset, using these two replacement integrations configs
35
+ will not compromise use cases which still rely on `integrations.bitbucket` as this was
36
+ set up in a backwards compatible way.
37
+
38
+ Additionally, please mind that the option `enableLFS` is only available (and always was)
39
+ for Bitbucket Server use cases and therefore, is not even part of the schema for
40
+ `publish:bitbucketCloud` anymore.
41
+
42
+ ### Patch Changes
43
+
44
+ - 0fc65cbf89: Override default commit message and author details in GitHub, Azure, bitbucket
45
+ - cfc0f19699: Updated dependency `fs-extra` to `10.1.0`.
46
+ - Updated dependencies
47
+ - @backstage/backend-common@0.13.3
48
+ - @backstage/plugin-catalog-backend@1.1.2
49
+ - @backstage/integration@1.2.0
50
+ - @backstage/plugin-scaffolder-common@1.1.0
51
+ - @backstage/config@1.0.1
52
+ - @backstage/catalog-client@1.0.2
53
+ - @backstage/catalog-model@1.0.2
54
+
55
+ ## 1.2.0-next.1
56
+
57
+ ### Minor Changes
58
+
59
+ - f8baf7df44: Added the ability to reference the user in the `template.yaml` manifest
60
+
61
+ ### Patch Changes
62
+
63
+ - Updated dependencies
64
+ - @backstage/backend-common@0.13.3-next.2
65
+ - @backstage/plugin-catalog-backend@1.1.2-next.2
66
+ - @backstage/plugin-scaffolder-common@1.1.0-next.0
67
+ - @backstage/config@1.0.1-next.0
68
+ - @backstage/catalog-model@1.0.2-next.0
69
+ - @backstage/integration@1.2.0-next.1
70
+ - @backstage/catalog-client@1.0.2-next.0
71
+
3
72
  ## 1.2.0-next.0
4
73
 
5
74
  ### Minor Changes
package/dist/index.cjs.js CHANGED
@@ -16,6 +16,7 @@ var child_process = require('child_process');
16
16
  var stream = require('stream');
17
17
  var azureDevopsNodeApi = require('azure-devops-node-api');
18
18
  var fetch = require('node-fetch');
19
+ var crypto = require('crypto');
19
20
  var octokit = require('octokit');
20
21
  var lodash = require('lodash');
21
22
  var octokitPluginCreatePullRequest = require('octokit-plugin-create-pull-request');
@@ -58,6 +59,7 @@ var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
58
59
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
59
60
  var globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
60
61
  var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
62
+ var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
61
63
  var ObservableImpl__default = /*#__PURE__*/_interopDefaultLegacy(ObservableImpl);
62
64
  var winston__namespace = /*#__PURE__*/_interopNamespace(winston);
63
65
  var nunjucks__default = /*#__PURE__*/_interopDefaultLegacy(nunjucks);
@@ -1578,6 +1580,138 @@ function createPublishFileAction() {
1578
1580
  });
1579
1581
  }
1580
1582
 
1583
+ const createGerritProject = async (config, options) => {
1584
+ const { projectName, parent, owner, description } = options;
1585
+ const fetchOptions = {
1586
+ method: "PUT",
1587
+ body: JSON.stringify({
1588
+ parent,
1589
+ description,
1590
+ owners: [owner],
1591
+ create_empty_commit: false
1592
+ }),
1593
+ headers: {
1594
+ ...integration.getGerritRequestOptions(config).headers,
1595
+ "Content-Type": "application/json"
1596
+ }
1597
+ };
1598
+ const response = await fetch__default["default"](`${config.baseUrl}/a/projects/${encodeURIComponent(projectName)}`, fetchOptions);
1599
+ if (response.status !== 201) {
1600
+ throw new Error(`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`);
1601
+ }
1602
+ };
1603
+ const generateCommitMessage = (config, commitSubject) => {
1604
+ const changeId = crypto__default["default"].randomBytes(20).toString("hex");
1605
+ const msg = `${config.getOptionalString("scaffolder.defaultCommitMessage") || commitSubject}
1606
+
1607
+ Change-Id: I${changeId}`;
1608
+ return msg;
1609
+ };
1610
+ function createPublishGerritAction(options) {
1611
+ const { integrations, config } = options;
1612
+ return createTemplateAction({
1613
+ id: "publish:gerrit",
1614
+ description: "Initializes a git repository of the content in the workspace, and publishes it to Gerrit.",
1615
+ schema: {
1616
+ input: {
1617
+ type: "object",
1618
+ required: ["repoUrl"],
1619
+ properties: {
1620
+ repoUrl: {
1621
+ title: "Repository Location",
1622
+ type: "string"
1623
+ },
1624
+ description: {
1625
+ title: "Repository Description",
1626
+ type: "string"
1627
+ },
1628
+ defaultBranch: {
1629
+ title: "Default Branch",
1630
+ type: "string",
1631
+ description: `Sets the default branch on the repository. The default value is 'master'`
1632
+ },
1633
+ gitCommitMessage: {
1634
+ title: "Git Commit Message",
1635
+ type: "string",
1636
+ description: `Sets the commit message on the repository. The default value is 'initial commit'`
1637
+ },
1638
+ gitAuthorName: {
1639
+ title: "Default Author Name",
1640
+ type: "string",
1641
+ description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
1642
+ },
1643
+ gitAuthorEmail: {
1644
+ title: "Default Author Email",
1645
+ type: "string",
1646
+ description: `Sets the default author email for the commit.`
1647
+ }
1648
+ }
1649
+ },
1650
+ output: {
1651
+ type: "object",
1652
+ properties: {
1653
+ remoteUrl: {
1654
+ title: "A URL to the repository with the provider",
1655
+ type: "string"
1656
+ },
1657
+ repoContentsUrl: {
1658
+ title: "A URL to the root of the repository",
1659
+ type: "string"
1660
+ }
1661
+ }
1662
+ }
1663
+ },
1664
+ async handler(ctx) {
1665
+ const {
1666
+ repoUrl,
1667
+ description,
1668
+ defaultBranch = "master",
1669
+ gitAuthorName,
1670
+ gitAuthorEmail,
1671
+ gitCommitMessage = "initial commit"
1672
+ } = ctx.input;
1673
+ const { repo, host, owner, workspace } = parseRepoUrl(repoUrl, integrations);
1674
+ const integrationConfig = integrations.gerrit.byHost(host);
1675
+ if (!integrationConfig) {
1676
+ throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
1677
+ }
1678
+ if (!owner) {
1679
+ throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing owner`);
1680
+ }
1681
+ if (!workspace) {
1682
+ throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`);
1683
+ }
1684
+ await createGerritProject(integrationConfig.config, {
1685
+ description,
1686
+ owner,
1687
+ projectName: repo,
1688
+ parent: workspace
1689
+ });
1690
+ const auth = {
1691
+ username: integrationConfig.config.username,
1692
+ password: integrationConfig.config.password
1693
+ };
1694
+ const gitAuthorInfo = {
1695
+ name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
1696
+ email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
1697
+ };
1698
+ const remoteUrl = `${integrationConfig.config.cloneUrl}/a/${repo}`;
1699
+ await initRepoAndPush({
1700
+ dir: getRepoSourceDirectory(ctx.workspacePath, void 0),
1701
+ remoteUrl,
1702
+ auth,
1703
+ defaultBranch,
1704
+ logger: ctx.logger,
1705
+ commitMessage: generateCommitMessage(config, gitCommitMessage),
1706
+ gitAuthorInfo
1707
+ });
1708
+ const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${defaultBranch}`;
1709
+ ctx.output("remoteUrl", remoteUrl);
1710
+ ctx.output("repoContentsUrl", repoContentsUrl);
1711
+ }
1712
+ });
1713
+ }
1714
+
1581
1715
  const DEFAULT_TIMEOUT_MS = 6e4;
1582
1716
  async function getOctokitOptions(options) {
1583
1717
  var _a;
@@ -1803,7 +1937,16 @@ function createPublishGithubAction(options) {
1803
1937
  allow_squash_merge: allowSquashMerge,
1804
1938
  allow_rebase_merge: allowRebaseMerge
1805
1939
  });
1806
- const { data: newRepo } = await repoCreationPromise;
1940
+ let newRepo;
1941
+ try {
1942
+ newRepo = (await repoCreationPromise).data;
1943
+ } catch (e) {
1944
+ errors.assertError(e);
1945
+ if (e.message === "Resource not accessible by integration") {
1946
+ ctx.logger.warn(`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`);
1947
+ }
1948
+ throw new Error(`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`);
1949
+ }
1807
1950
  if (access == null ? void 0 : access.startsWith(`${owner}/`)) {
1808
1951
  const [, team] = access.split("/");
1809
1952
  await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
@@ -2566,6 +2709,10 @@ const createBuiltinActions = (options) => {
2566
2709
  reader,
2567
2710
  additionalTemplateFilters
2568
2711
  }),
2712
+ createPublishGerritAction({
2713
+ integrations,
2714
+ config
2715
+ }),
2569
2716
  createPublishGithubAction({
2570
2717
  integrations,
2571
2718
  config,
@@ -2653,6 +2800,7 @@ class DatabaseTaskStore {
2653
2800
  this.db = options.database;
2654
2801
  }
2655
2802
  async getTask(taskId) {
2803
+ var _a;
2656
2804
  const [result] = await this.db("tasks").where({ id: taskId }).select();
2657
2805
  if (!result) {
2658
2806
  throw new errors.NotFoundError(`No task with id '${taskId}' found`);
@@ -2666,6 +2814,7 @@ class DatabaseTaskStore {
2666
2814
  status: result.status,
2667
2815
  lastHeartbeatAt: result.last_heartbeat_at,
2668
2816
  createdAt: result.created_at,
2817
+ createdBy: (_a = result.created_by) != null ? _a : void 0,
2669
2818
  secrets
2670
2819
  };
2671
2820
  } catch (error) {
@@ -2673,17 +2822,20 @@ class DatabaseTaskStore {
2673
2822
  }
2674
2823
  }
2675
2824
  async createTask(options) {
2825
+ var _a;
2676
2826
  const taskId = uuid.v4();
2677
2827
  await this.db("tasks").insert({
2678
2828
  id: taskId,
2679
2829
  spec: JSON.stringify(options.spec),
2680
2830
  secrets: options.secrets ? JSON.stringify(options.secrets) : void 0,
2831
+ created_by: (_a = options.createdBy) != null ? _a : null,
2681
2832
  status: "open"
2682
2833
  });
2683
2834
  return { taskId };
2684
2835
  }
2685
2836
  async claimTask() {
2686
2837
  return this.db.transaction(async (tx) => {
2838
+ var _a;
2687
2839
  const [task] = await tx("tasks").where({
2688
2840
  status: "open"
2689
2841
  }).limit(1).select();
@@ -2707,6 +2859,7 @@ class DatabaseTaskStore {
2707
2859
  status: "processing",
2708
2860
  lastHeartbeatAt: task.last_heartbeat_at,
2709
2861
  createdAt: task.created_at,
2862
+ createdBy: (_a = task.created_by) != null ? _a : void 0,
2710
2863
  secrets
2711
2864
  };
2712
2865
  } catch (error) {
@@ -2825,6 +2978,9 @@ class TaskManager {
2825
2978
  get secrets() {
2826
2979
  return this.task.secrets;
2827
2980
  }
2981
+ get createdBy() {
2982
+ return this.task.createdBy;
2983
+ }
2828
2984
  async getWorkspaceName() {
2829
2985
  return this.task.taskId;
2830
2986
  }
@@ -2884,7 +3040,8 @@ class StorageTaskBroker {
2884
3040
  return TaskManager.create({
2885
3041
  taskId: pendingTask.id,
2886
3042
  spec: pendingTask.spec,
2887
- secrets: pendingTask.secrets
3043
+ secrets: pendingTask.secrets,
3044
+ createdBy: pendingTask.createdBy
2888
3045
  }, this.storage, this.logger);
2889
3046
  }
2890
3047
  await this.waitForDispatch();
@@ -3035,7 +3192,8 @@ class NunjucksWorkflowRunner {
3035
3192
  await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
3036
3193
  const context = {
3037
3194
  parameters: task.spec.parameters,
3038
- steps: {}
3195
+ steps: {},
3196
+ user: task.spec.user
3039
3197
  };
3040
3198
  for (const step of task.spec.steps) {
3041
3199
  try {
@@ -3253,10 +3411,11 @@ async function createRouter(options) {
3253
3411
  router.get("/v2/templates/:namespace/:kind/:name/parameter-schema", async (req, res) => {
3254
3412
  var _a, _b;
3255
3413
  const { namespace, kind, name } = req.params;
3414
+ const { token } = parseBearerToken(req.headers.authorization);
3256
3415
  const template = await findTemplate({
3257
3416
  catalogApi: catalogClient,
3258
3417
  entityRef: { kind, namespace, name },
3259
- token: getBearerToken(req.headers.authorization)
3418
+ token
3260
3419
  });
3261
3420
  if (isSupportedTemplate(template)) {
3262
3421
  const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
@@ -3288,12 +3447,13 @@ async function createRouter(options) {
3288
3447
  const { kind, namespace, name } = catalogModel.parseEntityRef(templateRef, {
3289
3448
  defaultKind: "template"
3290
3449
  });
3450
+ const { token, entityRef: userEntityRef } = parseBearerToken(req.headers.authorization);
3451
+ const userEntity = userEntityRef ? await catalogClient.getEntityByRef(userEntityRef, { token }) : void 0;
3291
3452
  const values = req.body.values;
3292
- const token = getBearerToken(req.headers.authorization);
3293
3453
  const template = await findTemplate({
3294
3454
  catalogApi: catalogClient,
3295
3455
  entityRef: { kind, namespace, name },
3296
- token: getBearerToken(req.headers.authorization)
3456
+ token
3297
3457
  });
3298
3458
  if (!isSupportedTemplate(template)) {
3299
3459
  throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
@@ -3318,6 +3478,10 @@ async function createRouter(options) {
3318
3478
  }),
3319
3479
  output: (_b = template.spec.output) != null ? _b : {},
3320
3480
  parameters: values,
3481
+ user: {
3482
+ entity: userEntity,
3483
+ ref: userEntityRef
3484
+ },
3321
3485
  templateInfo: {
3322
3486
  entityRef: catalogModel.stringifyEntityRef({
3323
3487
  kind,
@@ -3329,6 +3493,7 @@ async function createRouter(options) {
3329
3493
  };
3330
3494
  const result = await taskBroker.dispatch({
3331
3495
  spec: taskSpec,
3496
+ createdBy: userEntityRef,
3332
3497
  secrets: {
3333
3498
  ...req.body.secrets,
3334
3499
  backstageToken: token
@@ -3403,9 +3568,17 @@ data: ${JSON.stringify(event)}
3403
3568
  app.use("/", router);
3404
3569
  return app;
3405
3570
  }
3406
- function getBearerToken(header) {
3571
+ function parseBearerToken(header) {
3407
3572
  var _a;
3408
- return (_a = header == null ? void 0 : header.match(/Bearer\s+(\S+)/i)) == null ? void 0 : _a[1];
3573
+ const token = (_a = header == null ? void 0 : header.match(/Bearer\s+(\S+)/i)) == null ? void 0 : _a[1];
3574
+ if (!token)
3575
+ return {};
3576
+ const [_header, rawPayload, _signature] = token.split(".");
3577
+ const payload = JSON.parse(Buffer.from(rawPayload, "base64").toString());
3578
+ return {
3579
+ entityRef: payload.sub,
3580
+ token
3581
+ };
3409
3582
  }
3410
3583
 
3411
3584
  class ScaffolderEntitiesProcessor {
@@ -3478,6 +3651,7 @@ exports.createPublishBitbucketAction = createPublishBitbucketAction;
3478
3651
  exports.createPublishBitbucketCloudAction = createPublishBitbucketCloudAction;
3479
3652
  exports.createPublishBitbucketServerAction = createPublishBitbucketServerAction;
3480
3653
  exports.createPublishFileAction = createPublishFileAction;
3654
+ exports.createPublishGerritAction = createPublishGerritAction;
3481
3655
  exports.createPublishGithubAction = createPublishGithubAction;
3482
3656
  exports.createPublishGithubPullRequestAction = createPublishGithubPullRequestAction;
3483
3657
  exports.createPublishGitlabAction = createPublishGitlabAction;