screwdriver-api 7.0.39 → 7.0.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screwdriver-api",
3
- "version": "7.0.39",
3
+ "version": "7.0.41",
4
4
  "description": "API server for the Screwdriver.cd service",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -321,4 +321,45 @@ If the template tag already exists, it will update the tag with the new version.
321
321
 
322
322
  `PUT /templates/{templateName}/tags/{tagName}` with the following payload
323
323
 
324
- * `version` - Exact version of the template (ex: `1.1.0`)
324
+ * `version` - Exact version of the template (ex: `1.1.0`)
325
+
326
+ ##### Delete a pipeline template
327
+ Deleting a pipeline template will delete a template and all of its associated tags and versions.
328
+
329
+ `DELETE /pipeline/templates/{namespace}/{name}`
330
+
331
+ ###### Arguments
332
+
333
+ * `name` - Name of the template
334
+
335
+ ##### Delete a pipeline template version
336
+
337
+ Delete the template version and all of its associated tags.
338
+ If the deleted version was the latest version, the API would set the `latestVersion` attribute of the templateMeta to the previous version.
339
+
340
+ `DELETE /pipeline/templates/{namespace}/{name}/versions/{version}`
341
+
342
+ ###### Arguments
343
+
344
+ 'namespace', 'name', 'version'
345
+
346
+ * `namespace` - Namespace of the template
347
+ * `name` - Name of the template
348
+ * `version` - Version of the template
349
+
350
+
351
+ ##### Delete a pipeline template tag
352
+
353
+ Delete the template tag. This does not delete the template itself.
354
+
355
+ *Note: This endpoint is only accessible in `build` scope and the permission is tied to the pipeline that creates the template.*
356
+
357
+ `DELETE /pipeline/templates/{namespace}/{name}/tags/{tag}`
358
+
359
+ ###### Arguments
360
+
361
+ 'namespace', 'name', 'tag'
362
+
363
+ * `namespace` - Namespace of the template
364
+ * `name` - Name of the template
365
+ * `tag` - Tag name of the template
@@ -38,6 +38,9 @@ const getTemplateRoute = require('./templates/get');
38
38
  const getTemplateByIdRoute = require('./templates/getTemplateById');
39
39
  const createTagRoute = require('./templates/createTag');
40
40
  const getVersionRoute = require('./templates/getVersion');
41
+ const removeTemplateRoute = require('./templates/remove');
42
+ const removeTemplateTagRoute = require('./templates/removeTag');
43
+ const removeTemplateVersionRoute = require('./templates/removeVersion');
41
44
 
42
45
  /**
43
46
  * Pipeline API Plugin
@@ -176,6 +179,59 @@ const pipelinesPlugin = {
176
179
  });
177
180
  });
178
181
 
182
+ /**
183
+ * Throws error if a credential does not have permission to remove pipeline template
184
+ * If credential has access, resolves to true
185
+ * @method canRemove
186
+ * @param {Object} credentials Credential object from Hapi
187
+ * @param {String} credentials.username Username of the person logged in (or build ID)
188
+ * @param {String} credentials.scmContext Scm of the person logged in (or build ID)
189
+ * @param {Array} credentials.scope Scope of the credential (user, build, admin)
190
+ * @param {String} [credentials.pipelineId] If credential is a build, this is the pipeline ID
191
+ * @param {Object} pipelineTemplate Target pipeline template object
192
+ * @param {String} permission Required permission level
193
+ * @param {String} app Server app object
194
+ * @return {Promise}
195
+ */
196
+ server.expose('canRemove', async (credentials, pipelineTemplate, permission, app) => {
197
+ const { username, scmContext, scope } = credentials;
198
+ const { userFactory, pipelineFactory } = app;
199
+
200
+ if (credentials.scope.includes('admin')) {
201
+ return true;
202
+ }
203
+
204
+ const pipeline = await pipelineFactory.get(pipelineTemplate.pipelineId);
205
+
206
+ if (!pipeline) {
207
+ throw boom.notFound(`Pipeline ${pipelineTemplate.pipelineId} does not exist`);
208
+ }
209
+
210
+ if (scope.includes('user')) {
211
+ const user = await userFactory.get({ username, scmContext });
212
+
213
+ if (!user) {
214
+ throw boom.notFound(`User ${username} does not exist`);
215
+ }
216
+
217
+ const permissions = await user.getPermissions(pipeline.scmUri);
218
+
219
+ if (!permissions[permission]) {
220
+ throw boom.forbidden(
221
+ `User ${username} does not have ${permission} access for this pipelineTemplate`
222
+ );
223
+ }
224
+
225
+ return true;
226
+ }
227
+
228
+ if (pipelineTemplate.pipelineId !== credentials.pipelineId || credentials.isPR) {
229
+ throw boom.forbidden('Not allowed to remove this pipelineTemplate');
230
+ }
231
+
232
+ return true;
233
+ });
234
+
179
235
  server.route([
180
236
  createRoute(),
181
237
  removeRoute(),
@@ -213,7 +269,10 @@ const pipelinesPlugin = {
213
269
  getVersionRoute(),
214
270
  getTemplateByIdRoute(),
215
271
  getTemplateRoute(),
216
- createTagRoute()
272
+ createTagRoute(),
273
+ removeTemplateRoute(),
274
+ removeTemplateTagRoute(),
275
+ removeTemplateVersionRoute()
217
276
  ]);
218
277
  }
