etcd 0.0.6 → 0.2.0.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1b268d252f6591fe8015fcf257369ec0502d62b2
4
- data.tar.gz: 0b2567328a06ca5f6433948c7431ee7c4bb4a2d8
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjEwNDYyYjk0OWMyNzU2M2NhMjk2N2QxYmVmYTFhMmI2NjhlNzdiOQ==
5
+ data.tar.gz: !binary |-
6
+ NDdkYzdhMzM4ZmFhZWU1YTkxZTM3ZWEwYTA1MDE1YjI5NWZmODgxZg==
5
7
  SHA512:
6
- metadata.gz: 9925238076f679e9983358c87f24d53e4f95726bfdea41f06aae831277f5126a99c956997848489c31ed0b228a8605a9f0b5c30fad323bbeca06e3564379f273
7
- data.tar.gz: b65c505cbad680da0cf1967f750408986c69cf17c0d781b21a218a3706afaf069dd10b13b3c7a0d9bbf394cb3b163e5237d7a1c2b5424d549da809f17455586d
8
+ metadata.gz: !binary |-
9
+ OGI3MTA1YmM5MjA4ZjUzYmE4Mjc0MTVmYWEzODU1NDI0ZjhiMzNlZDM3MWQ1
10
+ Mjk1NTIxNmY2MzA4MGYyZTEyZTRiZjExOTFiZTRkNGI3MzAyZjFkOWY3Yjdm
11
+ MGY5NGNmYjU5NDEwOTVjZDNjMzBiOWU5YTA5YzNmOTUwNGEzMDA=
12
+ data.tar.gz: !binary |-
13
+ OGYwZThkZjFmMDcxNzUyZTBjODBhMTgwZWYwMWVmZjU1NzAyZmVjNTg5YmY5
14
+ ZGQ0ODgxYmMzNjBlMTFmMDZhOTI3MmI2MTgzMGE0NDQ3ODkxYWIzYWI0NDc0
15
+ YzAzNzNjYzc3YzIwYzZiMGI5ZjEzN2ZjMWI5NmFlMDkzNDNlOWM=
@@ -0,0 +1,5 @@
1
+ :directories:
2
+ - lib
3
+ :excludes:
4
+ - spec
5
+ - .bundle
data/.gitignore CHANGED
@@ -4,16 +4,7 @@
4
4
  .config
5
5
  .yardoc
6
6
  Gemfile.lock
7
- InstalledFiles
8
7
  _yardoc
9
8
  coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
9
  spec/reports
15
- test/tmp
16
- test/version_tmp
17
10
  tmp
18
- html/
19
- etcd
@@ -1,14 +1,10 @@
1
- before_install:
2
- - sudo apt-get update -y
3
- - sudo add-apt-repository ppa:duh/golang -y
4
- - sudo apt-get update -y
5
- - sudo apt-get install golang -y
6
- - git clone https://github.com/coreos/etcd
7
- - cd etcd && bash build
1
+ before_install:
2
+ - bash build_etcd
8
3
  - bundle install --path .bundle
9
4
  rvm:
10
5
  - 1.9.3
11
6
  - 2.0.0
7
+ - 2.1.0
12
8
  branches:
13
9
  only:
14
10
  - master
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in etcd.gemspec
4
4
  gemspec
5
+ gem 'coco'
6
+ gem 'rubocop'
data/README.md CHANGED
@@ -20,9 +20,9 @@ Or install it yourself as:
20
20
  ### Create a client object
21
21
  ```ruby
22
22
  client = Etcd.client # this will create a client against etcd server running on localhost on port 4001
23
- client = Etcd.client(:port=>4002)
24
- client = Etcd.client(:host=>'127.0.0.1', :port=>4003)
25
- client = Etcd.client(:host=>'127.0.0.1', :port=>4003, :allow_redirect => false) # wont let you run sensitive commands on non-leader machines, default is true
23
+ client = Etcd.client(port: 4002)
24
+ client = Etcd.client(host: '127.0.0.1', port: 4003)
25
+ client = Etcd.client(host: '127.0.0.1', port: 4003, allow_redirect: false) # wont let you run sensitive commands on non-leader machines, default is true
26
26
  ```
