diff --git a/codefresh-release.yml b/codefresh-release.yml index 3c3d7d670..0e882c15f 100644 --- a/codefresh-release.yml +++ b/codefresh-release.yml @@ -445,13 +445,14 @@ steps: update_documentation: stage: documentation title: "Update documentation http://cli.codefresh.io" - image: codefresh/build-cli + image: docker:18.01 commands: - - "yarn" + - "apk update && apk add git nodejs" + - "npm install" - "echo cleaning previous public dir and recreating worktree" - - "rm -rf public && git worktree prune && git worktree add -B gh-pages public origin/gh-pages" + - "rm -rf public && git worktree prune && git worktree add -B gh-pages public origin/gh-pages" - "echo Building public docs" - - "yarn run build-public-docs" + - "npm run build-public-docs" - "echo Push new docs to gh-pages detached branch" - 'git config --global user.email "auto-ci@codefresh.io" && git config --global user.name "Automated CI"' - 'cd public && git add --all && git commit -m "Publish new documentation for version ${{PACKAGE_VERSION}}" && git push https://${{GITHUB_TOKEN}}@github.com/codefresh-io/cli.git' diff --git a/codefresh.yml b/codefresh.yml index 9f82aeb9b..f6875ef16 100644 --- a/codefresh.yml +++ b/codefresh.yml @@ -328,7 +328,7 @@ steps: type: codefresh-run arguments: PIPELINE_ID: 'codefresh-io/cli/release' - TRIGGER_ID: codefresh-io/cli_1 + DETACH: true BRANCH: master VARIABLE: - PACKAGE_VERSION=${{PACKAGE_VERSION}} @@ -342,18 +342,3 @@ steps: - name: create_manifest_list on: - success - - build_documentation: - stage: test - title: "build documentation http://cli.codefresh.io" - image: codefresh/build-cli - commands: - - "echo Building public docs" - - "npm run build-public-docs" - environment: - - HUGO_VERSION=0.32.0 - when: - steps: - - name: install_dependencies - on: - - success diff --git a/docs/content/pipelines/Run Pipeline.md b/docs/content/pipelines/Run Pipeline.md index 0a11842ca..eec6cdc8a 100644 --- a/docs/content/pipelines/Run Pipeline.md +++ b/docs/content/pipelines/Run Pipeline.md @@ -15,44 +15,22 @@ The pipeline will be triggered multiple times according to the array length. #### Variable yaml file with 2 sets of variables ```yaml -- VARIABLE_A: value_a_for_the_first_build - VARIABLE_B: value_b_for_the_first_build -- VARIABLE_A: value_a_for_the_first_build - VARIABLE_B: value_b_for_the_first_build +- key: value + key2: key1 +- key: value + key2: key2 ``` #### Variable json file with 2 sets of variables ```json [ { - "VARIABLE_A": "value_a_for_the_first_build", - "VARIABLE_B": "value_b_for_the_first_build" + "key": "value", + "key2": "key1" }, { - "VARIABLE_A": "value_a_for_the_first_build", - "VARIABLE_B": "value_b_for_the_first_build" - } -] -``` -### Use encrypted variables in Codefresh build runs; supported from CLI version: 0.83.1 -#### Variable yaml file with single variable set with encrypted variables -```yaml -- key: - val: value - encrypted: true - key2: val2 - -``` - -#### Variable json file single variable set with encrypted variables -```json -[ - { - "key": { - "val": "value", - "encrypted": true - }, - "key2": "key1" + "key": "value", + "key2": "key2" } ] ``` diff --git a/lib/interface/cli/commands/annotation/create.cmd.js b/lib/interface/cli/commands/annotation/create.cmd.js index 7513d2185..8eadeacc0 100644 --- a/lib/interface/cli/commands/annotation/create.cmd.js +++ b/lib/interface/cli/commands/annotation/create.cmd.js @@ -31,7 +31,7 @@ const command = new Command({ .example('codefresh create annotation image 2dfacdaad466 coverage=75%', 'Annotate entity with a single label') .example('codefresh create annotation image 2dfacdaad466 coverage=75% tests_passed=true', 'Annotate entity with multiple labels') // eslint-disable-next-line max-len - .example('codefresh create annotation workflow 643d807b85bbe35931ae2282 ENV=prod tests_passed=true --display ENV', 'Annotate entity with multiple labels and display selection'), + .example('codefresh create annotation image 2dfacdaad466 coverage=75% tests_passed=true --display coverage', 'Annotate entity with multiple labels and display selection'), handler: async (argv) => { const { entityType, entityId, labels, display } = argv; diff --git a/lib/interface/cli/commands/pipeline/pipeline.sdk.spec.js b/lib/interface/cli/commands/pipeline/pipeline.sdk.spec.js index 717508e5e..883d72618 100644 --- a/lib/interface/cli/commands/pipeline/pipeline.sdk.spec.js +++ b/lib/interface/cli/commands/pipeline/pipeline.sdk.spec.js @@ -1,7 +1,3 @@ -const yaml = require('js-yaml'); -const request = require('requestretry'); -const fs = require('fs'); -const _ = require('lodash'); const DEFAULTS = require('../../defaults'); const getCmd = require('./get.cmd').toCommand(); const deleteCmd = require('./delete.cmd').toCommand(); @@ -14,21 +10,18 @@ jest.mock('../../helpers/validation'); // eslint-disable-line jest.mock('../../../../../check-version'); jest.mock('../../completion/helpers', () => { // eslint-disable-line return { - authContextWrapper: (func) => func, + authContextWrapper: func => func, }; }); -jest.mock('../../helpers/general', () => ({ - ...jest.requireActual('../../helpers/general'), - isCompatibleApiVersion: () => true, -})); - jest.mock('../../../../logic/entities/Pipeline', () => { // eslint-disable-line return { - fromResponse: (res) => res, + fromResponse: res => res, }; }); +const request = require('requestretry'); + const DEFAULT_RESPONSE = request.__defaultResponse(); describe('pipeline', () => { @@ -64,11 +57,11 @@ describe('pipeline', () => { }); it('should return default limit', async () => { - expect(_getLimit(undefined, false)).toEqual(DEFAULTS.GET_LIMIT_RESULTS); + expect(_getLimit(undefined,false)).toEqual(DEFAULTS.GET_LIMIT_RESULTS); }); it('should return `unlimited` value', async () => { - expect(_getLimit(undefined, true)).toEqual(DEFAULTS.GET_ALL_PIPELINES_LIMIT); + expect(_getLimit(undefined,true)).toEqual(DEFAULTS.GET_ALL_PIPELINES_LIMIT); }); }); @@ -91,69 +84,6 @@ describe('pipeline', () => { }); }); - describe('run', () => { - it('should handle running pipeline with encrypted variables', async () => { - const argv = { name: 'some name', - detach: true, - annotation: [], - variable: [ - 'secret=secret', - 'VAR1=VAL1', - ], - encrypted: ['secret'], - }; - const pip = new CfPipeline(argv); - await pip.run(); - expect(pip.executionRequests[0].options.variables).toEqual([ - { - key: 'secret', - value: 'secret', - encrypted: true, - }, - { - key: 'VAR1', - value: 'VAL1', - }, - ]); - await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line - }); - - it('should handle running pipeline with encrypted variables passing inside json file', async () => { - const rawFile = fs.readFileSync('lib/interface/cli/commands/pipeline/var.json', 'utf8'); - - const argv = { name: 'some name', - detach: true, - annotation: [], - 'var-file': JSON.parse(rawFile), - }; - const pip = new CfPipeline(argv); - await pip.run(); - expect(pip.executionRequests[0].options.variables).toEqual( - [{ key: 'help6', value: '85858' }, - { key: 'should_be_encrepted', value: '0000' }, - { encrypted: true, key: 'help7', value: 'test' }], - ); - await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line - }); - - it('should handle running pipeline with encrypted variables passing inside yaml file', async () => { - const rawFile = fs.readFileSync('lib/interface/cli/commands/pipeline/var.yml', 'utf8'); - - const argv = { name: 'some name', - detach: true, - annotation: [], - 'var-file': yaml.safeLoad(rawFile), - }; - const pip = new CfPipeline(argv); - await pip.run(); - expect(pip.executionRequests[0].options.variables).toEqual( - [{ key: 'VAR1', value: 'VAL1' }, - { encrypted: true, key: 'VAR2', value: 'VAL2' }], - ); - await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line - }); - }); - describe('runImpl', () => { it('should handle running pipeline', async () => { const argv = { name: 'some name', detach: true }; diff --git a/lib/interface/cli/commands/pipeline/run.base.js b/lib/interface/cli/commands/pipeline/run.base.js index 0ab91e896..8b40ed323 100644 --- a/lib/interface/cli/commands/pipeline/run.base.js +++ b/lib/interface/cli/commands/pipeline/run.base.js @@ -1,16 +1,10 @@ const _ = require('lodash'); const Promise = require('bluebird'); -const CFError = require('cf-errors'); -const { prepareKeyValueFromCLIEnvOption, - markEncryptedFlagOnRequestedVariables, - prepareKeyValueObjectsFromEnvFileOption, - prepareKeyValueObjectsFromCLIEnvOption, - isCompatibleApiVersion, -} = require('../../helpers/general'); +const { prepareKeyValueFromCLIEnvOption } = require('../../helpers/general'); const { validatePipelineYaml } = require('../../helpers/validation'); const { printResult } = require('../root/validate.cmd'); +const CFError = require('cf-errors'); const { sdk } = require('../../../../logic'); -const defaults = require('../../defaults'); class RunBaseCommand { constructor(argv) { @@ -61,34 +55,22 @@ class RunBaseCommand { packName, }, }; - const encryptedVarsSupported = await isCompatibleApiVersion({ - supportedVersion: defaults.MIN_API_VERSION_FOR_ENCRYPTED_VARS_SUPPORT_IN_RUN_CMD, - }); + if (variablesFromFile) { _.forEach(variablesFromFile, (variables) => { const request = _.cloneDeep(executionRequestTemplate); - if (encryptedVarsSupported) { - request.options.variables = prepareKeyValueObjectsFromEnvFileOption(variables); - } else { - request.options.variables = variables; - } + request.options.variables = variables; this.executionRequests.push(request); }); } else { - let variables; - if (encryptedVarsSupported) { - const varsArr = prepareKeyValueObjectsFromCLIEnvOption(this.argv.variable); - variables = markEncryptedFlagOnRequestedVariables(varsArr, this.argv.encrypted); - } else { - variables = prepareKeyValueFromCLIEnvOption(this.argv.variable); - } + const variables = prepareKeyValueFromCLIEnvOption(this.argv.variable); const request = _.cloneDeep(executionRequestTemplate); request.options.variables = variables; request.options.contexts = contexts; this.executionRequests.push(request); } - const results = await Promise.all(this.executionRequests.map((request) => this.runImpl(request))); + const results = await Promise.all(this.executionRequests.map(request => this.runImpl(request))); const findMaxReducer = (accumulator, currentValue) => (currentValue > accumulator ? currentValue : accumulator); const exitCode = results.reduce(findMaxReducer); await this.postRunRequest(); diff --git a/lib/interface/cli/commands/pipeline/run.cmd.js b/lib/interface/cli/commands/pipeline/run.cmd.js index cf024c018..7a63157ff 100644 --- a/lib/interface/cli/commands/pipeline/run.cmd.js +++ b/lib/interface/cli/commands/pipeline/run.cmd.js @@ -86,12 +86,6 @@ const run = new Command({ default: [], alias: 'v', }) - .option('encrypted', { - array: true, - alias: 'e', - describe: 'Variable names to encrypt', - default: [], - }) .option('detach', { alias: 'd', describe: 'Run pipeline and print build ID', @@ -132,7 +126,6 @@ const run = new Command({ .example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master', 'Defining the source control context using a branch') .example('codefresh run PIPELINE_ID | PIPELINE_NAME -s=52b992e783d2f84dd0123c70ac8623b4f0f938d1', 'Defining the source control context using a commit') .example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master -v key1=value1 -v key2=value2', 'Setting variables through the command') - .example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master -v key1=value1 -v key2=value2 -e key1', 'Setting variables through the command with encrypted option') .example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master --var-file ./var_file.yml', 'Settings variables through a yml file') .example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master --context context', 'Inject contexts to the pipeline execution') .example('codefresh run PIPELINE_ID | PIPELINE_NAME --skip step1 step2 step3', 'Skip specific steps'); diff --git a/lib/interface/cli/commands/pipeline/var.json b/lib/interface/cli/commands/pipeline/var.json deleted file mode 100644 index bf6ed0f7d..000000000 --- a/lib/interface/cli/commands/pipeline/var.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "build1": { - "help6": "85858", - "should_be_encrepted": "0000", - "help7": { - "value": "test", - "encrypted": true - } - } -} - - diff --git a/lib/interface/cli/commands/pipeline/var.yml b/lib/interface/cli/commands/pipeline/var.yml deleted file mode 100644 index db6b028b0..000000000 --- a/lib/interface/cli/commands/pipeline/var.yml +++ /dev/null @@ -1,5 +0,0 @@ -build1: - VAR1: 'VAL1' - VAR2: - value: VAL2 - encrypted: true diff --git a/lib/interface/cli/commands/project/apply.cmd.js b/lib/interface/cli/commands/project/apply.cmd.js index 082b9106b..9e574c4f0 100644 --- a/lib/interface/cli/commands/project/apply.cmd.js +++ b/lib/interface/cli/commands/project/apply.cmd.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const { sdk } = require('../../../../logic'); const applyRoot = require('../root/apply.cmd'); -const { prepareKeyValueObjectsFromCLIEnvOption, ignoreHttpError, markEncryptedFlagOnRequestedVariables } = require('../../helpers/general'); +const { prepareKeyValueObjectsFromCLIEnvOption, ignoreHttpError } = require('../../helpers/general'); const command = new Command({ command: 'project [id|name]', @@ -61,7 +61,14 @@ const command = new Command({ encrypted, } = argv; - const requestedProjectVariables = markEncryptedFlagOnRequestedVariables(variables, encrypted); + const variableMap = _.reduce(variables, (acc, v) => _.assign(acc, { [v.key]: v }), {}); + _.forEach(encrypted, (varName) => { + const variable = variableMap[varName]; + if (!variable) { + throw new CFError(`Variable is not provided: "${varName}"`); + } + variable.encrypted = true; + }); let project = await sdk.projects.get({ id }).catch(ignoreHttpError); project = project || await sdk.projects.getByName({ name }).catch(ignoreHttpError); @@ -74,7 +81,7 @@ const command = new Command({ const updatePayload = _.pickBy({ projectName, tags: tags || existingTags, - variables: requestedProjectVariables || existingVariables, + variables: variables || existingVariables, }, _.identity); await sdk.projects.patch({ id: project.id }, updatePayload); diff --git a/lib/interface/cli/commands/project/create.cmd.js b/lib/interface/cli/commands/project/create.cmd.js index 306dc2c37..eb0371539 100644 --- a/lib/interface/cli/commands/project/create.cmd.js +++ b/lib/interface/cli/commands/project/create.cmd.js @@ -1,8 +1,10 @@ const Command = require('../../Command'); +const CFError = require('cf-errors'); +const _ = require('lodash'); const { sdk } = require('../../../../logic'); const createRoot = require('../root/create.cmd'); const { checkOrProjectExists } = require('../../helpers/validation'); -const { prepareKeyValueObjectsFromCLIEnvOption, markEncryptedFlagOnRequestedVariables } = require('../../helpers/general'); +const { prepareKeyValueObjectsFromCLIEnvOption } = require('../../helpers/general'); const command = new Command({ command: 'project ', @@ -50,10 +52,17 @@ const command = new Command({ encrypted, } = argv; - const requestedProjectVariables = markEncryptedFlagOnRequestedVariables(variables, encrypted); + const variableMap = _.reduce(variables, (acc, v) => _.assign(acc, { [v.key]: v }), {}); + _.forEach(encrypted, (varName) => { + const variable = variableMap[varName]; + if (!variable) { + throw new CFError(`Variable is not provided: "${varName}"`); + } + variable.encrypted = true; + }); await checkOrProjectExists(projectName); - await sdk.projects.create({ projectName, tags, variables: requestedProjectVariables }); + await sdk.projects.create({ projectName, tags, variables }); console.log(`Project: "${projectName}" created`); }, }); diff --git a/lib/interface/cli/defaults.js b/lib/interface/cli/defaults.js index 6e9f1640e..39801fc77 100644 --- a/lib/interface/cli/defaults.js +++ b/lib/interface/cli/defaults.js @@ -13,7 +13,6 @@ const DEFAULTS = { MAX_CONSECUTIVE_ERRORS_LIMIT: 10, CODEFRESH_PATH: path.resolve(homedir(), '.Codefresh'), ENGINE_IMAGE: process.env.ENGINE_IMAGE || 'codefresh/engine:master', - MIN_API_VERSION_FOR_ENCRYPTED_VARS_SUPPORT_IN_RUN_CMD: '21.221.14', }; module.exports = DEFAULTS; diff --git a/lib/interface/cli/helpers/general.js b/lib/interface/cli/helpers/general.js index 82d18c937..3becbc692 100644 --- a/lib/interface/cli/helpers/general.js +++ b/lib/interface/cli/helpers/general.js @@ -1,43 +1,15 @@ -const debug = require('debug')('codefresh:cli:helpers:general'); const Promise = require('bluebird'); const _ = require('lodash'); const fs = require('fs'); const yaml = require('js-yaml'); +const defaults = require('../defaults'); const CFError = require('cf-errors'); const path = require('path'); -const semver = require('semver'); -const request = require('requestretry'); -const defaults = require('../defaults'); const Output = require('../../../output/Output'); const { sdk } = require('../../../logic'); const isDebug = () => (process.env.DEBUG || '').includes(defaults.DEBUG_PATTERN); -async function getApiVersion(token, url) { - const RequestOptions = { - url: `${url}/api/user`, - headers: { - Authorization: token, - }, - json: true, - maxAttempts: 3, // Retry up to 3 times - timeout: 10000, - }; - try { - const res = await request(RequestOptions); - return _.get(res, 'headers.api_version'); - } catch (error) { - debug('Failed to get API version:', error); - return null; - } -} - -async function isCompatibleApiVersion({ supportedVersion }) { - const token = _.get(sdk, 'config.context.token'); - const url = _.get(sdk, 'config.context.url', defaults.URL); - const apiVersion = token ? await getApiVersion(token, url) : null; - return apiVersion && semver.gte(apiVersion, supportedVersion); -} const wrapHandler = (handler, requiresAuthentication) => { return async (argv) => { @@ -134,39 +106,6 @@ const prepareKeyValueObjectsFromCLIEnvOption = (environmentVariables) => { return variables; }; -/** - * will return an array of objects { key, value, encrypted } from parsing an array of objects: key=value ,key: {value: "value", encrypted: true } - * @param environmentVariables - * @returns Array of { key, value, encrypted } - */ -const prepareKeyValueObjectsFromEnvFileOption = (environmentVariables) => _.map(environmentVariables, (value, key) => { - if (_.isObject(value)) { - const { value: val, encrypted } = value; - return { key, value: val, encrypted }; - } - return { key, value }; -}); - -/** - * The function takes in a list of requested variables and an array of encrypted variable names, - * and sets the encrypted flag to true on each corresponding variable object in the list. - * @param environmentVariables - * @param encrypted - * @returns Array of { key, value, encrypted } - */ -const markEncryptedFlagOnRequestedVariables = (requestedVariables, encrypted) => { - const variables = _.cloneDeep(requestedVariables); - const variableMap = _.reduce(variables, (acc, v) => _.assign(acc, { [v.key]: v }), {}); - _.forEach(encrypted, (varName) => { - const variable = variableMap[varName]; - if (!variable) { - throw new CFError(`Variable is not provided: "${varName}"`); - } - variable.encrypted = true; - }); - return variables; -}; - const crudFilenameOption = (yargs, options = {}) => { const filenameOption = { alias: options.alias || 'f', @@ -183,19 +122,13 @@ const crudFilenameOption = (yargs, options = {}) => { .coerce(options.name || 'filename', (arg) => { try { const rawFile = fs.readFileSync(path.resolve(process.cwd(), arg), 'utf8'); - let content; if (arg.endsWith('.json')) { - content = options.raw ? rawFile : JSON.parse(rawFile); - } else if (arg.endsWith('.yml') || arg.endsWith('yaml')) { - content = options.raw ? rawFile : yaml.safeLoad(rawFile); - } else { - throw new CFError('File extension is not recognized'); + return options.raw ? rawFile : JSON.parse(rawFile); } - if (_.isObject(content)) { - return content; + if (arg.endsWith('.yml') || arg.endsWith('yaml')) { + return options.raw ? rawFile : yaml.safeLoad(rawFile); } - throw new CFError('Not a valid file.\n' - + 'For more information how to pass a valid file, go to https://codefresh-io.github.io/cli/pipelines/run-pipeline/'); + throw new CFError('File extension is not recognized'); } catch (err) { const error = new CFError({ message: 'Failed to read file', @@ -228,15 +161,15 @@ const selectColumnsOption = (yargs, options = {}) => { }; function pathExists(p) { - return new Promise((resolve) => fs.access(p, resolve)) - .then((err) => !err); + return new Promise(resolve => fs.access(p, resolve)) + .then(err => !err); } const readFile = Promise.promisify(fs.readFile); function watchFile(filename, listener) { fs.watchFile(filename, { interval: 500 }, listener); - const unwatcher = (f) => () => fs.unwatchFile(f); + const unwatcher = f => () => fs.unwatchFile(f); ['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'SIGTERM'].forEach((eventType) => { process.on(eventType, unwatcher(filename)); }); @@ -244,7 +177,7 @@ function watchFile(filename, listener) { function ignoreHttpError(e) { if (!e.statusCode) { - throw e; + throw e } return undefined; } @@ -299,7 +232,4 @@ module.exports = { detectProxy, keyValueArrayToObject, addProxyVariables, - markEncryptedFlagOnRequestedVariables, - prepareKeyValueObjectsFromEnvFileOption, - isCompatibleApiVersion, }; diff --git a/package.json b/package.json index 10d7533b9..69bfc1c80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codefresh", - "version": "0.83.2", + "version": "0.83.3", "description": "Codefresh command line utility", "main": "index.js", "preferGlobal": true,