bolt 0.14.0 → 0.15.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
  SHA1:
3
- metadata.gz: 6515bd6a3cf63131c23e0809aa98a057030977a3
4
- data.tar.gz: eee81f558aefb21e761082b04bd73bd631e6685d
3
+ metadata.gz: aa5ef349a62f61714a7ae4b664febe0a794a1acf
4
+ data.tar.gz: bd71a0e1d122717a98f35ec882fd90bc885067f4
5
5
  SHA512:
6
- metadata.gz: 5598b32bf144d8fe0a2cc0601f5ec136235646b182c1a4dc9c4f00aef467f6ea88cdcfcdec25c4fbf88241aa3485d5e9317f34a5d44d8a982f81725678ce8f1b
7
- data.tar.gz: 2aac3a8c35530eecaf1e57e206d24f8ae3c0f4eb0f53199ff4d14c491cbef873a8b0791843242fb5ff5890393356da0f0f1235b815d093f075c31938cf1d2e4d
6
+ metadata.gz: ae08cc350289783f725460ada932ccda8431ccc33ed71c2cbd2680e0d2b125e41b0abe45acba8c7cf9170832afca19bd87bb5ce99d97f5afa9617ba73751e54a
7
+ data.tar.gz: 7cf3ed3887d53350ca23e28ff45ce890037593204eb68532aa8e6d9c0a842918513ff6be014d2379fe2f84cfe22caaff59913335386c5ec2e86a4a6c4fae3ece
data/exe/bolt CHANGED
@@ -6,9 +6,10 @@ require 'bolt/cli'
6
6
  cli = Bolt::CLI.new(ARGV)
7
7
  begin
8
8
  opts = cli.parse
9
- cli.execute(opts)
9
+ exitcode = cli.execute(opts)
10
+ exit exitcode
10
11
  rescue Bolt::CLIExit
11
12
  exit
12
- rescue Bolt::CLIError => e
13
+ rescue Bolt::Error => e
13
14
  exit e.error_code
14
15
  end
data/lib/bolt/cli.rb CHANGED
@@ -1,23 +1,23 @@
1
1
  require 'uri'
2
2
  require 'optparse'
3
3
  require 'benchmark'
4
- require 'bolt/logger'
5
4
  require 'json'
5
+ require 'logging'
6
+ require 'bolt/logger'
6
7
  require 'bolt/node'
7
8
  require 'bolt/version'
8
9
  require 'bolt/error'
9
10
  require 'bolt/executor'
11
+ require 'bolt/target'
10
12
  require 'bolt/outputter'
11
13
  require 'bolt/config'
12
14
  require 'io/console'
13
15
 
14
16
  module Bolt
15
17
  class CLIError < Bolt::Error
16
- attr_reader :error_code
17
-
18
- def initialize(msg, error_code: 1)
18
+ def initialize(msg)
19
19
  super(msg, "bolt/cli-error")
20
- @error_code = error_code
20
+ @error_code = error_code if error_code
21
21
  end
22
22
  end
23
23
 
@@ -103,30 +103,37 @@ HELP
103
103
  attr_accessor :options
104
104
 
105
105
  def initialize(argv)
106
+ Bolt::Logger.initialize_logging
106
107
  @argv = argv
107
108
  @options = {
108
109
  nodes: []
109
110
  }
110
111
  @config = Bolt::Config.new
112
+
113
+ # parse mode and object, use COMMANDS as a whitelist
114
+ @options[:mode] = argv[0] if COMMANDS.keys.any? { |mode| argv[0] == mode }
115
+ @options[:object] = argv[1] if COMMANDS.values.flatten.uniq.any? { |object| argv[1] == object }
111
116
  @parser = create_option_parser(@options)
117
+ @logger = Logging.logger[self]
112
118
  end
113
119
 
114
120
  def create_option_parser(results)
