bolt 2.16.0 → 2.17.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.

@@ -89,8 +89,10 @@ module Bolt
89
89
  :notice
90
90
  end
91
91
 
92
+ # Explicitly check the log level names instead of the log level number, as levels
93
+ # that are stringified integers (e.g. "level" => "42") will return a truthy value
92
94
  def self.valid_level?(level)
93
- !Logging.level_num(level).nil?
95
+ Logging::LEVELS.include?(Logging.levelify(level))
94
96
  end
95
97
 
96
98
  def self.levels
@@ -26,5 +26,5 @@ end
26
26
 
27
27
  require 'bolt/outputter/human'
28
28
  require 'bolt/outputter/json'
29
- require 'bolt/outputter/rainbow'
30
29
  require 'bolt/outputter/logger'
30
+ require 'bolt/outputter/rainbow'
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bolt/pal'
4
- require 'paint'
5
4
 
6
5
  module Bolt
7
6
  class Outputter
8
7
  class Rainbow < Bolt::Outputter::Human
9
8
  def initialize(color, verbose, trace, stream = $stdout)
9
+ begin
10
+ require 'paint'
11
+ rescue LoadError
12
+ raise "The 'paint' gem is required to use the rainbow outputter."
13
+ end
10
14
  super
11
15
  @line_color = 0
12
16
  @color = 0
@@ -15,25 +15,36 @@ module Bolt
15
15
  # PALError is used to convert errors from executing puppet code into
16
16
  # Bolt::Errors
17
17
  class PALError < Bolt::Error
18
- # Puppet sometimes rescues exceptions notes the location and reraises.
19
- # Return the original error.
20
18
  def self.from_preformatted_error(err)
21
19
  if err.cause&.is_a? Bolt::Error
22
20
  err.cause
23
21
  else
24
- from_error(err.cause || err)
22
+ from_error(err)
25
23
  end
26
24
  end
27
25
 
28
26
  # Generate a Bolt::Pal::PALError for non-bolt errors
29
27
  def self.from_error(err)
30
- e = new(err.message)
28
+ # Use the original error message if available
29
+ message = err.cause ? err.cause.message : err.message
30
+
31
+ # Provide the location of an error if it came from a plan
32
+ details = if defined?(err.file) && err.file
33
+ { file: err.file,
34
+ line: err.line,
35
+ column: err.pos }.compact
36
+ else
37
+ {}
38
+ end
39
+
40
+ e = new(message, details)
41
+
31
42
  e.set_backtrace(err.backtrace)
32
43
  e
33
44
  end
34
45
 
35
- def initialize(msg)
36
- super(msg, 'bolt/pal-error')
46
+ def initialize(msg, details = {})
47
+ super(msg, 'bolt/pal-error', details)
37
48
  end
38
49
  end
39
50
 
@@ -159,8 +170,9 @@ module Bolt
159
170
  if e.issue_code == :UNKNOWN_VARIABLE &&
160
171
  %w[facts trusted server_facts settings].include?(e.arguments[:name])
161
172
  message = "Evaluation Error: Variable '#{e.arguments[:name]}' is not available in the current scope "\
162
- "unless explicitly defined. (file: #{e.file}, line: #{e.line}, column: #{e.pos})"
163
- PALError.new(message)
173
+ "unless explicitly defined."
174
+ details = { file: e.file, line: e.line, column: e.pos }
175
+ PALError.new(message, details)
164
176
  else
165
177
  PALError.from_preformatted_error(e)
166
178
  end
@@ -23,7 +23,7 @@ module Bolt
23
23
 
24
24
  def run_command(command, options = {})
25
25
  running_as(options[:run_as]) do
