bolt 0.8.0 → 0.9.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.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bolt/cli.rb +31 -21
  3. data/lib/bolt/config.rb +1 -0
  4. data/lib/bolt/error.rb +28 -0
  5. data/lib/bolt/executor.rb +10 -6
  6. data/lib/bolt/node.rb +7 -5
  7. data/lib/bolt/node/errors.rb +25 -3
  8. data/lib/bolt/node/orch.rb +7 -16
  9. data/lib/bolt/node/output.rb +17 -0
  10. data/lib/bolt/node/ssh.rb +101 -44
  11. data/lib/bolt/node/winrm.rb +86 -40
  12. data/lib/bolt/outputter/human.rb +65 -8
  13. data/lib/bolt/outputter/json.rb +8 -1
  14. data/lib/bolt/result.rb +74 -124
  15. data/lib/bolt/version.rb +1 -1
  16. data/{vendored/puppet → modules/boltlib}/lib/puppet/functions/file_upload.rb +9 -7
  17. data/{vendored/puppet → modules/boltlib}/lib/puppet/functions/run_command.rb +8 -9
  18. data/{vendored/puppet → modules/boltlib}/lib/puppet/functions/run_plan.rb +8 -12
  19. data/{vendored/puppet → modules/boltlib}/lib/puppet/functions/run_script.rb +12 -8
  20. data/{vendored/puppet → modules/boltlib}/lib/puppet/functions/run_task.rb +10 -10
  21. data/vendored/puppet/lib/puppet/agent.rb +1 -1
  22. data/vendored/puppet/lib/puppet/application/lookup.rb +1 -3
  23. data/vendored/puppet/lib/puppet/configurer.rb +2 -3
  24. data/vendored/puppet/lib/puppet/configurer/plugin_handler.rb +25 -7
  25. data/vendored/puppet/lib/puppet/defaults.rb +9 -1
  26. data/vendored/puppet/lib/puppet/face/epp.rb +4 -2
  27. data/vendored/puppet/lib/puppet/face/module/build.rb +1 -1
  28. data/vendored/puppet/lib/puppet/face/module/list.rb +5 -16
  29. data/vendored/puppet/lib/puppet/face/module/uninstall.rb +14 -3
  30. data/vendored/puppet/lib/puppet/face/plugin.rb +1 -3
  31. data/vendored/puppet/lib/puppet/forge/errors.rb +17 -7
  32. data/vendored/puppet/lib/puppet/functions.rb +8 -6
  33. data/vendored/puppet/lib/puppet/functions/each.rb +10 -4
  34. data/vendored/puppet/lib/puppet/functions/lookup.rb +2 -2
  35. data/vendored/puppet/lib/puppet/functions/map.rb +12 -2
  36. data/vendored/puppet/lib/puppet/functions/slice.rb +2 -3
  37. data/vendored/puppet/lib/puppet/graph/simple_graph.rb +9 -5
  38. data/vendored/puppet/lib/puppet/interface.rb +1 -0
  39. data/vendored/puppet/lib/puppet/module_tool/errors/installer.rb +27 -17
  40. data/vendored/puppet/lib/puppet/module_tool/errors/shared.rb +143 -63
  41. data/vendored/puppet/lib/puppet/module_tool/errors/uninstaller.rb +37 -14
  42. data/vendored/puppet/lib/puppet/module_tool/errors/upgrader.rb +30 -18
  43. data/vendored/puppet/lib/puppet/network/auth_config_parser.rb +8 -8
  44. data/vendored/puppet/lib/puppet/network/http/error.rb +7 -7
  45. data/vendored/puppet/lib/puppet/network/http/rack.rb +2 -2
  46. data/vendored/puppet/lib/puppet/network/http/webrick.rb +1 -1
  47. data/vendored/puppet/lib/puppet/node.rb +10 -0
  48. data/vendored/puppet/lib/puppet/node/facts.rb +9 -0
  49. data/vendored/puppet/lib/puppet/parameter/value_collection.rb +16 -6
  50. data/vendored/puppet/lib/puppet/parser/resource.rb +103 -31
  51. data/vendored/puppet/lib/puppet/pops/evaluator/access_operator.rb +13 -0
  52. data/vendored/puppet/lib/puppet/pops/evaluator/evaluator_impl.rb +22 -6
  53. data/vendored/puppet/lib/puppet/pops/loader/static_loader.rb +1 -1
  54. data/vendored/puppet/lib/puppet/pops/lookup/lookup_adapter.rb +13 -4
  55. data/vendored/puppet/lib/puppet/pops/model/ast_transformer.rb +1 -1
  56. data/vendored/puppet/lib/puppet/pops/parser/eparser.rb +527 -529
  57. data/vendored/puppet/lib/puppet/pops/serialization/abstract_reader.rb +4 -0
  58. data/vendored/puppet/lib/puppet/pops/serialization/abstract_writer.rb +6 -0
  59. data/vendored/puppet/lib/puppet/pops/serialization/extension.rb +1 -0
  60. data/vendored/puppet/lib/puppet/pops/serialization/serializer.rb +2 -1
  61. data/vendored/puppet/lib/puppet/pops/types/execution_result.rb +8 -0
  62. data/vendored/puppet/lib/puppet/pops/types/iterable.rb +2 -0
  63. data/vendored/puppet/lib/puppet/pops/types/p_object_type.rb +3 -0
  64. data/vendored/puppet/lib/puppet/pops/types/p_object_type_extension.rb +6 -0
  65. data/vendored/puppet/lib/puppet/pops/types/p_uri_type.rb +191 -0
  66. data/vendored/puppet/lib/puppet/pops/types/string_converter.rb +17 -0
  67. data/vendored/puppet/lib/puppet/pops/types/type_calculator.rb +5 -0
  68. data/vendored/puppet/lib/puppet/pops/types/type_factory.rb +7 -0
  69. data/vendored/puppet/lib/puppet/pops/types/type_formatter.rb +16 -18
  70. data/vendored/puppet/lib/puppet/pops/types/type_mismatch_describer.rb +15 -5
  71. data/vendored/puppet/lib/puppet/pops/types/type_parser.rb +6 -0
  72. data/vendored/puppet/lib/puppet/pops/types/type_with_members.rb +43 -0
  73. data/vendored/puppet/lib/puppet/pops/types/types.rb +3 -0
  74. data/vendored/puppet/lib/puppet/provider/package/gem.rb +1 -1
  75. data/vendored/puppet/lib/puppet/provider/package/nim.rb +7 -8
  76. data/vendored/puppet/lib/puppet/provider/package/opkg.rb +1 -1
  77. data/vendored/puppet/lib/puppet/provider/package/pkg.rb +6 -4
  78. data/vendored/puppet/lib/puppet/provider/package/pkgutil.rb +3 -3
  79. data/vendored/puppet/lib/puppet/provider/service/init.rb +1 -1
  80. data/vendored/puppet/lib/puppet/syntax_checkers/base64.rb +5 -6
  81. data/vendored/puppet/lib/puppet/transaction.rb +1 -1
  82. data/vendored/puppet/lib/puppet/type.rb +1 -9
  83. data/vendored/puppet/lib/puppet/type/schedule.rb +1 -1
  84. data/vendored/puppet/lib/puppet/util/log.rb +2 -3
  85. data/vendored/puppet/lib/puppet/util/plist.rb +1 -1
  86. data/vendored/puppet/lib/puppet/util/reference.rb +2 -3
  87. data/vendored/puppet/lib/puppet_pal.rb +326 -53
  88. metadata +28 -12
  89. data/lib/bolt/node/result.rb +0 -115
  90. data/vendored/puppet/lib/puppet/configurer/downloader_factory.rb +0 -44
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d17e85d4ead68354a59253671eaa9246fcd9c25
4
- data.tar.gz: e00bb08318f54e3d03cd1e144a646e9c4e7eb3fc
3
+ metadata.gz: 80377af090a1f1d5fd9515eac97623064459ce9f
4
+ data.tar.gz: 1b73aeaa5dce3a2c3b37581905212e00c0ed2afa
5
5
  SHA512:
