consul-ruby-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ea81d2569729debbb271b229358775f919a0de0c
4
+ data.tar.gz: 521ad0389c65a8d57ca62202faada9546d467416
5
+ SHA512:
6
+ metadata.gz: 5c86f347a8a0ca26a65a4cf825688544ce14d75d2988a6e38d78c5c4adfdb3f9f9cbfbef0ebe78dab87212220bf13bcf68a43be07f18160aa1253ceac90e7073
7
+ data.tar.gz: 93e0c798a11880043e1545daa87eb84e6210af4f91f5849bd1640c0a3adbc6db127db645943f7a5ed40244bcbb65591367d05196ed177908bae3c2cc6c27546a
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+
16
+ # Ignore all development gems.
17
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in consul-ruby-client.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Michael Hotan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Michael Hotan
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Consul::Client
2
+
3
+ Thin Ruby Client around Consul REST API
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'consul-ruby-client'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install consul-ruby-client
20
+
21
+ ## Usage
22
+
23
+ To use the client you have to require the root library
24
+
25
+ ```
26
+ # Add the dependencies
27
+ require 'consul/client'
28
+
29
+ # Import that namespace
30
+ include Consul::Client
31
+ ```
32
+
33
+ ### Key Value Store
34
+
35
+ ```
36
+ kvs = KeyValue.new
37
+ kvs.put('cat','dog')
38
+ kvs.get('cat')
39
+
40
+ ```
41
+
42
+ ### Agent
43
+
44
+ ```
45
+ agent = Agent.new
46
+ agent.register(Agent::Service.for_name('my_service'))
47
+ agent.services
48
+ ```
49
+
50
+ ### Catalog
51
+
52
+ TODO
53
+
54
+ ### Sessions
55
+
56
+ TODO
57
+
58
+ ### Status
59
+
60
+ TODO
61
+
62
+ ## TODO
63
+
64
+ * Tests,
65
+ Currently all test were done throught building and installing the ruby client
66
+ and verifying through REPL. That is not the long term solution. We are looking
67
+ at integrating Consul into the rspec test itself.
68
+ However a solid short term win will be completing rspec tests with set fixtures.
69
+
70
+ * Implement more advance locking mechanisms
71
+ * Complete the Agent API self, join, and force-leave
72
+ * ACL API
73
+ * Events
74
+
75
+ ## Contributing
76
+
77
+ 1. Fork it ( https://github.com/[my-github-username]/consul-client/fork )
78
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
79
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
80
+ 4. Push to the branch (`git push origin my-new-feature`)
81
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'consul/client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'consul-ruby-client'
8
+ spec.version = Consul::Client::VERSION
9
+ spec.authors = ['Michael Hotan']
10
+ spec.email = ['michael.hotan@socrata.com']
11
+ spec.summary = %q{Ruby Client library for communicating with Consul Agents.}
12
+ spec.description = %q{Consul agents expose a HTTP REST API and this library is used to communicate directly with it.}
13
+ spec.homepage = 'https://github.com/mhotan-s/consul-ruby-client'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec', '~> 3.2'
24
+ spec.add_development_dependency 'simplecov', '~> 0.9'
25
+ spec.add_development_dependency 'webmock', '~> 1.21'
26
+ spec.add_dependency 'rest-client', '~> 1.6'
27
+ spec.add_dependency 'representable', '~> 2.1'
28
+ spec.add_dependency 'json', '~> 1.8'
29
+ end
@@ -0,0 +1,18 @@
1
+ require_relative '../consul/model/health_check'
2
+ require_relative '../consul/model/key_value'
3
+ require_relative '../consul/model/node'
4
+ require_relative '../consul/model/service'
5
+ require_relative '../consul/model/session'
6
+ require_relative '../consul/client/version'
7
+ require_relative '../consul/client/base'
8
+ require_relative '../consul/client/agent'
9
+ require_relative '../consul/client/catalog'
10
+ require_relative '../consul/client/key_value'
11
+ require_relative '../consul/client/session'
12
+ require_relative '../consul/client/status'
13
+
14
+ module Consul
15
+ module Client
16
+ # Modules added above.
17
+ end
18
+ end
@@ -0,0 +1,322 @@
1
+ require_relative 'base'
2
+ require_relative '../model/service'
3
+ require_relative '../model/health_check'
4
+
5
+ # Consul Agent Client.
6
+ # Represents Ruby endpoint as described here: https://www.consul.io/docs/agent/http/agent.html
7
+ module Consul
8
+ module Client
9
+ class Agent
10
+ include Consul::Client::Base
11
+
12
+ # Public: Returns all the services registered with this Agent.
13
+ #
14
+ # Returns: An array of all the ConsulService(s) registered on this agent.
15
+ def services
16
+ begin
17
+ resp = _get build_agent_url('services')
18
+ rescue
19
+ @logger.warn('Unable to request all the services on this through the HTTP API')
20
+ return nil
21
+ end
22
+ # Consul returns id => ConsulServiceObjects.
23
+ s_hash = JSON.parse(resp)
24
+ s_hash.keys.map { |n| Consul::Model::Service.new.extend(Consul::Model::Service::Representer).from_hash(s_hash[n]) }
25
+ end
26
+
27
+ # Public: Returns the service that has the associated name.
28
+ #
29
+ # Returns: ConsulService if exists, nil if no service by this name exists.
30
+ def service(id)
31
+ ss = services
32
+ ss.keep_if {|s| s.id == id}.first unless ss.nil?
33
+ end
34
+
35
+ def checks
36
+ begin
37
+ resp = _get build_agent_url('checks')
38
+ rescue
39
+ @logger.warn('Unable to request all the checks on this through the HTTP API')
40
+ return nil
41
+ end
42
+ # Consul returns id => ConsulServiceObjects.
43
+ s_hash = JSON.parse(resp)
44
+ s_hash.keys.map { |n| Consul::Model::HealthCheck.new.extend(Consul::Model::HealthCheck::Representer).from_hash(s_hash[n]) }
45
+ end
46
+
47
+ # Public: Returns a check by a given id.
48
+ # Returns: nil if no such check exists, or the check instance with the correct id
49
+ def check(id)
50
+ c = checks
51
+ c.keep_if {|c| c.check_id == id}.first unless c.nil?
52
+ end
53
+
54
+ # Public: Returns a Health Check for a specific service.
55
+ #
56
+ # service_id - The ID of the service.
57
+ def service_check(service_id)
58
+ check("service:#{service_id}")
59
+ end
60
+
61
+ # Public: Registers either a service or a Health check with configured consul agent.
62
+ #
63
+ # entity - Consul::Model::Service or Consul::Model::HealthCheck instance.
64
+ #
65
+ # Example
66
+ # agent = Consul::Client::Agent.new('dc1')
67
+ # # Register a service
68
+ # agent.register(Consul::Client::Agent::Service.for_name('cat'))
69
+ # # Register a HealthCheck
70
+ # agent.register(Consul::Client::HealthCheck.ttl('my_check_name', '15m'))
71
+ # # Register a service with a Consul Health Check
72
+ # agent.register(Consul::Client::Agent::Service.for_name('cat', Consul::Client::Agent::Service.ttl_health_check('15m')))
73
+ #
74
+ # Returns true upon success, false upon failure
75
+ def register(entity)
76
+ raise TypeError unless entity.kind_of? Consul::Model::HealthCheck or entity.kind_of? Consul::Model::Service
77
+ case entity
78
+ when Consul::Model::HealthCheck
79
+ return register_with_backoff(build_check_url('register'), entity.extend(Consul::Model::HealthCheck::Representer), 0, 3)
80
+ else
81
+ entity = entity.extend(Consul::Model::Service::Representer)
82
+ success = register_with_backoff(build_service_url('register'), entity, 0, 3)
83
+ if success
84
+ @logger.info("Successfully registered service #{entity.name}.")
85
+ unless entity.check.nil?
86
+ # Pass the first health check
87
+ c = check("service:#{entity.name}")
88
+ @logger.info("Updating status for health check #{c.check_id} to \"pass\".")
89
+ _get build_check_status_url(c.check_id, 'pass')
90
+ end
91
+ end
92
+ return success
93
+ end
94
+ end
95
+
96
+ # Public: deregisters an existing ConsulHealthCheck or ConsulService
97
+ #
98
+ # entity - ConsulHealthCheck or ConsulService to unregister from this
99
+ #
100
+ # Returns - the HTTP Response
101
+ def deregister(entity)
102
+ unless entity.nil?
103
+ raise TypeError unless entity.kind_of? Consul::Model::HealthCheck or entity.kind_of? Consul::Model::Service
104
+ case entity
105
+ when Consul::Model::HealthCheck
106
+ url = build_check_url('deregister')
107
+ else
108
+ url = build_service_url('deregister')
109
+ end
110
+ _get "#{url}/#{entity.id}"
111
+ end
112
+ end
113
+
114
+ # Public: Pass a health check.
115
+ #
116
+ # check - Consul::Model::HealthCheck to pass. Cannot be nil or wrong type
117
+ #
118
+ def pass(check)
119
+ update_check_status(check, 'pass')
120
+ end
121
+
122
+ # Public: Warn a health check
123
+ #
124
+ # check - Consul::Model::HealthCheck to pass. Cannot be nil or wrong type
125
+ #
126
+ def warn(check)
127
+ update_check_status(check, 'warn')
128
+ end
129
+
130
+ # Public: Fails a health check
131
+ #
132
+ # check - Consul::Model::HealthCheck to pass. Cannot be nil or wrong type
133
+ #
134
+ def fail(check)
135
+ update_check_status(check, 'fail')
136
+ end
137
+
138
+ # Public: Enter maintenance mode.
139
+ #
140
+ # enable - Flag to indicate to enable maintanence mode or not
141
+ # service - Set maintanence for a particular service is set.
142
+ # reason - Optional reason.
143
+ def maintenance(enable, service = nil, reason = nil)
144
+ if service.nil?
145
+ url = build_agent_url('maintenance')
146
+ else
147
+ if service.instance_of?(Consul::Model::Service)
148
+ service = service.id
149
+ end
150
+ raise ArgumentError.new "Unable to create request for #{service}" unless service.respond_to?(:to_str)
151
+ url = build_service_url("maintenance/#{service}")
152
+ end
153
+ params = {:enable => enable}
154
+ params[:reason] = reason unless reason.nil?
155
+ _get url, params
156
+ end
157
+
158
+ module HealthCheck
159
+
160
+ # Public: TTL Check
161
+ #
162
+ # name - The name of the check, Cannot be nil
163
+ # ttl - Time to live time window. IE "15s", Cannot be nil
164
+ # id - ID to associate with this check if 'name' is not desired.
165
+ # notes - Message to place as notes for this check
166
+ #
167
+ # Returns: Consul::Model::HealthCheck instance
168
+ def self.ttl(name, ttl, id = name, notes = nil)
169
+ validate_arg name
170
+ validate_arg ttl
171
+ c = Consul::Model::HealthCheck.new(name: name, ttl: ttl)
172
+ c[:id] = id unless id.nil?
173
+ c[:notes] = notes unless notes.nil?
174
+ c
175
+ end
176
+
177
+ # Public: TTL Check
178
+ #
179
+ # name - The name of the check, Cannot be nil
180
+ # script - The script to run locally
181
+ # interval - The time interval to conduct the check. IE: '10s'
182
+ # id - ID to associate with this check if 'name' is not desired.
183
+ # notes - Message to place as notes for this check.
184
+ #
185
+ # Returns: Consul::Model::HealthCheck instance
186
+ def self.script(name, script, interval, id = name, notes = nil)
187
+ validate_arg name
188
+ validate_arg script
189
+ validate_arg interval
190
+ c = Consul::Model::HealthCheck.new(name: name, script: script, interval: interval)
191
+ c[:id] = id unless id.nil?
192
+ c[:notes] = notes unless notes.nil?
193
+ c
194
+ end
195
+
196
+ # Public: TTL Check
197
+ #
198
+ # name - The name of the check, Cannot be nil
199
+ # http - The HTTP endpoint to hit with periodic GET.
200
+ # interval - The time interval to conduct the check. IE: '10s'
201
+ # id - ID to associate with this check if 'name' is not desired.
202
+ # notes - Message to place as notes for this check
203
+ #
204
+ # Returns: Consul::Model::HealthCheck instance
205
+ def self.http(name, http, interval, id = name, notes = nil)
206
+ validate_arg name
207
+ validate_arg http
208
+ validate_arg interval
209
+ c = Consul::Model::HealthCheck.new(name: name, script: script, interval: interval)
210
+ c[:id] = id unless id.nil?
211
+ c[:notes] = notes unless notes.nil?
212
+ c
213
+ end
214
+
215
+ private
216
+
217
+ def self.validate_arg(arg)
218
+ raise ArgumentError.new "Illegal Argument: #{arg}" if arg.nil? or arg.empty?
219
+ end
220
+
221
+ end
222
+
223
+ # Container Module for simpler way to create a service.
224
+ module Service
225
+
226
+ # Public: Creates a service using a specific name
227
+ #
228
+ # name - Name of the service to create
229
+ # check - The Consul::Model::HealthCheck instance to associate with this Session
230
+ #
231
+ # Returns: Consul::Model::Service instance
232
+ def self.for_name(name, check = nil)
233
+ raise ArgumentError.new "Illegal name: \"#{name}\" for service." if name.nil?
234
+ unless check.nil? or check.is_a?(Consul::Model::HealthCheck)
235
+ raise TypeError.new "Illegal Check type: #{check}. Expecting Consul::Model::HealthCheck"
236
+ end
237
+ if check.nil?
238
+ Consul::Model::Service.new(name: name)
239
+ else # There is a health check to register
240
+ Consul::Model::Service.new(name: name, check: check)
241
+ end
242
+ end
243
+
244
+ # Public: Creates a health check meant to be used when registering a service.
245
+ #
246
+ #
247
+ #
248
+ # Returns: Consul::Model::HealthCheck instance that represents a script
249
+ def self.script_health_check(script, interval)
250
+ Consul::Model::HealthCheck.new(script: script, interval: interval)
251
+ end
252
+
253
+ def self.http_health_check(http, interval)
254
+ Consul::Model::HealthCheck.new(http: http, interval: interval)
255
+ end
256
+
257
+ def self.ttl_health_check(ttl)
258
+ Consul::Model::HealthCheck.new(ttl: ttl)
259
+ end
260
+ end
261
+
262
+ private
263
+
264
+ # Private: Updates the check with the argument status.
265
+ def update_check_status(check, status)
266
+ unless check.instance_of?(Consul::Model::HealthCheck) and check.respond_to?(:to_str)
267
+ check = check(check.to_str)
268
+ end
269
+ return false if check.nil?
270
+ raise ArgumentError.new "Illegal Status #{status}" unless status == 'pass' or status == 'warn' or status == 'fail'
271
+ resp = _get build_check_url("#{status}/#{check.check_id}")
272
+ resp.code == 200
273
+ end
274
+
275
+ # Private: Register a consul entity with the existing agent but attempts and exponentially increasing interval if
276
+ # fails.
277
+ #
278
+ # url - The url to make the register request through.
279
+ # entity - JSON representation of the entity
280
+ # iteration - The current attempt iteration
281
+ # threshold - Number of attempts to try
282
+ #
283
+ def register_with_backoff(url, entity, iteration, threshold)
284
+ # Checking a greater iteration just to be on the safe side.
285
+ unless iteration > threshold or threshold <= 0 or iteration < 0
286
+ sleep((1.0/2.0*(2.0**iteration - 1.0)).ceil) if iteration > 0
287
+ success, _ = _put(url, entity.to_json)
288
+ unless success # Unless we successfully registered.
289
+ if threshold == iteration
290
+ @logger.error("Unable to complete registration after #{threshold + 1} attempts")
291
+ else
292
+ # Attempt to register again using the exponential backoff.
293
+ @logger.warn("Unable to complete registration after #{iteration + 1} attempts, Retrying up to #{threshold+1} attempts")
294
+ register_with_backoff(url, entity, iteration + 1, threshold)
295
+ end
296
+ end
297
+ return true
298
+ end
299
+ false
300
+ end
301
+
302
+ def build_service_url(suffix)
303
+ build_agent_url("service/#{suffix}")
304
+ end
305
+
306
+ def build_check_url(suffix)
307
+ build_agent_url("check/#{suffix}")
308
+ end
309
+
310
+ # status : "pass", "warn", or "fail"
311
+ def build_check_status_url(check_id, status)
312
+ build_check_url("#{status}/#{check_id}")
313
+ end
314
+
315
+ # Returns host:port/v1/agent/suffix
316
+ def build_agent_url(suffix)
317
+ "#{base_versioned_url}/agent/#{suffix}"
318
+ end
319
+
320
+ end
321
+ end
322
+ end