115
- OptionParser.new('') do |opts|
116
- opts.on(
117
- '-n', '--nodes NODES',
118
- 'Node(s) to connect to in URI format [protocol://]host[:port]',
119
- 'Eg. --nodes bolt.puppet.com',
120
- 'Eg. --nodes localhost,ssh://nix.com:2222,winrm://windows.puppet.com',
121
- "\n",
122
- '* NODES can either be comma-separated, \'@<file>\' to read',
123
- '* nodes from a file, or \'-\' to read from stdin',
124
- '* Windows nodes must specify protocol with winrm://',
125
- '* protocol is `ssh` by default, may be `ssh` or `winrm`',
126
- '* port is `22` by default for SSH, `5985` for winrm (Optional)'
127
- ) do |nodes|
128
- results[:nodes] += parse_nodes(nodes)
129
- results[:nodes].uniq!
121
+ parser = OptionParser.new('') do |opts|
122
+ unless results[:mode] == 'plan'
123
+ opts.on('-n', '--nodes NODES',
124
+ 'Node(s) to connect to in URI format [protocol://]host[:port] (Optional)',
125
+ 'Eg. --nodes bolt.puppet.com',
126
+ 'Eg. --nodes localhost,ssh://nix.com:2222,winrm://windows.puppet.com',
127
+ "\n",
128
+ '* NODES can either be comma-separated, \'@<file>\' to read',
129
+ '* nodes from a file, or \'-\' to read from stdin',
130
+ '* Windows nodes must specify protocol with winrm://',
131
+ '* protocol is `ssh` by default, may be `ssh` or `winrm`',
132
+ '* port defaults to `22` for SSH',
133
+ '* port defaults to `5985` or `5986` for WinRM, based on the --insecure setting') do |nodes|
134
+ results[:nodes] += parse_nodes(nodes)
135
+ results[:nodes].uniq!
136
+ end
130
137
  end
131
138
  opts.on('-u', '--user USER',
132
139
  "User to authenticate as (Optional)") do |user|
@@ -148,7 +155,7 @@ HELP
148
155
  results[:key] = key
149
156
  end
150
157
  opts.on('--tmpdir DIR',
151
- "The directory to upload and execute temporary files on the target(Optional)") do |tmpdir|
158
+ "The directory to upload and execute temporary files on the target (Optional)") do |tmpdir|
152
159
  results[:tmpdir] = tmpdir
153
160
  end
