bolt 0.20.5 → 0.20.6

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +1 -1
  3. data/lib/bolt/analytics.rb +160 -0
  4. data/lib/bolt/cli.rb +14 -4
  5. data/lib/bolt/executor.rb +15 -2
  6. data/lib/bolt/pal.rb +3 -1
  7. data/lib/bolt/puppetdb/config.rb +9 -4
  8. data/lib/bolt/transport/local.rb +3 -2
  9. data/lib/bolt/transport/orch.rb +1 -1
  10. data/lib/bolt/transport/orch/connection.rb +1 -0
  11. data/lib/bolt/transport/ssh.rb +7 -2
  12. data/lib/bolt/transport/ssh/connection.rb +39 -19
  13. data/lib/bolt/transport/winrm.rb +1 -0
  14. data/lib/bolt/transport/winrm/connection.rb +3 -0
  15. data/lib/bolt/util.rb +5 -0
  16. data/lib/bolt/version.rb +1 -1
  17. data/lib/bolt_ext/puppetdb_inventory.rb +2 -1
  18. data/vendored/puppet/lib/puppet/defaults.rb +15 -1
  19. data/vendored/puppet/lib/puppet/pops/loader/static_loader.rb +0 -14
  20. data/vendored/puppet/lib/puppet/pops/pcore.rb +1 -1
  21. data/vendored/puppet/lib/puppet/type/file/mode.rb +1 -1
  22. data/vendored/require_vendored.rb +0 -2
  23. metadata +3 -21
  24. data/vendored/puppet/lib/puppet/external/nagios.rb +0 -46
  25. data/vendored/puppet/lib/puppet/external/nagios/base.rb +0 -472
  26. data/vendored/puppet/lib/puppet/external/nagios/parser.rb +0 -400
  27. data/vendored/puppet/lib/puppet/provider/naginator.rb +0 -63
  28. data/vendored/puppet/lib/puppet/type/nagios_command.rb +0 -3
  29. data/vendored/puppet/lib/puppet/type/nagios_contact.rb +0 -3
  30. data/vendored/puppet/lib/puppet/type/nagios_contactgroup.rb +0 -3
  31. data/vendored/puppet/lib/puppet/type/nagios_host.rb +0 -3
  32. data/vendored/puppet/lib/puppet/type/nagios_hostdependency.rb +0 -3
  33. data/vendored/puppet/lib/puppet/type/nagios_hostescalation.rb +0 -3
  34. data/vendored/puppet/lib/puppet/type/nagios_hostextinfo.rb +0 -3
  35. data/vendored/puppet/lib/puppet/type/nagios_hostgroup.rb +0 -3
  36. data/vendored/puppet/lib/puppet/type/nagios_service.rb +0 -3
  37. data/vendored/puppet/lib/puppet/type/nagios_servicedependency.rb +0 -3
  38. data/vendored/puppet/lib/puppet/type/nagios_serviceescalation.rb +0 -3
  39. data/vendored/puppet/lib/puppet/type/nagios_serviceextinfo.rb +0 -3
  40. data/vendored/puppet/lib/puppet/type/nagios_servicegroup.rb +0 -3
  41. data/vendored/puppet/lib/puppet/type/nagios_timeperiod.rb +0 -3
  42. data/vendored/puppet/lib/puppet/util/nagios_maker.rb +0 -85
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be38f987a3976c357fd09e70de9ad65509014fd7
4
- data.tar.gz: c92478de40249c5714dc67490b378dee76bd3f97
3
+ metadata.gz: ecd8364f409304d8286adae0544a3f2b34038403
4
+ data.tar.gz: e13c9abe1b098794394947baec4da9d349a922fe
5
5
  SHA512:
