From fe87d2b7879f424838989d7ca50ab9a403a10dcc Mon Sep 17 00:00:00 2001 From: Dayanand Sagar Date: Tue, 25 Apr 2023 16:13:28 -0700 Subject: [PATCH] feat(2862): Delete a specific version of a template (#37) --- README.md | 29 +++++++++++++++++++ bin/removeVersion.js | 5 ++++ commands.js | 29 +++++++++++++++++++ index.js | 34 ++++++++++++++++++++++ package.json | 2 ++ test/index.test.js | 68 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+) create mode 100755 bin/removeVersion.js diff --git a/README.md b/README.md index 21a96ab..f4eb07c 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,35 @@ $ ./node_modules/.bin/template-remove --json --name templateName {"name":"templateName"} ``` +### Removing a template version + +To remove a specific version of a template, run the `template-remove-version` binary. This must be done in the same pipeline that published the template. You'll need to specify the template name and version as arguments. +Removing a template version will remove all the tags associated with it. + +Example `screwdriver.yaml` with validation, publishing and tagging, and version removal as a detached job: + +```yaml +shared: + image: node:6 + steps: + - init: npm install screwdriver-template-main +jobs: + main: + requires: [~pr, ~commit] + steps: + - validate: ./node_modules/.bin/template-validate + publish: + requires: main + steps: + - publish: ./node_modules/.bin/template-publish + - tag: ./node_modules/.bin/template-tag --name templateName --version 1.0.0 --tag latest + detached_remove_version: + steps: + - remove: ./node_modules/.bin/template-remove-version --name templateName --version 1.0.0 +``` + +`template-remove-tag` can print a result as json by passing `--json` option to the command. + ### Tagging a template Optionally, tag a template using the `template-tag` script. This must be done in the same pipeline that published the template. You'll need to add arguments for the template name and tag. You can optionally specify a version; the version must be an exact version, not just a major or major.minor one. If omitted, the latest version will be tagged. diff --git a/bin/removeVersion.js b/bin/removeVersion.js new file mode 100755 index 0000000..fac61d3 --- /dev/null +++ b/bin/removeVersion.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +'use strict'; + +require('../commands').run('remove_version'); diff --git a/commands.js b/commands.js index 6354fcb..1c74f2c 100644 --- a/commands.js +++ b/commands.js @@ -125,6 +125,35 @@ const operations = { help: 'remove tag' }, + /* Remove template version */ + remove_version: { + opts: { + name: { required: true, abbr: 'n', help: 'Template name' }, + version: { abbr: 'v', required: true, help: 'Version' }, + json: { abbr: 'j', flag: true, help: 'Output result as json' } + }, + + exec(opts) { + return index + .removeVersion({ + name: opts.name, + version: opts.version + }) + .then(result => { + if (!opts.json) { + console.log(`Version ${opts.version} was successfully removed from ${opts.name}`); + } else { + console.log(JSON.stringify(result)); + } + }) + .catch(err => { + console.error(err); + process.exit(1); + }); + }, + help: 'remove version' + }, + /* remove a template */ remove_template: { opts: { diff --git a/index.js b/index.js index 5ad9951..bdaead5 100644 --- a/index.js +++ b/index.js @@ -125,6 +125,39 @@ function removeTemplate(name) { }); } +/** + * Removes specified version of a template by sending a delete request to the SDAPI /templates/{name}/versions/{version} endpoint + * @method removeTemplate + * @param {Object} config + * @param {String} config.name Template name + * @param {String} config.version Template version to be removed + * @return {Promise} Resolves if removed successfully + */ +function removeVersion({ name, version }) { + const hostname = process.env.SD_API_URL || 'https://api.screwdriver.cd/v4/'; + const templateName = encodeURIComponent(name); + const templateVersion = encodeURIComponent(version); + const url = URL.resolve(hostname, `templates/${templateName}/versions/${templateVersion}`); + + return request({ + method: 'DELETE', + url, + context: { + token: process.env.SD_TOKEN + } + }).then(response => { + const { body } = response; + + if (response.statusCode !== 204) { + throw new Error( + `Error removing version ${version} of template ${name}. ${response.statusCode} (${body.error}): ${body.message}` + ); + } + + return { name, version }; + }); +} + /** * Helper function that returns the latest version for a template * @method getLatestVersion @@ -264,6 +297,7 @@ module.exports = { validateTemplate, publishTemplate, removeTemplate, + removeVersion, tagTemplate, removeTag, getVersionFromTag diff --git a/package.json b/package.json index 797ae6b..8d3ae5b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "template-remove": "./bin/remove.js", "template-tag": "./bin/tag.js", "template-remove-tag": "./bin/removeTag.js", + "template-remove-version": "./bin/removeVersion.js", "template-get-version-from-tag": "./bin/getVersionFromTag.js" }, "repository": { @@ -31,6 +32,7 @@ "contributors": [ "Dao Lam ", "Darren Matsumoto ", + "Dayanand Sagar ", "Jeremiah Wuenschel ", "Jerry Zhang ", "Min Zhang ", diff --git a/test/index.test.js b/test/index.test.js index 6dbd3d7..f7285d2 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -262,6 +262,74 @@ describe('index', () => { .then(result => assert.deepEqual(result, { name: templateConfig.name })); }); }); + + describe('Remove version', () => { + const config = { + name: 'template/test', + version: '1.0.1' + }; + + const url = `${ + process.env.SD_API_URL || 'https://api.screwdriver.cd/v4/' + }templates/template%2Ftest/versions/1.0.1`; + + it('throws error when request yields an error', () => { + requestMock.rejects(new Error('error')); + + return index + .removeVersion(config) + .then(() => assert.fail('should not get here')) + .catch(err => { + assert.equal(err.message, 'error'); + }); + }); + + it('throws error for the corresponding request error status code if not 204', () => { + const responseFake = { + statusCode: 403, + body: { + statusCode: 403, + error: 'Forbidden', + message: 'Fake forbidden message' + } + }; + + requestMock.resolves(responseFake); + + return index + .removeVersion(config) + .then(() => assert.fail('should not get here')) + .catch(err => { + assert.equal( + err.message, + 'Error removing version 1.0.1 of template template/test. 403 (Forbidden): Fake forbidden message' + ); + }); + }); + + it('succeeds and does not throw an error if request status code is 204', () => { + const responseFake = { + statusCode: 204 + }; + + requestMock.resolves(responseFake); + + return index.removeVersion(config).then(result => { + assert.deepEqual(result, { + name: config.name, + version: config.version + }); + assert.calledWith(requestMock, { + method: 'DELETE', + url, + context: { + token: process.env.SD_TOKEN + } + }); + }); + }); + }); + describe('Template Tag', () => { const url = `${ process.env.SD_API_URL || 'https://api.screwdriver.cd/v4/'