From 33f2a5f7f02b1789150585c64e5e6f60ee2f4f08 Mon Sep 17 00:00:00 2001 From: Itai Gendler Date: Wed, 12 Jun 2019 16:33:35 +0300 Subject: [PATCH 1/2] wip --- lib/interface/cli/commands/root/create.cmd.js | 6 +- .../cli/commands/root/replace.cmd.js | 8 +- lib/interface/cli/commands/step/delete.cmd.js | 30 ++ lib/interface/cli/commands/step/get.cmd.js | 84 ++++ .../cli/commands/step/get.completion.js | 14 + .../cli/commands/step/step.sdk.spec.js | 110 ++++++ lib/logic/entities/Step.js | 22 ++ openapi.json | 366 ++++++++++++++++++ 8 files changed, 638 insertions(+), 2 deletions(-) create mode 100644 lib/interface/cli/commands/step/delete.cmd.js create mode 100644 lib/interface/cli/commands/step/get.cmd.js create mode 100644 lib/interface/cli/commands/step/get.completion.js create mode 100644 lib/interface/cli/commands/step/step.sdk.spec.js create mode 100644 lib/logic/entities/Step.js diff --git a/lib/interface/cli/commands/root/create.cmd.js b/lib/interface/cli/commands/root/create.cmd.js index faf33b3ea..05f304461 100644 --- a/lib/interface/cli/commands/root/create.cmd.js +++ b/lib/interface/cli/commands/root/create.cmd.js @@ -9,7 +9,7 @@ const get = new Command({ root: true, command: 'create', description: 'Create a resource from a file or stdin', - usage: 'Supported resources: \n\t\'context\'\n\t\'pipeline\'', + usage: 'Supported resources: \n\t\'context\'\n\t\'pipeline\'\'\n\t\'step\'', webDocs: { description: 'Create a resource from a file, directory or url', category: 'Operate On Resources', @@ -48,6 +48,10 @@ const get = new Command({ await sdk.pipelines.create(data); console.log(`Pipeline '${name}' created`); break; + case 'step': + await sdk.steps.create(data); + console.log(`Step '${name}' created`); + break; default: throw new CFError(`Entity: ${entity} not supported`); } diff --git a/lib/interface/cli/commands/root/replace.cmd.js b/lib/interface/cli/commands/root/replace.cmd.js index b3ca63391..69a54c89b 100644 --- a/lib/interface/cli/commands/root/replace.cmd.js +++ b/lib/interface/cli/commands/root/replace.cmd.js @@ -9,7 +9,7 @@ const annotate = new Command({ root: true, command: 'replace', description: 'Replace a resource by filename', - usage: 'Supported resources: \n\t\'Context\'\n\t\'Pipeline\'', + usage: 'Supported resources: \n\t\'Context\'\n\t\'Pipeline\'\'\n\t\'Step\'', webDocs: { description: 'Replace a resource from a file, directory or url', category: 'Operate On Resources', @@ -56,6 +56,12 @@ const annotate = new Command({ }, data); console.log(`Pipeline '${name}' updated`); break; + case 'step': + await sdk.steps.replace({ + name, + }, data); + console.log(`Step '${name}' updated`); + break; default: throw new CFError(`Entity: ${entity} not supported`); } diff --git a/lib/interface/cli/commands/step/delete.cmd.js b/lib/interface/cli/commands/step/delete.cmd.js new file mode 100644 index 000000000..f956aa23c --- /dev/null +++ b/lib/interface/cli/commands/step/delete.cmd.js @@ -0,0 +1,30 @@ +const Command = require('../../Command'); +const deleteRoot = require('../root/delete.cmd'); +const { sdk } = require('../../../../logic'); + + +const command = new Command({ + command: 'step [name]', + parent: deleteRoot, + description: 'Delete a step', + webDocs: { + category: 'Steps', + title: 'Delete Step', + }, + builder: (yargs) => { + return yargs + .positional('name', { + describe: 'Step name', + }); + }, + handler: async (argv) => { + const { name } = argv; + + await sdk.steps.delete({ name }); + console.log(`Step '${name}' deleted.`); + }, +}); + + +module.exports = command; + diff --git a/lib/interface/cli/commands/step/get.cmd.js b/lib/interface/cli/commands/step/get.cmd.js new file mode 100644 index 000000000..7417c99db --- /dev/null +++ b/lib/interface/cli/commands/step/get.cmd.js @@ -0,0 +1,84 @@ +const debug = require('debug')('codefresh:cli:create:pipelines2'); +const Command = require('../../Command'); +const CFError = require('cf-errors'); +const _ = require('lodash'); +const DEFAULTS = require('../../defaults'); +const { prepareKeyValueFromCLIEnvOption } = require('../../helpers/general'); +const Output = require('../../../../output/Output'); +const { sdk } = require('../../../../logic'); +const Step = require('../../../../logic/entities/Step'); + +const getRoot = require('../root/get.cmd'); + + +const command = new Command({ + command: 'steps [id..]', + aliases: ['step'], + parent: getRoot, + description: 'Get a specific step or an array of steps', + webDocs: { + category: 'Steps', + title: 'Get Step', + }, + builder: (yargs) => { + return yargs + .positional('id', { + describe: 'Step name/id', + }) + .option('name', { + describe: 'Filter steps by name', + }) + .option('label', { + describe: 'Filter by a label', + alias: 'l', + default: [], + }) + .option('limit', { + describe: 'Limit amount of returned results', + default: DEFAULTS.GET_LIMIT_RESULTS, + }) + .option('page', { + describe: 'Paginated page', + default: DEFAULTS.GET_PAGINATED_PAGE, + }); + }, + handler: async (argv) => { + const { id: ids, name } = argv; + const limit = argv.limit; + const offset = (argv.page - 1) * limit; + const labels = prepareKeyValueFromCLIEnvOption(argv.label); + + if (!_.isEmpty(ids)) { + const steps = []; + for (const id of ids) { + try { + const currStep = await sdk.steps.get({ name: id }); + steps.push(Step.fromResponse(currStep)); + } catch (err) { + if (steps.length) { + Output.print(steps); + } + + debug(err.toString()); + const message = err.toString().includes('not find') ? `Step '${id}' was not found.` : 'Error occurred'; + throw new CFError({ + cause: err, + message, + }); + } + } + Output.print(steps); + } else { + const steps = await sdk.steps.list({ + limit, + offset, + id: name, + labels, + }); + Output.print(_.map(_.get(steps, 'docs'), Step.fromResponse)); + } + }, +}); + +module.exports = command; + diff --git a/lib/interface/cli/commands/step/get.completion.js b/lib/interface/cli/commands/step/get.completion.js new file mode 100644 index 000000000..0c78b4573 --- /dev/null +++ b/lib/interface/cli/commands/step/get.completion.js @@ -0,0 +1,14 @@ +const _ = require('lodash'); +const pipelines = () => require('../../../../logic').sdk.pipelines; +const Pipeline = require('../../../../logic/entities/Pipeline'); +const { authContextWrapper } = require('../../completion/helpers'); + +const positionalHandler = async ({word, argv}) => { + const pips = await pipelines().list({ limit: 25, offset: 0 }); + return _.map(_.get(pips, 'docs'), Pipeline.fromResponse).map(p => p.name); +}; + +module.exports = { + positionalHandler: authContextWrapper(positionalHandler), +}; + diff --git a/lib/interface/cli/commands/step/step.sdk.spec.js b/lib/interface/cli/commands/step/step.sdk.spec.js new file mode 100644 index 000000000..5b49edcdd --- /dev/null +++ b/lib/interface/cli/commands/step/step.sdk.spec.js @@ -0,0 +1,110 @@ +const getCmd = require('./get.cmd').toCommand(); +const deleteCmd = require('./delete.cmd').toCommand(); +const BasePipeline = require('./run.base'); +const CfPipeline = require('./run.cf'); +const { positionalHandler } = require('./get.completion'); +const { sdk } = require('../../../../logic'); + +jest.mock('../../helpers/validation'); // eslint-disable-line + +jest.mock('../../completion/helpers', () => { // eslint-disable-line + return { + authContextWrapper: func => func, + }; +}); + +jest.mock('../../../../logic/entities/Pipeline', () => { // eslint-disable-line + return { + fromResponse: res => res, + }; +}); + +const request = require('requestretry'); + +const DEFAULT_RESPONSE = request.__defaultResponse(); + +describe('pipeline', () => { + beforeEach(async () => { + request.__reset(); + request.mockClear(); + await configureSdk(); // eslint-disable-line + }); + + describe('commands', () => { + describe('get', () => { + it('should handle getting given id', async () => { + const argv = { id: ['some id'] }; + await getCmd.handler(argv); + await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line + }); + + it('should handle getting all', async () => { + const argv = {}; + const response = { statusCode: 200, body: { docs: [DEFAULT_RESPONSE.body] } }; + request.__setResponse(response); + await getCmd.handler(argv); + await verifyResponsesReturned([response]); // eslint-disable-line + }); + }); + + // decided to follow unit tests modularity concept + describe('run', () => { + describe('cf', () => { + describe('preRunAll', () => { + it('should handle pipeline getting on pre run phase', async () => { + const argv = { name: 'some name' }; + const pip = new BasePipeline(argv); + await pip.preRunAll(); + await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line + }); + + it('should handle context getting on pre run phase', async () => { + const argv = { name: 'some name', context: ['context'] }; + const pip = new BasePipeline(argv); + await pip.preRunAll(); + await verifyResponsesReturned([DEFAULT_RESPONSE, DEFAULT_RESPONSE]); // eslint-disable-line + }); + }); + + describe('runImpl', () => { + it('should handle running pipeline', async () => { + const argv = { name: 'some name', detach: true }; + const pip = new CfPipeline(argv); + await pip.runImpl({ pipelineName: argv.name, options: {} }); + await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line + }); + + it('should handle showing logs aaa', async () => { + jest.spyOn(sdk.logs, 'showWorkflowLogs').mockImplementation(); + const argv = { name: 'some name' }; + const pip = new CfPipeline(argv); + pip.executionRequests = ['test']; + await pip.runImpl({ pipelineName: argv.name, options: {} }); + + await verifyResponsesReturned([DEFAULT_RESPONSE, DEFAULT_RESPONSE]); // eslint-disable-line + expect(sdk.logs.showWorkflowLogs).toBeCalled(); + }); + }); + }); + }); + + describe('delete', () => { + it('should handle deletion given name', async () => { + const argv = { name: 'some name' }; + await deleteCmd.handler(argv); + await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line + }); + }); + }); + + describe('completions', () => { + describe('get', () => { + it('should handle getting completion', async () => { + const response = { statusCode: 200, body: { docs: [{ name: 'some pip' }] } }; + request.__setResponse(response); + await positionalHandler({}); + await verifyResponsesReturned([response]); // eslint-disable-line + }); + }); + }); +}); diff --git a/lib/logic/entities/Step.js b/lib/logic/entities/Step.js new file mode 100644 index 000000000..b04f676ee --- /dev/null +++ b/lib/logic/entities/Step.js @@ -0,0 +1,22 @@ +const Entity = require('./Entity'); +const _ = require('lodash'); + +class Step extends Entity { + constructor(data) { + super(); + this.entityType = 'step'; + this.info = data; + this.id = this.info.metadata.id; + this.name = this.info.metadata.name; + this.created = this.info.metadata.created_at ? new Date(this.info.metadata.created_at) : undefined; + this.updated = this.info.metadata.updated_at ? new Date(this.info.metadata.updated_at) : undefined; + this.defaultColumns = ['name', 'updated', 'created']; + this.wideColumns = ['id'].concat(this.defaultColumns); + } + + static fromResponse(response) { + return new Step(_.pick(response, 'id', 'version', 'kind', 'metadata', 'spec')); + } +} + +module.exports = Step; diff --git a/openapi.json b/openapi.json index 9861de2ca..cece3c310 100644 --- a/openapi.json +++ b/openapi.json @@ -5394,6 +5394,372 @@ "x-sdk-interface": "pipelines.replace" } }, + + "/steps": { + "get": { + "responses": { + "200": { + "$ref": "#/components/responses/json" + } + }, + "tags": [ + "steps" + ], + "parameters": [ + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Offset" + }, + { + "name": "id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Id" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "string" + }, + "description": "Limit" + }, + { + "in": "query", + "name": "labels", + "schema": { + "type": "string" + }, + "description": "Labels" + } + ], + "operationId": "steps-list", + "summary": "List", + "x-sdk-interface": "steps.list", + "x-endpoint": { + "isEndpoint": false, + "preMiddleware": [ + "auth.isAuthenticated" + ], + "postMiddleware": [ + "global.iseMiddleware" + ], + "handler": "steps.getAllAccountSteps" + } + }, + "post": { + "x-entityId": { + "pathName": "body.metadata.name" + }, + "x-action": "stepCreated", + "responses": { + "200": { + "$ref": "#/components/responses/json" + } + }, + "tags": [ + "steps" + ], + "operationId": "steps-create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "summary": "Create", + "parameters": [], + "x-sdk-interface": "steps.create", + "x-endpoint": { + "isEndpoint": false, + "preMiddleware": [ + "auth.isAuthenticated" + ], + "postMiddleware": [ + "global.iseMiddleware" + ], + "handler": "steps.createStep" + } + } + }, + "/steps/labels": { + "get": { + "responses": { + "200": { + "$ref": "#/components/responses/json" + } + }, + "tags": [ + "steps" + ], + "operationId": "steps-get-labels", + "parameters": [], + "summary": "Get all labels", + "x-sdk-interface": "steps.getLabels", + "x-endpoint": { + "isEndpoint": false, + "preMiddleware": [ + "auth.isAuthenticated" + ], + "postMiddleware": [ + "global.iseMiddleware" + ], + "handler": "steps.getLabels" + } + } + }, + "/steps/labels/{label}": { + "get": { + "responses": { + "200": { + "$ref": "#/components/responses/json" + } + }, + "tags": [ + "steps" + ], + "operationId": "steps-get-label-values", + "parameters": [ + { + "in": "path", + "name": "label", + "schema": { + "type": "string" + }, + "required": true, + "description": "label" + } + + ], + "summary": "Get label values", + "x-sdk-interface": "steps.getLabelValues", + "x-endpoint": { + "isEndpoint": false, + "preMiddleware": [ + "auth.isAuthenticated" + ], + "postMiddleware": [ + "global.iseMiddleware" + ], + "handler": "steps.getLabelValues" + } + } + }, + "/steps/names": { + "get": { + "parameters": [ + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Offset" + }, + { + "name": "id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Id" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "string" + }, + "description": "Limit" + }, + { + "in": "query", + "name": "labels", + "schema": { + "type": "string" + }, + "description": "Labels" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/json" + } + }, + "tags": [ + "steps" + ], + "operationId": "steps-get-names", + "summary": "Get all step names", + "x-sdk-interface": "steps.getNames", + "x-endpoint": { + "isEndpoint": false, + "preMiddleware": [ + "auth.isAuthenticated" + ], + "postMiddleware": [ + "global.iseMiddleware" + ], + "handler": "steps.getAllAccountStepsNames" + } + } + }, + "/steps/{name}": { + "get": { + "parameters": [ + { + "description": "Name of step", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/json" + } + }, + "tags": [ + "steps" + ], + "operationId": "steps-get", + "summary": "Get step", + "x-sdk-interface": "steps.get", + "x-endpoint": { + "isEndpoint": false, + "preMiddleware": [ + "auth.isAuthenticated" + ], + "postMiddleware": [ + "global.iseMiddleware" + ], + "handler": "steps.getStepByNameOrId" + } + }, + "put": { + "x-entityId": { + "pathId": "params.name", + "pathName": "body.metadata.name" + }, + "x-action": "updateStep", + "parameters": [ + { + "description": "Name of step", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "kind": { + "type": "string" + }, + "metadata": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "spec": { + "type": "object", + "properties": {} + } + } + } + } + } + } + }, + "tags": [ + "steps" + ], + "operationId": "steps-replace", + "summary": "Replace", + "x-sdk-interface": "steps.replace", + "x-endpoint": { + "isEndpoint": false, + "preMiddleware": [ + "auth.isAuthenticated" + ], + "postMiddleware": [ + "global.iseMiddleware" + ], + "handler": "steps.replaceStepByName" + } + }, + "delete": { + "x-entityId": { + "pathId": "params.name" + }, + "x-action": "stepDeleted", + "parameters": [ + { + "description": "Name of step", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/json" + } + }, + "tags": [ + "steps" + ], + "operationId": "steps-delete", + "summary": "Delete", + "x-sdk-interface": "steps.delete", + "x-endpoint": { + "isEndpoint": false, + "preMiddleware": [ + "auth.isAuthenticated" + ], + "postMiddleware": [ + "global.iseMiddleware" + ], + "handler": "steps.deleteStepByName" + } + } + }, + "/projects": { "post": { "x-action": "createProject", From 141817dd61daa564ce6091d303a7e04f7cd0f3d9 Mon Sep 17 00:00:00 2001 From: Itai Gendler Date: Thu, 13 Jun 2019 00:02:18 +0300 Subject: [PATCH 2/2] wip --- lib/interface/cli/commands/step/get.cmd.js | 2 +- .../cli/commands/step/get.completion.js | 8 ++-- .../cli/commands/step/step.sdk.spec.js | 47 +------------------ package.json | 2 +- 4 files changed, 8 insertions(+), 51 deletions(-) diff --git a/lib/interface/cli/commands/step/get.cmd.js b/lib/interface/cli/commands/step/get.cmd.js index 7417c99db..84de61d6f 100644 --- a/lib/interface/cli/commands/step/get.cmd.js +++ b/lib/interface/cli/commands/step/get.cmd.js @@ -1,4 +1,4 @@ -const debug = require('debug')('codefresh:cli:create:pipelines2'); +const debug = require('debug')('codefresh:cli:get:step'); const Command = require('../../Command'); const CFError = require('cf-errors'); const _ = require('lodash'); diff --git a/lib/interface/cli/commands/step/get.completion.js b/lib/interface/cli/commands/step/get.completion.js index 0c78b4573..84baa88ca 100644 --- a/lib/interface/cli/commands/step/get.completion.js +++ b/lib/interface/cli/commands/step/get.completion.js @@ -1,11 +1,11 @@ const _ = require('lodash'); -const pipelines = () => require('../../../../logic').sdk.pipelines; -const Pipeline = require('../../../../logic/entities/Pipeline'); +const steps = () => require('../../../../logic').sdk.steps; +const Step = require('../../../../logic/entities/Step'); const { authContextWrapper } = require('../../completion/helpers'); const positionalHandler = async ({word, argv}) => { - const pips = await pipelines().list({ limit: 25, offset: 0 }); - return _.map(_.get(pips, 'docs'), Pipeline.fromResponse).map(p => p.name); + const stps = await steps().list({ limit: 25, offset: 0 }); + return _.map(_.get(stps, 'docs'), Step.fromResponse).map(p => p.name); }; module.exports = { diff --git a/lib/interface/cli/commands/step/step.sdk.spec.js b/lib/interface/cli/commands/step/step.sdk.spec.js index 5b49edcdd..18521fe9c 100644 --- a/lib/interface/cli/commands/step/step.sdk.spec.js +++ b/lib/interface/cli/commands/step/step.sdk.spec.js @@ -1,9 +1,6 @@ const getCmd = require('./get.cmd').toCommand(); const deleteCmd = require('./delete.cmd').toCommand(); -const BasePipeline = require('./run.base'); -const CfPipeline = require('./run.cf'); const { positionalHandler } = require('./get.completion'); -const { sdk } = require('../../../../logic'); jest.mock('../../helpers/validation'); // eslint-disable-line @@ -13,7 +10,7 @@ jest.mock('../../completion/helpers', () => { // eslint-disable-line }; }); -jest.mock('../../../../logic/entities/Pipeline', () => { // eslint-disable-line +jest.mock('../../../../logic/entities/Step', () => { // eslint-disable-line return { fromResponse: res => res, }; @@ -23,7 +20,7 @@ const request = require('requestretry'); const DEFAULT_RESPONSE = request.__defaultResponse(); -describe('pipeline', () => { +describe('step', () => { beforeEach(async () => { request.__reset(); request.mockClear(); @@ -48,46 +45,6 @@ describe('pipeline', () => { }); // decided to follow unit tests modularity concept - describe('run', () => { - describe('cf', () => { - describe('preRunAll', () => { - it('should handle pipeline getting on pre run phase', async () => { - const argv = { name: 'some name' }; - const pip = new BasePipeline(argv); - await pip.preRunAll(); - await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line - }); - - it('should handle context getting on pre run phase', async () => { - const argv = { name: 'some name', context: ['context'] }; - const pip = new BasePipeline(argv); - await pip.preRunAll(); - await verifyResponsesReturned([DEFAULT_RESPONSE, DEFAULT_RESPONSE]); // eslint-disable-line - }); - }); - - describe('runImpl', () => { - it('should handle running pipeline', async () => { - const argv = { name: 'some name', detach: true }; - const pip = new CfPipeline(argv); - await pip.runImpl({ pipelineName: argv.name, options: {} }); - await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line - }); - - it('should handle showing logs aaa', async () => { - jest.spyOn(sdk.logs, 'showWorkflowLogs').mockImplementation(); - const argv = { name: 'some name' }; - const pip = new CfPipeline(argv); - pip.executionRequests = ['test']; - await pip.runImpl({ pipelineName: argv.name, options: {} }); - - await verifyResponsesReturned([DEFAULT_RESPONSE, DEFAULT_RESPONSE]); // eslint-disable-line - expect(sdk.logs.showWorkflowLogs).toBeCalled(); - }); - }); - }); - }); - describe('delete', () => { it('should handle deletion given name', async () => { const argv = { name: 'some name' }; diff --git a/package.json b/package.json index 3d0e5e8ee..d8d3ea171 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codefresh", - "version": "0.20.3", + "version": "0.21.0", "description": "Codefresh command line utility", "main": "index.js", "preferGlobal": true,