6
- metadata.gz: e8094e1f2879c548bdd4f37af9419fbac6d6acefacfa32eead97fd020cdf301682060f6f1c482e9dd4ff4f8ddc41d7c6a0e94761099013bf006814c0a440d008
7
- data.tar.gz: a76234f46802619085b65907b5f38f4c83c5865ffd91a7a45ad6472461ae78d7cc7eb7cb3b776a49263f1af72715986b972123fb1e168cdb466c1a39d1e953df
6
+ metadata.gz: d98430251616d064eb98830532ce8c7de35c4f60b7b55768b9d82a9fbbb9c183363888da3e98d2d915df9637ff488893a495f6a5fe0a6254ed329ecfa2077908
7
+ data.tar.gz: 99827f1b25b5b83849c9900551168f3377b2f3b99da600c32f9284173247988c6fbf550ac737f81875d32ea78cf2885d2b9395879e314f6d64d4eb53ae2ff835
@@ -14,7 +14,7 @@ Puppet::DataTypes.create_type('Target') do
14
14
  protocol => Callable[[], Optional[String[1]]],
15
15
  user => Callable[[], Optional[String[1]]],
16
16
  }
17
- PUPPET
17
+ PUPPET
18
18
 
19
19
  load_file('bolt/target')
20
20
 
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/version'
4
+ require 'httpclient'
5
+ require 'json'
6
+ require 'locale'
7
+ require 'logging'
8
+ require 'securerandom'
9
+
10
+ module Bolt
11
+ module Analytics
12
+ PROTOCOL_VERSION = 1
13
+ APPLICATION_NAME = 'bolt'
14
+ TRACKING_ID = 'UA-120367942-1'
15
+ TRACKING_URL = 'https://google-analytics.com/collect'
16
+
17
+ def self.build_client
18
+ logger = Logging.logger[self]
19
+
20
+ config_file = File.expand_path('~/.puppetlabs/bolt/analytics.yaml')
21
+ config = load_config(config_file)
22
+
23
+ if config['disabled'] || ENV['BOLT_DISABLE_ANALYTICS']
24
+ logger.debug "Analytics opt-out is set, analytics will be disabled"
25
+ NoopClient.new
26
+ else
27
+ unless config.key?('user-id')
28
+ config['user-id'] = SecureRandom.uuid
29
+ write_config(config_file, config)
30
+ end
31
+
32
+ Client.new(config['user-id'])
33
+ end
34
+ rescue StandardError => e
35
+ logger.debug "Failed to initialize analytics client, analytics will be disabled: #{e}"
36
+ NoopClient.new
37
+ end
38
+
39
+ def self.load_config(filename)
40
+ if File.exist?(filename)
41
+ YAML.load_file(filename)
42
+ else
43
+ {}
44
+ end
45
+ end
46
+
47
+ def self.write_config(filename, config)
48
+ FileUtils.mkdir_p(File.dirname(filename))
49
+ File.write(filename, config.to_yaml)
50
+ end
51
+
52
+ class Client
53
+ attr_reader :user_id
54
+
55
+ def initialize(user_id)
56
+ @logger = Logging.logger[self]
57
+ @http = HTTPClient.new
58
+ @user_id = user_id
59
+ @executor = Concurrent.global_io_executor
60
+ @os = compute_os
61
+ end
62
+
63
+ def screen_view(screen)
64
+ screen_view_params = {
65
+ # Type
66
+ t: 'screenview',
67
+ # Screen Name
68
+ cd: screen
69
+ }
70
+
71
+ submit(base_params.merge(screen_view_params))
72
+ end
73
+
74
+ def event(category, action, label = nil, value = nil)
75
+ event_params = {
76
+ # Type
77
+ t: 'event',
78
+ # Event Category
79
+ ec: category,
80
+ # Event Action
81
+ ea: action
82
+ }
83
+
84
+ # Event Label
85
+ event_params[:el] = label if label
86
+ # Event Value
87
+ event_params[:ev] = value if value
88
+
89
+ submit(base_params.merge(event_params))
90
+ end
91
+
92
+ def submit(params)
93
+ # Handle analytics submission in the background to avoid blocking the
94
+ # app or polluting the log with errors
95
+ Concurrent::Future.execute(executor: @executor) do
96
+ @logger.debug "Submitting analytics: #{JSON.pretty_generate(params)}"
97
+ @http.post(TRACKING_URL, params)
98
+ @logger.debug "Completed analytics submission"
99
+ end
100
+ end
101
+
102
+ # These parameters have terrible names. See this page for complete documentation:
103
+ # https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
104
+ def base_params
105
+ {
106
+ v: PROTOCOL_VERSION,
107
+ # Client ID
108
+ cid: @user_id,
109
+ # Tracking ID
110
+ tid: TRACKING_ID,
111
+ # Application Name
112
+ an: APPLICATION_NAME,
113
+ # Application Version
114
+ av: Bolt::VERSION,
115
+ # Anonymize IPs
116
+ aip: true,
117
+ # User locale
118
+ ul: Locale.current.to_rfc,
119
+ # Custom Dimension 1 (Operating System)
120
+ cd1: @os.value
121
+ }
122
+ end
123
+
124
+ def compute_os
125
+ Concurrent::Future.execute(executor: @executor) do
126
+ require_relative '../../vendored/require_vendored'
127
+ require 'facter'
128
+ os = Facter.value('os')
129
+ "#{os['name']} #{os.dig('release', 'major')}"
130
+ end
131
+ end
132
+
133
+ # If the user is running a very fast command, there may not be time for
134
+ # analytics submission to complete before the command is finished. In
135
+ # that case, we give a little buffer for any stragglers to finish up.
136
+ # 250ms strikes a balance between accomodating slower networks while not
137
+ # introducing a noticeable "hang".
138
+ def finish
139
+ @executor.shutdown
140
+ @executor.wait_for_termination(0.25)
141
+ end
142
+ end
143
+
144
+ class NoopClient
145
+ def initialize
146
+ @logger = Logging.logger[self]
147
+ end
148
+
149
+ def screen_view(screen)
150
+ @logger.debug "Skipping submission of '#{screen}' screenview because analytics is disabled"
151
+ end
152
+
153
+ def event(category, action, _label = nil, _value = nil)
154
+ @logger.debug "Skipping submission of '#{category} #{action}' event because analytics is disabled"
155
+ end
156
+
157
+ def finish; end
158
+ end
159
+ end
160
+ end
@@ -6,6 +6,7 @@ require 'json'
6
6
  require 'io/console'