6
- metadata.gz: 0c8d07c175cda227ca2138956167fec92b403deeb967cdb1d83116f9012c13e9b6ba5d4a4bd2178a40cc04ee7153ebb59734263e2056d3054da5e89ede11bc4e
7
- data.tar.gz: e750e86b36f107295719ae0f6b8340c0e216208ebf7c15bbf17f99ac5785895a1b556a3ce8aead3b267f90abd3256eb2afa0d8f8df7b365334af5caaf7a6379f
6
+ metadata.gz: 445a3d714f38f2e8ab548df6c8b7b7ec777f407ec44625533c7fe1b1983210f5d82c648d149f9c1ba3de295b53422af27e89d1f1267ac2980d08bd12fabd4d66
7
+ data.tar.gz: d0ce607c758b9431c48e978d5ca1e67470a2afd3be1ce35e5694e5a3bed5f82487c5c082daaf19cf1880c2fac16ec9297efaf8105c4b9fc7ab8970dc69eab5ca
data/lib/bolt/cli.rb CHANGED
@@ -5,25 +5,20 @@ require 'logger'
5
5
  require 'json'
6
6
  require 'bolt/node'
7
7
  require 'bolt/version'
8
+ require 'bolt/error'
8
9
  require 'bolt/executor'
9
10
  require 'bolt/outputter'
