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