7
7
  require 'logging'
8
8
  require 'optparse'
9
+ require 'bolt/analytics'
9
10
  require 'bolt/config'
10
11
  require 'bolt/error'
11
12
  require 'bolt/executor'
@@ -493,6 +494,15 @@ Available options are:
493
494
  exit!
494
495
  end
495
496
 
497
+ @analytics = Bolt::Analytics.build_client
498
+
499
+ screen = "#{options[:mode]}_#{options[:action]}"
500
+ # submit a different screen for `bolt task show` and `bolt task show foo`
501
+ if options[:action] == 'show' && options[:object]
502
+ screen += '_object'
503
+ end
504
+ @analytics.screen_view(screen)
505
+
496
506
  if options[:mode] == 'plan' || options[:mode] == 'task'
497
507
  pal = Bolt::PAL.new(config)
498
508
  end
@@ -540,17 +550,16 @@ Available options are:
540
550
  params: params }
541
551
  plan_context[:description] = options[:description] if options[:description]
542
552
 
543
- executor = Bolt::Executor.new(config, options[:noop])
553
+ executor = Bolt::Executor.new(config, @analytics, options[:noop])
544
554
  executor.start_plan(plan_context)
545
555
  result = pal.run_plan(options[:object], options[:task_options], executor, inventory, puppetdb_client)
546
556
 
547
557
  # If a non-bolt exeception bubbles up the plan won't get finished
