@backstage/plugin-scaffolder-backend 1.3.0-next.1 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,91 @@
1
1
  # @backstage/plugin-scaffolder-backend
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Don't resolve symlinks, treat them as binary files and copy them as-is
8
+
9
+ ## 1.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 35a26131b3: **DEPRECATION**: The `projectid` input parameters to the `publish:gitlab:merge-request`, it's no longer required as it can be decoded from the `repoUrl` input parameter.
14
+ **DEPRECATION**: The `projectid` output of the action in favour of `projectPath`
15
+ - 72dfcbc8bf: A new scaffolder action has been added: `gerrit:publish`
16
+ - ce0d8d7eb1: Fixed a bug in `publish:github` action that didn't permit to add users as collaborators.
17
+ This fix required changing the way parameters are passed to the action.
18
+ In order to add a team as collaborator, now you must use the `team` field instead of `username`.
19
+ In order to add a user as collaborator, you must use the `user` field.
20
+
21
+ It's still possible to use the field `username` but is deprecated in favor of `team`.
22
+
23
+ ```yaml
24
+ - id: publish
25
+ name: Publish
26
+ action: publish:github
27
+ input:
28
+ repoUrl: ...
29
+ collaborators:
30
+ - access: ...
31
+ team: my_team
32
+ - access: ...
33
+ user: my_username
34
+ ```
35
+
36
+ - 582003a059: - Added an optional `list` method on the `TaskBroker` and `TaskStore` interface to list tasks by an optional `userEntityRef`
37
+ - Implemented a `list` method on the `DatabaseTaskStore` class to list tasks by an optional `userEntityRef`
38
+ - Added a route under `/v2/tasks` to list tasks by a `userEntityRef` using the `createdBy` query parameter
39
+ - c042c5eaff: Add an option to not protect the default branch.
40
+ - f93af969cd: Added the ability to support running of templates that are not in the `default` namespace
41
+
42
+ ### Patch Changes
43
+
44
+ - 8f7b1835df: Updated dependency `msw` to `^0.41.0`.
45
+ - 6901f6be4a: Adds more of an explanation when the `publish:github` scaffolder action fails to create a repository.
46
+ - Updated dependencies
47
+ - @backstage/plugin-catalog-backend@1.2.0
48
+ - @backstage/backend-common@0.14.0
49
+ - @backstage/integration@1.2.1
50
+ - @backstage/catalog-client@1.0.3
51
+ - @backstage/catalog-model@1.0.3
52
+ - @backstage/plugin-scaffolder-common@1.1.1
53
+
54
+ ## 1.3.0-next.2
55
+
56
+ ### Minor Changes
57
+
58
+ - ce0d8d7eb1: Fixed a bug in `publish:github` action that didn't permit to add users as collaborators.
59
+ This fix required changing the way parameters are passed to the action.
60
+ In order to add a team as collaborator, now you must use the `team` field instead of `username`.
61
+ In order to add a user as collaborator, you must use the `user` field.
62
+
63
+ It's still possible to use the field `username` but is deprecated in favor of `team`.
64
+
65
+ ```yaml
66
+ - id: publish
67
+ name: Publish
68
+ action: publish:github
69
+ input:
70
+ repoUrl: ...
71
+ collaborators:
72
+ - access: ...
73
+ team: my_team
74
+ - access: ...
75
+ user: my_username
76
+ ```
77
+
78
+ - 582003a059: - Added an optional `list` method on the `TaskBroker` and `TaskStore` interface to list tasks by an optional `userEntityRef`
79
+ - Implemented a `list` method on the `DatabaseTaskStore` class to list tasks by an optional `userEntityRef`
80
+ - Added a route under `/v2/tasks` to list tasks by a `userEntityRef` using the `createdBy` query parameter
81
+
82
+ ### Patch Changes
83
+
84
+ - Updated dependencies
85
+ - @backstage/backend-common@0.14.0-next.2
86
+ - @backstage/integration@1.2.1-next.2
87
+ - @backstage/plugin-catalog-backend@1.2.0-next.2
88
+
3
89
  ## 1.3.0-next.1
4
90
 
5
91
  ### Minor Changes
