bolt 2.35.0 → 2.40.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +1 -0
- data/lib/bolt/analytics.rb +27 -8
- data/lib/bolt/apply_result.rb +3 -3
- data/lib/bolt/bolt_option_parser.rb +45 -18
- data/lib/bolt/cli.rb +92 -110
- data/lib/bolt/config.rb +184 -80
- data/lib/bolt/config/options.rb +144 -83
- data/lib/bolt/config/transport/base.rb +10 -19
- data/lib/bolt/config/transport/local.rb +1 -7
- data/lib/bolt/config/transport/options.rb +11 -68
- data/lib/bolt/config/transport/ssh.rb +8 -19
- data/lib/bolt/executor.rb +5 -17
- data/lib/bolt/inventory.rb +25 -0
- data/lib/bolt/inventory/group.rb +0 -8
- data/lib/bolt/inventory/options.rb +130 -0
- data/lib/bolt/inventory/target.rb +10 -11
- data/lib/bolt/module_installer.rb +21 -13
- data/lib/bolt/module_installer/resolver.rb +1 -1
- data/lib/bolt/outputter.rb +19 -5
- data/lib/bolt/outputter/human.rb +20 -1
- data/lib/bolt/outputter/json.rb +1 -1
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/outputter/rainbow.rb +13 -2
- data/lib/bolt/plugin.rb +41 -12
- data/lib/bolt/plugin/cache.rb +76 -0
- data/lib/bolt/plugin/module.rb +4 -4
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +59 -40
- data/lib/bolt/project_manager.rb +201 -0
- data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +49 -4
- data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +3 -3
- data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
- data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +5 -3
- data/lib/bolt/puppetdb/client.rb +8 -0
- data/lib/bolt/puppetdb/config.rb +1 -2
- data/lib/bolt/rerun.rb +1 -5
- data/lib/bolt/shell/bash.rb +8 -2
- data/lib/bolt/shell/powershell.rb +21 -3
- data/lib/bolt/target.rb +4 -0
- data/lib/bolt/task/run.rb +1 -1
- data/lib/bolt/transport/local.rb +13 -0
- data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
- data/lib/bolt/util.rb +36 -7
- data/lib/bolt/validator.rb +227 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +3 -1
- data/lib/bolt_server/config.rb +3 -1
- data/lib/bolt_server/plugin.rb +13 -0
- data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
- data/lib/bolt_server/schemas/connect-data.json +22 -0
- data/lib/bolt_server/schemas/partials/task.json +3 -3
- data/lib/bolt_server/transport_app.rb +68 -40
- data/libexec/apply_catalog.rb +1 -1
- data/libexec/custom_facts.rb +1 -1
- data/libexec/query_resources.rb +1 -1
- metadata +23 -17
- data/lib/bolt/project_migrator.rb +0 -80
@@ -31,7 +31,8 @@ module Bolt
|
|
31
31
|
end
|
32
32
|
|
33
33
|
if @name == 'localhost'
|
34
|
-
|
34
|
+
default = { 'config' => { 'transport' => 'local' } }
|
35
|
+
target_data = Bolt::Util.deep_merge(default, target_data)
|
35
36
|
end
|
36
37
|
|
37
38
|
@config = target_data['config'] || {}
|
@@ -49,18 +50,16 @@ module Bolt
|
|
49
50
|
validate
|
50
51
|
end
|
51
52
|
|
52
|
-
def
|
53
|
+
def set_local_defaults
|
54
|
+
return if @set_local_default
|
53
55
|
defaults = {
|
54
|
-
'
|
55
|
-
'transport' => 'local',
|
56
|
-
'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
|
57
|
-
},
|
58
|
-
'features' => ['puppet-agent']
|
56
|
+
'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
|
59
57
|
}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
old_config = @config
|
59
|
+
@config = Bolt::Util.deep_merge(defaults, @config)
|
60
|
+
invalidate_config_cache! if old_config != @config
|
61
|
+
set_feature('puppet-agent')
|
62
|
+
@set_local_default = true
|
64
63
|
end
|
65
64
|
|
66
65
|
# rubocop:disable Naming/AccessorMethodName
|
@@ -17,14 +17,14 @@ module Bolt
|
|
17
17
|
|
18
18
|
# Adds a single module to the project.
|
19
19
|
#
|
20
|
-
def add(name, specs, puppetfile_path, moduledir,
|
20
|
+
def add(name, specs, puppetfile_path, moduledir, project_file, config)
|
21
21
|
project_specs = Specs.new(specs)
|
22
22
|
|
23
23
|
# Exit early if project config already includes a spec with this name.
|
24
24
|
if project_specs.include?(name)
|
25
25
|
@outputter.print_message(
|
26
|
-
"Project configuration file #{
|
27
|
-
"#{name}. Nothing to do."
|
26
|
+
"Project configuration file #{project_file} already includes specification "\
|
27
|
+
"with name #{name}. Nothing to do."
|
28
28
|
)
|
29
29
|
return true
|
30
30
|
end
|
@@ -47,30 +47,32 @@ module Bolt
|
|
47
47
|
# a version conflict.
|
48
48
|
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
49
49
|
|
50
|
+
@outputter.start_spin
|
50
51
|
begin
|
51
52
|
resolve_specs.add_specs('name' => name)
|
52
|
-
puppetfile = Resolver.new.resolve(resolve_specs)
|
53
|
+
puppetfile = Resolver.new.resolve(resolve_specs, config)
|
53
54
|
rescue Bolt::Error
|
54
55
|
project_specs.add_specs('name' => name)
|
55
|
-
puppetfile = Resolver.new.resolve(project_specs)
|
56
|
+
puppetfile = Resolver.new.resolve(project_specs, config)
|
56
57
|
end
|
58
|
+
@outputter.stop_spin
|
57
59
|
|
58
60
|
# Display the diff between the existing Puppetfile and the new Puppetfile.
|
59
61
|
print_puppetfile_diff(existing_puppetfile, puppetfile)
|
60
62
|
|
61
63
|
# Add the module to the project configuration.
|
62
|
-
@outputter.print_action_step("Updating project configuration file at #{
|
64
|
+
@outputter.print_action_step("Updating project configuration file at #{project_file}")
|
63
65
|
|
64
|
-
data = Bolt::Util.read_yaml_hash(
|
66
|
+
data = Bolt::Util.read_yaml_hash(project_file, 'project')
|
65
67
|
data['modules'] ||= []
|
66
68
|
data['modules'] << name.tr('-', '/')
|
67
69
|
|
68
70
|
begin
|
69
|
-
File.write(
|
71
|
+
File.write(project_file, data.to_yaml)
|
70
72
|
rescue SystemCallError => e
|
71
73
|
raise Bolt::FileError.new(
|
72
74
|
"Unable to update project configuration file: #{e.message}",
|
73
|
-
|
75
|
+
project_file
|
74
76
|
)
|
75
77
|
end
|
76
78
|
|
@@ -79,7 +81,7 @@ module Bolt
|
|
79
81
|
puppetfile.write(puppetfile_path, moduledir)
|
80
82
|
|
81
83
|
# Install the modules.
|
82
|
-
install_puppetfile(puppetfile_path, moduledir)
|
84
|
+
install_puppetfile(puppetfile_path, moduledir, config)
|
83
85
|
end
|
84
86
|
|
85
87
|
# Outputs a diff of an old Puppetfile and a new Puppetfile.
|
@@ -145,7 +147,7 @@ module Bolt
|
|
145
147
|
|
146
148
|
# Installs a project's module dependencies.
|
147
149
|
#
|
148
|
-
def install(specs, path, moduledir, force: false, resolve: true)
|
150
|
+
def install(specs, path, moduledir, config = {}, force: false, resolve: true)
|
149
151
|
@outputter.print_message("Installing project modules\n\n")
|
150
152
|
|
151
153
|
if resolve != false
|
@@ -155,7 +157,11 @@ module Bolt
|
|
155
157
|
# and write a Puppetfile.
|
156
158
|
if force || !path.exist?
|
157
159
|
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
158
|
-
|
160
|
+
|
161
|
+
# This doesn't use the block as it's more testable to just mock *_spin
|
162
|
+
@outputter.start_spin
|
163
|
+
puppetfile = Resolver.new.resolve(specs, config)
|
164
|
+
@outputter.stop_spin
|
159
165
|
|
160
166
|
# We get here either through 'bolt module install' which uses the
|
161
167
|
# managed modulepath (which isn't configurable) or through bolt
|
@@ -177,14 +183,16 @@ module Bolt
|
|
177
183
|
end
|
178
184
|
|
179
185
|
# Install the modules.
|
180
|
-
install_puppetfile(path, moduledir)
|
186
|
+
install_puppetfile(path, moduledir, config)
|
181
187
|
end
|
182
188
|
|
183
189
|
# Installs the Puppetfile and generates types.
|
184
190
|
#
|
185
191
|
def install_puppetfile(path, moduledir, config = {})
|
186
192
|
@outputter.print_action_step("Syncing modules from #{path} to #{moduledir}")
|
193
|
+
@outputter.start_spin
|
187
194
|
ok = Installer.new(config).install(path, moduledir)
|
195
|
+
@outputter.stop_spin
|
188
196
|
|
189
197
|
# Automatically generate types after installing modules
|
190
198
|
@outputter.print_action_step("Generating type references")
|
data/lib/bolt/outputter.rb
CHANGED
@@ -2,24 +2,25 @@
|
|
2
2
|
|
3
3
|
module Bolt
|
4
4
|
class Outputter
|
5
|
-
def self.for_format(format, color, verbose, trace)
|
5
|
+
def self.for_format(format, color, verbose, trace, spin)
|
6
6
|
case format
|
7
7
|
when 'human'
|
8
|
-
Bolt::Outputter::Human.new(color, verbose, trace)
|
8
|
+
Bolt::Outputter::Human.new(color, verbose, trace, spin)
|
9
9
|
when 'json'
|
10
|
-
Bolt::Outputter::JSON.new(color, verbose, trace)
|
10
|
+
Bolt::Outputter::JSON.new(color, verbose, trace, false)
|
11
11
|
when 'rainbow'
|
12
|
-
Bolt::Outputter::Rainbow.new(color, verbose, trace)
|
12
|
+
Bolt::Outputter::Rainbow.new(color, verbose, trace, spin)
|
13
13
|
when nil
|
14
14
|
raise "Cannot use outputter before parsing."
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def initialize(color, verbose, trace, stream = $stdout)
|
18
|
+
def initialize(color, verbose, trace, spin, stream = $stdout)
|
19
19
|
@color = color
|
20
20
|
@verbose = verbose
|
21
21
|
@trace = trace
|
22
22
|
@stream = stream
|
23
|
+
@spin = spin
|
23
24
|
end
|
24
25
|
|
25
26
|
def indent(indent, string)
|
@@ -34,6 +35,19 @@ module Bolt
|
|
34
35
|
def print_error
|
35
36
|
raise NotImplementedError, "print_error() must be implemented by the outputter class"
|
36
37
|
end
|
38
|
+
|
39
|
+
def start_spin; end
|
40
|
+
|
41
|
+
def stop_spin; end
|
42
|
+
|
43
|
+
def spin
|
44
|
+
start_spin
|
45
|
+
begin
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
stop_spin
|
49
|
+
end
|
50
|
+
end
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -14,12 +14,13 @@ module Bolt
|
|
14
14
|
|
15
15
|
def print_head; end
|
16
16
|
|
17
|
-
def initialize(color, verbose, trace, stream = $stdout)
|
17
|
+
def initialize(color, verbose, trace, spin, stream = $stdout)
|
18
18
|
super
|
19
19
|
# Plans and without_default_logging() calls can both be nested, so we
|
20
20
|
# track each of them with a "stack" consisting of an integer.
|
21
21
|
@plan_depth = 0
|
22
22
|
@disable_depth = 0
|
23
|
+
@pinwheel = %w[- \\ | /]
|
23
24
|
end
|
24
25
|
|
25
26
|
def colorize(color, string)
|
@@ -30,6 +31,24 @@ module Bolt
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
34
|
+
def start_spin
|
35
|
+
return unless @spin && @stream.isatty
|
36
|
+
@spin = true
|
37
|
+
@spin_thread = Thread.new do
|
38
|
+
loop do
|
39
|
+
sleep(0.1)
|
40
|
+
@stream.print(colorize(:cyan, @pinwheel.rotate!.first + "\b"))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop_spin
|
46
|
+
return unless @spin && @stream.isatty
|
47
|
+
@spin_thread.terminate
|
48
|
+
@spin = false
|
49
|
+
@stream.print("\b")
|
50
|
+
end
|
51
|
+
|
33
52
|
def remove_trail(string)
|
34
53
|
string.sub(/\s\z/, '')
|
35
54
|
end
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -5,7 +5,7 @@ require 'bolt/pal'
|
|
5
5
|
module Bolt
|
6
6
|
class Outputter
|
7
7
|
class Rainbow < Bolt::Outputter::Human
|
8
|
-
def initialize(color, verbose, trace, stream = $stdout)
|
8
|
+
def initialize(color, verbose, trace, spin, stream = $stdout)
|
9
9
|
begin
|
10
10
|
require 'paint'
|
11
11
|
if Bolt::Util.windows?
|
@@ -53,7 +53,7 @@ module Bolt
|
|
53
53
|
@state = :normal if c == 'm'
|
54
54
|
end
|
55
55
|
end
|
56
|
-
a.join
|
56
|
+
a.join
|
57
57
|
else
|
58
58
|
"\033[#{COLORS[color]}m#{string}\033[0m"
|
59
59
|
end
|
@@ -62,6 +62,17 @@ module Bolt
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
def start_spin
|
66
|
+
return unless @spin && @stream.isatty
|
67
|
+
@spin = true
|
68
|
+
@spin_thread = Thread.new do
|
69
|
+
loop do
|
70
|
+
@stream.print(colorize(:rainbow, @pinwheel.rotate!.first + "\b"))
|
71
|
+
sleep(0.1)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
65
76
|
def print_summary(results, elapsed_time = nil)
|
66
77
|
ok_set = results.ok_set
|
67
78
|
unless ok_set.empty?
|
data/lib/bolt/plugin.rb
CHANGED
@@ -4,6 +4,7 @@ require 'bolt/inventory'
|
|
4
4
|
require 'bolt/executor'
|
5
5
|
require 'bolt/module'
|
6
6
|
require 'bolt/pal'
|
7
|
+
require 'bolt/plugin/cache'
|
7
8
|
require 'bolt/plugin/puppetdb'
|
8
9
|
|
9
10
|
module Bolt
|
@@ -36,6 +37,13 @@ module Bolt
|
|
36
37
|
super("Plugin #{plugin_name} does not support #{hook}", 'bolt/unsupported-hook')
|
37
38
|
end
|
38
39
|
end
|
40
|
+
|
41
|
+
class LoadingDisabled < PluginError
|
42
|
+
def initialize(plugin_name)
|
43
|
+
msg = "Cannot load plugin #{plugin_name}: plugin loading is disabled"
|
44
|
+
super(msg, 'bolt/plugin-loading-disabled', { 'plugin_name' => plugin_name })
|
45
|
+
end
|
46
|
+
end
|
39
47
|
end
|
40
48
|
|
41
49
|
class PluginContext
|
@@ -119,15 +127,8 @@ module Bolt
|
|
119
127
|
end
|
120
128
|
end
|
121
129
|
|
122
|
-
def self.setup(config, pal, analytics = Bolt::Analytics::NoopClient.new)
|
123
|
-
plugins = new(config, pal, analytics)
|
124
|
-
|
125
|
-
# Initialize any plugins referenced in plugin config. This will also indirectly
|
126
|
-
# initialize any plugins they depend on.
|
127
|
-
if plugins.reference?(config.plugins)
|
128
|
-
msg = "The 'plugins' setting cannot be set by a plugin reference"
|
129
|
-
raise PluginError.new(msg, 'bolt/plugin-error')
|
130
|
-
end
|
130
|
+
def self.setup(config, pal, analytics = Bolt::Analytics::NoopClient.new, **opts)
|
131
|
+
plugins = new(config, pal, analytics, **opts)
|
131
132
|
|
132
133
|
config.plugins.each_key do |plugin|
|
133
134
|
plugins.by_name(plugin)
|
@@ -148,12 +149,13 @@ module Bolt
|
|
148
149
|
|
149
150
|
private_class_method :new
|
150
151
|
|
151
|
-
def initialize(config, pal, analytics)
|
152
|
+
def initialize(config, pal, analytics, load_plugins: true)
|
152
153
|
@config = config
|
153
154
|
@analytics = analytics
|
154
155
|
@plugin_context = PluginContext.new(config, pal, self)
|
155
156
|
@plugins = {}
|
156
157
|
@pal = pal
|
158
|
+
@load_plugins = load_plugins
|
157
159
|
@unknown = Set.new
|
158
160
|
@resolution_stack = []
|
159
161
|
@unresolved_plugin_configs = config.plugins.dup
|
@@ -176,6 +178,8 @@ module Bolt
|
|
176
178
|
end
|
177
179
|
|
178
180
|
def add_ruby_plugin(plugin_name)
|
181
|
+
raise PluginError::LoadingDisabled, plugin_name unless @load_plugins
|
182
|
+
|
179
183
|
cls_name = Bolt::Util.snake_name_to_class_name(plugin_name)
|
180
184
|
filename = "bolt/plugin/#{plugin_name}"
|
181
185
|
require filename
|
@@ -192,10 +196,17 @@ module Bolt
|
|
192
196
|
def add_module_plugin(plugin_name)
|
193
197
|
opts = {
|
194
198
|
context: @plugin_context,
|
199
|
+
# Make sure that the plugin's config is validated _before_ the unknown-plugin
|
200
|
+
# and loading-disabled checks. This way, we can fail early on invalid plugin
|
201
|
+
# config instead of _after_ loading the modulepath (which can be expensive).
|
195
202
|
config: config_for_plugin(plugin_name)
|
196
203
|
}
|
197
204
|
|
198
|
-
|
205
|
+
mod = modules[plugin_name]
|
206
|
+
raise PluginError::Unknown, plugin_name unless mod&.plugin?
|
207
|
+
raise PluginError::LoadingDisabled, plugin_name unless @load_plugins
|
208
|
+
|
209
|
+
plugin = Bolt::Plugin::Module.load(mod, opts)
|
199
210
|
add_plugin(plugin)
|
200
211
|
end
|
201
212
|
|
@@ -284,6 +295,16 @@ module Bolt
|
|
284
295
|
# Evaluates a single reference. The value returned may be another
|
285
296
|
# reference.
|
286
297
|
def resolve_single_reference(reference)
|
298
|
+
plugin_cache = if cache?(reference)
|
299
|
+
cache = Bolt::Plugin::Cache.new(reference,
|
300
|
+
@config.project.cache_file,
|
301
|
+
@config.plugin_cache)
|
302
|
+
entry = cache.read_and_clean_cache
|
303
|
+
return entry unless entry.nil?
|
304
|
+
|
305
|
+
cache
|
306
|
+
end
|
307
|
+
|
287
308
|
plugin_name = reference['_plugin']
|
288
309
|
hook = get_hook(plugin_name, :resolve_reference)
|
289
310
|
|
@@ -295,16 +316,24 @@ module Bolt
|
|
295
316
|
|
296
317
|
validate_proc.call(reference)
|
297
318
|
|
298
|
-
begin
|
319
|
+
result = begin
|
299
320
|
# Evaluate the plugin and then recursively evaluate any plugin returned by it.
|
300
321
|
hook.call(reference)
|
301
322
|
rescue StandardError => e
|
302
323
|
loc = "resolve_reference in #{plugin_name}"
|
303
324
|
raise PluginError::ExecutionError.new(e.message, plugin_name, loc)
|
304
325
|
end
|
326
|
+
|
327
|
+
plugin_cache.write_cache(result) if cache?(reference)
|
328
|
+
|
329
|
+
result
|
305
330
|
end
|
306
331
|
private :resolve_single_reference
|
307
332
|
|
333
|
+
private def cache?(reference)
|
334
|
+
reference.key?('_cache') || @config.plugin_cache.key?('ttl')
|
335
|
+
end
|
336
|
+
|
308
337
|
# Checks whether a given value is a _plugin reference
|
309
338
|
def reference?(input)
|
310
339
|
input.is_a?(Hash) && input.key?('_plugin')
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'bolt/error'
|
5
|
+
require 'bolt/util'
|
6
|
+
|
7
|
+
module Bolt
|
8
|
+
class Plugin
|
9
|
+
class Cache
|
10
|
+
attr_reader :reference, :cache_file, :default_config, :id
|
11
|
+
|
12
|
+
def initialize(reference, cache_file, default_config)
|
13
|
+
@reference = reference
|
14
|
+
@cache_file = cache_file
|
15
|
+
@default_config = default_config
|
16
|
+
end
|
17
|
+
|
18
|
+
def read_and_clean_cache
|
19
|
+
return if ttl == 0
|
20
|
+
validate
|
21
|
+
|
22
|
+
# Luckily we don't need to use a serious hash algorithm
|
23
|
+
require 'digest/bubblebabble'
|
24
|
+
r = reference.reject { |k, _| k == '_cache' }.sort.to_s
|
25
|
+
@id = Digest::SHA2.bubblebabble(r)[0..20]
|
26
|
+
|
27
|
+
unmodified = true
|
28
|
+
# First remove any cache entries past their ttl
|
29
|
+
# This prevents removing plugins from leaving orphaned cache entries
|
30
|
+
cache.delete_if do |_, entry|
|
31
|
+
expired = Time.now - Time.parse(entry['mtime']) >= entry['ttl']
|
32
|
+
unmodified = false if expired
|
33
|
+
expired
|
34
|
+
end
|
35
|
+
File.write(cache_file, cache.to_json) unless cache.empty? || unmodified
|
36
|
+
|
37
|
+
cache.dig(id, 'result')
|
38
|
+
end
|
39
|
+
|
40
|
+
private def cache
|
41
|
+
@cache ||= Bolt::Util.read_optional_json_file(@cache_file, 'cache')
|
42
|
+
end
|
43
|
+
|
44
|
+
def write_cache(result)
|
45
|
+
cache.merge!({ id => { 'result' => result,
|
46
|
+
'mtime' => Time.now,
|
47
|
+
'ttl' => ttl } })
|
48
|
+
FileUtils.touch(cache_file)
|
49
|
+
File.write(cache_file, cache.to_json)
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate
|
53
|
+
# The default cache `plugin-cache` will be validated by the config
|
54
|
+
# validator
|
55
|
+
return if reference['_cache'].nil?
|
56
|
+
r = reference['_cache']
|
57
|
+
unless r.is_a?(Hash)
|
58
|
+
raise Bolt::ValidationError,
|
59
|
+
"_cache must be a Hash, received #{r.class}: #{r.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
unless r.key?('ttl')
|
63
|
+
raise Bolt::ValidationError, "_cache must set 'ttl' key."
|
64
|
+
end
|
65
|
+
|
66
|
+
unless r['ttl'] >= 0
|
67
|
+
raise Bolt::ValidationError, "'ttl' key under '_cache' must be a minimum of 0."
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private def ttl
|
72
|
+
@ttl ||= reference.dig('_cache', 'ttl') || default_config['ttl']
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|