bolt 2.20.0 → 2.24.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +3 -1
  3. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +6 -0
  4. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +12 -6
  5. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
  6. data/bolt-modules/out/lib/puppet/functions/out/message.rb +1 -1
  7. data/exe/bolt +1 -0
  8. data/guides/inventory.txt +19 -0
  9. data/guides/project.txt +22 -0
  10. data/lib/bolt/analytics.rb +5 -5
  11. data/lib/bolt/applicator.rb +4 -3
  12. data/lib/bolt/bolt_option_parser.rb +75 -25
  13. data/lib/bolt/catalog.rb +9 -1
  14. data/lib/bolt/cli.rb +226 -73
  15. data/lib/bolt/config.rb +7 -0
  16. data/lib/bolt/config/options.rb +4 -4
  17. data/lib/bolt/executor.rb +16 -8
  18. data/lib/bolt/inventory/group.rb +3 -3
  19. data/lib/bolt/logger.rb +3 -4
  20. data/lib/bolt/module.rb +2 -1
  21. data/lib/bolt/outputter.rb +56 -0
  22. data/lib/bolt/outputter/human.rb +10 -9
  23. data/lib/bolt/outputter/json.rb +11 -4
  24. data/lib/bolt/outputter/logger.rb +2 -2
  25. data/lib/bolt/outputter/rainbow.rb +15 -0
  26. data/lib/bolt/pal.rb +5 -9
  27. data/lib/bolt/pal/yaml_plan/evaluator.rb +4 -0
  28. data/lib/bolt/pal/yaml_plan/step.rb +14 -1
  29. data/lib/bolt/pal/yaml_plan/step/message.rb +30 -0
  30. data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
  31. data/lib/bolt/plugin/prompt.rb +3 -3
  32. data/lib/bolt/project.rb +6 -4
  33. data/lib/bolt/project_migrate.rb +138 -0
  34. data/lib/bolt/shell/bash.rb +7 -7
  35. data/lib/bolt/transport/docker/connection.rb +9 -9
  36. data/lib/bolt/transport/local/connection.rb +2 -2
  37. data/lib/bolt/transport/orch.rb +3 -3
  38. data/lib/bolt/transport/ssh/connection.rb +5 -5
  39. data/lib/bolt/transport/ssh/exec_connection.rb +4 -4
  40. data/lib/bolt/transport/winrm/connection.rb +17 -8
  41. data/lib/bolt/util.rb +1 -1
  42. data/lib/bolt/util/puppet_log_level.rb +4 -3
  43. data/lib/bolt/version.rb +1 -1
  44. data/lib/bolt_server/base_config.rb +1 -1
  45. data/lib/bolt_server/pe/pal.rb +1 -1
  46. data/lib/bolt_server/transport_app.rb +76 -0
  47. data/lib/bolt_spec/plans.rb +1 -1
  48. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  49. data/libexec/apply_catalog.rb +2 -2
  50. data/libexec/bolt_catalog +1 -1
  51. data/libexec/custom_facts.rb +1 -1
  52. data/libexec/query_resources.rb +1 -1
  53. data/modules/secure_env_vars/plans/init.pp +20 -0
  54. metadata +8 -2
@@ -186,8 +186,8 @@ module Bolt
186
186
  "level" => {
187
187
  description: "The type of information to log.",
188
188
  type: String,
189
- enum: %w[debug error info notice warn fatal any],
190
- _default: "warn for console, notice for file"
189
+ enum: %w[trace debug error info warn fatal any],
190
+ _default: "warn"
191
191
  }
192
192
  }
193
193
  }
@@ -204,8 +204,8 @@ module Bolt
204
204
  "level" => {
205
205
  description: "The type of information to log.",
206
206
  type: String,
207
- enum: %w[debug error info notice warn fatal any],
208
- _default: "warn for console, notice for file"
207
+ enum: %w[trace debug error info warn fatal any],
208
+ _default: "warn"
209
209
  }
210
210
  }
211
211
  },
