diplomatic_bag 2.2.1

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