10
11
  require 'bolt/config'
11
12
  require 'io/console'
12
13
 
13
14
  module Bolt
14
- class CLIError < RuntimeError
15
+ class CLIError < Bolt::Error
15
16
  attr_reader :error_code
16
17
 
17
18
  def initialize(msg, error_code: 1)
18
- super(msg)
19
+ super(msg, "bolt/cli-error")
19
20
  @error_code = error_code
20
21
  end
21
-
22
- def to_json
23
- { kind: "bolt/cli-error",
24
- msg: message,
25
- details: { error_code: @error_code } }.to_json
26
- end
27
22
  end
28
23
 
29
24
  class CLIExit < StandardError; end
@@ -94,6 +89,7 @@ HELP
94
89
  MODES = %w[command script task plan file].freeze
95
90
  ACTIONS = %w[run upload download].freeze
96
91
  TRANSPORTS = %w[ssh winrm pcp].freeze
92
+ BOLTLIB_PATH = File.join(__FILE__, '../../../modules')
97
93
 
98
94
  attr_reader :parser, :config
99
95
  attr_accessor :options
@@ -142,6 +138,10 @@ HELP
142
138
  end
143
139
  end
144
140
  results[:concurrency] = 100
141
+ opts.on('--private-key KEY',
142
+ "Private ssh key to authenticate with (Optional)") do |key|
143
+ results[:key] = key
144
+ end
145
145
  opts.on('-c', '--concurrency CONCURRENCY', Integer,
146
146
  "Maximum number of simultaneous connections " \
147
147
  "(Optional, defaults to 100)") do |concurrency|
@@ -159,11 +159,11 @@ HELP
159
159
 
160
160
  results[:format] = 'human'
161
161
  opts.on('--format FORMAT',
162
- "Output format to use") do |format|
162
+ "Output format to use: human or json") do |format|
163
163
  if %w[human json].include? format
164
164
  results[:format] = format
165
165
  else
166
- raise ArgumentError "Unsupported format: #{format}"
166
+ raise Bolt::CLIError, "Unsupported format: #{format}"
167
167
  end
168
168
  end
169
169
  results[:insecure] = false
@@ -190,7 +190,13 @@ HELP
190
190
  end
191
191
  opts.on('--sudo-password [PASSWORD]',
192
192
  'Password for privilege escalation') do |password|
193
- options[:sudo_password] = password
193
+ if password.nil?
194
+ STDOUT.print "Please enter your privilege escalation password: "
195
+ results[:sudo_password] = STDIN.noecho(&:gets).chomp
196
+ STDOUT.puts
197
+ else
198
+ results[:sudo_password] = password
199
+ end
194
200
  end
