bolt 1.35.0 → 1.36.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: 795f28f4c6cb07e73c21931930b94859b00965416a800746a6e974eade2e637e
4
- data.tar.gz: 3d3becef8defffea7e2db0218bac41d9a8215d84ec9617da941378fb7d42809c
3
+ metadata.gz: bc29b4aca58aa7208e448120f59687cb5fbdd0cbab78af55861b629420a9fb9e
4
+ data.tar.gz: 46ec4cca8088b69d9196f3c251084bdeadaee9b676aa4c33617fd0e40a711d20
5
5
  SHA512:
6
- metadata.gz: 80cd0fe982cf6097316e34703627e4da6aaadb1bd58070c154d96b550d8d7c25528842128771f7600a4cb68fdf7f400987dd687d1a26f3cabea32695ec4b24ec
7
- data.tar.gz: 8e2ec2983b1d6d78d17170f7090a4628be18f6e970332ee627c0f0c474d1a2dd7a6ac8575efec374cd77afc65ce3399b5c51b82791e3ed98174a697c718ef93b
6
+ metadata.gz: 0a0fbaff3412cc6cc7b92922fae45daf150b950d9f60a2eed1d8a97d32ffd404d7c7577167efe90fecf9425444e4acca5794ad8a49ca6248f0a91b6cb6598dec
7
+ data.tar.gz: da32512009bcbe140b70374ea331a0edbe95696df347311b0b0de07f8740818088c9c2fbfc136572c20c9008c953f205c5e855e048141c9e0a933ce1c77f6d09
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ forge "http://forge.puppetlabs.com"
4
+
5
+ moduledir File.join(File.dirname(__FILE__), 'modules')
6
+
7
+ # Core modules used by 'apply'
8
+ mod 'puppetlabs-service', '1.1.0'
9
+ mod 'puppetlabs-facts', '0.6.0'
10
+ mod 'puppetlabs-puppet_agent', '2.2.1'
11
+
12
+ # Core types and providers for Puppet 6
13
+ mod 'puppetlabs-augeas_core', '1.0.5'
14
+ mod 'puppetlabs-host_core', '1.0.3'
15
+ mod 'puppetlabs-scheduled_task', '2.0.0'
16
+ mod 'puppetlabs-sshkeys_core', '1.0.3'
17
+ mod 'puppetlabs-zfs_core', '1.0.4'
18
+ mod 'puppetlabs-cron_core', '1.0.3'
19
+ mod 'puppetlabs-mount_core', '1.0.4'
20
+ mod 'puppetlabs-selinux_core', '1.0.4'
21
+ mod 'puppetlabs-yumrepo_core', '1.0.4'
22
+ mod 'puppetlabs-zone_core', '1.0.3'
23
+
24
+ # Useful additional modules
25
+ mod 'puppetlabs-package', '0.7.0'
26
+ mod 'puppetlabs-puppet_conf', '0.4.0'
27
+ mod 'puppetlabs-python_task_helper', '0.3.0'
28
+ mod 'puppetlabs-reboot', '2.2.0'
29
+ mod 'puppetlabs-ruby_task_helper', '0.4.0'
30
+
31
+ # Plugin modules
32
+ mod 'puppetlabs-azure_inventory', '0.2.0'
33
+ mod 'puppetlabs-terraform', '0.2.0'
34
+ mod 'puppetlabs-vault', '0.2.2'
35
+ mod 'puppetlabs-aws_inventory', '0.2.0'
36
+
37
+ # If we don't list these modules explicitly, r10k will purge them
38
+ mod 'canary', local: true
39
+ mod 'aggregate', local: true
40
+ mod 'puppetdb_fact', local: true
@@ -132,7 +132,9 @@ module Bolt
132
132
  def validate_hiera_config(hiera_config)
133
133
  if File.exist?(File.path(hiera_config))
134
134
  data = File.open(File.path(hiera_config), "r:UTF-8") { |f| YAML.safe_load(f.read, [Symbol]) }
135
- unless data['version'] == 5
135
+ if data.nil?
136
+ return nil
137
+ elsif data['version'] != 5
136
138
  raise Bolt::ParseError, "Hiera v5 is required, found v#{data['version'] || 3} in #{hiera_config}"
137
139
  end
138
140
  hiera_config
@@ -29,7 +29,7 @@ module Bolt
29
29
  { flags: ACTION_OPTS + %w[tmpdir],
30
30
  banner: FILE_HELP }
31
31
  when 'inventory'
