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.
- checksums.yaml +4 -4
- data/Puppetfile +3 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +6 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +12 -6
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
- 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 +75 -25
- data/lib/bolt/catalog.rb +9 -1
- data/lib/bolt/cli.rb +226 -73
- data/lib/bolt/config.rb +7 -0
- data/lib/bolt/config/options.rb +4 -4
- data/lib/bolt/executor.rb +16 -8
- 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 +4 -0
- data/lib/bolt/pal/yaml_plan/step.rb +14 -1
- data/lib/bolt/pal/yaml_plan/step/message.rb +30 -0
- 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 +17 -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/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
- data/modules/secure_env_vars/plans/init.pp +20 -0
- metadata +8 -2
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)
|
@@ -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
|
-
|
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
|
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
|
-
|
390
|
+
$stderr.print("#{prompt}: ")
|
383
391
|
|
384
392
|
value = if options[:sensitive]
|
385
|
-
|
393
|
+
$stdin.noecho(&:gets).to_s.chomp
|
386
394
|
else
|
387
|
-
|
395
|
+
$stdin.gets.to_s.chomp
|
388
396
|
end
|
389
397
|
|
390
|
-
|
398
|
+
$stderr.puts if options[:sensitive]
|
391
399
|
|
392
400
|
value
|
393
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
|
{
|
@@ -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[
|
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
|