195
201
  opts.on_tail('--[no-]tty',
196
202
  "Request a pseudo TTY on nodes that support it") do |tty|
@@ -237,6 +243,8 @@ HELP
237
243
  @config[:log_level] = Logger::INFO
238
244
  end
239
245
 
246
+ @config[:key] = options[:key]
247
+
240
248
  @config[:format] = options[:format]
241
249
 
242
250
  if options[:help]
@@ -398,16 +406,16 @@ HELP
398
406
  results =
399
407
  case options[:mode]
400
408
  when 'command'
401
- executor.run_command(nodes, options[:object]) do |node, result|
402
- outputter.print_result(node, result)
409
+ executor.run_command(nodes, options[:object]) do |node, event|
410
+ outputter.print_event(node, event)
403
411
  end
404
412
  when 'script'
405
413
  script = options[:object]
406
414
  validate_file('script', script)
407
415
  executor.run_script(
408
416
  nodes, script, options[:leftovers]
409
- ) do |node, result|
410
- outputter.print_result(node, result)
417
+ ) do |node, event|
418
+ outputter.print_event(node, event)
411
419
  end
412
420
  when 'task'
413
421
  task_name = options[:object]
@@ -418,8 +426,8 @@ HELP
418
426
  input_method ||= 'both'
419
427
  executor.run_task(
420
428
  nodes, path, input_method, options[:task_options]
421
- ) do |node, result|
422
- outputter.print_result(node, result)
429
+ ) do |node, event|
430
+ outputter.print_event(node, event)
423
431
  end
424
432
  when 'file'
425
433
  src = options[:object]
@@ -429,8 +437,8 @@ HELP
429
437
  raise Bolt::CLIError, "A destination path must be specified"
430
438
  end
431
439
  validate_file('source file', src)
432
- executor.file_upload(nodes, src, dest) do |node, result|
433
- outputter.print_result(node, result)
440
+ executor.file_upload(nodes, src, dest) do |node, event|
441
+ outputter.print_event(node, event)
434
442
  end
435
443
  end
436
444
  end
@@ -519,8 +527,10 @@ HELP
519
527
  cli << "--#{setting}" << dir
520
528
  end
521
529
  Puppet.initialize_settings(cli)
522
- Puppet::Pal.in_tmp_environment('bolt', modulepath: modulepath) do |pal|
523
- puts pal.run_plan(plan, plan_args: args)
530
+ Puppet::Pal.in_tmp_environment('bolt', modulepath: [BOLTLIB_PATH] + modulepath) do |pal|
531
+ pal.with_script_compiler do |compiler|
532
+ compiler.call_function('run_plan', plan, args)
533
+ end
524
534
  end
525
535
  end
526
536
  end
data/lib/bolt/config.rb CHANGED
@@ -12,6 +12,7 @@ module Bolt
12
12
  :sudo,
13
13
  :sudo_password,
14
14
  :transport,
15
+ :key,
15
16
  :tty,
16
17
  :user
17
18
  ) do
data/lib/bolt/error.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Bolt
2
+ class Error < RuntimeError
3
+ attr_reader :kind, :details, :issue_code
4
+
5
+ def initialize(msg, kind, details = nil, issue_code = nil)
6
+ super(msg)
7
+ @kind = kind
8
+ @issue_code = issue_code
9
+ @details = details || {}
10
+ end
11
+
12
+ def msg
13
+ message
14
+ end
15
+
16
+ def to_h
17
+ h = { 'kind' => kind,
18
+ 'msg' => message,
19
+ 'details' => details }
20
+ h['issue_code'] = issue_code if issue_code
21
+ h
22
+ end
23
+
24
+ def to_json
25
+ to_h.to_json
26
+ end
27
+ end
28
+ end
data/lib/bolt/executor.rb CHANGED
@@ -37,18 +37,22 @@ module Bolt
37
37
  pool.post do