32
- { flags: OPTIONS[:inventory] + OPTIONS[:global] + %w[format inventoryfile boltdir configfile],
32
+ { flags: OPTIONS[:inventory] + OPTIONS[:global] + %w[format inventoryfile boltdir configfile detail],
33
33
  banner: INVENTORY_HELP }
34
34
  when 'group'
35
35
  { flags: OPTIONS[:global] + %w[format inventoryfile boltdir configfile],
@@ -49,6 +49,15 @@ module Bolt
49
49
  { flags: ACTION_OPTS + %w[params compile-concurrency tmpdir],
50
50
  banner: PLAN_HELP }
51
51
  end
52
+ when 'project'
53
+ case action
54
+ when 'init'
55
+ { flags: OPTIONS[:global],
56
+ banner: PROJECT_INIT_HELP }
57
+ else
58
+ { flags: OPTIONS[:global],
59
+ banner: PROJECT_HELP }
60
+ end
52
61
  when 'puppetfile'
53
62
  case action
54
63
  when 'install'
@@ -121,6 +130,7 @@ module Bolt
121
130
  bolt secret decrypt <encrypted> Decrypt a value
122
131
  bolt inventory show Show the list of targets an action would run on
123
132
  bolt group show Show the list of groups in the inventory
133
+ bolt project init Create a new Bolt project
124
134
 
125
135
  Run `bolt <subcommand> --help` to view specific examples.
126
136
 
@@ -310,13 +320,32 @@ module Bolt
310
320
  Available options are:
311
321
  GROUP_HELP
312
322
 
323
+ PROJECT_HELP = <<~PROJECT_HELP
324
+ Usage: bolt project <action>
325
+
326
+ Available actions are:
327
+ init Create a new Bolt project
328
+
329
+ Available options are:
330
+ PROJECT_HELP
331
+
332
+ PROJECT_INIT_HELP = <<~PROJECT_INIT_HELP
333
+ Usage: bolt project init [directory]
334
+
335
+ Create a new Bolt project.
336
+ Specify a directory to create the Bolt project in. Defaults to the current working directory.
337
+
338
+ Available options are:
339
+ PROJECT_INIT_HELP
340
+
313
341
  def initialize(options)
314
342
  super()
315
343
 
316
344
  @options = options
317
345
 
318
346
  define('-n', '--nodes NODES',
319
- 'Alias for --targets') do |nodes|
347
+ 'Alias for --targets',
348
+ 'Deprecated in favor of --targets') do |nodes|
320
349
  @options [:nodes] ||= []
321
350
  @options[:nodes] << get_arg_input(nodes)
322
351
  end
@@ -357,20 +386,23 @@ module Bolt
357
386
  "Puppet manifest code to apply to the targets") do |code|
358
387
  @options[:code] = code
359
388
  end
389
+ define('--detail', 'Show resolved configuration for the targets') do |detail|
390
+ @options[:detail] = detail
391
+ end
360
392
 
361
393
  separator "\nAuthentication:"
362
394
  define('-u', '--user USER', 'User to authenticate as') do |user|
363
395
  @options[:user] = user
364
396
  end
365
397
  define('-p', '--password [PASSWORD]',
366
- 'Password to authenticate with. Omit the value to prompt for the password.') do |password|
367
- if password.nil?
368
- STDOUT.print "Please enter your password: "
369
- @options[:password] = STDIN.noecho(&:gets).chomp
370
- STDOUT.puts
371
- else
372
- @options[:password] = password
398
+ 'Password to authenticate with') do |password|
399
+ # TODO: Remove deprecation message
400
+ unless password
401
+ msg = "Optional parameter for --password is deprecated and no longer prompts for password. " \
402
+ "Use the prompt plugin instead to prompt for passwords."
403
+ raise Bolt::CLIError, msg
373
404
  end
405
+ @options[:password] = password
374
406
  end
375
407
  define('--private-key KEY', 'Private ssh key to authenticate with') do |key|
376
408
  @options[:'private-key'] = key
@@ -390,14 +422,14 @@ module Bolt
390
422
  @options[:'run-as'] = user
391
423
  end
392
424
  define('--sudo-password [PASSWORD]',
393
- 'Password for privilege escalation. Omit the value to prompt for the password.') do |password|
394
- if password.nil?
395
- STDOUT.print "Please enter your privilege escalation password: "
396
- @options[:'sudo-password'] = STDIN.noecho(&:gets).chomp
397
- STDOUT.puts
398
- else
399
- @options[:'sudo-password'] = password
425
+ 'Password for privilege escalation') do |password|
426
+ # TODO: Remove deprecation message
427
+ unless password
428
+ msg = "Optional parameter for --sudo-password is deprecated and no longer prompts for password. " \
429
+ "Use the prompt plugin instead to prompt for passwords."
430
+ raise Bolt::CLIError, msg
400
431
  end
432
+ @options[:'sudo-password'] = password
401
433
  end
402
434
 
403
435
  separator "\nRun context:"
@@ -422,7 +454,8 @@ module Bolt
422
454
  @options[:boltdir] = path
423
455
  end
424
456
  define('--configfile FILEPATH',
425
- 'Specify where to load config from (default: ~/.puppetlabs/bolt/bolt.yaml)') do |path|
457
+ 'Specify where to load config from (default: ~/.puppetlabs/bolt/bolt.yaml). ' \
458
+ 'Directory containing bolt.yaml will be used as the Boltdir.') do |path|
426
459
  @options[:configfile] = path
