consul-ruby-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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