Created
May 15, 2015 09:09
-
-
Save d5/adb58ea8eddcdee6eb73 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const Etcd = require('node-etcd'), | |
P = require('bluebird'), | |
async = P.coroutine, | |
_ = require('lodash'), | |
assert = require('assert'); | |
function keyToPath(key) { | |
return '/' + key.replace(/\./g, '/'); | |
} | |
function pathToKey(path) { | |
return path.slice(1).replace(/\//g, '.'); | |
} | |
function keyToRawPath(key) { | |
return 'v2/keys/' + key.replace(/\./g, '/'); | |
} | |
function isValidKey(key) { | |
return _.isString(key) && /[\w\d\.]+/.test(key); | |
} | |
function translateNode(node) { | |
if(node.dir) { | |
if(node.nodes) { | |
const obj = {}; | |
_.each(node.nodes, function(subNode) { | |
const subKey = pathToKey(subNode.key.slice(node.key.length)); | |
obj[subKey] = translateNode(subNode); | |
}); | |
return obj; | |
} else { | |
return {}; | |
} | |
} else { | |
return JSON.parse(node.value); | |
} | |
} | |
function isValidValueElement(value) { | |
return _.isNull(value) || | |
_.isString(value) || | |
_.isBoolean(value) || | |
(_.isNumber(value) && !_.isNaN(value)) || | |
_.isArray(value); | |
} | |
function Options() { | |
const self = this; | |
self.defaultOptions = {}; | |
self.hosts = _.map((process.env.ETCD_HOSTS || '127.0.0.1:5000').split(','), _.trim); | |
self.etcd = P.promisifyAll(new Etcd(self.hosts)); | |
} | |
Options.prototype.get = async(function* (key) { | |
const self = this; | |
console.assert(isValidKey(key)); | |
try { | |
const res = yield self.etcd.rawAsync('GET', keyToRawPath(key), null, { recursive: true }); | |
return translateNode(res[0].node); | |
} catch(err) { | |
if(err.errorCode === 100) { | |
return undefined; // key not found | |
} else { | |
throw err; | |
} | |
} | |
}); | |
// TODO: setting an object is not an atomic operation | |
Options.prototype.set = async(function* (key, value) { | |
const self = this; | |
console.assert(isValidKey(key)); | |
if(_.isUndefined(value)) { | |
// setting undefined = delete | |
try { | |
yield self.etcd.rawAsync('DELETE', keyToRawPath(key), null, { recursive: true }); | |
} catch(ignored) { } | |
} else if(_.isPlainObject(value)) { | |
// delete before setting to new object | |
try { | |
yield self.etcd.rawAsync('DELETE', keyToRawPath(key), null, { recursive: true }); | |
} catch(ignored) { } | |
// drop attributes of undefined value | |
value = JSON.parse(JSON.stringify(value)); | |
const keys = _.keys(value); | |
if(keys.length) { | |
yield P.each(keys, async(function *(k) { | |
yield self.set(key + '.' + k, value[k]); | |
})); | |
} else { | |
yield self.etcd.rawAsync('PUT', keyToRawPath(key), null, { dir: true }); | |
} | |
} else { | |
console.assert(isValidValueElement(value)); | |
yield self.etcd.rawAsync('PUT', keyToRawPath(key), JSON.stringify(value), {}); | |
} | |
}); | |
Options.prototype.del = async(function* (key) { | |
const self = this; | |
console.assert(isValidKey(key)); | |
try { | |
yield self.etcd.rawAsync('DELETE', keyToRawPath(key), null, { recursive: true }); | |
} catch(err) { | |
// key not found: ignored | |
if(err.errorCode !== 100) { throw err; } | |
} | |
}); | |
describe('Options', function() { | |
const options = new Options(); | |
function get(key, expected) { | |
it('get("' + key + '") == ' + JSON.stringify(expected), async(function* () { | |
if(_.isArray(expected) || _.isPlainObject(expected)) { | |
assert.deepEqual(yield options.get(key), expected); | |
} else { | |
assert.strictEqual(yield options.get(key), expected); | |
} | |
})); | |
} | |
function set(key, val) { | |
it('set("' + key + '", ' + JSON.stringify(val) + ')', async(function* () { | |
yield options.set(key, val); | |
if(_.isArray(val) || _.isPlainObject(val)) { | |
assert.deepEqual(yield options.get(key), JSON.parse(JSON.stringify(val))); | |
} else { | |
assert.strictEqual(yield options.get(key), val); | |
} | |
})); | |
} | |
function del(key) { | |
it('del("' + key + '")', async(function* () { | |
yield options.del(key); | |
assert.strictEqual(yield options.get(key), undefined); | |
})); | |
} | |
describe('simple', function() { | |
del('foo'); | |
set('foo', undefined); | |
set('foo', null); | |
set('foo', ''); | |
set('foo', 'string value'); | |
set('foo', 5264); | |
set('foo', -52.64); | |
set('foo', false); | |
set('foo', true); | |
set('foo', []); | |
set('foo', [1,2,3]); | |
set('foo', [1,'two',true]); | |
del('foo'); | |
}); | |
describe('set object value', function() { | |
del('foo'); | |
set('foo', {}); | |
set('foo', { key1: null }); | |
set('foo', { key1: null, key2: 5264 }); | |
set('foo', { key1: null, key2: 5264, key3: false }); | |
set('foo', { key1: null, key2: 5264, key3: false, key4: [1,2,3] }); | |
set('foo', { key1: {} }); | |
set('foo', { key1: { key2: {} } }); | |
set('foo', { key1: { key2: null } }); | |
set('foo', { key1: { key2: undefined } }); | |
set('foo', { key1: { key2: 'string value' } }); | |
set('foo', { key1: { key3: { key4: 'string value '} }, key2: 5264 }); | |
set('foo', { key1: 5264 }); | |
del('foo'); | |
}); | |
describe('dotted keys', function() { | |
del('foo'); | |
set('foo.key1', 5264); | |
get('foo', { key1: 5264 }); | |
set('foo.key2', [1,2,3]); | |
get('foo', { key1: 5264, key2: [1,2,3] }); | |
del('foo.key2'); | |
get('foo', { key1: 5264 }); | |
set('foo.key3', [1,2,3]); | |
get('foo', { key1: 5264, key3: [1,2,3] }); | |
set('foo.key3', undefined); | |
get('foo', { key1: 5264 }); | |
set('foo.key2.key4', 'string value'); | |
get('foo', { key1: 5264, key2: { key4: 'string value' } }); | |
del('foo.key2'); | |
get('foo', { key1: 5264 }); | |
del('foo'); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment