diplomatic_bag 2.2.1
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 +7 -0
- data/LICENSE +24 -0
- data/README.md +356 -0
- data/features/configuration.feature +9 -0
- data/features/step_definitions/setup_diplomat.rb +24 -0
- data/features/step_definitions/test_key_value.rb +9 -0
- data/lib/diplomat/acl.rb +81 -0
- data/lib/diplomat/agent.rb +42 -0
- data/lib/diplomat/check.rb +109 -0
- data/lib/diplomat/configuration.rb +28 -0
- data/lib/diplomat/datacenter.rb +21 -0
- data/lib/diplomat/error.rb +15 -0
- data/lib/diplomat/event.rb +166 -0
- data/lib/diplomat/health.rb +81 -0
- data/lib/diplomat/kv.rb +263 -0
- data/lib/diplomat/lock.rb +54 -0
- data/lib/diplomat/maintenance.rb +41 -0
- data/lib/diplomat/members.rb +14 -0
- data/lib/diplomat/node.rb +43 -0
- data/lib/diplomat/nodes.rb +22 -0
- data/lib/diplomat/query.rb +87 -0
- data/lib/diplomat/rest_client.rb +278 -0
- data/lib/diplomat/service.rb +111 -0
- data/lib/diplomat/session.rb +75 -0
- data/lib/diplomat/status.rb +22 -0
- data/lib/diplomat/version.rb +3 -0
- data/lib/diplomat.rb +62 -0
- data/lib/diplomatic_bag/datacenters.rb +11 -0
- data/lib/diplomatic_bag/info.rb +32 -0
- data/lib/diplomatic_bag/nodes.rb +28 -0
- data/lib/diplomatic_bag/service.rb +20 -0
- data/lib/diplomatic_bag/services.rb +38 -0
- data/lib/diplomatic_bag.rb +7 -0
- metadata +89 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Diplomat
|
|
2
|
+
# Methods for configuring Diplomat
|
|
3
|
+
class Configuration
|
|
4
|
+
attr_reader :middleware
|
|
5
|
+
attr_accessor :url, :acl_token, :options
|
|
6
|
+
|
|
7
|
+
# Override defaults for configuration
|
|
8
|
+
# @param url [String] consul's connection URL
|
|
9
|
+
# @param acl_token [String] a connection token used when making requests to consul
|
|
10
|
+
# @param options [Hash] extra options to configure Faraday::Connection
|
|
11
|
+
def initialize(url = 'http://localhost:8500', acl_token = nil, options = {})
|
|
12
|
+
@middleware = []
|
|
13
|
+
@url = url
|
|
14
|
+
@acl_token = acl_token
|
|
15
|
+
@options = options
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Define a middleware for Faraday
|
|
19
|
+
# @param middleware [Class] Faraday Middleware class
|
|
20
|
+
def middleware=(middleware)
|
|
21
|
+
if middleware.is_a? Array
|
|
22
|
+
@middleware = middleware
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
@middleware = [middleware]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Diplomat
|
|
2
|
+
# Methods for interacting with the Consul dataceneter API endpoint
|
|
3
|
+
class Datacenter < Diplomat::RestClient
|
|
4
|
+
@access_methods = [:get]
|
|
5
|
+
|
|
6
|
+
# Get an array of all avaliable datacenters accessible by the local consul agent
|
|
7
|
+
# @param meta [Hash] output structure containing header information about the request (index)
|
|
8
|
+
# @param options [Hash] options parameter hash
|
|
9
|
+
# @return [OpenStruct] all datacenters avaliable to this consul agent
|
|
10
|
+
def get(meta = nil, options = {})
|
|
11
|
+
ret = send_get_request(@conn, ['/v1/catalog/datacenters'], options)
|
|
12
|
+
|
|
13
|
+
if meta && ret.headers
|
|
14
|
+
meta[:index] = ret.headers['x-consul-index'] if ret.headers['x-consul-index']
|
|
15
|
+
meta[:knownleader] = ret.headers['x-consul-knownleader'] if ret.headers['x-consul-knownleader']
|
|
16
|
+
meta[:lastcontact] = ret.headers['x-consul-lastcontact'] if ret.headers['x-consul-lastcontact']
|
|
17
|
+
end
|
|
18
|
+
JSON.parse(ret.body)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Diplomat
|
|
2
|
+
class KeyNotFound < StandardError; end
|
|
3
|
+
class PathNotFound < StandardError; end
|
|
4
|
+
class KeyAlreadyExists < StandardError; end
|
|
5
|
+
class AclNotFound < StandardError; end
|
|
6
|
+
class AclAlreadyExists < StandardError; end
|
|
7
|
+
class EventNotFound < StandardError; end
|
|
8
|
+
class EventAlreadyExists < StandardError; end
|
|
9
|
+
class QueryNotFound < StandardError; end
|
|
10
|
+
class QueryAlreadyExists < StandardError; end
|
|
11
|
+
class UnknownStatus < StandardError; end
|
|
12
|
+
class IdParameterRequired < StandardError; end
|
|
13
|
+
class InvalidTransaction < StandardError; end
|
|
14
|
+
class DeprecatedArgument < StandardError; end
|
|
15
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
module Diplomat
|
|
2
|
+
# Methods for interacting with the Consul event API endpoint
|
|
3
|
+
class Event < Diplomat::RestClient
|
|
4
|
+
@access_methods = %i[fire get_all get]
|
|
5
|
+
|
|
6
|
+
# Send an event
|
|
7
|
+
# @param name [String] the event name
|
|
8
|
+
# @param value [String] the payload of the event
|
|
9
|
+
# @param service [String] the target service name
|
|
10
|
+
# @param node [String] the target node name
|
|
11
|
+
# @param tag [String] the target tag name, must only be used with service
|
|
12
|
+
# @param dc [String] the dc to target
|
|
13
|
+
# @param options [Hash] options parameter hash
|
|
14
|
+
# @return [nil]
|
|
15
|
+
# rubocop:disable Metrics/ParameterLists
|
|
16
|
+
def fire(name, value = nil, service = nil, node = nil, tag = nil, dc = nil, options = {})
|
|
17
|
+
custom_params = []
|
|
18
|
+
custom_params << use_named_parameter('service', service) if service
|
|
19
|
+
custom_params << use_named_parameter('node', node) if node
|
|
20
|
+
custom_params << use_named_parameter('tag', tag) if tag
|
|
21
|
+
custom_params << use_named_parameter('dc', dc) if dc
|
|
22
|
+
|
|
23
|
+
send_put_request(@conn, ["/v1/event/fire/#{name}"], options, value, custom_params)
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
# rubocop:enable Metrics/ParameterLists
|
|
27
|
+
|
|
28
|
+
# Get the list of events matching name
|
|
29
|
+
# @param name [String] the name of the event (regex)
|
|
30
|
+
# @param not_found [Symbol] behaviour if there are no events matching name;
|
|
31
|
+
# :reject with exception, :return degenerate value, or :wait for a non-empty list
|
|
32
|
+
# @param found [Symbol] behaviour if there are already events matching name;
|
|
33
|
+
# :reject with exception, :return its current value, or :wait for its next value
|
|
34
|
+
# @return [Array[hash]] The list of { :name, :payload } hashes
|
|
35
|
+
# @param options [Hash] options parameter hash
|
|
36
|
+
# @note
|
|
37
|
+
# Events are sent via the gossip protocol; there is no guarantee of delivery
|
|
38
|
+
# success or order, but the local agent will store up to 256 events that do
|
|
39
|
+
# arrive. This method lists those events.
|
|
40
|
+
# It has the same semantics as Kv::get, except the value returned is a list
|
|
41
|
+
# i.e. the current value is all events up until now, the next value is the
|
|
42
|
+
# current list plus the next event to arrive.
|
|
43
|
+
# To get a specific event in the sequence, @see #get
|
|
44
|
+
# When trying to get a list of events matching a name, there are two possibilities:
|
|
45
|
+
# - The list doesn't (yet) exist / is empty
|
|
46
|
+
# - The list exists / is non-empty
|
|
47
|
+
# The combination of not_found and found behaviour gives maximum possible
|
|
48
|
+
# flexibility. For X: reject, R: return, W: wait
|
|
49
|
+
# - X X - meaningless; never return a value
|
|
50
|
+
# - X R - "normal" non-blocking get operation. Default
|
|
51
|
+
# - X W - get the next value only (must have a current value)
|
|
52
|
+
# - R X - meaningless; never return a meaningful value
|
|
53
|
+
# - R R - "safe" non-blocking, non-throwing get-or-default operation
|
|
54
|
+
# - R W - get the next value or a default
|
|
55
|
+
# - W X - get the first value only (must not have a current value)
|
|
56
|
+
# - W R - get the first or current value; always return something, but
|
|
57
|
+
# block only when necessary
|
|
58
|
+
# - W W - get the first or next value; wait until there is an update
|
|
59
|
+
def get_all(name = nil, not_found = :reject, found = :return, options = {})
|
|
60
|
+
# Event list never returns 404 or blocks, but may return an empty list
|
|
61
|
+
@raw = send_get_request(@conn, ['/v1/event/list'], options, use_named_parameter('name', name))
|
|
62
|
+
if JSON.parse(@raw.body).count.zero?
|
|
63
|
+
case not_found
|
|
64
|
+
when :reject
|
|
65
|
+
raise Diplomat::EventNotFound, name
|
|
66
|
+
when :return
|
|
67
|
+
return []
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
case found
|
|
71
|
+
when :reject
|
|
72
|
+
raise Diplomat::EventAlreadyExists, name
|
|
73
|
+
when :return
|
|
74
|
+
@raw = parse_body
|
|
75
|
+
return return_payload
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@raw = wait_for_next_event(['/v1/event/list'], options, use_named_parameter('name', name))
|
|
80
|
+
@raw = parse_body
|
|
81
|
+
return_payload
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get a specific event in the sequence matching name
|
|
85
|
+
# @param name [String] the name of the event (regex)
|
|
86
|
+
# @param token [String|Symbol] the ordinate of the event in the sequence;
|
|
87
|
+
# String are tokens returned by previous calls to this function
|
|
88
|
+
# Symbols are the special tokens :first, :last, and :next
|
|
89
|
+
# @param not_found [Symbol] behaviour if there is no matching event;
|
|
90
|
+
# :reject with exception, :return degenerate value, or :wait for event
|
|
91
|
+
# @param found [Symbol] behaviour if there is a matching event;
|
|
92
|
+
# :reject with exception, or :return its current value
|
|
93
|
+
# @return [hash] A hash with keys :value and :token;
|
|
94
|
+
# :value is a further hash of the :name and :payload of the event,
|
|
95
|
+
# :token is the event's ordinate in the sequence and can be passed to future calls to get the subsequent event
|
|
96
|
+
# @param options [Hash] options parameter hash
|
|
97
|
+
# @note
|
|
98
|
+
# Whereas the consul API for events returns all past events that match
|
|
99
|
+
# name, this method allows retrieval of individual events from that
|
|
100
|
+
# sequence. However, because consul's API isn't conducive to this, we can
|
|
101
|
+
# offer first, last, next (last + 1) events, or arbitrary events in the
|
|
102
|
+
# middle, though these can only be identified relative to the preceding
|
|
103
|
+
# event. However, this is ideal for iterating through the sequence of
|
|
104
|
+
# events (while being sure that none are missed).
|
|
105
|
+
# rubocop:disable PerceivedComplexity
|
|
106
|
+
def get(name = nil, token = :last, not_found = :wait, found = :return, options = {})
|
|
107
|
+
@raw = send_get_request(@conn, ['/v1/event/list'], options, use_named_parameter('name', name))
|
|
108
|
+
body = JSON.parse(@raw.body)
|
|
109
|
+
# TODO: deal with unknown symbols, invalid indices (find_index will return nil)
|
|
110
|
+
idx = case token
|
|
111
|
+
when :first then 0
|
|
112
|
+
when :last then body.length - 1
|
|
113
|
+
when :next then body.length
|
|
114
|
+
else body.find_index { |e| e['ID'] == token } + 1
|
|
115
|
+
end
|
|
116
|
+
if JSON.parse(@raw.body).count.zero? || idx == body.length
|
|
117
|
+
case not_found
|
|
118
|
+
when :reject
|
|
119
|
+
raise Diplomat::EventNotFound, name
|
|
120
|
+
when :return
|
|
121
|
+
event_name = ''
|
|
122
|
+
event_payload = ''
|
|
123
|
+
event_token = :last
|
|
124
|
+
when :wait
|
|
125
|
+
@raw = wait_for_next_event(['/v1/event/list'], options, use_named_parameter('name', name))
|
|
126
|
+
@raw = parse_body
|
|
127
|
+
# If it's possible for two events to arrive at once,
|
|
128
|
+
# this needs to #find again:
|
|
129
|
+
event = @raw.last
|
|
130
|
+
event_name = event['Name']
|
|
131
|
+
event_payload = Base64.decode64(event['Payload'])
|
|
132
|
+
event_token = event['ID']
|
|
133
|
+
end
|
|
134
|
+
else
|
|
135
|
+
case found
|
|
136
|
+
when :reject
|
|
137
|
+
raise Diplomat::EventAlreadyExits, name
|
|
138
|
+
when :return
|
|
139
|
+
event = body[idx]
|
|
140
|
+
event_name = event['Name']
|
|
141
|
+
event_payload = event['Payload'].nil? ? nil : Base64.decode64(event['Payload'])
|
|
142
|
+
event_token = event['ID']
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
{
|
|
147
|
+
value: { name: event_name, payload: event_payload },
|
|
148
|
+
token: event_token
|
|
149
|
+
}
|
|
150
|
+
end
|
|
151
|
+
# rubocop:enable PerceivedComplexity
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def wait_for_next_event(url, options = {}, param = nil)
|
|
156
|
+
if options.nil?
|
|
157
|
+
options = { timeout: 86_400 }
|
|
158
|
+
else
|
|
159
|
+
options[:timeout] = 86_400
|
|
160
|
+
end
|
|
161
|
+
index = @raw.headers['x-consul-index']
|
|
162
|
+
param += use_named_parameter('index', index)
|
|
163
|
+
send_get_request(@conn, url, options, param)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module Diplomat
|
|
2
|
+
# Methods for interacting with the Consul health API endpoint
|
|
3
|
+
class Health < Diplomat::RestClient
|
|
4
|
+
@access_methods = %i[node checks service state
|
|
5
|
+
any passing warning critical]
|
|
6
|
+
|
|
7
|
+
# Get node health
|
|
8
|
+
# @param n [String] the node
|
|
9
|
+
# @param options [Hash] :dc string for dc specific query
|
|
10
|
+
# @return [OpenStruct] all data associated with the node
|
|
11
|
+
def node(n, options = {})
|
|
12
|
+
custom_params = []
|
|
13
|
+
custom_params << use_named_parameter('dc', options[:dc]) if options[:dc]
|
|
14
|
+
|
|
15
|
+
ret = send_get_request(@conn, ["/v1/health/node/#{n}"], options, custom_params)
|
|
16
|
+
JSON.parse(ret.body).map { |node| OpenStruct.new node }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Get service checks
|
|
20
|
+
# @param s [String] the service
|
|
21
|
+
# @param options [Hash] :dc string for dc specific query
|
|
22
|
+
# @return [OpenStruct] all data associated with the node
|
|
23
|
+
def checks(s, options = {})
|
|
24
|
+
custom_params = []
|
|
25
|
+
custom_params << use_named_parameter('dc', options[:dc]) if options[:dc]
|
|
26
|
+
|
|
27
|
+
ret = send_get_request(@conn, ["/v1/health/checks/#{s}"], options, custom_params)
|
|
28
|
+
JSON.parse(ret.body).map { |check| OpenStruct.new check }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get service health
|
|
32
|
+
# @param s [String] the service
|
|
33
|
+
# @param options [Hash] options parameter hash
|
|
34
|
+
# @return [OpenStruct] all data associated with the node
|
|
35
|
+
# rubocop:disable PerceivedComplexity
|
|
36
|
+
def service(s, options = {})
|
|
37
|
+
custom_params = []
|
|
38
|
+
custom_params << use_named_parameter('dc', options[:dc]) if options[:dc]
|
|
39
|
+
custom_params << ['passing'] if options[:passing]
|
|
40
|
+
custom_params << use_named_parameter('tag', options[:tag]) if options[:tag]
|
|
41
|
+
custom_params << use_named_parameter('near', options[:near]) if options[:near]
|
|
42
|
+
|
|
43
|
+
ret = send_get_request(@conn, ["/v1/health/service/#{s}"], options, custom_params)
|
|
44
|
+
JSON.parse(ret.body).map { |service| OpenStruct.new service }
|
|
45
|
+
end
|
|
46
|
+
# rubocop:enable PerceivedComplexity
|
|
47
|
+
|
|
48
|
+
# Get service health
|
|
49
|
+
# @param s [String] the state ("any", "passing", "warning", or "critical")
|
|
50
|
+
# @param options [Hash] :dc string for dc specific query
|
|
51
|
+
# @return [OpenStruct] all data associated with the node
|
|
52
|
+
def state(s, options = {})
|
|
53
|
+
custom_params = []
|
|
54
|
+
custom_params << use_named_parameter('dc', options[:dc]) if options[:dc]
|
|
55
|
+
custom_params << use_named_parameter('near', options[:near]) if options[:near]
|
|
56
|
+
|
|
57
|
+
ret = send_get_request(@conn, ["/v1/health/state/#{s}"], options, custom_params)
|
|
58
|
+
JSON.parse(ret.body).map { |status| OpenStruct.new status }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Convenience method to get services in any state
|
|
62
|
+
def any
|
|
63
|
+
state('any')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Convenience method to get services in passing state
|
|
67
|
+
def passing
|
|
68
|
+
state('passing')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Convenience method to get services in warning state
|
|
72
|
+
def warning
|
|
73
|
+
state('warning')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Convenience method to get services in critical state
|
|
77
|
+
def critical
|
|
78
|
+
state('critical')
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/diplomat/kv.rb
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
module Diplomat
|
|
2
|
+
# Methods for interacting with the Consul KV API endpoint
|
|
3
|
+
class Kv < Diplomat::RestClient
|
|
4
|
+
@access_methods = %i[get put delete txn]
|
|
5
|
+
attr_reader :key, :value, :raw
|
|
6
|
+
|
|
7
|
+
# Get a value by its key, potentially blocking for the first or next value
|
|
8
|
+
# @param key [String] the key
|
|
9
|
+
# @param options [Hash] the query params
|
|
10
|
+
# @option options [Boolean] :recurse If to make recursive get or not
|
|
11
|
+
# @option options [String] :consistency The read consistency type
|
|
12
|
+
# @option options [String] :dc Target datacenter
|
|
13
|
+
# @option options [Boolean] :keys Only return key names.
|
|
14
|
+
# @option options [Boolean] :modify_index Only return ModifyIndex value.
|
|
15
|
+
# @option options [Boolean] :session Only return Session value.
|
|
16
|
+
# @option options [Boolean] :decode_values Return consul response with decoded values.
|
|
17
|
+
# @option options [String] :separator List only up to a given separator.
|
|
18
|
+
# Only applies when combined with :keys option.
|
|
19
|
+
# @option options [Boolean] :nil_values If to return keys/dirs with nil values
|
|
20
|
+
# @option options [Boolean] :convert_to_hash Take the data returned from consul and build a hash
|
|
21
|
+
# @option options [Callable] :transformation funnction to invoke on keys values
|
|
22
|
+
# @param not_found [Symbol] behaviour if the key doesn't exist;
|
|
23
|
+
# :reject with exception, :return degenerate value, or :wait for it to appear
|
|
24
|
+
# @param found [Symbol] behaviour if the key does exist;
|
|
25
|
+
# :reject with exception, :return its current value, or :wait for its next value
|
|
26
|
+
# @return [String] The base64-decoded value associated with the key
|
|
27
|
+
# @note
|
|
28
|
+
# When trying to access a key, there are two possibilites:
|
|
29
|
+
# - The key doesn't (yet) exist
|
|
30
|
+
# - The key exists. This may be its first value, there is no way to tell
|
|
31
|
+
# The combination of not_found and found behaviour gives maximum possible
|
|
32
|
+
# flexibility. For X: reject, R: return, W: wait
|
|
33
|
+
# - X X - meaningless; never return a value
|
|
34
|
+
# - X R - "normal" non-blocking get operation. Default
|
|
35
|
+
# - X W - get the next value only (must have a current value)
|
|
36
|
+
# - R X - meaningless; never return a meaningful value
|
|
37
|
+
# - R R - "safe" non-blocking, non-throwing get-or-default operation
|
|
38
|
+
# - R W - get the next value or a default
|
|
39
|
+
# - W X - get the first value only (must not have a current value)
|
|
40
|
+
# - W R - get the first or current value; always return something, but
|
|
41
|
+
# block only when necessary
|
|
42
|
+
# - W W - get the first or next value; wait until there is an update
|
|
43
|
+
# rubocop:disable PerceivedComplexity, MethodLength, LineLength, CyclomaticComplexity
|
|
44
|
+
def get(key, options = {}, not_found = :reject, found = :return)
|
|
45
|
+
key_subst = if key.start_with? '/'
|
|
46
|
+
key[1..-1]
|
|
47
|
+
else
|
|
48
|
+
key.freeze
|
|
49
|
+
end
|
|
50
|
+
@key = key_subst
|
|
51
|
+
@options = options
|
|
52
|
+
custom_params = []
|
|
53
|
+
custom_params << recurse_get(@options)
|
|
54
|
+
custom_params << use_consistency(options)
|
|
55
|
+
custom_params << dc(@options)
|
|
56
|
+
custom_params << keys(@options)
|
|
57
|
+
custom_params << separator(@options)
|
|
58
|
+
|
|
59
|
+
return_nil_values = @options && @options[:nil_values]
|
|
60
|
+
transformation = @options && @options[:transformation] && @options[:transformation].methods.find_index(:call) ? @options[:transformation] : nil
|
|
61
|
+
|
|
62
|
+
raw = send_get_request(@conn_no_err, ["/v1/kv/#{@key}"], options, custom_params)
|
|
63
|
+
if raw.status == 404
|
|
64
|
+
case not_found
|
|
65
|
+
when :reject
|
|
66
|
+
raise Diplomat::KeyNotFound, key
|
|
67
|
+
when :return
|
|
68
|
+
return @value = ''
|
|
69
|
+
when :wait
|
|
70
|
+
index = raw.headers['x-consul-index']
|
|
71
|
+
end
|
|
72
|
+
elsif raw.status == 200
|
|
73
|
+
case found
|
|
74
|
+
when :reject
|
|
75
|
+
raise Diplomat::KeyAlreadyExists, key
|
|
76
|
+
when :return
|
|
77
|
+
@raw = raw
|
|
78
|
+
@raw = parse_body
|
|
79
|
+
return @raw.first['ModifyIndex'] if @options && @options[:modify_index]
|
|
80
|
+
return @raw.first['Session'] if @options && @options[:session]
|
|
81
|
+
return decode_values if @options && @options[:decode_values]
|
|
82
|
+
return convert_to_hash(return_value(return_nil_values, transformation, true)) if @options && @options[:convert_to_hash]
|
|
83
|
+
|
|
84
|
+
return return_value(return_nil_values, transformation)
|
|
85
|
+
when :wait
|
|
86
|
+
index = raw.headers['x-consul-index']
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
raise Diplomat::UnknownStatus, "status #{raw.status}: #{raw.body}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Wait for first/next value
|
|
93
|
+
custom_params << use_named_parameter('index', index)
|
|
94
|
+
if options.nil?
|
|
95
|
+
options = { timeout: 86_400 }
|
|
96
|
+
else
|
|
97
|
+
options[:timeout] = 86_400
|
|
98
|
+
end
|
|
99
|
+
@raw = send_get_request(@conn, ["/v1/kv/#{@key}"], options, custom_params)
|
|
100
|
+
@raw = parse_body
|
|
101
|
+
return_value(return_nil_values, transformation)
|
|
102
|
+
end
|
|
103
|
+
# rubocop:enable PerceivedComplexity, LineLength, MethodLength, CyclomaticComplexity
|
|
104
|
+
|
|
105
|
+
# Associate a value with a key
|
|
106
|
+
# @param key [String] the key
|
|
107
|
+
# @param value [String] the value
|
|
108
|
+
# @param options [Hash] the query params
|
|
109
|
+
# @option options [Integer] :cas The modify index
|
|
110
|
+
# @option options [String] :dc Target datacenter
|
|
111
|
+
# @option options [String] :acquire Session to attach to key
|
|
112
|
+
# @return [Bool] Success or failure of the write (can fail in c-a-s mode)
|
|
113
|
+
def put(key, value, options = {})
|
|
114
|
+
@options = options
|
|
115
|
+
custom_params = []
|
|
116
|
+
custom_params << use_cas(@options)
|
|
117
|
+
custom_params << dc(@options)
|
|
118
|
+
custom_params << acquire(@options)
|
|
119
|
+
@raw = send_put_request(@conn, ["/v1/kv/#{key}"], options, value, custom_params,
|
|
120
|
+
'application/x-www-form-urlencoded')
|
|
121
|
+
if @raw.body.chomp == 'true'
|
|
122
|
+
@key = key
|
|
123
|
+
@value = value
|
|
124
|
+
end
|
|
125
|
+
@raw.body.chomp == 'true'
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Delete a value by its key
|
|
129
|
+
# @param key [String] the key
|
|
130
|
+
# @param options [Hash] the query params
|
|
131
|
+
# @option options [String] :dc Target datacenter
|
|
132
|
+
# @option options [Boolean] :recurse If to make recursive get or not
|
|
133
|
+
# @return [OpenStruct]
|
|
134
|
+
def delete(key, options = {})
|
|
135
|
+
@key = key
|
|
136
|
+
@options = options
|
|
137
|
+
custom_params = []
|
|
138
|
+
custom_params << recurse_get(@options)
|
|
139
|
+
custom_params << dc(@options)
|
|
140
|
+
@raw = send_delete_request(@conn, ["/v1/kv/#{@key}"], options, custom_params)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Perform a key/value store transaction.
|
|
144
|
+
#
|
|
145
|
+
# @since 1.3.0
|
|
146
|
+
# @see https://www.consul.io/docs/agent/http/kv.html#txn Transaction key/value store API documentation
|
|
147
|
+
# @example Valid key/value store transaction format
|
|
148
|
+
# [
|
|
149
|
+
# {
|
|
150
|
+
# 'KV' => {
|
|
151
|
+
# 'Verb' => 'get',
|
|
152
|
+
# 'Key' => 'hello/world'
|
|
153
|
+
# }
|
|
154
|
+
# }
|
|
155
|
+
# ]
|
|
156
|
+
# @raise [Diplomat::InvalidTransaction] if transaction format is invalid
|
|
157
|
+
# @param value [Array] an array of transaction hashes
|
|
158
|
+
# @param [Hash] options transaction params
|
|
159
|
+
# @option options [Boolean] :decode_values of any GET requests, default: true
|
|
160
|
+
# @option options [String] :dc Target datacenter
|
|
161
|
+
# @option options [String] :consistency the accepted staleness level of the transaction.
|
|
162
|
+
# Can be 'stale' or 'consistent'
|
|
163
|
+
# @return [OpenStruct] result of the transaction
|
|
164
|
+
def txn(value, options = {})
|
|
165
|
+
# Verify the given value for the transaction
|
|
166
|
+
transaction_verification(value)
|
|
167
|
+
# Will return 409 if transaction was rolled back
|
|
168
|
+
custom_params = []
|
|
169
|
+
custom_params << dc(options)
|
|
170
|
+
custom_params << transaction_consistency(options)
|
|
171
|
+
raw = send_put_request(@conn_no_err, ['/v1/txn'], options, value, custom_params)
|
|
172
|
+
transaction_return JSON.parse(raw.body), options
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def recurse_get(options)
|
|
178
|
+
options[:recurse] ? ['recurse'] : []
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def dc(options)
|
|
182
|
+
options[:dc] ? use_named_parameter('dc', options[:dc]) : []
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def acquire(options)
|
|
186
|
+
options[:acquire] ? use_named_parameter('acquire', options[:acquire]) : []
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def keys(options)
|
|
190
|
+
options[:keys] ? ['keys'] : []
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def separator(options)
|
|
194
|
+
options[:separator] ? use_named_parameter('separator', options[:separator]) : []
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def transaction_consistency(options)
|
|
198
|
+
return [] unless options
|
|
199
|
+
|
|
200
|
+
if options[:consistency] && options[:consistency] == 'stale'
|
|
201
|
+
['stale']
|
|
202
|
+
elsif options[:consistency] && options[:consistency] == 'consistent'
|
|
203
|
+
['consistent']
|
|
204
|
+
else
|
|
205
|
+
[]
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def transaction_verification(transaction)
|
|
210
|
+
raise Diplomat::InvalidTransaction unless transaction.is_a?(Array)
|
|
211
|
+
|
|
212
|
+
transaction.each do |req|
|
|
213
|
+
raise Diplomat::InvalidTransaction unless transaction_type_verification(req)
|
|
214
|
+
raise Diplomat::InvalidTransaction unless transaction_verb_verification(req['KV'])
|
|
215
|
+
end
|
|
216
|
+
# Encode all value transacations if all checks pass
|
|
217
|
+
encode_transaction(transaction)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def transaction_type_verification(txn)
|
|
221
|
+
txn.is_a?(Hash) && txn.keys == %w[KV]
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def transaction_verb_verification(txn)
|
|
225
|
+
transaction_verb = txn['Verb']
|
|
226
|
+
raise Diplomat::InvalidTransaction unless valid_transaction_verbs.include? transaction_verb
|
|
227
|
+
|
|
228
|
+
test_requirements = valid_transaction_verbs[transaction_verb] - txn.keys
|
|
229
|
+
test_requirements.empty?
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def encode_transaction(transaction)
|
|
233
|
+
transaction.each do |txn|
|
|
234
|
+
next unless valid_value_transactions.include? txn['KV']['Verb']
|
|
235
|
+
|
|
236
|
+
value = txn['KV']['Value']
|
|
237
|
+
txn['KV']['Value'] = Base64.encode64(value).chomp
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def transaction_return(raw_return, options)
|
|
242
|
+
decoded_return =
|
|
243
|
+
options[:decode_values] == false ? raw_return : decode_transaction(raw_return)
|
|
244
|
+
OpenStruct.new decoded_return
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def decode_transaction(transaction)
|
|
248
|
+
return transaction if transaction['Results'].nil? || transaction['Results'].empty?
|
|
249
|
+
|
|
250
|
+
transaction.tap do |txn|
|
|
251
|
+
txn['Results'].each do |resp|
|
|
252
|
+
next unless resp['KV']['Value']
|
|
253
|
+
|
|
254
|
+
begin
|
|
255
|
+
resp['KV']['Value'] = Base64.decode64(resp['KV']['Value'])
|
|
256
|
+
rescue StandardError
|
|
257
|
+
nil
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Diplomat
|
|
2
|
+
# Methods for interacting with the Consul lock API endpoint
|
|
3
|
+
class Lock < Diplomat::RestClient
|
|
4
|
+
@access_methods = %i[acquire wait_to_acquire release]
|
|
5
|
+
|
|
6
|
+
# Acquire a lock
|
|
7
|
+
# @param key [String] the key
|
|
8
|
+
# @param session [String] the session, generated from Diplomat::Session.create
|
|
9
|
+
# @param value [String] the value for the key
|
|
10
|
+
# @param options [Hash] options parameter hash
|
|
11
|
+
# @return [Boolean] If the lock was acquired
|
|
12
|
+
def acquire(key, session, value = nil, options = {})
|
|
13
|
+
custom_params = []
|
|
14
|
+
custom_params << use_named_parameter('acquire', session)
|
|
15
|
+
custom_params << use_named_parameter('dc', options[:dc]) if options[:dc]
|
|
16
|
+
custom_params << use_named_parameter('flags', options[:flags]) if options && options[:flags]
|
|
17
|
+
data = value unless value.nil?
|
|
18
|
+
raw = send_put_request(@conn, ["/v1/kv/#{key}"], options, data, custom_params)
|
|
19
|
+
raw.body.chomp == 'true'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# wait to aquire a lock
|
|
23
|
+
# @param key [String] the key
|
|
24
|
+
# @param session [String] the session, generated from Diplomat::Session.create
|
|
25
|
+
# @param value [String] the value for the key
|
|
26
|
+
# @param check_interval [Integer] number of seconds to wait between retries
|
|
27
|
+
# @param options [Hash] options parameter hash
|
|
28
|
+
# @return [Boolean] If the lock was acquired
|
|
29
|
+
def wait_to_acquire(key, session, value = nil, check_interval = 10, options = {})
|
|
30
|
+
acquired = false
|
|
31
|
+
until acquired
|
|
32
|
+
acquired = acquire(key, session, value, options)
|
|
33
|
+
sleep(check_interval) unless acquired
|
|
34
|
+
return true if acquired
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Release a lock
|
|
39
|
+
# @param key [String] the key
|
|
40
|
+
# @param session [String] the session, generated from Diplomat::Session.create
|
|
41
|
+
# @param options [Hash] :dc string for dc specific query
|
|
42
|
+
# @return [nil]
|
|
43
|
+
# rubocop:disable AbcSize
|
|
44
|
+
def release(key, session, options = {})
|
|
45
|
+
custom_params = []
|
|
46
|
+
custom_params << use_named_parameter('release', session)
|
|
47
|
+
custom_params << use_named_parameter('dc', options[:dc]) if options[:dc]
|
|
48
|
+
custom_params << use_named_parameter('flags', options[:flags]) if options && options[:flags]
|
|
49
|
+
raw = send_put_request(@conn, ["/v1/kv/#{key}"], options, nil, custom_params)
|
|
50
|
+
raw.body
|
|
51
|
+
end
|
|
52
|
+
# rubocop:enable AbcSize
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Diplomat
|
|
2
|
+
# Methods to interact with the Consul maintenance API endpoint
|
|
3
|
+
class Maintenance < Diplomat::RestClient
|
|
4
|
+
@access_methods = %i[enabled enable]
|
|
5
|
+
|
|
6
|
+
# Get the maintenance state of a host
|
|
7
|
+
# @param n [String] the node
|
|
8
|
+
# @param options [Hash] :dc string for dc specific query
|
|
9
|
+
# @return [Hash] { :enabled => true, :reason => 'foo' }
|
|
10
|
+
def enabled(n, options = {})
|
|
11
|
+
health = Diplomat::Health.new(@conn)
|
|
12
|
+
result = health.node(n, options)
|
|
13
|
+
.select { |check| check['CheckID'] == '_node_maintenance' }
|
|
14
|
+
|
|
15
|
+
if result.empty?
|
|
16
|
+
{ enabled: false, reason: nil }
|
|
17
|
+
else
|
|
18
|
+
{ enabled: true, reason: result.first['Notes'] }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Enable or disable maintenance mode. This endpoint only works
|
|
23
|
+
# on the local agent.
|
|
24
|
+
# @param enable enable or disable maintenance mode
|
|
25
|
+
# @param reason [String] the reason for enabling maintenance mode
|
|
26
|
+
# @param options [Hash] :dc string for dc specific query
|
|
27
|
+
# @return true if call is successful
|
|
28
|
+
def enable(enable = true, reason = nil, options = {})
|
|
29
|
+
custom_params = []
|
|
30
|
+
custom_params << use_named_parameter('enable', enable.to_s)
|
|
31
|
+
custom_params << use_named_parameter('reason', reason) if reason
|
|
32
|
+
custom_params << use_named_parameter('dc', options[:dc]) if options[:dc]
|
|
33
|
+
raw = send_put_request(@conn, ['/v1/agent/maintenance'], options, nil, custom_params)
|
|
34
|
+
|
|
35
|
+
return_status = raw.status == 200
|
|
36
|
+
raise Diplomat::UnknownStatus, "status #{raw.status}: #{raw.body}" unless return_status
|
|
37
|
+
|
|
38
|
+
return_status
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|