548
- # TODO: finish the plan once ORCH-2224
549
- # executor.finish_plan(result)
558
+ executor.finish_plan(result)
550
559
  outputter.print_plan_result(result)
551
560
  code = result.ok? ? 0 : 1
552
561
  else
553
- executor = Bolt::Executor.new(config, options[:noop])
562
+ executor = Bolt::Executor.new(config, @analytics, options[:noop])
554
563
  targets = options[:targets]
555
564
 
556
565
  results = nil
@@ -606,6 +615,7 @@ Available options are:
606
615
  ensure
607
616
  # restore original signal handler
608
617
  Signal.trap :INT, handler if handler
618
+ @analytics&.finish
609
619
  end
610
620
 
611
621
  def validate_file(type, path)
@@ -5,6 +5,8 @@ require 'English'
5
5
  require 'json'
6
6
  require 'concurrent'
7
7
  require 'logging'
8
+ require 'set'
9
+ require 'bolt/analytics'
8
10
  require 'bolt/result'
9
11
  require 'bolt/config'
10
12
  require 'bolt/notifier'
@@ -16,14 +18,18 @@ module Bolt
16
18
  attr_reader :noop, :transports
17
19
  attr_accessor :run_as, :plan_logging
18
20
 
19
- def initialize(config = Bolt::Config.new, noop = nil)
21
+ def initialize(config = Bolt::Config.new, analytics = Bolt::Analytics::NoopClient.new, noop = nil)
20
22
  @config = config
23
+ @analytics = analytics
21
24
  @logger = Logging.logger[self]
22
25
  @plan_logging = false
23
26
 
24
27
  @transports = Bolt::TRANSPORTS.each_with_object({}) do |(key, val), coll|
25
- coll[key.to_s] = Concurrent::Delay.new { val.new }
28
+ coll[key.to_s] = Concurrent::Delay.new do
29
+ val.new
30
+ end
26
31
  end
32
+ @reported_transports = Set.new
27
33
 
28
34
  @noop = noop
29
35
  @run_as = nil
@@ -50,6 +56,7 @@ module Bolt
50
56
  def batch_execute(targets)
51
57
  promises = targets.group_by(&:protocol).flat_map do |protocol, protocol_targets|
52
58
  transport = transport(protocol)
59
+ report_transport(transport, protocol_targets.count)
53
60
  transport.batches(protocol_targets).flat_map do |batch|
54
61
  batch_promises = Array(batch).each_with_object({}) do |target, h|
55
62
  h[target] = Concurrent::Promise.new(executor: :immediate)
@@ -110,6 +117,12 @@ module Bolt
110
117
  end
111
118
  private :log_action
112
119
 
120
+ def report_transport(transport, count)
121
+ name = transport.class.name.split('::').last.downcase
122
+ @analytics&.event('Transport', 'initialize', name, count) unless @reported_transports.include?(name)
123
+ @reported_transports.add(name)
124
+ end
125
+
113
126
  def with_node_logging(description, batch)
114
127
  @logger.info("#{description} on #{batch.map(&:uri)}")
115
128
  result = yield
@@ -3,6 +3,7 @@
3
3
  require 'bolt/executor'
4
4
  require 'bolt/error'
5
5
  require 'bolt/plan_result'
6
+ require 'bolt/util'
6
7
 
7
8
  module Bolt
8
9
  class PAL
@@ -54,7 +55,7 @@ module Bolt
54
55
  end
55
56
 
56
57
  def self.load_puppet
57
- if Gem.win_platform?
58
+ if Bolt::Util.windows?
58
59
  # Windows 'fix' for openssl behaving strangely. Prevents very slow operation
59
60
  # of random_bytes later when establishing winrm connections from a Windows host.
60
61
  # See https://github.com/rails/rails/issues/25805 for background.
@@ -64,6 +65,7 @@ module Bolt
64
65
 
65
66
  begin
66
67
  require_relative '../../vendored/require_vendored'