27
27
  ### Set a key
28
28
  ```ruby
@@ -38,6 +38,7 @@ client.get('/nodes/n2').value
38
38
  ### Delete a key
39
39
  ```ruby
40
40
  client.delete('/nodes/n1')
41
+ client.delete('/nodes/', recursive: true)
41
42
  ```
42
43
 
43
44
  ### Test and set
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ wget -c https://go.googlecode.com/files/go1.2.linux-amd64.tar.gz
4
+ tar -zxf go1.2.linux-amd64.tar.gz
5
+ git clone https://github.com/coreos/etcd
6
+ export GOROOT=$PWD/go
7
+ export PATH=$GOROOT/bin:$PATH
8
+ cd etcd && ./build
@@ -18,12 +18,10 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "mixlib-cli"
22
21
  spec.add_dependency "mixlib-log"
23
- spec.add_dependency "uuid"
24
22
 
23
+ spec.add_development_dependency "uuid"
25
24
  spec.add_development_dependency "bundler"
26
25
  spec.add_development_dependency "rake"
27
26
  spec.add_development_dependency "rspec"
28
- spec.add_development_dependency "simplecov"
29
27
  end
@@ -1,10 +1,10 @@
1
+ # Encoding: utf-8
1
2
 
3
+ require 'etcd/client'
2
4
  ##
3
5
  # This module provides the Etcd:: name space for the gem and few
4
6
  # factory methods for Etcd domain objects
5
- require 'etcd/client'
6
7
  module Etcd
7
-
8
8
  ##
9
9
  # Create and return a Etcd::Client object. It takes a hash +opts+
10
10
  # as an argument which gets passed to the Etcd::Client.new method
@@ -0,0 +1,129 @@
1
+ # Encoding: utf-8
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'etcd/log'
6
+ require 'etcd/stats'
7
+ require 'etcd/keys'
8
+ require 'etcd/exceptions'
9
+ require 'etcd/mod/lock'
10
+ require 'etcd/mod/leader'
11
+
12
+ module Etcd
13
+ ##
14
+ # This is the central ruby class for Etcd. It provides methods for all
15
+ # etcd api calls. It also provides few additional methods beyond the core
16
+ # etcd api, like Etcd::Client#lock and Etcd::Client#eternal_watch, they
17
+ # are defined in separate modules and included in this class
18
+ class Client
19
+
20
+ HTTP_REDIRECT = ->(r){ r.is_a? Net::HTTPRedirection }
21
+ HTTP_SUCCESS = ->(r){ r.is_a? Net::HTTPSuccess }
22
+ HTTP_CLIENT_ERROR = ->(r){ r.is_a? Net::HTTPClientError }
23
+
24
+ include Stats
25
+ include Keys
26
+ include Mod::Lock
27
+ include Mod::Leader
28
+
29
+ attr_reader :host, :port, :http, :allow_redirect
30
+ attr_reader :use_ssl, :verify_mode, :read_timeout
31
+
32
+ ##
33
+ # Creates an Etcd::Client object. It accepts a hash +opts+ as argument
34
+ #
35
+ # @param [Hash] opts The options for new Etcd::Client object
36
+ # @opts [String] :host IP address of the etcd server (default 127.0.0.1)
37
+ # @opts [Fixnum] :port Port number of the etcd server (default 4001)
38
+ # @opts [Fixnum] :read_timeout set HTTP read timeouts (default 60)
39
+ def initialize(opts = {})
40
+ @host = opts[:host] || '127.0.0.1'
41
+ @port = opts[:port] || 4001
42
+ @read_timeout = opts[:read_timeout] || 60
43
+ @allow_redirect = opts.key?(:allow_redirect) ? opts[:allow_redirect] : true
44
+ @use_ssl = opts[:use_ssl] || false
45
+ @verify_mode = opts.key?(:verify_mode) ? opts[:verify_mode] : OpenSSL::SSL::VERIFY_PEER
46
+ end
47
+
48
+ # Returns the etcd api version that will be used for across API methods
49
+ def version_prefix
50
+ '/v2'
51
+ end
52
+
53
+ # Returns the etcd daemon version
54
+ def version
55
+ api_execute('/version', :get).body
56
+ end
57
+
58
+ # Returns array of all machines in the cluster
59
+ def machines
60
+ api_execute(version_prefix + '/machines', :get).body.split(',').map(&:strip)
61
+ end
62
+
63
+ # Get the current leader
64
+ def leader
65
+ api_execute(version_prefix + '/leader', :get).body.strip
66
+ end
67
+
68
+ # This method sends api request to etcd server.
69
+ #
70
+ # This method has following parameters as argument
71
+ # * path - etcd server path (etcd server end point)
72
+ # * method - the request method used
73
+ # * options - any additional parameters used by request method (optional)
74
+ def api_execute(path, method, options = {})
75
+ params = options[:params]
76
+ case method
77
+ when :get
78
+ req = build_http_request(Net::HTTP::Get, path, params)
79
+ when :post
80
+ req = build_http_request(Net::HTTP::Post, path, nil, params)
81
+ when :put
82
+ req = build_http_request(Net::HTTP::Put, path, nil, params)
83
+ when :delete
84
+ req = build_http_request(Net::HTTP::Delete, path, params)
85
+ else
86
+ fail "Unknown http action: #{method}"
87
+ end
88
+ timeout = options[:timeout] || @read_timeout
89
+ http = Net::HTTP.new(host, port)
90
+ http.read_timeout = timeout
91
+ http.use_ssl = use_ssl
92
+ http.verify_mode = verify_mode
93
+ Log.debug("Invoking: '#{req.class}' against '#{path}")
94
+ res = http.request(req)
95
+ Log.debug("Response code: #{res.code}")
96
+ process_http_request(res)
97
+ end
98
+
99
+ def process_http_request(res)
100
+ case res
101
+ when HTTP_SUCCESS
102
+ Log.debug('Http success')
103
+ res
104
+ when HTTP_REDIRECT
105
+ if allow_redirect
106
+ Log.debug('Http redirect, following')
107
+ api_execute(res['location'], method, params: params)
108
+ else
109
+ Log.debug('Http redirect not allowed')
110
+ res.error!
111
+ end
112
+ when HTTP_CLIENT_ERROR
113
+ fail Error.from_http_response(res)
114
+ else
115
+ Log.debug('Http error')
116
+ Log.debug(res.body)
117
+ res.error!
118
+ end
119
+ end
120
+
121
+ def build_http_request(klass, path, params = nil, body = nil)
122
+ path += '?' + URI.encode_www_form(params) unless params.nil?
123
+ req = klass.new(path)
124
+ req.body = URI.encode_www_form(body) unless body.nil?
125
+ Etcd::Log.debug("Built #{klass} path:'#{path}' body:'#{req.body}'")
126
+ req
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,78 @@
1
+ # Encoding: utf-8
2
+
3
+ require 'json'
4
+
5
+ # Provides Etcd namespace
6
+ module Etcd
7
+ # Represents all etcd custom errors
8
+ class Error < StandardError
9
+ attr_reader :cause, :error_code, :index
10
+
11
+ def initialize(opts = {})
12
+ super(opts['message'])
13
+ @cause = opts['cause']
14
+ @index = opts['index']
15
+ @error_code = opts['errorCode']
16
+ end
17
+
18
+ def self.from_http_response(response)
19
+ opts = JSON.parse(response.body)
20
+ unless ERROR_CODE_MAPPING.key?(opts['errorCode'])
21
+ fail "Unknown error code: #{opts['errorCode']}"
22
+ end
23
+ ERROR_CODE_MAPPING[opts['errorCode']].new(opts)
24
+ end
25
+
26
+ def inspect
27
+ "<#{self.class}: index:#{index}, code:#{error_code}, cause:'#{cause}'>"
28
+ end
29
+ end
30
+
31
+ # command related error
32
+ class KeyNotFound < Error; end
33
+ class TestFailed < Error; end
34
+ class NotFile < Error; end
35
+ class NoMorePeer < Error; end
36
+ class NotDir < Error; end
37
+ class NodeExist < Error; end
38
+ class KeyIsPreserved < Error; end
39
+
40
+ # Post form related error
41
+ class ValueRequired < Error; end
42
+ class PrevValueRequired < Error; end
43
+ class TTLNaN < Error; end
44
+ class IndexNaN < Error; end
45
+
46
+ # Raft related error
47
+ class RaftInternal < Error; end
48
+ class LeaderElect < Error; end
49
+
50
+ # Etcd related error
51
+ class WatcherCleared < Error; end
52
+ class EventIndexCleared < Error; end
53
+
54
+ ERROR_CODE_MAPPING = {
55
+ # command related error
56
+ 100 => KeyNotFound,
57
+ 101 => TestFailed,
58
+ 102 => NotFile,
59
+ 103 => NoMorePeer,
60
+ 104 => NotDir,
61
+ 105 => NodeExist,
62
+ 106 => KeyIsPreserved,
63
+
64
+ # Post form related error
65
+ 200 => ValueRequired,
66
+ 201 => PrevValueRequired,
67
+ 202 => TTLNaN,
68
+ 203 => IndexNaN,
69
+
70
+ # Raft related error
71
+ 300 => RaftInternal,
72
+ 301 => LeaderElect,
73
+
74
+ # Etcd related error
75
+ 400 => WatcherCleared,
76
+ 401 => EventIndexCleared
77
+ }
78
+ end
@@ -0,0 +1,147 @@
1
+ # Encoding: utf-8
2
+
3
+ require 'json'
4
+ require 'etcd/response'
5
+ require 'etcd/log'
6
+
7
+ module Etcd
8
+ # Keys module provides the basic key value operations against
9
+ # etcd /keys namespace
10
+ module Keys
11
+ # return etcd endpoint that is reserved for key/value store
12
+ def key_endpoint
13
+ version_prefix + '/keys'
14
+ end
15
+
16
+ # Retrives a key with its associated data, if key is not present it will
17
+ # return with message "Key Not Found"
18
+ #
19
+ # This method has following parameters as argument
20
+ # * key - whose data to be retrive
21
+ def get(key, opts = {})
22
+ response = api_execute(key_endpoint + key, :get, params: opts)
23
+ Response.from_http_response(response)
24
+ end
25
+
26
+ # Create or update a new key
27
+ #
28
+ # This method has following parameters as argument
29
+ # * key - whose value to be set
30
+ # * value - value to be set for specified key
31
+ # * ttl - shelf life of a key (in secsonds) (optional)
32
+ def set(key, value, opts = nil)
33
+ path = key_endpoint + key
34
+ payload = {}
35
+ if value.is_a?(Hash) # directory
36
+ opts = value.dup
37
+ else
38
+ payload['value'] = value
39
+ end
40
+ if opts.is_a? Fixnum
41
+ warn '[DEPRECATION] Passing ttl as raw argument is deprecated \
42
+ please use :ttl => value, this will be removed in next minor release'
43
+ payload['ttl'] = opts
44
+ elsif opts.is_a? Hash
45
+ [:ttl, :dir, :prevExist, :prevValue, :prevIndex].each do |k|
46
+ payload[k] = opts[k] if opts.key?(k)
47
+ end
48
+ elsif opts.nil?
49
+ # do nothing
50
+ else
51
+ fail ArgumentError, "Dont know how to parse #{opts}"
52
+ end
53
+ response = api_execute(path, :put, params: payload)
54
+ Response.from_http_response(response)
55
+ end
56
+
57
+ # Deletes a key (and its content)
58
+ #
59
+ # This method has following parameters as argument
60
+ # * key - key to be deleted
61
+ def delete(key, opts = {})
62
+ response = api_execute(key_endpoint + key, :delete, params: opts)
63
+ Response.from_http_response(response)
64
+ end
65
+
66
+ # Set a new value for key if previous value of key is matched
67
+ #
68
+ # This method takes following parameters as argument
69
+ # * key - whose value is going to change if previous value is matched
70
+ # * value - new value to be set for specified key
71
+ # * prevValue - value of a key to compare with existing value of key
72
+ # * ttl - shelf life of a key (in secsonds) (optional)
73
+ def compare_and_swap(key, value, prevValue, ttl = nil)
74
+ path = key_endpoint + key
75
+ payload = { 'value' => value, 'prevValue' => prevValue }
76
+ payload['ttl'] = ttl unless ttl.nil?
77
+ response = api_execute(path, :put, params: payload)
78
+ Response.from_http_response(response)
79
+ end
80
+
81
+ # Gives a notification when specified key changes
82
+ #
83
+ # This method has following parameters as argument
84
+ # @ key - key to be watched
85
+ # @options [Hash] additional options for watching a key
86
+ # @options [Fixnum] :index watch the specified key from given index
87
+ # @options [Fixnum] :timeout specify http timeout
88
+ def watch(key, options = {})
89
+ params = { wait: true }
90
+ timeout = options[:timeout] || @read_timeout
91
+ index = options[:waitIndex] || options[:index]
92
+ params[:waitIndex] = index unless index.nil?
93
+ params[:consistent] = options[:consistent] if options.key?(:consistent)
94
+
95
+ response = api_execute(key_endpoint + key, :get,
96
+ timeout: timeout, params: params)
97
+ Response.from_http_response(response)
98
+ end
99
+
100
+ def create_in_order(dir, value, opts = {})
101
+ path = key_endpoint + dir
102
+ payload = { 'value' => value }
103
+ payload['ttl'] = opts[:ttl] if opts[:ttl]
104
+ response = api_execute(path, :post, params: payload)
105
+ Response.from_http_response(response)
106
+ end
107
+
108
+ def exists?(key)
109
+ begin
110
+ Etcd::Log.debug("Checking if key:' #{key}' exists")
111
+ get(key)
112
+ true
113
+ rescue KeyNotFound => e
114
+ Etcd::Log.debug("Key does not exist #{e}")
115
+ false
116
+ end
117
+ end
118
+
119
+ def create(key, value, ttl = nil)
120
+ path = key_endpoint + key
121
+ payload = { value: value, prevExist: false }
122
+ payload['ttl'] = ttl unless ttl.nil?
123
+ response = api_execute(path, :put, params: payload)
124
+ Response.from_http_response(response)
125
+ end
126
+
127
+ def update(key, value, ttl = nil)
128
+ path = key_endpoint + key
129
+ payload = { value: value, prevExist: true }
130
+ payload['ttl'] = ttl unless ttl.nil?
131
+ response = api_execute(path, :put, params: payload)
132
+ Response.from_http_response(response)
133
+ end
134
+
135
+ def eternal_watch(key, index = nil)
136
+ loop do
137
+ response = watch(key, index)
138
+ yield response
139
+ end
140
+ end
141
+
142
+ alias_method :key?, :exists?
143
+ alias_method :exist?, :exists?
144
+ alias_method :has_key?, :exists?
145
+ alias_method :test_and_set, :compare_and_swap
146
+ end
147
+ end