38
38
  result =
39
39
  begin
40
+ @notifier.notify(callback, node, type: :node_start) if callback
40
41
  node.connect
41
42
  yield node
42
- rescue Bolt::Node::BaseError => ex
43
- Bolt::ErrorResult.new(ex.message, ex.issue_code, ex.kind)
44
43
  rescue StandardError => ex
45
- node.logger.error(ex)
46
- Bolt::ExceptionResult.new(ex)
44
+ Bolt::Result.from_exception(ex)
47
45
  ensure
48
- node.disconnect
46
+ begin
47
+ node.disconnect
48
+ rescue StandardError => ex
49
+ @logger.info("Failed to close connection to #{node.uri} : #{ex.message}")
50
+ end
49
51
  end
50
52
  results[node] = result
51
- @notifier.notify(callback, node, result) if callback
53
+ if callback
54
+ @notifier.notify(callback, node, type: :node_result, result: result)
55
+ end
52
56
  result
53
57
  end
54
58
  }
data/lib/bolt/node.rb CHANGED
@@ -37,6 +37,7 @@ module Bolt
37
37
  @port = port
38
38
  @user = user || config[:user]
39
39
  @password = password || config[:password]
40
+ @key = config[:key]
40
41
  @tty = config[:tty]
41
42
  @insecure = config[:insecure]
42
43
  @uri = uri
@@ -60,23 +61,24 @@ module Bolt
60
61
  @logger.debug { "Uploading #{source} to #{destination}" }
61
62
  result = _upload(source, destination)
62
63
  if result.success?
63
- Bolt::Result.new("Uploaded '#{source}' to '#{host}:#{destination}'")
64
+ Bolt::Result.new(nil, "Uploaded '#{source}' to '#{host}:#{destination}'")
64
65
  else
65
- result.to_result
66
+ result
66
67
  end
67
68
  end
68
69
 
69
70
  def run_command(command)
70
71
  @logger.info { "Running command: #{command}" }
71
- _run_command(command).to_command_result
72
+ _run_command(command)
72
73
  end
73
74
 
74
75
  def run_script(script, arguments)
75
- _run_script(script, arguments).to_command_result
76
+ @logger.info { "Running script: #{command}" }
77
+ _run_script(script, arguments)
76
78
  end
77
79
 
78
80
  def run_task(task, input_method, arguments)
79
- _run_task(task, input_method, arguments).to_task_result
81
+ _run_task(task, input_method, arguments)
80
82
  end
81
83
  end
82
84
  end
@@ -1,11 +1,10 @@
1
1
  module Bolt
2
2
  class Node
3
- class BaseError < StandardError
3
+ class BaseError < Bolt::Error
4
4
  attr_reader :issue_code
5
5
 
6
6
  def initialize(message, issue_code)
7
- super(message)
8
- @issue_code = issue_code
7
+ super(message, kind, nil, issue_code)
9
8
  end
10
9
 
11
10
  def kind
@@ -18,5 +17,28 @@ module Bolt
18
17
  'puppetlabs.tasks/connect-error'
19
18
  end
20
19
  end
20
+
21
+ class EscalateError < BaseError
22
+ def kind
23
+ 'puppetlabs.tasks/escalate-error'
24
+ end
25
+ end
26
+
27
+ class FileError < BaseError
28
+ def kind
29
+ 'puppetlabs.tasks/task_file_error'
30
+ end
31
+ end
32
+
33
+ class EnvironmentVarError < BaseError
34
+ def initialize(var, val)
35
+ message = "Could not set environment variable '#{var}' to '#{val}'"
36
+ super(message, 'ENVVAR_ERROR')
37
+ end
38
+
39
+ def kind
40
+ 'puppetlabs.tasks/environment-var-error'
41
+ end
42
+ end
21
43
  end
22
44
  end
@@ -47,10 +47,8 @@ module Bolt
47
47
  state = node_result['state']
48
48
  result = node_result['result']
49
49
 
50
- result_output = Bolt::Node::ResultOutput.new
51
- result_output.stdout << result.to_json
52
50
  if state == 'finished'