package/dist/index.cjs.js CHANGED
@@ -571,8 +571,9 @@ function createFetchTemplateAction(options) {
571
571
  await fs__default["default"].ensureDir(outputPath);
572
572
  } else {
573
573
  const inputFilePath = backendCommon.resolveSafeChildPath(templateDir, location);
574
- if (await isbinaryfile.isBinaryFile(inputFilePath)) {
575
- ctx.logger.info(`Copying binary file ${location} to template output path.`);
574
+ const stats = await fs__default["default"].promises.lstat(inputFilePath);
575
+ if (stats.isSymbolicLink() || await isbinaryfile.isBinaryFile(inputFilePath)) {
576
+ ctx.logger.info(`Copying file binary or symbolic link at ${location}, to template output path.`);
576
577
  await fs__default["default"].copy(inputFilePath, outputPath);
577
578
  } else {
578
579
  const statsObj = await fs__default["default"].stat(inputFilePath);
@@ -1862,22 +1863,36 @@ function createPublishGithubAction(options) {
1862
1863
  },
1863
1864
  collaborators: {
1864
1865
  title: "Collaborators",
1865
- description: "Provide additional users with permissions",
1866
+ description: "Provide additional users or teams with permissions",
1866
1867
  type: "array",
1867
1868
  items: {
1868
1869
  type: "object",
1869
- required: ["username", "access"],
1870
+ additionalProperties: false,
1871
+ required: ["access"],
1870
1872
  properties: {
1871
1873
  access: {
1872
1874
  type: "string",
1873
1875
  description: "The type of access for the user",
1874
1876
  enum: ["push", "pull", "admin", "maintain", "triage"]
1875
1877
  },
1878
+ user: {
1879
+ type: "string",
1880
+ description: "The name of the user that will be added as a collaborator"
1881
+ },
1876
1882
  username: {
1877
1883
  type: "string",
1878
- description: "The username or group"
1884
+ description: "Deprecated. Use the `team` or `user` field instead."
1885
+ },
1886
+ team: {
1887
+ type: "string",
1888
+ description: "The name of the team that will be added as a collaborator"
1879
1889
  }
1880
- }
1890
+ },
1891
+ oneOf: [
1892
+ { required: ["user"] },
1893
+ { required: ["username"] },
1894
+ { required: ["team"] }
1895
+ ]
1881
1896
  }
1882
1897
  },
1883
1898
  token: {
@@ -1990,21 +2005,37 @@ function createPublishGithubAction(options) {
1990
2005
  });
1991
2006
  }
1992
2007
  if (collaborators) {
1993
- for (const {
1994
- access: permission,
1995
- username: team_slug
1996
- } of collaborators) {
2008
+ for (const collaborator of collaborators) {
1997
2009
  try {
1998
- await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
1999
- org: owner,
2000
- team_slug,
2001
- owner,
2002
- repo,
2003
- permission
2004
- });
2010
+ if ("user" in collaborator) {
2011
+ await client.rest.repos.addCollaborator({
2012
+ owner,
2013
+ repo,
2014
+ username: collaborator.user,
2015
+ permission: collaborator.access
2016
+ });
2017
+ } else if ("username" in collaborator) {
2018
+ ctx.logger.warn("The field `username` is deprecated in favor of `team` and will be removed in the future.");
2019
+ await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
2020
+ org: owner,
2021
+ team_slug: collaborator.username,
2022
+ owner,
2023
+ repo,
2024
+ permission: collaborator.access
2025
+ });
2026
+ } else if ("team" in collaborator) {
2027
+ await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
2028
+ org: owner,
2029
+ team_slug: collaborator.team,
2030
+ owner,
2031
+ repo,
2032
+ permission: collaborator.access
2033
+ });
2034
+ }
2005
2035
  } catch (e) {
2006
2036
  errors.assertError(e);
2007
- ctx.logger.warn(`Skipping ${permission} access for ${team_slug}, ${e.message}`);
2037
+ const name = extractCollaboratorName(collaborator);
2038
+ ctx.logger.warn(`Skipping ${collaborator.access} access for ${name}, ${e.message}`);
2008
2039
  }
2009
2040
  }
2010
2041
  }
@@ -2059,6 +2090,13 @@ function createPublishGithubAction(options) {
2059
2090
  }
2060
2091
  });
2061
2092
  }
2093
+ function extractCollaboratorName(collaborator) {
2094
+ if ("username" in collaborator)
2095
+ return collaborator.username;
2096
+ if ("user" in collaborator)
2097
+ return collaborator.user;
2098
+ return collaborator.team;
2099
+ }
2062
2100
 
2063
2101
  const DEFAULT_GLOB_PATTERNS = ["./**", "!.git"];