427
460
  end
428
461
  define('-i', '--inventoryfile FILEPATH',
@@ -37,6 +37,7 @@ module Bolt
37
37
  'secret' => %w[encrypt decrypt createkeys],
38
38
  'inventory' => %w[show],
39
39
  'group' => %w[show],
40
+ 'project' => %w[init],
40
41
  'apply' => %w[] }.freeze
41
42
 
42
43
  attr_reader :config, :options
@@ -134,6 +135,7 @@ module Bolt
134
135
  # options[:targets] will contain a resolved set of Target objects
135
136
  unless options[:subcommand] == 'puppetfile' ||
136
137
  options[:subcommand] == 'secret' ||
138
+ options[:subcommand] == 'project' ||
137
139
  options[:action] == 'show' ||
138
140
  options[:action] == 'convert'
139
141
 
@@ -145,6 +147,13 @@ module Bolt
145
147
  options[:verbose] = options[:subcommand] != 'plan'
146
148
  end
147
149
 
150
+ # TODO: Remove deprecation warning
151
+ if options[:nodes]
152
+ @logger.warn("Deprecation Warning: The --nodes command line option has been " \
153
+ "deprecated in favor of --targets.")
154
+ end
155
+
156
+ warn_inventory_overrides_cli(options)
148
157
  options
149
158
  rescue Bolt::Error => e
150
159
  outputter.fatal_error(e)
@@ -244,7 +253,7 @@ module Bolt
244
253
 
245
254
  def puppetdb_client
246
255
  return @puppetdb_client if @puppetdb_client
247
- puppetdb_config = Bolt::PuppetDB::Config.load_config(nil, config.puppetdb)
256
+ puppetdb_config = Bolt::PuppetDB::Config.load_config(nil, config.puppetdb, config.boltdir.path)
248
257
  @puppetdb_client = Bolt::PuppetDB::Client.new(puppetdb_config)
249
258
  end
250
259
 
@@ -256,6 +265,35 @@ module Bolt
256
265
  puppetdb_client.query_certnames(query)
257
266
  end
258
267
 
268
+ def warn_inventory_overrides_cli(opts)
269
+ inventory_source = if ENV[Bolt::Inventory::ENVIRONMENT_VAR]
270
+ Bolt::Inventory::ENVIRONMENT_VAR
271
+ elsif @config.inventoryfile && Bolt::Util.file_stat(@config.inventoryfile)
272
+ @config.inventoryfile
273
+ elsif (inventory_file = @config.default_inventoryfile.find do |file|
274
+ begin
275
+ Bolt::Util.file_stat(file)
276
+ rescue Errno::ENOENT
277
+ false
278
+ end
279
+ end
280
+ )
281
+ inventory_file
282
+ end
283
+
284
+ inventory_cli_opts = %i[authentication escalation transports].each_with_object([]) do |key, acc|
285
+ acc.concat(Bolt::BoltOptionParser::OPTIONS[key])
286
+ end
287
+
288
+ inventory_cli_opts.concat(%w[no-host-key-check no-ssl no-ssl-verify no-tty])
289
+
290
+ conflicting_options = Set.new(opts.keys.map(&:to_s)).intersection(inventory_cli_opts)
291
+
292
+ if inventory_source && conflicting_options.any?
293
+ @logger.warn("CLI arguments #{conflicting_options.to_a} may be overridden by Inventory: #{inventory_source}")
294
+ end
295
+ end
296
+
259
297
  def execute(options)
260
298
  message = nil
261
299
 
@@ -307,7 +345,11 @@ module Bolt
307
345
  list_plans
308
346
  end
309
347
  elsif options[:subcommand] == 'inventory'
310
- list_targets
348
+ if options[:detail]
349
+ show_targets
350
+ else
351
+ list_targets
352
+ end
311
353
  elsif options[:subcommand] == 'group'
312
354
  list_groups
313
355
  end
@@ -324,6 +366,8 @@ module Bolt
324
366
  end
325
367
 
326
368
  case options[:subcommand]
369
+ when 'project'
370
+ code = initialize_project
327
371
  when 'plan'
328
372
  code = run_plan(options[:object], options[:task_options], options[:target_args], options)
329
373
  when 'puppetfile'
@@ -413,7 +457,12 @@ module Bolt
413
457
 
414
458
  def list_targets
415
459
  update_targets(options)
416
- outputter.print_targets(options)
460
+ outputter.print_targets(options[:targets])
461
+ end
462
+
463
+ def show_targets
464
+ update_targets(options)
465
+ outputter.print_target_info(options[:targets])
417
466
  end
418
467
 
419
468
  def list_groups
@@ -495,6 +544,21 @@ module Bolt
495
544
  0
496
545
  end
497
546
 
