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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b815f57b892f3750786159c9251e03f34cae09c721a6c18a2ace76d7319324b3
4
- data.tar.gz: 13d51985236eee75d10bd527a8fe4c0328b362175dc9313a84f519967c690d9a
3
+ metadata.gz: a9fe11fc4842de35277e098cfe7740eb274df8aa5a6e1049cab373c31d196612
4
+ data.tar.gz: 7884fb6bbc0292e0545a9429ba134dc3bbcb5045d86b924391cdafb523eba1f9
5
5
  SHA512:
6
- metadata.gz: f904cf6c228379b8f790ed6d102da43244cdc5b0fa8630f04758957e35c083ee9955d09b0d212c22ea5a9e07f989e72f73e88b07f99d984c9427a813386713f3
7
- data.tar.gz: a755bc82082397a067d8a8bdbfc81f7687071bbb2aa993c3a95e0db24feeda4e28c7b34644cbb1a2195f3211918c9de7fafd8c232ce38acd7ce5c42cfd5359da
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 --query switches by default
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
- if options[:query]
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 '--query'"
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 '--query' may be specified"
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, :nodes, :aliases, :name_or_alias, :groups, :config, :rest, :facts, :vars, :features
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 nodes]
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
- nodes = fetch_value(data, 'nodes', Array)
44
+ targets = fetch_value(data, 'targets', Array)
45
45
  groups = fetch_value(data, 'groups', Array)
46
46
 
47
- @nodes = {}
47
+ @targets = {}
48
48
  @aliases = {}
49
- nodes.reject { |node| node.is_a?(String) }.each do |node|
50
- unless node.is_a?(Hash)
51
- raise ValidationError.new("Node entry must be a String or Hash, not #{node.class}", @name)
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
- node['name'] ||= node['uri']
54
+ target['name'] ||= target['uri']
55
55
 
56
- if node['name'].nil? || node['name'].empty?
57
- raise ValidationError.new("No name or uri for node: #{node}", @name)
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 @nodes.include?(node['name'])
61
- @logger.warn("Ignoring duplicate node in #{@name}: #{node}")
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 #{node} does not have a name", @name) unless node['name']
66
- @nodes[node['name']] = node
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 = node.keys - NODE_KEYS).empty?
69
- msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in node #{node['name']}"
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 = node['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 node #{node['name']}"
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 node.include?('alias')
78
+ next unless target.include?('alias')
79
79
 
80
- aliases = node['alias']
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 #{node['name']} must be a String or Array, not #{aliases.class}"
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, node['name']), @name)
91
+ raise ValidationError.new(alias_conflict(alia, found, target['name']), @name)
92
92
  end
93
- @aliases[alia] = node['name']
93
+ @aliases[alia] = target['name']
94
94
  end
95
95
  end
96
96
 
97
- # If node is a string, it can refer to either a node name or alias. Which can't be determined
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 = nodes.select { |node| node.is_a?(String) }
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 node_data(node_name)
105
- if (data = @nodes[node_name])
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 node was found?
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, node_names)
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 node's uri.
148
- if node_names.include?(name_or_alias)
149
- @nodes[name_or_alias] = { 'name' => name_or_alias }
150
- elsif (node_name = aliases[name_or_alias])
151
- if @nodes.include?(node_name)
152
- @logger.warn("Ignoring duplicate node in #{@name}: #{node_name}")
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
- @nodes[node_name] = { 'name' => node_name }
154
+ @targets[target_name] = { 'name' => target_name }
155
155
  end
156
156
  else
157
- node_name = name_or_alias
157
+ target_name = name_or_alias
158
158
 
159
- if @nodes.include?(node_name)
160
- @logger.warn("Ignoring duplicate node in #{@name}: #{node_name}")
159
+ if @targets.include?(target_name)
160
+ @logger.warn("Ignoring duplicate target in #{@name}: #{target_name}")
161
161
  else
162
- @nodes[node_name] = { 'uri' => node_name }
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, node_names) }
167
+ @groups.each { |g| g.resolve_aliases(aliases, target_names) }
168
168
  end
169
169
 
