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.

data/lib/bolt/node/ssh.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'shellwords'
3
+ require 'logging'
3
4
  require 'net/ssh'
4
5
  require 'net/scp'
5
6
  require 'bolt/node/output'
@@ -19,12 +20,14 @@ module Bolt
19
20
  end
20
21
 
21
22
  def connect
23
+ transport_logger = Logging.logger[Net::SSH]
24
+ transport_logger.level = :warn
22
25
  options = {
23
- logger: @transport_logger,
26
+ logger: transport_logger,
24
27
  non_interactive: true
25
28
  }
26
29
 
27
- options[:port] = @port if @port
30
+ options[:port] = @target.port if @target.port
28
31
  options[:password] = @password if @password
29
32
  options[:keys] = @key if @key
30
33
  options[:verify_host_key] = if @insecure
@@ -34,7 +37,7 @@ module Bolt
34
37
  end
35
38
  options[:timeout] = @connect_timeout if @connect_timeout
36
39
 
37
- @session = Net::SSH.start(@host, @user, options)
40
+ @session = Net::SSH.start(@target.host, @user, options)
38
41
  @logger.debug { "Opened session" }
39
42
  rescue Net::SSH::AuthenticationFailed => e
40
43
  raise Bolt::Node::ConnectError.new(
@@ -43,17 +46,17 @@ module Bolt
43
46
  )
44
47
  rescue Net::SSH::HostKeyError => e
45
48
  raise Bolt::Node::ConnectError.new(
46
- "Host key verification failed for #{@uri}: #{e.message}",
49
+ "Host key verification failed for #{uri}: #{e.message}",
47
50
  'HOST_KEY_ERROR'
48
51
  )
49
52
  rescue Net::SSH::ConnectionTimeout
50
53
  raise Bolt::Node::ConnectError.new(
51
- "Timeout after #{@connect_timeout} seconds connecting to #{@uri}",
54
+ "Timeout after #{@connect_timeout} seconds connecting to #{uri}",
52
55
  'CONNECT_ERROR'
53
56
  )
54
57
  rescue StandardError => e
55
58
  raise Bolt::Node::ConnectError.new(
56
- "Failed to connect to #{@uri}: #{e.message}",
59
+ "Failed to connect to #{uri}: #{e.message}",
57
60
  'CONNECT_ERROR'
58
61
  )
59
62
  end
@@ -77,20 +80,20 @@ module Bolt
77
80
  return true
78
81
  else
79
82
  raise Bolt::Node::EscalateError.new(
80
- "Sudo password for user #{@user} was not provided for #{@uri}",
83
+ "Sudo password for user #{@user} was not provided for #{uri}",
81
84
  'NO_PASSWORD'
82
85
  )
83
86
  end
84
87
  elsif data =~ /^#{@user} is not in the sudoers file\./
85
88
  @logger.debug { data }
86
89
  raise Bolt::Node::EscalateError.new(
87
- "User #{@user} does not have sudo permission on #{@uri}",
90
+ "User #{@user} does not have sudo permission on #{uri}",
88
91
  'SUDO_DENIED'
89
92
  )
90
93
  elsif data =~ /^Sorry, try again\./
91
94
  @logger.debug { data }
92
95
  raise Bolt::Node::EscalateError.new(
93
- "Sudo password for user #{@user} not recognized on #{@uri}",
96
+ "Sudo password for user #{@user} not recognized on #{uri}",
94
97
  'BAD_PASSWORD'
95
98
  )
96
99
  end
@@ -157,11 +160,11 @@ module Bolt
157
160
  result_output
158
161
  end
159
162
 
160
- def _upload(source, destination)
163
+ def upload(source, destination)
161
164
  write_remote_file(source, destination)
162
- Bolt::Result.new
165
+ Bolt::Result.for_upload(@target, source, destination)
163
166
  rescue StandardError => e
164
- Bolt::Result.from_exception(e)
167
+ Bolt::Result.from_exception(@target, e)
165
168
  end
166
169
 
167
170
  def write_remote_file(source, destination)
@@ -250,28 +253,28 @@ SCRIPT
250
253
  end
251
254
  end
252
255
 
253
- def _run_command(command)
256
+ def run_command(command)
254
257
  output = execute(command, sudoable: true)
255
- Bolt::CommandResult.from_output(output)
258
+ Bolt::Result.for_command(@target, output.stdout.string, output.stderr.string, output.exit_code)
256
259
  # TODO: We should be able to rely on the excutor for this but it will mean