547
+ def initialize_project
548
+ path = File.expand_path(options[:object] || Dir.pwd)
549
+ FileUtils.mkdir_p(path)
550
+ ok = FileUtils.touch(File.join(path, 'bolt.yaml'))
551
+
552
+ result = if ok
553
+ "Successfully created Bolt project directory at #{path}"
554
+ else
555
+ "Could not create Bolt project directory at #{path}"
556
+ end
557
+ outputter.print_message result
558
+
559
+ ok ? 0 : 1
560
+ end
561
+
498
562
  def install_puppetfile(config, puppetfile, modulepath)
499
563
  require 'r10k/cli'
500
564
  require 'bolt/r10k_log_proxy'
@@ -110,7 +110,11 @@ module Bolt
110
110
  def normalize_log(target)
111
111
  return target if target == 'console'
112
112
  target = target[5..-1] if target.start_with?('file:')
113
- 'file:' + File.expand_path(target)
113
+ if @future
114
+ 'file:' + File.expand_path(target, @boltdir.path)
115
+ else
116
+ 'file:' + File.expand_path(target)
117
+ end
114
118
  end
115
119
 
116
120
  def update_logs(logs)
@@ -132,6 +136,8 @@ module Bolt
132
136
  end
133
137
 
134
138
  def update_from_file(data)
139
+ @future = data['future'] == true
140
+
135
141
  if data['log'].is_a?(Hash)
136
142
  update_logs(data['log'])
137
143
  end
@@ -164,8 +170,6 @@ module Bolt
164
170
  @plugins = data['plugins'] if data.key?('plugins')
165
171
  @plugin_hooks.merge!(data['plugin_hooks']) if data.key?('plugin_hooks')
166
172
 
167
- @future = data['future'] == true
168
-
169
173
  %w[concurrency format puppetdb color transport].each do |key|
170
174
  send("#{key}=", data[key]) if data.key?(key)
171
175
  end
@@ -173,6 +177,13 @@ module Bolt
173
177
  TRANSPORTS.each do |key, impl|
174
178
  if data[key.to_s]
175
179
  selected = impl.filter_options(data[key.to_s])
180
+ if @future
181
+ to_expand = %w[private-key cacert token-file] & selected.keys
182
+ to_expand.each do |opt|
183
+ selected[opt] = File.expand_path(selected[opt], @boltdir.path) if opt.is_a?(String)
184
+ end
185
+ end
186
+
176
187
  @transports[key] = Bolt::Util.deep_merge(@transports[key], selected)
177
188
  end
178
189
  if @transports[key]['interpreters']
@@ -47,6 +47,7 @@ module Bolt
47
47
  if ENV.include?(ENVIRONMENT_VAR)
48
48
  begin
49
49
  data = YAML.safe_load(ENV[ENVIRONMENT_VAR])
50
+ raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}" unless data.is_a?(Hash)
50
51
  rescue Psych::Exception
51
52
  raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}"
52
53
  end
@@ -67,7 +68,7 @@ module Bolt
67
68
  when 2
68
69
  Bolt::Inventory::Inventory2.new(data, config, plugins: plugins)
69
70
  else
70
- raise ValidationError, "Unsupported version #{version} specified in inventory"
71
+ raise ValidationError.new("Unsupported version #{version} specified in inventory", nil)
71
72
  end
72
73
  end
73
74
 
@@ -172,6 +173,14 @@ module Bolt
172
173
  @target_features[target.name] || Set.new
173
174
  end
174
175
 
176
+ def target_alias(target)
177
+ @groups.node_aliases.each_with_object([]) do |(alia, name), acc|
178
+ if target.name == name
179
+ acc << alia
180
+ end
181
+ end.uniq
182
+ end
183
+
175
184
  def data_hash
