bolt 1.16.0 → 1.17.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/lib/bolt/bolt_option_parser.rb +13 -4
- data/lib/bolt/boltdir.rb +2 -1
- data/lib/bolt/cli.rb +31 -13
- data/lib/bolt/config.rb +11 -2
- data/lib/bolt/inventory/group2.rb +77 -77
- data/lib/bolt/inventory/inventory2.rb +32 -20
- data/lib/bolt/rerun.rb +58 -0
- data/lib/bolt/result.rb +2 -0
- data/lib/bolt/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9fe11fc4842de35277e098cfe7740eb274df8aa5a6e1049cab373c31d196612
|
4
|
+
data.tar.gz: 7884fb6bbc0292e0545a9429ba134dc3bbcb5045d86b924391cdafb523eba1f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a9a4298796b099efb223ae4a9277a2a6b589f1f8d27267af0de768494579aa820bc3191338cb6eaa73c0f8a23112c48bef2e2037d5dea72d3b3d5204a0bb4ce
|
7
|
+
data.tar.gz: 89c4a471a4b4c23e71cc1fb25df7fcd29e5b76374eb63564deca6b6a113c7c5c0c052d461c0277f668c1b5c8be644d3796c3de2f5078ce0127e32d4090591ec4
|
@@ -149,6 +149,12 @@ Usage: bolt apply <manifest.pp> [options]
|
|
149
149
|
@query = define('-q', '--query QUERY', 'Query PuppetDB to determine the targets') do |query|
|
150
150
|
@options[:query] = query
|
151
151
|
end.extend(SwitchHider)
|
152
|
+
@rerun = define('--rerun FILTER', 'Retry on nodes from the last run',
|
153
|
+
"'all' all nodes that were part of the last run.",
|
154
|
+
"'failure' nodes that failed in the last run.",
|
155
|
+
"'success' nodes that succeeded in the last run.") do |rerun|
|
156
|
+
@options[:rerun] = rerun
|
157
|
+
end.extend(SwitchHider)
|
152
158
|
define('--noop', 'Execute a task that supports it in noop mode') do |_|
|
153
159
|
@options[:noop] = true
|
154
160
|
end
|
@@ -238,6 +244,9 @@ Usage: bolt apply <manifest.pp> [options]
|
|
238
244
|
end
|
239
245
|
@options[:inventoryfile] = File.expand_path(path)
|
240
246
|
end
|
247
|
+
define('--[no-]save-rerun', 'Whether to update the rerun file after this command.') do |save|
|
248
|
+
@options[:'save-rerun'] = save
|
249
|
+
end
|
241
250
|
|
242
251
|
separator 'Transports:'
|
243
252
|
define('--transport TRANSPORT', TRANSPORTS.keys.map(&:to_s),
|
@@ -282,16 +291,14 @@ Usage: bolt apply <manifest.pp> [options]
|
|
282
291
|
end
|
283
292
|
|
284
293
|
def update
|
285
|
-
# show the --nodes and --
|
286
|
-
@nodes.hide = @query.hide = false
|
294
|
+
# show the --nodes, --query, and --rerun switches by default
|
295
|
+
@nodes.hide = @query.hide = @rerun.hide = false
|
287
296
|
# Don't show the --execute switch except for `apply`
|
288
297
|
@execute.hide = true
|
289
298
|
|
290
299
|
# Update the banner according to the subcommand
|
291
300
|
self.banner = case @options[:subcommand]
|
292
301
|
when 'plan'
|
293
|
-
# don't show the --nodes and --query switches in the plan help
|
294
|
-
@nodes.hide = @query.hide = true
|
295
302
|
PLAN_HELP
|
296
303
|
when 'command'
|
297
304
|
COMMAND_HELP
|
@@ -302,6 +309,8 @@ Usage: bolt apply <manifest.pp> [options]
|
|
302
309
|
when 'file'
|
303
310
|
FILE_HELP
|
304
311
|
when 'puppetfile'
|
312
|
+
# Don't show targeting options for puppetfile
|
313
|
+
@nodes.hide = @query.hide = @rerun.hide = true
|
305
314
|
PUPPETFILE_HELP
|
306
315
|
when 'apply'
|
307
316
|
@execute.hide = false
|
data/lib/bolt/boltdir.rb
CHANGED
@@ -6,7 +6,7 @@ module Bolt
|
|
6
6
|
class Boltdir
|
7
7
|
BOLTDIR_NAME = 'Boltdir'
|
8
8
|
|
9
|
-
attr_reader :path, :config_file, :inventory_file, :modulepath, :hiera_config, :puppetfile
|
9
|
+
attr_reader :path, :config_file, :inventory_file, :modulepath, :hiera_config, :puppetfile, :rerunfile
|
10
10
|
|
11
11
|
def self.default_boltdir
|
12
12
|
Boltdir.new(File.join('~', '.puppetlabs', 'bolt'))
|
@@ -36,6 +36,7 @@ module Bolt
|
|
36
36
|
@modulepath = [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
|
37
37
|
@hiera_config = @path + 'hiera.yaml'
|
38
38
|
@puppetfile = @path + 'Puppetfile'
|
39
|
+
@rerunfile = @path + '.rerun.json'
|
39
40
|
end
|
40
41
|
|
41
42
|
def to_s
|
data/lib/bolt/cli.rb
CHANGED
@@ -15,6 +15,7 @@ require 'bolt/config'
|
|
15
15
|
require 'bolt/error'
|
16
16
|
require 'bolt/executor'
|
17
17
|
require 'bolt/inventory'
|
18
|
+
require 'bolt/rerun'
|
18
19
|
require 'bolt/logger'
|
19
20
|
require 'bolt/outputter'
|
20
21
|
require 'bolt/puppetdb'
|
@@ -117,16 +118,7 @@ module Bolt
|
|
117
118
|
|
118
119
|
# After validation, initialize inventory and targets. Errors here are better to catch early.
|
119
120
|
unless options[:subcommand] == 'puppetfile' || options[:action] == 'show'
|
120
|
-
|
121
|
-
if options[:nodes].any?
|
122
|
-
raise Bolt::CLIError, "Only one of '--nodes' or '--query' may be specified"
|
123
|
-
end
|
124
|
-
nodes = query_puppetdb_nodes(options[:query])
|
125
|
-
options[:targets] = inventory.get_targets(nodes)
|
126
|
-
options[:nodes] = nodes if options[:subcommand] == 'plan'
|
127
|
-
else
|
128
|
-
options[:targets] = inventory.get_targets(options[:nodes])
|
129
|
-
end
|
121
|
+
options[:targets] = get_targets(options)
|
130
122
|
end
|
131
123
|
|
132
124
|
options
|
@@ -135,6 +127,25 @@ module Bolt
|
|
135
127
|
raise e
|
136
128
|
end
|
137
129
|
|
130
|
+
def get_targets(options)
|
131
|
+
target_opts = options.keys.select { |opt| %i[query rerun].include?(opt) }
|
132
|
+
target_opts << :nodes unless options[:nodes].empty?
|
133
|
+
if target_opts.length > 1
|
134
|
+
raise Bolt::CLIError, "Only one of '--nodes', '--rerun', or '--query' may be specified"
|
135
|
+
end
|
136
|
+
|
137
|
+
if options[:query]
|
138
|
+
nodes = query_puppetdb_nodes(options[:query])
|
139
|
+
options[:nodes] = nodes if options[:subcommand] == 'plan'
|
140
|
+
elsif options[:rerun]
|
141
|
+
nodes = rerun.get_targets(options[:rerun])
|
142
|
+
options[:nodes] = nodes if options[:subcommand] == 'plan'
|
143
|
+
else
|
144
|
+
nodes = options[:nodes]
|
145
|
+
end
|
146
|
+
inventory.get_targets(nodes)
|
147
|
+
end
|
148
|
+
|
138
149
|
def validate(options)
|
139
150
|
unless COMMANDS.include?(options[:subcommand])
|
140
151
|
raise Bolt::CLIError,
|
@@ -174,10 +185,10 @@ module Bolt
|
|
174
185
|
end
|
175
186
|
|
176
187
|
if !%w[plan puppetfile].include?(options[:subcommand]) && options[:action] != 'show'
|
177
|
-
if options[:nodes].empty? && options[:query].nil?
|
178
|
-
raise Bolt::CLIError, "Targets must be specified with '--nodes' or '--
|
188
|
+
if options[:nodes].empty? && options[:query].nil? && options[:rerun].nil?
|
189
|
+
raise Bolt::CLIError, "Targets must be specified with '--nodes', '--query' or '--rerun'"
|
179
190
|
elsif options[:nodes].any? && options[:query]
|
180
|
-
raise Bolt::CLIError, "Only one of '--nodes' or '--
|
191
|
+
raise Bolt::CLIError, "Only one of '--nodes', '--query', or '--rerun' may be specified"
|
181
192
|
end
|
182
193
|
end
|
183
194
|
|
@@ -329,6 +340,7 @@ module Bolt
|
|
329
340
|
end
|
330
341
|
end
|
331
342
|
|
343
|
+
rerun.update(results)
|
332
344
|
outputter.print_summary(results, elapsed_time)
|
333
345
|
code = results.ok ? 0 : 2
|
334
346
|
end
|
@@ -380,6 +392,7 @@ module Bolt
|
|
380
392
|
|
381
393
|
# If a non-bolt exception bubbles up the plan won't get finished
|
382
394
|
executor.finish_plan(result)
|
395
|
+
rerun.update(result)
|
383
396
|
outputter.print_plan_result(result)
|
384
397
|
result.ok? ? 0 : 1
|
385
398
|
end
|
@@ -400,6 +413,7 @@ module Bolt
|
|
400
413
|
end
|
401
414
|
|
402
415
|
outputter.print_apply_result(results)
|
416
|
+
rerun.update(results)
|
403
417
|
|
404
418
|
results.ok ? 0 : 1
|
405
419
|
end
|
@@ -465,6 +479,10 @@ module Bolt
|
|
465
479
|
File.stat(path)
|
466
480
|
end
|
467
481
|
|
482
|
+
def rerun
|
483
|
+
@rerun ||= Bolt::Rerun.new(@config.rerunfile, @config.save_rerun)
|
484
|
+
end
|
485
|
+
|
468
486
|
def outputter
|
469
487
|
@outputter ||= Bolt::Outputter.for_format(config.format, config.color, config.trace)
|
470
488
|
end
|
data/lib/bolt/config.rb
CHANGED
@@ -31,8 +31,8 @@ module Bolt
|
|
31
31
|
end
|
32
32
|
|
33
33
|
class Config
|
34
|
-
attr_accessor :concurrency, :format, :trace, :log, :puppetdb, :color,
|
35
|
-
:transport, :transports, :inventoryfile, :compile_concurrency
|
34
|
+
attr_accessor :concurrency, :format, :trace, :log, :puppetdb, :color, :save_rerun,
|
35
|
+
:transport, :transports, :inventoryfile, :compile_concurrency, :boltdir
|
36
36
|
attr_writer :modulepath
|
37
37
|
|
38
38
|
TRANSPORT_OPTIONS = %i[password run-as sudo-password extensions
|
@@ -65,6 +65,7 @@ module Bolt
|
|
65
65
|
@format = 'human'
|
66
66
|
@puppetdb = {}
|
67
67
|
@color = true
|
68
|
+
@save_rerun = true
|
68
69
|
|
69
70
|
# add an entry for the default console logger
|
70
71
|
@log = { 'console' => {} }
|
@@ -147,6 +148,8 @@ module Bolt
|
|
147
148
|
@hiera_config = File.expand_path(data['hiera-config'], @boltdir.path) if data.key?('hiera-config')
|
148
149
|
@compile_concurrency = data['compile-concurrency'] if data.key?('compile-concurrency')
|
149
150
|
|
151
|
+
@save_rerun = data['save-rerun'] if data.key?('save-rerun')
|
152
|
+
|
150
153
|
%w[concurrency format puppetdb color transport].each do |key|
|
151
154
|
send("#{key}=", data[key]) if data.key?(key)
|
152
155
|
end
|
@@ -168,6 +171,8 @@ module Bolt
|
|
168
171
|
send("#{key}=", options[key]) if options.key?(key)
|
169
172
|
end
|
170
173
|
|
174
|
+
@save_rerun = options[:'save-rerun'] if options.key?(:'save-rerun')
|
175
|
+
|
171
176
|
if options[:debug]
|
172
177
|
@log['console'][:level] = :debug
|
173
178
|
elsif options[:verbose]
|
@@ -215,6 +220,10 @@ module Bolt
|
|
215
220
|
[@boltdir.inventory_file]
|
216
221
|
end
|
217
222
|
|
223
|
+
def rerunfile
|
224
|
+
@boltdir.rerunfile
|
225
|
+
end
|
226
|
+
|
218
227
|
def hiera_config
|
219
228
|
@hiera_config || @boltdir.hiera_config
|
220
229
|
end
|
@@ -5,7 +5,7 @@ require 'bolt/inventory/group'
|
|
5
5
|
module Bolt
|
6
6
|
class Inventory
|
7
7
|
class Group2
|
8
|
-
attr_accessor :name, :
|
8
|
+
attr_accessor :name, :targets, :aliases, :name_or_alias, :groups, :config, :rest, :facts, :vars, :features
|
9
9
|
|
10
10
|
# THESE are duplicates with the old groups for now.
|
11
11
|
# Regex used to validate group names and target aliases.
|
@@ -13,7 +13,7 @@ module Bolt
|
|
13
13
|
|
14
14
|
DATA_KEYS = %w[name config facts vars features].freeze
|
15
15
|
NODE_KEYS = DATA_KEYS + %w[alias uri]
|
16
|
-
GROUP_KEYS = DATA_KEYS + %w[groups
|
16
|
+
GROUP_KEYS = DATA_KEYS + %w[groups targets]
|
17
17
|
CONFIG_KEYS = Bolt::TRANSPORTS.keys.map(&:to_s) + ['transport']
|
18
18
|
|
19
19
|
def initialize(data)
|
@@ -41,46 +41,46 @@ module Bolt
|
|
41
41
|
@logger.warn(msg)
|
42
42
|
end
|
43
43
|
|
44
|
-
|
44
|
+
targets = fetch_value(data, 'targets', Array)
|
45
45
|
groups = fetch_value(data, 'groups', Array)
|
46
46
|
|
47
|
-
@
|
47
|
+
@targets = {}
|
48
48
|
@aliases = {}
|
49
|
-
|
50
|
-
unless
|
51
|
-
raise ValidationError.new("Node entry must be a String or Hash, not #{
|
49
|
+
targets.reject { |target| target.is_a?(String) }.each do |target|
|
50
|
+
unless target.is_a?(Hash)
|
51
|
+
raise ValidationError.new("Node entry must be a String or Hash, not #{target.class}", @name)
|
52
52
|
end
|
53
53
|
|
54
|
-
|
54
|
+
target['name'] ||= target['uri']
|
55
55
|
|
56
|
-
if
|
57
|
-
raise ValidationError.new("No name or uri for
|
56
|
+
if target['name'].nil? || target['name'].empty?
|
57
|
+
raise ValidationError.new("No name or uri for target: #{target}", @name)
|
58
58
|
end
|
59
59
|
|
60
|
-
if @
|
61
|
-
@logger.warn("Ignoring duplicate
|
60
|
+
if @targets.include?(target['name'])
|
61
|
+
@logger.warn("Ignoring duplicate target in #{@name}: #{target}")
|
62
62
|
next
|
63
63
|
end
|
64
64
|
|
65
|
-
raise ValidationError.new("Node #{
|
66
|
-
@
|
65
|
+
raise ValidationError.new("Node #{target} does not have a name", @name) unless target['name']
|
66
|
+
@targets[target['name']] = target
|
67
67
|
|
68
|
-
unless (unexpected_keys =
|
69
|
-
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in
|
68
|
+
unless (unexpected_keys = target.keys - NODE_KEYS).empty?
|
69
|
+
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in target #{target['name']}"
|
70
70
|
@logger.warn(msg)
|
71
71
|
end
|
72
|
-
config_keys =
|
72
|
+
config_keys = target['config']&.keys || []
|
73
73
|
unless (unexpected_keys = config_keys - CONFIG_KEYS).empty?
|
74
|
-
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for
|
74
|
+
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for target #{target['name']}"
|
75
75
|
@logger.warn(msg)
|
76
76
|
end
|
77
77
|
|
78
|
-
next unless
|
78
|
+
next unless target.include?('alias')
|
79
79
|
|
80
|
-
aliases =
|
80
|
+
aliases = target['alias']
|
81
81
|
aliases = [aliases] if aliases.is_a?(String)
|
82
82
|
unless aliases.is_a?(Array)
|
83
|
-
msg = "Alias entry on #{
|
83
|
+
msg = "Alias entry on #{target['name']} must be a String or Array, not #{aliases.class}"
|
84
84
|
raise ValidationError.new(msg, @name)
|
85
85
|
end
|
86
86
|
|
@@ -88,26 +88,26 @@ module Bolt
|
|
88
88
|
raise ValidationError.new("Invalid alias #{alia}", @name) unless alia =~ NAME_REGEX
|
89
89
|
|
90
90
|
if (found = @aliases[alia])
|
91
|
-
raise ValidationError.new(alias_conflict(alia, found,
|
91
|
+
raise ValidationError.new(alias_conflict(alia, found, target['name']), @name)
|
92
92
|
end
|
93
|
-
@aliases[alia] =
|
93
|
+
@aliases[alia] = target['name']
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
# If
|
97
|
+
# If target is a string, it can refer to either a target name or alias. Which can't be determined
|
98
98
|
# until all groups have been resolved, and requires a depth-first traversal to categorize them.
|
99
|
-
@name_or_alias =
|
99
|
+
@name_or_alias = targets.select { |target| target.is_a?(String) }
|
100
100
|
|
101
101
|
@groups = groups.map { |g| Group2.new(g) }
|
102
102
|
end
|
103
103
|
|
104
|
-
def
|
105
|
-
if (data = @
|
104
|
+
def target_data(target_name)
|
105
|
+
if (data = @targets[target_name])
|
106
106
|
{ 'config' => data['config'] || {},
|
107
107
|
'vars' => data['vars'] || {},
|
108
108
|
'facts' => data['facts'] || {},
|
109
109
|
'features' => data['features'] || [],
|
110
|
-
# This allows us to determine if a
|
110
|
+
# This allows us to determine if a target was found?
|
111
111
|
'name' => data['name'] || nil,
|
112
112
|
'uri' => data['uri'] || nil,
|
113
113
|
# groups come from group_data
|
@@ -142,78 +142,78 @@ module Bolt
|
|
142
142
|
value
|
143
143
|
end
|
144
144
|
|
145
|
-
def resolve_aliases(aliases,
|
145
|
+
def resolve_aliases(aliases, target_names)
|
146
146
|
@name_or_alias.each do |name_or_alias|
|
147
|
-
# If an alias is found, insert the name into this group. Otherwise use the name as a new
|
148
|
-
if
|
149
|
-
@
|
150
|
-
elsif (
|
151
|
-
if @
|
152
|
-
@logger.warn("Ignoring duplicate
|
147
|
+
# If an alias is found, insert the name into this group. Otherwise use the name as a new target's uri.
|
148
|
+
if target_names.include?(name_or_alias)
|
149
|
+
@targets[name_or_alias] = { 'name' => name_or_alias }
|
150
|
+
elsif (target_name = aliases[name_or_alias])
|
151
|
+
if @targets.include?(target_name)
|
152
|
+
@logger.warn("Ignoring duplicate target in #{@name}: #{target_name}")
|
153
153
|
else
|
154
|
-
@
|
154
|
+
@targets[target_name] = { 'name' => target_name }
|
155
155
|
end
|
156
156
|
else
|
157
|
-
|
157
|
+
target_name = name_or_alias
|
158
158
|
|
159
|
-
if @
|
160
|
-
@logger.warn("Ignoring duplicate
|
159
|
+
if @targets.include?(target_name)
|
160
|
+
@logger.warn("Ignoring duplicate target in #{@name}: #{target_name}")
|
161
161
|
else
|
162
|
-
@
|
162
|
+
@targets[target_name] = { 'uri' => target_name }
|
163
163
|
end
|
164
164
|
end
|
165
165
|
end
|
166
166
|
|
167
|
-
@groups.each { |g| g.resolve_aliases(aliases,
|
167
|
+
@groups.each { |g| g.resolve_aliases(aliases, target_names) }
|
168
168
|
end
|
169
169
|
|
170
|
-
private def alias_conflict(name,
|
171
|
-
"Alias #{name} refers to multiple targets: #{
|
170
|
+
private def alias_conflict(name, target1, target2)
|
171
|
+
"Alias #{name} refers to multiple targets: #{target1} and #{target2}"
|
172
172
|
end
|
173
173
|
|
174
174
|
private def group_alias_conflict(name)
|
175
175
|
"Group #{name} conflicts with alias of the same name"
|
176
176
|
end
|
177
177
|
|
178
|
-
private def
|
179
|
-
"Group #{name} conflicts with
|
178
|
+
private def group_target_conflict(name)
|
179
|
+
"Group #{name} conflicts with target of the same name"
|
180
180
|
end
|
181
181
|
|
182
|
-
private def
|
182
|
+
private def alias_target_conflict(name)
|
183
183
|
"Node name #{name} conflicts with alias of the same name"
|
184
184
|
end
|
185
185
|
|
186
|
-
def validate(used_names = Set.new,
|
186
|
+
def validate(used_names = Set.new, target_names = Set.new, aliased = {}, depth = 0)
|
187
187
|
# Test if this group name conflicts with anything used before.
|
188
188
|
raise ValidationError.new("Tried to redefine group #{@name}", @name) if used_names.include?(@name)
|
189
|
-
raise ValidationError.new(
|
189
|
+
raise ValidationError.new(group_target_conflict(@name), @name) if target_names.include?(@name)
|
190
190
|
raise ValidationError.new(group_alias_conflict(@name), @name) if aliased.include?(@name)
|
191
191
|
|
192
192
|
used_names << @name
|
193
193
|
|
194
|
-
# Collect
|
195
|
-
# Used names validate that previously used group names don't conflict with new
|
196
|
-
@
|
197
|
-
# Require
|
194
|
+
# Collect target names and aliases into a list used to validate that subgroups don't conflict.
|
195
|
+
# Used names validate that previously used group names don't conflict with new target names/aliases.
|
196
|
+
@targets.each_key do |n|
|
197
|
+
# Require targets to be parseable as a Target.
|
198
198
|
begin
|
199
199
|
Target.new(n)
|
200
200
|
rescue Bolt::ParseError => e
|
201
201
|
@logger.debug(e)
|
202
|
-
raise ValidationError.new("Invalid
|
202
|
+
raise ValidationError.new("Invalid target name #{n}", @name)
|
203
203
|
end
|
204
204
|
|
205
|
-
raise ValidationError.new(
|
205
|
+
raise ValidationError.new(group_target_conflict(n), @name) if used_names.include?(n)
|
206
206
|
if aliased.include?(n)
|
207
|
-
raise ValidationError.new(
|
207
|
+
raise ValidationError.new(alias_target_conflict(n), @name)
|
208
208
|
end
|
209
209
|
|
210
|
-
|
210
|
+
target_names << n
|
211
211
|
end
|
212
212
|
|
213
213
|
@aliases.each do |n, target|
|
214
214
|
raise ValidationError.new(group_alias_conflict(n), @name) if used_names.include?(n)
|
215
|
-
if
|
216
|
-
raise ValidationError.new(
|
215
|
+
if target_names.include?(n)
|
216
|
+
raise ValidationError.new(alias_target_conflict(n), @name)
|
217
217
|
end
|
218
218
|
|
219
219
|
if aliased.include?(n)
|
@@ -225,7 +225,7 @@ module Bolt
|
|
225
225
|
|
226
226
|
@groups.each do |g|
|
227
227
|
begin
|
228
|
-
g.validate(used_names,
|
228
|
+
g.validate(used_names, target_names, aliased, depth + 1)
|
229
229
|
rescue ValidationError => e
|
230
230
|
e.add_parent(@name)
|
231
231
|
raise e
|
@@ -237,8 +237,8 @@ module Bolt
|
|
237
237
|
|
238
238
|
# The data functions below expect and return nil or a hash of the schema
|
239
239
|
# { 'config' => Hash , 'vars' => Hash, 'facts' => Hash, 'features' => Array, groups => Array }
|
240
|
-
def data_for(
|
241
|
-
data_merge(group_collect(
|
240
|
+
def data_for(target_name)
|
241
|
+
data_merge(group_collect(target_name), target_collect(target_name))
|
242
242
|
end
|
243
243
|
|
244
244
|
def group_data
|
@@ -257,17 +257,17 @@ module Bolt
|
|
257
257
|
'groups' => [] }
|
258
258
|
end
|
259
259
|
|
260
|
-
# Returns all
|
261
|
-
def
|
262
|
-
@groups.inject(
|
263
|
-
acc.merge(g.
|
260
|
+
# Returns all targets contained within the group, which includes targets from subgroups.
|
261
|
+
def target_names
|
262
|
+
@groups.inject(local_target_names) do |acc, g|
|
263
|
+
acc.merge(g.target_names)
|
264
264
|
end
|
265
265
|
end
|
266
266
|
|
267
|
-
# Returns a mapping of aliases to
|
268
|
-
def
|
267
|
+
# Returns a mapping of aliases to targets contained within the group, which includes subgroups.
|
268
|
+
def target_aliases
|
269
269
|
@groups.inject(@aliases) do |acc, g|
|
270
|
-
acc.merge(g.
|
270
|
+
acc.merge(g.target_aliases)
|
271
271
|
end
|
272
272
|
end
|
273
273
|
|
@@ -278,25 +278,25 @@ module Bolt
|
|
278
278
|
end
|
279
279
|
end
|
280
280
|
|
281
|
-
def
|
282
|
-
Set.new(@
|
281
|
+
def local_target_names
|
282
|
+
Set.new(@targets.keys)
|
283
283
|
end
|
284
|
-
private :
|
284
|
+
private :local_target_names
|
285
285
|
|
286
|
-
def
|
286
|
+
def target_collect(target_name)
|
287
287
|
data = @groups.inject(nil) do |acc, g|
|
288
|
-
if (d = g.
|
288
|
+
if (d = g.target_collect(target_name))
|
289
289
|
data_merge(d, acc)
|
290
290
|
else
|
291
291
|
acc
|
292
292
|
end
|
293
293
|
end
|
294
|
-
data_merge(
|
294
|
+
data_merge(target_data(target_name), data)
|
295
295
|
end
|
296
296
|
|
297
|
-
def group_collect(
|
297
|
+
def group_collect(target_name)
|
298
298
|
data = @groups.inject(nil) do |acc, g|
|
299
|
-
if (d = g.data_for(
|
299
|
+
if (d = g.data_for(target_name))
|
300
300
|
data_merge(d, acc)
|
301
301
|
else
|
302
302
|
acc
|
@@ -305,7 +305,7 @@ module Bolt
|
|
305
305
|
|
306
306
|
if data
|
307
307
|
data_merge(group_data, data)
|
308
|
-
elsif @
|
308
|
+
elsif @targets.include?(target_name)
|
309
309
|
group_data
|
310
310
|
end
|
311
311
|
end
|
@@ -5,6 +5,13 @@ require 'bolt/inventory/group2'
|
|
5
5
|
module Bolt
|
6
6
|
class Inventory
|
7
7
|
class Inventory2
|
8
|
+
# This uses "targets" in the message instead of "nodes"
|
9
|
+
class WildcardError < Bolt::Error
|
10
|
+
def initialize(target)
|
11
|
+
super("Found 0 targets matching wildcard pattern #{target}", 'bolt.inventory/wildcard-error')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
8
15
|
def initialize(data, config = nil, target_vars: {}, target_facts: {}, target_features: {})
|
9
16
|
@logger = Logging.logger[self]
|
10
17
|
# Config is saved to add config options to targets
|
@@ -16,7 +23,7 @@ module Bolt
|
|
16
23
|
@target_facts = target_facts
|
17
24
|
@target_features = target_features
|
18
25
|
|
19
|
-
@groups.resolve_aliases(@groups.
|
26
|
+
@groups.resolve_aliases(@groups.target_aliases, @groups.target_names)
|
20
27
|
collect_groups
|
21
28
|
end
|
22
29
|
|
@@ -33,9 +40,11 @@ module Bolt
|
|
33
40
|
@group_lookup.keys
|
34
41
|
end
|
35
42
|
|
36
|
-
def
|
37
|
-
@groups.
|
43
|
+
def target_names
|
44
|
+
@groups.target_names
|
38
45
|
end
|
46
|
+
# alias for analytics
|
47
|
+
alias node_names target_names
|
39
48
|
|
40
49
|
def get_targets(targets)
|
41
50
|
targets = expand_targets(targets)
|
@@ -51,9 +60,9 @@ module Bolt
|
|
51
60
|
if group_names.include?(desired_group)
|
52
61
|
targets.each do |target|
|
53
62
|
if group_names.include?(target.name)
|
54
|
-
raise ValidationError.new("Group #{target.name} conflicts with
|
63
|
+
raise ValidationError.new("Group #{target.name} conflicts with target of the same name", target.name)
|
55
64
|
end
|
56
|
-
|
65
|
+
add_target(@groups, target, desired_group)
|
57
66
|
end
|
58
67
|
else
|
59
68
|
raise ValidationError.new("Group #{desired_group} does not exist in inventory", nil)
|
@@ -106,8 +115,8 @@ module Bolt
|
|
106
115
|
#### PRIVATE ####
|
107
116
|
#
|
108
117
|
# For debugging only now
|
109
|
-
def groups_in(
|
110
|
-
@groups.data_for(
|
118
|
+
def groups_in(target_name)
|
119
|
+
@groups.data_for(target_name)['groups'] || {}
|
111
120
|
end
|
112
121
|
private :groups_in
|
113
122
|
|
@@ -145,25 +154,25 @@ module Bolt
|
|
145
154
|
private :update_target
|
146
155
|
|
147
156
|
# If target is a group name, expand it to the members of that group.
|
148
|
-
# Else match against
|
157
|
+
# Else match against targets in inventory by name or alias.
|
149
158
|
# If a wildcard string, error if no matches are found.
|
150
159
|
# Else fall back to [target] if no matches are found.
|
151
160
|
def resolve_name(target)
|
152
161
|
if (group = @group_lookup[target])
|
153
|
-
group.
|
162
|
+
group.target_names
|
154
163
|
else
|
155
|
-
# Try to wildcard match
|
164
|
+
# Try to wildcard match targets in inventory
|
156
165
|
# Ignore case because hostnames are generally case-insensitive
|
157
166
|
regexp = Regexp.new("^#{Regexp.escape(target).gsub('\*', '.*?')}$", Regexp::IGNORECASE)
|
158
167
|
|
159
|
-
|
160
|
-
|
168
|
+
targets = @groups.target_names.select { |targ| targ =~ regexp }
|
169
|
+
targets += @groups.target_aliases.select { |target_alias, _target| target_alias =~ regexp }.values
|
161
170
|
|
162
|
-
if
|
171
|
+
if targets.empty?
|
163
172
|
raise(WildcardError, target) if target.include?('*')
|
164
173
|
[target]
|
165
174
|
else
|
166
|
-
|
175
|
+
targets
|
167
176
|
end
|
168
177
|
end
|
169
178
|
end
|
@@ -208,12 +217,15 @@ module Bolt
|
|
208
217
|
end
|
209
218
|
private :set_facts
|
210
219
|
|
211
|
-
def
|
220
|
+
def add_target(current_group, target, desired_group, track = { 'all' => nil })
|
212
221
|
if current_group.name == desired_group
|
213
222
|
# Group to add to is found
|
214
223
|
t_name = target.name
|
215
|
-
# Add target to
|
216
|
-
|
224
|
+
# Add target to targets hash
|
225
|
+
target_hash = { 'name' => t_name }.merge(target.options)
|
226
|
+
target_hash['uri'] = target.uri if target.uri
|
227
|
+
current_group.targets[t_name] = target_hash
|
228
|
+
|
217
229
|
# Inherit facts, vars, and features from hierarchy
|
218
230
|
current_group_data = { facts: current_group.facts,
|
219
231
|
vars: current_group.vars,
|
@@ -229,10 +241,10 @@ module Bolt
|
|
229
241
|
# Recurse on children Groups if not desired_group
|
230
242
|
current_group.groups.each do |child_group|
|
231
243
|
track[child_group.name] = current_group
|
232
|
-
|
244
|
+
add_target(child_group, target, desired_group, track)
|
233
245
|
end
|
234
246
|
end
|
235
|
-
private :
|
247
|
+
private :add_target
|
236
248
|
|
237
249
|
def inherit_data(track, name, data)
|
238
250
|
unless track[name].nil?
|
@@ -250,7 +262,7 @@ module Bolt
|
|
250
262
|
name_opt = {}
|
251
263
|
name_opt['name'] = data['name'] if data['name']
|
252
264
|
|
253
|
-
# If there is no name then this
|
265
|
+
# If there is no name then this target was only referred to as a string.
|
254
266
|
uri = data['uri']
|
255
267
|
uri ||= target_name unless data['name']
|
256
268
|
|
data/lib/bolt/rerun.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'logging'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
class Rerun
|
8
|
+
def initialize(path, save_failures)
|
9
|
+
@path = path
|
10
|
+
@save_failures = save_failures
|
11
|
+
@logger = Logging.logger[self]
|
12
|
+
end
|
13
|
+
|
14
|
+
def data
|
15
|
+
@data ||= JSON.parse(File.read(@path))
|
16
|
+
unless @data.is_a?(Array) && @data.all? { |r| r['target'] && r['status'] }
|
17
|
+
raise Bolt::FileError.new("Missing data in rerun file: #{@path}", @path)
|
18
|
+
end
|
19
|
+
@data
|
20
|
+
rescue JSON::ParserError
|
21
|
+
raise Bolt::FileError.new("Could not parse rerun file: #{@path}", @path)
|
22
|
+
rescue IOError, SystemCallError
|
23
|
+
raise Bolt::FileError.new("Could not read rerun file: #{@path}", @path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_targets(filter)
|
27
|
+
filtered = case filter
|
28
|
+
when 'all'
|
29
|
+
data
|
30
|
+
when 'failure'
|
31
|
+
data.select { |result| result['status'] == 'failure' }
|
32
|
+
when 'success'
|
33
|
+
data.select { |result| result['status'] == 'success' }
|
34
|
+
else
|
35
|
+
raise Bolt::CLIError, "Unexpected option #{filter} for '--retry'"
|
36
|
+
end
|
37
|
+
filtered.map { |result| result['target'] }
|
38
|
+
end
|
39
|
+
|
40
|
+
def update(result_set)
|
41
|
+
unless @save_failures == false
|
42
|
+
if result_set.is_a?(Bolt::PlanResult)
|
43
|
+
result_set = result_set.value
|
44
|
+
result_set = result_set.result_set if result_set.is_a?(Bolt::RunFailure)
|
45
|
+
end
|
46
|
+
|
47
|
+
if result_set.is_a?(Bolt::ResultSet)
|
48
|
+
data = result_set.map { |res| res.status_hash.select { |k, _| %i[target status].include? k } }
|
49
|
+
File.write(@path, data.to_json)
|
50
|
+
elsif File.exist?(@path)
|
51
|
+
FileUtils.rm(@path)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
rescue StandardError => e
|
55
|
+
@logger.warn("Failed to save result to #{@path}: #{e.message}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/bolt/result.rb
CHANGED
data/lib/bolt/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bolt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-04-
|
11
|
+
date: 2019-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -385,6 +385,7 @@ files:
|
|
385
385
|
- lib/bolt/puppetdb/client.rb
|
386
386
|
- lib/bolt/puppetdb/config.rb
|
387
387
|
- lib/bolt/r10k_log_proxy.rb
|
388
|
+
- lib/bolt/rerun.rb
|
388
389
|
- lib/bolt/result.rb
|
389
390
|
- lib/bolt/result_set.rb
|
390
391
|
- lib/bolt/target.rb
|