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