53
- Bolt::Node::Success.new(result.to_json, result_output)
51
+ exit_code = 0
54
52
  else
55
53
  # Try to extract the exit_code from _error
56
54
  begin
@@ -58,29 +56,20 @@ module Bolt
58
56
  rescue NoMethodError
59
57
  exit_code = 'unknown'
60
58
  end
61
- Bolt::Node::Failure.new(exit_code, result_output)
62
59
  end
60
+ Bolt::TaskResult.new(result.to_json, "", exit_code)
63
61
  end
64
62
 
65
63
  # run_task generates a result that makes sense for a generic task which
66
64
  # needs to be unwrapped to extract stdout/stderr/exitcode.
67
65
  #
68
66
  def unwrap_bolt_result(result)
69
- task_result = JSON.parse(result.output.stdout.string)
70
- if task_result['exit_code'].nil?
67
+ if result.error
71
68
  # something went wrong return the failure
72
69
  return result
73
70
  end
74
71
 
75
- # Otherwise create a new result with the captured output
76
- result_output = Bolt::Node::ResultOutput.new
77
- result_output.stdout << task_result['stdout']
78
- result_output.stderr << task_result['stderr']
79
- if (task_result['exit_code']).zero?
80
- Bolt::Node::Success.new(task_result['stdout'], result_output)
81
- else
82
- Bolt::Node::Failure.new(task_result['exit_code'], result_output)
83
- end
72
+ Bolt::CommandResult.new(result.value['stdout'], result.value['stderr'], result.value['exit_code'])
84
73
  end
85
74
 
86
75
  def _run_command(command, options = {})
@@ -102,7 +91,9 @@ module Bolt
102
91
  content: content,
103
92
  mode: mode
104
93
  }
105
- _run_task(BOLT_MOCK_FILE, 'stdin', params)
94
+ result = _run_task(BOLT_MOCK_FILE, 'stdin', params)
95
+ result = Bolt::Result.new unless result.error
96
+ result
106
97
  end
107
98
 
108
99
  def _run_script(script, arguments)
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+ require 'bolt/result'
3
+
4
+ module Bolt
5
+ class Node
6
+ class Output
7
+ attr_reader :stdout, :stderr
8
+ attr_accessor :exit_code
9
+
10
+ def initialize
11
+ @stdout = StringIO.new
12
+ @stderr = StringIO.new
13
+ @exit_code = 'unkown'
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/bolt/node/ssh.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  require 'json'
2
2
  require 'shellwords'
3
3
  require 'net/ssh'
4
- require 'net/sftp'
5
- require 'bolt/node/result'
4
+ require 'net/scp'
5
+ require 'bolt/node/output'
6
6
 
7
7
  module Bolt
8
8
  class SSH < Node
@@ -22,6 +22,7 @@ module Bolt
22
22
 
23
23
  options[:port] = @port if @port
24
24
  options[:password] = @password if @password
25
+ options[:keys] = @key if @key
25
26
  options[:verify_host_key] = if @insecure
26
27
  Net::SSH::Verifiers::Lenient.new
27
28
  else
@@ -54,11 +55,41 @@ module Bolt
54
55
  end
55
56
  end
56
57
 
58
+ def sudo_prompt
59
+ '[sudo] Bolt needs to run as another user, password: '
60
+ end
61
+
62
+ def handled_sudo(channel, data)
63
+ if data == sudo_prompt
64
+ if @sudo_password
65
+ channel.send_data "#{@sudo_password}\n"
66
+ channel.wait
67
+ return true
68
+ else
69
+ raise Bolt::Node::EscalateError.new(
70
+ "Sudo password for user #{@user} was not provided for #{@uri}",
71
+ 'NO_PASSWORD'
72
+ )
73
+ end
74
+ elsif data =~ /^#{@user} is not in the sudoers file\./
75
+ @logger.info { data }
76
+ raise Bolt::Node::EscalateError.new(
77
+ "User #{@user} does not have sudo permission on #{@uri}",
78
+ 'SUDO_DENIED'
79
+ )
80
+ elsif data =~ /^Sorry, try again\./
81
+ @logger.info { data }
82
+ raise Bolt::Node::EscalateError.new(
83
+ "Sudo password for user #{@user} not recognized on #{@uri}",
84
+ 'BAD_PASSWORD'
85
+ )
86
+ end
87
+ false
88
+ end
89
+
57
90
  def execute(command, sudoable: false, **options)
