bolt 2.21.0 → 2.25.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.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +12 -6
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +1 -1
- data/exe/bolt +1 -0
- data/guides/inventory.txt +19 -0
- data/guides/project.txt +22 -0
- data/lib/bolt/analytics.rb +5 -5
- data/lib/bolt/applicator.rb +4 -3
- data/lib/bolt/bolt_option_parser.rb +64 -23
- data/lib/bolt/catalog.rb +9 -1
- data/lib/bolt/cli.rb +218 -73
- data/lib/bolt/config.rb +7 -0
- data/lib/bolt/config/options.rb +4 -4
- data/lib/bolt/executor.rb +9 -7
- data/lib/bolt/inventory/group.rb +3 -3
- data/lib/bolt/logger.rb +3 -4
- data/lib/bolt/module.rb +2 -1
- data/lib/bolt/outputter.rb +56 -0
- data/lib/bolt/outputter/human.rb +10 -9
- data/lib/bolt/outputter/json.rb +11 -4
- data/lib/bolt/outputter/logger.rb +2 -2
- data/lib/bolt/outputter/rainbow.rb +15 -0
- data/lib/bolt/pal.rb +5 -9
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
- data/lib/bolt/plugin/prompt.rb +3 -3
- data/lib/bolt/project.rb +6 -4
- data/lib/bolt/project_migrate.rb +138 -0
- data/lib/bolt/shell/bash.rb +7 -7
- data/lib/bolt/transport/docker/connection.rb +9 -9
- data/lib/bolt/transport/local/connection.rb +2 -2
- data/lib/bolt/transport/orch.rb +3 -3
- data/lib/bolt/transport/ssh/connection.rb +5 -5
- data/lib/bolt/transport/ssh/exec_connection.rb +4 -4
- data/lib/bolt/transport/winrm/connection.rb +8 -8
- data/lib/bolt/util.rb +1 -1
- data/lib/bolt/util/puppet_log_level.rb +4 -3
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +1 -1
- data/lib/bolt_server/pe/pal.rb +1 -1
- data/lib/bolt_server/transport_app.rb +76 -0
- data/lib/bolt_spec/plans.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs.rb +1 -1
- data/lib/bolt_spec/run.rb +3 -0
- data/libexec/apply_catalog.rb +2 -2
- data/libexec/bolt_catalog +1 -1
- data/libexec/custom_facts.rb +1 -1
- data/libexec/query_resources.rb +1 -1
- metadata +7 -4
data/lib/bolt/config.rb
CHANGED
@@ -213,6 +213,13 @@ module Bolt
|
|
213
213
|
'transport' => 'ssh'
|
214
214
|
}
|
215
215
|
|
216
|
+
if project.path.directory?
|
217
|
+
default_data['log']['bolt-debug.log'] = {
|
218
|
+
'level' => 'debug',
|
219
|
+
'append' => false
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
216
223
|
loaded_data = config_data.each_with_object([]) do |data, acc|
|
217
224
|
@warnings.concat(data[:warnings]) if data[:warnings].any?
|
218
225
|
@deprecations.concat(data[:deprecations]) if data[:deprecations].any?
|
data/lib/bolt/config/options.rb
CHANGED
@@ -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
|
190
|
-
_default: "warn
|
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
|
208
|
-
_default: "warn
|
207
|
+
enum: %w[trace debug error info warn fatal any],
|
208
|
+
_default: "warn"
|
209
209
|
}
|
210
210
|
}
|
211
211
|
},
|
data/lib/bolt/executor.rb
CHANGED
@@ -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.
|
246
|
+
@logger.trace { "Failed to submit analytics event: #{e.message}" }
|
245
247
|
end
|
246
248
|
|
247
249
|
def with_node_logging(description, batch)
|
@@ -381,19 +383,19 @@ module Bolt
|
|
381
383
|
end
|
382
384
|
|
383
385
|
def prompt(prompt, options)
|
384
|
-
unless
|
386
|
+
unless $stdin.tty?
|
385
387
|
raise Bolt::Error.new('STDIN is not a tty, unable to prompt', 'bolt/no-tty-error')
|
386
388
|
end
|
387
389
|
|
388
|
-
|
390
|
+
$stderr.print("#{prompt}: ")
|
389
391
|
|
390
392
|
value = if options[:sensitive]
|
391
|
-
|
393
|
+
$stdin.noecho(&:gets).to_s.chomp
|
392
394
|
else
|
393
|
-
|
395
|
+
$stdin.gets.to_s.chomp
|
394
396
|
end
|
395
397
|
|
396
|
-
|
398
|
+
$stderr.puts if options[:sensitive]
|
397
399
|
|
398
400
|
value
|
399
401
|
end
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -119,7 +119,7 @@ module Bolt
|
|
119
119
|
end
|
120
120
|
|
121
121
|
if contains_target?(t_name)
|
122
|
-
@logger.
|
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.
|
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.
|
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
|
data/lib/bolt/logger.rb
CHANGED
@@ -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
|
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
|
-
:
|
93
|
+
:warn
|
95
94
|
end
|
96
95
|
|
97
96
|
# Explicitly check the log level names instead of the log level number, as levels
|
data/lib/bolt/module.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
# Is this Bolt::Pobs::Module?
|
4
4
|
module Bolt
|
5
5
|
class Module
|
6
|
-
|
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|
|
data/lib/bolt/outputter.rb
CHANGED
@@ -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
|
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -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
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -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.
|
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.
|
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
|
data/lib/bolt/pal.rb
CHANGED
@@ -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
|
-
@
|
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.
|
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
|
-
@
|
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
|
{
|
@@ -21,10 +21,18 @@ module Bolt
|
|
21
21
|
validate_path
|
22
22
|
|
23
23
|
plan_object = parse_plan
|
24
|
+
param_descriptions = plan_object.parameters.map do |param|
|
25
|
+
str = String.new("# @param #{param.name}")
|
26
|
+
str << " #{param.description}" if param.description
|
27
|
+
str
|
28
|
+
end.join("\n")
|
24
29
|
|
25
|
-
plan_string = String.new(
|
26
|
-
|
27
|
-
|
30
|
+
plan_string = String.new('')
|
31
|
+
plan_string << "# #{plan_object.description}\n" if plan_object.description
|
32
|
+
plan_string << "# WARNING: This is an autogenerated plan. It may not behave as expected.\n"
|
33
|
+
plan_string << "#{param_descriptions}\n" unless param_descriptions.empty?
|
34
|
+
|
35
|
+
plan_string << "plan #{plan_object.name}("
|
28
36
|
# Parameters are Bolt::PAL::YamlPlan::Parameter
|
29
37
|
plan_object.parameters&.each_with_index do |param, i|
|
30
38
|
plan_string << param.transpile
|
data/lib/bolt/plugin/prompt.rb
CHANGED
@@ -18,9 +18,9 @@ module Bolt
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def resolve_reference(opts)
|
21
|
-
|
22
|
-
value =
|
23
|
-
|
21
|
+
$stderr.print("#{opts['message']}: ")
|
22
|
+
value = $stdin.noecho(&:gets).to_s.chomp
|
23
|
+
$stderr.puts
|
24
24
|
|
25
25
|
value
|
26
26
|
end
|
data/lib/bolt/project.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'bolt/config'
|
5
5
|
require 'bolt/pal'
|
6
|
+
require 'bolt/module'
|
6
7
|
|
7
8
|
module Bolt
|
8
9
|
class Project
|
@@ -17,7 +18,7 @@ module Bolt
|
|
17
18
|
|
18
19
|
attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
|
19
20
|
:puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file,
|
20
|
-
:deprecations, :downloads
|
21
|
+
:deprecations, :downloads, :plans_path
|
21
22
|
|
22
23
|
def self.default_project
|
23
24
|
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
|
@@ -82,6 +83,7 @@ module Bolt
|
|
82
83
|
@resource_types = @path + '.resource_types'
|
83
84
|
@type = type
|
84
85
|
@downloads = @path + 'downloads'
|
86
|
+
@plans_path = @path + 'plans'
|
85
87
|
|
86
88
|
tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
|
87
89
|
if tc.any?
|
@@ -138,10 +140,10 @@ module Bolt
|
|
138
140
|
|
139
141
|
def validate
|
140
142
|
if name
|
141
|
-
|
142
|
-
if name !~ name_regex
|
143
|
+
if name !~ Bolt::Module::MODULE_NAME_REGEX
|
143
144
|
raise Bolt::ValidationError, <<~ERROR_STRING
|
144
|
-
Invalid project name '#{name}' in bolt-project.yaml; project name must
|
145
|
+
Invalid project name '#{name}' in bolt-project.yaml; project name must begin with a lowercase letter
|
146
|
+
and can include lowercase letters, numbers, and underscores.
|
145
147
|
ERROR_STRING
|
146
148
|
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
147
149
|
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|