170
- private def alias_conflict(name, node1, node2)
171
- "Alias #{name} refers to multiple targets: #{node1} and #{node2}"
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 group_node_conflict(name)
179
- "Group #{name} conflicts with node of the same name"
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 alias_node_conflict(name)
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, node_names = Set.new, aliased = {}, depth = 0)
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(group_node_conflict(@name), @name) if node_names.include?(@name)
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 node 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 node names/aliases.
196
- @nodes.each_key do |n|
197
- # Require nodes to be parseable as a Target.
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 node name #{n}", @name)
202
+ raise ValidationError.new("Invalid target name #{n}", @name)
203
203
  end
204
204
 
205
- raise ValidationError.new(group_node_conflict(n), @name) if used_names.include?(n)
205
+ raise ValidationError.new(group_target_conflict(n), @name) if used_names.include?(n)
206
206
  if aliased.include?(n)
207
- raise ValidationError.new(alias_node_conflict(n), @name)
207
+ raise ValidationError.new(alias_target_conflict(n), @name)
208
208
  end
209
209
 
210
- node_names << n
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 node_names.include?(n)
216
- raise ValidationError.new(alias_node_conflict(n), @name)
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, node_names, aliased, depth + 1)
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(node_name)
241
- data_merge(group_collect(node_name), node_collect(node_name))
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 nodes contained within the group, which includes nodes from subgroups.
261
- def node_names
262
- @groups.inject(local_node_names) do |acc, g|
263
- acc.merge(g.node_names)
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 nodes contained within the group, which includes subgroups.
268
- def node_aliases
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.node_aliases)
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 local_node_names
282
- Set.new(@nodes.keys)
281
+ def local_target_names
282
+ Set.new(@targets.keys)
283
283
  end
284
- private :local_node_names
284
+ private :local_target_names
285
285
 
286
- def node_collect(node_name)
286
+ def target_collect(target_name)
287
287
  data = @groups.inject(nil) do |acc, g|
288
- if (d = g.node_collect(node_name))
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(node_data(node_name), data)
294
+ data_merge(target_data(target_name), data)
295
295
  end
296
296
 
297
- def group_collect(node_name)
297
+ def group_collect(target_name)
298
298
  data = @groups.inject(nil) do |acc, g|
299
- if (d = g.data_for(node_name))
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 @nodes.include?(node_name)
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.node_aliases, @groups.node_names)
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 node_names
37
- @groups.node_names
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 node of the same name", target.name)
63
+ raise ValidationError.new("Group #{target.name} conflicts with target of the same name", target.name)
55
64
  end
56
- add_node(@groups, target, desired_group)
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(node_name)
110
- @groups.data_for(node_name)['groups'] || {}
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 nodes in inventory by name or alias.
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.node_names
162
+ group.target_names
154
163
  else
155
- # Try to wildcard match nodes in inventory
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
- nodes = @groups.node_names.select { |node| node =~ regexp }
160
- nodes += @groups.node_aliases.select { |target_alias, _node| target_alias =~ regexp }.values
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 nodes.empty?
171
+ if targets.empty?
163
172
  raise(WildcardError, target) if target.include?('*')
164
173
  [target]
165
174
  else
166
- nodes
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 add_node(current_group, target, desired_group, track = { 'all' => nil })
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 nodes hash
216
- current_group.nodes[t_name] = { 'name' => t_name }.merge(target.options)
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
- add_node(child_group, target, desired_group, track)
244
+ add_target(child_group, target, desired_group, track)
233
245
  end
234
246
  end
235
- private :add_node
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 node was only referred to as a string.
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
@@ -91,7 +91,9 @@ module Bolt
91
91
  end
92
92
 
93
93
  def status_hash
94
+ # DEPRECATION: node in status hashes is deprecated and should be removed in 2.0
94
95
  { node: @target.name,
96
+ target: @target.name,
95
97
  type: type,
96
98
  object: object,
97
99
  status: ok? ? 'success' : 'failure',
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.16.0'
4
+ VERSION = '1.17.0'
5
5
  end
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.16.0
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 00:00:00.000000000 Z
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