@@ -56,11 +56,12 @@ module Bolt
56
56
  @reported_transports = Set.new
57
57
  @subscribers = {}
58
58
  @publisher = Concurrent::SingleThreadExecutor.new
59
+ @publisher.post { Thread.current[:name] = 'event-publisher' }
59
60
 
60
61
  @noop = noop
61
62
  @run_as = nil
62
63
  @pool = if concurrency > 0
63
- Concurrent::ThreadPoolExecutor.new(max_threads: concurrency)
64
+ Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
64
65
  else
65
66
  Concurrent.global_immediate_executor
66
67
  end
@@ -125,6 +126,7 @@ module Bolt
125
126
  # Pass this argument through to avoid retaining a reference to a
126
127
  # local variable that will change on the next iteration of the loop.
127
128
  @pool.post(batch_promises) do |result_promises|
129
+ Thread.current[:name] ||= Thread.current.name
128
130
  results = yield transport, batch
129
131
  Array(results).each do |result|
130
132
  result_promises[result.target].set(result)
@@ -241,7 +243,7 @@ module Bolt
241
243
 
242
244
  @analytics&.event('Plan', 'yaml', plan_steps: steps, return_type: return_type)
243
245
  rescue StandardError => e
244
- @logger.debug { "Failed to submit analytics event: #{e.message}" }
246
+ @logger.trace { "Failed to submit analytics event: #{e.message}" }
245
247
  end
246
248
 
247
249
  def with_node_logging(description, batch)
@@ -322,7 +324,13 @@ module Bolt
322
324
 
323
325
  def download_file(targets, source, destination, options = {})
324
326
  description = options.fetch(:description, "file download from #{source} to #{destination}")
325
- FileUtils.mkdir_p(destination)
327
+
328
+ begin
329
+ FileUtils.mkdir_p(destination)
330
+ rescue Errno::EEXIST => e
331
+ message = "#{e.message}; unable to create destination directory #{destination}"
332
+ raise Bolt::Error.new(message, 'bolt/file-exist-error')
333
+ end
326
334
 
327
335
  log_action(description, targets) do
328
336
  options[:run_as] = run_as if run_as && !options.key?(:run_as)
@@ -375,19 +383,19 @@ module Bolt
375
383
  end
376
384
 
377
385
  def prompt(prompt, options)
378
- unless STDIN.tty?
386
+ unless $stdin.tty?
379
387
  raise Bolt::Error.new('STDIN is not a tty, unable to prompt', 'bolt/no-tty-error')
380
388
  end
381
389
 
382
- STDERR.print("#{prompt}: ")
390
+ $stderr.print("#{prompt}: ")
383
391
 
384
392
  value = if options[:sensitive]
385
- STDIN.noecho(&:gets).to_s.chomp
393
+ $stdin.noecho(&:gets).to_s.chomp
386
394
  else
387
- STDIN.gets.to_s.chomp
395
+ $stdin.gets.to_s.chomp
388
396
  end
389
397
 
390
- STDERR.puts if options[:sensitive]
398
+ $stderr.puts if options[:sensitive]
391
399
 
392
400
  value
393
401
  end
@@ -119,7 +119,7 @@ module Bolt
119
119
  end
120
120
 
121
121
  if contains_target?(t_name)
122
- @logger.warn("Ignoring duplicate target in #{@name}: #{target}")
122
+ @logger.debug("Ignoring duplicate target in #{@name}: #{target}")
123
123
  return
124
124
  end
125
125
 
@@ -200,14 +200,14 @@ module Bolt
200
200
  # If this is an alias for an existing target, then add it to this group
201
201
  elsif (canonical_name = aliases[string_target])
202
202
  if contains_target?(canonical_name)
203
- @logger.warn("Ignoring duplicate target in #{@name}: #{canonical_name}")
203
+ @logger.debug("Ignoring duplicate target in #{@name}: #{canonical_name}")
204
204
  else
205
205
  @unresolved_targets[canonical_name] = { 'name' => canonical_name }
206
206
  end