68
+ require 'puppet_pal'
67
69
  rescue LoadError
68
70
  raise Bolt::Error.new("Puppet must be installed to execute tasks", "bolt/puppet-missing")
69
71
  end
@@ -1,30 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'bolt/util'
4
5
 
5
6
  module Bolt
6
7
  module PuppetDB
7
8
  class Config
8
9
  DEFAULT_TOKEN = File.expand_path('~/.puppetlabs/token')
9
- DEFAULT_CONFIG = File.expand_path('~/.puppetlabs/client-tools/puppetdb.conf')
10
+ DEFAULT_CONFIG = { user: File.expand_path('~/.puppetlabs/client-tools/puppetdb.conf'),
11
+ global: '/etc/puppetlabs/client-tools/puppetdb.conf',
12
+ win_global: 'C:/ProgramData/PuppetLabs/client-tools/puppetdb.conf' }.freeze
10
13
 
11
14
  def initialize(config_file, options)
12
15
  @settings = load_config(config_file)
13
16
  @settings.merge!(options)
14
-
15
17
  expand_paths
16
18
  validate
17
19
  end
18
20
 
19
21
  def load_config(filename)
22
+ global_path = Bolt::Util.windows? ? DEFAULT_CONFIG[:win_global] : DEFAULT_CONFIG[:global]
20
23
  if filename
21
24
  if File.exist?(filename)
22
25
  config = JSON.parse(File.read(filename))
23
26
  else
24
27
  raise Bolt::PuppetDBError, "config file #{filename} does not exist"
25
28
  end
26
- elsif File.exist?(DEFAULT_CONFIG)
27
- config = JSON.parse(File.read(DEFAULT_CONFIG))
29
+ elsif File.exist?(DEFAULT_CONFIG[:user])
30
+ config = JSON.parse(File.read(DEFAULT_CONFIG[:user]))
31
+ elsif File.exist?(global_path)
32
+ config = JSON.parse(File.read(global_path))
28
33
  else
29
34
  config = {}
30
35
  end
@@ -5,6 +5,7 @@ require 'fileutils'
5
5
  require 'tmpdir'
6
6
  require 'bolt/transport/base'
7
7
  require 'bolt/result'
8
+ require 'bolt/util'
8
9
 
9
10
  module Bolt
10
11
  module Transport
@@ -20,7 +21,7 @@ module Bolt
20
21
  def initialize
21
22
  super
22
23
 
23
- if Gem.win_platform?
24
+ if Bolt::Util.windows?
24
25
  raise NotImplementedError, "The local transport is not yet implemented on Windows"
25
26
  else
26
27
  @conn = Shell.new
@@ -83,7 +84,7 @@ module Bolt
83
84
  executable = target.select_impl(task, PROVIDED_FEATURES)
84
85
  raise "No suitable implementation of #{task.name} for #{target.name}" unless executable
85
86
 
86
- input_method = task.input_method
87
+ input_method = task.input_method || "both"
87
88
  stdin = STDIN_METHODS.include?(input_method) ? JSON.dump(arguments) : nil
88
89
  env = ENVIRONMENT_METHODS.include?(input_method) ? arguments : nil
89
90
 
@@ -42,7 +42,7 @@ module Bolt
42
42
  begin
43
43
  conn.finish_plan(result)
44
44
  rescue StandardError => e
45
- @logger.error("Failed to finish plan on #{conn.key}: #{e.message}")
45
+ @logger.debug("Failed to finish plan on #{conn.key}: #{e.message}")
46
46
  end
47
47
  end
48
48
  end
@@ -28,6 +28,7 @@ module Bolt
28
28
 
29
29
  @client = OrchestratorClient.new(client_opts, true)
30
30
  @plan_job = start_plan(plan_context)
31
+ logger.debug("Started plan #{@plan_job}")
31
32
  @environment = opts["task-environment"]
32
33
  end
33
34
 
@@ -9,7 +9,7 @@ module Bolt
9
9
  module Transport
