From 62a71bb03dca9a10d260ce49b003c77e4c067d8f Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Thu, 29 Nov 2018 14:25:20 +0200 Subject: [PATCH 01/17] cli-config output formatting --- lib/interface/cli/helpers/cli-config.js | 48 +++++++++++++++++++++++++ package.json | 1 + 2 files changed, 49 insertions(+) create mode 100644 lib/interface/cli/helpers/cli-config.js diff --git a/lib/interface/cli/helpers/cli-config.js b/lib/interface/cli/helpers/cli-config.js new file mode 100644 index 000000000..75ea5753c --- /dev/null +++ b/lib/interface/cli/helpers/cli-config.js @@ -0,0 +1,48 @@ +const _ = require('lodash'); +const flattern = require('flat'); +const columnify = require('columnify'); +const yaml = require('js-yaml'); + +const _jsonFormatter = (data) => JSON.stringify(data, null, 4); + +const _flatteredObjComparator = (a, b) => { + const aDots = a.split('.').length; + const bDots = b.split('.').length; + if (aDots > bDots) return 1; + if (aDots < bDots) return -1; + + if (a > b) return 1; + if (a < b) return -1; + return 0; +}; + + +function _defaultOutput(data) { + const flat = flattern(data); + const columns = _.keys(flat) + .sort(_flatteredObjComparator) + .map((key) => { + return { key, value: _.get(data, key) }; + }); + return columnify(columns, {columnSplitter: ' '}); +} + +function _formatter(format) { + switch (format) { + case 'yaml': + return yaml.safeDump; + case 'json': + return _jsonFormatter; + default: + return _defaultOutput; + } +} + +function outputCliConfig(format, data) { + const formatter = _formatter(format); + console.log(formatter(data)); +} + +module.exports = { + outputCliConfig, +}; diff --git a/package.json b/package.json index 78d6ad19f..378148280 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "draftlog": "^1.0.12", "filesize": "^3.5.11", "firebase": "git+https://github.com/codefresh-io/firebase.git#80b2ed883ff281cd67b53bd0f6a0bbd6f330fed5", + "flat": "^4.1.0", "js-yaml": "^3.10.0", "jsonwebtoken": "^8.1.0", "kefir": "^3.8.1", From c7d3213173a6068b17cc08e8a92a845c3721ce8a Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Thu, 29 Nov 2018 20:03:16 +0200 Subject: [PATCH 02/17] constants extracted --- lib/constants.js | 9 +++++++++ .../cli/commands/runtimeEnvironments/create.cmd.js | 7 +++---- lib/logic/api/cluster.js | 11 ++++++----- 3 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 lib/constants.js diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 000000000..39c8de8a2 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,9 @@ +const { homedir } = require('os'); +const path = require('path'); + +const codefreshPath = path.resolve(homedir(), '.Codefresh'); + + +module.exports = { + codefreshPath, +}; diff --git a/lib/interface/cli/commands/runtimeEnvironments/create.cmd.js b/lib/interface/cli/commands/runtimeEnvironments/create.cmd.js index d269e2162..b7a3f9f78 100644 --- a/lib/interface/cli/commands/runtimeEnvironments/create.cmd.js +++ b/lib/interface/cli/commands/runtimeEnvironments/create.cmd.js @@ -2,15 +2,14 @@ const debug = require('debug')('codefresh:cli:create:context'); const Command = require('../../Command'); const fs = require('fs'); const { spawn } = require('child_process'); -const { homedir } = require('os'); const rp = require('request-promise'); const createRoot = require('../root/create.cmd'); const authManager = require('../../../../logic/auth').manager; // eslint-disable-line +const { codefreshPath } = require('../../../../constants'); const scriptUrl = 'https://raw.githubusercontent.com/codefresh-io/k8s-dind-config/master/codefresh-k8s-configure.sh'; -let filePath = `${homedir()}/.Codefresh/runtime/codefresh-k8s-configure.sh`; -const dirPath = `${homedir()}/.Codefresh/runtime`; -const codefreshPath = `${homedir()}/.Codefresh`; +let filePath = `${codefreshPath}/runtime/codefresh-k8s-configure.sh`; +const dirPath = `${codefreshPath}/runtime`; const callToScript = (k8sScript) =>{ diff --git a/lib/logic/api/cluster.js b/lib/logic/api/cluster.js index 2d1fac8c8..c28c920a2 100644 --- a/lib/logic/api/cluster.js +++ b/lib/logic/api/cluster.js @@ -10,6 +10,8 @@ const decompress = require('decompress'); const decompressTargz = require('decompress-targz'); const compareVersions = require('compare-versions'); +const { codefreshPath } = require('../../constants'); + const _createClusterScript = (info, filePath) => { const { name, context } = info; fs.chmodSync(filePath, '755'); @@ -46,12 +48,11 @@ const getAllClusters = async () => { }; const createCluster = async (info) => { - const dirPath = `${homedir()}/.Codefresh/cluster`; - const filePath = `${homedir()}/.Codefresh/cluster/stevedore`; - const versionPath = `${homedir()}/.Codefresh/cluster/version.txt`; + const dirPath = `${codefreshPath}/cluster`; + const filePath = `${codefreshPath}/cluster/stevedore`; + const versionPath = `${codefreshPath}/cluster/version.txt`; const versionUrl = 'https://raw.githubusercontent.com/codefresh-io/Stevedore/master/VERSION'; - const codefreshPath = `${homedir()}/.Codefresh`; - let zipPath = `${homedir()}/.Codefresh/cluster/data`; + let zipPath = `${codefreshPath}/cluster/data`; let shouldUpdate = true; const options = { url: versionUrl, From 01236209406796ebedfdc085939777a6703f66f0 Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Thu, 29 Nov 2018 20:03:52 +0200 Subject: [PATCH 03/17] cli config management --- .../cli/commands/cli-config/cli-config.cmd.js | 40 ++++++ .../cli/commands/cli-config/ops/get.cmd.js | 46 +++++++ .../cli/commands/cli-config/ops/set.cmd.js | 47 +++++++ lib/interface/cli/helpers/cli-config.js | 34 ++++- lib/logic/cli-config/errors.js | 26 ++++ lib/logic/cli-config/manager.js | 119 ++++++++++++++++++ lib/logic/cli-config/model.js | 31 +++++ lib/logic/cli-config/schema.js | 15 +++ 8 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 lib/interface/cli/commands/cli-config/cli-config.cmd.js create mode 100644 lib/interface/cli/commands/cli-config/ops/get.cmd.js create mode 100644 lib/interface/cli/commands/cli-config/ops/set.cmd.js create mode 100644 lib/logic/cli-config/errors.js create mode 100644 lib/logic/cli-config/manager.js create mode 100644 lib/logic/cli-config/model.js create mode 100644 lib/logic/cli-config/schema.js diff --git a/lib/interface/cli/commands/cli-config/cli-config.cmd.js b/lib/interface/cli/commands/cli-config/cli-config.cmd.js new file mode 100644 index 000000000..8d5cdd162 --- /dev/null +++ b/lib/interface/cli/commands/cli-config/cli-config.cmd.js @@ -0,0 +1,40 @@ +const Command = require('../../Command'); + +const manager = require('../../../../logic/cli-config/manager'); +const {outputCliConfig} = require('../../helpers/cli-config'); + +// todo : add profiles +// todo : check descriptions for docs +const cliConfig = new Command({ + root: true, + command: 'cli-config', + description: 'Options for codefresh cli', + usage: 'Prints current cli-config', + webDocs: { + description: 'Prints current cli-config', + category: 'CLI Config', + title: 'CLI Config', + weight: 20, + }, + builder: (yargs) => { + return yargs + .option('output', { + alias: 'o', + describe: 'Output format', + options: ['json', 'yaml'], + }); + }, + handler: async (argv) => { + let config; + try { + config = manager.config(); + } catch (e) { + + return; + } + console.log(`Using profile: | ${manager.profile()} |\n`); + outputCliConfig(argv.output, config); + }, +}); + +module.exports = cliConfig; diff --git a/lib/interface/cli/commands/cli-config/ops/get.cmd.js b/lib/interface/cli/commands/cli-config/ops/get.cmd.js new file mode 100644 index 000000000..483886ecb --- /dev/null +++ b/lib/interface/cli/commands/cli-config/ops/get.cmd.js @@ -0,0 +1,46 @@ +const Command = require('../../../Command'); +const cliCommand = require('../cli-config.cmd'); + +const manager = require('../../../../../logic/cli-config/manager'); +const {outputSingleOption, propertyErrorHandler, printProperties} = require('../../../helpers/cli-config'); + +// todo : fix descriptions for docs +// todo : value validation by schema +const getCommand = new Command({ + command: 'get [name]', + parent: cliCommand, + description: 'Options for codefresh cli', + usage: 'Prints current cli-config', + webDocs: { + description: 'Create a resource from a file, directory or url', + category: 'Operate On Resources', + title: 'CLI Config', + weight: 20, + }, + builder: (yargs) => { + return yargs + .positional('name', { + describe: 'Property name', + }); + }, + handler: async (argv) => { + const propertyName = argv.name; + if (!propertyName) { + console.log('Available properties: \n'); + printProperties(manager.availableProperties()); + return; + } + + let config; + try { + config = manager.get(propertyName); + } catch (e) { + propertyErrorHandler(e); + return; + } + console.log(`Using profile: | ${manager.profile()} |\n`); + outputSingleOption(propertyName, config); + }, +}); + +module.exports = getCommand; diff --git a/lib/interface/cli/commands/cli-config/ops/set.cmd.js b/lib/interface/cli/commands/cli-config/ops/set.cmd.js new file mode 100644 index 000000000..9d5e7b600 --- /dev/null +++ b/lib/interface/cli/commands/cli-config/ops/set.cmd.js @@ -0,0 +1,47 @@ +const Command = require('../../../Command'); +const cliCommand = require('../cli-config.cmd'); + +const manager = require('../../../../../logic/cli-config/manager'); +const {outputSingleOption, propertyErrorHandler, printProperties} = require('../../../helpers/cli-config'); + +// todo : fix descriptions for docs +const setCommand = new Command({ + command: 'set [name]', + parent: cliCommand, + description: 'Options for codefresh cli', + usage: 'Prints current cli-config', + webDocs: { + description: 'Create a resource from a file, directory or url', + category: 'Operate On Resources', + title: 'CLI Config', + weight: 20, + }, + builder: (yargs) => { + return yargs + .positional('name', { + describe: 'Property name', + }) + .positional('value', { + describe: 'Property name', + }); + }, + handler: async (argv) => { + const { propertyName, value } = argv; + if (!propertyName) { + console.log('Available properties: \n'); + printProperties(manager.availableProperties()); + return; + } + + try { + manager.set(propertyName, value); + } catch (e) { + propertyErrorHandler(e); + return; + } + console.log(`Property set on profile: | ${manager.profile()} |\n`); + outputSingleOption(propertyName, value); + }, +}); + +module.exports = setCommand; diff --git a/lib/interface/cli/helpers/cli-config.js b/lib/interface/cli/helpers/cli-config.js index 75ea5753c..f15765bbd 100644 --- a/lib/interface/cli/helpers/cli-config.js +++ b/lib/interface/cli/helpers/cli-config.js @@ -2,10 +2,11 @@ const _ = require('lodash'); const flattern = require('flat'); const columnify = require('columnify'); const yaml = require('js-yaml'); +const {NoPropertyError, MultiplePropertiesError, NotFullPropertyError} = require('../../../logic/cli-config/errors'); const _jsonFormatter = (data) => JSON.stringify(data, null, 4); -const _flatteredObjComparator = (a, b) => { +const propertyComparator = (a, b) => { const aDots = a.split('.').length; const bDots = b.split('.').length; if (aDots > bDots) return 1; @@ -20,7 +21,7 @@ const _flatteredObjComparator = (a, b) => { function _defaultOutput(data) { const flat = flattern(data); const columns = _.keys(flat) - .sort(_flatteredObjComparator) + .sort(propertyComparator) .map((key) => { return { key, value: _.get(data, key) }; }); @@ -43,6 +44,35 @@ function outputCliConfig(format, data) { console.log(formatter(data)); } +function outputSingleOption(key, value) { + console.log(columnify([{key, value}])); +} + +function printProperties(properties) { + properties.sort(propertyComparator).forEach(prop => console.log(prop)); +} + +function propertyErrorHandler(e) { + if (e instanceof NoPropertyError) { + console.log(`Property "${e.property}" is not supported`); + return; + } + if (e instanceof MultiplePropertiesError) { + console.log('Choose one of the following properties:\n'); + printProperties(e.properties); + return; + } + if (e instanceof NotFullPropertyError) { + console.log(`Did you mean property: ${e.property}?`); + return; + } + throw e; +} + module.exports = { outputCliConfig, + outputSingleOption, + propertyComparator, + printProperties, + propertyErrorHandler }; diff --git a/lib/logic/cli-config/errors.js b/lib/logic/cli-config/errors.js new file mode 100644 index 000000000..20f603ebd --- /dev/null +++ b/lib/logic/cli-config/errors.js @@ -0,0 +1,26 @@ +class NoPropertyError extends Error{ + constructor(property) { + super(); + this.property = property; + } +} + +class MultiplePropertiesError extends Error{ + constructor(properties) { + super(); + this.properties = properties; + } +} + +class NotFullPropertyError extends Error{ + constructor(property) { + super(); + this.property = property; + } +} + +module.exports = { + NoPropertyError, + MultiplePropertiesError, + NotFullPropertyError, +}; diff --git a/lib/logic/cli-config/manager.js b/lib/logic/cli-config/manager.js new file mode 100644 index 000000000..ea18d461c --- /dev/null +++ b/lib/logic/cli-config/manager.js @@ -0,0 +1,119 @@ +const CFError = require('cf-errors'); // eslint-disable-line +const _ = require('lodash'); +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); +const {NoPropertyError, MultiplePropertiesError, NotFullPropertyError} = require('./errors'); + +const model = require('./model'); + +const {codefreshPath} = require('../../constants'); + +const dirPath = path.resolve(codefreshPath, 'cli-config'); +const filePath = path.resolve(dirPath, 'config.yaml'); + +let FULL_CONFIG; + +function _loadFullConfig() { + if (!fs.existsSync(codefreshPath)) { + fs.mkdirSync(codefreshPath); + } + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath); + } + if (!fs.existsSync(filePath)) { + const fullConfig = { + currentProfile: 'default', + profiles: { + default: model.default(), + }, + }; + const file = fs.openSync(filePath, 'w'); + fs.writeSync(file, yaml.safeDump(fullConfig)); + fs.closeSync(file); + return fullConfig; + } + return yaml.safeLoad(fs.readFileSync(filePath)); +} +// +// function _loadConfig(profile) { +// +// } + +class CliConfigManager { + constructor(config) { + this._config = config; + } + + get(propertyName) { + const properties = model.findProperties(propertyName); + this.validate(properties, propertyName); + return _.get(this._config, propertyName); + } + + set(propertyName, value) { + const properties = model.findProperties(propertyName); + this.validate(properties, propertyName); + _.set(this._config, propertyName, value); + } + + update(config) { + _.keys(config).forEach((key) => { + this.set(key, _.get(config, key)); + }); + } + + validate(properties, propertyName) { + if (!properties.length) { + throw new NoPropertyError(propertyName); + } + if (properties.length > 1) { + throw new MultiplePropertiesError(properties); + } + if (properties[0] !== propertyName) { + throw new NotFullPropertyError(properties[0]); + } + } + + config() { + return _.cloneDeep(this._config); + } + + profile() { + return FULL_CONFIG.currentProfile; + } + + availableProperties() { + return model.properties(); + } + + /** + * @return boolean: defines whether profile was created or not + * */ + useProfile(name) { + let created = false; + if (name === FULL_CONFIG.currentProfile) { + throw new CFError('Already using this profile'); + } + let selectedConfig = FULL_CONFIG.profiles[name]; + if (!selectedConfig) { + selectedConfig = {}; + FULL_CONFIG.profiles[name] = selectedConfig; + created = true; + } + this._config = _.defaultsDeep(selectedConfig, model.default()); + FULL_CONFIG.currentProfile = name; + return created; + } + + persistConfig() { + const file = fs.openSync(filePath, 'w'); + fs.writeSync(file, yaml.safeDump(FULL_CONFIG)); + fs.closeSync(file); + } +} + +FULL_CONFIG = _loadFullConfig(); +const profile = FULL_CONFIG.currentProfile || 'default'; + +module.exports = new CliConfigManager(FULL_CONFIG.profiles[profile]); diff --git a/lib/logic/cli-config/model.js b/lib/logic/cli-config/model.js new file mode 100644 index 000000000..485debf7b --- /dev/null +++ b/lib/logic/cli-config/model.js @@ -0,0 +1,31 @@ +const _ = require('lodash'); +const cliConfigSchema = require('./schema'); +const flatten = require('flat'); + + +class Model { + constructor(schema) { + this._schema = schema; + this._properties = _.keys(flatten(schema)); + } + + exists(propertyName) { + const field = _.get(this._schema, propertyName); + return field === undefined; + } + + default() { + return _.cloneDeep(this._schema); + } + + properties() { + return this._properties; + } + + findProperties(name) { + return this._properties.filter(prop => prop.includes(name)); + } +} + +module.exports = new Model(cliConfigSchema); + diff --git a/lib/logic/cli-config/schema.js b/lib/logic/cli-config/schema.js new file mode 100644 index 000000000..25a3214f8 --- /dev/null +++ b/lib/logic/cli-config/schema.js @@ -0,0 +1,15 @@ +/** + * Use for future properties definition. + * + * key -- property name + * value -- default value form Model.default() + * + * todo: add validation on schema + * */ +const schema = { + output: { + pretty: false, + }, +}; + +module.exports = schema; From 0edcb0120e53d93f474863ad29662f81b1788af7 Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Tue, 4 Dec 2018 14:30:56 +0200 Subject: [PATCH 04/17] profiles + schema validation + output changes + get command changes + docs --- .../cli/commands/cli-config/cli-config.cmd.js | 21 ++-- .../cli/commands/cli-config/ops/get.cmd.js | 30 ++--- .../commands/cli-config/ops/profile.cmd.js | 44 +++++++ .../cli/commands/cli-config/ops/set.cmd.js | 42 ++++--- lib/interface/cli/helpers/cli-config.js | 20 ++- lib/logic/cli-config/errors.js | 20 ++- lib/logic/cli-config/manager.js | 118 +++++++++--------- lib/logic/cli-config/model.js | 43 ++++--- lib/logic/cli-config/schema.js | 15 --- lib/logic/cli-config/schema.json | 17 +++ 10 files changed, 223 insertions(+), 147 deletions(-) create mode 100644 lib/interface/cli/commands/cli-config/ops/profile.cmd.js delete mode 100644 lib/logic/cli-config/schema.js create mode 100644 lib/logic/cli-config/schema.json diff --git a/lib/interface/cli/commands/cli-config/cli-config.cmd.js b/lib/interface/cli/commands/cli-config/cli-config.cmd.js index 8d5cdd162..6956d2132 100644 --- a/lib/interface/cli/commands/cli-config/cli-config.cmd.js +++ b/lib/interface/cli/commands/cli-config/cli-config.cmd.js @@ -3,15 +3,13 @@ const Command = require('../../Command'); const manager = require('../../../../logic/cli-config/manager'); const {outputCliConfig} = require('../../helpers/cli-config'); -// todo : add profiles -// todo : check descriptions for docs const cliConfig = new Command({ root: true, command: 'cli-config', - description: 'Options for codefresh cli', - usage: 'Prints current cli-config', + description: 'Codefresh CLI configuration. Uses profiles', + usage: 'CLI configuration is used for user-specific properties, for example pretty-print', webDocs: { - description: 'Prints current cli-config', + description: 'Codefresh CLI configuration. Uses profiles', category: 'CLI Config', title: 'CLI Config', weight: 20, @@ -22,17 +20,12 @@ const cliConfig = new Command({ alias: 'o', describe: 'Output format', options: ['json', 'yaml'], - }); + }) + .example('codefresh cli-config', 'Print configuration for current profile'); }, handler: async (argv) => { - let config; - try { - config = manager.config(); - } catch (e) { - - return; - } - console.log(`Using profile: | ${manager.profile()} |\n`); + const config = manager.config(); + console.log(`Current profile: | ${manager.currentProfile()} |\n`); outputCliConfig(argv.output, config); }, }); diff --git a/lib/interface/cli/commands/cli-config/ops/get.cmd.js b/lib/interface/cli/commands/cli-config/ops/get.cmd.js index 483886ecb..06d8ce718 100644 --- a/lib/interface/cli/commands/cli-config/ops/get.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/get.cmd.js @@ -2,44 +2,44 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); const manager = require('../../../../../logic/cli-config/manager'); -const {outputSingleOption, propertyErrorHandler, printProperties} = require('../../../helpers/cli-config'); +const { printProperties, outputCliConfig, propertyErrorHandler } = require('../../../helpers/cli-config'); -// todo : fix descriptions for docs -// todo : value validation by schema const getCommand = new Command({ command: 'get [name]', parent: cliCommand, - description: 'Options for codefresh cli', - usage: 'Prints current cli-config', + description: 'For current profile get all properties containing provided "name"', + usage: 'Used when you may need to know some exact properties values', webDocs: { - description: 'Create a resource from a file, directory or url', - category: 'Operate On Resources', - title: 'CLI Config', + description: 'For current profile get all properties containing provided "name"', + category: 'CLI Config', + title: 'Get Config Properties', weight: 20, }, builder: (yargs) => { return yargs .positional('name', { describe: 'Property name', - }); + }) + .example('codefresh cli-config get', 'Print available property names') + .example('codefresh cli-config get output', 'Print properties, containing "output" word') + .example('codefresh cli-config get output.pretty', 'Print properties, containing "output.pretty" path'); }, handler: async (argv) => { const propertyName = argv.name; if (!propertyName) { - console.log('Available properties: \n'); + console.log('Available properties:\n'); printProperties(manager.availableProperties()); return; } - let config; + let properties; try { - config = manager.get(propertyName); + properties = manager.get(propertyName); } catch (e) { propertyErrorHandler(e); - return; } - console.log(`Using profile: | ${manager.profile()} |\n`); - outputSingleOption(propertyName, config); + console.log(`Current profile: | ${manager.currentProfile()} |\n`); + outputCliConfig(argv.output, properties); }, }); diff --git a/lib/interface/cli/commands/cli-config/ops/profile.cmd.js b/lib/interface/cli/commands/cli-config/ops/profile.cmd.js new file mode 100644 index 000000000..121ce8c62 --- /dev/null +++ b/lib/interface/cli/commands/cli-config/ops/profile.cmd.js @@ -0,0 +1,44 @@ +const Command = require('../../../Command'); +const cliCommand = require('../cli-config.cmd'); + +const CliConfigManager = require('../../../../../logic/cli-config/manager'); + +const setCommand = new Command({ + command: 'profile [name]', + parent: cliCommand, + description: 'Change cli-config profile', + usage: 'Using profiles gives an ability to switch fast between the different configuration sets', + webDocs: { + description: 'Change cli-config profile', + category: 'CLI Config', + title: 'CLI Config: Profiles', + weight: 20, + }, + builder: (yargs) => { + return yargs + .positional('name', { + describe: 'Profile name', + }) + .example('codefresh cli-config profile myProfile', 'Use or create profile with name "myProfile"'); + }, + handler: async (argv) => { + const { name } = argv; + + if (!name) { + console.log(`Current profile: | ${CliConfigManager.currentProfile()} |\n`); + console.log('Available profiles:'); + CliConfigManager.profiles().forEach((profile) => { + console.log(profile); + }); + return; + } + + const created = CliConfigManager.useProfile(name); + CliConfigManager.persistConfig(); + + const message = created ? `Profile "${name}" was created` : `Using profile "${name}"`; + console.log(message); + }, +}); + +module.exports = setCommand; diff --git a/lib/interface/cli/commands/cli-config/ops/set.cmd.js b/lib/interface/cli/commands/cli-config/ops/set.cmd.js index 9d5e7b600..6d0b0972c 100644 --- a/lib/interface/cli/commands/cli-config/ops/set.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/set.cmd.js @@ -1,19 +1,18 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); -const manager = require('../../../../../logic/cli-config/manager'); -const {outputSingleOption, propertyErrorHandler, printProperties} = require('../../../helpers/cli-config'); +const Manager = require('../../../../../logic/cli-config/manager'); +const {outputCliConfig, propertyErrorHandler, printProperties} = require('../../../helpers/cli-config'); // todo : fix descriptions for docs const setCommand = new Command({ - command: 'set [name]', + command: 'set [name] [value]', parent: cliCommand, - description: 'Options for codefresh cli', - usage: 'Prints current cli-config', + description: 'For current profile set a property with "name"', webDocs: { - description: 'Create a resource from a file, directory or url', - category: 'Operate On Resources', - title: 'CLI Config', + description: 'For current profile set an available property', + category: 'CLI Config', + title: 'Set Config Property', weight: 20, }, builder: (yargs) => { @@ -22,25 +21,34 @@ const setCommand = new Command({ describe: 'Property name', }) .positional('value', { - describe: 'Property name', - }); + describe: 'Property value', + }) + .example('codefresh cli-config set', 'Print available property names') + .example('codefresh cli-config set output.pretty false', 'Set "output.pretty" property'); }, handler: async (argv) => { - const { propertyName, value } = argv; - if (!propertyName) { - console.log('Available properties: \n'); - printProperties(manager.availableProperties()); + const { name, value } = argv; + if (!name) { + console.log('Available properties:\n'); + printProperties(Manager.availableProperties()); + return; + } + if (!value) { + console.log(`No value provided for property "${name}"`); return; } + let props; try { - manager.set(propertyName, value); + Manager.set(name, value); + Manager.persistConfig(); + props = Manager.get(name); } catch (e) { propertyErrorHandler(e); return; } - console.log(`Property set on profile: | ${manager.profile()} |\n`); - outputSingleOption(propertyName, value); + console.log(`Property set on profile: | ${Manager.currentProfile()} |\n`); + outputCliConfig(argv.output, props); }, }); diff --git a/lib/interface/cli/helpers/cli-config.js b/lib/interface/cli/helpers/cli-config.js index f15765bbd..3e014a748 100644 --- a/lib/interface/cli/helpers/cli-config.js +++ b/lib/interface/cli/helpers/cli-config.js @@ -1,10 +1,10 @@ const _ = require('lodash'); const flattern = require('flat'); const columnify = require('columnify'); -const yaml = require('js-yaml'); -const {NoPropertyError, MultiplePropertiesError, NotFullPropertyError} = require('../../../logic/cli-config/errors'); +const yaml = require('js-yaml'); +const { NoPropertyError, MultiplePropertiesError, NotFullPropertyError, SchemaValidationError } = require('../../../logic/cli-config/errors'); -const _jsonFormatter = (data) => JSON.stringify(data, null, 4); +const _jsonFormatter = data => JSON.stringify(data, null, 4); const propertyComparator = (a, b) => { const aDots = a.split('.').length; @@ -25,7 +25,7 @@ function _defaultOutput(data) { .map((key) => { return { key, value: _.get(data, key) }; }); - return columnify(columns, {columnSplitter: ' '}); + return columnify(columns, { columnSplitter: ' ' }); } function _formatter(format) { @@ -44,10 +44,6 @@ function outputCliConfig(format, data) { console.log(formatter(data)); } -function outputSingleOption(key, value) { - console.log(columnify([{key, value}])); -} - function printProperties(properties) { properties.sort(propertyComparator).forEach(prop => console.log(prop)); } @@ -66,13 +62,15 @@ function propertyErrorHandler(e) { console.log(`Did you mean property: ${e.property}?`); return; } + if (e instanceof SchemaValidationError) { + e.printErrors(); + return; + } throw e; } module.exports = { outputCliConfig, - outputSingleOption, - propertyComparator, printProperties, - propertyErrorHandler + propertyErrorHandler, }; diff --git a/lib/logic/cli-config/errors.js b/lib/logic/cli-config/errors.js index 20f603ebd..1bf7ee3d7 100644 --- a/lib/logic/cli-config/errors.js +++ b/lib/logic/cli-config/errors.js @@ -1,26 +1,40 @@ -class NoPropertyError extends Error{ +class NoPropertyError extends Error { constructor(property) { super(); this.property = property; } } -class MultiplePropertiesError extends Error{ +class MultiplePropertiesError extends Error { constructor(properties) { super(); this.properties = properties; } } -class NotFullPropertyError extends Error{ +class NotFullPropertyError extends Error { constructor(property) { super(); this.property = property; } } +class SchemaValidationError extends Error { + constructor(errors) { + super(); + this.errors = errors; + } + + printErrors() { + this.errors.forEach((e) => { + console.log(`Validation error: property "${e.dataPath.replace('.', '')}" ${e.message}`); + }); + } +} + module.exports = { NoPropertyError, MultiplePropertiesError, NotFullPropertyError, + SchemaValidationError, }; diff --git a/lib/logic/cli-config/manager.js b/lib/logic/cli-config/manager.js index ea18d461c..58e5a133e 100644 --- a/lib/logic/cli-config/manager.js +++ b/lib/logic/cli-config/manager.js @@ -1,18 +1,29 @@ -const CFError = require('cf-errors'); // eslint-disable-line const _ = require('lodash'); const fs = require('fs'); const path = require('path'); const yaml = require('js-yaml'); -const {NoPropertyError, MultiplePropertiesError, NotFullPropertyError} = require('./errors'); +const { NoPropertyError, MultiplePropertiesError, NotFullPropertyError } = require('./errors'); -const model = require('./model'); +const Model = require('./model'); -const {codefreshPath} = require('../../constants'); +const { codefreshPath } = require('../../constants'); const dirPath = path.resolve(codefreshPath, 'cli-config'); const filePath = path.resolve(dirPath, 'config.yaml'); let FULL_CONFIG; +let CURRENT_CONFIG; + +/** + * Remove properties that not exist at schema and add default values + * */ +function _extractConfig(config = {}) { + const newConfig = {}; + Model.properties().forEach((key) => { + _.set(newConfig, key, _.get(config, key)); + }); + return _.defaultsDeep(newConfig, Model.default()); +} function _loadFullConfig() { if (!fs.existsSync(codefreshPath)) { @@ -25,7 +36,7 @@ function _loadFullConfig() { const fullConfig = { currentProfile: 'default', profiles: { - default: model.default(), + default: Model.default(), }, }; const file = fs.openSync(filePath, 'w'); @@ -35,78 +46,73 @@ function _loadFullConfig() { } return yaml.safeLoad(fs.readFileSync(filePath)); } -// -// function _loadConfig(profile) { -// -// } -class CliConfigManager { - constructor(config) { - this._config = config; +function _validate(properties, propertyName) { + if (!properties.length) { + throw new NoPropertyError(propertyName); } - - get(propertyName) { - const properties = model.findProperties(propertyName); - this.validate(properties, propertyName); - return _.get(this._config, propertyName); - } - - set(propertyName, value) { - const properties = model.findProperties(propertyName); - this.validate(properties, propertyName); - _.set(this._config, propertyName, value); + if (properties.length > 1) { + throw new MultiplePropertiesError(properties); } - - update(config) { - _.keys(config).forEach((key) => { - this.set(key, _.get(config, key)); - }); + if (properties[0] !== propertyName) { + throw new NotFullPropertyError(properties[0]); } +} - validate(properties, propertyName) { +class CliConfigManager { + /** + * Get all available properties containing "propertyName" + * */ + static get(propertyName) { + const properties = Model.findProperties(propertyName); if (!properties.length) { throw new NoPropertyError(propertyName); } - if (properties.length > 1) { - throw new MultiplePropertiesError(properties); - } - if (properties[0] !== propertyName) { - throw new NotFullPropertyError(properties[0]); - } + return properties.reduce((obj, key) => { + _.set(obj, key, _.get(CURRENT_CONFIG, key)); + return obj; + }, {}); + } + + + static set(propertyName, value) { + const properties = Model.findProperties(propertyName); + _validate(properties, propertyName); + _.set(CURRENT_CONFIG, propertyName, value); + Model.validate(CURRENT_CONFIG); } - config() { - return _.cloneDeep(this._config); + static config() { + return _.cloneDeep(CURRENT_CONFIG); } - profile() { + static currentProfile() { return FULL_CONFIG.currentProfile; } - availableProperties() { - return model.properties(); + static profiles() { + return _.keys(FULL_CONFIG.profiles); + } + + static availableProperties() { + return Model.properties(); } /** + * Use or create a profile. + * * @return boolean: defines whether profile was created or not * */ - useProfile(name) { - let created = false; - if (name === FULL_CONFIG.currentProfile) { - throw new CFError('Already using this profile'); - } - let selectedConfig = FULL_CONFIG.profiles[name]; - if (!selectedConfig) { - selectedConfig = {}; - FULL_CONFIG.profiles[name] = selectedConfig; - created = true; - } - this._config = _.defaultsDeep(selectedConfig, model.default()); - FULL_CONFIG.currentProfile = name; + static useProfile(profile) { + const selectedConfig = FULL_CONFIG.profiles[profile]; + const created = !selectedConfig; + CURRENT_CONFIG = _extractConfig(selectedConfig); + FULL_CONFIG.profiles[profile] = CURRENT_CONFIG; + FULL_CONFIG.currentProfile = profile; return created; } - persistConfig() { + static persistConfig() { const file = fs.openSync(filePath, 'w'); fs.writeSync(file, yaml.safeDump(FULL_CONFIG)); fs.closeSync(file); @@ -114,6 +120,6 @@ class CliConfigManager { } FULL_CONFIG = _loadFullConfig(); -const profile = FULL_CONFIG.currentProfile || 'default'; +CliConfigManager.useProfile(FULL_CONFIG.currentProfile || 'default'); -module.exports = new CliConfigManager(FULL_CONFIG.profiles[profile]); +module.exports = CliConfigManager; diff --git a/lib/logic/cli-config/model.js b/lib/logic/cli-config/model.js index 485debf7b..1c890a916 100644 --- a/lib/logic/cli-config/model.js +++ b/lib/logic/cli-config/model.js @@ -1,31 +1,42 @@ const _ = require('lodash'); -const cliConfigSchema = require('./schema'); +const Ajv = require('ajv'); const flatten = require('flat'); +const { SchemaValidationError } = require('../../logic/cli-config/errors'); -class Model { - constructor(schema) { - this._schema = schema; - this._properties = _.keys(flatten(schema)); - } +const ajv = new Ajv({ coerceTypes: true, useDefaults: true }); +const cliConfigSchema = require('./schema.json'); + +const validate = ajv.compile(cliConfigSchema); + +const defaults = {}; +validate(defaults); // fill with default values - exists(propertyName) { - const field = _.get(this._schema, propertyName); - return field === undefined; +const properties = _.keys(flatten(defaults)); + + +class Model { + static default() { + return _.cloneDeep(defaults); } - default() { - return _.cloneDeep(this._schema); + static properties() { + return _.cloneDeep(properties); } - properties() { - return this._properties; + static findProperties(name) { + return properties.filter(prop => prop.includes(name)); } - findProperties(name) { - return this._properties.filter(prop => prop.includes(name)); + /** + * also fills with default values and coerces types + * */ + static validate(obj) { + if (!ajv.validate('cli-config-schema', obj)) { + throw new SchemaValidationError(ajv.errors); + } } } -module.exports = new Model(cliConfigSchema); +module.exports = Model; diff --git a/lib/logic/cli-config/schema.js b/lib/logic/cli-config/schema.js deleted file mode 100644 index 25a3214f8..000000000 --- a/lib/logic/cli-config/schema.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Use for future properties definition. - * - * key -- property name - * value -- default value form Model.default() - * - * todo: add validation on schema - * */ -const schema = { - output: { - pretty: false, - }, -}; - -module.exports = schema; diff --git a/lib/logic/cli-config/schema.json b/lib/logic/cli-config/schema.json new file mode 100644 index 000000000..b2be17c88 --- /dev/null +++ b/lib/logic/cli-config/schema.json @@ -0,0 +1,17 @@ +{ + "$id": "cli-config-schema", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "output": { + "type": "object", + "properties": { + "pretty": { + "type": "boolean", + "default": false + } + }, + "default": {} + } + } +} From 38e34b90ca54770c92d9417ae070ec849ab358be Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Tue, 4 Dec 2018 15:01:13 +0200 Subject: [PATCH 05/17] packages + docs --- docs/index.js | 1 + .../cli/commands/cli-config/cli-config.cmd.js | 5 +++-- .../cli/commands/cli-config/ops/get.cmd.js | 2 +- .../commands/cli-config/ops/profile.cmd.js | 4 ++-- .../cli/commands/cli-config/ops/set.cmd.js | 2 +- package.json | 1 + yarn.lock | 22 +++++++++++++++++++ 7 files changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/index.js b/docs/index.js index bbd8e87f9..05a9a1b62 100644 --- a/docs/index.js +++ b/docs/index.js @@ -27,6 +27,7 @@ const categoriesOrder = { 'helm repos' : 111 , 'predefined pipelines': 120, teams: 130, + 'cli config': 121, more : 150, }; diff --git a/lib/interface/cli/commands/cli-config/cli-config.cmd.js b/lib/interface/cli/commands/cli-config/cli-config.cmd.js index 6956d2132..e80d2d2c0 100644 --- a/lib/interface/cli/commands/cli-config/cli-config.cmd.js +++ b/lib/interface/cli/commands/cli-config/cli-config.cmd.js @@ -8,11 +8,12 @@ const cliConfig = new Command({ command: 'cli-config', description: 'Codefresh CLI configuration. Uses profiles', usage: 'CLI configuration is used for user-specific properties, for example pretty-print', + category: 'cli config', webDocs: { description: 'Codefresh CLI configuration. Uses profiles', category: 'CLI Config', - title: 'CLI Config', - weight: 20, + title: 'Show Config', + weight: 130, }, builder: (yargs) => { return yargs diff --git a/lib/interface/cli/commands/cli-config/ops/get.cmd.js b/lib/interface/cli/commands/cli-config/ops/get.cmd.js index 06d8ce718..8093e8cd5 100644 --- a/lib/interface/cli/commands/cli-config/ops/get.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/get.cmd.js @@ -13,7 +13,7 @@ const getCommand = new Command({ description: 'For current profile get all properties containing provided "name"', category: 'CLI Config', title: 'Get Config Properties', - weight: 20, + weight: 110, }, builder: (yargs) => { return yargs diff --git a/lib/interface/cli/commands/cli-config/ops/profile.cmd.js b/lib/interface/cli/commands/cli-config/ops/profile.cmd.js index 121ce8c62..e06761007 100644 --- a/lib/interface/cli/commands/cli-config/ops/profile.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/profile.cmd.js @@ -11,8 +11,8 @@ const setCommand = new Command({ webDocs: { description: 'Change cli-config profile', category: 'CLI Config', - title: 'CLI Config: Profiles', - weight: 20, + title: 'Profiles', + weight: 120, }, builder: (yargs) => { return yargs diff --git a/lib/interface/cli/commands/cli-config/ops/set.cmd.js b/lib/interface/cli/commands/cli-config/ops/set.cmd.js index 6d0b0972c..a9a39a721 100644 --- a/lib/interface/cli/commands/cli-config/ops/set.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/set.cmd.js @@ -13,7 +13,7 @@ const setCommand = new Command({ description: 'For current profile set an available property', category: 'CLI Config', title: 'Set Config Property', - weight: 20, + weight: 110, }, builder: (yargs) => { return yargs diff --git a/package.json b/package.json index 378148280..1637702d8 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@codefresh-io/docker-reference": "^0.0.5", + "ajv": "^6.6.1", "bluebird": "^3.5.1", "cf-errors": "^0.1.11", "chalk": "^1.1.3", diff --git a/yarn.lock b/yarn.lock index 8211ea8fd..edb98398c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -88,6 +88,16 @@ ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.6.1: + version "6.6.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" + integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -1549,6 +1559,13 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" +flat@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" + integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + dependencies: + is-buffer "~2.0.3" + follow-redirects@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-0.0.7.tgz#34b90bab2a911aa347571da90f22bd36ecd8a919" @@ -2060,6 +2077,11 @@ is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" +is-buffer@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" + integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== + is-builtin-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" From 0e7a6726c0630fe71e54208bfb586b5d37d355db Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Tue, 4 Dec 2018 15:02:38 +0200 Subject: [PATCH 06/17] fix docs order --- docs/index.js | 2 +- lib/interface/cli/commands/cli-config/cli-config.cmd.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.js b/docs/index.js index 05a9a1b62..d9352f3d3 100644 --- a/docs/index.js +++ b/docs/index.js @@ -26,8 +26,8 @@ const categoriesOrder = { compositions : 110 , 'helm repos' : 111 , 'predefined pipelines': 120, - teams: 130, 'cli config': 121, + teams: 130, more : 150, }; diff --git a/lib/interface/cli/commands/cli-config/cli-config.cmd.js b/lib/interface/cli/commands/cli-config/cli-config.cmd.js index e80d2d2c0..501cbfe5f 100644 --- a/lib/interface/cli/commands/cli-config/cli-config.cmd.js +++ b/lib/interface/cli/commands/cli-config/cli-config.cmd.js @@ -13,7 +13,7 @@ const cliConfig = new Command({ description: 'Codefresh CLI configuration. Uses profiles', category: 'CLI Config', title: 'Show Config', - weight: 130, + weight: 100, }, builder: (yargs) => { return yargs From be5fe45462407f1e8efeb6a55a01a07561f843be Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Tue, 4 Dec 2018 16:37:25 +0200 Subject: [PATCH 07/17] removed constants --- lib/constants.js | 9 --------- .../cli/commands/runtimeEnvironments/create.cmd.js | 10 +++++----- lib/interface/cli/defaults.js | 4 ++++ lib/logic/api/cluster.js | 14 +++++++------- lib/logic/cli-config/manager.js | 8 ++++---- 5 files changed, 20 insertions(+), 25 deletions(-) delete mode 100644 lib/constants.js diff --git a/lib/constants.js b/lib/constants.js deleted file mode 100644 index 39c8de8a2..000000000 --- a/lib/constants.js +++ /dev/null @@ -1,9 +0,0 @@ -const { homedir } = require('os'); -const path = require('path'); - -const codefreshPath = path.resolve(homedir(), '.Codefresh'); - - -module.exports = { - codefreshPath, -}; diff --git a/lib/interface/cli/commands/runtimeEnvironments/create.cmd.js b/lib/interface/cli/commands/runtimeEnvironments/create.cmd.js index b7a3f9f78..31628806e 100644 --- a/lib/interface/cli/commands/runtimeEnvironments/create.cmd.js +++ b/lib/interface/cli/commands/runtimeEnvironments/create.cmd.js @@ -6,10 +6,10 @@ const rp = require('request-promise'); const createRoot = require('../root/create.cmd'); const authManager = require('../../../../logic/auth').manager; // eslint-disable-line -const { codefreshPath } = require('../../../../constants'); +const { CODEFRESH_PATH } = require('../../defaults'); const scriptUrl = 'https://raw.githubusercontent.com/codefresh-io/k8s-dind-config/master/codefresh-k8s-configure.sh'; -let filePath = `${codefreshPath}/runtime/codefresh-k8s-configure.sh`; -const dirPath = `${codefreshPath}/runtime`; +let filePath = `${CODEFRESH_PATH}/runtime/codefresh-k8s-configure.sh`; +const dirPath = `${CODEFRESH_PATH}/runtime`; const callToScript = (k8sScript) =>{ @@ -60,8 +60,8 @@ const command = new Command({ context = ''; } if (!process.env.LOCAL) { - if (!fs.existsSync(codefreshPath)) { - fs.mkdirSync(codefreshPath); + if (!fs.existsSync(CODEFRESH_PATH)) { + fs.mkdirSync(CODEFRESH_PATH); fs.mkdirSync(dirPath); } else if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); diff --git a/lib/interface/cli/defaults.js b/lib/interface/cli/defaults.js index 557f48b47..8f6d5563e 100644 --- a/lib/interface/cli/defaults.js +++ b/lib/interface/cli/defaults.js @@ -1,3 +1,6 @@ +const { homedir } = require('os'); +const path = require('path'); + const DEFAULTS = { URL: 'https://g.codefresh.io', CFCONFIG: `${process.env.HOME || process.env.USERPROFILE}/.cfconfig`, @@ -7,6 +10,7 @@ const DEFAULTS = { CODEFRESH_REGISTRIES: ['r.cfcr.io', '192.168.99.100:5000'], WATCH_INTERVAL_MS: 3000, MAX_CONSECUTIVE_ERRORS_LIMIT: 10, + CODEFRESH_PATH: path.resolve(homedir(), '.Codefresh'), }; module.exports = DEFAULTS; diff --git a/lib/logic/api/cluster.js b/lib/logic/api/cluster.js index c28c920a2..b6d5545a3 100644 --- a/lib/logic/api/cluster.js +++ b/lib/logic/api/cluster.js @@ -10,7 +10,7 @@ const decompress = require('decompress'); const decompressTargz = require('decompress-targz'); const compareVersions = require('compare-versions'); -const { codefreshPath } = require('../../constants'); +const { CODEFRESH_PATH } = require('../../interface/cli/defaults'); const _createClusterScript = (info, filePath) => { const { name, context } = info; @@ -48,11 +48,11 @@ const getAllClusters = async () => { }; const createCluster = async (info) => { - const dirPath = `${codefreshPath}/cluster`; - const filePath = `${codefreshPath}/cluster/stevedore`; - const versionPath = `${codefreshPath}/cluster/version.txt`; + const dirPath = `${CODEFRESH_PATH}/cluster`; + const filePath = `${CODEFRESH_PATH}/cluster/stevedore`; + const versionPath = `${CODEFRESH_PATH}/cluster/version.txt`; const versionUrl = 'https://raw.githubusercontent.com/codefresh-io/Stevedore/master/VERSION'; - let zipPath = `${codefreshPath}/cluster/data`; + let zipPath = `${CODEFRESH_PATH}/cluster/data`; let shouldUpdate = true; const options = { url: versionUrl, @@ -62,8 +62,8 @@ const createCluster = async (info) => { }, }; const version = await rp(options); - if (!fs.existsSync(codefreshPath)) { - fs.mkdirSync(codefreshPath); + if (!fs.existsSync(CODEFRESH_PATH)) { + fs.mkdirSync(CODEFRESH_PATH); fs.mkdirSync(dirPath); } else if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); diff --git a/lib/logic/cli-config/manager.js b/lib/logic/cli-config/manager.js index 58e5a133e..2dd52869e 100644 --- a/lib/logic/cli-config/manager.js +++ b/lib/logic/cli-config/manager.js @@ -6,9 +6,9 @@ const { NoPropertyError, MultiplePropertiesError, NotFullPropertyError } = requi const Model = require('./model'); -const { codefreshPath } = require('../../constants'); +const { CODEFRESH_PATH } = require('../../interface/cli/defaults'); -const dirPath = path.resolve(codefreshPath, 'cli-config'); +const dirPath = path.resolve(CODEFRESH_PATH, 'cli-config'); const filePath = path.resolve(dirPath, 'config.yaml'); let FULL_CONFIG; @@ -26,8 +26,8 @@ function _extractConfig(config = {}) { } function _loadFullConfig() { - if (!fs.existsSync(codefreshPath)) { - fs.mkdirSync(codefreshPath); + if (!fs.existsSync(CODEFRESH_PATH)) { + fs.mkdirSync(CODEFRESH_PATH); } if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); From b651b31a77544d1f8973e005322aece43228797e Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Tue, 4 Dec 2018 16:42:37 +0200 Subject: [PATCH 08/17] errors refactored --- lib/logic/cli-config/errors.js | 40 ------------------- .../errors/MultiplePropertiesError.js | 8 ++++ .../cli-config/errors/NoPropertyError.js | 8 ++++ .../cli-config/errors/NotFullPropertyError.js | 8 ++++ .../errors/SchemaValidationError.js | 14 +++++++ lib/logic/cli-config/errors/index.js | 11 +++++ lib/logic/cli-config/model.js | 2 +- 7 files changed, 50 insertions(+), 41 deletions(-) delete mode 100644 lib/logic/cli-config/errors.js create mode 100644 lib/logic/cli-config/errors/MultiplePropertiesError.js create mode 100644 lib/logic/cli-config/errors/NoPropertyError.js create mode 100644 lib/logic/cli-config/errors/NotFullPropertyError.js create mode 100644 lib/logic/cli-config/errors/SchemaValidationError.js create mode 100644 lib/logic/cli-config/errors/index.js diff --git a/lib/logic/cli-config/errors.js b/lib/logic/cli-config/errors.js deleted file mode 100644 index 1bf7ee3d7..000000000 --- a/lib/logic/cli-config/errors.js +++ /dev/null @@ -1,40 +0,0 @@ -class NoPropertyError extends Error { - constructor(property) { - super(); - this.property = property; - } -} - -class MultiplePropertiesError extends Error { - constructor(properties) { - super(); - this.properties = properties; - } -} - -class NotFullPropertyError extends Error { - constructor(property) { - super(); - this.property = property; - } -} - -class SchemaValidationError extends Error { - constructor(errors) { - super(); - this.errors = errors; - } - - printErrors() { - this.errors.forEach((e) => { - console.log(`Validation error: property "${e.dataPath.replace('.', '')}" ${e.message}`); - }); - } -} - -module.exports = { - NoPropertyError, - MultiplePropertiesError, - NotFullPropertyError, - SchemaValidationError, -}; diff --git a/lib/logic/cli-config/errors/MultiplePropertiesError.js b/lib/logic/cli-config/errors/MultiplePropertiesError.js new file mode 100644 index 000000000..3153dc939 --- /dev/null +++ b/lib/logic/cli-config/errors/MultiplePropertiesError.js @@ -0,0 +1,8 @@ +class MultiplePropertiesError extends Error { + constructor(properties) { + super(); + this.properties = properties; + } +} + +module.exports = MultiplePropertiesError; diff --git a/lib/logic/cli-config/errors/NoPropertyError.js b/lib/logic/cli-config/errors/NoPropertyError.js new file mode 100644 index 000000000..2ea5d842f --- /dev/null +++ b/lib/logic/cli-config/errors/NoPropertyError.js @@ -0,0 +1,8 @@ +class NoPropertyError extends Error { + constructor(property) { + super(); + this.property = property; + } +} + +module.exports = NoPropertyError; diff --git a/lib/logic/cli-config/errors/NotFullPropertyError.js b/lib/logic/cli-config/errors/NotFullPropertyError.js new file mode 100644 index 000000000..8495ac119 --- /dev/null +++ b/lib/logic/cli-config/errors/NotFullPropertyError.js @@ -0,0 +1,8 @@ +class NotFullPropertyError extends Error { + constructor(property) { + super(); + this.property = property; + } +} + +module.exports = NotFullPropertyError; diff --git a/lib/logic/cli-config/errors/SchemaValidationError.js b/lib/logic/cli-config/errors/SchemaValidationError.js new file mode 100644 index 000000000..7b6fc4bc1 --- /dev/null +++ b/lib/logic/cli-config/errors/SchemaValidationError.js @@ -0,0 +1,14 @@ +class SchemaValidationError extends Error { + constructor(errors) { + super(); + this.errors = errors; + } + + printErrors() { + this.errors.forEach((e) => { + console.log(`Validation error: property "${e.dataPath.replace('.', '')}" ${e.message}`); + }); + } +} + +module.exports = SchemaValidationError; diff --git a/lib/logic/cli-config/errors/index.js b/lib/logic/cli-config/errors/index.js new file mode 100644 index 000000000..63cee5387 --- /dev/null +++ b/lib/logic/cli-config/errors/index.js @@ -0,0 +1,11 @@ +const NoPropertyError = require('./NoPropertyError'); +const MultiplePropertiesError = require('./MultiplePropertiesError'); +const NotFullPropertyError = require('./NotFullPropertyError'); +const SchemaValidationError = require('./SchemaValidationError'); + +module.exports = { + NoPropertyError, + MultiplePropertiesError, + NotFullPropertyError, + SchemaValidationError, +}; diff --git a/lib/logic/cli-config/model.js b/lib/logic/cli-config/model.js index 1c890a916..d7f4a6f13 100644 --- a/lib/logic/cli-config/model.js +++ b/lib/logic/cli-config/model.js @@ -2,7 +2,7 @@ const _ = require('lodash'); const Ajv = require('ajv'); const flatten = require('flat'); -const { SchemaValidationError } = require('../../logic/cli-config/errors'); +const { SchemaValidationError } = require('./errors'); const ajv = new Ajv({ coerceTypes: true, useDefaults: true }); const cliConfigSchema = require('./schema.json'); From 75f17f1f32130b5b3b1cc495826cddc077135689 Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Tue, 4 Dec 2018 19:33:02 +0200 Subject: [PATCH 09/17] set null support --- lib/interface/cli/commands/cli-config/cli-config.cmd.js | 8 ++++---- lib/interface/cli/commands/cli-config/ops/get.cmd.js | 2 +- lib/interface/cli/commands/cli-config/ops/profile.cmd.js | 2 +- lib/interface/cli/commands/cli-config/ops/set.cmd.js | 9 ++++++--- lib/logic/cli-config/{manager.js => Manager.js} | 2 +- lib/logic/cli-config/{model.js => Model.js} | 0 6 files changed, 13 insertions(+), 10 deletions(-) rename lib/logic/cli-config/{manager.js => Manager.js} (99%) rename lib/logic/cli-config/{model.js => Model.js} (100%) diff --git a/lib/interface/cli/commands/cli-config/cli-config.cmd.js b/lib/interface/cli/commands/cli-config/cli-config.cmd.js index 501cbfe5f..6e730f26c 100644 --- a/lib/interface/cli/commands/cli-config/cli-config.cmd.js +++ b/lib/interface/cli/commands/cli-config/cli-config.cmd.js @@ -1,7 +1,7 @@ const Command = require('../../Command'); -const manager = require('../../../../logic/cli-config/manager'); -const {outputCliConfig} = require('../../helpers/cli-config'); +const Manager = require('../../../../logic/cli-config/Manager'); +const { outputCliConfig } = require('../../helpers/cli-config'); const cliConfig = new Command({ root: true, @@ -25,8 +25,8 @@ const cliConfig = new Command({ .example('codefresh cli-config', 'Print configuration for current profile'); }, handler: async (argv) => { - const config = manager.config(); - console.log(`Current profile: | ${manager.currentProfile()} |\n`); + const config = Manager.config(); + console.log(`Current profile: | ${Manager.currentProfile()} |\n`); outputCliConfig(argv.output, config); }, }); diff --git a/lib/interface/cli/commands/cli-config/ops/get.cmd.js b/lib/interface/cli/commands/cli-config/ops/get.cmd.js index 8093e8cd5..4c5a2348d 100644 --- a/lib/interface/cli/commands/cli-config/ops/get.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/get.cmd.js @@ -1,7 +1,7 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); -const manager = require('../../../../../logic/cli-config/manager'); +const manager = require('../../../../../logic/cli-config/Manager'); const { printProperties, outputCliConfig, propertyErrorHandler } = require('../../../helpers/cli-config'); const getCommand = new Command({ diff --git a/lib/interface/cli/commands/cli-config/ops/profile.cmd.js b/lib/interface/cli/commands/cli-config/ops/profile.cmd.js index e06761007..2ac84aa9e 100644 --- a/lib/interface/cli/commands/cli-config/ops/profile.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/profile.cmd.js @@ -1,7 +1,7 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); -const CliConfigManager = require('../../../../../logic/cli-config/manager'); +const CliConfigManager = require('../../../../../logic/cli-config/Manager'); const setCommand = new Command({ command: 'profile [name]', diff --git a/lib/interface/cli/commands/cli-config/ops/set.cmd.js b/lib/interface/cli/commands/cli-config/ops/set.cmd.js index a9a39a721..90a7038ec 100644 --- a/lib/interface/cli/commands/cli-config/ops/set.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/set.cmd.js @@ -1,7 +1,7 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); -const Manager = require('../../../../../logic/cli-config/manager'); +const Manager = require('../../../../../logic/cli-config/Manager'); const {outputCliConfig, propertyErrorHandler, printProperties} = require('../../../helpers/cli-config'); // todo : fix descriptions for docs @@ -27,16 +27,19 @@ const setCommand = new Command({ .example('codefresh cli-config set output.pretty false', 'Set "output.pretty" property'); }, handler: async (argv) => { - const { name, value } = argv; + let { name, value } = argv; if (!name) { console.log('Available properties:\n'); printProperties(Manager.availableProperties()); return; } - if (!value) { + if (value === undefined) { console.log(`No value provided for property "${name}"`); return; } + if (value === 'null') { + value = null; + } let props; try { diff --git a/lib/logic/cli-config/manager.js b/lib/logic/cli-config/Manager.js similarity index 99% rename from lib/logic/cli-config/manager.js rename to lib/logic/cli-config/Manager.js index 2dd52869e..7ef5c7f9d 100644 --- a/lib/logic/cli-config/manager.js +++ b/lib/logic/cli-config/Manager.js @@ -4,7 +4,7 @@ const path = require('path'); const yaml = require('js-yaml'); const { NoPropertyError, MultiplePropertiesError, NotFullPropertyError } = require('./errors'); -const Model = require('./model'); +const Model = require('./Model'); const { CODEFRESH_PATH } = require('../../interface/cli/defaults'); diff --git a/lib/logic/cli-config/model.js b/lib/logic/cli-config/Model.js similarity index 100% rename from lib/logic/cli-config/model.js rename to lib/logic/cli-config/Model.js From 7ec8a09e50641311a601cc9052695d048e92d9ed Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Fri, 7 Dec 2018 13:05:09 +0200 Subject: [PATCH 10/17] help for properties + null is now displayed --- .../cli/commands/cli-config/cli-config.cmd.js | 6 ++++ .../cli/commands/cli-config/ops/get.cmd.js | 5 +++ .../cli/commands/cli-config/ops/help.cmd.js | 36 +++++++++++++++++++ .../commands/cli-config/ops/profile.cmd.js | 5 +++ .../cli/commands/cli-config/ops/set.cmd.js | 7 +++- lib/interface/cli/helpers/cli-config.js | 26 +++++++++++--- lib/logic/cli-config/Manager.js | 9 +++++ lib/logic/cli-config/Model.js | 10 +++++- lib/logic/cli-config/schema.json | 19 +++++++++- 9 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 lib/interface/cli/commands/cli-config/ops/help.cmd.js diff --git a/lib/interface/cli/commands/cli-config/cli-config.cmd.js b/lib/interface/cli/commands/cli-config/cli-config.cmd.js index 6e730f26c..59c0d335d 100644 --- a/lib/interface/cli/commands/cli-config/cli-config.cmd.js +++ b/lib/interface/cli/commands/cli-config/cli-config.cmd.js @@ -1,4 +1,5 @@ const Command = require('../../Command'); +const yargs = require('yargs'); const Manager = require('../../../../logic/cli-config/Manager'); const { outputCliConfig } = require('../../helpers/cli-config'); @@ -22,9 +23,14 @@ const cliConfig = new Command({ describe: 'Output format', options: ['json', 'yaml'], }) + .help(false) .example('codefresh cli-config', 'Print configuration for current profile'); }, handler: async (argv) => { + if (argv.help) { + yargs.showHelp(); + return; + } const config = Manager.config(); console.log(`Current profile: | ${Manager.currentProfile()} |\n`); outputCliConfig(argv.output, config); diff --git a/lib/interface/cli/commands/cli-config/ops/get.cmd.js b/lib/interface/cli/commands/cli-config/ops/get.cmd.js index 4c5a2348d..ce5573cbd 100644 --- a/lib/interface/cli/commands/cli-config/ops/get.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/get.cmd.js @@ -1,5 +1,6 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); +const yargs = require('yargs'); const manager = require('../../../../../logic/cli-config/Manager'); const { printProperties, outputCliConfig, propertyErrorHandler } = require('../../../helpers/cli-config'); @@ -25,6 +26,10 @@ const getCommand = new Command({ .example('codefresh cli-config get output.pretty', 'Print properties, containing "output.pretty" path'); }, handler: async (argv) => { + if (argv.help) { + yargs.showHelp(); + return; + } const propertyName = argv.name; if (!propertyName) { console.log('Available properties:\n'); diff --git a/lib/interface/cli/commands/cli-config/ops/help.cmd.js b/lib/interface/cli/commands/cli-config/ops/help.cmd.js new file mode 100644 index 000000000..51e2fe014 --- /dev/null +++ b/lib/interface/cli/commands/cli-config/ops/help.cmd.js @@ -0,0 +1,36 @@ +const Command = require('../../../Command'); +const cliCommand = require('../cli-config.cmd'); +const yargs = require('yargs'); + +const manager = require('../../../../../logic/cli-config/Manager'); +const { outputCliMeta } = require('../../../helpers/cli-config'); + +const helpCommand = new Command({ + command: 'help [name]', + parent: cliCommand, + description: 'Show help for properties', + usage: 'Used when you may need to know some properties types, defaults, description etc.', + webDocs: { + description: 'Show help for properties. Used when you may need to know some properties types, defaults, description etc.', + category: 'CLI Config', + title: 'Describe Config Properties', + weight: 130, + }, + builder: (yargs) => { + return yargs + .positional('name', { + describe: 'Property name', + }) + .example('codefresh cli-config help', 'Print help for all properties') + .example('codefresh cli-config help output', 'Print help for properties, containing "output" word') + }, + handler: async (argv) => { + if (argv.help) { + yargs.showHelp(); + return; + } + outputCliMeta(manager.meta(argv.name)); + }, +}); + +module.exports = helpCommand; diff --git a/lib/interface/cli/commands/cli-config/ops/profile.cmd.js b/lib/interface/cli/commands/cli-config/ops/profile.cmd.js index 2ac84aa9e..51a1ff1ba 100644 --- a/lib/interface/cli/commands/cli-config/ops/profile.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/profile.cmd.js @@ -1,5 +1,6 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); +const yargs = require('yargs'); const CliConfigManager = require('../../../../../logic/cli-config/Manager'); @@ -22,6 +23,10 @@ const setCommand = new Command({ .example('codefresh cli-config profile myProfile', 'Use or create profile with name "myProfile"'); }, handler: async (argv) => { + if (argv.help) { + yargs.showHelp(); + return; + } const { name } = argv; if (!name) { diff --git a/lib/interface/cli/commands/cli-config/ops/set.cmd.js b/lib/interface/cli/commands/cli-config/ops/set.cmd.js index 90a7038ec..2a16cb010 100644 --- a/lib/interface/cli/commands/cli-config/ops/set.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/set.cmd.js @@ -1,8 +1,9 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); +const yargs = require('yargs'); const Manager = require('../../../../../logic/cli-config/Manager'); -const {outputCliConfig, propertyErrorHandler, printProperties} = require('../../../helpers/cli-config'); +const { outputCliConfig, propertyErrorHandler, printProperties } = require('../../../helpers/cli-config'); // todo : fix descriptions for docs const setCommand = new Command({ @@ -27,6 +28,10 @@ const setCommand = new Command({ .example('codefresh cli-config set output.pretty false', 'Set "output.pretty" property'); }, handler: async (argv) => { + if (argv.help) { + yargs.showHelp(); + return; + } let { name, value } = argv; if (!name) { console.log('Available properties:\n'); diff --git a/lib/interface/cli/helpers/cli-config.js b/lib/interface/cli/helpers/cli-config.js index 3e014a748..490d108d4 100644 --- a/lib/interface/cli/helpers/cli-config.js +++ b/lib/interface/cli/helpers/cli-config.js @@ -1,10 +1,12 @@ const _ = require('lodash'); -const flattern = require('flat'); +const flatten = require('flat'); const columnify = require('columnify'); const yaml = require('js-yaml'); +const Style = require('../../../output/style'); const { NoPropertyError, MultiplePropertiesError, NotFullPropertyError, SchemaValidationError } = require('../../../logic/cli-config/errors'); const _jsonFormatter = data => JSON.stringify(data, null, 4); +const COLUMNIFY_OPTS = { columnSplitter: ' ', headingTransform: Style.bold.uppercase }; const propertyComparator = (a, b) => { const aDots = a.split('.').length; @@ -19,13 +21,17 @@ const propertyComparator = (a, b) => { function _defaultOutput(data) { - const flat = flattern(data); + const flat = flatten(data); const columns = _.keys(flat) .sort(propertyComparator) .map((key) => { - return { key, value: _.get(data, key) }; + let value = _.get(data, key); + if (value === null) { + value = 'null'; + } + return { key, value }; }); - return columnify(columns, { columnSplitter: ' ' }); + return columnify(columns, COLUMNIFY_OPTS); } function _formatter(format) { @@ -44,6 +50,16 @@ function outputCliConfig(format, data) { console.log(formatter(data)); } +function outputCliMeta(props) { + const columns = props.map(meta => _.mapValues(meta, (val) => { + if (val === null) { + return 'null'; + } + return val; + })); + console.log(columnify(columns, COLUMNIFY_OPTS)); +} + function printProperties(properties) { properties.sort(propertyComparator).forEach(prop => console.log(prop)); } @@ -73,4 +89,6 @@ module.exports = { outputCliConfig, printProperties, propertyErrorHandler, + outputCliMeta, + propertyComparator, }; diff --git a/lib/logic/cli-config/Manager.js b/lib/logic/cli-config/Manager.js index 7ef5c7f9d..1f2fd7352 100644 --- a/lib/logic/cli-config/Manager.js +++ b/lib/logic/cli-config/Manager.js @@ -5,6 +5,7 @@ const yaml = require('js-yaml'); const { NoPropertyError, MultiplePropertiesError, NotFullPropertyError } = require('./errors'); const Model = require('./Model'); +const { propertyComparator } = require('../../interface/cli/helpers/cli-config'); const { CODEFRESH_PATH } = require('../../interface/cli/defaults'); @@ -117,6 +118,14 @@ class CliConfigManager { fs.writeSync(file, yaml.safeDump(FULL_CONFIG)); fs.closeSync(file); } + + static meta(propertyName) { + const meta = Model.meta(); + return _.keys(meta) + .filter(key => !propertyName || !propertyName.length || key.includes(propertyName)) + .sort(propertyComparator) + .map(key => Object.assign({ key }, _.get(meta, key))); + } } FULL_CONFIG = _loadFullConfig(); diff --git a/lib/logic/cli-config/Model.js b/lib/logic/cli-config/Model.js index d7f4a6f13..088580280 100644 --- a/lib/logic/cli-config/Model.js +++ b/lib/logic/cli-config/Model.js @@ -13,7 +13,11 @@ const defaults = {}; validate(defaults); // fill with default values const properties = _.keys(flatten(defaults)); - +const propertiesMeta = properties.reduce((obj, prop) => { + obj[prop] = prop.split('.') + .reduce((prev, pathPart) => prev.properties[pathPart], cliConfigSchema); + return obj; +}, {}); class Model { static default() { @@ -28,6 +32,10 @@ class Model { return properties.filter(prop => prop.includes(name)); } + + static meta() { + return _.cloneDeep(propertiesMeta); + } /** * also fills with default values and coerces types * */ diff --git a/lib/logic/cli-config/schema.json b/lib/logic/cli-config/schema.json index b2be17c88..985150f63 100644 --- a/lib/logic/cli-config/schema.json +++ b/lib/logic/cli-config/schema.json @@ -8,7 +8,24 @@ "properties": { "pretty": { "type": "boolean", - "default": false + "default": false, + "description": "Defines whether to show data in table view in pretty mode or not" + }, + "test": { + "type": "object", + "properties": { + "first": { + "type": ["string", "null"], + "pattern": ".*", + "default": null, + "description": "Some empty string" + }, + "second": { + "type": ["string", "null"], + "default": "not empty string" + } + }, + "default": {} } }, "default": {} From 86d00decba8bcefd5d1a66ddcae64ed19a48cfd5 Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Mon, 10 Dec 2018 11:17:06 +0200 Subject: [PATCH 11/17] config validation --- lib/logic/cli-config/Manager.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/logic/cli-config/Manager.js b/lib/logic/cli-config/Manager.js index 1f2fd7352..4e3c9e691 100644 --- a/lib/logic/cli-config/Manager.js +++ b/lib/logic/cli-config/Manager.js @@ -13,7 +13,7 @@ const dirPath = path.resolve(CODEFRESH_PATH, 'cli-config'); const filePath = path.resolve(dirPath, 'config.yaml'); let FULL_CONFIG; -let CURRENT_CONFIG; +let CURRENT_CONFIG = {}; /** * Remove properties that not exist at schema and add default values @@ -126,9 +126,32 @@ class CliConfigManager { .sort(propertyComparator) .map(key => Object.assign({ key }, _.get(meta, key))); } + + /** + * Replaces "broken" properties with defaults and writes to config + * */ + static _validateConfig() { + try { + Model.validate(CURRENT_CONFIG); + } catch (e) { + const errorPaths = e.errors.map(er => er.dataPath.replace('.', '')); + + console.warn('---------------'); + console.warn('!!! Some properties are invalid -- replacing with defaults:'); + + errorPaths.forEach(ep => { + console.warn(ep); + _.set(CURRENT_CONFIG, ep, undefined); + }); + console.warn('---------------\n'); + CURRENT_CONFIG = _.defaultsDeep(CURRENT_CONFIG, Model.default()); + CliConfigManager.persistConfig(); + } + } } FULL_CONFIG = _loadFullConfig(); CliConfigManager.useProfile(FULL_CONFIG.currentProfile || 'default'); +CliConfigManager._validateConfig(); module.exports = CliConfigManager; From 10e85436a9ac58ca5b82235d39ac14159aa7f7a4 Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Mon, 10 Dec 2018 12:31:25 +0200 Subject: [PATCH 12/17] model defaults for objects in schema --- lib/logic/cli-config/Model.js | 66 ++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/lib/logic/cli-config/Model.js b/lib/logic/cli-config/Model.js index 088580280..0dad7e9cd 100644 --- a/lib/logic/cli-config/Model.js +++ b/lib/logic/cli-config/Model.js @@ -3,48 +3,80 @@ const Ajv = require('ajv'); const flatten = require('flat'); const { SchemaValidationError } = require('./errors'); - -const ajv = new Ajv({ coerceTypes: true, useDefaults: true }); const cliConfigSchema = require('./schema.json'); -const validate = ajv.compile(cliConfigSchema); +const VALIDATOR = new Ajv({ coerceTypes: true, useDefaults: true }); -const defaults = {}; -validate(defaults); // fill with default values +let DEFAULTS; +let PROPERTIES; +let PROPERTIES_META; + + +/** + * Every schema object need to have default set to {} in order to init its properties with default value + * */ +function _fillSchemaObjectsWithDefault(schema) { + if (schema.type === 'object') { + schema.default = schema.default || {}; + if (schema.properties) { + _.values(schema.properties).forEach((value) => { + if (typeof value === 'object') { + _fillSchemaObjectsWithDefault(value); + } + }); + } + } +} + +function _compileSchema() { + const validate = VALIDATOR.compile(cliConfigSchema); + const defaults = {}; + + validate(defaults); // fill with default values + return defaults; +} + +function _extractMeta(props) { + return props.reduce((obj, prop) => { + obj[prop] = prop.split('.') + .reduce((prev, pathPart) => prev.properties[pathPart], cliConfigSchema); + return obj; + }, {}); +} -const properties = _.keys(flatten(defaults)); -const propertiesMeta = properties.reduce((obj, prop) => { - obj[prop] = prop.split('.') - .reduce((prev, pathPart) => prev.properties[pathPart], cliConfigSchema); - return obj; -}, {}); class Model { static default() { - return _.cloneDeep(defaults); + return _.cloneDeep(DEFAULTS); } static properties() { - return _.cloneDeep(properties); + return _.cloneDeep(PROPERTIES); } static findProperties(name) { - return properties.filter(prop => prop.includes(name)); + return PROPERTIES.filter(prop => prop.includes(name)); } static meta() { - return _.cloneDeep(propertiesMeta); + return _.cloneDeep(PROPERTIES_META); } /** * also fills with default values and coerces types * */ static validate(obj) { - if (!ajv.validate('cli-config-schema', obj)) { - throw new SchemaValidationError(ajv.errors); + if (!VALIDATOR.validate('cli-config-schema', obj)) { + throw new SchemaValidationError(VALIDATOR.errors); } } } +_fillSchemaObjectsWithDefault(cliConfigSchema); + +DEFAULTS = _compileSchema(); +PROPERTIES = _.keys(flatten(DEFAULTS)); +PROPERTIES_META = _extractMeta(PROPERTIES); + module.exports = Model; From c04f8a36bdb76071246b2dcfd267ac490c5a0850 Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Mon, 10 Dec 2018 12:32:17 +0200 Subject: [PATCH 13/17] manager config "lazy load" --- lib/logic/cli-config/Manager.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/logic/cli-config/Manager.js b/lib/logic/cli-config/Manager.js index 4e3c9e691..bc7f238e0 100644 --- a/lib/logic/cli-config/Manager.js +++ b/lib/logic/cli-config/Manager.js @@ -69,6 +69,7 @@ class CliConfigManager { if (!properties.length) { throw new NoPropertyError(propertyName); } + this._preloadConfig(); return properties.reduce((obj, key) => { _.set(obj, key, _.get(CURRENT_CONFIG, key)); return obj; @@ -79,19 +80,23 @@ class CliConfigManager { static set(propertyName, value) { const properties = Model.findProperties(propertyName); _validate(properties, propertyName); + this._preloadConfig(); _.set(CURRENT_CONFIG, propertyName, value); Model.validate(CURRENT_CONFIG); } static config() { + this._preloadConfig(); return _.cloneDeep(CURRENT_CONFIG); } static currentProfile() { + this._preloadConfig(); return FULL_CONFIG.currentProfile; } static profiles() { + this._preloadConfig(); return _.keys(FULL_CONFIG.profiles); } @@ -105,6 +110,7 @@ class CliConfigManager { * @return boolean: defines whether profile was created or not * */ static useProfile(profile) { + this._preloadConfig(); const selectedConfig = FULL_CONFIG.profiles[profile]; const created = !selectedConfig; CURRENT_CONFIG = _extractConfig(selectedConfig); @@ -114,6 +120,7 @@ class CliConfigManager { } static persistConfig() { + this._preloadConfig(); const file = fs.openSync(filePath, 'w'); fs.writeSync(file, yaml.safeDump(FULL_CONFIG)); fs.closeSync(file); @@ -127,6 +134,18 @@ class CliConfigManager { .map(key => Object.assign({ key }, _.get(meta, key))); } + static _preloadConfig() { + if (!FULL_CONFIG) { + this.loadConfig(); + } + } + + static loadConfig() { + FULL_CONFIG = _loadFullConfig(); + this.useProfile(FULL_CONFIG.currentProfile || 'default'); + this._validateConfig(); + } + /** * Replaces "broken" properties with defaults and writes to config * */ @@ -150,8 +169,4 @@ class CliConfigManager { } } -FULL_CONFIG = _loadFullConfig(); -CliConfigManager.useProfile(FULL_CONFIG.currentProfile || 'default'); -CliConfigManager._validateConfig(); - module.exports = CliConfigManager; From 7cc798482b557c6ee989593d3e4bdad27cf37d2d Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Mon, 10 Dec 2018 14:57:42 +0200 Subject: [PATCH 14/17] advanced formatting + tests --- .../cli/commands/cli-config/ops/get.cmd.js | 9 +- .../cli/commands/cli-config/ops/help.cmd.js | 4 +- lib/interface/cli/helpers/cli-config.js | 49 +++--- lib/logic/cli-config/Manager.js | 3 +- lib/logic/cli-config/manager.spec.js | 152 ++++++++++++++++++ lib/logic/cli-config/schema.json | 19 +-- 6 files changed, 186 insertions(+), 50 deletions(-) create mode 100644 lib/logic/cli-config/manager.spec.js diff --git a/lib/interface/cli/commands/cli-config/ops/get.cmd.js b/lib/interface/cli/commands/cli-config/ops/get.cmd.js index ce5573cbd..41eb55080 100644 --- a/lib/interface/cli/commands/cli-config/ops/get.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/get.cmd.js @@ -2,7 +2,7 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); const yargs = require('yargs'); -const manager = require('../../../../../logic/cli-config/Manager'); +const Manager = require('../../../../../logic/cli-config/Manager'); const { printProperties, outputCliConfig, propertyErrorHandler } = require('../../../helpers/cli-config'); const getCommand = new Command({ @@ -33,17 +33,18 @@ const getCommand = new Command({ const propertyName = argv.name; if (!propertyName) { console.log('Available properties:\n'); - printProperties(manager.availableProperties()); + printProperties(Manager.availableProperties()); return; } let properties; try { - properties = manager.get(propertyName); + properties = Manager.get(propertyName); } catch (e) { propertyErrorHandler(e); + return; } - console.log(`Current profile: | ${manager.currentProfile()} |\n`); + console.log(`Current profile: | ${Manager.currentProfile()} |\n`); outputCliConfig(argv.output, properties); }, }); diff --git a/lib/interface/cli/commands/cli-config/ops/help.cmd.js b/lib/interface/cli/commands/cli-config/ops/help.cmd.js index 51e2fe014..900c41702 100644 --- a/lib/interface/cli/commands/cli-config/ops/help.cmd.js +++ b/lib/interface/cli/commands/cli-config/ops/help.cmd.js @@ -2,7 +2,7 @@ const Command = require('../../../Command'); const cliCommand = require('../cli-config.cmd'); const yargs = require('yargs'); -const manager = require('../../../../../logic/cli-config/Manager'); +const Manager = require('../../../../../logic/cli-config/Manager'); const { outputCliMeta } = require('../../../helpers/cli-config'); const helpCommand = new Command({ @@ -29,7 +29,7 @@ const helpCommand = new Command({ yargs.showHelp(); return; } - outputCliMeta(manager.meta(argv.name)); + outputCliMeta(Manager.meta(argv.name)); }, }); diff --git a/lib/interface/cli/helpers/cli-config.js b/lib/interface/cli/helpers/cli-config.js index 490d108d4..df2e11fbb 100644 --- a/lib/interface/cli/helpers/cli-config.js +++ b/lib/interface/cli/helpers/cli-config.js @@ -8,27 +8,27 @@ const { NoPropertyError, MultiplePropertiesError, NotFullPropertyError, SchemaVa const _jsonFormatter = data => JSON.stringify(data, null, 4); const COLUMNIFY_OPTS = { columnSplitter: ' ', headingTransform: Style.bold.uppercase }; -const propertyComparator = (a, b) => { - const aDots = a.split('.').length; - const bDots = b.split('.').length; - if (aDots > bDots) return 1; - if (aDots < bDots) return -1; - - if (a > b) return 1; - if (a < b) return -1; - return 0; -}; +const NO_PROPERTIES_MESSAGE = 'no properties'; +function _valueFormatter(value) { + if (value === null) { + return Style.gray(''); + } + if (typeof value === 'string') { + return Style.green(`'${value}'`); + } + if (typeof value === 'boolean') { + return Style.yellow(value); + } + return value; +} function _defaultOutput(data) { const flat = flatten(data); const columns = _.keys(flat) - .sort(propertyComparator) + .sort() .map((key) => { - let value = _.get(data, key); - if (value === null) { - value = 'null'; - } + const value = _valueFormatter(_.get(data, key)); return { key, value }; }); return columnify(columns, COLUMNIFY_OPTS); @@ -51,17 +51,19 @@ function outputCliConfig(format, data) { } function outputCliMeta(props) { - const columns = props.map(meta => _.mapValues(meta, (val) => { - if (val === null) { - return 'null'; - } - return val; - })); - console.log(columnify(columns, COLUMNIFY_OPTS)); + const columns = props.map(meta => { + meta.default = _valueFormatter(meta.default); + return meta; + }); + if (columns.length) { + console.log(columnify(columns, COLUMNIFY_OPTS)); + } else { + console.log(NO_PROPERTIES_MESSAGE); + } } function printProperties(properties) { - properties.sort(propertyComparator).forEach(prop => console.log(prop)); + properties.sort().forEach(prop => console.log(prop)); } function propertyErrorHandler(e) { @@ -90,5 +92,4 @@ module.exports = { printProperties, propertyErrorHandler, outputCliMeta, - propertyComparator, }; diff --git a/lib/logic/cli-config/Manager.js b/lib/logic/cli-config/Manager.js index bc7f238e0..d76290f95 100644 --- a/lib/logic/cli-config/Manager.js +++ b/lib/logic/cli-config/Manager.js @@ -5,7 +5,6 @@ const yaml = require('js-yaml'); const { NoPropertyError, MultiplePropertiesError, NotFullPropertyError } = require('./errors'); const Model = require('./Model'); -const { propertyComparator } = require('../../interface/cli/helpers/cli-config'); const { CODEFRESH_PATH } = require('../../interface/cli/defaults'); @@ -130,7 +129,7 @@ class CliConfigManager { const meta = Model.meta(); return _.keys(meta) .filter(key => !propertyName || !propertyName.length || key.includes(propertyName)) - .sort(propertyComparator) + .sort() .map(key => Object.assign({ key }, _.get(meta, key))); } diff --git a/lib/logic/cli-config/manager.spec.js b/lib/logic/cli-config/manager.spec.js new file mode 100644 index 000000000..acfb67532 --- /dev/null +++ b/lib/logic/cli-config/manager.spec.js @@ -0,0 +1,152 @@ +const _ = require('lodash'); +const path = require('path'); + +const Manager = require('./Manager'); +const Model = require('./Model'); + +const { CODEFRESH_PATH: mockCodefreshPath } = require('../../interface/cli/defaults'); +const mockDirPath = path.resolve(mockCodefreshPath, 'cli-config'); +const mockFilePath = path.resolve(mockDirPath, 'config.yaml'); + + +jest.mock('fs', () => { // eslint-disable-line + const readFileSync = () => this.configFile; + const existsSync = (path) => { + return { + [mockCodefreshPath]: true, + [mockDirPath]: true, + [mockFilePath]: this.exists, + }[path]; + }; + const openSync = () => null; + const writeSync = (file, config) => { + this.configFile = config; + }; + const closeSync = () => null; + + const setConfigFile = (c) => { + this.configFile = c; + }; + const getConfigFile = () => this.configFile; + const setFileExists = (val) => { + this.exists = val; + }; + + return { + configFile: null, + exists: false, + existsSync, + readFileSync, + openSync, + closeSync, + writeSync, + setConfigFile, + getConfigFile, + setFileExists, + }; +}); + +jest.mock('js-yaml', () => { + const safeLoad = d => d; + const safeDump = d => d; + return { + safeLoad, + safeDump, + }; +}); + +jest.mock('./schema', () => { // eslint-disable-line + return { + $id: 'cli-config-schema', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + output: { + type: 'object', + properties: { + pretty: { + type: 'boolean', + default: false, + description: 'Defines whether to show data in table view in pretty mode or not' + }, + test: { + type: 'object', + properties: { + first: { + type: ['string', 'null'], + pattern: '.*', + default: null, + description: 'Some empty string', + }, + second: { + type: ['string', 'null'], + default: 'not empty string', + }, + }, + default: {}, + }, + }, + default: {}, + }, + }, + }; +}); + +const fs = require('fs'); + +describe('CliConfigManager', () => { + describe('config loading', () => { + // this test case must be first due to node module cache + it('should lazy load config', () => { + fs.setFileExists(true); + const DEFAULT_CONFIG = { + currentProfile: 'default', + profiles: { + default: Model.default(), + }, + }; + fs.setConfigFile(DEFAULT_CONFIG); + + Manager.config(); // lazy load + + expect(Manager.config()).toEqual(Model.default()); + }); + + it('should create file with default config if it not exist', () => { + fs.setFileExists(false); + fs.setConfigFile(null); + + Manager.loadConfig(); + + const configFile = fs.getConfigFile(); + + expect(configFile).not.toBeNull(); + expect(configFile.currentProfile).toBe('default'); + expect(configFile.profiles.default).toEqual(Model.default()); + expect(Manager.config()).toEqual(Model.default()); + }); + }); + + describe('config validation', () => { + it('should replace broken properties with defaults', () => { + const INVALID_CONFIG = { + currentProfile: 'default', + profiles: { + default: { + output: { + pretty: 'asdf', // this must be boolean + }, + }, + }, + }; + fs.setConfigFile(INVALID_CONFIG); + fs.setFileExists(true); + + Manager.loadConfig(); // validates config and fixes broken props + + const configFile = fs.getConfigFile(); + const prettyDefault = _.get(Model.default(), 'output.pretty'); + expect(configFile.profiles.default.output.pretty).toBe(prettyDefault); + }); + }); +}); diff --git a/lib/logic/cli-config/schema.json b/lib/logic/cli-config/schema.json index 985150f63..983905904 100644 --- a/lib/logic/cli-config/schema.json +++ b/lib/logic/cli-config/schema.json @@ -10,25 +10,8 @@ "type": "boolean", "default": false, "description": "Defines whether to show data in table view in pretty mode or not" - }, - "test": { - "type": "object", - "properties": { - "first": { - "type": ["string", "null"], - "pattern": ".*", - "default": null, - "description": "Some empty string" - }, - "second": { - "type": ["string", "null"], - "default": "not empty string" - } - }, - "default": {} } - }, - "default": {} + } } } } From 36060ebd3d263c87b35d14df6079fc6f8cb58e40 Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Mon, 10 Dec 2018 15:09:14 +0200 Subject: [PATCH 15/17] output formatting config on get command + fallback mode --- lib/interface/cli/helpers/get.js | 9 +++++++-- lib/logic/cli-config/Manager.js | 18 +++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/interface/cli/helpers/get.js b/lib/interface/cli/helpers/get.js index b17f6782a..2b36e891c 100644 --- a/lib/interface/cli/helpers/get.js +++ b/lib/interface/cli/helpers/get.js @@ -7,6 +7,9 @@ const { Formatter } = require('../../../output'); const { FormatterRegistry } = require('../../../output/formatters'); const Style = require('../../../output/style'); +const CliConfigManager = require('../../../logic/cli-config/Manager'); +const CLI_CONFIG = CliConfigManager.config(); + const NO_RESOURCES_MESSAGE = 'no available resources'; const STYLED_NO_RESOURCES_MESSAGE = Style.yellow.prependedBy(Style.red('▸'))(NO_RESOURCES_MESSAGE); const NOOP_FORMATTER = new Formatter(); @@ -63,8 +66,9 @@ const _printSingleTable = (info, pretty = false) => { // todo: extract output layer from commands -const specifyOutputForSingle = (type, entity, pretty = false) => { +const specifyOutputForSingle = (type, entity, pretty) => { let formatter = NOOP_FORMATTER; + pretty = pretty !== undefined ? pretty : CLI_CONFIG.output.pretty; if (pretty && entity) { formatter = FormatterRegistry.get(entity.constructor.name); } @@ -88,8 +92,9 @@ const specifyOutputForSingle = (type, entity, pretty = false) => { // todo: extract output layer from commands -const specifyOutputForArray = (type, entities, pretty = false) => { +const specifyOutputForArray = (type, entities, pretty) => { let formatter = NOOP_FORMATTER; + pretty = pretty !== undefined ? pretty : CLI_CONFIG.output.pretty; if (pretty && entities.length) { formatter = FormatterRegistry.get(entities[0].constructor.name); } diff --git a/lib/logic/cli-config/Manager.js b/lib/logic/cli-config/Manager.js index d76290f95..1d45eb166 100644 --- a/lib/logic/cli-config/Manager.js +++ b/lib/logic/cli-config/Manager.js @@ -11,6 +11,13 @@ const { CODEFRESH_PATH } = require('../../interface/cli/defaults'); const dirPath = path.resolve(CODEFRESH_PATH, 'cli-config'); const filePath = path.resolve(dirPath, 'config.yaml'); +const FALLBACK_CONFIG = { + currentProfile: 'default', + profiles: { + default: Model.default(), + }, +}; + let FULL_CONFIG; let CURRENT_CONFIG = {}; @@ -140,9 +147,14 @@ class CliConfigManager { } static loadConfig() { - FULL_CONFIG = _loadFullConfig(); - this.useProfile(FULL_CONFIG.currentProfile || 'default'); - this._validateConfig(); + try { + FULL_CONFIG = _loadFullConfig(); + this.useProfile(FULL_CONFIG.currentProfile || 'default'); + this._validateConfig(); + } catch (e) { + console.warn('CLI config not loaded -- running in fallback mode.'); + FULL_CONFIG = FALLBACK_CONFIG; + } } /** From 5a61c1a01c89a5fd096da28b4c074a2940066cdc Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Mon, 10 Dec 2018 15:10:55 +0200 Subject: [PATCH 16/17] version updated --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a8801f5d..de33138e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codefresh", - "version": "0.8.108", + "version": "0.8.109", "description": "Codefresh command line utility", "main": "index.js", "preferGlobal": true, From 55e5563fc7d464e80285ea7a3706f353ced1b0d5 Mon Sep 17 00:00:00 2001 From: yaroslav-codefresh Date: Mon, 10 Dec 2018 15:15:27 +0200 Subject: [PATCH 17/17] add current config to fallback mode --- lib/logic/cli-config/Manager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/logic/cli-config/Manager.js b/lib/logic/cli-config/Manager.js index 1d45eb166..4ccd47e1e 100644 --- a/lib/logic/cli-config/Manager.js +++ b/lib/logic/cli-config/Manager.js @@ -152,8 +152,12 @@ class CliConfigManager { this.useProfile(FULL_CONFIG.currentProfile || 'default'); this._validateConfig(); } catch (e) { + console.warn('--------------'); console.warn('CLI config not loaded -- running in fallback mode.'); + console.warn('--------------\n'); + FULL_CONFIG = FALLBACK_CONFIG; + CURRENT_CONFIG = Model.default(); } }