207
207
  # If it's not the name or alias of an existing target, then make a
208
208
  # new target using the string as the URI
209
209
  elsif contains_target?(string_target)
210
- @logger.warn("Ignoring duplicate target in #{@name}: #{string_target}")
210
+ @logger.debug("Ignoring duplicate target in #{@name}: #{string_target}")
211
211
  else
212
212
  @unresolved_targets[string_target] = { 'uri' => string_target }
213
213
  end
@@ -14,13 +14,12 @@ module Bolt
14
14
  # redefs, so skip it if it's already been initialized
15
15
  return if Logging.initialized?
16
16
 
17
- Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
17
+ Logging.init :trace, :debug, :info, :notice, :warn, :error, :fatal, :any
18
18
  @mutex = Mutex.new
19
19
 
20
20
  Logging.color_scheme(
21
21
  'bolt',
22
22
  lines: {
23
- notice: :green,
24
23
  warn: :yellow,
25
24
  error: :red,
26
25
  fatal: %i[white on_red]
@@ -81,7 +80,7 @@ module Bolt
81
80
 
82
81
  def self.default_layout
83
82
  Logging.layouts.pattern(
84
- pattern: '%d %-6l %c: %m\n',
83
+ pattern: '%d %-6l [%T] [%c] %m\n',
85
84
  date_pattern: '%Y-%m-%dT%H:%M:%S.%6N'
86
85
  )
87
86
  end
@@ -91,7 +90,7 @@ module Bolt
91
90
  end
92
91
 
93
92
  def self.default_file_level
94
- :notice
93
+ :warn
95
94
  end
96
95
 
97
96
  # Explicitly check the log level names instead of the log level number, as levels
@@ -3,7 +3,8 @@
3
3
  # Is this Bolt::Pobs::Module?
4
4
  module Bolt
5
5
  class Module
6
- MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
6
+ CONTENT_NAME_REGEX = /\A[a-z][a-z0-9_]*(::[a-z][a-z0-9_]*)*\z/.freeze
7
+ MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
7
8
 
8
9
  def self.discover(modulepath)
9
10
  modulepath.each_with_object({}) do |path, mods|
@@ -21,6 +21,62 @@ module Bolt
21
21
  @trace = trace
22
22
  @stream = stream
23
23
  end
24
+
25
+ def indent(indent, string)
26
+ indent = ' ' * indent
27
+ string.gsub(/^/, indent.to_s)
28
+ end
29
+
30
+ def print_message_event(event)
31
+ print_message(stringify(event[:message]))
32
+ end
33
+
34
+ def print_message
35
+ raise NotImplementedError, "print_message() must be implemented by the outputter class"
36
+ end
37
+
38
+ def stringify(message)
39
+ formatted = format_message(message)
40
+ if formatted.is_a?(Hash) || formatted.is_a?(Array)
41
+ ::JSON.pretty_generate(formatted)
42
+ else
43
+ formatted
44
+ end
45
+ end
46
+
47
+ def format_message(message)
48
+ case message
49
+ when Array
50
+ message.map { |item| format_message(item) }
51
+ when Bolt::ApplyResult
52
+ format_apply_result(message)
53
+ when Bolt::Result, Bolt::ResultSet
54
+ # This is equivalent to to_s, but formattable
55
+ message.to_data
56
+ when Bolt::RunFailure
57
+ formatted_resultset = message.result_set.to_data
58
+ message.to_h.merge('result_set' => formatted_resultset)
59
+ when Hash
60
+ message.each_with_object({}) do |(k, v), h|
61
+ h[format_message(k)] = format_message(v)
62
+ end
63
+ when Integer, Float, NilClass
64
+ message
65
+ else
66
+ message.to_s
67
+ end
68
+ end
69
+
70
+ def format_apply_result(result)
71
+ logs = result.resource_logs&.map do |log|
72
+ # Omit low-level info/debug messages
73
+ next if %w[info debug].include?(log['level'])
74
+ indent(2, format_log(log))
75
+ end
76
+ hash = result.to_data
77
+ hash['logs'] = logs unless logs.empty?
78
+ hash
79
+ end
24
80
  end
25
81
  end
26
82
 
@@ -27,11 +27,6 @@ module Bolt
27
27
  end
28
28
  end
29
29
 
30
- def indent(indent, string)
31
- indent = ' ' * indent
32
- string.gsub(/^/, indent.to_s)
33
- end
34
-
35
30
  def remove_trail(string)
36
31
  string.sub(/\s\z/, '')
37
32
  end
@@ -291,6 +286,16 @@ module Bolt
291
286
  "details and parameters for a specific plan.")
292
287
  end
293
288
 
289
+ def print_topics(topics)
290
+ print_message("Available topics are:")
291
+ print_message(topics.join("\n"))
292
+ print_message("\nUse `bolt guide <topic>` to view a specific guide.")
293
+ end
294
+
295
+ def print_guide(guide, _topic)
296
+ @stream.puts(guide)
297
+ end
298
+
294
299
  def print_module_list(module_list)
295
300
  module_list.each do |path, modules|
296
301
  if (mod = modules.find { |m| m[:internal_module_group] })
@@ -372,10 +377,6 @@ module Bolt
372
377
  end
373
378
  end
374
379
 
375
- def print_message_event(event)
376
- print_message(event[:message])
377
- end
378
-
379
380
  def fatal_error(err)
380
381
  @stream.puts(colorize(:red, err.message))
381
382
  if err.is_a? Bolt::RunFailure
@@ -83,6 +83,17 @@ module Bolt
83
83
  @stream.puts result.to_json
84
84
  end
85
85
 
86
+ def print_topics(topics)
87
+ print_table('topics' => topics)
88
+ end
89
+
90
+ def print_guide(guide, topic)
91
+ @stream.puts({
92
+ 'topic' => topic,
93
+ 'guide' => guide
94
+ }.to_json)
95
+ end
96
+
86
97
  def print_puppetfile_result(success, puppetfile, moduledir)
87
98
  @stream.puts({ "success": success,
88
99
  "puppetfile": puppetfile,
@@ -121,10 +132,6 @@ module Bolt
121
132
  @stream.puts '}' if @object_open
122
133
  end
123
134
 
124
- def print_message_event(event)
125
- print_message(event[:message])
126
- end
127
-
128
135
  def print_message(message)
129
136
  $stderr.puts(message)
130
137
  end
@@ -40,13 +40,13 @@ module Bolt
40
40
 
41
41
  def log_plan_start(event)
42
42
  plan = event[:plan]
43
- @logger.notice("Starting: plan #{plan}")
43
+ @logger.info("Starting: plan #{plan}")
44
44
  end
45
45
 
46
46
  def log_plan_finish(event)
47
47
  plan = event[:plan]
48
48
  duration = event[:duration]
49
- @logger.notice("Finished: plan #{plan} in #{duration.round(2)} sec")
49
+ @logger.info("Finished: plan #{plan} in #{duration.round(2)} sec")
50
50
  end
51
51
  end
52
52
  end
@@ -86,6 +86,21 @@ module Bolt
86
86
  total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
87
87
  @stream.puts colorize(:rainbow, total_msg)
88
88
  end
89
+
90
+ def print_guide(guide, _topic)
91
+ @stream.puts colorize(:rainbow, guide)
92
+ end
93
+
94
+ def print_topics(topics)
95
+ content = String.new("Available topics are:\n")
96
+ content += topics.join("\n")
97
+ content += "\n\nUse `bolt guide <topic>` to view a specific guide."
98
+ @stream.puts colorize(:rainbow, content)
99
+ end
100
+
101
+ def print_message(message)
102
+ @stream.puts colorize(:rainbow, message)
103
+ end
89
104
  end
90
105
  end
91
106
  end
@@ -48,7 +48,7 @@ module Bolt
48
48
  end
49
49
  end
50
50
 
51
- attr_reader :modulepath
51
+ attr_reader :modulepath, :user_modulepath
52
52
 
53
53
  def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors,
54
54
  trusted_external = nil, apply_settings = {}, project = nil)
@@ -56,7 +56,7 @@ module Bolt
56
56
  # is safe and in practice only happens in tests
57
57
  self.class.load_puppet
58
58
 
59
- @original_modulepath = modulepath
59
+ @user_modulepath = modulepath
60
60
  @modulepath = [BOLTLIB_PATH, *modulepath, MODULES_PATH]
61
61
  @hiera_config = hiera_config
62
62
  @trusted_external = trusted_external
@@ -67,7 +67,7 @@ module Bolt
67
67
 
68
68
  @logger = Logging.logger[self]
69
69
  if modulepath && !modulepath.empty?
70
- @logger.info("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
70
+ @logger.debug("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
71
71
  end
72
72
 
73
73
  @loaded = false
@@ -208,7 +208,7 @@ module Bolt
208
208
  # Skip syncing built-in plugins, since we vendor some Puppet 6
209
209
  # versions of "core" types, which are already present on the agent,
210
210
  # but may cause issues on Puppet 5 agents.
211
- @original_modulepath,
211
+ @user_modulepath,
212
212
  @project,
213
213
  pdb_client,
214
214
  @hiera_config,
@@ -278,10 +278,6 @@ module Bolt
278
278
  end
279
279
  end
280
280
 
281
- def list_modulepath
282
- @modulepath - [BOLTLIB_PATH, MODULES_PATH]
283
- end
284
-
285
281
  def parse_params(type, object_name, params)
286
282
  in_bolt_compiler do |compiler|
287
283
  case type
@@ -413,7 +409,7 @@ module Bolt
413
409
  end
414
410
  params[name] = { 'type' => type_str }
415
411
  params[name]['sensitive'] = param.type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
416
- params[name]['default_value'] = param.value
412
+ params[name]['default_value'] = param.value unless param.value.nil?
417
413
  params[name]['description'] = param.description if param.description
418
414
  end
419
415
  {
@@ -108,6 +108,10 @@ module Bolt
108
108
  apply_manifest(scope, targets, manifest)
109
109
  end
110
110
 
111
+ def message_step(scope, step)
112
+ scope.call_function('out::message', [step['message']])
113
+ end
114
+
111
115
  def generate_manifest(resources)
112
116
  # inspect returns the Ruby representation of the resource hashes,
113
117
  # which happens to be the same as the Puppet representation
@@ -12,7 +12,19 @@ module Bolt
12
12
  Set['name', 'description', 'target', 'targets']
13
13
  end
14
14
 
15
- STEP_KEYS = %w[command script task plan source destination eval resources upload download].freeze
15
+ STEP_KEYS = %w[
16
+ command
17
+ destination
18
+ download
19
+ eval
20
+ message
21
+ plan
22
+ resources
23
+ script
24
+ source
25
+ task
26
+ upload
27
+ ].freeze
16
28
 
17
29
  def self.create(step_body, step_number)
18
30
  type_keys = (STEP_KEYS & step_body.keys)
@@ -165,3 +177,4 @@ require 'bolt/pal/yaml_plan/step/script'
165
177
  require 'bolt/pal/yaml_plan/step/task'
166
178
  require 'bolt/pal/yaml_plan/step/upload'
167
179
  require 'bolt/pal/yaml_plan/step/download'
180
+ require 'bolt/pal/yaml_plan/step/message'
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ class PAL
5
+ class YamlPlan
6
+ class Step
7
+ class Message < Step
8
+ def self.allowed_keys
9
+ super + Set['message']
10
+ end
11
+
12
+ def self.required_keys
13
+ Set['message']
14
+ end
15
+
16
+ def initialize(step_body)
17
+ super
18
+ @message = step_body['message']
19
+ end
20
+
21
+ def transpile
22
+ code = String.new(" ")
23
+ code << function_call('out::message', [@message])
24
+ code << "\n"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end