2064
2102
  const isExecutable = (fileMode) => {
@@ -2379,7 +2417,7 @@ const createPublishGitlabMergeRequestAction = (options) => {
2379
2417
  id: "publish:gitlab:merge-request",
2380
2418
  schema: {
2381
2419
  input: {
2382
- required: ["projectid", "repoUrl", "targetPath", "branchName"],
2420
+ required: ["repoUrl", "targetPath", "branchName"],
2383
2421
  type: "object",
2384
2422
  properties: {
2385
2423
  repoUrl: {
@@ -2426,6 +2464,10 @@ const createPublishGitlabMergeRequestAction = (options) => {
2426
2464
  title: "Gitlab Project id/Name(slug)",
2427
2465
  type: "string"
2428
2466
  },
2467
+ projectPath: {
2468
+ title: "Gitlab Project path",
2469
+ type: "string"
2470
+ },
2429
2471
  mergeRequestURL: {
2430
2472
  title: "MergeRequest(MR) URL",
2431
2473
  type: "string",
@@ -2437,7 +2479,13 @@ const createPublishGitlabMergeRequestAction = (options) => {
2437
2479
  async handler(ctx) {
2438
2480
  var _a;
2439
2481
  const repoUrl = ctx.input.repoUrl;
2440
- const { host } = parseRepoUrl(repoUrl, integrations);
2482
+ const { host, owner, repo } = parseRepoUrl(repoUrl, integrations);
2483
+ const projectPath = `${owner}/${repo}`;
2484
+ if (ctx.input.projectid) {
2485
+ const deprecationWarning = `Property "projectid" is deprecated and no longer to needed to create a MR`;
2486
+ ctx.logger.warn(deprecationWarning);
2487
+ console.warn(deprecationWarning);
2488
+ }
2441
2489
  const integrationConfig = integrations.gitlab.byHost(host);
2442
2490
  const destinationBranch = ctx.input.branchName;
2443
2491
  if (!integrationConfig) {
@@ -2463,23 +2511,24 @@ const createPublishGitlabMergeRequestAction = (options) => {
2463
2511
  content: file.content.toString("base64"),
2464
2512
  execute_filemode: file.executable
2465
2513
  }));
2466
- const projects = await api.Projects.show(ctx.input.projectid);
2514
+ const projects = await api.Projects.show(projectPath);
2467
2515
  const { default_branch: defaultBranch } = projects;
2468
2516
  try {
2469
- await api.Branches.create(ctx.input.projectid, destinationBranch, String(defaultBranch));
2517
+ await api.Branches.create(projectPath, destinationBranch, String(defaultBranch));
2470
2518
  } catch (e) {
2471
2519
  throw new errors.InputError(`The branch creation failed ${e}`);
2472
2520
  }
2473
2521
  try {
2474
- await api.Commits.create(ctx.input.projectid, destinationBranch, ctx.input.title, actions);
2522
+ await api.Commits.create(projectPath, destinationBranch, ctx.input.title, actions);
2475
2523
  } catch (e) {
2476
2524
  throw new errors.InputError(`Committing the changes to ${destinationBranch} failed ${e}`);
2477
2525
  }
2478
2526
  try {
2479
- const mergeRequestUrl = await api.MergeRequests.create(ctx.input.projectid, destinationBranch, String(defaultBranch), ctx.input.title, { description: ctx.input.description }).then((mergeRequest) => {
2527
+ const mergeRequestUrl = await api.MergeRequests.create(projectPath, destinationBranch, String(defaultBranch), ctx.input.title, { description: ctx.input.description }).then((mergeRequest) => {
2480
2528
  return mergeRequest.web_url;
2481
2529
  });
2482
- ctx.output("projectid", ctx.input.projectid);
2530
+ ctx.output("projectid", projectPath);
2531
+ ctx.output("projectPath", projectPath);
2483
2532
  ctx.output("mergeRequestUrl", mergeRequestUrl);
2484
2533
  } catch (e) {
2485
2534
  throw new errors.InputError(`Merge request creation failed${e}`);
@@ -2834,6 +2883,12 @@ class TemplateActionRegistry {
2834
2883
  }
2835
2884
 
2836
2885
  const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-scaffolder-backend", "migrations");
2886
+ const parseSqlDateToIsoString = (input) => {
2887
+ if (typeof input === "string") {
2888
+ return luxon.DateTime.fromSQL(input, { zone: "UTC" }).toISO();
2889
+ }
2890
+ return input;
2891
+ };
2837
2892
  class DatabaseTaskStore {
2838
2893
  static async create(options) {
2839
2894
  await options.database.migrate.latest({
@@ -2844,6 +2899,27 @@ class DatabaseTaskStore {
2844
2899
  constructor(options) {
2845
2900
  this.db = options.database;
2846
2901
  }
2902
+ async list(options) {
2903
+ const queryBuilder = this.db("tasks");
2904
+ if (options.createdBy) {
2905
+ queryBuilder.where({
2906
+ created_by: options.createdBy
2907
+ });
2908
+ }
2909
+ const results = await queryBuilder.orderBy("created_at", "desc").select();
2910
+ const tasks = results.map((result) => {
2911
+ var _a;
2912
+ return {
2913
+ id: result.id,
2914
+ spec: JSON.parse(result.spec),
2915
+ status: result.status,
2916
+ createdBy: (_a = result.created_by) != null ? _a : void 0,
2917
+ lastHeartbeatAt: parseSqlDateToIsoString(result.last_heartbeat_at),
2918
+ createdAt: parseSqlDateToIsoString(result.created_at)
2919
+ };
2920
+ });
2921
+ return { tasks };
2922
+ }
2847
2923
  async getTask(taskId) {
2848
2924
  var _a;
2849
2925
  const [result] = await this.db("tasks").where({ id: taskId }).select();
@@ -2857,8 +2933,8 @@ class DatabaseTaskStore {
2857
2933
  id: result.id,
2858
2934
  spec,
2859
2935
  status: result.status,
2860
- lastHeartbeatAt: result.last_heartbeat_at,
2861
- createdAt: result.created_at,
2936
+ lastHeartbeatAt: parseSqlDateToIsoString(result.last_heartbeat_at),
2937
+ createdAt: parseSqlDateToIsoString(result.created_at),
2862
2938
  createdBy: (_a = result.created_by) != null ? _a : void 0,
2863
2939
  secrets
2864
2940
  };
@@ -2995,7 +3071,7 @@ class DatabaseTaskStore {
2995
3071
  taskId,
2996
3072
  body,
2997
3073
  type: event.event_type,
2998
- createdAt: typeof event.created_at === "string" ? luxon.DateTime.fromSQL(event.created_at, { zone: "UTC" }).toISO() : event.created_at
3074
+ createdAt: parseSqlDateToIsoString(event.created_at)
2999
3075
  };
3000
3076
  } catch (error) {
3001
3077
  throw new Error(`Failed to parse event body from event taskId=${taskId} id=${event.id}, ${error}`);
@@ -3078,6 +3154,12 @@ class StorageTaskBroker {
3078
3154
  this.logger = logger;
3079
3155
  this.deferredDispatch = defer();
3080
3156
  }
3157
+ async list(options) {
3158
+ if (!this.storage.list) {
3159
+ throw new Error("TaskStore does not implement the list method. Please implement the list method to be able to list tasks");
3160
+ }
3161
+ return await this.storage.list({ createdBy: options == null ? void 0 : options.createdBy });
3162
+ }
3081
3163
  async claim() {
3082
3164
  for (; ; ) {
3083
3165
  const pendingTask = await this.storage.claimTask();
@@ -3520,9 +3602,6 @@ function getEntityBaseUrl(entity) {
3520
3602
  }
3521
3603
  async function findTemplate(options) {
3522
3604
  const { entityRef, token, catalogApi } = options;
3523
- if (entityRef.namespace.toLocaleLowerCase("en-US") !== catalogModel.DEFAULT_NAMESPACE) {
3524
- throw new errors.InputError(`Invalid namespace, only '${catalogModel.DEFAULT_NAMESPACE}' namespace is supported`);
3525
- }
3526
3605
  if (entityRef.kind.toLocaleLowerCase("en-US") !== "template") {
3527
3606
  throw new errors.InputError(`Invalid kind, only 'Template' kind is supported`);
3528
3607
  }
@@ -3682,6 +3761,18 @@ async function createRouter(options) {
3682
3761
  }
3683
3762
  });
3684
3763
  res.status(201).json({ id: result.taskId });
3764
+ }).get("/v2/tasks", async (req, res) => {
3765
+ const [userEntityRef] = [req.query.createdBy].flat();
3766
+ if (typeof userEntityRef !== "string" && typeof userEntityRef !== "undefined") {
3767
+ throw new errors.InputError("createdBy query parameter must be a string");
3768
+ }
3769
+ if (!taskBroker.list) {
3770
+ throw new Error("TaskBroker does not support listing tasks, please implement the list method on the TaskBroker.");
3771
+ }
3772
+ const tasks = await taskBroker.list({
3773
+ createdBy: userEntityRef
3774
+ });
3775
+ res.status(200).json(tasks);
3685
3776
  }).get("/v2/tasks/:taskId", async (req, res) => {
3686
3777
  const { taskId } = req.params;
3687
3778
  const task = await taskBroker.get(taskId);