consul-ruby-client 0.0.2

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.
@@ -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