10
10
  class SSH < Base
11
11
  def self.options
12
- %w[port user password sudo-password private-key host-key-check connect-timeout tmpdir run-as tty]
12
+ %w[port user password sudo-password private-key host-key-check connect-timeout tmpdir run-as tty run-as-command]
13
13
  end
14
14
 
15
15
  PROVIDED_FEATURES = ['shell'].freeze
@@ -39,6 +39,11 @@ module Bolt
39
39
  error_msg = "connect-timeout value must be an Integer, received #{timeout_value}:#{timeout_value.class}"
40
40
  raise Bolt::ValidationError, error_msg
41
41
  end
42
+
43
+ run_as_cmd = options['run-as-command']
44
+ if run_as_cmd && (!run_as_cmd.is_a?(Array) || run_as_cmd.any? { |n| !n.is_a?(String) })
45
+ raise Bolt::ValidationError, "run-as-command must be an Array of Strings, received #{run_as_cmd}"
46
+ end
42
47
  end
43
48
 
44
49
  def initialize
@@ -113,7 +118,7 @@ module Bolt
113
118
  executable = target.select_impl(task, PROVIDED_FEATURES)
114
119
  raise "No suitable implementation of #{task.name} for #{target.name}" unless executable
115
120
 
116
- input_method = task.input_method
121
+ input_method = task.input_method || "both"
117
122
  with_connection(target) do |conn|
118
123
  conn.running_as(options['_run_as']) do
119
124
  stdin, output = nil
@@ -4,6 +4,7 @@ require 'logging'
4
4
  require 'shellwords'
5
5
  require 'bolt/node/errors'
6
6
  require 'bolt/node/output'
7
+ require 'bolt/util'
7
8
 
8
9
  module Bolt
9
10
  module Transport
@@ -24,12 +25,22 @@ module Bolt
24
25
  def chown(owner)
25
26
  return if owner.nil? || owner == @owner
26
27
 
27
- @owner = owner
28
- result = @node.execute(['chown', '-R', "#{@owner}:", @path], sudoable: true, run_as: 'root')
28
+ result = @node.execute(['id', '-g', owner])
29
+ if result.exit_code != 0
30
+ message = "Could not identify group of user #{owner}: #{result.stderr.string}"
31
+ raise Bolt::Node::FileError.new(message, 'ID_ERROR')
32
+ end
33
+ group = result.stdout.string.chomp
34
+
35
+ # Chown can only be run by root.
36
+ result = @node.execute(['chown', '-R', "#{owner}:#{group}", @path], sudoable: true, run_as: 'root')
29
37
  if result.exit_code != 0
30
- message = "Could not change owner of '#{@path}' to #{@owner}: #{result.stderr.string}"
38
+ message = "Could not change owner of '#{@path}' to #{owner}: #{result.stderr.string}"
31
39
  raise Bolt::Node::FileError.new(message, 'CHOWN_ERROR')
32
40
  end
41
+
42
+ # File ownership successfully changed, record the new owner.
43
+ @owner = owner
33
44
  end
34
45
 
35
46
  def delete
@@ -52,7 +63,7 @@ module Bolt
52
63
  @logger = Logging.logger[@target.host]
53
64
  end
54
65
 
55
- if !!File::ALT_SEPARATOR
66
+ if Bolt::Util.windows?
56
67
  require 'ffi'
57
68
  module Win
58
69
  extend FFI::Library
@@ -95,7 +106,7 @@ module Bolt
95
106
  @logger.debug { "Disabling use_agent in net-ssh: ssh-agent is not available" }
96
107
  options[:use_agent] = false
97
108
  end
98
- elsif !!File::ALT_SEPARATOR
109
+ elsif Bolt::Util.windows?
99
110
  pageant_wide = 'Pageant'.encode('UTF-16LE')
100
111
  if Win.FindWindow(pageant_wide, pageant_wide).to_i == 0
