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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +22 -0
- data/README.md +81 -0
- data/Rakefile +2 -0
- data/consul-ruby-client.gemspec +29 -0
- data/lib/consul/client.rb +18 -0
- data/lib/consul/client/agent.rb +322 -0
- data/lib/consul/client/base.rb +132 -0
- data/lib/consul/client/catalog.rb +99 -0
- data/lib/consul/client/key_value.rb +142 -0
- data/lib/consul/client/session.rb +135 -0
- data/lib/consul/client/status.rb +44 -0
- data/lib/consul/client/version.rb +5 -0
- data/lib/consul/model/health_check.rb +39 -0
- data/lib/consul/model/key_value.rb +28 -0
- data/lib/consul/model/node.rb +25 -0
- data/lib/consul/model/service.rb +30 -0
- data/lib/consul/model/session.rb +30 -0
- data/lib/consul/util/utils.rb +17 -0
- data/spec/base_client_spec.rb +25 -0
- data/spec/spec_helper.rb +13 -0
- metadata +182 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'logger'
|
3
|
+
require 'rest-client'
|
4
|
+
require_relative '../util/utils'
|
5
|
+
|
6
|
+
module Consul
|
7
|
+
module Client
|
8
|
+
|
9
|
+
# Public API Base.
|
10
|
+
module Base
|
11
|
+
|
12
|
+
# Public: Creates an API Endpoint
|
13
|
+
#
|
14
|
+
# data_center - The data center to utilize, defaults to bootstrap 'dc1' datat center
|
15
|
+
# api_host - The host the Consul Agent is running on. Default: 127.0.0.1
|
16
|
+
# api_port - The port the Consul Agent is listening on. Default: 8500
|
17
|
+
# version - The version of the api to use.
|
18
|
+
# logger - Logging mechanism. Must conform to Ruby Logger interface
|
19
|
+
#
|
20
|
+
def initialize(data_center = 'dc1', api_host = '127.0.0.1', api_port = '8500', version = 'v1', logger = Logger.new(STDOUT))
|
21
|
+
@dc = data_center
|
22
|
+
@host = api_host
|
23
|
+
@port = api_port
|
24
|
+
@logger = logger
|
25
|
+
@version = version
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Test if this Consul Client is reachable.
|
29
|
+
def is_reachable
|
30
|
+
_get(base_url, nil, false) == 'Consul Agent'
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# Protected: Generic get request. Wraps error handling and url validation.
|
36
|
+
#
|
37
|
+
# url - url endpoint to hit.
|
38
|
+
# params - Hash of key to value parameters
|
39
|
+
# json_only - Flag that annotates we should only be expecting json back. Default true, most consul endpoints return JSON.
|
40
|
+
#
|
41
|
+
# Returns:
|
42
|
+
# Throws:
|
43
|
+
# ArgumentError: the url is not valid.
|
44
|
+
# IOError: Unable to reach Consul Agent.
|
45
|
+
def _get(url, params = nil, json_only = true)
|
46
|
+
# Validation
|
47
|
+
validate_url(url)
|
48
|
+
|
49
|
+
opts = {}
|
50
|
+
opts[:params] = params unless params.nil?
|
51
|
+
opts[:accept] = :json if json_only
|
52
|
+
begin
|
53
|
+
return RestClient.get url, opts
|
54
|
+
rescue Exception => e
|
55
|
+
# Unable to communicate with consul agent.
|
56
|
+
logger.warn(e.message)
|
57
|
+
raise IOError.new "Unable to complete get request: #{e}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Protected: Generic put request. Wraps error and translates Rest response to success or failure.
|
62
|
+
#
|
63
|
+
# url - The url endpoint for the put request.
|
64
|
+
# value - The value to put at the url endpoint.
|
65
|
+
#
|
66
|
+
# Returns: true on success or false on failure and the body of the return message.
|
67
|
+
# Throws:
|
68
|
+
# ArgumentError: the url is not valid.
|
69
|
+
# IOError: Unable to reach Consul Agent.
|
70
|
+
def _put(url, value, params = nil)
|
71
|
+
# Validation
|
72
|
+
validate_url(url)
|
73
|
+
|
74
|
+
p = {}
|
75
|
+
p[:params] = params unless params.nil?
|
76
|
+
begin
|
77
|
+
if Consul::Utils.valid_json?(value)
|
78
|
+
resp = RestClient.put(url, value, :content_type => :json) {|response, req, res| response }
|
79
|
+
else
|
80
|
+
resp = RestClient.put(url, value) {|response, req, res| response }
|
81
|
+
end
|
82
|
+
success = (resp.code == 200 or resp.code == 201)
|
83
|
+
logger.warn("Unable to send #{value} to endpoint #{url} returned code: #{resp.code}") unless success
|
84
|
+
return success, resp.body
|
85
|
+
rescue Exception => e
|
86
|
+
logger.error('RestClient.put Error: Unable to reach consul agent')
|
87
|
+
raise IOError.new "Unable to complete put request: #{e}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def data_center
|
92
|
+
@data_center ||= 'dc1'
|
93
|
+
end
|
94
|
+
|
95
|
+
def host
|
96
|
+
@host ||= '127.0.0.1'
|
97
|
+
end
|
98
|
+
|
99
|
+
def port
|
100
|
+
@port ||= '8500'
|
101
|
+
end
|
102
|
+
|
103
|
+
def version
|
104
|
+
@version ||= 'v1'
|
105
|
+
end
|
106
|
+
|
107
|
+
def logger
|
108
|
+
@logger ||= Logger.new(STDOUT)
|
109
|
+
end
|
110
|
+
|
111
|
+
def https
|
112
|
+
@https = false
|
113
|
+
end
|
114
|
+
|
115
|
+
def base_versioned_url
|
116
|
+
"#{base_url}/#{version}"
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def base_url
|
122
|
+
"#{(https ? 'https': 'http')}://#{host}:#{port}"
|
123
|
+
end
|
124
|
+
|
125
|
+
# Private: Validates the url
|
126
|
+
def validate_url(url)
|
127
|
+
raise ArgumentError.new 'URL cannot be blank' if url.to_s == ''
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative '../model/node'
|
3
|
+
|
4
|
+
# Consul Catalog End Point.
|
5
|
+
module Consul
|
6
|
+
module Client
|
7
|
+
class Catalog
|
8
|
+
include Consul::Client::Base
|
9
|
+
|
10
|
+
# Public: Returns a list of all the nodes on this client
|
11
|
+
#
|
12
|
+
# dc - Data Center to look for services in, defaults to the agents data center
|
13
|
+
#
|
14
|
+
def nodes(dc = nil)
|
15
|
+
params = {}
|
16
|
+
params[:dc] = dc unless dc.nil?
|
17
|
+
JSON.parse(_get build_url('nodes'), params).map {|n| Consul::Model::Node.new.extend(Consul::Model::Node::Representer).from_hash(n)}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: Returns a list of services that are within the supplied or agent data center
|
21
|
+
#
|
22
|
+
# dc - Data Center to look for services in, defaults to the agents data center
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
# Consul::Client::Catalog.new('dc1').services =>
|
26
|
+
# {
|
27
|
+
# "consul": [],
|
28
|
+
# "redis": [],
|
29
|
+
# "postgresql": [
|
30
|
+
# "master",
|
31
|
+
# "slave"
|
32
|
+
# ]
|
33
|
+
# }
|
34
|
+
#
|
35
|
+
# Returns: List of services ids.
|
36
|
+
def services(dc = nil)
|
37
|
+
params = {}
|
38
|
+
params[:dc] = dc unless dc.nil?
|
39
|
+
JSON.parse(_get build_url('services'), params)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Public: Returns all the nodes within a data center that have the service specified.
|
43
|
+
#
|
44
|
+
# dc - Data center, default: agent current data center
|
45
|
+
# tag - Tag, filter for tags
|
46
|
+
#
|
47
|
+
# Example:
|
48
|
+
# ConsulCatalog.new('dc1').service('my_service_id') =>
|
49
|
+
# [ConsulNode<@service_id=my_service_id ...>,
|
50
|
+
# ConsulNode<@service_id=my_service_id ...>,
|
51
|
+
# ...]
|
52
|
+
#
|
53
|
+
# Returns: List of nodes that have this service.
|
54
|
+
def service(id, dc = nil, tag = nil)
|
55
|
+
params = {}
|
56
|
+
params[:dc] = dc unless dc.nil?
|
57
|
+
params.add[:tag] = tag unless tag.nil?
|
58
|
+
JSON.parse(_get build_url("service/#{id}"), params).map {|n| Consul::Model::Node.new.extend(Consul::Model::Node::Representer).from_hash(n)}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Public: Returns all the nodes within a data center that have the service specified.
|
62
|
+
#
|
63
|
+
# dc - Data center, default: agent current data center
|
64
|
+
# tag - Tag, filter for tags
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
# ConsulCatalog.new('dc1').node('my_node') =>
|
68
|
+
# ConsulNode<@service_id=my_service_id ...>
|
69
|
+
#
|
70
|
+
# Returns: Returns the node by the argument name.
|
71
|
+
def node(name, dc = nil)
|
72
|
+
params = {}
|
73
|
+
params[:dc] = dc unless dc.nil?
|
74
|
+
resp = JSON.parse(_get build_url("node/#{name}"), params)
|
75
|
+
n = Consul::Model::Node.new.extend(Consul::Model::Node::Representer).from_hash(resp['Node'])
|
76
|
+
unless resp[:Services].nil?
|
77
|
+
n.services = resp['Services'].keys.map{|k| Consul::Model::Service.new.extend(Consul::Model::Service::Representer).from_hash(resp[:Services][k])}
|
78
|
+
end
|
79
|
+
n
|
80
|
+
end
|
81
|
+
|
82
|
+
def data_centers
|
83
|
+
_get build_url('datacenters'), params = nil, json_only = false
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Public: Builds the base url
|
89
|
+
#
|
90
|
+
# Example:
|
91
|
+
#
|
92
|
+
# Returns: The base
|
93
|
+
def build_url(suffix)
|
94
|
+
"#{base_versioned_url}/catalog/#{suffix}"
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require_relative 'base'
|
3
|
+
require_relative '../model/key_value'
|
4
|
+
|
5
|
+
module Consul
|
6
|
+
module Client
|
7
|
+
class KeyValue
|
8
|
+
include Consul::Client::Base
|
9
|
+
|
10
|
+
# Public: Creates an API Endpoint
|
11
|
+
#
|
12
|
+
# data_center - The data center to utilize, defaults to bootstrap 'dc1' datat center
|
13
|
+
# api_host - The host the Consul Agent is running on. Default: 127.0.0.1
|
14
|
+
# api_port - The port the Consul Agent is listening on. Default: 8500
|
15
|
+
# version - The version of the api to use.
|
16
|
+
# logger - Logging mechanism. Must conform to Ruby Logger interface
|
17
|
+
#
|
18
|
+
def initialize(name_space = '', data_center = 'dc1', api_host = '127.0.0.1', api_port = '8500', version = 'v1', logger = Logger.new(STDOUT))
|
19
|
+
name_space = sanitize(name_space)
|
20
|
+
name_space = "#{name_space}/" unless name_space.nil? or name_space.empty?
|
21
|
+
@namespace = sanitize(name_space)
|
22
|
+
@dc = data_center
|
23
|
+
@host = api_host
|
24
|
+
@port = api_port
|
25
|
+
@logger = logger
|
26
|
+
@version = version
|
27
|
+
end
|
28
|
+
|
29
|
+
def name_space
|
30
|
+
@namespace ||= ''
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Gets the value associated with a given key.
|
34
|
+
#
|
35
|
+
# Reference: https://www.consul.io/docs/agent/http/kv.html
|
36
|
+
#
|
37
|
+
# key - Key to get value for, if recurse = true the key is treated by a prefix
|
38
|
+
# recurse - Flag to signify treating the key as a prefix
|
39
|
+
# index - Can be used to establish blocking queries by setting
|
40
|
+
# only_keys - Flag to return only keys
|
41
|
+
# separator - list only up to a given separator
|
42
|
+
#
|
43
|
+
# Returns: An array of Consul::Model::KeyValue objects, if only
|
44
|
+
def get(key,
|
45
|
+
recurse = false,
|
46
|
+
index = false,
|
47
|
+
only_keys = false,
|
48
|
+
separator = nil)
|
49
|
+
key = sanitize(key)
|
50
|
+
params = {}
|
51
|
+
params[:recurse] = nil if recurse
|
52
|
+
params[:index] = nil if index
|
53
|
+
params[:keys] = nil if only_keys
|
54
|
+
params[:separator] = separator unless separator.nil?
|
55
|
+
# begin
|
56
|
+
# resp = RestClient.get key_url(key), {:params => params}
|
57
|
+
# rescue
|
58
|
+
# # TODO need to pass more information back to the client.
|
59
|
+
# logger.warn("Unable to get value for #{key}")
|
60
|
+
# nil
|
61
|
+
# end
|
62
|
+
begin
|
63
|
+
resp = _get key_url(key), params
|
64
|
+
rescue Exception => e
|
65
|
+
logger.warn("Unable to get value for #{key} due to: #{e}")
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
return nil if resp.code == 404
|
69
|
+
json = JSON.parse(_get key_url(key), params)
|
70
|
+
return json if only_keys
|
71
|
+
json.map { |kv|
|
72
|
+
kv = Consul::Model::KeyValue.new.extend(Consul::Model::KeyValue::Representer).from_hash(kv)
|
73
|
+
kv.value = Base64.decode64(kv.value)
|
74
|
+
kv
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: Put the Key Value pair in consul.
|
79
|
+
#
|
80
|
+
# Low level put key value implementation.
|
81
|
+
#
|
82
|
+
# Reference: https://www.consul.io/docs/agent/http/kv.html
|
83
|
+
#
|
84
|
+
# key - Key
|
85
|
+
# value - Value to assign for Key
|
86
|
+
# flags - Client specified value [0, 2e64-1]
|
87
|
+
# cas - Check and Set operation
|
88
|
+
# acquire - Session id to acquire the lock with a valid session.
|
89
|
+
# release - Session id to release the lock with a valid session.
|
90
|
+
#
|
91
|
+
# Returns: True on success, False on failure
|
92
|
+
# Throws: IOError: Unable to contact Consul Agent.
|
93
|
+
def put(key,
|
94
|
+
value,
|
95
|
+
flags = nil,
|
96
|
+
cas = nil,
|
97
|
+
acquire_session = nil,
|
98
|
+
release_session = nil)
|
99
|
+
key = sanitize(key)
|
100
|
+
params = {}
|
101
|
+
params[:flags] = flags unless flags.nil?
|
102
|
+
params[:cas] = cas unless cas.nil?
|
103
|
+
params[:acquire] = acquire_session unless acquire_session.nil?
|
104
|
+
params[:release_session] = release_session unless release_session.nil?
|
105
|
+
begin
|
106
|
+
value = JSON.generate(value)
|
107
|
+
rescue JSON::GeneratorError
|
108
|
+
@logger.debug("Using non-JSON value for key #{key}")
|
109
|
+
end
|
110
|
+
_put build_url(key), value, {:params => params}
|
111
|
+
end
|
112
|
+
|
113
|
+
# Public: Delete the Key Value pair in consul.
|
114
|
+
#
|
115
|
+
# key - Key
|
116
|
+
# recurse - Delete all keys as the 'key' is a prefix for
|
117
|
+
# cas - Check and Set
|
118
|
+
def delete(key, recurse = false, cas = nil)
|
119
|
+
key = sanitize(key)
|
120
|
+
params = {}
|
121
|
+
params[:recurse] = nil if recurse
|
122
|
+
params[:cas] = cas unless cas.nil?
|
123
|
+
RestClient.delete build_url(key), {:params => params}
|
124
|
+
end
|
125
|
+
|
126
|
+
def build_url(suffix)
|
127
|
+
"#{base_versioned_url}/kv/#{suffix}"
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def sanitize(key)
|
133
|
+
key.gsub(/^\//,'').gsub(/\/$/,'')
|
134
|
+
end
|
135
|
+
|
136
|
+
def key_url(key)
|
137
|
+
build_url("#{name_space}#{key}")
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative '../model/session'
|
3
|
+
require_relative '../util/utils'
|
4
|
+
|
5
|
+
module Consul
|
6
|
+
module Client
|
7
|
+
# Consul Session Client
|
8
|
+
class Session
|
9
|
+
include Consul::Client::Base
|
10
|
+
|
11
|
+
# Public: Creates an instance of Consul::Model::Session with as many preset
|
12
|
+
# defaults as possible.
|
13
|
+
#
|
14
|
+
# name - The name of the session.
|
15
|
+
# lock_delay - Allowance window for leaders to Valid values between '0s' and '60s'
|
16
|
+
# node - The name of the node, defaults to the node the agent is running on
|
17
|
+
# checks - Health Checks to associate to this session
|
18
|
+
# behaviour - 'release' or 'destroy' Behaviour when session is invalidated.
|
19
|
+
# ttl - When provided Must be between '10s' and '3600s'
|
20
|
+
#
|
21
|
+
# Returns: Consul::Model::Session instance.
|
22
|
+
def self.for_name(name,
|
23
|
+
lock_delay = '15s',
|
24
|
+
node = nil,
|
25
|
+
checks = ['serfHealth'],
|
26
|
+
behaviour = 'release',
|
27
|
+
ttl = nil)
|
28
|
+
raise ArgumentError.new "Illegal Name: #{name}" if name.nil?
|
29
|
+
session = Consul::Model::Session.new(name: name)
|
30
|
+
session[:lock_delay] = lock_delay unless lock_delay.nil?
|
31
|
+
session[:node] = node unless node.nil?
|
32
|
+
checks = [] if checks.nil?
|
33
|
+
checks += 'serfHealth' unless checks.include? 'serfHealth'
|
34
|
+
session[:checks] = checks
|
35
|
+
behaviour = 'release' if behaviour.nil? or behaviour != 'release' or behaviour != 'destroy'
|
36
|
+
session[:behaviour] = behaviour
|
37
|
+
session[:ttl] = ttl unless ttl.nil?
|
38
|
+
session
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Creates a new Consul Session.
|
42
|
+
#
|
43
|
+
# session - Session to create.
|
44
|
+
# dc - Consul data center
|
45
|
+
#
|
46
|
+
# Returns The Session ID a
|
47
|
+
def create(session, dc = nil)
|
48
|
+
raise TypeError, 'Session must be of type Consul::Model::Session' unless session.kind_of? Consul::Model::Session
|
49
|
+
params = {}
|
50
|
+
params[:dc] = dc unless dc.nil?
|
51
|
+
success, body = _put(build_url('create'), session.extend(Consul::Model::Session::Representer).to_json, params)
|
52
|
+
return Consul::Model::Session.new.extend(Consul::Model::Service::Representer).from_json(body) if success
|
53
|
+
logger.warn("Unable to create session with #{session}")
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Destroys a given session
|
58
|
+
def destroy(session, dc = nil)
|
59
|
+
return false if session.nil?
|
60
|
+
session = extract_session_id(session)
|
61
|
+
params = nil
|
62
|
+
params = {:dc => dc} unless dc.nil?
|
63
|
+
success, _ = _put build_url("destroy/#{session}"), '', params
|
64
|
+
success
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Return the session info for a given session name.
|
68
|
+
#ccs
|
69
|
+
def info(session, dc = nil)
|
70
|
+
return nil if session.nil?
|
71
|
+
session = extract_session_id(session)
|
72
|
+
params = {}
|
73
|
+
params[:dc] = dc unless dc.nil?
|
74
|
+
resp = _get build_url("info/#{session}"), params
|
75
|
+
JSON.parse(resp).map{|session_hash| session(session_hash)} unless resp.nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Lists sessions belonging to a node
|
79
|
+
def node(session, dc = nil)
|
80
|
+
return nil if session.nil?
|
81
|
+
session = extract_session_id(session)
|
82
|
+
params = {}
|
83
|
+
params[:dc] = dc unless dc.nil?
|
84
|
+
resp = _get build_url("node/#{session}"), params
|
85
|
+
JSON.parse(resp).map{|session_hash| session(session_hash)} unless resp.nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
# Lists all active sessions
|
89
|
+
def list(dc = nil)
|
90
|
+
params = {}
|
91
|
+
params[:dc] = dc unless dc.nil?
|
92
|
+
resp = _get build_url('list'), params
|
93
|
+
JSON.parse(resp).map{|session_hash| session(session_hash)} unless resp.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
# Renews a TTL-based session
|
97
|
+
def renew(session, dc = nil)
|
98
|
+
return nil if session.nil?
|
99
|
+
session = extract_session_id(session)
|
100
|
+
params = {}
|
101
|
+
params[:dc] = dc unless dc.nil?
|
102
|
+
success, _ = _put build_url("renew/#{session.name}"), session.to_json
|
103
|
+
success
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Private: Extracts the Session
|
109
|
+
def extract_session_id(session)
|
110
|
+
raise TypeError, 'Session cannot be null' if session.nil?
|
111
|
+
session = session.id if session.kind_of? Consul::Model::Session
|
112
|
+
session = session.to_str if session.respond_to?(:to_str)
|
113
|
+
session
|
114
|
+
end
|
115
|
+
|
116
|
+
def session(obj)
|
117
|
+
if Consul::Utils.valid_json?(obj)
|
118
|
+
Consul::Model::Session.new.extend(Consul::Model::Session::Representer).from_json(obj)
|
119
|
+
elsif obj.is_a?(Hash)
|
120
|
+
Consul::Model::Session.new.extend(Consul::Model::Session::Representer).from_hash(obj)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Private: Create the url for a session endpoint.
|
125
|
+
#
|
126
|
+
# suffix - Suffix of the url endpoint
|
127
|
+
#
|
128
|
+
# Return: The URL for a reachable endpoint
|
129
|
+
def build_url(suffix)
|
130
|
+
"#{base_versioned_url}/session/#{suffix}"
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|