154
161
  opts.on('-c', '--concurrency CONCURRENCY', Integer,
@@ -222,6 +229,22 @@ HELP
222
229
  raise Bolt::CLIExit
223
230
  end
224
231
  end
232
+
233
+ parser.banner = case results[:mode]
234
+ when "plan"
235
+ PLAN_HELP
236
+ when "command"
237
+ COMMAND_HELP
238
+ when "script"
239
+ SCRIPT_HELP
240
+ when "task"
241
+ TASK_HELP
242
+ when "file"
243
+ FILE_HELP
244
+ else
245
+ BANNER
246
+ end
247
+ parser
225
248
  end
226
249
 
227
250
  def parse
@@ -238,18 +261,21 @@ HELP
238
261
 
239
262
  if options[:mode] == 'help'
240
263
  options[:help] = true
264
+
265
+ # regenerate options parser with new mode
241
266
  options[:mode] = remaining.shift
267
+ @parser = create_option_parser(options)
242
268
  end
243
269
 
244
270
  if options[:help]
245
- print_help(options[:mode])
271
+ puts parser.help
246
272
  raise Bolt::CLIExit
247
273
  end
248
274
 
249
275
  @config.load_file(options[:configfile])
250
276
  @config.update_from_cli(options)
251
- Logger.configure(@config)
252
277
  @config.validate
278
+ Logging.logger[:root].level = @config[:log_level] || :notice
253
279
 
254
280
  # This section handles parsing non-flag options which are
255
281
  # mode specific rather then part of the config
@@ -277,27 +303,9 @@ HELP
277
303
  raise e
278
304
  end
279
305
 
280
- def print_help(mode)
281
- parser.banner = case mode
282
- when 'task'
283
- TASK_HELP
284
- when 'command'
285
- COMMAND_HELP
286
- when 'script'
287
- SCRIPT_HELP
288
- when 'file'
289
- FILE_HELP
290
- when 'plan'
291
- PLAN_HELP
292
- else
293
- BANNER
294
- end
295
- puts parser.help
296
- end
297
-
298
306
  def parse_nodes(nodes)
299
307
  list = get_arg_input(nodes)
300
- list.split(/[[:space:],]+/).reject(&:empty?).uniq
308
+ Target.parse_urls(list)
301
309
  end
302
310
 
303
311
  def parse_params(params)
@@ -380,7 +388,7 @@ HELP
380
388
  yield
381
389
  rescue OptionParser::MissingArgument => e
382
390
  raise Bolt::CLIError, "Option '#{e.args.first}' needs a parameter"
383
- rescue OptionParser::InvalidOption => e
391
+ rescue OptionParser::InvalidOption, OptionParser::AmbiguousOption => e
384
392
  raise Bolt::CLIError, "Unknown argument '#{e.args.first}'"
385
393
  end
386
394
 
@@ -392,18 +400,17 @@ HELP
392
400
  raise Bolt::CLIError, "Puppet must be installed to execute tasks"
393
401
  end
394
402
 
403
+ # Now that puppet is loaded we can include puppet mixins in data types
404
+ Bolt::ResultSet.include_iterable
405
+
395
406
  Puppet::Util::Log.newdestination(:console)
396
- Puppet[:log_level] = if @config[:log_level] == Logger::DEBUG
407
+ Puppet[:log_level] = if @config[:log_level] == :debug
397
408
  'debug'
398
409
  else
399
410
  'notice'
400
411
  end
401
412
  end
402
413
 
403
- # ExecutionResult loaded here so that it can get puppet features if
404
- # puppet is present
405
- require 'bolt/execution_result'
406
-
407
414
  if options[:action] == 'show'
408
415
  if options[:mode] == 'task'
409
416
  if options[:object]
@@ -417,15 +424,16 @@ HELP
417
424
  elsif options[:mode] == 'plan'
418
425
  outputter.print_table(list_plans)
419
426
  end
420
- return
427
+ return 0
421
428
  end
422
429
 
423
430
  if options[:mode] == 'plan'
424
431
  executor = Bolt::Executor.new(@config, options[:noop], true)
425
432
  execute_plan(executor, options)
433
+ code = 0
426
434
  else
427
435
  executor = Bolt::Executor.new(@config, options[:noop])
428
- nodes = executor.from_uris(options[:nodes])
436
+ targets = options[:nodes]
429
437
 
430
438
  results = nil
431
439
  outputter.print_head
@@ -434,20 +442,20 @@ HELP
434
442
  results =
435
443
  case options[:mode]
436
444
  when 'command'
437
- executor.run_command(nodes, options[:object]) do |node, event|
438
- outputter.print_event(node, event)
445
+ executor.run_command(targets, options[:object]) do |event|
446
+ outputter.print_event(event)
439
447
  end
440
448
  when 'script'
441
449
  script = options[:object]
442
450
  validate_file('script', script)
443
451
  executor.run_script(
444
- nodes, script, options[:leftovers]
445
- ) do |node, event|
446
- outputter.print_event(node, event)
452
+ targets, script, options[:leftovers]
453
+ ) do |event|
454
+ outputter.print_event(event)
447
455
  end
448
456
  when 'task'
449
- execute_task(executor, options) do |node, event|
450
- outputter.print_event(node, event)
457
+ execute_task(executor, options) do |event|
458
+ outputter.print_event(event)
451
459
  end
452
460
  when 'file'
453
461
  src = options[:object]
@@ -457,15 +465,17 @@ HELP
457
465
  raise Bolt::CLIError, "A destination path must be specified"
458
466
  end
459
467
  validate_file('source file', src)