101
112
  @logger.debug { "Disabling use_agent in net-ssh: pageant process not running" }
@@ -160,6 +171,8 @@ module Bolt
160
171
  channel.wait
161
172
  return true
162
173
  else
174
+ # Cancel the sudo prompt to prevent later commands getting stuck
175
+ channel.close
163
176
  raise Bolt::Node::EscalateError.new(
164
177
  "Sudo password for user #{@user} was not provided for #{target.uri}",
165
178
  'NO_PASSWORD'
@@ -184,14 +197,20 @@ module Bolt
184
197
  def execute(command, sudoable: false, **options)
185
198
  result_output = Bolt::Node::Output.new
186
199
  run_as = options[:run_as] || self.run_as
187
- use_sudo = sudoable && run_as && @user != run_as
200
+ escalate = sudoable && run_as && @user != run_as
201
+ use_sudo = escalate && @target.options['run-as-command'].nil?
188
202
 
189
203
  command_str = command.is_a?(String) ? command : Shellwords.shelljoin(command)
190
- if use_sudo
191
- sudo_flags = ["sudo", "-S", "-u", run_as, "-p", sudo_prompt]
192
- sudo_flags += ["-E"] if options[:environment]
193
- sudo_str = Shellwords.shelljoin(sudo_flags)
194
- command_str = "#{sudo_str} #{command_str}"
204
+ if escalate
205
+ if use_sudo
206
+ sudo_flags = ["sudo", "-S", "-u", run_as, "-p", sudo_prompt]
207
+ sudo_flags += ["-E"] if options[:environment]
208
+ sudo_str = Shellwords.shelljoin(sudo_flags)
209
+ command_str = "#{sudo_str} #{command_str}"
210
+ else
211
+ run_as_str = Shellwords.shelljoin(@target.options['run-as-command'] + [run_as])
212
+ command_str = "#{run_as_str} #{command_str}"
213
+ end
195
214
  end
196
215
 
197
216
  # Including the environment declarations in the shelljoin will escape
@@ -221,14 +240,14 @@ module Bolt
221
240
  unless use_sudo && handled_sudo(channel, data)
222
241
  result_output.stdout << data
223
242
  end
224
- @logger.debug { "stdout: #{data}" }
243
+ @logger.debug { "stdout: #{data.strip}" }
225
244
  end
226
245
 
227
246
  channel.on_extended_data do |_, _, data|
228
247
  unless use_sudo && handled_sudo(channel, data)
229
248
  result_output.stderr << data
230
249
  end
231
- @logger.debug { "stderr: #{data}" }
250
+ @logger.debug { "stderr: #{data.strip}" }
232
251
  end
233
252
 
234
253
  channel.on_request("exit-status") do |_, data|
@@ -249,6 +268,9 @@ module Bolt
249
268
  @logger.info { "Command failed with exit code #{result_output.exit_code}" }
250
269
  end
251
270
  result_output
271
+ rescue StandardError
272
+ @logger.debug { "Command aborted" }
273
+ raise
252
274
  end
253
275
 
254
276
  def write_remote_file(source, destination)
@@ -258,12 +280,10 @@ module Bolt
258
280
  end
259
281
 
260
282
  def make_tempdir
261
- if target.options['tmpdir']
262
- tmppath = "#{target.options['tmpdir']}/#{SecureRandom.uuid}"
263
- command = ['mkdir', '-m', 700, tmppath]
264
- else
265
- command = ['mktemp', '-d']
266
- end
283
+ tmpdir = target.options.fetch('tmpdir', '/tmp')
284
+ tmppath = "#{tmpdir}/#{SecureRandom.uuid}"
285
+ command = ['mkdir', '-m', 700, tmppath]
286
+
267
287
  result = execute(command)
268
288
  if result.exit_code != 0
269
289
  raise Bolt::Node::FileError.new("Could not make tempdir: #{result.stderr.string}", 'TEMPDIR_ERROR')