176
185
  {
177
186
  data: @data,
@@ -9,7 +9,6 @@ module Bolt
9
9
  class Group2
10
10
  attr_accessor :name, :groups
11
11
 
12
- # THESE are duplicates with the old groups for now.
13
12
  # Regex used to validate group names and target aliases.
14
13
  NAME_REGEX = /\A[a-z0-9_][a-z0-9_-]*\Z/.freeze
15
14
 
@@ -41,8 +40,7 @@ module Bolt
41
40
 
42
41
  @unresolved_targets = {}
43
42
  @resolved_targets = {}
44
- @targets = Set.new
45
- # @target_objects = {}
43
+
46
44
  @aliases = {}
47
45
  @string_targets = []
48
46
 
@@ -57,7 +55,7 @@ module Bolt
57
55
  elsif target.is_a?(Hash)
58
56
  add_target_definition(target)
59
57
  else
60
- raise ValidationError.new("Node entry must be a String or Hash, not #{target.class}", @name)
58
+ raise ValidationError.new("Target entry must be a String or Hash, not #{target.class}", @name)
61
59
  end
62
60
  end
63
61
 
@@ -150,6 +148,7 @@ module Bolt
150
148
  resolved_data = resolve_data_keys(target, target_name).merge(
151
149
  'name' => target['name'],
152
150
  'uri' => target['uri'],
151
+ 'alias' => target['alias'],
153
152
  # groups come from group_data
154
153
  'groups' => []
155
154
  )
@@ -159,12 +158,16 @@ module Bolt
159
158
  end
160
159
  end
161
160
 
161
+ def all_target_names
162
+ @unresolved_targets.keys + @resolved_targets.keys
163
+ end
164
+
162
165
  def add_target_definition(target)
163
166
  # This check ensures target lookup plugins do not returns bare strings.
164
- # Remove it if we decide to allows task plugins to return string node
167
+ # Remove it if we decide to allows task plugins to return string Target
165
168
  # names.
166
169
  unless target.is_a?(Hash)
167
- raise ValidationError.new("Node entry must be a Hash, not #{target.class}", @name)
170
+ raise ValidationError.new("Target entry must be a Hash, not #{target.class}", @name)
168
171
  end
169
172
 
170
173
  target['name'] = resolve_references(target['name']) if target.key?('name')
@@ -201,14 +204,7 @@ module Bolt
201
204
  raise ValidationError.new(msg, @name)
202
205
  end
203
206
 
204
- aliases.each do |alia|
205
- raise ValidationError.new("Invalid alias #{alia}", @name) unless alia =~ NAME_REGEX
206
-
207
- if (found = @aliases[alia])
208
- raise ValidationError.new(alias_conflict(alia, found, t_name), @name)
209
- end
210
- @aliases[alia] = t_name
211
- end
207
+ insert_alia(t_name, aliases)
212
208
  end
213
209
 
214
210
  @unresolved_targets[t_name] = target
@@ -218,6 +214,17 @@ module Bolt
218
214
  @resolved_targets[target.name] = { 'name' => target.name }
219
215
  end
220
216
 
217
+ def insert_alia(target_name, aliases)
218
+ aliases.each do |alia|
219
+ raise ValidationError.new("Invalid alias #{alia}", @name) unless alia =~ NAME_REGEX
220
+
221
+ if (found = @aliases[alia])
222
+ raise ValidationError.new(alias_conflict(alia, found, target_name), @name)
223
+ end
224
+ @aliases[alia] = target_name
225
+ end
226
+ end
227
+
221
228
  def data_merge(data1, data2)
222
229
  if data2.nil? || data1.nil?
223
230
  return data2 || data1
@@ -227,6 +234,7 @@ module Bolt
227
234
  'config' => Bolt::Util.deep_merge(data1['config'], data2['config']),
228
235
  'name' => data1['name'] || data2['name'],
229
236
  'uri' => data1['uri'] || data2['uri'],
237
+ 'alias' => data1['alias'] || data2['alias'],
230
238
  # Shallow merge instead of deep merge so that vars with a hash value
231
239
  # are assigned a new hash, rather than merging the existing value
232
240
  # with the value meant to replace it
@@ -277,7 +285,7 @@ module Bolt
277
285
  end
278
286
 
279
287
  private def alias_target_conflict(name)
280
- "Node name #{name} conflicts with alias of the same name"
288
+ "Target name #{name} conflicts with alias of the same name"
281
289
  end
282
290
 
283
291
  def validate_group_input(input)
@@ -176,10 +176,29 @@ module Bolt
176
176
  unless existing_target
177
177
  add_to_group([new_target], 'all')
178
178
  end
179
+ # Insert target alias into groups that contain the target
180
+ if (aliases = new_target.target_alias)
181
+ aliases = [aliases] if aliases.is_a?(String)
182
+ unless aliases.is_a?(Array)
183
+ msg = "Alias entry on #{t_name} must be a String or Array, not #{aliases.class}"
184
+ raise ValidationError.new(msg, @name)
185
+ end
186
+
187
+ insert_alias_into_group(@groups, new_target.name, aliases)
188
+ end
179
189
 
180
190
  new_target
181
191
  end
182
192
 
193
+ def insert_alias_into_group(group, target_name, aliases)
194
+ if group.all_target_names.include?(target_name)
195
+ group.insert_alia(target_name, aliases)
196
+ end
197
+ group.groups.each do |grp|
198
+ insert_alias_into_group(grp, target_name, aliases)
199
+ end
200
+ end
201
+
183
202
  def add_to_group(targets, desired_group)
184
203
  if group_names.include?(desired_group)
185
204
  targets.each do |target|
@@ -4,7 +4,7 @@ module Bolt
4
4
  class Inventory
5
5
  # This class represents the active state of a target within the inventory.
6
6
  class Target
7
- attr_reader :name, :uri, :safe_name
7
+ attr_reader :name, :uri, :safe_name, :target_alias
8
8
 
9
9
  def initialize(target_data, inventory)
10
10
  unless target_data['name'] || target_data['uri']
@@ -34,7 +34,9 @@ module Bolt
34
34
  @features = target_data['features'] || Set.new
35
35
  @options = target_data['options'] || {}
36
36
  @plugin_hooks = target_data['plugin_hooks'] || {}
37
- @target_alias = target_data['target_alias'] || []
37
+ # When alias is specified in a plan, the key will be `target_alias`, when
38
+ # alias is specified in inventory the key will be `alias`.
39
+ @target_alias = target_data['target_alias'] || target_data['alias'] || []
38
40
 
39
41
  @inventory = inventory
40
42
 
@@ -42,7 +44,6 @@ module Bolt
42
44
  end
43
45
 
44
46
  def vars
45
- # XXX Return vars from the cache
46
47
  group_cache['vars'].merge(@vars)
47
48
  end
48
49
 
@@ -55,7 +56,6 @@ module Bolt
55
56
  # rubocop:enable Naming/AccessorMethodName
56
57
 
57
58
  def facts
58
- # XXX Return facts from the cache
59
59
  Bolt::Util.deep_merge(group_cache['facts'], @facts)
60
60
  end
61
61
 
@@ -135,7 +135,7 @@ module Bolt
135
135
  target_str = if targets.length > 5
136
136
  "#{targets.count} targets"
137
137
  else
138
- targets.map(&:uri).join(', ')
138
+ targets.map(&:safe_name).join(', ')
139
139
  end
140
140
  @stream.puts(colorize(:green, "Starting: #{description} on #{target_str}"))
141
141
  end
@@ -219,7 +219,7 @@ module Bolt
219
219
  # Building lots of strings...
220
220
  pretty_params = +""
221
221
  task_info = +""
222
- usage = +"bolt task run --nodes <node-name> #{task['name']}"
222
+ usage = +"bolt task run --targets <node-name> #{task['name']}"
223
223
 
224
224
  task['metadata']['parameters']&.each do |k, v|
225
225
  pretty_params << "- #{k}: #{v['type'] || 'Any'}\n"
@@ -311,10 +311,17 @@ module Bolt
311
311
  end
312
312
  end
313
313
 
314
- def print_targets(options)
315
- targets = options[:targets].map(&:name)
314
+ def print_targets(targets)
315
+ count = "#{targets.count} target#{'s' unless targets.count == 1}"
316
+ @stream.puts targets.map(&:name).join("\n")
317
+ @stream.puts colorize(:green, count)
318
+ end
319
+
320
+ def print_target_info(targets)
321
+ @stream.puts ::JSON.pretty_generate(
322
+ "targets": targets.map(&:detail)
323
+ )
316
324
  count = "#{targets.count} target#{'s' unless targets.count == 1}"
317
- @stream.puts targets.join("\n")
318
325
  @stream.puts colorize(:green, count)
319
326
  end
320
327
 
@@ -89,11 +89,18 @@ module Bolt
89
89
  "moduledir": moduledir }.to_json)
