vault_ruby_client 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +287 -0
- data/LICENSE +364 -0
- data/README.md +223 -0
- data/lib/vault/api/approle.rb +221 -0
- data/lib/vault/api/auth.rb +324 -0
- data/lib/vault/api/auth_tls.rb +95 -0
- data/lib/vault/api/auth_token.rb +245 -0
- data/lib/vault/api/help.rb +36 -0
- data/lib/vault/api/kv.rb +230 -0
- data/lib/vault/api/logical.rb +153 -0
- data/lib/vault/api/secret.rb +171 -0
- data/lib/vault/api/sys/audit.rb +94 -0
- data/lib/vault/api/sys/auth.rb +119 -0
- data/lib/vault/api/sys/health.rb +66 -0
- data/lib/vault/api/sys/init.rb +86 -0
- data/lib/vault/api/sys/leader.rb +51 -0
- data/lib/vault/api/sys/lease.rb +52 -0
- data/lib/vault/api/sys/mount.rb +165 -0
- data/lib/vault/api/sys/namespace.rb +86 -0
- data/lib/vault/api/sys/policy.rb +95 -0
- data/lib/vault/api/sys/quota.rb +110 -0
- data/lib/vault/api/sys/seal.rb +84 -0
- data/lib/vault/api/sys.rb +30 -0
- data/lib/vault/api/transform/alphabet.rb +46 -0
- data/lib/vault/api/transform/role.rb +45 -0
- data/lib/vault/api/transform/template.rb +57 -0
- data/lib/vault/api/transform/transformation.rb +64 -0
- data/lib/vault/api/transform.rb +32 -0
- data/lib/vault/api.rb +17 -0
- data/lib/vault/client.rb +460 -0
- data/lib/vault/configurable.rb +53 -0
- data/lib/vault/defaults.rb +218 -0
- data/lib/vault/encode.rb +22 -0
- data/lib/vault/errors.rb +87 -0
- data/lib/vault/persistent/connection.rb +45 -0
- data/lib/vault/persistent/pool.rb +51 -0
- data/lib/vault/persistent/timed_stack_multi.rb +73 -0
- data/lib/vault/persistent.rb +1161 -0
- data/lib/vault/request.rb +47 -0
- data/lib/vault/response.rb +92 -0
- data/lib/vault/vendor/connection_pool/timed_stack.rb +181 -0
- data/lib/vault/vendor/connection_pool/version.rb +8 -0
- data/lib/vault/vendor/connection_pool.rb +153 -0
- data/lib/vault/version.rb +6 -0
- data/lib/vault_ruby_client.rb +53 -0
- metadata +158 -0
data/lib/vault/client.rb
ADDED
@@ -0,0 +1,460 @@
|
|
1
|
+
# Copyright (c) HashiCorp, Inc.
|
2
|
+
# SPDX-License-Identifier: MPL-2.0
|
3
|
+
|
4
|
+
require "cgi"
|
5
|
+
require "json"
|
6
|
+
require "uri"
|
7
|
+
|
8
|
+
require_relative "persistent"
|
9
|
+
require_relative "configurable"
|
10
|
+
require_relative "errors"
|
11
|
+
require_relative "version"
|
12
|
+
require_relative "encode"
|
13
|
+
|
14
|
+
module Vault
|
15
|
+
class Client
|
16
|
+
# The user agent for this client.
|
17
|
+
USER_AGENT = "VaultRuby/#{Vault::VERSION} (+github.com/khiav223577/vault_ruby_client)".freeze
|
18
|
+
|
19
|
+
# The name of the header used to hold the Vault token.
|
20
|
+
TOKEN_HEADER = "X-Vault-Token".freeze
|
21
|
+
|
22
|
+
# The name of the header used to hold the Namespace.
|
23
|
+
NAMESPACE_HEADER = "X-Vault-Namespace".freeze
|
24
|
+
|
25
|
+
# The name of the header used to hold the wrapped request ttl.
|
26
|
+
WRAP_TTL_HEADER = "X-Vault-Wrap-TTL".freeze
|
27
|
+
|
28
|
+
# The name of the header used for redirection.
|
29
|
+
LOCATION_HEADER = "location".freeze
|
30
|
+
|
31
|
+
# The default headers that are sent with every request.
|
32
|
+
DEFAULT_HEADERS = {
|
33
|
+
"Content-Type" => "application/json",
|
34
|
+
"Accept" => "application/json",
|
35
|
+
"User-Agent" => USER_AGENT,
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
# The default list of options to use when parsing JSON.
|
39
|
+
JSON_PARSE_OPTIONS = {
|
40
|
+
max_nesting: false,
|
41
|
+
create_additions: false,
|
42
|
+
symbolize_names: true,
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
RESCUED_EXCEPTIONS = [].tap do |a|
|
46
|
+
# Failure to even open the socket (usually permissions)
|
47
|
+
a << SocketError
|
48
|
+
|
49
|
+
# Failed to reach the server (aka bad URL)
|
50
|
+
a << Errno::ECONNREFUSED
|
51
|
+
a << Errno::EADDRNOTAVAIL
|
52
|
+
|
53
|
+
# Failed to read body or no response body given
|
54
|
+
a << EOFError
|
55
|
+
|
56
|
+
# Timeout (Ruby 1.9-)
|
57
|
+
a << Timeout::Error
|
58
|
+
|
59
|
+
# Timeout (Ruby 1.9+) - Ruby 1.9 does not define these constants so we
|
60
|
+
# only add them if they are defiend
|
61
|
+
a << Net::ReadTimeout if defined?(Net::ReadTimeout)
|
62
|
+
a << Net::OpenTimeout if defined?(Net::OpenTimeout)
|
63
|
+
|
64
|
+
a << PersistentHTTP::Error
|
65
|
+
end.freeze
|
66
|
+
|
67
|
+
# Vault requires at least TLS1.2
|
68
|
+
MIN_TLS_VERSION = if defined? OpenSSL::SSL::TLS1_2_VERSION
|
69
|
+
OpenSSL::SSL::TLS1_2_VERSION
|
70
|
+
else
|
71
|
+
"TLSv1_2"
|
72
|
+
end
|
73
|
+
|
74
|
+
include Vault::Configurable
|
75
|
+
|
76
|
+
# Create a new Client with the given options. Any options given take
|
77
|
+
# precedence over the default options.
|
78
|
+
#
|
79
|
+
# @return [Vault::Client]
|
80
|
+
def initialize(options = {})
|
81
|
+
# Use any options given, but fall back to the defaults set on the module
|
82
|
+
Vault::Configurable.keys.each do |key|
|
83
|
+
value = options.key?(key) ? options[key] : Defaults.public_send(key)
|
84
|
+
instance_variable_set(:"@#{key}", value)
|
85
|
+
end
|
86
|
+
|
87
|
+
@lock = Mutex.new
|
88
|
+
@nhp = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def pool
|
92
|
+
@lock.synchronize do
|
93
|
+
return @nhp if @nhp
|
94
|
+
|
95
|
+
@nhp = PersistentHTTP.new("vault-ruby", nil, pool_size, pool_timeout)
|
96
|
+
|
97
|
+
if proxy_address
|
98
|
+
proxy_uri = URI.parse "http://#{proxy_address}"
|
99
|
+
|
100
|
+
proxy_uri.port = proxy_port if proxy_port
|
101
|
+
|
102
|
+
if proxy_username
|
103
|
+
proxy_uri.user = proxy_username
|
104
|
+
proxy_uri.password = proxy_password
|
105
|
+
end
|
106
|
+
|
107
|
+
@nhp.proxy = proxy_uri
|
108
|
+
end
|
109
|
+
|
110
|
+
# Use a custom open timeout
|
111
|
+
if open_timeout || timeout
|
112
|
+
@nhp.open_timeout = (open_timeout || timeout).to_i
|
113
|
+
end
|
114
|
+
|
115
|
+
# Use a custom read timeout
|
116
|
+
if read_timeout || timeout
|
117
|
+
@nhp.read_timeout = (read_timeout || timeout).to_i
|
118
|
+
end
|
119
|
+
|
120
|
+
@nhp.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
121
|
+
|
122
|
+
@nhp.min_version = MIN_TLS_VERSION
|
123
|
+
|
124
|
+
# Only use secure ciphers
|
125
|
+
@nhp.ciphers = ssl_ciphers
|
126
|
+
|
127
|
+
# Custom pem files, no problem!
|
128
|
+
pem = ssl_pem_contents || (ssl_pem_file ? File.read(ssl_pem_file) : nil)
|
129
|
+
if pem
|
130
|
+
@nhp.cert = OpenSSL::X509::Certificate.new(pem)
|
131
|
+
@nhp.key = OpenSSL::PKey::RSA.new(pem, ssl_pem_passphrase)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Use custom CA cert for verification
|
135
|
+
if ssl_ca_cert
|
136
|
+
@nhp.ca_file = ssl_ca_cert
|
137
|
+
end
|
138
|
+
|
139
|
+
# Use custom CA path that contains CA certs
|
140
|
+
if ssl_ca_path
|
141
|
+
@nhp.ca_path = ssl_ca_path
|
142
|
+
end
|
143
|
+
|
144
|
+
if ssl_cert_store
|
145
|
+
@nhp.cert_store = ssl_cert_store
|
146
|
+
end
|
147
|
+
|
148
|
+
# Naughty, naughty, naughty! Don't blame me when someone hops in
|
149
|
+
# and executes a MITM attack!
|
150
|
+
if !ssl_verify
|
151
|
+
@nhp.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
152
|
+
end
|
153
|
+
|
154
|
+
# Use custom timeout for connecting and verifying via SSL
|
155
|
+
if ssl_timeout || timeout
|
156
|
+
@nhp.ssl_timeout = (ssl_timeout || timeout).to_i
|
157
|
+
end
|
158
|
+
|
159
|
+
@nhp
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
private :pool
|
164
|
+
|
165
|
+
# Shutdown any open pool connections. Pool will be recreated upon next request.
|
166
|
+
def shutdown
|
167
|
+
@nhp.shutdown()
|
168
|
+
@nhp = nil
|
169
|
+
end
|
170
|
+
|
171
|
+
# Creates and yields a new client object with the given token. This may be
|
172
|
+
# used safely in a threadsafe manner because the original client remains
|
173
|
+
# unchanged. The value of the block is returned.
|
174
|
+
#
|
175
|
+
# @yield [Vault::Client]
|
176
|
+
def with_token(token)
|
177
|
+
client = self.dup
|
178
|
+
client.token = token
|
179
|
+
return yield client if block_given?
|
180
|
+
return nil
|
181
|
+
end
|
182
|
+
|
183
|
+
# Determine if the given options are the same as ours.
|
184
|
+
# @return [true, false]
|
185
|
+
def same_options?(opts)
|
186
|
+
options.hash == opts.hash
|
187
|
+
end
|
188
|
+
|
189
|
+
# Perform a GET request.
|
190
|
+
# @see Client#request
|
191
|
+
def get(path, params = {}, headers = {})
|
192
|
+
request(:get, path, params, headers)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Perform a LIST request.
|
196
|
+
# @see Client#request
|
197
|
+
def list(path, params = {}, headers = {})
|
198
|
+
params = params.merge(list: true)
|
199
|
+
request(:get, path, params, headers)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Perform a POST request.
|
203
|
+
# @see Client#request
|
204
|
+
def post(path, data = {}, headers = {})
|
205
|
+
request(:post, path, data, headers)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Perform a PUT request.
|
209
|
+
# @see Client#request
|
210
|
+
def put(path, data, headers = {})
|
211
|
+
request(:put, path, data, headers)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Perform a PATCH request.
|
215
|
+
# @see Client#request
|
216
|
+
def patch(path, data, headers = {})
|
217
|
+
request(:patch, path, data, headers)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Perform a DELETE request.
|
221
|
+
# @see Client#request
|
222
|
+
def delete(path, params = {}, headers = {})
|
223
|
+
request(:delete, path, params, headers)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Make an HTTP request with the given verb, data, params, and headers. If
|
227
|
+
# the response has a return type of JSON, the JSON is automatically parsed
|
228
|
+
# and returned as a hash; otherwise it is returned as a string.
|
229
|
+
#
|
230
|
+
# @raise [HTTPError]
|
231
|
+
# if the request is not an HTTP 200 OK
|
232
|
+
#
|
233
|
+
# @param [Symbol] verb
|
234
|
+
# the lowercase symbol of the HTTP verb (e.g. :get, :delete)
|
235
|
+
# @param [String] path
|
236
|
+
# the absolute or relative path from {Defaults.address} to make the
|
237
|
+
# request against
|
238
|
+
# @param [#read, Hash, nil] data
|
239
|
+
# the data to use (varies based on the +verb+)
|
240
|
+
# @param [Hash] headers
|
241
|
+
# the list of headers to use
|
242
|
+
#
|
243
|
+
# @return [String, Hash]
|
244
|
+
# the response body
|
245
|
+
def request(verb, path, data = {}, headers = {})
|
246
|
+
# Build the URI and request object from the given information
|
247
|
+
uri = build_uri(verb, path, data)
|
248
|
+
request = class_for_request(verb).new(uri.request_uri)
|
249
|
+
if uri.userinfo()
|
250
|
+
request.basic_auth uri.user, uri.password
|
251
|
+
end
|
252
|
+
|
253
|
+
# Get a list of headers
|
254
|
+
headers = DEFAULT_HEADERS.merge(headers)
|
255
|
+
|
256
|
+
# Add the Vault token header - users could still override this on a
|
257
|
+
# per-request basis
|
258
|
+
if !token.nil?
|
259
|
+
headers[TOKEN_HEADER] ||= token
|
260
|
+
end
|
261
|
+
|
262
|
+
# Add the Vault Namespace header - users could still override this on a
|
263
|
+
# per-request basis
|
264
|
+
if !namespace.nil?
|
265
|
+
headers[NAMESPACE_HEADER] ||= namespace
|
266
|
+
end
|
267
|
+
|
268
|
+
# Add headers
|
269
|
+
headers.each do |key, value|
|
270
|
+
request.add_field(key, value)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Setup PATCH/POST/PUT
|
274
|
+
if [:patch, :post, :put].include?(verb)
|
275
|
+
if data.respond_to?(:read)
|
276
|
+
request.content_length = data.size
|
277
|
+
request.body_stream = data
|
278
|
+
elsif data.is_a?(Hash)
|
279
|
+
request.form_data = data
|
280
|
+
else
|
281
|
+
request.body = data
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
begin
|
286
|
+
# Create a connection using the block form, which will ensure the socket
|
287
|
+
# is properly closed in the event of an error.
|
288
|
+
response = pool.request(uri, request)
|
289
|
+
|
290
|
+
case response
|
291
|
+
when Net::HTTPRedirection
|
292
|
+
# On a redirect of a GET or HEAD request, the URL already contains
|
293
|
+
# the data as query string parameters.
|
294
|
+
if [:head, :get].include?(verb)
|
295
|
+
data = {}
|
296
|
+
end
|
297
|
+
request(verb, response[LOCATION_HEADER], data, headers)
|
298
|
+
when Net::HTTPSuccess
|
299
|
+
success(response)
|
300
|
+
else
|
301
|
+
error(response)
|
302
|
+
end
|
303
|
+
rescue *RESCUED_EXCEPTIONS => e
|
304
|
+
raise HTTPConnectionError.new(address, e)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Construct a URL from the given verb and path. If the request is a GET or
|
309
|
+
# DELETE request, the params are assumed to be query params are are
|
310
|
+
# converted as such using {Client#to_query_string}.
|
311
|
+
#
|
312
|
+
# If the path is relative, it is merged with the {Defaults.address}
|
313
|
+
# attribute. If the path is absolute, it is converted to a URI object and
|
314
|
+
# returned.
|
315
|
+
#
|
316
|
+
# @param [Symbol] verb
|
317
|
+
# the lowercase HTTP verb (e.g. :+get+)
|
318
|
+
# @param [String] path
|
319
|
+
# the absolute or relative HTTP path (url) to get
|
320
|
+
# @param [Hash] params
|
321
|
+
# the list of params to build the URI with (for GET and DELETE requests)
|
322
|
+
#
|
323
|
+
# @return [URI]
|
324
|
+
def build_uri(verb, path, params = {})
|
325
|
+
# Add any query string parameters
|
326
|
+
if [:delete, :get].include?(verb)
|
327
|
+
path = [path, to_query_string(params)].compact.join("?")
|
328
|
+
end
|
329
|
+
|
330
|
+
# Parse the URI
|
331
|
+
uri = URI.parse(path)
|
332
|
+
|
333
|
+
# Don't merge absolute URLs
|
334
|
+
uri = URI.parse(File.join(address, path)) unless uri.absolute?
|
335
|
+
|
336
|
+
# Return the URI object
|
337
|
+
uri
|
338
|
+
end
|
339
|
+
|
340
|
+
# Helper method to get the corresponding {Net::HTTP} class from the given
|
341
|
+
# HTTP verb.
|
342
|
+
#
|
343
|
+
# @param [#to_s] verb
|
344
|
+
# the HTTP verb to create a class from
|
345
|
+
#
|
346
|
+
# @return [Class]
|
347
|
+
def class_for_request(verb)
|
348
|
+
Net::HTTP.const_get(verb.to_s.capitalize)
|
349
|
+
end
|
350
|
+
|
351
|
+
# Convert the given hash to a list of query string parameters. Each key and
|
352
|
+
# value in the hash is URI-escaped for safety.
|
353
|
+
#
|
354
|
+
# @param [Hash] hash
|
355
|
+
# the hash to create the query string from
|
356
|
+
#
|
357
|
+
# @return [String, nil]
|
358
|
+
# the query string as a string, or +nil+ if there are no params
|
359
|
+
def to_query_string(hash)
|
360
|
+
hash.map do |key, value|
|
361
|
+
"#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
362
|
+
end.join('&')[/.+/]
|
363
|
+
end
|
364
|
+
|
365
|
+
# Parse the response object and manipulate the result based on the given
|
366
|
+
# +Content-Type+ header. For now, this method only parses JSON, but it
|
367
|
+
# could be expanded in the future to accept other content types.
|
368
|
+
#
|
369
|
+
# @param [HTTP::Message] response
|
370
|
+
# the response object from the request
|
371
|
+
#
|
372
|
+
# @return [String, Hash]
|
373
|
+
# the parsed response, as an object
|
374
|
+
def success(response)
|
375
|
+
if response.body && (response.content_type || '').include?("json")
|
376
|
+
JSON.parse(response.body, JSON_PARSE_OPTIONS)
|
377
|
+
else
|
378
|
+
response.body
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# Raise a response error, extracting as much information from the server's
|
383
|
+
# response as possible.
|
384
|
+
#
|
385
|
+
# @raise [HTTPError]
|
386
|
+
#
|
387
|
+
# @param [HTTP::Message] response
|
388
|
+
# the response object from the request
|
389
|
+
def error(response)
|
390
|
+
if response.body && response.body.match("missing client token")
|
391
|
+
# Vault 1.10+ no longer returns "missing" client token" so we use HTTPClientError
|
392
|
+
klass = HTTPClientError
|
393
|
+
else
|
394
|
+
# Use the correct exception class
|
395
|
+
case response
|
396
|
+
when Net::HTTPPreconditionFailed
|
397
|
+
raise MissingRequiredStateError.new
|
398
|
+
when Net::HTTPClientError
|
399
|
+
klass = HTTPClientError
|
400
|
+
when Net::HTTPServerError
|
401
|
+
klass = HTTPServerError
|
402
|
+
else
|
403
|
+
klass = HTTPError
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
if (response.content_type || '').include?("json")
|
408
|
+
# Attempt to parse the error as JSON
|
409
|
+
begin
|
410
|
+
json = JSON.parse(response.body, JSON_PARSE_OPTIONS)
|
411
|
+
|
412
|
+
if json[:errors]
|
413
|
+
raise klass.new(address, response, json[:errors])
|
414
|
+
end
|
415
|
+
rescue JSON::ParserError; end
|
416
|
+
end
|
417
|
+
|
418
|
+
raise klass.new(address, response, [response.body])
|
419
|
+
end
|
420
|
+
|
421
|
+
# Execute the given block with retries and exponential backoff.
|
422
|
+
#
|
423
|
+
# @param [Array<Exception>] rescued
|
424
|
+
# the list of exceptions to rescue
|
425
|
+
def with_retries(*rescued, &block)
|
426
|
+
options = rescued.last.is_a?(Hash) ? rescued.pop : {}
|
427
|
+
exception = nil
|
428
|
+
retries = 0
|
429
|
+
|
430
|
+
rescued = Defaults::RETRIED_EXCEPTIONS if rescued.empty?
|
431
|
+
|
432
|
+
max_attempts = options[:attempts] || Defaults::RETRY_ATTEMPTS
|
433
|
+
backoff_base = options[:base] || Defaults::RETRY_BASE
|
434
|
+
backoff_max = options[:max_wait] || Defaults::RETRY_MAX_WAIT
|
435
|
+
|
436
|
+
begin
|
437
|
+
return yield retries, exception
|
438
|
+
rescue *rescued => e
|
439
|
+
exception = e
|
440
|
+
|
441
|
+
retries += 1
|
442
|
+
raise if retries > max_attempts
|
443
|
+
|
444
|
+
# Calculate the exponential backoff combined with an element of
|
445
|
+
# randomness.
|
446
|
+
backoff = [backoff_base * (2 ** (retries - 1)), backoff_max].min
|
447
|
+
backoff = backoff * (0.5 * (1 + Kernel.rand))
|
448
|
+
|
449
|
+
# Ensure we are sleeping at least the minimum interval.
|
450
|
+
backoff = [backoff_base, backoff].max
|
451
|
+
|
452
|
+
# Exponential backoff.
|
453
|
+
Kernel.sleep(backoff)
|
454
|
+
|
455
|
+
# Now retry
|
456
|
+
retry
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright (c) HashiCorp, Inc.
|
2
|
+
# SPDX-License-Identifier: MPL-2.0
|
3
|
+
|
4
|
+
require_relative "defaults"
|
5
|
+
|
6
|
+
module Vault
|
7
|
+
module Configurable
|
8
|
+
def self.keys
|
9
|
+
@keys ||= [
|
10
|
+
:address,
|
11
|
+
:token,
|
12
|
+
:hostname,
|
13
|
+
:namespace,
|
14
|
+
:open_timeout,
|
15
|
+
:proxy_address,
|
16
|
+
:proxy_password,
|
17
|
+
:proxy_port,
|
18
|
+
:proxy_username,
|
19
|
+
:pool_size,
|
20
|
+
:pool_timeout,
|
21
|
+
:read_timeout,
|
22
|
+
:ssl_ciphers,
|
23
|
+
:ssl_pem_contents,
|
24
|
+
:ssl_pem_file,
|
25
|
+
:ssl_pem_passphrase,
|
26
|
+
:ssl_ca_cert,
|
27
|
+
:ssl_ca_path,
|
28
|
+
:ssl_cert_store,
|
29
|
+
:ssl_verify,
|
30
|
+
:ssl_timeout,
|
31
|
+
:timeout,
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
Vault::Configurable.keys.each(&method(:attr_accessor))
|
36
|
+
|
37
|
+
# Configure yields self for block-style configuration.
|
38
|
+
#
|
39
|
+
# @yield [self]
|
40
|
+
def configure
|
41
|
+
yield self
|
42
|
+
end
|
43
|
+
|
44
|
+
# The list of options for this configurable.
|
45
|
+
#
|
46
|
+
# @return [Hash<Symbol, Object>]
|
47
|
+
def options
|
48
|
+
Hash[*Vault::Configurable.keys.map do |key|
|
49
|
+
[key, instance_variable_get(:"@#{key}")]
|
50
|
+
end.flatten]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|