460
- executor.file_upload(nodes, src, dest) do |node, event|
461
- outputter.print_event(node, event)
468
+ executor.file_upload(targets, src, dest) do |event|
469
+ outputter.print_event(event)
462
470
  end
463
471
  end
464
472
  end
465
473
 
466
474
  outputter.print_summary(results, elapsed_time)
475
+ code = results.ok ? 0 : 2
467
476
  end
468
- rescue Bolt::CLIError => e
477
+ code
478
+ rescue Bolt::Error => e
469
479
  outputter.fatal_error(e)
470
480
  raise e
471
481
  end
@@ -516,8 +526,9 @@ HELP
516
526
  @outputter ||= Bolt::Outputter.for_format(@config[:format])
517
527
  end
518
528
 
519
- # Runs a block in a PAL script compiler configured for Bolt.
520
- # Catches exceptions thrown by the block and re-raises them as Bolt::CLIError.
529
+ # Runs a block in a PAL script compiler configured for Bolt. Catches
530
+ # exceptions thrown by the block and re-raises them ensuring they are
531
+ # Bolt::Errors since the script compiler block will squash all exceptions.
521
532
  def in_bolt_compiler(opts = [])
522
533
  Puppet.initialize_settings(opts)
523
534
  r = Puppet::Pal.in_tmp_environment('bolt', modulepath: [BOLTLIB_PATH] + @config[:modulepath], facts: {}) do |pal|
@@ -525,14 +536,33 @@ HELP
525
536
  begin
526
537
  yield compiler
527
538
  rescue Puppet::PreformattedError => err
528
- err.cause
539
+ # Puppet sometimes rescues exceptions notes the location and reraises
540
+ # For now return the original error. Exception cause support was added in Ruby 2.1
541
+ # so we fall back to reporting the error we got for Ruby 2.0.
542
+ if err.respond_to?(:cause) && err.cause
543
+ if err.cause.is_a? Bolt::Error
544
+ err.cause
545
+ else
546
+ e = Bolt::CLIError.new(err.cause.message)
547
+ e.set_backtrace(err.cause.backtrace)
548
+ e
549
+ end
550
+ else
551
+ e = Bolt::CLIError.new(err.message)
552
+ e.set_backtrace(err.backtrace)
553
+ e
554
+ end
529
555
  rescue StandardError => err
530
- err
556
+ e = Bolt::CLIError.new(err.message)
557
+ e.set_backtrace(err.backtrace)
558
+ e
531
559
  end
532
560
  end
533
561
  end
534
562
 
535
- raise Bolt::CLIError, r.message if r.is_a? StandardError
563
+ if r.is_a? StandardError
564
+ raise r
565
+ end
536
566
  r
537
567
  end
538
568
 
@@ -560,30 +590,10 @@ HELP
560
590
  task.task_hash
561
591
  end
562
592
 
563
- def run_task(name, nodes, args, &block)
593
+ def run_task(name, targets, args, &block)
594
+ args = args.merge('_catch_errors' => true)
564
595
  in_bolt_compiler do |compiler|
565
- compiler.call_function('run_task', name, nodes, args, &block)
566
- end
567
- end
568
-
569
- # Expects to be called with a configured Puppet compiler or error.instance? will fail
570
- def unwrap_execution_result(result)
571
- if result.instance_of? Bolt::ExecutionResult
572
- result.iterator.map do |node, output|
573
- if output.is_a?(Puppet::DataTypes::Error)
574
- # Get the original error hash used to initialize the Error type object.
575
- result = output.partial_result || {}
576
- result[:_error] = { msg: output.message,
577
- kind: output.kind,
578
- details: output.details,
579
- issue_code: output.issue_code }
580
- { node: node, status: 'failed', result: result }
581
- else
582
- { node: node, status: 'finished', result: output }
583
- end
584
- end
585
- else
586
- result
596
+ compiler.call_function('run_task', name, targets, args, &block)
587
597
  end
588
598
  end
589
599
 
@@ -594,10 +604,7 @@ HELP
594
604
  cli << "--#{setting}" << dir
