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