219
278
  };
@@ -18,7 +18,12 @@ module.exports = () => ({
18
18
  scope: ['build']
19
19
  },
20
20
  handler: async (request, h) => {
21
- const { pipelineFactory, pipelineTemplateVersionFactory, pipelineTemplateTagFactory } = request.server.app;
21
+ const {
22
+ pipelineFactory,
23
+ pipelineTemplateFactory,
24
+ pipelineTemplateVersionFactory,
25
+ pipelineTemplateTagFactory
26
+ } = request.server.app;
22
27
 
23
28
  const { isPR, pipelineId } = request.auth.credentials;
24
29
 
@@ -28,7 +33,7 @@ module.exports = () => ({
28
33
 
29
34
  const [pipeline, template, templateTag] = await Promise.all([
30
35
  pipelineFactory.get(pipelineId),
31
- pipelineTemplateVersionFactory.get({ name, namespace, version }),
36
+ pipelineTemplateVersionFactory.get({ name, namespace, version }, pipelineTemplateFactory),
32
37
  pipelineTemplateTagFactory.get({ name, namespace, tag })
33
38
  ]);
34
39
 
@@ -20,13 +20,16 @@ module.exports = () => ({
20
20
  },
21
21
  handler: async (request, h) => {
22
22
  const { namespace, name, versionOrTag } = request.params;
23
- const { pipelineTemplateVersionFactory } = request.server.app;
23
+ const { pipelineTemplateFactory, pipelineTemplateVersionFactory } = request.server.app;
24
24
 
25
- const pipelineTemplate = await pipelineTemplateVersionFactory.getWithMetadata({
26
- name,
27
- namespace,
28
- versionOrTag
29
- });
25
+ const pipelineTemplate = await pipelineTemplateVersionFactory.getWithMetadata(
26
+ {
27
+ name,
28
+ namespace,
29
+ versionOrTag
30
+ },
31
+ pipelineTemplateFactory
32
+ );
30
33
 
31
34
  if (!pipelineTemplate) {
32
35
  throw boom.notFound('Pipeline Template does not exist');
@@ -22,7 +22,7 @@ module.exports = () => ({
22
22
  scope: ['user', 'build']
23
23
  },
24
24
  handler: async (request, h) => {
25
- const { pipelineTemplateVersionFactory } = request.server.app;
25
+ const { pipelineTemplateFactory, pipelineTemplateVersionFactory } = request.server.app;
26
26
  const config = {
27
27
  params: request.params,
28
28
  sort: request.query.sort
@@ -35,7 +35,7 @@ module.exports = () => ({
35
35
  };
36
36
  }
37
37
 
38
- const templates = await pipelineTemplateVersionFactory.list(config);
38
+ const templates = await pipelineTemplateVersionFactory.list(config, pipelineTemplateFactory);
39
39
 
40
40
  if (!templates || templates.length === 0) {
41
41
  throw boom.notFound('Template does not exist');
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ const boom = require('@hapi/boom');
4
+ const joi = require('joi');
5
+ const schema = require('screwdriver-data-schema');
6
+ const baseSchema = schema.models.templateMeta.base;
7
+
8
+ module.exports = () => ({
9
+ method: 'DELETE',
10
+ path: '/pipeline/templates/{namespace}/{name}',
11
+ options: {
12
+ description: 'Delete a pipeline template and its related versions and tags',
13
+ notes: 'Returns null if successful',
14
+ tags: ['api', 'templates'],
15
+ auth: {
16
+ strategies: ['token'],
17
+ scope: ['build', 'user', '!guest']
18
+ },
19
+
20
+ handler: async (request, h) => {
21
+ const { namespace, name } = request.params;
22
+ const { credentials } = request.auth;
23
+ const { pipelineTemplateFactory, pipelineTemplateTagFactory, pipelineTemplateVersionFactory } =
24
+ request.server.app;
25
+ const { canRemove } = request.server.plugins.pipelines;
26
+
27
+ const pipelineTemplate = await pipelineTemplateFactory.get({ namespace, name });
28
+
29
+ if (!pipelineTemplate) {
30
+ throw boom.notFound(`PipelineTemplate ${namespace / name} does not exist`);
31
+ }
32
+
33
+ const [tags, templateVersions] = await Promise.all([
34
+ pipelineTemplateTagFactory.list({ params: { namespace, name } }),
35
+ pipelineTemplateVersionFactory.list({ params: { namespace, name } }, pipelineTemplateFactory)
36
+ ]);
37
+
38
+ const canRemoveFlag = await canRemove(credentials, pipelineTemplate, 'admin', request.server.app);
39
+
40
+ if (canRemoveFlag) {
41
+ const templatePromise = pipelineTemplate.remove();
42
+ const tagPromises = tags.map(tag => tag.remove());
43
+ const versionPromises = templateVersions.map(version => version.remove());
44
+
45
+ await Promise.all([templatePromise, ...tagPromises, ...versionPromises]);
46
+ }
47
+
48
+ return h.response().code(204);
49
+ },
50
+ validate: {
51
+ params: joi.object({
52
+ namespace: baseSchema.extract('namespace'),
53
+ name: baseSchema.extract('name')
54
+ })
55
+ }
56
+ }
57
+ });
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const boom = require('@hapi/boom');
4
+ const joi = require('joi');
5
+ const schema = require('screwdriver-data-schema');
6
+ const baseSchema = schema.models.templateTag.base;
7
+ const metaSchema = schema.models.templateMeta.base;
8
+
9
+ module.exports = () => ({
10
+ method: 'DELETE',
11
+ path: '/pipeline/templates/{namespace}/{name}/tags/{tag}',
12
+ options: {
13
+ description: 'Delete a pipeline template tag',
14
+ notes: 'Delete a specific pipeline template',
15
+ tags: ['api', 'templates'],
16
+ auth: {
17
+ strategies: ['token'],
18
+ scope: ['build']
19
+ },
20
+ handler: async (request, h) => {
21
+ const {
22
+ pipelineFactory,
23
+ pipelineTemplateFactory,
24
+ pipelineTemplateTagFactory,
25
+ pipelineTemplateVersionFactory
26
+ } = request.server.app;
27
+ const { pipelineId, isPR } = request.auth.credentials;
28
+ const { name, namespace, tag } = request.params;
29
+
30
+ const templateTag = await pipelineTemplateTagFactory.get({ namespace, name, tag });
31
+
32
+ if (!templateTag) {
33
+ throw boom.notFound('PipelineTemplate tag does not exist');
34
+ }
35
+
36
+ const [pipeline, pipelineTemplate] = await Promise.all([
37
+ pipelineFactory.get(pipelineId),
38
+ pipelineTemplateVersionFactory.getWithMetadata(
39
+ {
40
+ namespace,
41
+ name,
42
+ version: templateTag.version
43
+ },
44
+ pipelineTemplateFactory
45
+ )
46
+ ]);
47
+
48
+ // Check for permission
49
+ if (pipeline.id !== pipelineTemplate.pipelineId || isPR) {
50
+ throw boom.forbidden('Not allowed to delete this pipeline template tag');
51
+ }
52
+
53
+ // Remove the template tag
54
+ await templateTag.remove();
55
+
56
+ return h.response().code(204);
57
+ },
58
+ validate: {
59
+ params: joi.object({
60
+ namespace: metaSchema.extract('namespace'),
61
+ name: metaSchema.extract('name'),
62
+ tag: baseSchema.extract('tag')
63
+ })
64
+ }
65
+ }
66
+ });
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const boom = require('@hapi/boom');
4
+ const joi = require('joi');
5
+ const schema = require('screwdriver-data-schema');
6
+ const baseSchema = schema.models.templateMeta.base;
7
+ const exactVersionSchema = schema.models.pipelineTemplateVersions.base.extract('version');
8
+
9
+ module.exports = () => ({
10
+ method: 'DELETE',
11
+ path: '/pipeline/templates/{namespace}/{name}/versions/{version}',
12
+ options: {
13
+ description: 'Delete the specified version of a pipeline template and the tags associated with it',
14
+ notes: 'Returns null if successful',
15
+ tags: ['api', 'templates'],
16
+ auth: {
17
+ strategies: ['token'],
18
+ scope: ['build', 'user', '!guest']
19
+ },
20
+
21
+ handler: async (request, h) => {
22
+ const { namespace, name, version } = request.params;
23
+ const { credentials } = request.auth;
24
+
25
+ const { pipelineTemplateFactory, pipelineTemplateVersionFactory, pipelineTemplateTagFactory } =
26
+ request.server.app;
27
+
28
+ const [templateVersion, tags] = await Promise.all([
29
+ pipelineTemplateVersionFactory.getWithMetadata({ namespace, name, version }, pipelineTemplateFactory),
30
+ pipelineTemplateTagFactory.list({ params: { namespace, name, version } })
31
+ ]);
32
+
33
+ if (!templateVersion) {
34
+ throw boom.notFound(`PipelineTemplate ${namespace}/${name} with version ${version} does not exist`);
35
+ }
36
+
37
+ const { canRemove } = request.server.plugins.pipelines;
38
+
39
+ const canRemoveFlag = await canRemove(credentials, templateVersion, 'admin', request.server.app);
40
+
41
+ if (canRemoveFlag) {
42
+ const { latestVersion, templateId } = templateVersion;
43
+ const removeTemplatePromise = templateVersion.remove();
44
+ const removeTagPromises = tags.map(tag => tag.remove());
45
+
46
+ await Promise.all([removeTemplatePromise, ...removeTagPromises]);
47
+
48
+ if (latestVersion === templateVersion.version) {
49
+ const templateVersions = await pipelineTemplateVersionFactory.list(
50
+ {
51
+ params: { templateId },
52
+ sort: 'descending',
53
+ sortBy: 'createTime',
54
+ paginate: { count: 1 }
55
+ },
56
+ pipelineTemplateFactory
57
+ );
58
+
59
+ if (templateVersions.length > 0) {
60
+ const templateMeta = await pipelineTemplateFactory.get({ id: templateId });
61
+
62
+ const newLatestTemplateVersion = templateVersions[0];
63
+
64
+ templateMeta.latestVersion = newLatestTemplateVersion.version;
65
+
66
+ await templateMeta.update();
67
+ }
68
+ }
69
+ }
70
+
71
+ return h.response().code(204);
72
+ },
73
+ validate: {
74
+ params: joi.object({
75
+ namespace: baseSchema.extract('namespace'),
76
+ name: baseSchema.extract('name'),
77
+ version: exactVersionSchema
78
+ })
79
+ }
80
+ }
81
+ });