node-etcd
Version:
etcd library for node.js (etcd v2 api)
257 lines (199 loc) • 7.49 kB
text/coffeescript
_ = require 'lodash'
Watcher = require './watcher'
Client = require './client'
HttpsAgent = (require 'https').Agent
URL = require 'url-parse'
# Etcd client for etcd protocol version 2
class Etcd
# Constructor, set etcd host and port.
# For https: provide {ca, crt, key} as sslopts.
# constructor: (hosts = ["http://127.0.0.1:2379"], options = {}) ->
constructor: (hosts = "127.0.0.1:2379", options = {}) ->
@hosts = @_cleanHostList hosts
@client = new Client(@hosts, options, null)
# Set key to value
# Usage:
# .set("key", "value", callback)
# .set("key", "value", {prevValue: "oldvalue"}, callback)
set: (key, value, options, callback) ->
[options, callback] = @_argParser options, callback
opt = @_prepareOpts ("keys/" + @_stripSlashPrefix key), "/v2", value, options
@client.put opt, callback
# Set key to value synchronously
# Usage:
# .set("key", "value")
# .set("key", "value", {prevValue: "oldvalue"})
setSync: (key, value, options = {}) ->
this.set key, value, @_synchronousOpts(options)
# Get value of key
# Usage:
# .get("key", callback)
# .get("key", {recursive: true}, callback)
get: (key, options, callback) ->
[options, callback] = @_argParser options, callback
opt = @_prepareOpts ("keys/" + @_stripSlashPrefix key), "/v2", null, options
@client.get opt, callback
# Synchronously get value of key
# Usage:
# .get("key")
# .get("key", {recursive: true})
getSync: (key, options = {}) ->
this.get key, @_synchronousOpts(options)
# Create a key (atomic in order)
# Usage:
# .create("path", "value", callback)
# .create("path", "value", options, callback)
create: (dir, value, options, callback) ->
[options, callback] = @_argParser options, callback
opt = @_prepareOpts ("keys/" + @_stripSlashPrefix dir), "/v2", value, options
@client.post opt, callback
post: @::create
# Delete a key
# Usage:
# .del("key", callback)
# .del("key", {recursive: true}, callback)
# .delete("key", callback)
del: (key, options, callback) ->
[options, callback] = @_argParser options, callback
opt = @_prepareOpts ("keys/" + @_stripSlashPrefix key), "/v2", null, options
@client.delete opt, callback
delete: @::del
# Synchronous delete a key
# Usage:
# .del("key")
# .del("key", {recursive: true}))
delSync: (key, options = {}) ->
this.del key, @_synchronousOpts(options)
# Make a directory
# Usage:
# .mkdir("dir", callback)
# .mkdir("dir", options, callback)
mkdir: (dir, options, callback) ->
[options, callback] = @_argParser options, callback
options.dir = true
@set dir, null, options, callback
# Synchronously make a directory
# Usage:
# .mkdir("dir")
# .mkdir("dir", options)
mkdirSync: (dir, options = {}) ->
this.mkdir dir, @_synchronousOpts(options)
# Remove a directory
# Usage:
# .rmdir("dir", callback)
# .rmdir("dir", {recursive: true}, callback)
rmdir: (dir, options, callback) ->
[options, callback] = @_argParser options, callback
options.dir = true
@del dir, options, callback
# Synchronously remove a directory
# Usage:
# .rmdir("dir")
# .rmdir("dir", {recursive: true})
rmdirSync: (dir, options = {}) ->
this.rmdir dir, @_synchronousOpts(options)
# Compare and swap value if unchanged
# Usage:
# .compareAndSwap("key", "newValue", "oldValue", callback)
# .compareAndSwap("key", "newValue", "oldValue", options, callback)
# .testAndSet("key", "newValue", "oldValue", options, callback)
compareAndSwap: (key, value, oldvalue, options, callback) ->
[options, callback] = @_argParser options, callback
options ?= {}
options.prevValue = oldvalue
@set key, value, options, callback
testAndSet: @::compareAndSwap
# Compare and delete if value is unchanged
# Usage:
# .compareAndDelete("key", "oldValue", options, callback)
compareAndDelete: (key, oldvalue, options, callback) ->
[options, callback] = @_argParser options, callback
options ?= {}
options.prevValue = oldvalue
@del key, options, callback
testAndDelete: @::compareAndDelete
# Execute a raw etcd query
# Where method is one of: PUT, GET, POST, PATCH, DELETE
#
# Usage:
# .raw("METHOD", "path", "value", options, callback)
# .raw("GET", "v2/stats/leader", null, {}, callback)
# .raw("PUT", "v2/keys/key", "value", {}, callback)
raw: (method, key, value, options, callback) ->
[options, callback] = @_argParser options, callback
opt = @_prepareOpts key, "", value, options
@client.execute method, opt, callback
# Watch for value changes on a key
watch: (key, options, callback) ->
[options, callback] = @_argParser options, callback
options ?= {}
options.wait = true
@get key, options, callback
# Watch for value changes on a key since a specific index
watchIndex: (key, index, options, callback) ->
[options, callback] = @_argParser options, callback
options ?= {}
options.waitIndex = index
@watch key, options, callback
# Returns an eventemitter that watches a key, emits 'change' on value change
# or 'reconnect' when trying to recover from errors.
watcher: (key, index = null, options = {}) =>
return new Watcher this, key, index, options
# Get the etcd cluster machines (server)
machines: (callback) ->
opt = @_prepareOpts "keys/_etcd/machines"
@client.get opt, callback
# List servers this etcd client will try to connect to
getHosts: () ->
_.clone(@hosts)
# Get the current cluster leader
leader: (callback) ->
opt = @_prepareOpts "leader"
@client.get opt, callback
# Get statistics about the leader
leaderStats: (callback) ->
opt = @_prepareOpts "stats/leader"
@client.get opt, callback
# Get statistics about the currently connected entity
selfStats: (callback) ->
opt = @_prepareOpts "stats/self"
@client.get opt, callback
# Get version of etcd
version: (callback) ->
opt = @_prepareOpts "version", ""
@client.get opt, callback
# Strip the prefix slash if set
_stripSlashPrefix: (key) ->
key.replace /^\//, ''
_synchronousOpts: (options) ->
_.extend {}, options, { synchronous: true }
# Prepare request options
_prepareOpts: (path, apiVersion = "/v2", value = null, allOpts = {}) ->
# serverprotocol = if @sslopts? then "https" else "http"
queryString = _.omit allOpts, 'maxRetries', 'synchronous'
clientOptions = _.pick allOpts, 'maxRetries'
opt = {
path: "#{apiVersion}/#{path}"
# serverprotocol: serverprotocol
json: true
qs: queryString
clientOptions: clientOptions
synchronous: allOpts.synchronous
form: { value: value } if value?
agentOptions: @sslopts if @sslopts?
}
# Swap callback and options if no options was given.
_argParser: (options = {}, callback) ->
if typeof options is 'function'
[{}, options]
else
[options, callback]
# Make sure hosts is a list, make sure all have protocol added
# defaults to http and remove trailing slash
_cleanHostList: (hosts) ->
hostlist = if _.isArray(hosts) then hosts else [hosts]
hostlist.map (host) ->
host = 'http://' + host if host.indexOf('http') is -1
url = new URL(host)
url.href.replace /\/$/, "" # Trailing slash
exports = module.exports = Etcd