From da033dc58c325ada2ddad8f7709a2a5a71d18f2b Mon Sep 17 00:00:00 2001 From: Jesse Hansen Date: Fri, 8 Jan 2016 11:39:48 -0700 Subject: [PATCH] Modularize implementation --- .eslintrc | 17 ++++--- consul-kv-sync.js | 123 ++++++++-------------------------------------- lib/client.js | 22 +++++++++ lib/logger.js | 9 ++++ lib/workflow.js | 104 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + test/test.js | 13 ++--- 7 files changed, 172 insertions(+), 117 deletions(-) create mode 100644 lib/client.js create mode 100644 lib/logger.js create mode 100644 lib/workflow.js diff --git a/.eslintrc b/.eslintrc index f80e475..d904d07 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,11 @@ { - "env": { - "node": true - }, - "extends": "leisurelink", - "rules": { - "no-console": 0, - "complexity": 0 - } + "extends": "leisurelink", + "env": { + "node": true, + "es6": true + }, + "rules": { + "no-console": 0, + "complexity": 0 + } } diff --git a/consul-kv-sync.js b/consul-kv-sync.js index 7470d0a..6ad72f7 100755 --- a/consul-kv-sync.js +++ b/consul-kv-sync.js @@ -1,53 +1,11 @@ #!/usr/bin/env node - 'use strict'; -/*eslint no-console: 0*/ - -var fs = require('fs'); -var Promise = require('bluebird'); -var program = require('commander'); -var jptr = require('json-ptr'); -var _ = require('lodash'); -var consul = require('consul'); - -var pkg = require('./package.json'); - -var readFile = Promise.promisify(fs.readFile); -var firstFragmentId, client, flattened, reduced, deleteCount = 0, putCount = 0, existing = {}; - -function info(message) { - console.log(message); -} -function error(message) { - console.log(message); -} -function debug(message) { - if (program.verbose) { - console.log(message); - } -} - -function readFragments(fileName) { - return readFile(fileName, 'utf8').then(JSON.parse).then(function validateContents(contents) { - var pointers, keys; - debug('Read ' + fileName); - debug(contents); - keys = _.keys(contents); - if (keys.length !== 1) { - return Promise.reject(new Error('Each configuration file must have a single top-level node identifying the service. "' + fileName + '" has ' + keys.length + ' top-level nodes.')); - } - pointers = jptr.list(contents); - if (firstFragmentId) { - if (pointers[1].pointer !== firstFragmentId) { - return Promise.reject(new Error('Each file must have the same top-level node. Expected "' + fileName + '" to have top-level node "' + firstFragmentId.substring(1) + '", but it has "' + pointers[1].pointer.substring(1) + '".')); - } - } else { - firstFragmentId = pointers[1].pointer; - } - return Promise.resolve(pointers); - }); -} +const program = require('commander'); +const pkg = require('./package'); +const log = require('./lib/logger'); +const clientFactory = require('./lib/client'); +const workflowFactory = require('./lib/workflow'); function collectCA(value, items) { items.push(value); @@ -77,63 +35,22 @@ if (!program.args.length) { program.outputHelp(); process.exit(1); } +if (program.verbose) { + log.transports.console.level = 'debug'; +} -Promise.all(_.map(program.ca, readFile)).then(function(certificates) { - var config = { - host: program.host || process.env.CONSUL_HOST || 'consul.service.consul', - port: program.port || process.env.CONSUL_PORT || 8500, - secure: program.secure || process.env.CONSUL_SECURE === 'true', - ca: certificates - }; - debug('Config:'); - debug(config); - client = consul(config); - Promise.promisifyAll(client.kv); - - return Promise.all(_.map(program.args, readFragments)); -}).then(function(files) { - var prefix; - flattened = _.flatten(files); - prefix = flattened[1].pointer.substring(1) + '/'; - - debug('Getting keys for ' + prefix); - return client.kv.keysAsync(prefix); -}).then(function(results) { - debug('Keys:'); - debug(results); - existing = results; -}).catch(function(err) { - if (err.message === 'not found') { - debug('No existing keys found'); +let client = clientFactory(program); +let workflow = workflowFactory(client, program.args); +workflow.exec().then(() => { + log.info('Sync completed. ' + workflow.stats.put + ' items set, ' + workflow.stats.deleted + ' items deleted.'); + log.info('Config:'); + log.info(workflow.config); +}) +.catch((err) => { + if (program.verbose) { + log.error(err); } else { - error(err); - process.exit(99); + log.error(err.message); } -}).then(function() { - reduced = _.reduce(_.filter(flattened, function(x) { - return _.isString(x.value) || _.isFinite(x.value); - }), function(acc, item) { - acc[item.pointer.substring(1)] = item.value; - return acc; - }, {}); - return Promise.all(_.map(reduced, function(value, key) { - putCount++; - existing = _.filter(existing, function(item) { - return item !== key; - }); - debug('Setting "' + key + '" to "' + value + '"'); - return client.kv.setAsync({ - key: key, - value: ''+value - }); - })); -}).then(function() { - return Promise.all(_.map(existing, function(key) { - deleteCount++; - return client.kv.delAsync(key); - })); -}).then(function() { - info('Sync completed. ' + putCount + ' items set, ' + deleteCount + ' items deleted.'); - info('Config:'); - info(reduced); + process.exit(99); }); diff --git a/lib/client.js b/lib/client.js new file mode 100644 index 0000000..8ea2467 --- /dev/null +++ b/lib/client.js @@ -0,0 +1,22 @@ +'use strict'; + +const consul = require('consul'); +const _ = require('lodash'); +const Promise = require('bluebird'); +const readFileSync = require('fs').readFileSync; +const log = require('./logger'); + +module.exports = (program) => { + const certificates = _.map(program.ca, readFileSync); + var config = { + host: program.host || process.env.CONSUL_HOST || 'consul.service.consul', + port: program.port || process.env.CONSUL_PORT || 8500, + secure: program.secure || process.env.CONSUL_SECURE === 'true', + ca: certificates + }; + log.debug('Config: ', config); + + let client = consul(config); + Promise.promisifyAll(client.kv); + return client; +}; diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..944e48d --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,9 @@ +'use strict'; + +const SkinnyLoggins = require('@leisurelink/skinny-loggins'); +const logger = new SkinnyLoggins(); + +logger.transports.console.timestamp = false; +logger.transports.console.showLevel = false; + +module.exports = logger; diff --git a/lib/workflow.js b/lib/workflow.js new file mode 100644 index 0000000..6d953fe --- /dev/null +++ b/lib/workflow.js @@ -0,0 +1,104 @@ +'use strict'; + +const jptr = require('json-ptr'); +const Promise = require('bluebird'); +const readFile = Promise.promisify(require('fs').readFile); +const _ = require('lodash'); +const log = require('./logger'); + +module.exports = (client, files) => { + let workflow = { stats: { put: 0, deleted: 0 }, config: null }; + let firstFragmentId; + const readFragments = (fileName) => { + return readFile(fileName, 'utf8') + .then(JSON.parse) + .then((contents) => { + log.debug(`Read ${fileName}:`); + log.debug(contents); + let keys = _.keys(contents); + if (keys.length !== 1) { + throw new Error('Each configuration file must have a single top-level node identifying the service. "' + fileName + '" has ' + keys.length + ' top-level nodes.'); + } + let pointers = jptr.list(contents); + if (firstFragmentId) { + if (pointers[1].pointer !== firstFragmentId) { + throw new Error('Each file must have the same top-level node. Expected "' + fileName + '" to have top-level node "' + firstFragmentId.substring(1) + '", but it has "' + pointers[1].pointer.substring(1) + '".'); + } + } else { + firstFragmentId = pointers[1].pointer; + } + return pointers; + }); + }; + + const readFiles = () => { + return Promise.map(files, readFragments); + }; + + let flattened, prefix; + const flatten = (contents) => { + flattened = _.flatten(contents); + prefix = flattened[1].pointer.substring(1) + '/'; + return flattened; + }; + + const reduce = (flattened) => { + let reduced = _.reduce(_.filter(flattened, (x) => { + return _.isString(x.value) || _.isFinite(x.value); + }), function (acc, item) { + acc[item.pointer.substring(1)] = item.value; + return acc; + }, {}); + workflow.config = reduced; + return reduced; + }; + + let existing; + const getExistingKeys = () => { + log.debug('Getting all keys for ' + prefix); + return client.kv.keysAsync(prefix) + .catch((err) => { + if (err.message === 'not found') { + return []; + } + throw err; + }) + .then((keys) => { + log.debug('Keys: ', keys); + existing = keys; + }); + }; + + const put = () => { + return Promise.all(_.map(workflow.config, (value, key) => { + workflow.stats.put++; + existing = _.filter(existing, (item) => { + return item !== key; + }); + log.debug(`Setting "${key}" to "${value}"`); + return client.kv.setAsync({ + key: key, + value: '' + value + }); + })); + }; + + const del = () => { + return Promise.map(existing, function(key) { + workflow.stats.deleted++; + log.debug(`Deleting "${key}"`); + return client.kv.delAsync(key); + }); + }; + + workflow.exec = () => { + return readFiles() + .then(flatten) + .then(reduce) + .then(getExistingKeys) + .then(put) + .then(del); + }; + + return workflow; +}; diff --git a/package.json b/package.json index 815357b..83c8e2e 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "ci": "npm run lint && npm run test" }, "dependencies": { + "@leisurelink/skinny-loggins": "^0.3.2", "bluebird": "^3.1.1", "commander": "^2.9.0", "consul": "^0.22.0", diff --git a/test/test.js b/test/test.js index 4c08892..94c0143 100644 --- a/test/test.js +++ b/test/test.js @@ -1,15 +1,16 @@ 'use strict'; -var exec = require('child_process').exec; -var expect = require('chai').expect; -var _ = require('lodash'); -var Promise = require('bluebird'); -var consul = require('consul'); +const exec = require('child_process').exec; +const expect = require('chai').expect; +const _ = require('lodash'); +const Promise = require('bluebird'); +const consul = require('consul'); function execute(commandLine) { return new Promise(function(resolve, reject) { exec(commandLine, { - cwd: __dirname + cwd: __dirname, + env: process.env }, function(err, stdout, stderr) { if (err) { reject(err);