58
- result_output = Bolt::Node::ResultOutput.new
59
- status = {}
91
+ result_output = Bolt::Node::Output.new
60
92
  use_sudo = sudoable && (@sudo || @run_as)
61
- sudo_prompt = '[sudo] Bolt needs to run as another user, password: '
62
93
  if use_sudo
63
94
  user_clause = if @run_as
64
95
  "-u #{@run_as}"
@@ -75,30 +106,29 @@ module Bolt
75
106
  channel.request_pty if @tty
76
107
 
77
108
  channel.exec(command) do |_, success|
78
- raise "could not execute command: #{command.inspect}" unless success
109
+ unless success
110
+ raise Bolt::Node::ConnectError.new(
111
+ "Could not execute command: #{command.inspect}",
112
+ 'EXEC_ERROR'
113
+ )
114
+ end
79
115
 
80
116
  channel.on_data do |_, data|
81
- if use_sudo && data == sudo_prompt
82
- channel.send_data "#{@sudo_password}\n"
83
- channel.wait
84
- else
117
+ unless use_sudo && handled_sudo(channel, data)
85
118
  result_output.stdout << data
86
119
  end
87
120
  @logger.debug { "stdout: #{data}" }
88
121
  end
89
122
 
90
123
  channel.on_extended_data do |_, _, data|
91
- if use_sudo && data == sudo_prompt
92
- channel.send_data "#{@sudo_password}\n"
93
- channel.wait
94
- else
124
+ unless use_sudo && handled_sudo(channel, data)
95
125
  result_output.stderr << data
96
126
  end
97
127
  @logger.debug { "stderr: #{data}" }
98
128
  end
99
129
 
100
130
  channel.on_request("exit-status") do |_, data|
101
- status[:exit_code] = data.read_long
131
+ result_output.exit_code = data.read_long
102
132
  end
103
133
 
104
134
  if options[:stdin]
@@ -109,45 +139,52 @@ module Bolt
109
139
  end
110
140
  session_channel.wait
111
141
 
112
- if status[:exit_code].zero?
142
+ if result_output.exit_code == 0
113
143
  @logger.debug { "Command returned successfully" }
114
- Bolt::Node::Success.new(result_output.stdout.string, result_output)
115
144
  else
116
- @logger.info { "Command failed with exit code #{status[:exit_code]}" }
117
- Bolt::Node::Failure.new(status[:exit_code], result_output)
145
+ @logger.info { "Command failed with exit code #{result_output.exit_code}" }
118
146
  end
147
+ result_output
119
148
  end
120
149
 
121
150
  def _upload(source, destination)
122
- Net::SFTP::Session.new(@session).connect! do |sftp|
123
- sftp.upload!(source, destination)
124
- end
125
- Bolt::Node::Success.new
151
+ write_remote_file(source, destination)
152
+ Bolt::Result.new
126
153
  rescue StandardError => e
127
- Bolt::Node::ExceptionFailure.new(e)
154
+ Bolt::Result.from_exception(e)
128
155
  end
129
156
 
130
- def make_tempdir
131
- Bolt::Node::Success.new(@session.exec!('mktemp -d').chomp)
157
+ def write_remote_file(source, destination)
158
+ @session.scp.upload!(source, destination)
132
159
  rescue StandardError => e
133
- Bolt::Node::ExceptionFailure.new(e)
160
+ raise FileError.new(e.message, 'WRITE_ERROR')
161
+ end
162
+
163
+ def make_tempdir
164
+ result = execute('mktemp -d')
165
+ if result.exit_code != 0
166
+ raise FileError.new("Could not make tempdir: #{result.stderr.string}", 'TEMPDIR_ERROR')
167
+ end
168
+ result.stdout.string.chomp
134
169
  end
