diplomat-blsk 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
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 = nil)
12
+ url = ["/v1/health/node/#{n}"]
13
+ url << use_named_parameter('dc', options[:dc]) if options && options[:dc]
14
+
15
+ # If the request fails, it's probably due to a bad path
16
+ # so return a PathNotFound error.
17
+ ret = @conn.get concat_url url
18
+ JSON.parse(ret.body).map { |node| OpenStruct.new node }
19
+ rescue Faraday::ClientError
20
+ raise Diplomat::PathNotFound
21
+ end
22
+
23
+ # Get service checks
24
+ # @param s [String] the service
25
+ # @param options [Hash] :dc string for dc specific query
26
+ # @return [OpenStruct] all data associated with the node
27
+ def checks(s, options = nil)
28
+ url = ["/v1/health/checks/#{s}"]
29
+ url << use_named_parameter('dc', options[:dc]) if options && options[:dc]
30
+
31
+ # If the request fails, it's probably due to a bad path
32
+ # so return a PathNotFound error.
33
+ ret = @conn.get concat_url url
34
+ JSON.parse(ret.body).map { |check| OpenStruct.new check }
35
+ rescue Faraday::ClientError
36
+ raise Diplomat::PathNotFound
37
+ end
38
+
39
+ # Get service health
40
+ # @param s [String] the service
41
+ # @param options [Hash] :dc string for dc specific query
42
+ # @param options [Hash] :passing boolean to return only checks in passing state
43
+ # @param options [Hash] :tag string for specific tag
44
+ # @return [OpenStruct] all data associated with the node
45
+ # rubocop:disable PerceivedComplexity, CyclomaticComplexity, AbcSize
46
+ def service(s, options = nil)
47
+ url = ["/v1/health/service/#{s}"]
48
+ url << use_named_parameter('dc', options[:dc]) if options && options[:dc]
49
+ url << 'passing' if options && options[:passing]
50
+ url << use_named_parameter('tag', options[:tag]) if options && options[:tag]
51
+ url << use_named_parameter('near', options[:near]) if options && options[:near]
52
+
53
+ # If the request fails, it's probably due to a bad path
54
+ # so return a PathNotFound error.
55
+ ret = @conn.get concat_url url
56
+ JSON.parse(ret.body).map { |service| OpenStruct.new service }
57
+ rescue Faraday::ClientError
58
+ raise Diplomat::PathNotFound
59
+ end
60
+ # rubocop:enable PerceivedComplexity, CyclomaticComplexity, AbcSize
61
+
62
+ # Get service health
63
+ # @param s [String] the state ("any", "passing", "warning", or "critical")
64
+ # @param options [Hash] :dc string for dc specific query
65
+ # @return [OpenStruct] all data associated with the node
66
+ # rubocop:disable AbcSize
67
+ def state(s, options = nil)
68
+ url = ["/v1/health/state/#{s}"]
69
+ url << use_named_parameter('dc', options[:dc]) if options && options[:dc]
70
+ url << use_named_parameter('near', options[:near]) if options && options[:near]
71
+
72
+ # If the request fails, it's probably due to a bad path
73
+ # so return a PathNotFound error.
74
+ ret = @conn.get concat_url url
75
+ JSON.parse(ret.body).map { |status| OpenStruct.new status }
76
+ rescue Faraday::ClientError
77
+ raise Diplomat::PathNotFound
78
+ end
79
+ # rubocop:enable AbcSize
80
+
81
+ # Convenience method to get services in any state
82
+ def any
83
+ state('any')
84
+ end
85
+
86
+ # Convenience method to get services in passing state
87
+ def passing
88
+ state('passing')
89
+ end
90
+
91
+ # Convenience method to get services in warning state
92
+ def warning
93
+ state('warning')
94
+ end
95
+
96
+ # Convenience method to get services in critical state
97
+ def critical
98
+ state('critical')
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,266 @@
1
+ module Diplomat
2
+ # Methods for interacting with the Consul KV API endpoint
3
+ class Kv < Diplomat::RestClient
4
+ include ApiOptions
5
+
6
+ @access_methods = %i[get put delete txn]
7
+ attr_reader :key, :value, :raw
8
+
9
+ # Get a value by its key, potentially blocking for the first or next value
10
+ # @param key [String] the key
11
+ # @param options [Hash] the query params
12
+ # @option options [Boolean] :recurse If to make recursive get or not
13
+ # @option options [String] :consistency The read consistency type
14
+ # @option options [String] :dc Target datacenter
15
+ # @option options [Boolean] :keys Only return key names.
16
+ # @option options [Boolean] :modify_index Only return ModifyIndex value.
17
+ # @option options [Boolean] :session Only return Session value.
18
+ # @option options [Boolean] :decode_values Return consul response with decoded values.
19
+ # @option options [String] :separator List only up to a given separator.
20
+ # Only applies when combined with :keys option.
21
+ # @option options [Boolean] :nil_values If to return keys/dirs with nil values
22
+ # @option options [Boolean] :convert_to_hash Take the data returned from consul and build a hash
23
+ # @option options [Callable] :transformation funnction to invoke on keys values
24
+ # @param not_found [Symbol] behaviour if the key doesn't exist;
25
+ # :reject with exception, :return degenerate value, or :wait for it to appear
26
+ # @param found [Symbol] behaviour if the key does exist;
27
+ # :reject with exception, :return its current value, or :wait for its next value
28
+ # @return [String] The base64-decoded value associated with the key
29
+ # @note
30
+ # When trying to access a key, there are two possibilites:
31
+ # - The key doesn't (yet) exist
32
+ # - The key exists. This may be its first value, there is no way to tell
33
+ # The combination of not_found and found behaviour gives maximum possible
34
+ # flexibility. For X: reject, R: return, W: wait
35
+ # - X X - meaningless; never return a value
36
+ # - X R - "normal" non-blocking get operation. Default
37
+ # - X W - get the next value only (must have a current value)
38
+ # - R X - meaningless; never return a meaningful value
39
+ # - R R - "safe" non-blocking, non-throwing get-or-default operation
40
+ # - R W - get the next value or a default
41
+ # - W X - get the first value only (must not have a current value)
42
+ # - W R - get the first or current value; always return something, but
43
+ # block only when necessary
44
+ # - W W - get the first or next value; wait until there is an update
45
+ # rubocop:disable PerceivedComplexity, MethodLength, CyclomaticComplexity, AbcSize, LineLength
46
+ def get(key, options = nil, not_found = :reject, found = :return)
47
+ @key = key
48
+ @options = options
49
+
50
+ url = ["/v1/kv/#{@key}"]
51
+ url += recurse_get(@options)
52
+ url += check_acl_token
53
+ url += use_consistency(@options)
54
+ url += dc(@options)
55
+ url += keys(@options)
56
+ url += separator(@options)
57
+
58
+ return_nil_values = @options && @options[:nil_values]
59
+ transformation = @options && @options[:transformation] && @options[:transformation].methods.find_index(:call) ? @options[:transformation] : nil
60
+
61
+ # 404s OK using this connection
62
+ raw = @conn_no_err.get concat_url url
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
+ return return_value(return_nil_values, transformation)
84
+ when :wait
85
+ index = raw.headers['x-consul-index']
86
+ end
87
+ else
88
+ raise Diplomat::UnknownStatus, "status #{raw.status}: #{raw.body}"
89
+ end
90
+
91
+ # Wait for first/next value
92
+ url += use_named_parameter('index', index)
93
+ @raw = @conn.get do |req|
94
+ req.url concat_url url
95
+ req.options.timeout = 86_400
96
+ end
97
+ @raw = parse_body
98
+ return_value(return_nil_values, transformation)
99
+ end
100
+ # rubocop:enable PerceivedComplexity, MethodLength, CyclomaticComplexity, AbcSize, LineLength
101
+
102
+ # Associate a value with a key
103
+ # @param key [String] the key
104
+ # @param value [String] the value
105
+ # @param options [Hash] the query params
106
+ # @option options [Integer] :cas The modify index
107
+ # @option options [String] :dc Target datacenter
108
+ # @option options [String] :acquire Session to attach to key
109
+ # @return [Bool] Success or failure of the write (can fail in c-a-s mode)
110
+ # rubocop:disable MethodLength, AbcSize
111
+ def put(key, value, options = nil)
112
+ @options = options
113
+ @raw = @conn.put do |req|
114
+ url = ["/v1/kv/#{key}"]
115
+ url += check_acl_token
116
+ url += use_cas(@options)
117
+ url += dc(@options)
118
+ url += acquire(@options)
119
+ req.url concat_url url
120
+ req.body = value
121
+ end
122
+ if @raw.body.chomp == 'true'
123
+ @key = key
124
+ @value = value
125
+ end
126
+ @raw.body.chomp == 'true'
127
+ end
128
+ # rubocop:enable MethodLength, AbcSize
129
+
130
+ # Delete a value by its key
131
+ # @param key [String] the key
132
+ # @param options [Hash] the query params
133
+ # @option options [String] :dc Target datacenter
134
+ # @option options [Boolean] :recurse If to make recursive get or not
135
+ # @return [OpenStruct]
136
+ def delete(key, options = nil)
137
+ @key = key
138
+ @options = options
139
+ url = ["/v1/kv/#{@key}"]
140
+ url += recurse_get(@options)
141
+ url += check_acl_token
142
+ url += dc(@options)
143
+ @raw = @conn.delete concat_url url
144
+ end
145
+
146
+ # Perform a key/value store transaction.
147
+ #
148
+ # @since 1.3.0
149
+ # @see https://www.consul.io/docs/agent/http/kv.html#txn Transaction key/value store API documentation
150
+ # @example Valid key/value store transaction format
151
+ # [
152
+ # {
153
+ # 'KV' => {
154
+ # 'Verb' => 'get',
155
+ # 'Key' => 'hello/world'
156
+ # }
157
+ # }
158
+ # ]
159
+ # @raise [Diplomat::InvalidTransaction] if transaction format is invalid
160
+ # @param value [Array] an array of transaction hashes
161
+ # @param [Hash] options transaction params
162
+ # @option options [Boolean] :decode_values of any GET requests, default: true
163
+ # @option options [String] :dc Target datacenter
164
+ # @option options [String] :consistency the accepted staleness level of the transaction.
165
+ # Can be 'stale' or 'consistent'
166
+ # @return [OpenStruct] result of the transaction
167
+ def txn(value, options = nil)
168
+ # Verify the given value for the transaction
169
+ transaction_verification(value)
170
+ # Will return 409 if transaction was rolled back
171
+ raw = @conn_no_err.put do |req|
172
+ url = ['/v1/txn']
173
+ url += check_acl_token
174
+ url += dc(options)
175
+ url += transaction_consistency(options)
176
+
177
+ req.url concat_url url
178
+ req.body = JSON.generate(value)
179
+ end
180
+ transaction_return JSON.parse(raw.body), options
181
+ end
182
+
183
+ private
184
+
185
+ def recurse_get(options)
186
+ options && options[:recurse] ? ['recurse'] : []
187
+ end
188
+
189
+ def dc(options)
190
+ options && options[:dc] ? use_named_parameter('dc', options[:dc]) : []
191
+ end
192
+
193
+ def acquire(options)
194
+ options && options[:acquire] ? use_named_parameter('acquire', options[:acquire]) : []
195
+ end
196
+
197
+ def keys(options)
198
+ options && options[:keys] ? ['keys'] : []
199
+ end
200
+
201
+ def separator(options)
202
+ options && options[:separator] ? use_named_parameter('separator', options[:separator]) : []
203
+ end
204
+
205
+ def transaction_consistency(options)
206
+ return [] unless options
207
+ if options[:consistency] && options[:consistency] == 'stale'
208
+ ['stale']
209
+ elsif options[:consistency] && options[:consistency] == 'consistent'
210
+ ['consistent']
211
+ else
212
+ []
213
+ end
214
+ end
215
+
216
+ def transaction_verification(transaction)
217
+ raise Diplomat::InvalidTransaction unless transaction.is_a?(Array)
218
+ transaction.each do |req|
219
+ raise Diplomat::InvalidTransaction unless transaction_type_verification(req)
220
+ raise Diplomat::InvalidTransaction unless transaction_verb_verification(req['KV'])
221
+ end
222
+ # Encode all value transacations if all checks pass
223
+ encode_transaction(transaction)
224
+ end
225
+
226
+ def transaction_type_verification(txn)
227
+ txn.is_a?(Hash) && txn.keys == %w[KV]
228
+ end
229
+
230
+ def transaction_verb_verification(txn)
231
+ transaction_verb = txn['Verb']
232
+ raise Diplomat::InvalidTransaction unless valid_transaction_verbs.include? transaction_verb
233
+ test_requirements = valid_transaction_verbs[transaction_verb] - txn.keys
234
+ test_requirements.empty?
235
+ end
236
+
237
+ def encode_transaction(transaction)
238
+ transaction.each do |txn|
239
+ next unless valid_value_transactions.include? txn['KV']['Verb']
240
+ value = txn['KV']['Value']
241
+ txn['KV']['Value'] = Base64.encode64(value).chomp
242
+ end
243
+ end
244
+
245
+ def transaction_return(raw_return, options)
246
+ decoded_return =
247
+ options && options[:decode_values] == false ? raw_return : decode_transaction(raw_return)
248
+ OpenStruct.new decoded_return
249
+ end
250
+
251
+ def decode_transaction(transaction) # rubocop:disable Metrics/MethodLength
252
+ return transaction if transaction['Results'].nil? || transaction['Results'].empty?
253
+
254
+ transaction.tap do |txn|
255
+ txn['Results'].each do |resp|
256
+ next unless resp['KV']['Value']
257
+ begin
258
+ resp['KV']['Value'] = Base64.decode64(resp['KV']['Value'])
259
+ rescue # rubocop:disable RescueWithoutErrorClass
260
+ nil
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,62 @@
1
+ module Diplomat
2
+ # Methods for interacting with the Consul lock API endpoint
3
+ class Lock < Diplomat::RestClient
4
+ include ApiOptions
5
+
6
+ @access_methods = %i[acquire wait_to_acquire release]
7
+
8
+ # Acquire a lock
9
+ # @param key [String] the key
10
+ # @param session [String] the session, generated from Diplomat::Session.create
11
+ # @param value [String] the value for the key
12
+ # @param options [Hash] :dc string for dc specific query
13
+ # @return [Boolean] If the lock was acquired
14
+ # rubocop:disable AbcSize
15
+ def acquire(key, session, value = nil, options = nil)
16
+ raw = @conn.put do |req|
17
+ url = ["/v1/kv/#{key}"]
18
+ url += use_named_parameter('acquire', session)
19
+ url += check_acl_token
20
+ url += use_named_parameter('dc', options[:dc]) if options && options[:dc]
21
+
22
+ req.url concat_url url
23
+ req.body = value unless value.nil?
24
+ end
25
+ raw.body.chomp == 'true'
26
+ end
27
+ # rubocop:enable AbcSize
28
+
29
+ # wait to aquire a lock
30
+ # @param key [String] the key
31
+ # @param session [String] the session, generated from Diplomat::Session.create
32
+ # @param value [String] the value for the key
33
+ # @param check_interval [Integer] number of seconds to wait between retries
34
+ # @param options [Hash] :dc string for dc specific query
35
+ # @return [Boolean] If the lock was acquired
36
+ def wait_to_acquire(key, session, value = nil, check_interval = 10, options = nil)
37
+ acquired = false
38
+ until acquired
39
+ acquired = acquire(key, session, value, options)
40
+ sleep(check_interval) unless acquired
41
+ return true if acquired
42
+ end
43
+ end
44
+
45
+ # Release a lock
46
+ # @param key [String] the key
47
+ # @param session [String] the session, generated from Diplomat::Session.create
48
+ # @param options [Hash] :dc string for dc specific query
49
+ # @return [nil]
50
+ def release(key, session, options = nil)
51
+ raw = @conn.put do |req|
52
+ url = ["/v1/kv/#{key}"]
53
+ url += use_named_parameter('release', session)
54
+ url += check_acl_token
55
+ url += use_named_parameter('dc', options[:dc]) if options && options[:dc]
56
+
57
+ req.url concat_url url
58
+ end
59
+ raw.body
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,44 @@
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 = nil)
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
+ # rubocop:disable AbcSize
29
+ def enable(enable = true, reason = nil, options = nil)
30
+ raw = @conn.put do |req|
31
+ url = ['/v1/agent/maintenance']
32
+ url << use_named_parameter('enable', enable.to_s)
33
+ url << use_named_parameter('reason', reason) if reason
34
+ url << use_named_parameter('dc', options[:dc]) if options && options[:dc]
35
+ req.url concat_url url
36
+ end
37
+
38
+ return_status = raw.status == 200
39
+ raise Diplomat::UnknownStatus, "status #{raw.status}: #{raw.body}" unless return_status
40
+ return_status
41
+ end
42
+ # rubocop:enable AbcSize
43
+ end
44
+ end