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 +4 -4
- data/exe/bolt +3 -2
- data/lib/bolt/cli.rb +99 -92
- data/lib/bolt/config.rb +7 -8
- data/lib/bolt/error.rb +20 -3
- data/lib/bolt/executor.rb +44 -45
- data/lib/bolt/logger.rb +28 -52
- data/lib/bolt/node.rb +12 -41
- data/lib/bolt/node/orch.rb +28 -34
- data/lib/bolt/node/ssh.rb +26 -21
- data/lib/bolt/node/winrm.rb +41 -19
- data/lib/bolt/notifier.rb +2 -2
- data/lib/bolt/outputter/human.rb +34 -29
- data/lib/bolt/outputter/json.rb +4 -10
- data/lib/bolt/pal.rb +106 -0
- data/lib/bolt/result.rb +90 -87
- data/lib/bolt/result_set.rb +94 -0
- data/lib/bolt/target.rb +61 -13
- data/lib/bolt/version.rb +1 -1
- data/modules/boltlib/lib/puppet/datatypes/result.rb +18 -0
- data/modules/boltlib/lib/puppet/datatypes/resultset.rb +22 -0
- data/modules/boltlib/lib/puppet/datatypes/target.rb +4 -1
- data/modules/boltlib/lib/puppet/functions/file_upload.rb +15 -10
- data/modules/boltlib/lib/puppet/functions/run_command.rb +15 -8
- data/modules/boltlib/lib/puppet/functions/run_script.rb +15 -26
- data/modules/boltlib/lib/puppet/functions/run_task.rb +17 -15
- metadata +23 -9
- data/lib/bolt/execution_result.rb +0 -109
- data/lib/bolt/formatter.rb +0 -9
- data/lib/bolt/node_uri.rb +0 -42
- data/modules/boltlib/lib/puppet/datatypes/executionresult.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa5ef349a62f61714a7ae4b664febe0a794a1acf
|
4
|
+
data.tar.gz: bd71a0e1d122717a98f35ec882fd90bc885067f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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::
|
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
|
-
|
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
|
-
|
117
|
-
'-n', '--nodes NODES',
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
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
|
-
|
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] ==
|
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
|
-
|
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(
|
438
|
-
outputter.print_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
|
-
|
445
|
-
) do |
|
446
|
-
outputter.print_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 |
|
450
|
-
outputter.print_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(
|
461
|
-
outputter.print_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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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,
|
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,
|
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
|
-
|
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
|
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] =
|
164
|
+
self[:log_level] = :debug
|
165
165
|
elsif options[:verbose]
|
166
|
-
self[:log_level] =
|
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
|
188
|
-
|
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
|