diff --git a/docs/content/pipelines v2/spec.md b/docs/content/pipelines v2 (beta)/spec.md similarity index 78% rename from docs/content/pipelines v2/spec.md rename to docs/content/pipelines v2 (beta)/spec.md index b2b2de293..b90f45f2d 100644 --- a/docs/content/pipelines v2/spec.md +++ b/docs/content/pipelines v2 (beta)/spec.md @@ -1,6 +1,5 @@ +++ title = "Spec" -weight = 10 +++ A Pipeline needs `.apiVersion`, `.kind`, and `.metadata` fields. @@ -15,8 +14,12 @@ apiVersion: "v1" kind: "pipeline" metadata: name: "new-pipeline" + description: "my description" labels: - repo: "github:ArikMaor/ping-server" + repo: "ArikMaor/ping-server" + key1: "value1" + project: "asd" + spec: triggers: - type: "scm" @@ -32,13 +35,25 @@ spec: value: "BLA BLA" encrypted: true steps: - test_step: + clone_step: + repo: github.com/itai-codefresh/test-env-file + revision: master + test_step_1: image: "alpine" + working_directory: ${{clone_step}} commands: + - echo ls - echo "hello world" - echo "plain value $PORT" - echo "encrypted value $PAPA" - echo "value from context $COOKIE" + build: + type: build + working_directory: ${{clone_step}} + dockerfile: ./Dockerfile + image_name: itai/test + tag: bla + ``` #### Pipeline which is stored on a remote git @@ -48,7 +63,7 @@ kind: "pipeline" metadata: name: "ew-pipeline-git" labels: - repo: "github:ArikMaor/ping-server" + repo: "ArikMaor/ping-server" spec: triggers: - type: "scm" @@ -76,7 +91,7 @@ kind: "pipeline" metadata: name: "new-pipeline-url" labels: - repo: "github:codefresh-io/cli" + repo: "codefresh-io/cli" spec: triggers: - type: "scm" diff --git a/docs/content/pipelines v2/_index.md b/docs/content/pipelines v2/_index.md deleted file mode 100644 index f8665a7c4..000000000 --- a/docs/content/pipelines v2/_index.md +++ /dev/null @@ -1,7 +0,0 @@ -+++ -title = "Pipelines V2 (beta)" -weight = 0 -+++ - - -{{COMMANDS}} diff --git a/docs/index.js b/docs/index.js index 30aa54a6a..9e9ffa50f 100644 --- a/docs/index.js +++ b/docs/index.js @@ -10,12 +10,13 @@ const CFError = require('cf-errors'); const TEMP_DIR = path.resolve(__dirname, '../temp'); const TEMPLATE_DIR = path.resolve(__dirname); -const FILES_TO_IGNORE = ['index.js', 'content/pipelines v2/_index.md', 'content/pipelines v2/spec.md']; +const FILES_TO_IGNORE = ['index.js']; const baseDir = path.resolve(TEMP_DIR, './content'); const ALLOW_BETA_COMMANDS = process.env.ALLOW_BETA_COMMANDS; const categoriesOrder = { authentication: 30, pipelines : 40, + 'pipelines v2 (beta)' : 42, builds: 50, 'operate on resources' : 60, contexts : 70 , @@ -340,7 +341,7 @@ const createDownloadPage = async () => { const RequestOptions = { url: 'https://api.github.com/repos/codefresh-io/cli/releases/latest', headers: { - 'User-Agent': 'codefresh-cli}', + 'User-Agent': 'codefresh-cli-build', }, json: true, }; diff --git a/lib/interface/cli/commands/context/replace.cmd.js b/lib/interface/cli/commands/context/replace.cmd.js deleted file mode 100644 index bf120789e..000000000 --- a/lib/interface/cli/commands/context/replace.cmd.js +++ /dev/null @@ -1,37 +0,0 @@ -const debug = require('debug')('codefresh:cli:replace:context'); -const Command = require('../../Command'); -const CFError = require('cf-errors'); -const _ = require('lodash'); -const { context } = require('../../../../logic').api; -const replaceRoot = require('../root/replace.cmd'); - -const command = new Command({ - command: 'context', - aliases: ['ctx'], - parent: replaceRoot, - description: 'Replace a context', - webDocs: { - category: 'Contexts', - title: 'Replace Context', - }, - builder: (yargs) => { - return yargs - .example('codefresh replace context -f ./context.yml', 'Replace a context that matches name field of context.yml content'); - }, - handler: async (argv) => { - const data = argv.filename; - const name = _.get(data, 'metadata.name'); - - if (!name) { - throw new CFError('Missing name in metadata'); - } - - await context.replaceByName(name, data); - - console.log(`Context: ${name} replaced`); - }, -}); - - -module.exports = command; - diff --git a/lib/interface/cli/commands/pipeline/run.cmd.js b/lib/interface/cli/commands/pipeline/run.cmd.js index 9a2b8bb7f..90112c494 100644 --- a/lib/interface/cli/commands/pipeline/run.cmd.js +++ b/lib/interface/cli/commands/pipeline/run.cmd.js @@ -4,9 +4,7 @@ const _ = require('lodash'); const CFError = require('cf-errors'); const { prepareKeyValueFromCLIEnvOption, crudFilenameOption } = require('../../helpers/general'); const ObjectID = require('mongodb').ObjectID; -const { workflow, pipeline, pipeline2, log } = require('../../../../logic').api; -const authManager = require('../../../../logic').auth.manager; - +const { workflow, pipeline, log } = require('../../../../logic').api; const run = new Command({ root: true, @@ -79,34 +77,20 @@ const run = new Command({ const variablesFromFile = argv['var-file']; const contexts = argv['context']; - if (!authManager.getCurrentContext() - .isBetaFeatEnabled()) { - // validate that passed pipeline id an a mongo object id in case of pipeline V1 - if (!ObjectID.isValid(pipelineId)) { - throw new CFError({ - message: `Passed pipeline id: ${pipelineId} is not valid`, - }); - } + if (!ObjectID.isValid(pipelineId)) { + throw new CFError({ + message: `Passed pipeline id: ${pipelineId} is not valid`, + }); } - let pipelineVersion = 'v1'; - if (authManager.getCurrentContext() - .isBetaFeatEnabled()) { - try { - await pipeline.getPipelineById(pipelineId); - } catch (err) { - try { - await pipeline2.getPipelineByName(pipelineId); - pipelineVersion = 'v2'; - } catch (err) { - throw new CFError({ - message: `Passed pipeline id: ${pipelineId} does not exist`, - }); - } - } + try { + await pipeline.getPipelineById(pipelineId); + } catch (err) { + throw new CFError({ + message: `Passed pipeline id: ${pipelineId} does not exist`, + }); } - const executionRequests = []; const executionRequestTemplate = { pipelineId, @@ -133,12 +117,7 @@ const run = new Command({ } _.forEach(executionRequests, async ({ pipelineId, options }) => { - let workflowId; - if (pipelineVersion === 'v1') { - workflowId = await pipeline.runPipelineById(pipelineId, options); - } else { - workflowId = await pipeline2.runPipelineByName(pipelineId, options); - } + const workflowId = await pipeline.runPipelineById(pipelineId, options); if (executionRequests.length === 1) { if (argv.detach) { diff --git a/lib/interface/cli/commands/pipeline2/create.cmd.js b/lib/interface/cli/commands/pipeline2/create.cmd.js deleted file mode 100644 index 959106c13..000000000 --- a/lib/interface/cli/commands/pipeline2/create.cmd.js +++ /dev/null @@ -1,32 +0,0 @@ -const debug = require('debug')('codefresh:cli:create:pipelines2'); -const Command = require('../../Command'); -const CFError = require('cf-errors'); -const _ = require('lodash'); -const { prepareKeyValueFromCLIEnvOption } = require('../../helpers/general'); -const { pipeline2 } = require('../../../../logic').api; -const createRoot = require('../root/create.cmd'); - -const command = new Command({ - betaCommand: true, - command: 'pipeline2 [name]', - aliases: ['pip2'], - parent: createRoot, - description: 'Create a pipeline', - webDocs: { - category: 'Pipelines V2', - title: 'Create Pipeline', - }, - builder: (yargs) => { - return yargs - .positional('name', { - describe: 'Name of context', - }); - }, - handler: async (argv) => { - throw new CFError('Not implemented'); - }, -}); - - -module.exports = command; - diff --git a/lib/interface/cli/commands/pipeline2/delete.cmd.js b/lib/interface/cli/commands/pipeline2/delete.cmd.js index e8ff45186..ca528b0c4 100644 --- a/lib/interface/cli/commands/pipeline2/delete.cmd.js +++ b/lib/interface/cli/commands/pipeline2/delete.cmd.js @@ -2,19 +2,17 @@ const debug = require('debug')('codefresh:cli:create:pipelines2'); const Command = require('../../Command'); const CFError = require('cf-errors'); const _ = require('lodash'); -const { pipeline2 } = require('../../../../logic').api; -const { specifyOutputForSingle, specifyOutputForArray } = require('../../helpers/get'); +const { pipeline2: pipeline } = require('../../../../logic').api; const deleteRoot = require('../root/delete.cmd'); const command = new Command({ - betaCommand: true, - command: 'pipeline2 [name]', - aliases: ['pip2'], + command: 'pipeline-v2 [name]', + aliases: ['pip-v2'], parent: deleteRoot, description: 'Delete a pipeline', webDocs: { - category: 'Pipelines V2', + category: 'Pipelines V2 (beta)', title: 'Delete Pipeline', }, builder: (yargs) => { @@ -26,7 +24,7 @@ const command = new Command({ handler: async (argv) => { const {name} = argv; - await pipeline2.deletePipelineByName(name); + await pipeline.deletePipelineByName(name); console.log(`Pipeline '${name}' deleted.`); }, }); diff --git a/lib/interface/cli/commands/pipeline2/get.cmd.js b/lib/interface/cli/commands/pipeline2/get.cmd.js index 2d1a2b1e2..e2b41564d 100644 --- a/lib/interface/cli/commands/pipeline2/get.cmd.js +++ b/lib/interface/cli/commands/pipeline2/get.cmd.js @@ -2,39 +2,76 @@ const debug = require('debug')('codefresh:cli:create:pipelines2'); const Command = require('../../Command'); const CFError = require('cf-errors'); const _ = require('lodash'); -const { pipeline2 } = require('../../../../logic').api; -const { specifyOutputForSingle, specifyOutputForArray } = require('../../helpers/get'); +const DEFAULTS = require('../../defaults'); +const { pipeline2: pipeline } = require('../../../../logic').api; +const { prepareKeyValueFromCLIEnvOption, printError } = require('../../helpers/general'); +const { specifyOutputForArray } = require('../../helpers/get'); + const getRoot = require('../root/get.cmd'); const command = new Command({ - betaCommand: true, - command: 'pipelines2 [name..]', - aliases: ['pip2', 'pipeline2'], + command: 'pipelines-v2 [name..]', + aliases: ['pip-v2', 'pipeline-v2'], parent: getRoot, description: 'Get a specific pipeline or an array of pipelines', webDocs: { - category: 'Pipelines V2', - title: 'Get Pipeline V2', + category: 'Pipelines V2 (beta)', + title: 'Get Pipeline', }, builder: (yargs) => { return yargs .positional('name', { describe: 'Pipeline name', + }) + .option('name-regex', { + describe: 'Filter pipelines 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 {names, output} = argv; - let pipelines = []; + const {name: names, output} = argv; + const limit = argv.limit; + const offset = (argv.page - 1) * limit; + const nameRegex = argv['name-regex']; + const labels = prepareKeyValueFromCLIEnvOption(argv.label); + + + const pipelines = []; if (!_.isEmpty(names)) { for (const name of names) { - const currPipeline = await pipeline2.getPipelineByName(name); - pipelines.push(currPipeline); + try { + const currPipeline = await pipeline.getPipelineByName(name); + pipelines.push(currPipeline); + } catch (err) { + const message = err.toString() + .includes('404') ? `Pipeline '${name}' was not found.` : 'Error occurred'; + throw new CFError({ + cause: err, + message, + }); + } } } else { - pipelines = await pipeline2.getAll(); + specifyOutputForArray(output, await pipeline.getAll({ + limit, + offset, + nameRegex, + labels, + })); } - specifyOutputForArray(output, pipelines); }, }); diff --git a/lib/interface/cli/commands/pipeline2/replace.cmd.js b/lib/interface/cli/commands/pipeline2/replace.cmd.js deleted file mode 100644 index da9ced309..000000000 --- a/lib/interface/cli/commands/pipeline2/replace.cmd.js +++ /dev/null @@ -1,47 +0,0 @@ -const debug = require('debug')('codefresh:cli:create:pipelines2'); -const Command = require('../../Command'); -const CFError = require('cf-errors'); -const _ = require('lodash'); -const { prepareKeyValueFromCLIEnvOption } = require('../../helpers/general'); -const { pipeline2 } = require('../../../../logic').api; -const replaceRoot = require('../root/replace.cmd'); - -const command = new Command({ - betaCommand: true, - command: 'pipeline2 [name]', - aliases: ['pip2'], - parent: replaceRoot, - description: 'Replace a pipeline', - webDocs: { - category: 'Pipelines V2', - title: 'Replace Pipeline', - }, - builder: (yargs) => { - return yargs - .positional('name', { - describe: 'Name of context', - }); - }, - handler: async (argv) => { - const {filename, name} = argv; - - if (!filename) { - throw new CFError('Pipeline definitions file must be provided'); - } - - if (!_.get(filename, 'metadata.name') && !name) { - throw new CFError('Name must be provided'); - } - - const data = argv.filename; - if (name) { - _.set(data, 'metadata.name', name); - }; - - await pipeline2.replaceByName(data.metadata.name, data); - console.log(`Pipeline '${data.metadata.name}' updated`); - }, -}); - -module.exports = command; - diff --git a/lib/interface/cli/commands/pipeline2/run.cmd-beta.js b/lib/interface/cli/commands/pipeline2/run.cmd-beta.js new file mode 100644 index 000000000..7ab20023f --- /dev/null +++ b/lib/interface/cli/commands/pipeline2/run.cmd-beta.js @@ -0,0 +1,148 @@ +const debug = require('debug')('codefresh:cli:run:pipeline'); +const Command = require('../../Command'); +const _ = require('lodash'); +const CFError = require('cf-errors'); +const { prepareKeyValueFromCLIEnvOption, crudFilenameOption } = require('../../helpers/general'); +const ObjectID = require('mongodb').ObjectID; +const { workflow, pipeline, log } = require('../../../../logic').api; +const authManager = require('../../../../logic').auth.manager; + + +const run = new Command({ + root: true, + command: 'run ', + description: 'Run a pipeline and attach the created workflow logs.', + usage: 'Returns an exit code according to the workflow finish status (Success: 0, Error: 1, Terminated: 2)', + webDocs: { + category: 'Pipelines', + title: 'Run Pipeline', + weight: 50, + }, + builder: (yargs) => { + yargs + .option('branch', { + describe: 'Branch', + alias: 'b', + }) + .positional('name', { + describe: 'Pipeline name', + }) + .option('sha', { + describe: 'Set commit sha', + alias: 's', + }) + .option('no-cache', { + describe: 'Ignore cached images', + alias: 'nc', + default: false, + }) + .option('reset-volume', { + describe: 'Reset pipeline cached volume', + alias: 'rv', + default: false, + }) + .option('variable', { + describe: 'Set build variables', + default: [], + alias: 'v', + }) + .option('detach', { + alias: 'd', + describe: 'Run pipeline and print build ID', + }) + .option('context', { + alias: 'c', + describe: 'Run pipeline with contexts', + default: [], + }) + .example('codefresh run PIPELINE_ID -b=master', 'Defining the source control context using a branch') + .example('codefresh run PIPELINE_ID -s=52b992e783d2f84dd0123c70ac8623b4f0f938d1', 'Defining the source control context using a commit') + .example('codefresh run PIPELINE_ID -b=master -v key1=value1 -v key2=value2', 'Setting variables through the command') + .example('codefresh run PIPELINE_ID -b=master --var-file ./var_file.yml', 'Settings variables through a yml file') + .example('codefresh run PIPELINE_ID -b=master --context context', 'Inject contexts to the pipeline execution'); + + crudFilenameOption(yargs, { + name: 'variable-file', + alias: 'var-file', + describe: 'Set build variables from a file', + }); + + return yargs; + }, + handler: async (argv) => { + const pipelineName = argv.name; + const branch = argv.branch; + const sha = argv.sha; + const noCache = argv['no-cache']; + const resetVolume = argv['reset-volume']; + const variablesFromFile = argv['var-file']; + const contexts = argv['context']; + + try { + await pipeline.getPipelineByName(pipelineName); + } catch (err) { + throw new CFError({ + message: `Passed pipeline id: ${pipelineName} does not exist`, + }); + } + + const executionRequests = []; + const executionRequestTemplate = { + pipelineName, + options: { + noCache, + resetVolume, + branch, + sha, + }, + }; + + if (variablesFromFile) { + _.forEach(variablesFromFile, (variables) => { + const request = _.cloneDeep(executionRequestTemplate); + request.options.variables = variables; + executionRequests.push(request); + }); + } else { + const variables = prepareKeyValueFromCLIEnvOption(argv.variable); + const request = _.cloneDeep(executionRequestTemplate); + request.options.variables = variables; + request.options.contexts = contexts; + executionRequests.push(request); + } + + _.forEach(executionRequests, async ({ pipelineName, options }) => { + let workflowId; + workflowId = await pipeline.runPipelineByName(pipelineName, options); + + if (executionRequests.length === 1) { + if (argv.detach) { + console.log(workflowId); + } else { + await log.showWorkflowLogs(workflowId, true); + const workflowInstance = await workflow.getWorkflowById(workflowId); + switch (workflowInstance.getStatus()) { + case 'success': + process.exit(0); + break; + case 'error': + process.exit(1); + break; + case 'terminated': + process.exit(2); + break; + default: + process.exit(100); + break; + } + } + } else { + console.log(workflowId); + } + }); + + + }, +}); + +module.exports = run; diff --git a/lib/interface/cli/commands/root/create.cmd.js b/lib/interface/cli/commands/root/create.cmd.js index d0dc64168..0f351c1a7 100644 --- a/lib/interface/cli/commands/root/create.cmd.js +++ b/lib/interface/cli/commands/root/create.cmd.js @@ -1,14 +1,14 @@ const CFError = require('cf-errors'); const Command = require('../../Command'); const { crudFilenameOption } = require('../../helpers/general'); -const { context, pipeline2 } = require('../../../../logic').api; +const { context, pipeline2: pipeline } = require('../../../../logic').api; const yargs = require('yargs'); const get = new Command({ root: true, command: 'create', description: 'Create a resource from a file or stdin', - usage: 'Supported resources: \n\t\'Context\'', + usage: 'Supported resources: \n\t\'context\'\n\t\'pipeline-v2\'', webDocs: { description: 'Create a resource from a file, directory or url', category: 'Operate On Resources', @@ -27,14 +27,19 @@ const get = new Command({ const data = argv.filename; const entity = data.kind; + const name = data.metadata.name; + if (!name) { + throw new CFError("Name is missing"); + } + switch (entity) { case 'context': await context.createContext(data); - console.log(`Context: ${data.metadata.name} created`); + console.log(`Context: ${name} created`); break; case 'pipeline': - await pipeline2.createPipeline(data); - console.log(`Pipeline '${data.metadata.name}' created`); + await pipeline.createPipeline(data); + console.log(`Pipeline-v2 '${name}' created`); break; default: throw new CFError(`Entity: ${entity} not supported`); diff --git a/lib/interface/cli/commands/root/delete.cmd.js b/lib/interface/cli/commands/root/delete.cmd.js index 416168fb0..ca9748736 100644 --- a/lib/interface/cli/commands/root/delete.cmd.js +++ b/lib/interface/cli/commands/root/delete.cmd.js @@ -1,6 +1,7 @@ const CFError = require('cf-errors'); const Command = require('../../Command'); const { crudFilenameOption } = require('../../helpers/general'); +const { context, pipeline2: pipeline } = require('../../../../logic').api; const yargs = require('yargs'); @@ -8,7 +9,7 @@ const get = new Command({ root: true, command: 'delete', description: 'Delete a resource by file or resource name', - usage: 'Supported resources:', + usage: 'Supported resources: \n\t\'Context\'\n\t\'Pipeline-v2\'', webDocs: { description: 'Delete a resource from a file, directory or url', category: 'Operate On Resources', @@ -27,7 +28,21 @@ const get = new Command({ const data = argv.filename; const entity = data.kind; + const name = data.metadata.name; + if (!name) { + throw new CFError('Name is missing'); + } + switch (entity) { + case 'context': + const owner = data.owner; + await context.deleteContextByName(name, owner); + console.log(`Context: ${name} deleted`); + break; + case 'pipeline-v2': + await pipeline.deletePipelineByName(name); + console.log(`Pipeline '${name}' deleted`); + 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 02597aa98..d95ec6607 100644 --- a/lib/interface/cli/commands/root/replace.cmd.js +++ b/lib/interface/cli/commands/root/replace.cmd.js @@ -1,13 +1,14 @@ const CFError = require('cf-errors'); const Command = require('../../Command'); const { crudFilenameOption } = require('../../helpers/general'); +const { context, pipeline2: pipeline } = require('../../../../logic').api; const yargs = require('yargs'); const annotate = new Command({ root: true, command: 'replace', - description: 'Replace a resource by filename or stdin', - usage: 'Replace operation will completely overwrite the existing resource and will throw an error if the resource does not exist.\n\n Supported resources: \n\t\'Context\'', + description: 'Replace a resource by filename', + usage: 'Supported resources: \n\t\'Context\'\n\t\'Pipeline-v2\'', webDocs: { description: 'Replace a resource from a file, directory or url', category: 'Operate On Resources', @@ -26,7 +27,20 @@ const annotate = new Command({ const data = argv.filename; const entity = data.kind; + const name = data.metadata.name; + if (!name) { + throw new CFError("Name is missing"); + } + switch (entity) { + case 'context': + await context.replaceByName(name, data); + console.log(`Context: ${name} created`); + break; + case 'pipeline-v2': + await pipeline.replaceByName(name, data); + console.log(`Pipeline '${name}' updated`); + break; default: throw new CFError(`Entity: ${entity} not supported`); } diff --git a/lib/interface/cli/helpers/general.js b/lib/interface/cli/helpers/general.js index 46d96d8ad..9a2ef6da6 100644 --- a/lib/interface/cli/helpers/general.js +++ b/lib/interface/cli/helpers/general.js @@ -11,7 +11,7 @@ const printError = (error) => { if ((process.env.DEBUG || '').includes(defaults.DEBUG_PATTERN)) { console.error(error.stack); } else { - console.error(error.toString()); + console.error(`${error.message}`); } }; diff --git a/lib/interface/cli/helpers/get.js b/lib/interface/cli/helpers/get.js index 87a72189f..b08ae13d3 100644 --- a/lib/interface/cli/helpers/get.js +++ b/lib/interface/cli/helpers/get.js @@ -18,20 +18,21 @@ const print = (output) => { //i tried that this function will be dynamic (with the keys). it is also possible to add an array with all the fields if you think it better const _printArrayTable = (data) => { if (data.length === 0) { - throw ('no available resources'); - } - const keys = Object.keys(data[0]); - const res = []; - let obj = []; - _.forEach(data, (row) => { - obj = []; - _.forEach(keys, (key) => { - obj[key.toUpperCase()] = row[key]; + console.log('no available resources'); + } else { + const keys = Object.keys(data[0]); + const res = []; + let obj = []; + _.forEach(data, (row) => { + obj = []; + _.forEach(keys, (key) => { + obj[key.toUpperCase()] = row[key]; + }); + res.push(obj); }); - res.push(obj); - }); - const columns = columnify(res); - print(columns); + const columns = columnify(res); + print(columns); + } }; const _printSingleTable = (info) => { diff --git a/lib/logic/api/MultipleRunner.js b/lib/logic/api/MultipleRunner.js deleted file mode 100644 index 137df73c9..000000000 --- a/lib/logic/api/MultipleRunner.js +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-disable */ - -const kefir = require('kefir'); -const EventEmitter = require('events'); -const debug = require('debug')('workflow.js') -const util = require('util'); -const http = require('./helper'); - - -const _ = require('lodash'); -const pipelines = require('./pipeline'); - -class MultipleJobRunner extends EventEmitter{ - - constructor(jobs, options){ - super(); - _.set(this, "jobs" , jobs); - let self = this; - - } - - - run (){ - let self = this; - let progressList = []; - let stream = kefir.sequentially(100, this.jobs).flatMap((job)=>{ - return kefir.fromPromise(pipelines.runPipelineById(job.id, {envVars:job.envs})); - }).log().flatMap((workflow)=>{ - let options = { - url: `/api/builds/${workflow}`, - method: 'GET' - }; - return kefir.fromPromise(http.sendHttpRequest(options)) - }).log('workflow->').map((b)=>{ - return _.get(b, "id") - }).scan((prev, current)=>{ - prev.push(current); - return prev - }, progressList) - - class Emitter extends EventEmitter{ - start(period, buildId){ - this.buildId = buildId; - let intervalId = setInterval(()=>{ - let options = { - url: `/api/builds/${buildId}`, - method: 'GET' - }; - const statuses = ["success", "terminated", "failed"] - http.sendHttpRequest(options).then((v)=>{ - this.emit('value', v); - (_.get(v, "finished")) ? this.stop() : _.noop() - }); - }, period); - this.intervalId = intervalId; - } - - stop(){ - debug(`end of http request for buildId ${this.buildId}`); - clearInterval(this.intervalId); - this.emit('end'); - } - } - - let resultStream = stream.last().flatten().flatMap((progressId)=>{ - let emitter = new Emitter(); - emitter.start(1000, progressId); - return kefir.fromEvents(emitter, "value") - }) - - //.map(_.partial(_.pick,_ ,["steps","branchName", "revision", "status"] )).log(); - let onlyFinalResults = resultStream.filter(_.partial(_.get,_, "finished")).take(_.size(this.jobs)).log('finished->'); - let results = []; - onlyFinalResults.scan((prev , current)=>{ - prev.push(current); - return prev; - }, results).onEnd(()=>{ - self.emit('end' ,results); - }) - resultStream.onValue((v)=>{ - self.emit('progress', v); - }) - resultStream.filter((v)=>v.status !== "success") - resultStream.filter((v)=>v.status === "success") - onlyFinalResults.onEnd(()=>{ - console.log('running env finsihed'); - // self.emit('end'); - }) - } -} -module.exports = MultipleJobRunner; diff --git a/lib/logic/api/helper.js b/lib/logic/api/helper.js index 3b328e830..0b2640066 100644 --- a/lib/logic/api/helper.js +++ b/lib/logic/api/helper.js @@ -33,15 +33,19 @@ const sendHttpRequest = async (httpOptions, authContext) => { response = await rp(finalOptions); } catch (err) { if (_.isEqual(err.statusCode, 401)) { - printError('Unauthorized error: Please create or update your authentication context'); + printError('Error: Please create or update your authentication context'); process.exit(1); } if (_.isEqual(err.statusCode, 403)) { - printError('Forbidden error: You do not have permissions to perform this action'); + printError('Error: You do not have permissions to perform this action'); process.exit(1); } - throw err; + if (_.get(err, 'error.message')) { + throw new Error(err.error.message); + } else { + throw err; + } } debug('Response:\n%O', response); return response; diff --git a/lib/logic/api/pipeline2.js b/lib/logic/api/pipeline2.js index ac42c691e..374381803 100644 --- a/lib/logic/api/pipeline2.js +++ b/lib/logic/api/pipeline2.js @@ -1,19 +1,32 @@ const _ = require('lodash'); // eslint-disable-line -const CFError = require('cf-errors'); // eslint-disable-line +const CFError = require('cf-errors'); const { sendHttpRequest } = require('./helper'); const Pipeline = require('../entities/Pipeline2'); +const { getContextByName } = require('./context'); +const Promise = require('bluebird'); const _extractFieldsForPipelineEntity = pipeline => _.pick(pipeline, 'id', 'kind', 'metadata', 'spec'); -const getAll = async () => { - const options = { +const getAll = async (options) => { + const qs = { + limit: options.limit, + offset: options.offset, + labels: options.labels, + }; + + if (options.nameRegex) { + qs.name = options.nameRegex; + } + + const RequestOptions = { url: '/api/pipelines/new', method: 'GET', + qs, }; - const result = await sendHttpRequest(options); + const result = await sendHttpRequest(RequestOptions); const pipelines = []; - _.forEach(result, (pipeline) => { + _.forEach(result.docs, (pipeline) => { const data = _extractFieldsForPipelineEntity(pipeline); pipelines.push(new Pipeline(data)); }); @@ -63,11 +76,68 @@ const deletePipelineByName = async (name) => { return sendHttpRequest(options); }; +/** + * will update a pipeline with only changes that were passed + * @param name + * @param repoOwner + * @param repoName + * @returns {Promise<*>} + */ +const patchPipelineByName = async () => { + // TODO + throw new Error('not implemented'); +}; + +/** + * will run a pipeline by its id + * @param id + * @returns {Promise<*>} + */ const runPipelineByName = async (name, data) => { - const body = _.pick(data, 'variables', 'branch'); + const body = { + options: {}, + }; + + if (data.branch) { + body.branch = data.branch; + } + + if (data.variables) { + body.variables = data.variables; + } + + if (data.noCache) { + body.options.noCache = data.noCache; + } + + if (data.resetVolume) { + body.options.resetVolume = data.resetVolume; + } + + if (data.sha) { + body.sha = data.sha; + } + + if (data.contexts) { + let contexts = []; + if (_.isString(data.contexts)) { + contexts = [data.contexts]; + } + await Promise.map(data.contexts, async (name) => { + try { + await getContextByName(name); + contexts.push({ + name, + }); + } catch (err) { + throw new CFError(err, `Failed to verify context ${name} with error ${err.message}`); + } + }); + body.contexts = contexts; + } const options = { - url: `/api/pipelines/new/run/${name}`, + url: `/api/builds/${name}`, method: 'POST', body, }; @@ -80,6 +150,7 @@ module.exports = { getPipelineByName, createPipeline, replaceByName, + patchPipelineByName, deletePipelineByName, runPipelineByName, }; diff --git a/lib/logic/entities/Pipeline2.js b/lib/logic/entities/Pipeline2.js index 17cba7618..b9c72d0ab 100644 --- a/lib/logic/entities/Pipeline2.js +++ b/lib/logic/entities/Pipeline2.js @@ -1,13 +1,17 @@ +const moment = require('moment'); const Entity = require('./Entity'); -class Pipeline2 extends Entity { +class Pipeline extends Entity { constructor(data) { super(); - this.entityType = 'pipeline2'; + this.entityType = 'pipeline V2'; this.info = data; - this.defaultColumns = ['id', 'metadata.name']; + this.name = this.info.metadata.name; + this.created = moment(this.info.metadata.created_at).fromNow(); + this.updated = moment(this.info.metadata.updated_at).fromNow(); + this.defaultColumns = ['name', 'updated', 'created']; this.wideColumns = this.defaultColumns.concat([]); } } -module.exports = Pipeline2; +module.exports = Pipeline; diff --git a/package.json b/package.json index bdc86cb66..6f3bc1495 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codefresh", - "version": "0.8.28", + "version": "0.8.29", "description": "Codefresh command line utility", "main": "index.js", "preferGlobal": true,