bolt 1.34.0 → 1.35.0
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/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -1
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +16 -3
- data/lib/bolt/config.rb +1 -1
- data/lib/bolt/inventory.rb +1 -1
- data/lib/bolt/inventory/group.rb +1 -1
- data/lib/bolt/inventory/group2.rb +232 -216
- data/lib/bolt/inventory/inventory2.rb +64 -219
- data/lib/bolt/inventory/target.rb +227 -0
- data/lib/bolt/module.rb +4 -4
- data/lib/bolt/plugin.rb +0 -3
- data/lib/bolt/plugin/module.rb +52 -5
- data/lib/bolt/plugin/task.rb +5 -1
- data/lib/bolt/target.rb +24 -46
- data/lib/bolt/util.rb +18 -0
- data/lib/bolt/version.rb +1 -1
- metadata +3 -5
- data/lib/bolt/plugin/aws_inventory.rb +0 -102
- data/lib/bolt/plugin/terraform.rb +0 -175
- data/lib/bolt/plugin/vault.rb +0 -206
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 795f28f4c6cb07e73c21931930b94859b00965416a800746a6e974eade2e637e
|
4
|
+
data.tar.gz: 3d3becef8defffea7e2db0218bac41d9a8215d84ec9617da941378fb7d42809c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80cd0fe982cf6097316e34703627e4da6aaadb1bd58070c154d96b550d8d7c25528842128771f7600a4cb68fdf7f400987dd687d1a26f3cabea32695ec4b24ec
|
7
|
+
data.tar.gz: 8e2ec2983b1d6d78d17170f7090a4628be18f6e970332ee627c0f0c474d1a2dd7a6ac8575efec374cd77afc65ce3399b5c51b82791e3ed98174a697c718ef93b
|
@@ -33,7 +33,7 @@ Puppet::Functions.create_function(:apply_prep) do
|
|
33
33
|
|
34
34
|
def get_task(name, params = {})
|
35
35
|
tasksig = script_compiler.task_signature(name)
|
36
|
-
raise Bolt::Error.new("#{name} could not be found", 'bolt/apply-prep') unless tasksig
|
36
|
+
raise Bolt::Error.new("Task '#{name}' could not be found", 'bolt/apply-prep') unless tasksig
|
37
37
|
|
38
38
|
errors = []
|
39
39
|
unless tasksig.runnable_with?(params) { |msg| errors << msg }
|
@@ -2,17 +2,30 @@
|
|
2
2
|
|
3
3
|
# Repeat the block until it returns a truthy value. Returns the value.
|
4
4
|
Puppet::Functions.create_function(:'ctrl::do_until') do
|
5
|
+
# @param options Additional options: 'until'
|
5
6
|
# @example Run a task until it succeeds
|
6
7
|
# ctrl::do_until() || {
|
7
|
-
# run_task('test', $target, _catch_errors => true).ok
|
8
|
+
# run_task('test', $target, _catch_errors => true).ok()
|
8
9
|
# }
|
10
|
+
#
|
11
|
+
# @example Run a task until it succeeds or fails 10 times
|
12
|
+
# ctrl::do_until('limit' => 10) || {
|
13
|
+
# run_task('test', $target, _catch_errors => true).ok()
|
14
|
+
# }
|
15
|
+
#
|
9
16
|
dispatch :do_until do
|
17
|
+
optional_param 'Hash[String[1], Any]', :options
|
10
18
|
block_param
|
11
19
|
end
|
12
20
|
|
13
|
-
def do_until
|
21
|
+
def do_until(options = { 'limit' => 0 })
|
14
22
|
Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
|
15
|
-
|
23
|
+
limit = options['limit']
|
24
|
+
i = 0
|
25
|
+
until (x = yield)
|
26
|
+
i += 1
|
27
|
+
break if limit != 0 && i >= limit
|
28
|
+
end
|
16
29
|
x
|
17
30
|
end
|
18
31
|
end
|
data/lib/bolt/config.rb
CHANGED
@@ -71,7 +71,7 @@ module Bolt
|
|
71
71
|
@save_rerun = true
|
72
72
|
@puppetfile_config = {}
|
73
73
|
@plugins = {}
|
74
|
-
@plugin_hooks = { 'puppet_library' => { 'plugin' => '
|
74
|
+
@plugin_hooks = { 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }
|
75
75
|
|
76
76
|
# add an entry for the default console logger
|
77
77
|
@log = { 'console' => {} }
|
data/lib/bolt/inventory.rb
CHANGED
data/lib/bolt/inventory/group.rb
CHANGED
@@ -153,7 +153,7 @@ module Bolt
|
|
153
153
|
@nodes.each_key do |n|
|
154
154
|
# Require nodes to be parseable as a Target.
|
155
155
|
begin
|
156
|
-
Target.new(n)
|
156
|
+
Bolt::Target.new(n)
|
157
157
|
rescue Bolt::ParseError => e
|
158
158
|
@logger.debug(e)
|
159
159
|
raise ValidationError.new("Invalid node name #{n}", @name)
|
@@ -2,153 +2,164 @@
|
|
2
2
|
|
3
3
|
require 'bolt/inventory/group'
|
4
4
|
require 'bolt/inventory/inventory2'
|
5
|
+
require 'bolt/inventory/target'
|
5
6
|
|
6
7
|
module Bolt
|
7
8
|
class Inventory
|
8
9
|
class Group2
|
9
|
-
attr_accessor :name, :
|
10
|
-
:config, :facts, :vars, :features, :plugin_hooks
|
10
|
+
attr_accessor :name, :groups
|
11
11
|
|
12
12
|
# THESE are duplicates with the old groups for now.
|
13
13
|
# Regex used to validate group names and target aliases.
|
14
14
|
NAME_REGEX = /\A[a-z0-9_][a-z0-9_-]*\Z/.freeze
|
15
15
|
|
16
|
-
DATA_KEYS = %w[
|
17
|
-
TARGET_KEYS = DATA_KEYS + %w[alias uri]
|
18
|
-
GROUP_KEYS = DATA_KEYS + %w[groups targets]
|
16
|
+
DATA_KEYS = %w[config facts vars features plugin_hooks].freeze
|
17
|
+
TARGET_KEYS = DATA_KEYS + %w[name alias uri]
|
18
|
+
GROUP_KEYS = DATA_KEYS + %w[name groups targets]
|
19
19
|
CONFIG_KEYS = Bolt::TRANSPORTS.keys.map(&:to_s) + ['transport']
|
20
20
|
|
21
|
-
def initialize(
|
21
|
+
def initialize(input, plugins)
|
22
22
|
@logger = Logging.logger[self]
|
23
|
-
raise ValidationError.new("Expected group to be a Hash, not #{data.class}", nil) unless data.is_a?(Hash)
|
24
|
-
raise ValidationError.new("Cannot set group with plugin", nil) if data.key?('_plugin')
|
25
|
-
raise ValidationError.new("Group does not have a name", nil) unless data.key?('name')
|
26
23
|
@plugins = plugins
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
input = resolve_top_level_references(input) if reference?(input)
|
26
|
+
|
27
|
+
raise ValidationError.new("Group does not have a name", nil) unless input.key?('name')
|
28
|
+
|
29
|
+
@name = resolve_references(input['name'])
|
31
30
|
|
32
|
-
@name = data['name']
|
33
31
|
raise ValidationError.new("Group name must be a String, not #{@name.inspect}", nil) unless @name.is_a?(String)
|
34
32
|
raise ValidationError.new("Invalid group name #{@name}", @name) unless @name =~ NAME_REGEX
|
35
33
|
|
36
|
-
|
37
|
-
if data.key?('target-lookups')
|
38
|
-
msg = "'target-lookups' are no longer a separate key. Merge 'target-lookups' and 'targets' lists and replace 'plugin' with '_plugin'" # rubocop:disable Metrics/LineLength
|
39
|
-
raise ValidationError.new(msg, @name)
|
40
|
-
end
|
34
|
+
validate_group_input(input)
|
41
35
|
|
42
|
-
|
43
|
-
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in group #{@name}"
|
44
|
-
@logger.warn(msg)
|
45
|
-
end
|
36
|
+
@input = input
|
46
37
|
|
47
|
-
@
|
48
|
-
@facts = fetch_value(data, 'facts', Hash)
|
49
|
-
@features = fetch_value(data, 'features', Array)
|
50
|
-
@plugin_hooks = fetch_value(data, 'plugin_hooks', Hash)
|
38
|
+
validate_data_keys(@input)
|
51
39
|
|
52
|
-
|
40
|
+
targets = resolve_top_level_references(input.fetch('targets', []))
|
53
41
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
targets = fetch_value(data, 'targets', Array)
|
60
|
-
groups = fetch_value(data, 'groups', Array)
|
61
|
-
|
62
|
-
@targets = {}
|
42
|
+
@unresolved_targets = {}
|
43
|
+
@resolved_targets = {}
|
44
|
+
@targets = Set.new
|
63
45
|
# @target_objects = {}
|
64
46
|
@aliases = {}
|
65
|
-
@
|
47
|
+
@string_targets = []
|
66
48
|
|
67
|
-
targets.each do |target|
|
68
|
-
# If target is a string, it can
|
69
|
-
#
|
70
|
-
#
|
49
|
+
Array(targets).each do |target|
|
50
|
+
# If target is a string, it can either be trivially defining a target
|
51
|
+
# or it could be a name/alias of a target defined in another group.
|
52
|
+
# We can't tell the difference until all groups have been resolved,
|
53
|
+
# so we store the string on its own here and process it later.
|
71
54
|
if target.is_a?(String)
|
72
|
-
@
|
55
|
+
@string_targets << target
|
73
56
|
# Handle plugins at this level so that lookups cannot trigger recursive lookups
|
74
57
|
elsif target.is_a?(Hash)
|
75
|
-
|
76
|
-
lookup_targets(target)
|
77
|
-
else
|
78
|
-
add_target(target)
|
79
|
-
end
|
58
|
+
add_target_definition(target)
|
80
59
|
else
|
81
60
|
raise ValidationError.new("Node entry must be a String or Hash, not #{target.class}", @name)
|
82
61
|
end
|
83
62
|
end
|
84
63
|
|
85
|
-
|
64
|
+
groups = input.fetch('groups', [])
|
65
|
+
# 'groups' can be a _plugin reference, in which case we want to resolve
|
66
|
+
# it. That can itself return a reference, so we want to keep resolving
|
67
|
+
# them until we have a value. We don't just use resolve_references
|
68
|
+
# though, since that will resolve any nested references and we want to
|
69
|
+
# leave it to the group to do that lazily.
|
70
|
+
groups = resolve_top_level_references(groups)
|
71
|
+
|
72
|
+
@groups = Array(groups).map { |g| Group2.new(g, plugins) }
|
86
73
|
end
|
87
74
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
75
|
+
# Evaluate all _plugin references in a data structure. Leaves are
|
76
|
+
# evaluated and then their parents are evaluated with references replaced
|
77
|
+
# by their values. If the result of a reference contains more references,
|
78
|
+
# they are resolved again before continuing to ascend the tree. The final
|
79
|
+
# result will not contain any references.
|
80
|
+
def resolve_references(data)
|
81
|
+
Bolt::Util.postwalk_vals(data) do |value|
|
82
|
+
reference?(value) ? resolve_references(resolve_single_reference(value)) : value
|
95
83
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
84
|
+
end
|
85
|
+
private :resolve_references
|
86
|
+
|
87
|
+
# Iteratively resolves "top-level" references until the result no longer
|
88
|
+
# has top-level references. A top-level reference is one which is not
|
89
|
+
# contained within another hash. It may be either the actual top-level
|
90
|
+
# result or arbitrarily nested within arrays. If parameters of the
|
91
|
+
# reference are themselves references, they will be looked. Any remaining
|
92
|
+
# references nested inside the result will *not* be evaluated once the
|
93
|
+
# top-level result is not a reference. This is used to resolve the
|
94
|
+
# `targets` and `groups` keys which are allowed to be references or
|
95
|
+
# arrays of references, but which may return data with nested references
|
96
|
+
# that should be resolved lazily. The end result will either be a single
|
97
|
+
# hash or a flat array of hashes.
|
98
|
+
def resolve_top_level_references(data)
|
99
|
+
if data.is_a?(Array)
|
100
|
+
data.flat_map { |elem| resolve_top_level_references(elem) }
|
101
|
+
elsif reference?(data)
|
102
|
+
partially_resolved = data.map do |k, v|
|
103
|
+
[k, resolve_references(v)]
|
104
|
+
end.to_h
|
105
|
+
fully_resolved = resolve_single_reference(partially_resolved)
|
106
|
+
# The top-level reference may have returned more references, so repeat the process
|
107
|
+
resolve_top_level_references(fully_resolved)
|
108
|
+
else
|
109
|
+
data
|
99
110
|
end
|
100
111
|
end
|
101
|
-
private :
|
102
|
-
|
103
|
-
def config_only_plugin(data)
|
104
|
-
Bolt::Util.walk_vals(data) do |value|
|
105
|
-
if value.is_a?(Hash) && value.include?('_plugin')
|
106
|
-
plugin_name = value['_plugin']
|
107
|
-
begin
|
108
|
-
hook = @plugins.get_hook(plugin_name, :resolve_reference)
|
109
|
-
rescue Bolt::Plugin::PluginError => e
|
110
|
-
raise ValidationError.new(e.message, @name)
|
111
|
-
end
|
112
|
+
private :resolve_top_level_references
|
112
113
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
# Evaluates a single reference. The value returned may be another
|
115
|
+
# reference.
|
116
|
+
def resolve_single_reference(reference)
|
117
|
+
plugin_name = reference['_plugin']
|
118
|
+
begin
|
119
|
+
hook = @plugins.get_hook(plugin_name, :resolve_reference)
|
120
|
+
rescue Bolt::Plugin::PluginError => e
|
121
|
+
raise ValidationError.new(e.message, @name)
|
122
|
+
end
|
123
|
+
|
124
|
+
begin
|
125
|
+
validate_proc = @plugins.get_hook(plugin_name, :validate_resolve_reference)
|
126
|
+
rescue Bolt::Plugin::PluginError
|
127
|
+
validate_proc = proc { |*args| }
|
128
|
+
end
|
118
129
|
|
119
|
-
|
130
|
+
validate_proc.call(reference)
|
120
131
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
128
|
-
end
|
129
|
-
else
|
130
|
-
value
|
131
|
-
end
|
132
|
+
begin
|
133
|
+
# Evaluate the plugin and then recursively evaluate any plugin returned by it.
|
134
|
+
hook.call(reference)
|
135
|
+
rescue StandardError => e
|
136
|
+
loc = "resolve_reference in #{@name}"
|
137
|
+
raise Bolt::Plugin::PluginError::ExecutionError.new(e.message, plugin_name, loc)
|
132
138
|
end
|
133
139
|
end
|
134
|
-
private :
|
140
|
+
private :resolve_single_reference
|
141
|
+
|
142
|
+
# Checks whether a given value is a _plugin reference
|
143
|
+
def reference?(input)
|
144
|
+
input.is_a?(Hash) && input.key?('_plugin')
|
145
|
+
end
|
135
146
|
|
136
147
|
def target_data(target_name)
|
137
|
-
if (
|
138
|
-
|
139
|
-
|
140
|
-
'
|
141
|
-
'
|
142
|
-
'plugin_hooks' => data['plugin_hooks'] || {},
|
143
|
-
# This allows us to determine if a target was found?
|
144
|
-
'name' => data['name'] || nil,
|
145
|
-
'uri' => data['uri'] || nil,
|
148
|
+
if @unresolved_targets.key?(target_name)
|
149
|
+
target = @unresolved_targets.delete(target_name)
|
150
|
+
resolved_data = resolve_data_keys(target, target_name).merge(
|
151
|
+
'name' => target['name'],
|
152
|
+
'uri' => target['uri'],
|
146
153
|
# groups come from group_data
|
147
|
-
'groups' => []
|
154
|
+
'groups' => []
|
155
|
+
)
|
156
|
+
@resolved_targets[target_name] = resolved_data
|
157
|
+
else
|
158
|
+
@resolved_targets[target_name]
|
148
159
|
end
|
149
160
|
end
|
150
161
|
|
151
|
-
def
|
162
|
+
def add_target_definition(target)
|
152
163
|
# This check ensures target lookup plugins do not returns bare strings.
|
153
164
|
# Remove it if we decide to allows task plugins to return string node
|
154
165
|
# names.
|
@@ -156,12 +167,9 @@ module Bolt
|
|
156
167
|
raise ValidationError.new("Node entry must be a Hash, not #{target.class}", @name)
|
157
168
|
end
|
158
169
|
|
159
|
-
|
160
|
-
|
161
|
-
target
|
162
|
-
next if k == 'config'
|
163
|
-
validate_config_plugin(v, k, @name)
|
164
|
-
end
|
170
|
+
target['name'] = resolve_references(target['name']) if target.key?('name')
|
171
|
+
target['uri'] = resolve_references(target['uri']) if target.key?('uri')
|
172
|
+
target['alias'] = resolve_references(target['alias']) if target.key?('alias')
|
165
173
|
|
166
174
|
t_name = target['name'] || target['uri']
|
167
175
|
|
@@ -173,7 +181,7 @@ module Bolt
|
|
173
181
|
raise ValidationError.new("Target name must be ASCII characters: #{target}", @name)
|
174
182
|
end
|
175
183
|
|
176
|
-
if
|
184
|
+
if local_targets.include?(t_name)
|
177
185
|
@logger.warn("Ignoring duplicate target in #{@name}: #{target}")
|
178
186
|
return
|
179
187
|
end
|
@@ -183,17 +191,7 @@ module Bolt
|
|
183
191
|
@logger.warn(msg)
|
184
192
|
end
|
185
193
|
|
186
|
-
|
187
|
-
raise ValidationError.new("Invalid configuration for target: #{t_name}", @name)
|
188
|
-
end
|
189
|
-
|
190
|
-
config_keys = target['config']&.keys || []
|
191
|
-
unless (unexpected_keys = config_keys - CONFIG_KEYS).empty?
|
192
|
-
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for target #{t_name}"
|
193
|
-
@logger.warn(msg)
|
194
|
-
end
|
195
|
-
|
196
|
-
target['config'] = config_only_plugin(target['config'])
|
194
|
+
validate_data_keys(target, t_name)
|
197
195
|
|
198
196
|
if target.include?('alias')
|
199
197
|
aliases = target['alias']
|
@@ -213,24 +211,11 @@ module Bolt
|
|
213
211
|
end
|
214
212
|
end
|
215
213
|
|
216
|
-
@
|
214
|
+
@unresolved_targets[t_name] = target
|
217
215
|
end
|
218
216
|
|
219
|
-
def
|
220
|
-
|
221
|
-
hook = @plugins.get_hook(lookup['_plugin'], :resolve_reference)
|
222
|
-
rescue Bolt::Plugin::PluginError => e
|
223
|
-
raise ValidationError.new(e.message, @name)
|
224
|
-
end
|
225
|
-
|
226
|
-
begin
|
227
|
-
targets = hook.call(lookup)
|
228
|
-
rescue StandardError => e
|
229
|
-
loc = "resolve_reference in #{@name}"
|
230
|
-
raise Bolt::Plugin::PluginError::ExecutionError.new(e.message, lookup['_plugin'], loc)
|
231
|
-
end
|
232
|
-
|
233
|
-
targets.each { |target| add_target(target) }
|
217
|
+
def add_target(target)
|
218
|
+
@resolved_targets[target.name] = { 'name' => target.name }
|
234
219
|
end
|
235
220
|
|
236
221
|
def data_merge(data1, data2)
|
@@ -253,37 +238,30 @@ module Bolt
|
|
253
238
|
}
|
254
239
|
end
|
255
240
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
if target_names.include?(name_or_alias)
|
268
|
-
@targets[name_or_alias] = { 'name' => name_or_alias }
|
269
|
-
elsif (target_name = aliases[name_or_alias])
|
270
|
-
if @targets.include?(target_name)
|
271
|
-
@logger.warn("Ignoring duplicate target in #{@name}: #{target_name}")
|
241
|
+
def resolve_string_targets(aliases, known_targets)
|
242
|
+
@string_targets.each do |string_target|
|
243
|
+
# If this is the name of a target defined elsewhere, then insert the
|
244
|
+
# target into this group as just a name. Otherwise, add a new target
|
245
|
+
# with the string as the URI.
|
246
|
+
if known_targets.include?(string_target)
|
247
|
+
@unresolved_targets[string_target] = { 'name' => string_target }
|
248
|
+
# If this is an alias for an existing target, then add it to this group
|
249
|
+
elsif (canonical_name = aliases[string_target])
|
250
|
+
if local_targets.include?(canonical_name)
|
251
|
+
@logger.warn("Ignoring duplicate target in #{@name}: #{canonical_name}")
|
272
252
|
else
|
273
|
-
@
|
253
|
+
@unresolved_targets[canonical_name] = { 'name' => canonical_name }
|
274
254
|
end
|
255
|
+
# If it's not the name or alias of an existing target, then make a
|
256
|
+
# new target using the string as the URI
|
257
|
+
elsif local_targets.include?(string_target)
|
258
|
+
@logger.warn("Ignoring duplicate target in #{@name}: #{string_target}")
|
275
259
|
else
|
276
|
-
|
277
|
-
|
278
|
-
if @targets.include?(target_name)
|
279
|
-
@logger.warn("Ignoring duplicate target in #{@name}: #{target_name}")
|
280
|
-
else
|
281
|
-
@targets[target_name] = { 'uri' => target_name }
|
282
|
-
end
|
260
|
+
@unresolved_targets[string_target] = { 'uri' => string_target }
|
283
261
|
end
|
284
262
|
end
|
285
263
|
|
286
|
-
@groups.each { |g| g.
|
264
|
+
@groups.each { |g| g.resolve_string_targets(aliases, known_targets) }
|
287
265
|
end
|
288
266
|
|
289
267
|
private def alias_conflict(name, target1, target2)
|
@@ -302,50 +280,73 @@ module Bolt
|
|
302
280
|
"Node name #{name} conflicts with alias of the same name"
|
303
281
|
end
|
304
282
|
|
305
|
-
def
|
283
|
+
def validate_group_input(input)
|
284
|
+
raise ValidationError.new("Expected group to be a Hash, not #{input.class}", nil) unless input.is_a?(Hash)
|
285
|
+
|
286
|
+
# DEPRECATION : remove this before finalization
|
287
|
+
if input.key?('target-lookups')
|
288
|
+
msg = "'target-lookups' are no longer a separate key. Merge 'target-lookups' and 'targets' lists and replace 'plugin' with '_plugin'" # rubocop:disable Metrics/LineLength
|
289
|
+
raise ValidationError.new(msg, @name)
|
290
|
+
end
|
291
|
+
|
292
|
+
unless (unexpected_keys = input.keys - GROUP_KEYS).empty?
|
293
|
+
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in group #{@name}"
|
294
|
+
@logger.warn(msg)
|
295
|
+
end
|
296
|
+
|
297
|
+
Bolt::Util.walk_keys(input) do |key|
|
298
|
+
if reference?(key)
|
299
|
+
raise ValidationError.new("Group keys cannot be specified as _plugin references", @name)
|
300
|
+
else
|
301
|
+
key
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def validate(used_group_names = Set.new, used_target_names = Set.new, used_aliases = {})
|
306
307
|
# Test if this group name conflicts with anything used before.
|
307
|
-
raise ValidationError.new("Tried to redefine group #{@name}", @name) if
|
308
|
-
raise ValidationError.new(group_target_conflict(@name), @name) if
|
309
|
-
raise ValidationError.new(group_alias_conflict(@name), @name) if
|
308
|
+
raise ValidationError.new("Tried to redefine group #{@name}", @name) if used_group_names.include?(@name)
|
309
|
+
raise ValidationError.new(group_target_conflict(@name), @name) if used_target_names.include?(@name)
|
310
|
+
raise ValidationError.new(group_alias_conflict(@name), @name) if used_aliases.include?(@name)
|
310
311
|
|
311
|
-
|
312
|
+
used_group_names << @name
|
312
313
|
|
313
314
|
# Collect target names and aliases into a list used to validate that subgroups don't conflict.
|
314
315
|
# Used names validate that previously used group names don't conflict with new target names/aliases.
|
315
|
-
@
|
316
|
+
@unresolved_targets.merge(@resolved_targets).each do |t_name, t_data|
|
316
317
|
# Require targets to be parseable as a Target.
|
317
318
|
begin
|
318
319
|
# Catch malformed URI here
|
319
|
-
Bolt::Inventory::
|
320
|
+
Bolt::Inventory::Target.parse_uri(t_data['uri'])
|
320
321
|
rescue Bolt::ParseError => e
|
321
322
|
@logger.debug(e)
|
322
323
|
raise ValidationError.new("Invalid target uri #{t_data['uri']}", @name)
|
323
324
|
end
|
324
325
|
|
325
|
-
raise ValidationError.new(group_target_conflict(t_name), @name) if
|
326
|
-
if
|
326
|
+
raise ValidationError.new(group_target_conflict(t_name), @name) if used_group_names.include?(t_name)
|
327
|
+
if used_aliases.include?(t_name)
|
327
328
|
raise ValidationError.new(alias_target_conflict(t_name), @name)
|
328
329
|
end
|
329
330
|
|
330
|
-
|
331
|
+
used_target_names << t_name
|
331
332
|
end
|
332
333
|
|
333
334
|
@aliases.each do |n, target|
|
334
|
-
raise ValidationError.new(group_alias_conflict(n), @name) if
|
335
|
-
if
|
335
|
+
raise ValidationError.new(group_alias_conflict(n), @name) if used_group_names.include?(n)
|
336
|
+
if used_target_names.include?(n)
|
336
337
|
raise ValidationError.new(alias_target_conflict(n), @name)
|
337
338
|
end
|
338
339
|
|
339
|
-
if
|
340
|
-
raise ValidationError.new(alias_conflict(n, target,
|
340
|
+
if used_aliases.include?(n)
|
341
|
+
raise ValidationError.new(alias_conflict(n, target, used_aliases[n]), @name)
|
341
342
|
end
|
342
343
|
|
343
|
-
|
344
|
+
used_aliases[n] = target
|
344
345
|
end
|
345
346
|
|
346
347
|
@groups.each do |g|
|
347
348
|
begin
|
348
|
-
g.validate(
|
349
|
+
g.validate(used_group_names, used_target_names, used_aliases)
|
349
350
|
rescue ValidationError => e
|
350
351
|
e.add_parent(@name)
|
351
352
|
raise e
|
@@ -355,35 +356,57 @@ module Bolt
|
|
355
356
|
nil
|
356
357
|
end
|
357
358
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
359
|
+
def resolve_data_keys(data, target = nil)
|
360
|
+
result = {
|
361
|
+
'config' => resolve_references(data.fetch('config', {})),
|
362
|
+
'vars' => resolve_references(data.fetch('vars', {})),
|
363
|
+
'facts' => resolve_references(data.fetch('facts', {})),
|
364
|
+
'features' => resolve_references(data.fetch('features', [])),
|
365
|
+
'plugin_hooks' => resolve_references(data.fetch('plugin_hooks', {}))
|
366
|
+
}
|
367
|
+
validate_data_keys(result, target)
|
368
|
+
result['features'] = Set.new(result['features'].flatten)
|
369
|
+
result
|
370
|
+
end
|
371
|
+
|
372
|
+
def validate_data_keys(data, target = nil)
|
373
|
+
{
|
374
|
+
'config' => Hash,
|
375
|
+
'vars' => Hash,
|
376
|
+
'facts' => Hash,
|
377
|
+
'features' => Array,
|
378
|
+
'plugin_hooks' => Hash
|
379
|
+
}.each do |key, expected_type|
|
380
|
+
next if !data.key?(key) || data[key].is_a?(expected_type) || reference?(data[key])
|
381
|
+
|
382
|
+
msg = +"Expected #{key} to be of type #{expected_type}, not #{data[key].class}"
|
383
|
+
msg << " for target #{target}" if target
|
384
|
+
raise ValidationError.new(msg, @name)
|
385
|
+
end
|
386
|
+
unless reference?(data['config'])
|
387
|
+
unexpected_keys = data.fetch('config', {}).keys - CONFIG_KEYS
|
388
|
+
if unexpected_keys.any?
|
389
|
+
msg = +"Found unexpected key(s) #{unexpected_keys.join(', ')} in config for"
|
390
|
+
msg << " target #{target} in" if target
|
391
|
+
msg << " group #{@name}"
|
392
|
+
@logger.warn(msg)
|
393
|
+
end
|
394
|
+
end
|
363
395
|
end
|
364
396
|
|
365
397
|
def group_data
|
366
|
-
|
367
|
-
'vars' => @vars,
|
368
|
-
'facts' => @facts,
|
369
|
-
'features' => @features,
|
370
|
-
'plugin_hooks' => @plugin_hooks,
|
371
|
-
'groups' => [@name] }
|
398
|
+
@group_data ||= resolve_data_keys(@input).merge('groups' => [@name])
|
372
399
|
end
|
373
400
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
'facts' => {},
|
378
|
-
'features' => [],
|
379
|
-
'plugin_hooks' => {},
|
380
|
-
'groups' => [] }
|
401
|
+
# Returns targets contained directly within the group, ignoring subgroups
|
402
|
+
def local_targets
|
403
|
+
Set.new(@unresolved_targets.keys) + Set.new(@resolved_targets.keys)
|
381
404
|
end
|
382
405
|
|
383
406
|
# Returns all targets contained within the group, which includes targets from subgroups.
|
384
|
-
def
|
385
|
-
@groups.inject(
|
386
|
-
acc.merge(g.
|
407
|
+
def all_targets
|
408
|
+
@groups.inject(local_targets) do |acc, g|
|
409
|
+
acc.merge(g.all_targets)
|
387
410
|
end
|
388
411
|
end
|
389
412
|
|
@@ -401,35 +424,28 @@ module Bolt
|
|
401
424
|
end
|
402
425
|
end
|
403
426
|
|
404
|
-
def local_target_names
|
405
|
-
Set.new(@targets.keys)
|
406
|
-
end
|
407
|
-
private :local_target_names
|
408
|
-
|
409
427
|
def target_collect(target_name)
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
acc
|
415
|
-
end
|
428
|
+
child_data = @groups.map { |group| group.target_collect(target_name) }
|
429
|
+
# Data from earlier groups wins
|
430
|
+
child_result = child_data.inject do |acc, group_data|
|
431
|
+
data_merge(group_data, acc)
|
416
432
|
end
|
417
|
-
|
433
|
+
# Children override the parent
|
434
|
+
data_merge(target_data(target_name), child_result)
|
418
435
|
end
|
419
436
|
|
420
437
|
def group_collect(target_name)
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
acc
|
426
|
-
end
|
438
|
+
child_data = @groups.map { |group| group.group_collect(target_name) }
|
439
|
+
# Data from earlier groups wins
|
440
|
+
child_result = child_data.inject do |acc, group_data|
|
441
|
+
data_merge(group_data, acc)
|
427
442
|
end
|
428
443
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
444
|
+
# If this group has the target or one of the child groups has the
|
445
|
+
# target, return the data, otherwise return nil
|
446
|
+
if child_result || local_targets.include?(target_name)
|
447
|
+
# Children override the parent
|
448
|
+
data_merge(group_data, child_result)
|
433
449
|
end
|
434
450
|
end
|
435
451
|
end
|