595
605
  end
596
606
  in_bolt_compiler(cli) do |compiler|
597
- result = compiler.call_function('run_plan', plan, args)
598
- # Querying ExecutionResult for failures currently requires a script compiler.
599
- # Convert from an ExecutionResult to structured output that we can print.
600
- unwrap_execution_result(result)
607
+ compiler.call_function('run_plan', plan, args)
601
608
  end
602
609
  end
603
610
  end
data/lib/bolt/config.rb CHANGED
@@ -1,6 +1,6 @@
1
- require 'bolt/logger'
2
1
  require 'yaml'
3
2
  require 'bolt/cli'
3
+ require 'logging'
4
4
 
5
5
  module Bolt
6
6
  Config = Struct.new(
@@ -33,6 +33,7 @@ module Bolt
33
33
 
34
34
  def initialize(**kwargs)
35
35
  super()
36
+ @logger = Logging.logger[self]
36
37
  DEFAULTS.merge(kwargs).each { |k, v| self[k] = v }
37
38
 
38
39
  self[:transports] ||= {}
@@ -58,8 +59,7 @@ module Bolt
58
59
  if path.nil?
59
60
  found_default = default_paths.select { |p| File.exist?(p) }
60
61
  if found_default.size > 1
61
- logger = Logger.get_logger
62
- logger.warn "Config files found at #{found_default.join(', ')}, using the first"
62
+ @logger.warn "Config files found at #{found_default.join(', ')}, using the first"
63
63
  end
64
64
  # Use first found, fall back to first default and try to load even if it didn't exist
65
65
  path = found_default.first || default_paths.first
@@ -161,9 +161,9 @@ module Bolt
161
161
  end
162
162
 
163
163
  if options[:debug]
164
- self[:log_level] = Logger::DEBUG
164
+ self[:log_level] = :debug
165
165
  elsif options[:verbose]
166
- self[:log_level] = Logger::INFO
166
+ self[:log_level] = :info
167
167
  end
168
168
 
169
169
  TRANSPORT_OPTIONS.each do |key|
@@ -184,9 +184,8 @@ module Bolt
184
184
  end
185
185
 
186
186
  if self[:transports][:ssh][:sudo_password] && self[:transports][:ssh][:run_as].nil?
187
- logger = Logger.get_logger
188
- logger.warn("--sudo-password will not be used without specifying a " \
189
- "user to escalate to with --run-as")
187
+ @logger.warn("--sudo-password will not be used without specifying a " \
188
+ "user to escalate to with --run-as")
190
189
  end
191
190
 
192
191
  self[:transports].each_value do |v|
data/lib/bolt/error.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  module Bolt
2
2
  class Error < RuntimeError
3
- attr_reader :kind, :details, :issue_code
3
+ attr_reader :kind, :details, :issue_code, :error_code
4
4
 
5
5
  def initialize(msg, kind, details = nil, issue_code = nil)
6
6
  super(msg)
7
7
  @kind = kind
8
8
  @issue_code = issue_code
9
9
  @details = details || {}
10
+ @error_code ||= 1
10
11
  end
11
12
 
12
13
  def msg
@@ -21,8 +22,24 @@ module Bolt
21
22
  h
22
23
  end
23
24
 
24
- def to_json
25
- to_h.to_json
25
+ def to_json(opts = nil)
26
+ to_h.to_json(opts)
27
+ end
28
+ end
29
+
30
+ class RunFailure < Error
31
+ attr_reader :resultset
32
+
33
+ def initialize(resultset, action, object)
34
+ details = {
35
+ action: action,
36
+ object: object,
37
+ failed_targets: resultset.error_set.names
38
+ }
39
+ message = "Plan aborted: #{action} '#{object}' failed on #{details[:failed_targets].length} nodes"
40
+ super(message, 'bolt/run-failure', details)
41
+ @resultset = resultset
42
+ @error_code = 2
26
43
  end
27
44
  end
28
45
  end