135
170
 
136
171
  def with_remote_tempdir
137
- make_tempdir.then do |dir|
138
- (yield dir).ensure do
139
- execute("rm -rf '#{dir}'")
172
+ dir = make_tempdir
173
+ begin
174
+ yield dir
175
+ ensure
176
+ output = execute("rm -rf '#{dir}'")
177
+ if output.exit_code != 0
178
+ logger.warn("Failed to clean up tempdir '#{dir}': #{output.stderr.string}")
140
179
  end
141
180
  end
142
181
  end
143
182
 
144
183
  def with_remote_script(dir, file)
145
184
  remote_path = "#{dir}/#{File.basename(file)}"
146
- _upload(file, remote_path).then do
147
- execute("chmod u+x '#{remote_path}'")
148
- end.then do
149
- yield remote_path
150
- end
185
+ write_remote_file(file, remote_path)
186
+ make_executable(remote_path)
187
+ yield remote_path
151
188
  end
152
189
 
153
190
  def with_remote_file(file)
@@ -167,14 +204,19 @@ EOF
167
204
  SCRIPT
168
205
  end
169
206
 
207
+ def make_executable(path)
208
+ result = execute("chmod u+x '#{path}'")
209
+ if result.exit_code != 0
210
+ raise FileError.new("Could not make file '#{path}' executable: #{result.stderr.string}", 'CHMOD_ERROR')
211
+ end
212
+ end
213
+
170
214
  def with_task_wrapper(remote_task, dir, stdin)
171
215
  wrapper = make_wrapper_stringio(remote_task, stdin)
172
216
  command = "#{dir}/wrapper.sh"
173
- _upload(wrapper, command).then do
174
- execute("chmod u+x '#{command}'")
175
- end.then do
176
- yield command
177
- end
217
+ write_remote_file(wrapper, command)
218
+ make_executable(command)
219
+ yield command
178
220
  end
179
221
 
180
222
  def with_remote_task(task_file, stdin)
@@ -192,7 +234,12 @@ SCRIPT
192
234
  end
193
235
 
194
236
  def _run_command(command)
195
- execute(command, sudoable: true)
237
+ output = execute(command, sudoable: true)
238
+ Bolt::CommandResult.from_output(output)
239
+ # TODO: We should be able to rely on the excutor for this but it will mean
240
+ # a test refactor
241
+ rescue StandardError => e
242
+ Bolt::Result.from_exception(e)
196
243
  end
197
244
 
198
245
  def _run_script(script, arguments)
@@ -200,9 +247,14 @@ SCRIPT
200
247
  @logger.debug { "arguments: #{arguments}" }
201
248
 
202
249
  with_remote_file(script) do |remote_path|
203
- execute("'#{remote_path}' #{Shellwords.join(arguments)}",
204
- sudoable: true)
250
+ output = execute("'#{remote_path}' #{Shellwords.join(arguments)}",
251
+ sudoable: true)
252
+ Bolt::CommandResult.from_output(output)
205
253
  end
254
+ # TODO: We should be able to rely on the excutor for this but it will mean
255
+ # a test refactor
256
+ rescue StandardError => e
257
+ Bolt::Result.from_exception(e)
206
258
  end
207
259
 
208
260
  def _run_task(task, input_method, arguments)
@@ -228,8 +280,13 @@ SCRIPT
228
280
  else
229
281
  "#{export_args} '#{remote_path}'"
230
282
  end
231
- execute(command, sudoable: true)
283
+ output = execute(command, sudoable: true)
284
+ Bolt::TaskResult.from_output(output)
232
285
  end
286
+ # TODO: We should be able to rely on the excutor for this but it will mean
287
+ # a test refactor
288
+ rescue StandardError => e
289
+ Bolt::Result.from_exception(e)
233
290
  end
234
291
  end
235
292
  end