@backstage/plugin-scaffolder-backend-module-gitlab 0.4.0 → 0.4.1-next.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/dist/index.cjs.js CHANGED
@@ -2,493 +2,161 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var errors = require('@backstage/errors');
5
6
  var pluginScaffolderNode = require('@backstage/plugin-scaffolder-node');
6
7
  var node = require('@gitbeaker/node');
8
+ var yaml = require('yaml');
7
9
  var zod = require('zod');
8
- var errors = require('@backstage/errors');
9
10
  var rest = require('@gitbeaker/rest');
10
- var yaml = require('yaml');
11
- var luxon = require('luxon');
12
11
  var path = require('path');
13
12
  var backendPluginApi = require('@backstage/backend-plugin-api');
14
- var alpha = require('@backstage/plugin-scaffolder-node/alpha');
13
+ var luxon = require('luxon');
15
14
  var integration = require('@backstage/integration');
15
+ var alpha = require('@backstage/plugin-scaffolder-node/alpha');
16
16
 
17
17
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
18
18
 
19
19
  var yaml__default = /*#__PURE__*/_interopDefaultCompat(yaml);
20
20
  var path__default = /*#__PURE__*/_interopDefaultCompat(path);
21
21
 
22
- const commonGitlabConfig = zod.z.object({
23
- repoUrl: zod.z.string({ description: "Repository Location" }),
24
- token: zod.z.string({ description: "The token to use for authorization to GitLab" }).optional()
25
- });
26
- const commonGitlabConfigExample = {
27
- repoUrl: "gitlab.com?owner=namespace-or-owner&repo=project-name",
28
- token: "${{ secrets.USER_OAUTH_TOKEN }}"
29
- };
30
-
31
- const parseRepoHost = (repoUrl) => {
32
- let parsed;
33
- try {
34
- parsed = new URL(`https://${repoUrl}`);
35
- } catch (error) {
36
- throw new errors.InputError(
37
- `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`
38
- );
39
- }
40
- return parsed.host;
41
- };
42
- const getToken = (config, integrations) => {
43
- const host = parseRepoHost(config.repoUrl);
44
- const integrationConfig = integrations.gitlab.byHost(host);
45
- if (!integrationConfig) {
46
- throw new errors.InputError(
47
- `No matching integration configuration for host ${host}, please check your integrations config`
48
- );
49
- }
50
- const token = config.token || integrationConfig.config.token;
51
- return { token, integrationConfig };
52
- };
53
- const parseRepoUrl = (repoUrl, integrations) => {
54
- var _a, _b;
55
- let parsed;
56
- try {
57
- parsed = new URL(`https://${repoUrl}`);
58
- } catch (error) {
59
- throw new errors.InputError(
60
- `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`
61
- );
62
- }
63
- const host = parsed.host;
64
- const owner = (_a = parsed.searchParams.get("owner")) != null ? _a : void 0;
65
- const repo = parsed.searchParams.get("repo");
66
- const type = (_b = integrations.byHost(host)) == null ? void 0 : _b.type;
67
- if (!type) {
68
- throw new errors.InputError(
69
- `No matching integration configuration for host ${host}, please check your integrations config`
70
- );
71
- }
72
- return { host, owner, repo };
73
- };
74
- function getClient(props) {
75
- const { host, token, integrations } = props;
76
- const integrationConfig = integrations.gitlab.byHost(host);
77
- if (!integrationConfig) {
78
- throw new errors.InputError(
79
- `No matching integration configuration for host ${host}, please check your integrations config`
80
- );
81
- }
82
- const { config } = integrationConfig;
83
- if (!config.token && !token) {
84
- throw new errors.InputError(`No token available for host ${host}`);
85
- }
86
- const requestToken = token || config.token;
87
- const tokenType = token ? "oauthToken" : "token";
88
- const gitlabOptions = {
89
- host: config.baseUrl
90
- };
91
- gitlabOptions[tokenType] = requestToken;
92
- return new rest.Gitlab(gitlabOptions);
93
- }
94
- function convertDate(inputDate, defaultDate) {
95
- try {
96
- return inputDate ? new Date(inputDate).toISOString() : new Date(defaultDate).toISOString();
97
- } catch (error) {
98
- throw new errors.InputError(`Error converting input date - ${error}`);
99
- }
100
- }
101
- async function getTopLevelParentGroup(client, groupId) {
102
- try {
103
- const topParentGroup = await client.Groups.show(groupId);
104
- if (topParentGroup.parent_id) {
105
- return getTopLevelParentGroup(
106
- client,
107
- topParentGroup.parent_id
108
- );
109
- }
110
- return topParentGroup;
111
- } catch (error) {
112
- throw new errors.InputError(
113
- `Error finding top-level parent group ID: ${error.message}`
114
- );
115
- }
116
- }
117
- async function checkEpicScope(client, projectId, epicId) {
118
- try {
119
- const project = await client.Projects.show(projectId);
120
- if (!project) {
121
- throw new errors.InputError(
122
- `Project with id ${projectId} not found. Check your GitLab instance.`
123
- );
124
- }
125
- const topParentGroup = await getTopLevelParentGroup(
126
- client,
127
- project.namespace.id
128
- );
129
- if (!topParentGroup) {
130
- throw new errors.InputError(`Couldn't find a suitable top-level parent group.`);
131
- }
132
- const epic = (await client.Epics.all(topParentGroup.id)).find(
133
- (x) => x.id === epicId
134
- );
135
- if (!epic) {
136
- throw new errors.InputError(
137
- `Epic with id ${epicId} not found in the top-level parent group ${topParentGroup.name}.`
138
- );
139
- }
140
- const epicGroup = await client.Groups.show(epic.group_id);
141
- const projectNamespace = project.path_with_namespace;
142
- return projectNamespace.startsWith(epicGroup.full_path);
143
- } catch (error) {
144
- throw new errors.InputError(`Could not find epic scope: ${error.message}`);
145
- }
146
- }
147
-
148
- const examples$7 = [
22
+ const examples$8 = [
149
23
  {
150
- description: "Creating a group at the top level",
24
+ description: "Initializes a git repository with the content in the workspace, and publishes it to GitLab with the default configuration.",
151
25
  example: yaml__default.default.stringify({
152
26
  steps: [
153
27
  {
154
- id: "gitlabGroup",
155
- name: "Group",
156
- action: "gitlab:group:ensureExists",
28
+ id: "publish",
29
+ action: "publish:gitlab",
30
+ name: "Publish to GitLab",
157
31
  input: {
158
- repoUrl: "gitlab.com",
159
- path: ["group1"]
32
+ repoUrl: "gitlab.com?repo=project_name&owner=group_name"
160
33
  }
161
34
  }
162
35
  ]
163
36
  })
164
37
  },
165
38
  {
166
- description: "Create a group nested within another group",
39
+ description: "Add a description.",
167
40
  example: yaml__default.default.stringify({
168
41
  steps: [
169
42
  {
170
- id: "gitlabGroup",
171
- name: "Group",
172
- action: "gitlab:group:ensureExists",
43
+ id: "publish",
44
+ action: "publish:gitlab",
45
+ name: "Publish to GitLab",
173
46
  input: {
174
- repoUrl: "gitlab.com",
175
- path: ["group1", "group2"]
47
+ repoUrl: "gitlab.com?repo=project_name&owner=group_name",
48
+ description: "Initialize a git repository"
176
49
  }
177
50
  }
178
51
  ]
179
52
  })
180
53
  },
181
54
  {
182
- description: "Create a group nested within multiple other groups",
55
+ description: "Initializes a GitLab repository with an initial commit message, if not set defaults to `initial commit`.",
183
56
  example: yaml__default.default.stringify({
184
57
  steps: [
185
58
  {
186
- id: "gitlabGroup",
187
- name: "Group",
188
- action: "gitlab:group:ensureExists",
59
+ id: "publish",
60
+ action: "publish:gitlab",
61
+ name: "Publish to GitLab",
189
62
  input: {
190
- repoUrl: "gitlab.com",
191
- path: ["group1", "group2", "group3"]
63
+ repoUrl: "gitlab.com?repo=project_name&owner=group_name",
64
+ description: "Initialize a git repository",
65
+ gitCommitMessage: "Started a project."
192
66
  }
193
67
  }
194
68
  ]
195
69
  })
196
70
  },
197
71
  {
198
- description: "Create a group in dry run mode",
72
+ description: "Initializes a GitLab repository with aditional settings.",
199
73
  example: yaml__default.default.stringify({
200
74
  steps: [
201
75
  {
202
- id: "gitlabGroup",
203
- name: "Group",
204
- action: "gitlab:group:ensureExists",
205
- isDryRun: true,
76
+ id: "publish",
77
+ action: "publish:gitlab",
78
+ name: "Publish to GitLab",
206
79
  input: {
207
- repoUrl: "https://gitlab.com/my-repo",
208
- path: ["group1", "group2", "group3"]
80
+ repoUrl: "gitlab.com?repo=project_name&owner=group_name",
81
+ settings: {
82
+ ci_config_path: ".gitlab-ci.yml",
83
+ visibility: "public"
84
+ }
209
85
  }
210
86
  }
211
87
  ]
212
88
  })
213
- }
214
- ];
215
-
216
- const createGitlabGroupEnsureExistsAction = (options) => {
217
- const { integrations } = options;
218
- return pluginScaffolderNode.createTemplateAction({
219
- id: "gitlab:group:ensureExists",
220
- description: "Ensures a Gitlab group exists",
221
- supportsDryRun: true,
222
- examples: examples$7,
223
- schema: {
224
- input: commonGitlabConfig.merge(
225
- zod.z.object({
226
- path: zod.z.array(zod.z.string(), {
227
- description: "A path of group names that is ensured to exist"
228
- }).min(1)
229
- })
230
- ),
231
- output: zod.z.object({
232
- groupId: zod.z.number({ description: "The id of the innermost sub-group" }).optional()
233
- })
234
- },
235
- async handler(ctx) {
236
- if (ctx.isDryRun) {
237
- ctx.output("groupId", 42);
238
- return;
239
- }
240
- const { path } = ctx.input;
241
- const { token, integrationConfig } = getToken(ctx.input, integrations);
242
- const api = new node.Gitlab({
243
- host: integrationConfig.config.baseUrl,
244
- token
245
- });
246
- let currentPath = null;
247
- let parent = null;
248
- for (const pathElement of path) {
249
- const fullPath = currentPath ? `${currentPath}/${pathElement}` : pathElement;
250
- const result = await api.Groups.search(
251
- fullPath
252
- );
253
- const subGroup = result.find(
254
- (searchPathElem) => searchPathElem.full_path === fullPath
255
- );
256
- if (!subGroup) {
257
- ctx.logger.info(`creating missing group ${fullPath}`);
258
- parent = await api.Groups.create(
259
- pathElement,
260
- pathElement,
261
- parent ? {
262
- parent_id: parent.id
263
- } : {}
264
- );
265
- } else {
266
- parent = subGroup;
267
- }
268
- currentPath = fullPath;
269
- }
270
- if (parent !== null) {
271
- ctx.output("groupId", parent == null ? void 0 : parent.id);
272
- }
273
- }
274
- });
275
- };
276
-
277
- const examples$6 = [
89
+ },
278
90
  {
279
- description: "Create a GitLab project deploy token with minimal options.",
91
+ description: "Initializes a GitLab repository with fast forward merge and always squash settings.",
280
92
  example: yaml__default.default.stringify({
281
93
  steps: [
282
94
  {
283
- id: "createDeployToken",
284
- action: "gitlab:projectDeployToken:create",
285
- name: "Create GitLab Project Deploy Token",
286
- input: {
287
- repoUrl: "gitlab.com?repo=repo&owner=owner",
288
- projectId: "456",
289
- name: "tokenname"
290
- }
291
- }
292
- ]
293
- })
294
- },
295
- {
296
- description: "Create a GitLab project deploy token with custom scopes.",
297
- example: yaml__default.default.stringify({
298
- steps: [
299
- {
300
- id: "createDeployToken",
301
- action: "gitlab:projectDeployToken:create",
302
- name: "Create GitLab Project Deploy Token",
303
- input: {
304
- repoUrl: "gitlab.com?repo=repo&owner=owner",
305
- projectId: "789",
306
- name: "tokenname",
307
- scopes: ["read_registry", "write_repository"]
308
- }
309
- }
310
- ]
311
- })
312
- },
313
- {
314
- description: "Create a GitLab project deploy token with a specified name.",
315
- example: yaml__default.default.stringify({
316
- steps: [
317
- {
318
- id: "createDeployToken",
319
- action: "gitlab:projectDeployToken:create",
320
- name: "Create GitLab Project Deploy Token",
321
- input: {
322
- repoUrl: "gitlab.com?repo=repo&owner=owner",
323
- projectId: "101112",
324
- name: "my-custom-token"
325
- }
326
- }
327
- ]
328
- })
329
- },
330
- {
331
- description: "Create a GitLab project deploy token with a numeric project ID.",
332
- example: yaml__default.default.stringify({
333
- steps: [
334
- {
335
- id: "createDeployToken",
336
- action: "gitlab:projectDeployToken:create",
337
- name: "Create GitLab Project Deploy Token",
338
- input: {
339
- repoUrl: "gitlab.com?repo=repo&owner=owner",
340
- projectId: 42,
341
- name: "tokenname"
342
- }
343
- }
344
- ]
345
- })
346
- },
347
- {
348
- description: "Create a GitLab project deploy token with a custom username",
349
- example: yaml__default.default.stringify({
350
- steps: [
351
- {
352
- id: "createDeployToken",
353
- action: "gitlab:projectDeployToken:create",
354
- name: "Create GitLab Project Deploy Token",
355
- input: {
356
- repoUrl: "gitlab.com?repo=repo&owner=owner",
357
- projectId: 42,
358
- name: "tokenname",
359
- username: "tokenuser"
360
- }
361
- }
362
- ]
363
- })
364
- }
365
- ];
366
-
367
- const createGitlabProjectDeployTokenAction = (options) => {
368
- const { integrations } = options;
369
- return pluginScaffolderNode.createTemplateAction({
370
- id: "gitlab:projectDeployToken:create",
371
- examples: examples$6,
372
- schema: {
373
- input: commonGitlabConfig.merge(
374
- zod.z.object({
375
- projectId: zod.z.union([zod.z.number(), zod.z.string()], {
376
- description: "Project ID"
377
- }),
378
- name: zod.z.string({ description: "Deploy Token Name" }),
379
- username: zod.z.string({ description: "Deploy Token Username" }).optional(),
380
- scopes: zod.z.array(zod.z.string(), { description: "Scopes" }).optional()
381
- })
382
- ),
383
- output: zod.z.object({
384
- deploy_token: zod.z.string({ description: "Deploy Token" }),
385
- user: zod.z.string({ description: "User" })
386
- })
387
- },
388
- async handler(ctx) {
389
- ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`);
390
- const { projectId, name, username, scopes } = ctx.input;
391
- const { token, integrationConfig } = getToken(ctx.input, integrations);
392
- const api = new node.Gitlab({
393
- host: integrationConfig.config.baseUrl,
394
- token
395
- });
396
- const deployToken = await api.ProjectDeployTokens.add(
397
- projectId,
398
- name,
399
- scopes,
400
- {
401
- username
402
- }
403
- );
404
- if (!deployToken.hasOwnProperty("token")) {
405
- throw new errors.InputError(`No deploy_token given from gitlab instance`);
406
- }
407
- ctx.output("deploy_token", deployToken.token);
408
- ctx.output("user", deployToken.username);
409
- }
410
- });
411
- };
412
-
413
- const examples$5 = [
414
- {
415
- description: "Create a GitLab project access token with minimal options.",
416
- example: yaml__default.default.stringify({
417
- steps: [
418
- {
419
- id: "createAccessToken",
420
- action: "gitlab:projectAccessToken:create",
421
- name: "Create GitLab Project Access Token",
422
- input: {
423
- repoUrl: "gitlab.com?repo=repo&owner=owner",
424
- projectId: "456"
425
- }
426
- }
427
- ]
428
- })
429
- },
430
- {
431
- description: "Create a GitLab project access token with custom scopes.",
432
- example: yaml__default.default.stringify({
433
- steps: [
434
- {
435
- id: "createAccessToken",
436
- action: "gitlab:projectAccessToken:create",
437
- name: "Create GitLab Project Access Token",
438
- input: {
439
- repoUrl: "gitlab.com?repo=repo&owner=owner",
440
- projectId: "789",
441
- scopes: ["read_registry", "write_repository"]
442
- }
443
- }
444
- ]
445
- })
446
- },
447
- {
448
- description: "Create a GitLab project access token with a specified name.",
449
- example: yaml__default.default.stringify({
450
- steps: [
451
- {
452
- id: "createAccessToken",
453
- action: "gitlab:projectAccessToken:create",
454
- name: "Create GitLab Project Access Token",
95
+ id: "publish",
96
+ action: "publish:gitlab",
97
+ name: "Publish to GitLab",
455
98
  input: {
456
- repoUrl: "gitlab.com?repo=repo&owner=owner",
457
- projectId: "101112",
458
- name: "my-custom-token"
99
+ repoUrl: "gitlab.com?repo=project_name&owner=group_name",
100
+ settings: {
101
+ merge_method: "ff",
102
+ squash_option: "always"
103
+ }
459
104
  }
460
105
  }
461
106
  ]
462
107
  })
463
108
  },
464
109
  {
465
- description: "Create a GitLab project access token with a numeric project ID.",
110
+ description: "Initializes a GitLab repository with branch settings.",
466
111
  example: yaml__default.default.stringify({
467
112
  steps: [
468
113
  {
469
- id: "createAccessToken",
470
- action: "gitlab:projectAccessToken:create",
471
- name: "Create GitLab Project Access Token",
114
+ id: "publish",
115
+ action: "publish:gitlab",
116
+ name: "Publish to GitLab",
472
117
  input: {
473
- repoUrl: "gitlab.com?repo=repo&owner=owner",
474
- projectId: 42
118
+ repoUrl: "gitlab.com?repo=project_name&owner=group_name",
119
+ branches: [
120
+ {
121
+ name: "dev",
122
+ create: true,
123
+ protected: true,
124
+ ref: "master"
125
+ },
126
+ {
127
+ name: "master",
128
+ protected: true
129
+ }
130
+ ]
475
131
  }
476
132
  }
477
133
  ]
478
134
  })
479
135
  },
480
136
  {
481
- description: "Create a GitLab project access token with a specified expired Date.",
137
+ description: "Initializes a GitLab repository with environment variables.",
482
138
  example: yaml__default.default.stringify({
483
139
  steps: [
484
140
  {
485
- id: "createAccessToken",
486
- action: "gitlab:projectAccessToken:create",
487
- name: "Create GitLab Project Access Token",
141
+ id: "publish",
142
+ action: "publish:gitlab",
143
+ name: "Publish to GitLab",
488
144
  input: {
489
- repoUrl: "gitlab.com?repo=repo&owner=owner",
490
- projectId: "123",
491
- expiresAt: "2024-06-25"
145
+ repoUrl: "gitlab.com?repo=project_name&owner=group_name",
146
+ projectVariables: [
147
+ {
148
+ key: "key1",
149
+ value: "value1",
150
+ protected: true,
151
+ masked: false
152
+ },
153
+ {
154
+ key: "key2",
155
+ value: "value2",
156
+ protected: true,
157
+ masked: false
158
+ }
159
+ ]
492
160
  }
493
161
  }
494
162
  ]
@@ -496,213 +164,557 @@ const examples$5 = [
496
164
  }
497
165
  ];
498
166
 
499
- const createGitlabProjectAccessTokenAction = (options) => {
500
- const { integrations } = options;
167
+ function createPublishGitlabAction(options) {
168
+ const { integrations, config } = options;
501
169
  return pluginScaffolderNode.createTemplateAction({
502
- id: "gitlab:projectAccessToken:create",
503
- examples: examples$5,
170
+ id: "publish:gitlab",
171
+ description: "Initializes a git repository of the content in the workspace, and publishes it to GitLab.",
172
+ examples: examples$8,
504
173
  schema: {
505
- input: zod.z.object({
506
- projectId: zod.z.union([zod.z.number(), zod.z.string()], {
507
- description: "Project ID/Name(slug) of the Gitlab Project"
508
- }),
509
- token: zod.z.string({
510
- description: "The token to use for authorization to GitLab"
511
- }).optional(),
512
- name: zod.z.string({ description: "Name of Access Key" }).optional(),
513
- repoUrl: zod.z.string({ description: "URL to gitlab instance" }),
514
- accessLevel: zod.z.number({
515
- description: "Access Level of the Token, 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), and 50 (Owner)"
516
- }).optional(),
517
- scopes: zod.z.string({
518
- description: "Scopes for a project access token"
519
- }).array().optional(),
520
- expiresAt: zod.z.string({
521
- description: "Expiration date of the access token in ISO format (YYYY-MM-DD). If Empty, it will set to the maximum of 365 days."
522
- }).optional()
523
- }),
524
- output: zod.z.object({
525
- access_token: zod.z.string({ description: "Access Token" })
526
- })
174
+ input: {
175
+ type: "object",
176
+ required: ["repoUrl"],
177
+ properties: {
178
+ repoUrl: {
179
+ title: "Repository Location",
180
+ type: "string",
181
+ description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`
182
+ },
183
+ repoVisibility: {
184
+ title: "Repository Visibility",
185
+ description: `Sets the visibility of the repository. The default value is 'private'. (deprecated, use settings.visibility instead)`,
186
+ type: "string",
187
+ enum: ["private", "public", "internal"]
188
+ },
189
+ defaultBranch: {
190
+ title: "Default Branch",
191
+ type: "string",
192
+ description: `Sets the default branch on the repository. The default value is 'master'`
193
+ },
194
+ gitCommitMessage: {
195
+ title: "Git Commit Message",
196
+ type: "string",
197
+ description: `Sets the commit message on the repository. The default value is 'initial commit'`
198
+ },
199
+ gitAuthorName: {
200
+ title: "Default Author Name",
201
+ type: "string",
202
+ description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
203
+ },
204
+ gitAuthorEmail: {
205
+ title: "Default Author Email",
206
+ type: "string",
207
+ description: `Sets the default author email for the commit.`
208
+ },
209
+ sourcePath: {
210
+ title: "Source Path",
211
+ description: "Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.",
212
+ type: "string"
213
+ },
214
+ token: {
215
+ title: "Authentication Token",
216
+ type: "string",
217
+ description: "The token to use for authorization to GitLab"
218
+ },
219
+ setUserAsOwner: {
220
+ title: "Set User As Owner",
221
+ type: "boolean",
222
+ description: "Set the token user as owner of the newly created repository. Requires a token authorized to do the edit in the integration configuration for the matching host"
223
+ },
224
+ topics: {
225
+ title: "Topic labels",
226
+ description: "Topic labels to apply on the repository. (deprecated, use settings.topics instead)",
227
+ type: "array",
228
+ items: {
229
+ type: "string"
230
+ }
231
+ },
232
+ settings: {
233
+ title: "Project settings",
234
+ description: "Additional project settings, based on https://docs.gitlab.com/ee/api/projects.html#create-project attributes",
235
+ type: "object",
236
+ properties: {
237
+ path: {
238
+ title: "Project path",
239
+ description: "Repository name for new project. Generated based on name if not provided (generated as lowercase with dashes).",
240
+ type: "string"
241
+ },
242
+ auto_devops_enabled: {
243
+ title: "Auto DevOps enabled",
244
+ description: "Enable Auto DevOps for this project",
245
+ type: "boolean"
246
+ },
247
+ ci_config_path: {
248
+ title: "CI config path",
249
+ description: "Custom CI config path for this project",
250
+ type: "string"
251
+ },
252
+ description: {
253
+ title: "Project description",
254
+ description: "Short project description",
255
+ type: "string"
256
+ },
257
+ merge_method: {
258
+ title: "Merge Method to use",
259
+ description: "Merge Methods (merge, rebase_merge, ff)",
260
+ type: "string",
261
+ enum: ["merge", "rebase_merge", "ff"]
262
+ },
263
+ squash_option: {
264
+ title: "Squash option",
265
+ description: "Set squash option for the project (never, always, default_on, default_off)",
266
+ type: "string",
267
+ enum: ["default_off", "default_on", "never", "always"]
268
+ },
269
+ topics: {
270
+ title: "Topic labels",
271
+ description: "Topic labels to apply on the repository",
272
+ type: "array",
273
+ items: {
274
+ type: "string"
275
+ }
276
+ },
277
+ visibility: {
278
+ title: "Project visibility",
279
+ description: "The visibility of the project. Can be private, internal, or public. The default value is private.",
280
+ type: "string",
281
+ enum: ["private", "public", "internal"]
282
+ }
283
+ }
284
+ },
285
+ branches: {
286
+ title: "Project branches settings",
287
+ type: "array",
288
+ items: {
289
+ type: "object",
290
+ required: ["name"],
291
+ properties: {
292
+ name: {
293
+ title: "Branch name",
294
+ type: "string"
295
+ },
296
+ protect: {
297
+ title: "Should branch be protected",
298
+ description: `Will mark branch as protected. The default value is 'false'`,
299
+ type: "boolean"
300
+ },
301
+ create: {
302
+ title: "Should branch be created",
303
+ description: `If branch does not exist, it will be created from provided ref. The default value is 'false'`,
304
+ type: "boolean"
305
+ },
306
+ ref: {
307
+ title: "Branch reference",
308
+ description: `Branch reference to create branch from. The default value is 'master'`,
309
+ type: "string"
310
+ }
311
+ }
312
+ }
313
+ },
314
+ projectVariables: {
315
+ title: "Project variables",
316
+ description: "Project variables settings based on Gitlab Project Environments API - https://docs.gitlab.com/ee/api/project_level_variables.html#create-a-variable",
317
+ type: "array",
318
+ items: {
319
+ type: "object",
320
+ required: ["key", "value"],
321
+ properties: {
322
+ key: {
323
+ title: "Variable key",
324
+ description: "The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed",
325
+ type: "string"
326
+ },
327
+ value: {
328
+ title: "Variable value",
329
+ description: "The value of a variable",
330
+ type: "string"
331
+ },
332
+ description: {
333
+ title: "Variable description",
334
+ description: `The description of the variable. The default value is 'null'`,
335
+ type: "string"
336
+ },
337
+ variable_type: {
338
+ title: "Variable type",
339
+ description: `The type of a variable. The default value is 'env_var'`,
340
+ type: "string",
341
+ enum: ["env_var", "file"]
342
+ },
343
+ protected: {
344
+ title: "Variable protection",
345
+ description: `Whether the variable is protected. The default value is 'false'`,
346
+ type: "boolean"
347
+ },
348
+ raw: {
349
+ title: "Variable raw",
350
+ description: `Whether the variable is in raw format. The default value is 'false'`,
351
+ type: "boolean"
352
+ },
353
+ environment_scope: {
354
+ title: "Variable environment scope",
355
+ description: `The environment_scope of the variable. The default value is '*'`,
356
+ type: "string"
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+ },
363
+ output: {
364
+ type: "object",
365
+ properties: {
366
+ remoteUrl: {
367
+ title: "A URL to the repository with the provider",
368
+ type: "string"
369
+ },
370
+ repoContentsUrl: {
371
+ title: "A URL to the root of the repository",
372
+ type: "string"
373
+ },
374
+ projectId: {
375
+ title: "The ID of the project",
376
+ type: "string"
377
+ },
378
+ commitHash: {
379
+ title: "The git commit hash of the initial commit",
380
+ type: "string"
381
+ }
382
+ }
383
+ }
527
384
  },
528
385
  async handler(ctx) {
529
- ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`);
530
386
  const {
531
- projectId,
532
- name = "tokenname",
533
- accessLevel = 40,
534
- scopes = ["read_repository"],
535
- expiresAt
387
+ repoUrl,
388
+ repoVisibility = "private",
389
+ defaultBranch = "master",
390
+ gitCommitMessage = "initial commit",
391
+ gitAuthorName,
392
+ gitAuthorEmail,
393
+ setUserAsOwner = false,
394
+ topics = [],
395
+ settings = {},
396
+ branches = [],
397
+ projectVariables = []
536
398
  } = ctx.input;
537
- const { token, integrationConfig } = getToken(ctx.input, integrations);
538
- if (!integrationConfig.config.token && token) {
399
+ const { owner, repo, host } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations);
400
+ if (!owner) {
539
401
  throw new errors.InputError(
540
- `No token available for host ${integrationConfig.config.baseUrl}`
402
+ `No owner provided for host: ${host}, and repo ${repo}`
541
403
  );
542
404
  }
543
- let api;
544
- if (!ctx.input.token) {
545
- api = new rest.Gitlab({
546
- host: integrationConfig.config.baseUrl,
547
- token
548
- });
549
- } else {
550
- api = new rest.Gitlab({
405
+ const integrationConfig = integrations.gitlab.byHost(host);
406
+ if (!integrationConfig) {
407
+ throw new errors.InputError(
408
+ `No matching integration configuration for host ${host}, please check your integrations config`
409
+ );
410
+ }
411
+ if (!integrationConfig.config.token && !ctx.input.token) {
412
+ throw new errors.InputError(`No token available for host ${host}`);
413
+ }
414
+ const token = ctx.input.token || integrationConfig.config.token;
415
+ const tokenType = ctx.input.token ? "oauthToken" : "token";
416
+ const client = new node.Gitlab({
417
+ host: integrationConfig.config.baseUrl,
418
+ [tokenType]: token
419
+ });
420
+ let targetNamespaceId;
421
+ try {
422
+ const namespaceResponse = await client.Namespaces.show(owner);
423
+ targetNamespaceId = namespaceResponse.id;
424
+ } catch (e) {
425
+ if (e.response && e.response.statusCode === 404) {
426
+ throw new errors.InputError(
427
+ `The namespace ${owner} is not found or the user doesn't have permissions to access it`
428
+ );
429
+ }
430
+ throw e;
431
+ }
432
+ const { id: userId } = await client.Users.current();
433
+ if (!targetNamespaceId) {
434
+ targetNamespaceId = userId;
435
+ }
436
+ const { id: projectId, http_url_to_repo } = await client.Projects.create({
437
+ namespace_id: targetNamespaceId,
438
+ name: repo,
439
+ visibility: repoVisibility,
440
+ ...topics.length ? { topics } : {},
441
+ ...Object.keys(settings).length ? { ...settings } : {}
442
+ });
443
+ if (setUserAsOwner && integrationConfig.config.token) {
444
+ const adminClient = new node.Gitlab({
551
445
  host: integrationConfig.config.baseUrl,
552
- oauthToken: token
446
+ token: integrationConfig.config.token
553
447
  });
448
+ await adminClient.ProjectMembers.add(projectId, userId, 50);
554
449
  }
555
- const response = await api.ProjectAccessTokens.create(
556
- projectId,
557
- name,
558
- scopes,
559
- {
560
- expiresAt: expiresAt || luxon.DateTime.now().plus({ days: 365 }).toISODate(),
561
- accessLevel
450
+ const remoteUrl = http_url_to_repo.replace(/\.git$/, "");
451
+ const repoContentsUrl = `${remoteUrl}/-/blob/${defaultBranch}`;
452
+ const gitAuthorInfo = {
453
+ name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
454
+ email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
455
+ };
456
+ const commitResult = await pluginScaffolderNode.initRepoAndPush({
457
+ dir: pluginScaffolderNode.getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
458
+ remoteUrl: http_url_to_repo,
459
+ defaultBranch,
460
+ auth: {
461
+ username: "oauth2",
462
+ password: token
463
+ },
464
+ logger: ctx.logger,
465
+ commitMessage: gitCommitMessage ? gitCommitMessage : config.getOptionalString("scaffolder.defaultCommitMessage"),
466
+ gitAuthorInfo
467
+ });
468
+ if (branches) {
469
+ for (const branch of branches) {
470
+ const {
471
+ name,
472
+ protect = false,
473
+ create = false,
474
+ ref = "master"
475
+ } = branch;
476
+ if (create) {
477
+ try {
478
+ await client.Branches.create(projectId, name, ref);
479
+ } catch (e) {
480
+ throw new errors.InputError(
481
+ `Branch creation failed for ${name}. ${printGitlabError(e)}`
482
+ );
483
+ }
484
+ ctx.logger.info(
485
+ `Branch ${name} created for ${projectId} with ref ${ref}`
486
+ );
487
+ }
488
+ if (protect) {
489
+ try {
490
+ await client.ProtectedBranches.protect(projectId, name);
491
+ } catch (e) {
492
+ throw new errors.InputError(
493
+ `Branch protection failed for ${name}. ${printGitlabError(e)}`
494
+ );
495
+ }
496
+ ctx.logger.info(`Branch ${name} protected for ${projectId}`);
497
+ }
562
498
  }
563
- );
564
- if (!response.token) {
565
- throw new Error("Could not create project access token");
566
499
  }
567
- ctx.output("access_token", response.token);
500
+ if (projectVariables) {
501
+ for (const variable of projectVariables) {
502
+ const variableWithDefaults = Object.assign(variable, {
503
+ variable_type: variable.variable_type ?? "env_var",
504
+ protected: variable.protected ?? false,
505
+ masked: variable.masked ?? false,
506
+ raw: variable.raw ?? false,
507
+ environment_scope: variable.environment_scope ?? "*"
508
+ });
509
+ try {
510
+ await client.ProjectVariables.create(
511
+ projectId,
512
+ variableWithDefaults
513
+ );
514
+ } catch (e) {
515
+ throw new errors.InputError(
516
+ `Environment variable creation failed for ${variableWithDefaults.key}. ${printGitlabError(e)}`
517
+ );
518
+ }
519
+ }
520
+ }
521
+ ctx.output("commitHash", commitResult?.commitHash);
522
+ ctx.output("remoteUrl", remoteUrl);
523
+ ctx.output("repoContentsUrl", repoContentsUrl);
524
+ ctx.output("projectId", projectId);
568
525
  }
569
526
  });
527
+ }
528
+ function printGitlabError(error) {
529
+ return JSON.stringify({ code: error.code, message: error.description });
530
+ }
531
+
532
+ const commonGitlabConfig = zod.z.object({
533
+ repoUrl: zod.z.string({ description: "Repository Location" }),
534
+ token: zod.z.string({ description: "The token to use for authorization to GitLab" }).optional()
535
+ });
536
+ const commonGitlabConfigExample = {
537
+ repoUrl: "gitlab.com?owner=namespace-or-owner&repo=project-name",
538
+ token: "${{ secrets.USER_OAUTH_TOKEN }}"
570
539
  };
571
540
 
572
- const examples$4 = [
573
- {
574
- description: "Creating a GitLab project variable of type env_var",
575
- example: yaml__default.default.stringify({
576
- steps: [
577
- {
578
- id: "createVariable",
579
- action: "gitlab:createGitlabProjectVariableAction",
580
- name: "Create GitLab Project Variable",
581
- input: {
582
- repoUrl: "gitlab.com?repo=repo&owner=owner",
583
- projectId: "123",
584
- key: "MY_VARIABLE",
585
- value: "my_value",
586
- variableType: "env_var"
587
- }
588
- }
589
- ]
590
- })
591
- },
592
- {
593
- description: "Creating a GitLab project variable of type file",
594
- example: yaml__default.default.stringify({
595
- steps: [
596
- {
597
- id: "createVariable",
598
- action: "gitlab:createGitlabProjectVariableAction",
599
- name: "Create GitLab Project Variable",
600
- input: {
601
- repoUrl: "gitlab.com?repo=repo&owner=owner",
602
- projectId: "123",
603
- key: "MY_VARIABLE",
604
- value: "my-file-content",
605
- variableType: "file"
606
- }
607
- }
608
- ]
609
- })
610
- },
611
- {
612
- description: "Create a GitLab project variable that is protected.",
613
- example: yaml__default.default.stringify({
614
- steps: [
615
- {
616
- id: "createVariable",
617
- action: "gitlab:createGitlabProjectVariableAction",
618
- name: "Create GitLab Project Variable",
619
- input: {
620
- repoUrl: "gitlab.com?repo=repo&owner=owner",
621
- projectId: "456",
622
- key: "MY_VARIABLE",
623
- value: "my_value",
624
- variableType: "env_var",
625
- variableProtected: true
626
- }
627
- }
628
- ]
629
- })
630
- },
541
+ const parseRepoHost = (repoUrl) => {
542
+ let parsed;
543
+ try {
544
+ parsed = new URL(`https://${repoUrl}`);
545
+ } catch (error) {
546
+ throw new errors.InputError(
547
+ `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`
548
+ );
549
+ }
550
+ return parsed.host;
551
+ };
552
+ const getToken = (config, integrations) => {
553
+ const host = parseRepoHost(config.repoUrl);
554
+ const integrationConfig = integrations.gitlab.byHost(host);
555
+ if (!integrationConfig) {
556
+ throw new errors.InputError(
557
+ `No matching integration configuration for host ${host}, please check your integrations config`
558
+ );
559
+ }
560
+ const token = config.token || integrationConfig.config.token;
561
+ return { token, integrationConfig };
562
+ };
563
+ const parseRepoUrl = (repoUrl, integrations) => {
564
+ let parsed;
565
+ try {
566
+ parsed = new URL(`https://${repoUrl}`);
567
+ } catch (error) {
568
+ throw new errors.InputError(
569
+ `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`
570
+ );
571
+ }
572
+ const host = parsed.host;
573
+ const owner = parsed.searchParams.get("owner") ?? void 0;
574
+ const repo = parsed.searchParams.get("repo");
575
+ const type = integrations.byHost(host)?.type;
576
+ if (!type) {
577
+ throw new errors.InputError(
578
+ `No matching integration configuration for host ${host}, please check your integrations config`
579
+ );
580
+ }
581
+ return { host, owner, repo };
582
+ };
583
+ function getClient(props) {
584
+ const { host, token, integrations } = props;
585
+ const integrationConfig = integrations.gitlab.byHost(host);
586
+ if (!integrationConfig) {
587
+ throw new errors.InputError(
588
+ `No matching integration configuration for host ${host}, please check your integrations config`
589
+ );
590
+ }
591
+ const { config } = integrationConfig;
592
+ if (!config.token && !token) {
593
+ throw new errors.InputError(`No token available for host ${host}`);
594
+ }
595
+ const requestToken = token || config.token;
596
+ const tokenType = token ? "oauthToken" : "token";
597
+ const gitlabOptions = {
598
+ host: config.baseUrl
599
+ };
600
+ gitlabOptions[tokenType] = requestToken;
601
+ return new rest.Gitlab(gitlabOptions);
602
+ }
603
+ function convertDate(inputDate, defaultDate) {
604
+ try {
605
+ return inputDate ? new Date(inputDate).toISOString() : new Date(defaultDate).toISOString();
606
+ } catch (error) {
607
+ throw new errors.InputError(`Error converting input date - ${error}`);
608
+ }
609
+ }
610
+ async function getTopLevelParentGroup(client, groupId) {
611
+ try {
612
+ const topParentGroup = await client.Groups.show(groupId);
613
+ if (topParentGroup.parent_id) {
614
+ return getTopLevelParentGroup(
615
+ client,
616
+ topParentGroup.parent_id
617
+ );
618
+ }
619
+ return topParentGroup;
620
+ } catch (error) {
621
+ throw new errors.InputError(
622
+ `Error finding top-level parent group ID: ${error.message}`
623
+ );
624
+ }
625
+ }
626
+ async function checkEpicScope(client, projectId, epicId) {
627
+ try {
628
+ const project = await client.Projects.show(projectId);
629
+ if (!project) {
630
+ throw new errors.InputError(
631
+ `Project with id ${projectId} not found. Check your GitLab instance.`
632
+ );
633
+ }
634
+ const topParentGroup = await getTopLevelParentGroup(
635
+ client,
636
+ project.namespace.id
637
+ );
638
+ if (!topParentGroup) {
639
+ throw new errors.InputError(`Couldn't find a suitable top-level parent group.`);
640
+ }
641
+ const epic = (await client.Epics.all(topParentGroup.id)).find(
642
+ (x) => x.id === epicId
643
+ );
644
+ if (!epic) {
645
+ throw new errors.InputError(
646
+ `Epic with id ${epicId} not found in the top-level parent group ${topParentGroup.name}.`
647
+ );
648
+ }
649
+ const epicGroup = await client.Groups.show(epic.group_id);
650
+ const projectNamespace = project.path_with_namespace;
651
+ return projectNamespace.startsWith(epicGroup.full_path);
652
+ } catch (error) {
653
+ throw new errors.InputError(`Could not find epic scope: ${error.message}`);
654
+ }
655
+ }
656
+
657
+ const examples$7 = [
631
658
  {
632
- description: "Create a GitLab project variable with masked flag as true",
659
+ description: "Creating a group at the top level",
633
660
  example: yaml__default.default.stringify({
634
661
  steps: [
635
662
  {
636
- id: "createVariable",
637
- action: "gitlab:createGitlabProjectVariableAction",
638
- name: "Create GitLab Project Variable",
663
+ id: "gitlabGroup",
664
+ name: "Group",
665
+ action: "gitlab:group:ensureExists",
639
666
  input: {
640
- repoUrl: "gitlab.com?repo=repo&owner=owner",
641
- projectId: "789",
642
- key: "DB_PASSWORD",
643
- value: "password123",
644
- variableType: "env_var",
645
- masked: true
667
+ repoUrl: "gitlab.com",
668
+ path: ["group1"]
646
669
  }
647
670
  }
648
671
  ]
649
672
  })
650
673
  },
651
674
  {
652
- description: "Create a GitLab project variable that is expandable.",
675
+ description: "Create a group nested within another group",
653
676
  example: yaml__default.default.stringify({
654
677
  steps: [
655
678
  {
656
- id: "createVariable",
657
- action: "gitlab:projectVariable:create",
658
- name: "Create GitLab Project Variable",
679
+ id: "gitlabGroup",
680
+ name: "Group",
681
+ action: "gitlab:group:ensureExists",
659
682
  input: {
660
- repoUrl: "gitlab.com?repo=repo&owner=owner",
661
- projectId: "123",
662
- key: "MY_VARIABLE",
663
- value: "my_value",
664
- variableType: "env_var",
665
- raw: true
683
+ repoUrl: "gitlab.com",
684
+ path: ["group1", "group2"]
666
685
  }
667
686
  }
668
687
  ]
669
688
  })
670
689
  },
671
690
  {
672
- description: "Create a GitLab project variable with a specific environment scope.",
691
+ description: "Create a group nested within multiple other groups",
673
692
  example: yaml__default.default.stringify({
674
693
  steps: [
675
694
  {
676
- id: "createVariable",
677
- action: "gitlab:projectVariable:create",
678
- name: "Create GitLab Project Variable",
695
+ id: "gitlabGroup",
696
+ name: "Group",
697
+ action: "gitlab:group:ensureExists",
679
698
  input: {
680
- repoUrl: "gitlab.com?repo=repo&owner=owner",
681
- projectId: "123",
682
- key: "MY_VARIABLE",
683
- value: "my_value",
684
- variableType: "env_var",
685
- environmentScope: "production"
699
+ repoUrl: "gitlab.com",
700
+ path: ["group1", "group2", "group3"]
686
701
  }
687
702
  }
688
703
  ]
689
704
  })
690
705
  },
691
706
  {
692
- description: "Create a GitLab project variable with a wildcard environment scope.",
707
+ description: "Create a group in dry run mode",
693
708
  example: yaml__default.default.stringify({
694
709
  steps: [
695
710
  {
696
- id: "createVariable",
697
- action: "gitlab:projectVariable:create",
698
- name: "Create GitLab Project Variable",
711
+ id: "gitlabGroup",
712
+ name: "Group",
713
+ action: "gitlab:group:ensureExists",
714
+ isDryRun: true,
699
715
  input: {
700
- repoUrl: "gitlab.com?repo=repo&owner=owner",
701
- projectId: "123",
702
- key: "MY_VARIABLE",
703
- value: "my_value",
704
- variableType: "env_var",
705
- environmentScope: "*"
716
+ repoUrl: "https://gitlab.com/my-repo",
717
+ path: ["group1", "group2", "group3"]
706
718
  }
707
719
  }
708
720
  ]
@@ -710,61 +722,68 @@ const examples$4 = [
710
722
  }
711
723
  ];
712
724
 
713
- const createGitlabProjectVariableAction = (options) => {
725
+ const createGitlabGroupEnsureExistsAction = (options) => {
714
726
  const { integrations } = options;
715
727
  return pluginScaffolderNode.createTemplateAction({
716
- id: "gitlab:projectVariable:create",
717
- examples: examples$4,
728
+ id: "gitlab:group:ensureExists",
729
+ description: "Ensures a Gitlab group exists",
730
+ supportsDryRun: true,
731
+ examples: examples$7,
718
732
  schema: {
719
733
  input: commonGitlabConfig.merge(
720
734
  zod.z.object({
721
- projectId: zod.z.union([zod.z.number(), zod.z.string()], {
722
- description: "Project ID"
723
- }),
724
- key: zod.z.string({
725
- description: "The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed"
726
- }).regex(/^[A-Za-z0-9_]{1,255}$/),
727
- value: zod.z.string({ description: "The value of a variable" }),
728
- variableType: zod.z.string({
729
- description: "Variable Type (env_var or file)"
730
- }),
731
- variableProtected: zod.z.boolean({ description: "Whether the variable is protected" }).default(false).optional(),
732
- masked: zod.z.boolean({ description: "Whether the variable is masked" }).default(false).optional(),
733
- raw: zod.z.boolean({ description: "Whether the variable is expandable" }).default(false).optional(),
734
- environmentScope: zod.z.string({ description: "The environment_scope of the variable" }).default("*").optional()
735
+ path: zod.z.array(zod.z.string(), {
736
+ description: "A path of group names that is ensured to exist"
737
+ }).min(1)
735
738
  })
736
- )
739
+ ),
740
+ output: zod.z.object({
741
+ groupId: zod.z.number({ description: "The id of the innermost sub-group" }).optional()
742
+ })
737
743
  },
738
744
  async handler(ctx) {
739
- const {
740
- projectId,
741
- key,
742
- value,
743
- variableType,
744
- variableProtected = false,
745
- masked = false,
746
- raw = false,
747
- environmentScope = "*"
748
- } = ctx.input;
745
+ if (ctx.isDryRun) {
746
+ ctx.output("groupId", 42);
747
+ return;
748
+ }
749
+ const { path } = ctx.input;
749
750
  const { token, integrationConfig } = getToken(ctx.input, integrations);
750
751
  const api = new node.Gitlab({
751
752
  host: integrationConfig.config.baseUrl,
752
753
  token
753
754
  });
754
- await api.ProjectVariables.create(projectId, {
755
- key,
756
- value,
757
- variable_type: variableType,
758
- protected: variableProtected,
759
- masked,
760
- raw,
761
- environment_scope: environmentScope
762
- });
755
+ let currentPath = null;
756
+ let parent = null;
757
+ for (const pathElement of path) {
758
+ const fullPath = currentPath ? `${currentPath}/${pathElement}` : pathElement;
759
+ const result = await api.Groups.search(
760
+ fullPath
761
+ );
762
+ const subGroup = result.find(
763
+ (searchPathElem) => searchPathElem.full_path === fullPath
764
+ );
765
+ if (!subGroup) {
766
+ ctx.logger.info(`creating missing group ${fullPath}`);
767
+ parent = await api.Groups.create(
768
+ pathElement,
769
+ pathElement,
770
+ parent ? {
771
+ parent_id: parent.id
772
+ } : {}
773
+ );
774
+ } else {
775
+ parent = subGroup;
776
+ }
777
+ currentPath = fullPath;
778
+ }
779
+ if (parent !== null) {
780
+ ctx.output("groupId", parent?.id);
781
+ }
763
782
  }
764
783
  });
765
784
  };
766
785
 
767
- const examples$3 = [
786
+ const examples$6 = [
768
787
  {
769
788
  description: "Create a GitLab issue with minimal options",
770
789
  example: yaml__default.default.stringify({
@@ -885,7 +904,7 @@ const createGitlabIssueAction = (options) => {
885
904
  return pluginScaffolderNode.createTemplateAction({
886
905
  id: "gitlab:issues:create",
887
906
  description: "Creates a Gitlab issue.",
888
- examples: examples$3,
907
+ examples: examples$6,
889
908
  schema: {
890
909
  input: commonGitlabConfig.merge(issueInputProperties),
891
910
  output: issueOutputProperties
@@ -966,144 +985,356 @@ const createGitlabIssueAction = (options) => {
966
985
  });
967
986
  };
968
987
 
969
- const examples$2 = [
988
+ function createGitlabApi(options) {
989
+ const { integrations, token: providedToken, repoUrl } = options;
990
+ const { host } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations);
991
+ const integrationConfig = integrations.gitlab.byHost(host);
992
+ if (!integrationConfig) {
993
+ throw new errors.InputError(
994
+ `No matching integration configuration for host ${host}, please check your integrations config`
995
+ );
996
+ }
997
+ if (!integrationConfig.config.token && !providedToken) {
998
+ throw new errors.InputError(`No token available for host ${host}`);
999
+ }
1000
+ const token = providedToken ?? integrationConfig.config.token;
1001
+ const tokenType = providedToken ? "oauthToken" : "token";
1002
+ return new node.Gitlab({
1003
+ host: integrationConfig.config.baseUrl,
1004
+ [tokenType]: token
1005
+ });
1006
+ }
1007
+
1008
+ const examples$5 = [
970
1009
  {
971
- description: "Initializes a git repository with the content in the workspace, and publishes it to GitLab with the default configuration.",
1010
+ description: "Create a merge request with a specific assignee",
972
1011
  example: yaml__default.default.stringify({
973
1012
  steps: [
974
1013
  {
975
- id: "publish",
976
- action: "publish:gitlab",
977
- name: "Publish to GitLab",
1014
+ id: "createMergeRequest",
1015
+ action: "publish:gitlab:merge-request",
1016
+ name: "Create a Merge Request",
978
1017
  input: {
979
- repoUrl: "gitlab.com?repo=project_name&owner=group_name"
1018
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1019
+ title: "Create my new MR",
1020
+ description: "This MR is really good",
1021
+ sourcePath: "./path/to/my/changes",
1022
+ branchName: "new-mr",
1023
+ assignee: "my-assignee"
980
1024
  }
981
1025
  }
982
1026
  ]
983
1027
  })
984
1028
  },
985
1029
  {
986
- description: "Add a description.",
1030
+ description: "Create a merge request with removal of source branch after merge",
987
1031
  example: yaml__default.default.stringify({
988
1032
  steps: [
989
1033
  {
990
- id: "publish",
991
- action: "publish:gitlab",
992
- name: "Publish to GitLab",
1034
+ id: "createMergeRequest",
1035
+ action: "publish:gitlab:merge-request",
1036
+ name: "Create a Merge Request",
993
1037
  input: {
994
- repoUrl: "gitlab.com?repo=project_name&owner=group_name",
995
- description: "Initialize a git repository"
1038
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1039
+ title: "Create my new MR",
1040
+ description: "This MR is really good",
1041
+ sourcePath: "./path/to/my/changes",
1042
+ branchName: "new-mr",
1043
+ removeSourceBranch: true
996
1044
  }
997
1045
  }
998
1046
  ]
999
1047
  })
1000
1048
  },
1001
1049
  {
1002
- description: "Initializes a GitLab repository with an initial commit message, if not set defaults to `initial commit`.",
1050
+ description: "Create a merge request with a target branch",
1003
1051
  example: yaml__default.default.stringify({
1004
1052
  steps: [
1005
1053
  {
1006
- id: "publish",
1007
- action: "publish:gitlab",
1008
- name: "Publish to GitLab",
1054
+ id: "createMergeRequest",
1055
+ action: "publish:gitlab:merge-request",
1056
+ name: "Create a Merge Request",
1009
1057
  input: {
1010
- repoUrl: "gitlab.com?repo=project_name&owner=group_name",
1011
- description: "Initialize a git repository",
1012
- gitCommitMessage: "Started a project."
1058
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1059
+ title: "Create my new MR",
1060
+ description: "This MR is really good",
1061
+ sourcePath: "./path/to/my/changes",
1062
+ branchName: "new-mr",
1063
+ targetBranchName: "test",
1064
+ targetPath: "Subdirectory"
1013
1065
  }
1014
1066
  }
1015
1067
  ]
1016
1068
  })
1017
1069
  },
1018
1070
  {
1019
- description: "Initializes a GitLab repository with aditional settings.",
1071
+ description: "Create a merge request with a commit action as create",
1020
1072
  example: yaml__default.default.stringify({
1021
1073
  steps: [
1022
1074
  {
1023
- id: "publish",
1024
- action: "publish:gitlab",
1025
- name: "Publish to GitLab",
1075
+ id: "createMergeRequest",
1076
+ action: "publish:gitlab:merge-request",
1077
+ name: "Create a Merge Request",
1026
1078
  input: {
1027
- repoUrl: "gitlab.com?repo=project_name&owner=group_name",
1028
- settings: {
1029
- ci_config_path: ".gitlab-ci.yml",
1030
- visibility: "public"
1031
- }
1079
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1080
+ title: "Create my new MR",
1081
+ branchName: "new-mr",
1082
+ description: "MR description",
1083
+ commitAction: "create",
1084
+ targetPath: "source"
1032
1085
  }
1033
1086
  }
1034
1087
  ]
1035
1088
  })
1036
1089
  },
1037
1090
  {
1038
- description: "Initializes a GitLab repository with fast forward merge and always squash settings.",
1091
+ description: "Create a merge request with a commit action as delete",
1039
1092
  example: yaml__default.default.stringify({
1040
1093
  steps: [
1041
1094
  {
1042
- id: "publish",
1043
- action: "publish:gitlab",
1044
- name: "Publish to GitLab",
1095
+ id: "createMergeRequest",
1096
+ action: "publish:gitlab:merge-request",
1097
+ name: "Create a Merge Request",
1045
1098
  input: {
1046
- repoUrl: "gitlab.com?repo=project_name&owner=group_name",
1047
- settings: {
1048
- merge_method: "ff",
1049
- squash_option: "always"
1050
- }
1099
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1100
+ title: "Create my new MR",
1101
+ branchName: "new-mr",
1102
+ description: "MR description",
1103
+ commitAction: "delete",
1104
+ targetPath: "source"
1051
1105
  }
1052
1106
  }
1053
1107
  ]
1054
1108
  })
1055
1109
  },
1056
1110
  {
1057
- description: "Initializes a GitLab repository with branch settings.",
1111
+ description: "Create a merge request with a commit action as update",
1058
1112
  example: yaml__default.default.stringify({
1059
1113
  steps: [
1060
1114
  {
1061
- id: "publish",
1062
- action: "publish:gitlab",
1063
- name: "Publish to GitLab",
1115
+ id: "createMergeRequest",
1116
+ action: "publish:gitlab:merge-request",
1117
+ name: "Create a Merge Request",
1064
1118
  input: {
1065
- repoUrl: "gitlab.com?repo=project_name&owner=group_name",
1066
- branches: [
1067
- {
1068
- name: "dev",
1069
- create: true,
1070
- protected: true,
1071
- ref: "master"
1072
- },
1073
- {
1074
- name: "master",
1075
- protected: true
1076
- }
1077
- ]
1119
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1120
+ title: "Create my new MR",
1121
+ branchName: "new-mr",
1122
+ description: "MR description",
1123
+ commitAction: "update",
1124
+ targetPath: "source"
1078
1125
  }
1079
1126
  }
1080
1127
  ]
1081
1128
  })
1082
- },
1129
+ }
1130
+ ];
1131
+
1132
+ const createPublishGitlabMergeRequestAction = (options) => {
1133
+ const { integrations } = options;
1134
+ return pluginScaffolderNode.createTemplateAction({
1135
+ id: "publish:gitlab:merge-request",
1136
+ examples: examples$5,
1137
+ schema: {
1138
+ input: {
1139
+ required: ["repoUrl", "branchName"],
1140
+ type: "object",
1141
+ properties: {
1142
+ repoUrl: {
1143
+ type: "string",
1144
+ title: "Repository Location",
1145
+ description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`
1146
+ },
1147
+ /** @deprecated projectID is passed as query parameters in the repoUrl */
1148
+ projectid: {
1149
+ type: "string",
1150
+ title: "projectid",
1151
+ description: "Project ID/Name(slug) of the Gitlab Project"
1152
+ },
1153
+ title: {
1154
+ type: "string",
1155
+ title: "Merge Request Name",
1156
+ description: "The name for the merge request"
1157
+ },
1158
+ description: {
1159
+ type: "string",
1160
+ title: "Merge Request Description",
1161
+ description: "The description of the merge request"
1162
+ },
1163
+ branchName: {
1164
+ type: "string",
1165
+ title: "Source Branch Name",
1166
+ description: "The source branch name of the merge request"
1167
+ },
1168
+ targetBranchName: {
1169
+ type: "string",
1170
+ title: "Target Branch Name",
1171
+ description: "The target branch name of the merge request"
1172
+ },
1173
+ sourcePath: {
1174
+ type: "string",
1175
+ title: "Working Subdirectory",
1176
+ description: "Subdirectory of working directory to copy changes from"
1177
+ },
1178
+ targetPath: {
1179
+ type: "string",
1180
+ title: "Repository Subdirectory",
1181
+ description: "Subdirectory of repository to apply changes to"
1182
+ },
1183
+ token: {
1184
+ title: "Authentication Token",
1185
+ type: "string",
1186
+ description: "The token to use for authorization to GitLab"
1187
+ },
1188
+ commitAction: {
1189
+ title: "Commit action",
1190
+ type: "string",
1191
+ enum: ["create", "update", "delete"],
1192
+ description: "The action to be used for git commit. Defaults to create."
1193
+ },
1194
+ removeSourceBranch: {
1195
+ title: "Delete source branch",
1196
+ type: "boolean",
1197
+ description: "Option to delete source branch once the MR has been merged. Default: false"
1198
+ },
1199
+ assignee: {
1200
+ title: "Merge Request Assignee",
1201
+ type: "string",
1202
+ description: "User this merge request will be assigned to"
1203
+ }
1204
+ }
1205
+ },
1206
+ output: {
1207
+ type: "object",
1208
+ properties: {
1209
+ targetBranchName: {
1210
+ title: "Target branch name of the merge request",
1211
+ type: "string"
1212
+ },
1213
+ projectid: {
1214
+ title: "Gitlab Project id/Name(slug)",
1215
+ type: "string"
1216
+ },
1217
+ projectPath: {
1218
+ title: "Gitlab Project path",
1219
+ type: "string"
1220
+ },
1221
+ mergeRequestUrl: {
1222
+ title: "MergeRequest(MR) URL",
1223
+ type: "string",
1224
+ description: "Link to the merge request in GitLab"
1225
+ }
1226
+ }
1227
+ }
1228
+ },
1229
+ async handler(ctx) {
1230
+ const {
1231
+ assignee,
1232
+ branchName,
1233
+ targetBranchName,
1234
+ description,
1235
+ repoUrl,
1236
+ removeSourceBranch,
1237
+ targetPath,
1238
+ sourcePath,
1239
+ title,
1240
+ token
1241
+ } = ctx.input;
1242
+ const { owner, repo, project } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations);
1243
+ const repoID = project ? project : `${owner}/${repo}`;
1244
+ const api = createGitlabApi({
1245
+ integrations,
1246
+ token,
1247
+ repoUrl
1248
+ });
1249
+ let assigneeId = void 0;
1250
+ if (assignee !== void 0) {
1251
+ try {
1252
+ const assigneeUser = await api.Users.username(assignee);
1253
+ assigneeId = assigneeUser[0].id;
1254
+ } catch (e) {
1255
+ ctx.logger.warn(
1256
+ `Failed to find gitlab user id for ${assignee}: ${e}. Proceeding with MR creation without an assignee.`
1257
+ );
1258
+ }
1259
+ }
1260
+ let fileRoot;
1261
+ if (sourcePath) {
1262
+ fileRoot = backendPluginApi.resolveSafeChildPath(ctx.workspacePath, sourcePath);
1263
+ } else if (targetPath) {
1264
+ fileRoot = backendPluginApi.resolveSafeChildPath(ctx.workspacePath, targetPath);
1265
+ } else {
1266
+ fileRoot = ctx.workspacePath;
1267
+ }
1268
+ const fileContents = await pluginScaffolderNode.serializeDirectoryContents(fileRoot, {
1269
+ gitignore: true
1270
+ });
1271
+ const actions = fileContents.map((file) => ({
1272
+ action: ctx.input.commitAction ?? "create",
1273
+ filePath: targetPath ? path__default.default.posix.join(targetPath, file.path) : file.path,
1274
+ encoding: "base64",
1275
+ content: file.content.toString("base64"),
1276
+ execute_filemode: file.executable
1277
+ }));
1278
+ let targetBranch = targetBranchName;
1279
+ if (!targetBranch) {
1280
+ const projects = await api.Projects.show(repoID);
1281
+ const { default_branch: defaultBranch } = projects;
1282
+ targetBranch = defaultBranch;
1283
+ }
1284
+ try {
1285
+ await api.Branches.create(repoID, branchName, String(targetBranch));
1286
+ } catch (e) {
1287
+ throw new errors.InputError(
1288
+ `The branch creation failed. Please check that your repo does not already contain a branch named '${branchName}'. ${e}`
1289
+ );
1290
+ }
1291
+ try {
1292
+ await api.Commits.create(repoID, branchName, ctx.input.title, actions);
1293
+ } catch (e) {
1294
+ throw new errors.InputError(
1295
+ `Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${e}`
1296
+ );
1297
+ }
1298
+ try {
1299
+ const mergeRequestUrl = await api.MergeRequests.create(
1300
+ repoID,
1301
+ branchName,
1302
+ String(targetBranch),
1303
+ title,
1304
+ {
1305
+ description,
1306
+ removeSourceBranch: removeSourceBranch ? removeSourceBranch : false,
1307
+ assigneeId
1308
+ }
1309
+ ).then((mergeRequest) => {
1310
+ return mergeRequest.web_url;
1311
+ });
1312
+ ctx.output("projectid", repoID);
1313
+ ctx.output("targetBranchName", targetBranch);
1314
+ ctx.output("projectPath", repoID);
1315
+ ctx.output("mergeRequestUrl", mergeRequestUrl);
1316
+ } catch (e) {
1317
+ throw new errors.InputError(`Merge request creation failed${e}`);
1318
+ }
1319
+ }
1320
+ });
1321
+ };
1322
+
1323
+ const examples$4 = [
1083
1324
  {
1084
- description: "Initializes a GitLab repository with environment variables.",
1325
+ description: "Trigger a GitLab Project Pipeline",
1085
1326
  example: yaml__default.default.stringify({
1086
1327
  steps: [
1087
1328
  {
1088
- id: "publish",
1089
- action: "publish:gitlab",
1090
- name: "Publish to GitLab",
1329
+ id: "triggerPipeline",
1330
+ name: "Trigger Project Pipeline",
1331
+ action: "gitlab:pipeline:trigger",
1091
1332
  input: {
1092
- repoUrl: "gitlab.com?repo=project_name&owner=group_name",
1093
- projectVariables: [
1094
- {
1095
- key: "key1",
1096
- value: "value1",
1097
- protected: true,
1098
- masked: false
1099
- },
1100
- {
1101
- key: "key2",
1102
- value: "value2",
1103
- protected: true,
1104
- masked: false
1105
- }
1106
- ]
1333
+ ...commonGitlabConfigExample,
1334
+ projectId: 12,
1335
+ tokenDescription: "This is the text that will appear in the pipeline token",
1336
+ token: "glpt-xxxxxxxxxxxx",
1337
+ branch: "main"
1107
1338
  }
1108
1339
  }
1109
1340
  ]
@@ -1111,509 +1342,509 @@ const examples$2 = [
1111
1342
  }
1112
1343
  ];
1113
1344
 
1114
- function createPublishGitlabAction(options) {
1115
- const { integrations, config } = options;
1345
+ const pipelineInputProperties = zod.z.object({
1346
+ projectId: zod.z.number().describe("Project Id"),
1347
+ tokenDescription: zod.z.string().describe("Pipeline token description"),
1348
+ branch: zod.z.string().describe("Project branch")
1349
+ });
1350
+ const pipelineOutputProperties = zod.z.object({
1351
+ pipelineUrl: zod.z.string({ description: "Pipeline Url" })
1352
+ });
1353
+ const createTriggerGitlabPipelineAction = (options) => {
1354
+ const { integrations } = options;
1116
1355
  return pluginScaffolderNode.createTemplateAction({
1117
- id: "publish:gitlab",
1118
- description: "Initializes a git repository of the content in the workspace, and publishes it to GitLab.",
1119
- examples: examples$2,
1356
+ id: "gitlab:pipeline:trigger",
1357
+ description: "Triggers a GitLab Pipeline.",
1358
+ examples: examples$4,
1120
1359
  schema: {
1121
- input: {
1122
- type: "object",
1123
- required: ["repoUrl"],
1124
- properties: {
1125
- repoUrl: {
1126
- title: "Repository Location",
1127
- type: "string",
1128
- description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`
1129
- },
1130
- repoVisibility: {
1131
- title: "Repository Visibility",
1132
- description: `Sets the visibility of the repository. The default value is 'private'. (deprecated, use settings.visibility instead)`,
1133
- type: "string",
1134
- enum: ["private", "public", "internal"]
1135
- },
1136
- defaultBranch: {
1137
- title: "Default Branch",
1138
- type: "string",
1139
- description: `Sets the default branch on the repository. The default value is 'master'`
1140
- },
1141
- gitCommitMessage: {
1142
- title: "Git Commit Message",
1143
- type: "string",
1144
- description: `Sets the commit message on the repository. The default value is 'initial commit'`
1145
- },
1146
- gitAuthorName: {
1147
- title: "Default Author Name",
1148
- type: "string",
1149
- description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
1150
- },
1151
- gitAuthorEmail: {
1152
- title: "Default Author Email",
1153
- type: "string",
1154
- description: `Sets the default author email for the commit.`
1155
- },
1156
- sourcePath: {
1157
- title: "Source Path",
1158
- description: "Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.",
1159
- type: "string"
1160
- },
1161
- token: {
1162
- title: "Authentication Token",
1163
- type: "string",
1164
- description: "The token to use for authorization to GitLab"
1165
- },
1166
- setUserAsOwner: {
1167
- title: "Set User As Owner",
1168
- type: "boolean",
1169
- description: "Set the token user as owner of the newly created repository. Requires a token authorized to do the edit in the integration configuration for the matching host"
1170
- },
1171
- topics: {
1172
- title: "Topic labels",
1173
- description: "Topic labels to apply on the repository. (deprecated, use settings.topics instead)",
1174
- type: "array",
1175
- items: {
1176
- type: "string"
1177
- }
1178
- },
1179
- settings: {
1180
- title: "Project settings",
1181
- description: "Additional project settings, based on https://docs.gitlab.com/ee/api/projects.html#create-project attributes",
1182
- type: "object",
1183
- properties: {
1184
- path: {
1185
- title: "Project path",
1186
- description: "Repository name for new project. Generated based on name if not provided (generated as lowercase with dashes).",
1187
- type: "string"
1188
- },
1189
- auto_devops_enabled: {
1190
- title: "Auto DevOps enabled",
1191
- description: "Enable Auto DevOps for this project",
1192
- type: "boolean"
1193
- },
1194
- ci_config_path: {
1195
- title: "CI config path",
1196
- description: "Custom CI config path for this project",
1197
- type: "string"
1198
- },
1199
- description: {
1200
- title: "Project description",
1201
- description: "Short project description",
1202
- type: "string"
1203
- },
1204
- merge_method: {
1205
- title: "Merge Method to use",
1206
- description: "Merge Methods (merge, rebase_merge, ff)",
1207
- type: "string",
1208
- enum: ["merge", "rebase_merge", "ff"]
1209
- },
1210
- squash_option: {
1211
- title: "Squash option",
1212
- description: "Set squash option for the project (never, always, default_on, default_off)",
1213
- type: "string",
1214
- enum: ["default_off", "default_on", "never", "always"]
1215
- },
1216
- topics: {
1217
- title: "Topic labels",
1218
- description: "Topic labels to apply on the repository",
1219
- type: "array",
1220
- items: {
1221
- type: "string"
1222
- }
1223
- },
1224
- visibility: {
1225
- title: "Project visibility",
1226
- description: "The visibility of the project. Can be private, internal, or public. The default value is private.",
1227
- type: "string",
1228
- enum: ["private", "public", "internal"]
1229
- }
1230
- }
1231
- },
1232
- branches: {
1233
- title: "Project branches settings",
1234
- type: "array",
1235
- items: {
1236
- type: "object",
1237
- required: ["name"],
1238
- properties: {
1239
- name: {
1240
- title: "Branch name",
1241
- type: "string"
1242
- },
1243
- protect: {
1244
- title: "Should branch be protected",
1245
- description: `Will mark branch as protected. The default value is 'false'`,
1246
- type: "boolean"
1247
- },
1248
- create: {
1249
- title: "Should branch be created",
1250
- description: `If branch does not exist, it will be created from provided ref. The default value is 'false'`,
1251
- type: "boolean"
1252
- },
1253
- ref: {
1254
- title: "Branch reference",
1255
- description: `Branch reference to create branch from. The default value is 'master'`,
1256
- type: "string"
1257
- }
1258
- }
1259
- }
1260
- },
1261
- projectVariables: {
1262
- title: "Project variables",
1263
- description: "Project variables settings based on Gitlab Project Environments API - https://docs.gitlab.com/ee/api/project_level_variables.html#create-a-variable",
1264
- type: "array",
1265
- items: {
1266
- type: "object",
1267
- required: ["key", "value"],
1268
- properties: {
1269
- key: {
1270
- title: "Variable key",
1271
- description: "The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed",
1272
- type: "string"
1273
- },
1274
- value: {
1275
- title: "Variable value",
1276
- description: "The value of a variable",
1277
- type: "string"
1278
- },
1279
- description: {
1280
- title: "Variable description",
1281
- description: `The description of the variable. The default value is 'null'`,
1282
- type: "string"
1283
- },
1284
- variable_type: {
1285
- title: "Variable type",
1286
- description: `The type of a variable. The default value is 'env_var'`,
1287
- type: "string",
1288
- enum: ["env_var", "file"]
1289
- },
1290
- protected: {
1291
- title: "Variable protection",
1292
- description: `Whether the variable is protected. The default value is 'false'`,
1293
- type: "boolean"
1294
- },
1295
- raw: {
1296
- title: "Variable raw",
1297
- description: `Whether the variable is in raw format. The default value is 'false'`,
1298
- type: "boolean"
1299
- },
1300
- environment_scope: {
1301
- title: "Variable environment scope",
1302
- description: `The environment_scope of the variable. The default value is '*'`,
1303
- type: "string"
1304
- }
1305
- }
1306
- }
1360
+ input: commonGitlabConfig.merge(pipelineInputProperties),
1361
+ output: pipelineOutputProperties
1362
+ },
1363
+ async handler(ctx) {
1364
+ let pipelineTokenResponse = null;
1365
+ const { repoUrl, projectId, tokenDescription, token, branch } = commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input);
1366
+ const { host } = parseRepoUrl(repoUrl, integrations);
1367
+ const api = getClient({ host, integrations, token });
1368
+ try {
1369
+ pipelineTokenResponse = await api.PipelineTriggerTokens.create(
1370
+ projectId,
1371
+ tokenDescription
1372
+ );
1373
+ if (!pipelineTokenResponse.token) {
1374
+ ctx.logger.error("Failed to create pipeline token.");
1375
+ return;
1376
+ }
1377
+ ctx.logger.info(
1378
+ `Pipeline token id ${pipelineTokenResponse.id} created.`
1379
+ );
1380
+ const pipelineTriggerResponse = await api.PipelineTriggerTokens.trigger(
1381
+ projectId,
1382
+ branch,
1383
+ pipelineTokenResponse.token
1384
+ );
1385
+ if (!pipelineTriggerResponse.id) {
1386
+ ctx.logger.error("Failed to trigger pipeline.");
1387
+ return;
1388
+ }
1389
+ ctx.logger.info(`Pipeline id ${pipelineTriggerResponse.id} triggered.`);
1390
+ ctx.output("pipelineUrl", pipelineTriggerResponse.web_url);
1391
+ } catch (error) {
1392
+ if (error instanceof zod.z.ZodError) {
1393
+ throw new errors.InputError(`Validation error: ${error.message}`, {
1394
+ validationErrors: error.errors
1395
+ });
1396
+ }
1397
+ throw new errors.InputError(`Failed to trigger Pipeline: ${error.message}`);
1398
+ } finally {
1399
+ if (pipelineTokenResponse && pipelineTokenResponse.id) {
1400
+ try {
1401
+ await api.PipelineTriggerTokens.remove(
1402
+ projectId,
1403
+ pipelineTokenResponse.id
1404
+ );
1405
+ ctx.logger.info(
1406
+ `Deleted pipeline token ${pipelineTokenResponse.id}.`
1407
+ );
1408
+ } catch (error) {
1409
+ ctx.logger.error(
1410
+ `Failed to delete pipeline token id ${pipelineTokenResponse.id}.`
1411
+ );
1412
+ }
1413
+ }
1414
+ }
1415
+ }
1416
+ });
1417
+ };
1418
+
1419
+ const examples$3 = [
1420
+ {
1421
+ description: "Create a GitLab project access token with minimal options.",
1422
+ example: yaml__default.default.stringify({
1423
+ steps: [
1424
+ {
1425
+ id: "createAccessToken",
1426
+ action: "gitlab:projectAccessToken:create",
1427
+ name: "Create GitLab Project Access Token",
1428
+ input: {
1429
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1430
+ projectId: "456"
1307
1431
  }
1308
1432
  }
1309
- },
1310
- output: {
1311
- type: "object",
1312
- properties: {
1313
- remoteUrl: {
1314
- title: "A URL to the repository with the provider",
1315
- type: "string"
1316
- },
1317
- repoContentsUrl: {
1318
- title: "A URL to the root of the repository",
1319
- type: "string"
1320
- },
1321
- projectId: {
1322
- title: "The ID of the project",
1323
- type: "string"
1324
- },
1325
- commitHash: {
1326
- title: "The git commit hash of the initial commit",
1327
- type: "string"
1433
+ ]
1434
+ })
1435
+ },
1436
+ {
1437
+ description: "Create a GitLab project access token with custom scopes.",
1438
+ example: yaml__default.default.stringify({
1439
+ steps: [
1440
+ {
1441
+ id: "createAccessToken",
1442
+ action: "gitlab:projectAccessToken:create",
1443
+ name: "Create GitLab Project Access Token",
1444
+ input: {
1445
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1446
+ projectId: "789",
1447
+ scopes: ["read_registry", "write_repository"]
1328
1448
  }
1329
1449
  }
1330
- }
1450
+ ]
1451
+ })
1452
+ },
1453
+ {
1454
+ description: "Create a GitLab project access token with a specified name.",
1455
+ example: yaml__default.default.stringify({
1456
+ steps: [
1457
+ {
1458
+ id: "createAccessToken",
1459
+ action: "gitlab:projectAccessToken:create",
1460
+ name: "Create GitLab Project Access Token",
1461
+ input: {
1462
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1463
+ projectId: "101112",
1464
+ name: "my-custom-token"
1465
+ }
1466
+ }
1467
+ ]
1468
+ })
1469
+ },
1470
+ {
1471
+ description: "Create a GitLab project access token with a numeric project ID.",
1472
+ example: yaml__default.default.stringify({
1473
+ steps: [
1474
+ {
1475
+ id: "createAccessToken",
1476
+ action: "gitlab:projectAccessToken:create",
1477
+ name: "Create GitLab Project Access Token",
1478
+ input: {
1479
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1480
+ projectId: 42
1481
+ }
1482
+ }
1483
+ ]
1484
+ })
1485
+ },
1486
+ {
1487
+ description: "Create a GitLab project access token with a specified expired Date.",
1488
+ example: yaml__default.default.stringify({
1489
+ steps: [
1490
+ {
1491
+ id: "createAccessToken",
1492
+ action: "gitlab:projectAccessToken:create",
1493
+ name: "Create GitLab Project Access Token",
1494
+ input: {
1495
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1496
+ projectId: "123",
1497
+ expiresAt: "2024-06-25"
1498
+ }
1499
+ }
1500
+ ]
1501
+ })
1502
+ }
1503
+ ];
1504
+
1505
+ const createGitlabProjectAccessTokenAction = (options) => {
1506
+ const { integrations } = options;
1507
+ return pluginScaffolderNode.createTemplateAction({
1508
+ id: "gitlab:projectAccessToken:create",
1509
+ examples: examples$3,
1510
+ schema: {
1511
+ input: zod.z.object({
1512
+ projectId: zod.z.union([zod.z.number(), zod.z.string()], {
1513
+ description: "Project ID/Name(slug) of the Gitlab Project"
1514
+ }),
1515
+ token: zod.z.string({
1516
+ description: "The token to use for authorization to GitLab"
1517
+ }).optional(),
1518
+ name: zod.z.string({ description: "Name of Access Key" }).optional(),
1519
+ repoUrl: zod.z.string({ description: "URL to gitlab instance" }),
1520
+ accessLevel: zod.z.number({
1521
+ description: "Access Level of the Token, 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), and 50 (Owner)"
1522
+ }).optional(),
1523
+ scopes: zod.z.string({
1524
+ description: "Scopes for a project access token"
1525
+ }).array().optional(),
1526
+ expiresAt: zod.z.string({
1527
+ description: "Expiration date of the access token in ISO format (YYYY-MM-DD). If Empty, it will set to the maximum of 365 days."
1528
+ }).optional()
1529
+ }),
1530
+ output: zod.z.object({
1531
+ access_token: zod.z.string({ description: "Access Token" })
1532
+ })
1331
1533
  },
1332
1534
  async handler(ctx) {
1333
- var _a, _b, _c, _d, _e;
1535
+ ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`);
1334
1536
  const {
1335
- repoUrl,
1336
- repoVisibility = "private",
1337
- defaultBranch = "master",
1338
- gitCommitMessage = "initial commit",
1339
- gitAuthorName,
1340
- gitAuthorEmail,
1341
- setUserAsOwner = false,
1342
- topics = [],
1343
- settings = {},
1344
- branches = [],
1345
- projectVariables = []
1537
+ projectId,
1538
+ name = "tokenname",
1539
+ accessLevel = 40,
1540
+ scopes = ["read_repository"],
1541
+ expiresAt
1346
1542
  } = ctx.input;
1347
- const { owner, repo, host } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations);
1348
- if (!owner) {
1349
- throw new errors.InputError(
1350
- `No owner provided for host: ${host}, and repo ${repo}`
1351
- );
1352
- }
1353
- const integrationConfig = integrations.gitlab.byHost(host);
1354
- if (!integrationConfig) {
1543
+ const { token, integrationConfig } = getToken(ctx.input, integrations);
1544
+ if (!integrationConfig.config.token && token) {
1355
1545
  throw new errors.InputError(
1356
- `No matching integration configuration for host ${host}, please check your integrations config`
1546
+ `No token available for host ${integrationConfig.config.baseUrl}`
1357
1547
  );
1358
1548
  }
1359
- if (!integrationConfig.config.token && !ctx.input.token) {
1360
- throw new errors.InputError(`No token available for host ${host}`);
1361
- }
1362
- const token = ctx.input.token || integrationConfig.config.token;
1363
- const tokenType = ctx.input.token ? "oauthToken" : "token";
1364
- const client = new node.Gitlab({
1365
- host: integrationConfig.config.baseUrl,
1366
- [tokenType]: token
1367
- });
1368
- let targetNamespaceId;
1369
- try {
1370
- const namespaceResponse = await client.Namespaces.show(owner);
1371
- targetNamespaceId = namespaceResponse.id;
1372
- } catch (e) {
1373
- if (e.response && e.response.statusCode === 404) {
1374
- throw new errors.InputError(
1375
- `The namespace ${owner} is not found or the user doesn't have permissions to access it`
1376
- );
1377
- }
1378
- throw e;
1379
- }
1380
- const { id: userId } = await client.Users.current();
1381
- if (!targetNamespaceId) {
1382
- targetNamespaceId = userId;
1383
- }
1384
- const { id: projectId, http_url_to_repo } = await client.Projects.create({
1385
- namespace_id: targetNamespaceId,
1386
- name: repo,
1387
- visibility: repoVisibility,
1388
- ...topics.length ? { topics } : {},
1389
- ...Object.keys(settings).length ? { ...settings } : {}
1390
- });
1391
- if (setUserAsOwner && integrationConfig.config.token) {
1392
- const adminClient = new node.Gitlab({
1549
+ let api;
1550
+ if (!ctx.input.token) {
1551
+ api = new rest.Gitlab({
1393
1552
  host: integrationConfig.config.baseUrl,
1394
- token: integrationConfig.config.token
1553
+ token
1554
+ });
1555
+ } else {
1556
+ api = new rest.Gitlab({
1557
+ host: integrationConfig.config.baseUrl,
1558
+ oauthToken: token
1395
1559
  });
1396
- await adminClient.ProjectMembers.add(projectId, userId, 50);
1397
1560
  }
1398
- const remoteUrl = http_url_to_repo.replace(/\.git$/, "");
1399
- const repoContentsUrl = `${remoteUrl}/-/blob/${defaultBranch}`;
1400
- const gitAuthorInfo = {
1401
- name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
1402
- email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
1403
- };
1404
- const commitResult = await pluginScaffolderNode.initRepoAndPush({
1405
- dir: pluginScaffolderNode.getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
1406
- remoteUrl: http_url_to_repo,
1407
- defaultBranch,
1408
- auth: {
1409
- username: "oauth2",
1410
- password: token
1411
- },
1412
- logger: ctx.logger,
1413
- commitMessage: gitCommitMessage ? gitCommitMessage : config.getOptionalString("scaffolder.defaultCommitMessage"),
1414
- gitAuthorInfo
1415
- });
1416
- if (branches) {
1417
- for (const branch of branches) {
1418
- const {
1419
- name,
1420
- protect = false,
1421
- create = false,
1422
- ref = "master"
1423
- } = branch;
1424
- if (create) {
1425
- try {
1426
- await client.Branches.create(projectId, name, ref);
1427
- } catch (e) {
1428
- throw new errors.InputError(
1429
- `Branch creation failed for ${name}. ${printGitlabError(e)}`
1430
- );
1431
- }
1432
- ctx.logger.info(
1433
- `Branch ${name} created for ${projectId} with ref ${ref}`
1434
- );
1561
+ const response = await api.ProjectAccessTokens.create(
1562
+ projectId,
1563
+ name,
1564
+ scopes,
1565
+ {
1566
+ expiresAt: expiresAt || luxon.DateTime.now().plus({ days: 365 }).toISODate(),
1567
+ accessLevel
1568
+ }
1569
+ );
1570
+ if (!response.token) {
1571
+ throw new Error("Could not create project access token");
1572
+ }
1573
+ ctx.output("access_token", response.token);
1574
+ }
1575
+ });
1576
+ };
1577
+
1578
+ const examples$2 = [
1579
+ {
1580
+ description: "Create a GitLab project deploy token with minimal options.",
1581
+ example: yaml__default.default.stringify({
1582
+ steps: [
1583
+ {
1584
+ id: "createDeployToken",
1585
+ action: "gitlab:projectDeployToken:create",
1586
+ name: "Create GitLab Project Deploy Token",
1587
+ input: {
1588
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1589
+ projectId: "456",
1590
+ name: "tokenname"
1435
1591
  }
1436
- if (protect) {
1437
- try {
1438
- await client.ProtectedBranches.protect(projectId, name);
1439
- } catch (e) {
1440
- throw new errors.InputError(
1441
- `Branch protection failed for ${name}. ${printGitlabError(e)}`
1442
- );
1443
- }
1444
- ctx.logger.info(`Branch ${name} protected for ${projectId}`);
1592
+ }
1593
+ ]
1594
+ })
1595
+ },
1596
+ {
1597
+ description: "Create a GitLab project deploy token with custom scopes.",
1598
+ example: yaml__default.default.stringify({
1599
+ steps: [
1600
+ {
1601
+ id: "createDeployToken",
1602
+ action: "gitlab:projectDeployToken:create",
1603
+ name: "Create GitLab Project Deploy Token",
1604
+ input: {
1605
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1606
+ projectId: "789",
1607
+ name: "tokenname",
1608
+ scopes: ["read_registry", "write_repository"]
1445
1609
  }
1446
1610
  }
1447
- }
1448
- if (projectVariables) {
1449
- for (const variable of projectVariables) {
1450
- const variableWithDefaults = Object.assign(variable, {
1451
- variable_type: (_a = variable.variable_type) != null ? _a : "env_var",
1452
- protected: (_b = variable.protected) != null ? _b : false,
1453
- masked: (_c = variable.masked) != null ? _c : false,
1454
- raw: (_d = variable.raw) != null ? _d : false,
1455
- environment_scope: (_e = variable.environment_scope) != null ? _e : "*"
1456
- });
1457
- try {
1458
- await client.ProjectVariables.create(
1459
- projectId,
1460
- variableWithDefaults
1461
- );
1462
- } catch (e) {
1463
- throw new errors.InputError(
1464
- `Environment variable creation failed for ${variableWithDefaults.key}. ${printGitlabError(e)}`
1465
- );
1611
+ ]
1612
+ })
1613
+ },
1614
+ {
1615
+ description: "Create a GitLab project deploy token with a specified name.",
1616
+ example: yaml__default.default.stringify({
1617
+ steps: [
1618
+ {
1619
+ id: "createDeployToken",
1620
+ action: "gitlab:projectDeployToken:create",
1621
+ name: "Create GitLab Project Deploy Token",
1622
+ input: {
1623
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1624
+ projectId: "101112",
1625
+ name: "my-custom-token"
1626
+ }
1627
+ }
1628
+ ]
1629
+ })
1630
+ },
1631
+ {
1632
+ description: "Create a GitLab project deploy token with a numeric project ID.",
1633
+ example: yaml__default.default.stringify({
1634
+ steps: [
1635
+ {
1636
+ id: "createDeployToken",
1637
+ action: "gitlab:projectDeployToken:create",
1638
+ name: "Create GitLab Project Deploy Token",
1639
+ input: {
1640
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1641
+ projectId: 42,
1642
+ name: "tokenname"
1643
+ }
1644
+ }
1645
+ ]
1646
+ })
1647
+ },
1648
+ {
1649
+ description: "Create a GitLab project deploy token with a custom username",
1650
+ example: yaml__default.default.stringify({
1651
+ steps: [
1652
+ {
1653
+ id: "createDeployToken",
1654
+ action: "gitlab:projectDeployToken:create",
1655
+ name: "Create GitLab Project Deploy Token",
1656
+ input: {
1657
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1658
+ projectId: 42,
1659
+ name: "tokenname",
1660
+ username: "tokenuser"
1466
1661
  }
1467
1662
  }
1663
+ ]
1664
+ })
1665
+ }
1666
+ ];
1667
+
1668
+ const createGitlabProjectDeployTokenAction = (options) => {
1669
+ const { integrations } = options;
1670
+ return pluginScaffolderNode.createTemplateAction({
1671
+ id: "gitlab:projectDeployToken:create",
1672
+ examples: examples$2,
1673
+ schema: {
1674
+ input: commonGitlabConfig.merge(
1675
+ zod.z.object({
1676
+ projectId: zod.z.union([zod.z.number(), zod.z.string()], {
1677
+ description: "Project ID"
1678
+ }),
1679
+ name: zod.z.string({ description: "Deploy Token Name" }),
1680
+ username: zod.z.string({ description: "Deploy Token Username" }).optional(),
1681
+ scopes: zod.z.array(zod.z.string(), { description: "Scopes" }).optional()
1682
+ })
1683
+ ),
1684
+ output: zod.z.object({
1685
+ deploy_token: zod.z.string({ description: "Deploy Token" }),
1686
+ user: zod.z.string({ description: "User" })
1687
+ })
1688
+ },
1689
+ async handler(ctx) {
1690
+ ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`);
1691
+ const { projectId, name, username, scopes } = ctx.input;
1692
+ const { token, integrationConfig } = getToken(ctx.input, integrations);
1693
+ const api = new node.Gitlab({
1694
+ host: integrationConfig.config.baseUrl,
1695
+ token
1696
+ });
1697
+ const deployToken = await api.ProjectDeployTokens.add(
1698
+ projectId,
1699
+ name,
1700
+ scopes,
1701
+ {
1702
+ username
1703
+ }
1704
+ );
1705
+ if (!deployToken.hasOwnProperty("token")) {
1706
+ throw new errors.InputError(`No deploy_token given from gitlab instance`);
1468
1707
  }
1469
- ctx.output("commitHash", commitResult == null ? void 0 : commitResult.commitHash);
1470
- ctx.output("remoteUrl", remoteUrl);
1471
- ctx.output("repoContentsUrl", repoContentsUrl);
1472
- ctx.output("projectId", projectId);
1708
+ ctx.output("deploy_token", deployToken.token);
1709
+ ctx.output("user", deployToken.username);
1473
1710
  }
1474
1711
  });
1475
- }
1476
- function printGitlabError(error) {
1477
- return JSON.stringify({ code: error.code, message: error.description });
1478
- }
1479
-
1480
- function createGitlabApi(options) {
1481
- const { integrations, token: providedToken, repoUrl } = options;
1482
- const { host } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations);
1483
- const integrationConfig = integrations.gitlab.byHost(host);
1484
- if (!integrationConfig) {
1485
- throw new errors.InputError(
1486
- `No matching integration configuration for host ${host}, please check your integrations config`
1487
- );
1488
- }
1489
- if (!integrationConfig.config.token && !providedToken) {
1490
- throw new errors.InputError(`No token available for host ${host}`);
1491
- }
1492
- const token = providedToken != null ? providedToken : integrationConfig.config.token;
1493
- const tokenType = providedToken ? "oauthToken" : "token";
1494
- return new node.Gitlab({
1495
- host: integrationConfig.config.baseUrl,
1496
- [tokenType]: token
1497
- });
1498
- }
1712
+ };
1499
1713
 
1500
1714
  const examples$1 = [
1501
1715
  {
1502
- description: "Create a merge request with a specific assignee",
1716
+ description: "Creating a GitLab project variable of type env_var",
1503
1717
  example: yaml__default.default.stringify({
1504
1718
  steps: [
1505
1719
  {
1506
- id: "createMergeRequest",
1507
- action: "publish:gitlab:merge-request",
1508
- name: "Create a Merge Request",
1720
+ id: "createVariable",
1721
+ action: "gitlab:createGitlabProjectVariableAction",
1722
+ name: "Create GitLab Project Variable",
1509
1723
  input: {
1510
1724
  repoUrl: "gitlab.com?repo=repo&owner=owner",
1511
- title: "Create my new MR",
1512
- description: "This MR is really good",
1513
- sourcePath: "./path/to/my/changes",
1514
- branchName: "new-mr",
1515
- assignee: "my-assignee"
1725
+ projectId: "123",
1726
+ key: "MY_VARIABLE",
1727
+ value: "my_value",
1728
+ variableType: "env_var"
1516
1729
  }
1517
1730
  }
1518
1731
  ]
1519
1732
  })
1520
1733
  },
1521
1734
  {
1522
- description: "Create a merge request with removal of source branch after merge",
1735
+ description: "Creating a GitLab project variable of type file",
1523
1736
  example: yaml__default.default.stringify({
1524
1737
  steps: [
1525
1738
  {
1526
- id: "createMergeRequest",
1527
- action: "publish:gitlab:merge-request",
1528
- name: "Create a Merge Request",
1739
+ id: "createVariable",
1740
+ action: "gitlab:createGitlabProjectVariableAction",
1741
+ name: "Create GitLab Project Variable",
1529
1742
  input: {
1530
1743
  repoUrl: "gitlab.com?repo=repo&owner=owner",
1531
- title: "Create my new MR",
1532
- description: "This MR is really good",
1533
- sourcePath: "./path/to/my/changes",
1534
- branchName: "new-mr",
1535
- removeSourceBranch: true
1744
+ projectId: "123",
1745
+ key: "MY_VARIABLE",
1746
+ value: "my-file-content",
1747
+ variableType: "file"
1536
1748
  }
1537
1749
  }
1538
1750
  ]
1539
1751
  })
1540
1752
  },
1541
1753
  {
1542
- description: "Create a merge request with a target branch",
1754
+ description: "Create a GitLab project variable that is protected.",
1543
1755
  example: yaml__default.default.stringify({
1544
1756
  steps: [
1545
1757
  {
1546
- id: "createMergeRequest",
1547
- action: "publish:gitlab:merge-request",
1548
- name: "Create a Merge Request",
1758
+ id: "createVariable",
1759
+ action: "gitlab:createGitlabProjectVariableAction",
1760
+ name: "Create GitLab Project Variable",
1549
1761
  input: {
1550
1762
  repoUrl: "gitlab.com?repo=repo&owner=owner",
1551
- title: "Create my new MR",
1552
- description: "This MR is really good",
1553
- sourcePath: "./path/to/my/changes",
1554
- branchName: "new-mr",
1555
- targetBranchName: "test",
1556
- targetPath: "Subdirectory"
1763
+ projectId: "456",
1764
+ key: "MY_VARIABLE",
1765
+ value: "my_value",
1766
+ variableType: "env_var",
1767
+ variableProtected: true
1557
1768
  }
1558
1769
  }
1559
1770
  ]
1560
1771
  })
1561
1772
  },
1562
1773
  {
1563
- description: "Create a merge request with a commit action as create",
1774
+ description: "Create a GitLab project variable with masked flag as true",
1564
1775
  example: yaml__default.default.stringify({
1565
1776
  steps: [
1566
1777
  {
1567
- id: "createMergeRequest",
1568
- action: "publish:gitlab:merge-request",
1569
- name: "Create a Merge Request",
1778
+ id: "createVariable",
1779
+ action: "gitlab:createGitlabProjectVariableAction",
1780
+ name: "Create GitLab Project Variable",
1570
1781
  input: {
1571
1782
  repoUrl: "gitlab.com?repo=repo&owner=owner",
1572
- title: "Create my new MR",
1573
- branchName: "new-mr",
1574
- description: "MR description",
1575
- commitAction: "create",
1576
- targetPath: "source"
1783
+ projectId: "789",
1784
+ key: "DB_PASSWORD",
1785
+ value: "password123",
1786
+ variableType: "env_var",
1787
+ masked: true
1788
+ }
1789
+ }
1790
+ ]
1791
+ })
1792
+ },
1793
+ {
1794
+ description: "Create a GitLab project variable that is expandable.",
1795
+ example: yaml__default.default.stringify({
1796
+ steps: [
1797
+ {
1798
+ id: "createVariable",
1799
+ action: "gitlab:projectVariable:create",
1800
+ name: "Create GitLab Project Variable",
1801
+ input: {
1802
+ repoUrl: "gitlab.com?repo=repo&owner=owner",
1803
+ projectId: "123",
1804
+ key: "MY_VARIABLE",
1805
+ value: "my_value",
1806
+ variableType: "env_var",
1807
+ raw: true
1577
1808
  }
1578
1809
  }
1579
1810
  ]
1580
1811
  })
1581
1812
  },
1582
1813
  {
1583
- description: "Create a merge request with a commit action as delete",
1814
+ description: "Create a GitLab project variable with a specific environment scope.",
1584
1815
  example: yaml__default.default.stringify({
1585
1816
  steps: [
1586
1817
  {
1587
- id: "createMergeRequest",
1588
- action: "publish:gitlab:merge-request",
1589
- name: "Create a Merge Request",
1818
+ id: "createVariable",
1819
+ action: "gitlab:projectVariable:create",
1820
+ name: "Create GitLab Project Variable",
1590
1821
  input: {
1591
1822
  repoUrl: "gitlab.com?repo=repo&owner=owner",
1592
- title: "Create my new MR",
1593
- branchName: "new-mr",
1594
- description: "MR description",
1595
- commitAction: "delete",
1596
- targetPath: "source"
1823
+ projectId: "123",
1824
+ key: "MY_VARIABLE",
1825
+ value: "my_value",
1826
+ variableType: "env_var",
1827
+ environmentScope: "production"
1597
1828
  }
1598
1829
  }
1599
1830
  ]
1600
1831
  })
1601
1832
  },
1602
1833
  {
1603
- description: "Create a merge request with a commit action as update",
1834
+ description: "Create a GitLab project variable with a wildcard environment scope.",
1604
1835
  example: yaml__default.default.stringify({
1605
1836
  steps: [
1606
1837
  {
1607
- id: "createMergeRequest",
1608
- action: "publish:gitlab:merge-request",
1609
- name: "Create a Merge Request",
1838
+ id: "createVariable",
1839
+ action: "gitlab:projectVariable:create",
1840
+ name: "Create GitLab Project Variable",
1610
1841
  input: {
1611
1842
  repoUrl: "gitlab.com?repo=repo&owner=owner",
1612
- title: "Create my new MR",
1613
- branchName: "new-mr",
1614
- description: "MR description",
1615
- commitAction: "update",
1616
- targetPath: "source"
1843
+ projectId: "123",
1844
+ key: "MY_VARIABLE",
1845
+ value: "my_value",
1846
+ variableType: "env_var",
1847
+ environmentScope: "*"
1617
1848
  }
1618
1849
  }
1619
1850
  ]
@@ -1621,196 +1852,56 @@ const examples$1 = [
1621
1852
  }
1622
1853
  ];
1623
1854
 
1624
- const createPublishGitlabMergeRequestAction = (options) => {
1855
+ const createGitlabProjectVariableAction = (options) => {
1625
1856
  const { integrations } = options;
1626
1857
  return pluginScaffolderNode.createTemplateAction({
1627
- id: "publish:gitlab:merge-request",
1858
+ id: "gitlab:projectVariable:create",
1628
1859
  examples: examples$1,
1629
1860
  schema: {
1630
- input: {
1631
- required: ["repoUrl", "branchName"],
1632
- type: "object",
1633
- properties: {
1634
- repoUrl: {
1635
- type: "string",
1636
- title: "Repository Location",
1637
- description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`
1638
- },
1639
- /** @deprecated projectID is passed as query parameters in the repoUrl */
1640
- projectid: {
1641
- type: "string",
1642
- title: "projectid",
1643
- description: "Project ID/Name(slug) of the Gitlab Project"
1644
- },
1645
- title: {
1646
- type: "string",
1647
- title: "Merge Request Name",
1648
- description: "The name for the merge request"
1649
- },
1650
- description: {
1651
- type: "string",
1652
- title: "Merge Request Description",
1653
- description: "The description of the merge request"
1654
- },
1655
- branchName: {
1656
- type: "string",
1657
- title: "Source Branch Name",
1658
- description: "The source branch name of the merge request"
1659
- },
1660
- targetBranchName: {
1661
- type: "string",
1662
- title: "Target Branch Name",
1663
- description: "The target branch name of the merge request"
1664
- },
1665
- sourcePath: {
1666
- type: "string",
1667
- title: "Working Subdirectory",
1668
- description: "Subdirectory of working directory to copy changes from"
1669
- },
1670
- targetPath: {
1671
- type: "string",
1672
- title: "Repository Subdirectory",
1673
- description: "Subdirectory of repository to apply changes to"
1674
- },
1675
- token: {
1676
- title: "Authentication Token",
1677
- type: "string",
1678
- description: "The token to use for authorization to GitLab"
1679
- },
1680
- commitAction: {
1681
- title: "Commit action",
1682
- type: "string",
1683
- enum: ["create", "update", "delete"],
1684
- description: "The action to be used for git commit. Defaults to create."
1685
- },
1686
- removeSourceBranch: {
1687
- title: "Delete source branch",
1688
- type: "boolean",
1689
- description: "Option to delete source branch once the MR has been merged. Default: false"
1690
- },
1691
- assignee: {
1692
- title: "Merge Request Assignee",
1693
- type: "string",
1694
- description: "User this merge request will be assigned to"
1695
- }
1696
- }
1697
- },
1698
- output: {
1699
- type: "object",
1700
- properties: {
1701
- targetBranchName: {
1702
- title: "Target branch name of the merge request",
1703
- type: "string"
1704
- },
1705
- projectid: {
1706
- title: "Gitlab Project id/Name(slug)",
1707
- type: "string"
1708
- },
1709
- projectPath: {
1710
- title: "Gitlab Project path",
1711
- type: "string"
1712
- },
1713
- mergeRequestUrl: {
1714
- title: "MergeRequest(MR) URL",
1715
- type: "string",
1716
- description: "Link to the merge request in GitLab"
1717
- }
1718
- }
1719
- }
1861
+ input: commonGitlabConfig.merge(
1862
+ zod.z.object({
1863
+ projectId: zod.z.union([zod.z.number(), zod.z.string()], {
1864
+ description: "Project ID"
1865
+ }),
1866
+ key: zod.z.string({
1867
+ description: "The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed"
1868
+ }).regex(/^[A-Za-z0-9_]{1,255}$/),
1869
+ value: zod.z.string({ description: "The value of a variable" }),
1870
+ variableType: zod.z.string({
1871
+ description: "Variable Type (env_var or file)"
1872
+ }),
1873
+ variableProtected: zod.z.boolean({ description: "Whether the variable is protected" }).default(false).optional(),
1874
+ masked: zod.z.boolean({ description: "Whether the variable is masked" }).default(false).optional(),
1875
+ raw: zod.z.boolean({ description: "Whether the variable is expandable" }).default(false).optional(),
1876
+ environmentScope: zod.z.string({ description: "The environment_scope of the variable" }).default("*").optional()
1877
+ })
1878
+ )
1720
1879
  },
1721
1880
  async handler(ctx) {
1722
1881
  const {
1723
- assignee,
1724
- branchName,
1725
- targetBranchName,
1726
- description,
1727
- repoUrl,
1728
- removeSourceBranch,
1729
- targetPath,
1730
- sourcePath,
1731
- title,
1732
- token
1882
+ projectId,
1883
+ key,
1884
+ value,
1885
+ variableType,
1886
+ variableProtected = false,
1887
+ masked = false,
1888
+ raw = false,
1889
+ environmentScope = "*"
1733
1890
  } = ctx.input;
1734
- const { owner, repo, project } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations);
1735
- const repoID = project ? project : `${owner}/${repo}`;
1736
- const api = createGitlabApi({
1737
- integrations,
1738
- token,
1739
- repoUrl
1740
- });
1741
- let assigneeId = void 0;
1742
- if (assignee !== void 0) {
1743
- try {
1744
- const assigneeUser = await api.Users.username(assignee);
1745
- assigneeId = assigneeUser[0].id;
1746
- } catch (e) {
1747
- ctx.logger.warn(
1748
- `Failed to find gitlab user id for ${assignee}: ${e}. Proceeding with MR creation without an assignee.`
1749
- );
1750
- }
1751
- }
1752
- let fileRoot;
1753
- if (sourcePath) {
1754
- fileRoot = backendPluginApi.resolveSafeChildPath(ctx.workspacePath, sourcePath);
1755
- } else if (targetPath) {
1756
- fileRoot = backendPluginApi.resolveSafeChildPath(ctx.workspacePath, targetPath);
1757
- } else {
1758
- fileRoot = ctx.workspacePath;
1759
- }
1760
- const fileContents = await pluginScaffolderNode.serializeDirectoryContents(fileRoot, {
1761
- gitignore: true
1891
+ const { token, integrationConfig } = getToken(ctx.input, integrations);
1892
+ const api = new node.Gitlab({
1893
+ host: integrationConfig.config.baseUrl,
1894
+ token
1762
1895
  });
1763
- const actions = fileContents.map((file) => {
1764
- var _a;
1765
- return {
1766
- action: (_a = ctx.input.commitAction) != null ? _a : "create",
1767
- filePath: targetPath ? path__default.default.posix.join(targetPath, file.path) : file.path,
1768
- encoding: "base64",
1769
- content: file.content.toString("base64"),
1770
- execute_filemode: file.executable
1771
- };
1896
+ await api.ProjectVariables.create(projectId, {
1897
+ key,
1898
+ value,
1899
+ variable_type: variableType,
1900
+ protected: variableProtected,
1901
+ masked,
1902
+ raw,
1903
+ environment_scope: environmentScope
1772
1904
  });
1773
- let targetBranch = targetBranchName;
1774
- if (!targetBranch) {
1775
- const projects = await api.Projects.show(repoID);
1776
- const { default_branch: defaultBranch } = projects;
1777
- targetBranch = defaultBranch;
1778
- }
1779
- try {
1780
- await api.Branches.create(repoID, branchName, String(targetBranch));
1781
- } catch (e) {
1782
- throw new errors.InputError(
1783
- `The branch creation failed. Please check that your repo does not already contain a branch named '${branchName}'. ${e}`
1784
- );
1785
- }
1786
- try {
1787
- await api.Commits.create(repoID, branchName, ctx.input.title, actions);
1788
- } catch (e) {
1789
- throw new errors.InputError(
1790
- `Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${e}`
1791
- );
1792
- }
1793
- try {
1794
- const mergeRequestUrl = await api.MergeRequests.create(
1795
- repoID,
1796
- branchName,
1797
- String(targetBranch),
1798
- title,
1799
- {
1800
- description,
1801
- removeSourceBranch: removeSourceBranch ? removeSourceBranch : false,
1802
- assigneeId
1803
- }
1804
- ).then((mergeRequest) => {
1805
- return mergeRequest.web_url;
1806
- });
1807
- ctx.output("projectid", repoID);
1808
- ctx.output("targetBranchName", targetBranch);
1809
- ctx.output("projectPath", repoID);
1810
- ctx.output("mergeRequestUrl", mergeRequestUrl);
1811
- } catch (e) {
1812
- throw new errors.InputError(`Merge request creation failed${e}`);
1813
- }
1814
1905
  }
1815
1906
  });
1816
1907
  };
@@ -1939,7 +2030,6 @@ const createGitlabRepoPushAction = (options) => {
1939
2030
  }
1940
2031
  },
1941
2032
  async handler(ctx) {
1942
- var _a;
1943
2033
  const {
1944
2034
  branchName,
1945
2035
  repoUrl,
@@ -1965,7 +2055,7 @@ const createGitlabRepoPushAction = (options) => {
1965
2055
  gitignore: true
1966
2056
  });
1967
2057
  const actions = fileContents.map((file) => ({
1968
- action: commitAction != null ? commitAction : "create",
2058
+ action: commitAction ?? "create",
1969
2059
  filePath: targetPath ? path__default.default.posix.join(targetPath, file.path) : file.path,
1970
2060
  encoding: "base64",
1971
2061
  content: file.content.toString("base64"),
@@ -1976,7 +2066,7 @@ const createGitlabRepoPushAction = (options) => {
1976
2066
  await api.Branches.show(repoID, branchName);
1977
2067
  branchExists = true;
1978
2068
  } catch (e) {
1979
- if (((_a = e.response) == null ? void 0 : _a.statusCode) !== 404) {
2069
+ if (e.response?.statusCode !== 404) {
1980
2070
  throw new errors.InputError(
1981
2071
  `Failed to check status of branch '${branchName}'. Please make sure that branch already exists or Backstage has permissions to create one. ${e}`
1982
2072
  );
@@ -2031,7 +2121,8 @@ const gitlabModule = backendPluginApi.createBackendModule({
2031
2121
  createGitlabProjectVariableAction({ integrations }),
2032
2122
  createGitlabRepoPushAction({ integrations }),
2033
2123
  createPublishGitlabAction({ config, integrations }),
2034
- createPublishGitlabMergeRequestAction({ integrations })
2124
+ createPublishGitlabMergeRequestAction({ integrations }),
2125
+ createTriggerGitlabPipelineAction({ integrations })
2035
2126
  );
2036
2127
  }
2037
2128
  });
@@ -2047,5 +2138,6 @@ exports.createGitlabProjectVariableAction = createGitlabProjectVariableAction;
2047
2138
  exports.createGitlabRepoPushAction = createGitlabRepoPushAction;
2048
2139
  exports.createPublishGitlabAction = createPublishGitlabAction;
2049
2140
  exports.createPublishGitlabMergeRequestAction = createPublishGitlabMergeRequestAction;
2141
+ exports.createTriggerGitlabPipelineAction = createTriggerGitlabPipelineAction;
2050
2142
  exports.default = gitlabModule;
2051
2143
  //# sourceMappingURL=index.cjs.js.map