257
260
  # a test refactor
258
261
  rescue StandardError => e
259
- Bolt::Result.from_exception(e)
262
+ Bolt::Result.from_exception(@target, e)
260
263
  end
261
264
 
262
- def _run_script(script, arguments)
265
+ def run_script(script, arguments)
263
266
  with_remote_file(script) do |remote_path|
264
267
  output = execute("'#{remote_path}' #{Shellwords.join(arguments)}",
265
268
  sudoable: true)
266
- Bolt::CommandResult.from_output(output)
269
+ Bolt::Result.for_command(@target, output.stdout.string, output.stderr.string, output.exit_code)
267
270
  end
268
271
  # TODO: We should be able to rely on the excutor for this but it will mean
269
272
  # a test refactor
270
273
  rescue StandardError => e
271
- Bolt::Result.from_exception(e)
274
+ Bolt::Result.from_exception(@target, e)
272
275
  end
273
276
 
274
- def _run_task(task, input_method, arguments)
277
+ def run_task(task, input_method, arguments)
275
278
  export_args = {}
276
279
  stdin, output = nil
277
280
 
@@ -298,12 +301,14 @@ SCRIPT
298
301
  output = execute(command, stdin: stdin)
299
302
  end
300
303
  end
301
- Bolt::TaskResult.from_output(output)
304
+ Bolt::Result.for_task(@target, output.stdout.string,
305
+ output.stderr.string,
306
+ output.exit_code)
302
307
 
303
308
  # TODO: We should be able to rely on the excutor for this but it will mean
304
309
  # a test refactor
305
310
  rescue StandardError => e
306
- Bolt::Result.from_exception(e)
311
+ Bolt::Result.from_exception(@target, e)
307
312
  end
308
313
  end
309
314
  end
@@ -1,5 +1,6 @@
1
1
  require 'winrm'
2
2
  require 'winrm-fs'
3
+ require 'logging'
3
4
  require 'bolt/result'
4
5
  require 'base64'
5
6
  require 'set'
@@ -10,13 +11,16 @@ module Bolt
10
11
  'winrm'
11
12
  end
12
13
 
14
+ HTTP_PORT = 5985
15
+ HTTPS_PORT = 5986
16
+
13
17
  def port
14
- default_port = @insecure ? 5985 : 5986
15
- @port || default_port
18
+ default_port = @insecure ? HTTP_PORT : HTTPS_PORT
19
+ @target.port || default_port
16
20
  end
17
21
 
18
- def initialize(host, port, user, password, **kwargs)
19
- super(host, port, user, password, **kwargs)
22
+ def initialize(target, **kwargs)
23
+ super(target, **kwargs)
20
24
  @extensions = DEFAULT_EXTENSIONS.to_set.merge(@extensions || [])
21
25
  @logger.debug { "WinRM initialized for #{@extensions.to_a} extensions" }
22
26
  end
@@ -29,7 +33,7 @@ module Bolt
29
33
  scheme = 'https'
30
34
  transport = :ssl
31
35
  end
32
- endpoint = "#{scheme}://#{host}:#{port}/wsman"
36
+ endpoint = "#{scheme}://#{@target.host}:#{port}/wsman"
33
37
  options = { endpoint: endpoint,
34
38
  user: @user,
35
39
  password: @password,
@@ -39,15 +43,22 @@ module Bolt
39
43
 
40
44
  Timeout.timeout(@connect_timeout) do
41
45
  @connection = ::WinRM::Connection.new(options)
42
- @connection.logger = @transport_logger
46
+ transport_logger = Logging.logger[::WinRM]
47
+ transport_logger.level = :warn
48
+ @connection.logger = transport_logger
43
49
 
44
50
  @session = @connection.shell(:powershell)
45
51
  @session.run('$PSVersionTable.PSVersion')
46
52
  @logger.debug { "Opened session" }
47
53
  end
48
54
  rescue Timeout::Error
55
+ # If we're using the default port with SSL, a timeout probably means the
56
+ # host doesn't support SSL.
57
+ if !@insecure && port == HTTPS_PORT
58
+ theres_your_problem = "\nUse --insecure if this host isn't configured to use SSL for WinRM"
59
+ end
49
60
  raise Bolt::Node::ConnectError.new(
50
- "Timeout after #{@connect_timeout} seconds connecting to #{endpoint}",
61
+ "Timeout after #{@connect_timeout} seconds connecting to #{endpoint}#{theres_your_problem}",
51
62
  'CONNECT_ERROR'
52
63
  )
53
64
  rescue ::WinRM::WinRMAuthorizationError
@@ -55,6 +66,15 @@ module Bolt
55
66
  "Authentication failed for #{endpoint}",
56
67
  'AUTH_ERROR'
57
68
  )