90
90
  end
91
91
 
92
- def print_targets(options)
93
- targets = options[:targets].map(&:name)
94
- count = targets.count
95
- @stream.puts({ "targets": targets,
96
- "count": count }.to_json)
92
+ def print_targets(targets)
93
+ @stream.puts ::JSON.pretty_generate(
94
+ "targets": targets.map(&:name),
95
+ "count": targets.count
96
+ )
97
+ end
98
+
99
+ def print_target_info(targets)
100
+ @stream.puts ::JSON.pretty_generate(
101
+ "targets": targets.map(&:detail),
102
+ "count": targets.count
103
+ )
97
104
  end
98
105
 
99
106
  def print_groups(groups)
@@ -27,7 +27,7 @@ module Bolt
27
27
  target_str = if targets.length > 5
28
28
  "#{targets.count} targets"
29
29
  else
30
- targets.map(&:uri).join(', ')
30
+ targets.map(&:safe_name).join(', ')
31
31
  end
32
32
  @logger.info("Starting: #{description} on #{target_str}")
33
33
  end
@@ -19,9 +19,12 @@ module Bolt
19
19
  end
20
20
 
21
21
  def resolve_reference(opts)
22
- STDOUT.print "#{opts['message']}:"
22
+ # rubocop:disable Style/GlobalVars
23
+ $future ? STDERR.print("#{opts['message']}:") : STDOUT.print("#{opts['message']}:")
23
24
  value = STDIN.noecho(&:gets).chomp
24
- STDOUT.puts
25
+ $future ? STDERR.puts : STDOUT.puts
26
+ # rubocop:enable Style/GlobalVars
27
+
25
28
  value
26
29
  end
27
30
  end
@@ -19,7 +19,7 @@ module Bolt
19
19
 
20
20
  end
21
21
 
22
- def self.load_config(filename, options)
22
+ def self.load_config(filename, options, boltdir_path = nil)
23
23
  config = {}
24
24
  global_path = Bolt::Util.windows? ? DEFAULT_CONFIG[:win_global] : DEFAULT_CONFIG[:global]
25
25
  if filename
@@ -43,11 +43,12 @@ module Bolt
43
43
  end
44
44
 
45
45
  config = config.fetch('puppetdb', {})
46
- new(config.merge(options))
46
+ new(config.merge(options), boltdir_path)
47
47
  end
48
48
 
49
- def initialize(settings)
49
+ def initialize(settings, boltdir_path = nil)
50
50
  @settings = settings