26
- output = execute(command, sudoable: true)
26
+ output = execute(command, environment: options[:env_vars], sudoable: true)
27
27
  Bolt::Result.for_command(target,
28
28
  output.stdout.string,
29
29
  output.stderr.string,
@@ -58,7 +58,7 @@ module Bolt
58
58
  with_tmpdir do |dir|
59
59
  path = write_executable(dir.to_s, script)
60
60
  dir.chown(run_as)
61
- output = execute([path, *arguments], sudoable: true)
61
+ output = execute([path, *arguments], environment: options[:env_vars], sudoable: true)
62
62
  Bolt::Result.for_command(target,
63
63
  output.stdout.string,
64
64
  output.stderr.string,
@@ -170,7 +170,7 @@ module Bolt
170
170
  def check_sudo(out, inp, stdin)
171
171
  buffer = out.readpartial(CHUNK_SIZE)
172
172
  # Split on newlines, including the newline
173
- lines = buffer.split(/(?<=[\n])/)
173
+ lines = buffer.split(/(?<=\n)/)
174
174
  # handle_sudo will return the line if it is not a sudo prompt or error
175
175
  lines.map! { |line| handle_sudo(inp, line, stdin) }
176
176
  lines.join("")
@@ -277,31 +277,8 @@ module Bolt
277
277
  end
278
278
  end
279
279
 
280
- # In the case where a task is run with elevated privilege and needs stdin
281
- # a random string is echoed to stderr indicating that the stdin is available
282
- # for task input data because the sudo password has already either been
283
- # provided on stdin or was not needed.
284
- def prepend_sudo_success(sudo_id, command_str)
285
- command_str = "cd; #{command_str}" if conn.reset_cwd?
286
- "sh -c #{Shellwords.shellescape("echo #{sudo_id} 1>&2; #{command_str}")}"
287
- end
288
-
289
- def prepend_chdir(command_str)
290
- "sh -c #{Shellwords.shellescape("cd; #{command_str}")}"
291
- end
292
-
293
- # A helper to build up a single string that contains all of the options for
294
- # privilege escalation. A wrapper script is used to direct task input to stdin
295
- # when a tty is allocated and thus we do not need to prepend_sudo_success when
296
- # using the wrapper or when the task does not require stdin data.
297
- def build_sudoable_command_str(command_str, sudo_str, sudo_id, options)
298
- if options[:stdin] && !options[:wrapper]
299
- "#{sudo_str} #{prepend_sudo_success(sudo_id, command_str)}"
300
- elsif conn.reset_cwd?
301
- "#{sudo_str} #{prepend_chdir(command_str)}"
302
- else
303
- "#{sudo_str} #{command_str}"
304
- end
280
+ def sudo_success(sudo_id)
281
+ "echo #{sudo_id} 1>&2"
305
282
  end
306
283
 
307
284
  # Returns string with the interpreter conditionally prepended
@@ -322,13 +299,15 @@ module Bolt
322
299
  escalate = sudoable && run_as && conn.user != run_as
323
300
  use_sudo = escalate && @target.options['run-as-command'].nil?
324
301
 
325
- command_str = inject_interpreter(options[:interpreter], command)
302
+ # Depending on the transport, whether we're using sudo and whether
303
+ # there are environment variables to set, we may need to stitch
304
+ # together multiple commands into a single sh invocation
305
+ commands = [inject_interpreter(options[:interpreter], command)]
326
306
 
327
307
  if options[:environment]
328
- env_decls = options[:environment].map do |env, val|
308
+ env_decl = options[:environment].map do |env, val|
329
309
  "#{env}=#{Shellwords.shellescape(val)}"
330
- end
331
- command_str = "#{env_decls.join(' ')} #{command_str}"
310
+ end.join(' ')
332
311
  end
333
312
 
334
313
  if escalate
@@ -340,9 +319,18 @@ module Bolt
340
319
  else
341
320
  Shellwords.shelljoin(@target.options['run-as-command'] + [run_as])
342
321
  end
343
- command_str = build_sudoable_command_str(command_str, sudo_str, @sudo_id, options)
322
+ commands.unshift('cd') if conn.reset_cwd?
323
+ commands.unshift(sudo_success(@sudo_id)) if options[:stdin] && !options[:wrapper]
344
324
  end
345
325
 
326
+ command_str = if sudo_str || env_decl
327
+ "sh -c #{Shellwords.shellescape(commands.join('; '))}"
328
+ else
329
+ commands.last
330
+ end
331
+
332
+ command_str = [sudo_str, env_decl, command_str].compact.join(' ')
333
+
346
334
  @logger.debug { "Executing: #{command_str}" }
347
335
 
348
336
  in_buffer = if !use_sudo && options[:stdin]
@@ -71,8 +71,10 @@ module Bolt
71
71
  end
72
72
  end
73
73
 
74
- def set_env(arg, val)
75
- "[Environment]::SetEnvironmentVariable('#{arg}', @'\n#{val}\n'@)"
74
+ def env_declarations(env_vars)
75
+ env_vars.map do |var, val|
76
+ "[Environment]::SetEnvironmentVariable('#{var}', @'\n#{val}\n'@)"
77
+ end
76
78
  end
77
79
 
78
80
  def quote_string(string)
@@ -166,7 +168,9 @@ module Bolt
166
168
  Bolt::Result.for_upload(target, source, destination)
167
169
  end
168
170
 
169
- def run_command(command, _options = {})
171
+ def run_command(command, options = {})
172
+ command = [*env_declarations(options[:env_vars]), command].join("\r\n") if options[:env_vars]
173
+
170
174
  output = execute(command)
171
175
  Bolt::Result.for_command(target,
172
176
  output.stdout.string,
@@ -175,7 +179,7 @@ module Bolt
175
179
  'command', command)
176
180
  end
177
181
 
178
- def run_script(script, arguments, _options = {})
182
+ def run_script(script, arguments, options = {})
179
183
  # unpack any Sensitive data
180
184
  arguments = unwrap_sensitive_args(arguments)
181
185
  with_tmpdir do |dir|
@@ -187,6 +191,8 @@ module Bolt
187
191
  args += escape_arguments(arguments)
188
192
  execute_process(path, args)
189
193
  end
194
+ command = [*env_declarations(options[:env_vars]), command].join("\r\n") if options[:env_vars]
195
+
190
196
  output = execute(command)
191
197
  Bolt::Result.for_command(target,
192
198
  output.stdout.string,
@@ -237,9 +243,7 @@ module Bolt
237
243
  end
238
244
 
239
245
  env_assignments = if Bolt::Task::ENVIRONMENT_METHODS.include?(input_method)
240
- envify_params(arguments).map do |(arg, val)|
241
- set_env(arg, val)
242
- end
246
+ env_declarations(envify_params(arguments))
243
247
  else
244
248
  []
245
249
  end
@@ -39,7 +39,9 @@ module Bolt
39
39
  end
40
40
 
41
41
  def run_command(target, command, options = {})
42
- options[:tty] = target.options['tty']
42
+ execute_options = {}
43
+ execute_options[:tty] = target.options['tty']
44
+ execute_options[:environment] = options[:env_vars]
43
45
 
44
46
  if target.options['shell-command'] && !target.options['shell-command'].empty?
45
47
  # escape any double quotes in command
@@ -47,19 +49,21 @@ module Bolt
47
49
  command = "#{target.options['shell-command']} \" #{command}\""
48
50
  end
49
51
  with_connection(target) do |conn|
50
- stdout, stderr, exitcode = conn.execute(*Shellwords.split(command), options)
52
+ stdout, stderr, exitcode = conn.execute(*Shellwords.split(command), execute_options)
51
53
  Bolt::Result.for_command(target, stdout, stderr, exitcode, 'command', command)
52
54
  end
53
55
  end
54
56
 
55
- def run_script(target, script, arguments, _options = {})
57
+ def run_script(target, script, arguments, options = {})
56
58
  # unpack any Sensitive data
57
59
  arguments = unwrap_sensitive_args(arguments)
60
+ execute_options = {}
61
+ execute_options[:environment] = options[:env_vars]
58
62
 
59
63
  with_connection(target) do |conn|
60
64
  conn.with_remote_tmpdir do |dir|
61
65
  remote_path = conn.write_remote_executable(dir, script)
62
- stdout, stderr, exitcode = conn.execute(remote_path, *arguments, {})
66
+ stdout, stderr, exitcode = conn.execute(remote_path, *arguments, execute_options)
63
67
  Bolt::Result.for_command(target, stdout, stderr, exitcode, 'script', script)
64
68
  end
65
69
  end
@@ -82,6 +82,10 @@ module Bolt
82
82
  end
83
83
 
84
84
  def batch_command(targets, command, options = {}, &callback)
85
+ if options[:env_vars] && !options[:env_vars].empty?
86
+ raise NotImplementedError, "pcp transport does not support setting environment variables"
87
+ end
88
+
85
89
  params = {
86
90
  'command' => command
87
91
  }
@@ -98,6 +102,10 @@ module Bolt
98
102
  end
99
103
 
100
104
  def batch_script(targets, script, arguments, options = {}, &callback)
105
+ if options[:env_vars] && !options[:env_vars].empty?
106
+ raise NotImplementedError, "pcp transport does not support setting environment variables"
107
+ end
108
+
101
109
  content = File.open(script, &:read)
102
110
  content = Base64.encode64(content)
103
111
  params = {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.16.0'
4
+ VERSION = '2.17.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.16.0
4
+ version: 2.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-29 00:00:00.000000000 Z
11
+ date: 2020-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -178,20 +178,6 @@ dependencies:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0.4'
181
- - !ruby/object:Gem::Dependency
182
- name: paint
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - "~>"
186
- - !ruby/object:Gem::Version
187
- version: '2.2'
188
- type: :runtime
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '2.2'
195
181
  - !ruby/object:Gem::Dependency
196
182
  name: puppet
197
183
  requirement: !ruby/object:Gem::Requirement
@@ -213,47 +199,47 @@ dependencies:
213
199
  - !ruby/object:Gem::Version
214
200
  version: '7'
215
201
  - !ruby/object:Gem::Dependency
216
- name: puppet-resource_api
202
+ name: puppetfile-resolver
217
203
  requirement: !ruby/object:Gem::Requirement
218
204
  requirements:
219
- - - ">="
205
+ - - "~>"
220
206
  - !ruby/object:Gem::Version
221
- version: 1.8.1
207
+ version: 0.1.0
222
208
  type: :runtime
223
209
  prerelease: false
224
210
  version_requirements: !ruby/object:Gem::Requirement
225
211
  requirements:
226
- - - ">="
212
+ - - "~>"
227
213
  - !ruby/object:Gem::Version
228
- version: 1.8.1
214
+ version: 0.1.0
229
215
  - !ruby/object:Gem::Dependency
230
- name: puppet-strings
216
+ name: puppet-resource_api
231
217
  requirement: !ruby/object:Gem::Requirement
232
218
  requirements:
233
- - - "~>"
219
+ - - ">="
234
220
  - !ruby/object:Gem::Version
235
- version: '2.3'
221
+ version: 1.8.1
236
222
  type: :runtime
237
223
  prerelease: false
238
224
  version_requirements: !ruby/object:Gem::Requirement
239
225
  requirements:
240
- - - "~>"
226
+ - - ">="
241
227
  - !ruby/object:Gem::Version
242
- version: '2.3'
228
+ version: 1.8.1
243
229
  - !ruby/object:Gem::Dependency
244
- name: puppetfile-resolver
230
+ name: puppet-strings
245
231
  requirement: !ruby/object:Gem::Requirement
246
232
  requirements:
247
233
  - - "~>"
248
234
  - !ruby/object:Gem::Version
249
- version: 0.1.0
235
+ version: '2.3'
250
236
  type: :runtime
251
237
  prerelease: false
252
238
  version_requirements: !ruby/object:Gem::Requirement
253
239
  requirements:
254
240
  - - "~>"
255
241
  - !ruby/object:Gem::Version
256
- version: 0.1.0
242
+ version: '2.3'
257
243
  - !ruby/object:Gem::Dependency
258
244
  name: r10k
259
245
  requirement: !ruby/object:Gem::Requirement