69
+ rescue OpenSSL::SSL::SSLError => e
70
+ # If we're using SSL with the default non-SSL port, mention that as a likely problem
71
+ if !@insecure && port == HTTP_PORT
72
+ theres_your_problem = "\nAre you using SSL to connect to a non-SSL port?"
73
+ end
74
+ raise Bolt::Node::ConnectError.new(
75
+ "Failed to connect to #{endpoint}: #{e.message}#{theres_your_problem}",
76
+ "CONNECT_ERROR"
77
+ )
58
78
  rescue StandardError => e
59
79
  raise Bolt::Node::ConnectError.new(
60
80
  "Failed to connect to #{endpoint}: #{e.message}",
@@ -440,12 +460,12 @@ PS
440
460
  end
441
461
  end
442
462
 
443
- def _upload(source, destination)
463
+ def upload(source, destination)
444
464
  write_remote_file(source, destination)
445
- Bolt::Result.new
465
+ Bolt::Result.for_upload(@target, source, destination)
446
466
  # TODO: we should rely on the executor for this
447
467
  rescue StandardError => ex
448
- Bolt::Result.from_exception(ex)
468
+ Bolt::Result.from_exception(@target, ex)
449
469
  end
450
470
 
451
471
  def write_remote_file(source, destination)
@@ -490,15 +510,15 @@ PS
490
510
  end
491
511
  end
492
512
 
493
- def _run_command(command)
513
+ def run_command(command)
494
514
  output = execute(command)
495
- Bolt::CommandResult.from_output(output)
515
+ Bolt::Result.for_command(@target, output.stdout.string, output.stderr.string, output.exit_code)
496
516
  # TODO: we should rely on the executor for this
497
517
  rescue StandardError => e
498
- Bolt::Result.from_exception(e)
518
+ Bolt::Result.from_exception(@target, e)
499
519
  end
500
520
 
501
- def _run_script(script, arguments)
521
+ def run_script(script, arguments)
502
522
  with_remote_file(script) do |remote_path|
503
523
  if powershell_file?(remote_path)
504
524
  mapped_args = arguments.map do |a|
@@ -525,14 +545,14 @@ catch
525
545
  args += escape_arguments(arguments)
526
546
  output = execute_process(path, args)
527
547
  end
528
- Bolt::CommandResult.from_output(output)
548
+ Bolt::Result.for_command(@target, output.stdout.string, output.stderr.string, output.exit_code)
529
549
  end
530
550
  # TODO: we should rely on the executor for this
531
551
  rescue StandardError => e
532
- Bolt::Result.from_exception(e)
552
+ Bolt::Result.from_exception(@target, e)
533
553
  end
534
554
 
535
- def _run_task(task, input_method, arguments)
555
+ def run_task(task, input_method, arguments)
536
556
  if STDIN_METHODS.include?(input_method)
537
557
  stdin = JSON.dump(arguments)
538
558
  end
@@ -567,11 +587,13 @@ try { & "#{remote_path}" @taskArgs } catch { exit 1 }
567
587
  path, args = *process_from_extension(remote_path)
568
588
  execute_process(path, args, stdin)
569
589
  end
570
- Bolt::TaskResult.from_output(output)
590
+ Bolt::Result.for_task(@target, output.stdout.string,
591
+ output.stderr.string,
592
+ output.exit_code)
571
593
  end
572
594
  # TODO: we should rely on the executor for this
573
595
  rescue StandardError => e
574
- Bolt::Result.from_exception(e)
596
+ Bolt::Result.from_exception(@target, e)
575
597
  end
576
598
 
577
599
  def escape_arguments(arguments)
data/lib/bolt/notifier.rb CHANGED
@@ -6,9 +6,9 @@ module Bolt
6
6
  @executor = executor
7
7
  end
8
8
 
9
- def notify(callback, node, result)
9
+ def notify(callback, event)
10
10
  @executor.post do
11
- callback.call(node, result)
11
+ callback.call(event)
12
12
  end
13
13
  end
14
14
 
@@ -25,48 +25,48 @@ module Bolt
25
25
  string.sub(/\s\z/, '')
26
26
  end
27
27
 
28
- def print_event(node, event)
28
+ def print_event(event)
29
29
  case event[:type]
30
30
  when :node_start
31
- print_start(node)
31
+ print_start(event[:target])
32
32
  when :node_result
33
- print_result(node, event[:result])
33
+ print_result(event[:result])
34
34
  end
35
35
  end
36
36
 
37
- def print_start(node)
38
- @stream.puts(colorize(:green, "Started on #{node.host}..."))
37
+ def print_start(target)
38
+ @stream.puts(colorize(:green, "Started on #{target.host}..."))
39
39
  end
40
40
 
41
- def print_result(node, result)
41
+ def print_result(result)
42
42
  if result.success?
43
- @stream.puts(colorize(:green, "Finished on #{node.host}:"))
43
+ @stream.puts(colorize(:green, "Finished on #{result.target.host}:"))
44
44
  else
45
- @stream.puts(colorize(:red, "Failed on #{node.host}:"))
45
+ @stream.puts(colorize(:red, "Failed on #{result.target.host}:"))
46
46
  end
47
47
 
48
- if result.error
49
- if result.error['msg']
50
- @stream.puts(colorize(:red, remove_trail(indent(2, result.error['msg']))))
51
- else
52
- @stream.puts(colorize(:red, remove_trail(indent(2, result.error))))
53
- end
48
+ if result.error_hash
49
+ @stream.puts(colorize(:red, remove_trail(indent(2, result.error_hash['msg']))))
54
50
  end
55
51
 
56
52
  if result.message
57
53
  @stream.puts(remove_trail(indent(2, result.message)))
58
54
  end
59
55
 
60
- if result.instance_of? Bolt::TaskResult
61
- @stream.puts(indent(2, ::JSON.pretty_generate(result.value)))
62
- elsif result.instance_of? Bolt::CommandResult
63
- unless result.stdout.strip.empty?
64
- @stream.puts(indent(2, "STDOUT:"))
65
- @stream.puts(indent(4, result.stdout))
66
- end
67
- unless result.stderr.strip.empty?
68
- @stream.puts(indent(2, "STDERR:"))
69
- @stream.puts(indent(4, result.stderr))
56
+ # There is more information to output
57
+ if result.generic_value
58
+ # Use special handling if the result looks like a command or script result
59
+ if result.generic_value.keys == %w[stdout stderr exit_code]
60
+ unless result['stdout'].strip.empty?
61
+ @stream.puts(indent(2, "STDOUT:"))
62
+ @stream.puts(indent(4, result['stdout']))
63
+ end
64
+ unless result['stderr'].strip.empty?
65
+ @stream.puts(indent(2, "STDERR:"))
66
+ @stream.puts(indent(4, result['stderr']))
67
+ end
68
+ else
69
+ @stream.puts(indent(2, ::JSON.pretty_generate(result.generic_value)))
70
70
  end
71
71
  end
72
72
  end
@@ -123,13 +123,15 @@ module Bolt
123
123
  end
124
124
 
125
125
  def print_plan(result)
126
- # If a hash or array, pretty-print as JSON
127
- if result.is_a?(Hash) || result.is_a?(Array)
128
- if result.empty?
129
- # Avoids extra lines for an empty result
126
+ # If the object has a json representation display it
127
+ if result.respond_to?(:to_json)
128
+ # Guard against to_json methods that don't accept options
129
+ # and don't print empty results on multiple lines
130
+ if result.method(:to_json).arity == 0 ||
131
+ (result.respond_to?(:empty?) && result.empty?)
130
132
  @stream.puts(result.to_json)
131
133
  else
132
- @stream.puts(::JSON.pretty_generate(result))
134
+ @stream.puts(::JSON.pretty_generate(result, quirks_mode: true))
133
135
  end
134
136
  else
135
137
  @stream.puts result.to_s
@@ -138,6 +140,9 @@ module Bolt
138
140
 
139
141
  def fatal_error(e)
140
142
  @stream.puts(colorize(:red, e.message))
143
+ if e.is_a? Bolt::RunFailure
144
+ @stream.puts ::JSON.pretty_generate(e.resultset)
145
+ end
141
146
  end
142
147
  end
143
148
 
@@ -15,22 +15,16 @@ module Bolt
15
15
  @object_open = true
16
16
  end
17
17
 
18
- def print_event(node, event)
18
+ def print_event(event)
19
19
  case event[:type]
20
20
  when :node_result
21
- print_result(node, event[:result])
21
+ print_result(event[:result])
22
22
  end
23
23
  end
24
24
 
25
- def print_result(node, result)
26
- item = {
27
- name: node.uri,
28
- status: result.success? ? 'success' : 'failure',
29
- result: result.to_result
30
- }
31
-
25
+ def print_result(result)
32
26
  @stream.puts ',' if @preceding_item
33
- @stream.puts item.to_json
27
+ @stream.puts result.status_hash.to_json
34
28
  @preceding_item = true
35
29
  end
36
30
 
data/lib/bolt/pal.rb ADDED
@@ -0,0 +1,106 @@
1
+ # TODO: This is currently used only for testing. I will refactor the CLI to use
2
+ # this in a separate PR
3
+ module Bolt
4
+ class PAL
5
+ BOLTLIB_PATH = File.join(__FILE__, '../../../modules')
6
+
7
+ def initialize(config)
8
+ # TODO: how should we manage state? Does noop go here?
9
+ # This allows us to copypaste from BOLT::CLI for now
10
+ @config = config
11
+ end
12
+
13
+ # WARNING: Nothing in here works without calling this!
14
+ def self.load_puppet
15
+ begin
16
+ require_relative '../../vendored/require_vendored'
17
+ rescue LoadError
18
+ raise Bolt::CLIError, "Puppet must be installed to execute tasks"
19
+ end
20
+
21
+ # Now that puppet is loaded we can include puppet mixins in data types
22
+ Bolt::ResultSet.include_iterable
23
+
24
+ Puppet::Util::Log.newdestination(:console)
25
+ Puppet[:log_level] = 'notice'
26
+ # Puppet[:log_level] = if @config[:log_level] == :debug
27
+ # 'debug'
28
+ # else
29
+ # 'notice'
30
+ # end
31
+ end
32
+
33
+ # Runs a block in a PAL script compiler configured for Bolt. Catches
34
+ # exceptions thrown by the block and re-raises them ensuring they are
35
+ # Bolt::Errors since the script compiler block will squash all exceptions.
36
+ def in_bolt_compiler(opts = [])
37
+ Puppet.initialize_settings(opts)
38
+ r = Puppet::Pal.in_tmp_environment('bolt', modulepath: [BOLTLIB_PATH] + @config[:modulepath], facts: {}) do |pal|
39
+ pal.with_script_compiler do |compiler|
40
+ begin
41
+ yield compiler
42
+ rescue Puppet::PreformattedError => err
43
+ # Puppet sometimes rescues exceptions notes the location and reraises
44
+ # For now return the original error.
45
+ if err.cause
46
+ if err.cause.is_a? Bolt::Error
47
+ err.cause
48
+ else
49
+ e = Bolt::CLIError.new(err.cause.message)
50
+ e.set_backtrace(err.cause.backtrace)
51
+ e
52
+ end
53
+ else
54
+ e = Bolt::CLIError.new(err.message)
55
+ e.set_backtrace(err.backtrace)
56
+ e
57
+ end
58
+ rescue StandardError => err
59
+ e = Bolt::CLIError.new(err.message)
60
+ e.set_backtrace(err.backtrace)
61
+ e
62
+ end
63
+ end
64
+ end
65
+
66
+ if r.is_a? StandardError
67
+ raise r
68
+ end
69
+ r
70
+ end
71
+
72
+ def with_bolt_executor(executor, &block)
73
+ Puppet.override(bolt_executor: executor, &block)
74
+ end
75
+
76
+ def in_plan_compiler(noop)
77
+ executor = Bolt::Executor.new(@config, noop, true)
78
+ with_bolt_executor(executor) do
79
+ with_puppet_settings do |opts|
80
+ in_bolt_compiler(opts) do |compiler|
81
+ yield compiler
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def in_task_compiler(noop)
88
+ executor = Bolt::Executor.new(@config, noop)
89
+ with_bolt_executor(executor) do
90
+ in_bolt_compiler(opts) do |compiler|
91
+ yield compiler
92
+ end
93
+ end
94
+ end
95
+
96
+ def with_puppet_settings
97
+ Dir.mktmpdir('bolt') do |dir|
98
+ cli = []
99
+ Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
100
+ cli << "--#{setting}" << dir
101
+ end
102
+ yield cli
103
+ end
104
+ end
105
+ end
106
+ end