51
+ @boltdir_path = boltdir_path
51
52
  expand_paths
52
53
  end
53
54
 
@@ -66,7 +67,14 @@ module Bolt
66
67
 
67
68
  def expand_paths
68
69
  %w[cacert cert key token].each do |file|
69
- @settings[file] = File.expand_path(@settings[file]) if @settings[file]
70
+ next unless @settings[file]
71
+ # rubocop:disable Style/GlobalVars
72
+ @settings[file] = if $future && @boltdir_path
73
+ File.expand_path(@settings[file], @boltdir_path)
74
+ else
75
+ File.expand_path(@settings[file])
76
+ end
77
+ # rubocop:enable Style/GlobalVars
70
78
  end
71
79
  end
72
80
 
@@ -14,6 +14,7 @@ module Bolt
14
14
  new(target.name, inventory)
15
15
  end
16
16
 
17
+ # TODO: Disallow any positional argument other than URI.
17
18
  # Target.new from a plan with just a uri. Puppet requires the arguments to
18
19
  # this method to match (by name) the attributes defined on the datatype.
19
20
  # rubocop:disable Lint/UnusedMethodArgument
@@ -27,12 +28,12 @@ module Bolt
27
28
  plugin_hooks = nil)
28
29
  from_asserted_hash('uri' => uri)
29
30
  end
31
+ # rubocop:enable Lint/UnusedMethodArgument
30
32
 
31
33
  def initialize(name, inventory = nil)
32
34
  @name = name
33
35
  @inventory = inventory
34
36
  end
35
- # rubocop:enable Lint/UnusedMethodArgument
36
37
 
37
38
  # features returns an array to be compatible with plans
38
39
  def features
@@ -80,6 +81,19 @@ module Bolt
80
81
  )
81
82
  end
82
83
 
84
+ def detail
85
+ {
86
+ 'name' => name,
87
+ 'uri' => uri,
88
+ 'alias' => target_alias,
89
+ 'config' => Bolt::Util.deep_merge(config, 'transport' => transport, transport => options),
90
+ 'vars' => vars,
91
+ 'features' => features,
92
+ 'facts' => facts,
93
+ 'plugin_hooks' => plugin_hooks
94
+ }
95
+ end
96
+
83
97
  def inventory_target
84
98
  @inventory.targets[@name]
85
99
  end
@@ -226,6 +240,18 @@ module Bolt
226
240
  end
227
241
  end
228
242
 
243
+ def vars
244
+ @inventory.vars(self)
245
+ end
246
+
247
+ def facts
248
+ @inventory.facts(self)
249
+ end
250
+
251
+ def target_alias
252
+ @inventory.target_alias(self)
253
+ end
254
+
229
255
  # TODO: WHAT does equality mean here?
230
256
  # should we just compare names? is there something else that is meaninful?
231
257
  def eql?(other)
@@ -258,6 +284,21 @@ module Bolt
258
284
  )
259
285
  end
260
286
 
287
+ def detail
288
+ {
289
+ 'name' => name,
290
+ 'alias' => target_alias,
291
+ 'config' => {
292
+ 'transport' => transport,
293
+ transport => options
294
+ },
295
+ 'vars' => vars,
296
+ 'facts' => facts,
297
+ 'features' => features.to_a,
298
+ 'plugin_hooks' => plugin_hooks
299
+ }
300
+ end
301
+
261
302
  def host
262
303
  @uri_obj&.hostname || @host
263
304
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.35.0'
4
+ VERSION = '1.36.0'
5
5
  end
@@ -4,6 +4,7 @@ require 'bolt_spec/plans/mock_executor'
4
4
  require 'bolt/config'
5
5
  require 'bolt/inventory'
6
6
  require 'bolt/pal'
7
+ require 'bolt/plugin'
7
8
 
8
9
  # These helpers are intended to be used for plan unit testing without calling
9
10
  # out to target nodes. It accomplishes this by replacing bolt's executor with a
@@ -158,6 +159,13 @@ module BoltSpec
158
159
  raise "RSpec.configuration.module_path not defined set up rspec puppet or define modulepath for this test"
159
160
  end
160
161
 
162
+ def plugins
163
+ @plugins ||= Bolt::Plugin.setup(config,
164
+ pal,
165
+ puppetdb_client,
166
+ Bolt::Analytics::NoopClient.new)
167
+ end
168
+
161
169
  # Override in your tests
162
170
  def config
163
171
  @config ||= begin
@@ -168,8 +176,12 @@ module BoltSpec
168
176
  end
169
177
 
170
178
  # Override in your tests
179
+ def inventory_data
180
+ {}
181
+ end
182
+
171
183
  def inventory
172
- @inventory ||= Bolt::Inventory.new({})
184
+ @inventory ||= Bolt::Inventory.create_version(inventory_data, config, plugins)
173
185
  end
174
186
 
175
187
  # Provided as a class so expectations can be placed on it.
@@ -179,6 +191,10 @@ module BoltSpec
179
191
  @puppetdb_client ||= MockPuppetDBClient.new
180
192
  end
181
193
 
194
+ def pal
195
+ @pal ||= Bolt::PAL.new(config.modulepath, config.hiera_config, config.boltdir.resource_types)
196
+ end
197
+
182
198
  def run_plan(name, params)
183
199
  pal = Bolt::PAL.new(config.modulepath, config.hiera_config, config.boltdir.resource_types)
184
200
  result = pal.run_plan(name, params, executor, inventory, puppetdb_client)
@@ -23,7 +23,10 @@ module BoltSpec
23
23
  @calls += 1
24
24
  if @return_block
25
25
  # Merge arguments and options into params to match puppet function signature.
26
- check_resultset(@return_block.call(targets: targets, task: task, params: arguments.merge(options)), task)
26
+ params = options.map { |k, v| ["_#{k}", v] }.to_h
27
+ params = params.merge(arguments)
28
+
29
+ check_resultset(@return_block.call(targets: targets, task: task, params: params), task)
27
30
  else
28
31
  Bolt::ResultSet.new(targets.map { |target| @data[target.name] || default_for(target) })
29
32
  end
@@ -17,7 +17,7 @@ module BoltSpec
17
17
  # Nothing on the executor is 'public'
18
18
  class MockExecutor
19
19
  attr_reader :noop, :error_message
20
- attr_accessor :run_as
20
+ attr_accessor :run_as, :transport_features
21
21
 
22
22
  def initialize(modulepath)
23
23
  @noop = false
@@ -27,6 +27,7 @@ module BoltSpec
27
27
  @modulepath = [modulepath].flatten.map { |path| File.absolute_path(path) }
28
28
  MOCKED_ACTIONS.each { |action| instance_variable_set(:"@#{action}_doubles", {}) }
29
29
  @stub_out_message = nil
30
+ @transport_features = ['puppet-agent']
30
31
  end
31
32
 
32
33
  def module_file_id(file)
@@ -168,12 +169,12 @@ module BoltSpec
168
169
 
169
170
  # Mocked for apply_prep
170
171
  def transport(_protocol)
171
- # Always return a transport that includes the puppet-agent feature so version/install are skipped.
172
172
  Class.new do
173
- def provided_features
174
- ['puppet-agent']
173
+ attr_reader :provided_features
174
+ def initialize(features)
175
+ @provided_features = features
175
176
  end
176
- end.new
177
+ end.new(transport_features)
177
178
  end
178
179
  # End apply_prep mocking
179
180
  end
@@ -5,6 +5,7 @@ require 'bolt/config'
5
5
  require 'bolt/executor'
6
6
  require 'bolt/inventory'
7
7
  require 'bolt/pal'
8
+ require 'bolt/plugin'
8
9
  require 'bolt/puppetdb'
9
10
  require 'bolt/util'
10
11
 
@@ -130,20 +131,30 @@ module BoltSpec
130
131
  # still be loaded
131
132
  def self.with_runner(config_data, inventory_data)
132
133
  Dir.mktmpdir do |boltdir_path|
133
- config = Bolt::Config.new(Bolt::Boltdir.new(boltdir_path), config_data || {})
134
- inventory = Bolt::Inventory.new(inventory_data || {}, config)
135
- yield new(config, inventory)
134
+ runner = new(config_data, inventory_data, boltdir_path)
135
+ yield runner
136
136
  end
137
137
  end
138
138
 
139
- attr_reader :config, :inventory
140
-
141
- def initialize(config, inventory)
142
- @config = config
143
- @inventory = inventory
139
+ def initialize(config_data, inventory_data, boltdir_path)
140
+ @config_data = config_data || {}
141
+ @inventory_data = inventory_data || {}
142
+ @boltdir_path = boltdir_path
144
143
  @analytics = Bolt::Analytics::NoopClient.new
145
144
  end
146
145
 
146
+ def config
147
+ @config ||= Bolt::Config.new(Bolt::Boltdir.new(@boltdir_path), @config_data)
148
+ end
149
+
150
+ def inventory
151
+ @inventory ||= Bolt::Inventory.create_version(@inventory_data, config, plugins)
152
+ end
153
+
154
+ def plugins
155
+ @plugins ||= Bolt::Plugin.setup(config, pal, puppetdb_client, @analytics)
156
+ end
157
+
147
158
  def puppetdb_client
148
159
  @puppetdb_client ||= begin
149
160
  puppetdb_config = Bolt::PuppetDB::Config.load_config(nil, config.puppetdb)
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.35.0
4
+ version: 1.36.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-10-25 00:00:00.000000000 Z
11
+ date: 2019-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -319,6 +319,7 @@ executables:
319
319
  extensions: []
320
320
  extra_rdoc_files: []
321
321
  files:
322
+ - Puppetfile
322
323
  - bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb
323
324
  - bolt-modules/boltlib/lib/puppet/datatypes/result.rb
324
325
  - bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb