bolt 0.21.3 → 0.21.4
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/exe/bolt-inventory-pdb +8 -2
- data/lib/bolt/applicator.rb +89 -11
- data/lib/bolt/bolt_option_parser.rb +18 -3
- data/lib/bolt/boltdir.rb +42 -0
- data/lib/bolt/catalog.rb +7 -52
- data/lib/bolt/catalog/compiler.rb +48 -0
- data/lib/bolt/catalog/logging.rb +15 -0
- data/lib/bolt/cli.rb +146 -85
- data/lib/bolt/config.rb +121 -156
- data/lib/bolt/error.rb +25 -2
- data/lib/bolt/executor.rb +3 -4
- data/lib/bolt/inventory.rb +3 -3
- data/lib/bolt/logger.rb +3 -3
- data/lib/bolt/outputter/human.rb +10 -0
- data/lib/bolt/outputter/json.rb +6 -0
- data/lib/bolt/pal.rb +10 -11
- data/lib/bolt/pal/logging.rb +3 -14
- data/lib/bolt/puppetdb/client.rb +7 -27
- data/lib/bolt/puppetdb/config.rb +50 -23
- data/lib/bolt/r10k_log_proxy.rb +30 -0
- data/lib/bolt/util.rb +2 -2
- data/lib/bolt/util/puppet_log_level.rb +20 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_ext/puppetdb_inventory.rb +3 -9
- data/lib/bolt_spec/plans.rb +174 -0
- data/lib/bolt_spec/plans/mock_executor.rb +217 -0
- metadata +23 -3
- data/lib/bolt/util/on_access.rb +0 -26
data/lib/bolt/config.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'logging'
|
5
5
|
require 'concurrent'
|
6
|
+
require 'pathname'
|
7
|
+
require 'bolt/boltdir'
|
6
8
|
require 'bolt/cli'
|
7
9
|
require 'bolt/transport/ssh'
|
8
10
|
require 'bolt/transport/winrm'
|
@@ -24,30 +26,10 @@ module Bolt
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
|
-
Config
|
28
|
-
:concurrency,
|
29
|
-
|
30
|
-
:
|
31
|
-
:trace,
|
32
|
-
:inventoryfile,
|
33
|
-
:log,
|
34
|
-
:modulepath,
|
35
|
-
:puppetdb,
|
36
|
-
:'hiera-config',
|
37
|
-
:color,
|
38
|
-
:transport,
|
39
|
-
:transports
|
40
|
-
) do
|
41
|
-
|
42
|
-
DEFAULTS = {
|
43
|
-
concurrency: 100,
|
44
|
-
'compile-concurrency': Concurrent.processor_count,
|
45
|
-
transport: 'ssh',
|
46
|
-
format: 'human',
|
47
|
-
modulepath: [],
|
48
|
-
puppetdb: {},
|
49
|
-
color: true
|
50
|
-
}.freeze
|
29
|
+
class Config
|
30
|
+
attr_accessor :concurrency, :format, :trace, :log, :puppetdb, :color,
|
31
|
+
:transport, :transports, :inventoryfile, :compile_concurrency
|
32
|
+
attr_writer :modulepath
|
51
33
|
|
52
34
|
TRANSPORT_OPTIONS = %i[password run-as sudo-password extensions
|
53
35
|
private-key tty tmpdir user connect-timeout
|
@@ -73,35 +55,55 @@ module Bolt
|
|
73
55
|
local: {}
|
74
56
|
}.freeze
|
75
57
|
|
76
|
-
|
58
|
+
def self.default
|
59
|
+
new(Bolt::Boltdir.new('.'), {})
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.from_boltdir(boltdir, overrides = {})
|
63
|
+
# *Optionally* load the boltdir config file, and fall back to the legacy
|
64
|
+
# config if that isn't found. Because logging is built in to the
|
65
|
+
# legacy_conf method, we don't want to look that up unless we need it.
|
66
|
+
configs = if boltdir.config_file.exist?
|
67
|
+
[boltdir.config_file]
|
68
|
+
else
|
69
|
+
[legacy_conf]
|
70
|
+
end
|
71
|
+
|
72
|
+
data = Bolt::Util.read_config_file(nil, configs, 'config') || {}
|
77
73
|
|
78
|
-
|
79
|
-
|
74
|
+
new(boltdir, data, overrides)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.from_file(configfile, overrides = {})
|
78
|
+
boltdir = Bolt::Boltdir.new(Pathname.new(configfile).expand_path.dirname)
|
79
|
+
data = Bolt::Util.read_config_file(configfile, [], 'config') || {}
|
80
|
+
|
81
|
+
new(boltdir, data, overrides)
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize(boltdir, config_data, overrides = {})
|
80
85
|
@logger = Logging.logger[self]
|
81
|
-
@pwd = kwargs.delete(:pwd)
|
82
86
|
|
83
|
-
|
87
|
+
@boltdir = boltdir
|
88
|
+
@concurrency = 100
|
89
|
+
@compile_concurrency = Concurrent.processor_count
|
90
|
+
@transport = 'ssh'
|
91
|
+
@format = 'human'
|
92
|
+
@puppetdb = {}
|
93
|
+
@color = true
|
84
94
|
|
85
95
|
# add an entry for the default console logger
|
86
|
-
|
87
|
-
self[:log]['console'] ||= {}
|
96
|
+
@log = { 'console' => {} }
|
88
97
|
|
89
|
-
|
98
|
+
@transports = {}
|
90
99
|
TRANSPORTS.each_key do |transport|
|
91
|
-
|
100
|
+
@transports[transport] = TRANSPORT_DEFAULTS.merge(TRANSPORT_SPECIFIC_DEFAULTS[transport])
|
101
|
+
end
|
92
102
|
|
93
|
-
|
94
|
-
|
95
|
-
self[:transports][transport][k] = v
|
96
|
-
end
|
97
|
-
end
|
103
|
+
update_from_file(config_data)
|
104
|
+
apply_overrides(overrides)
|
98
105
|
|
99
|
-
|
100
|
-
unless self[:transports][transport].key? k
|
101
|
-
self[:transports][transport][k] = v
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
106
|
+
validate
|
105
107
|
end
|
106
108
|
|
107
109
|
def deep_clone
|
@@ -114,110 +116,79 @@ module Bolt
|
|
114
116
|
'file:' + File.expand_path(target)
|
115
117
|
end
|
116
118
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
119
|
+
def update_logs(logs)
|
120
|
+
logs.each_pair do |k, v|
|
121
|
+
log_name = normalize_log(k)
|
122
|
+
@log[log_name] ||= {}
|
123
|
+
log = @log[log_name]
|
121
124
|
|
122
|
-
|
125
|
+
next unless v.is_a?(Hash)
|
123
126
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
+
if v.key?('level')
|
128
|
+
log[:level] = v['level'].to_s
|
129
|
+
end
|
127
130
|
|
128
|
-
|
129
|
-
|
130
|
-
end
|
131
|
+
if v.key?('append')
|
132
|
+
log[:append] = v['append']
|
131
133
|
end
|
132
134
|
end
|
135
|
+
end
|
133
136
|
|
134
|
-
|
135
|
-
|
137
|
+
def update_from_file(data)
|
138
|
+
if data['log'].is_a?(Hash)
|
139
|
+
update_logs(data['log'])
|
136
140
|
end
|
137
141
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
+
@modulepath = data['modulepath'].split(File::PATH_SEPARATOR) if data.key?('modulepath')
|
143
|
+
|
144
|
+
@inventoryfile = data['inventoryfile'] if data.key?('inventoryfile')
|
145
|
+
|
146
|
+
@hiera_config = data['hiera-config'] if data.key?('hiera-config')
|
147
|
+
@compile_concurrency = data['compile-concurrency'] if data.key?('compile-concurrency')
|
148
|
+
|
149
|
+
%w[concurrency format puppetdb color transport].each do |key|
|
150
|
+
send("#{key}=", data[key]) if data.key?(key)
|
142
151
|
end
|
143
152
|
|
144
153
|
TRANSPORTS.each do |key, impl|
|
145
154
|
if data[key.to_s]
|
146
155
|
selected = data[key.to_s].select { |k| impl.options.include?(k) }
|
147
|
-
|
156
|
+
@transports[key].merge!(selected)
|
148
157
|
end
|
149
158
|
end
|
150
159
|
end
|
151
160
|
private :update_from_file
|
152
161
|
|
153
|
-
def find_boltdir(dir)
|
154
|
-
path = dir
|
155
|
-
boltdir = nil
|
156
|
-
while boltdir.nil? && path && path != File.dirname(path)
|
157
|
-
maybe_boltdir = File.join(path, BOLTDIR_NAME)
|
158
|
-
boltdir = maybe_boltdir if File.directory?(maybe_boltdir)
|
159
|
-
path = File.dirname(path)
|
160
|
-
end
|
161
|
-
boltdir
|
162
|
-
end
|
163
|
-
|
164
|
-
def pwd
|
165
|
-
@pwd ||= Dir.pwd
|
166
|
-
end
|
167
|
-
|
168
|
-
def boltdir
|
169
|
-
@boltdir ||= find_boltdir(pwd) || default_boltdir
|
170
|
-
end
|
171
|
-
|
172
|
-
def default_boltdir
|
173
|
-
File.expand_path(File.join('~', '.puppetlabs', 'bolt'))
|
174
|
-
end
|
175
|
-
|
176
|
-
def default_modulepath
|
177
|
-
[File.join(boltdir, "modules")]
|
178
|
-
end
|
179
|
-
|
180
162
|
# TODO: This is deprecated in 0.21.0 and can be removed in release 0.22.0.
|
181
|
-
def legacy_conf
|
182
|
-
return @legacy_conf if defined?(@legacy_conf)
|
163
|
+
def self.legacy_conf
|
183
164
|
root_path = File.expand_path(File.join('~', '.puppetlabs'))
|
184
165
|
legacy_paths = [File.join(root_path, 'bolt.yaml'), File.join(root_path, 'bolt.yml')]
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
166
|
+
legacy_conf = legacy_paths.find { |path| File.exist?(path) }
|
167
|
+
found_legacy_conf = !!legacy_conf
|
168
|
+
legacy_conf ||= legacy_paths[0]
|
169
|
+
if found_legacy_conf
|
170
|
+
correct_path = Bolt::Boltdir.default_boltdir.config_file
|
171
|
+
msg = "Found configfile at deprecated location #{legacy_conf}. Global config should be in #{correct_path}"
|
172
|
+
Logging.logger[self].warn(msg)
|
191
173
|
end
|
192
|
-
|
174
|
+
legacy_conf
|
193
175
|
end
|
194
176
|
|
195
|
-
def
|
196
|
-
|
197
|
-
|
198
|
-
end
|
199
|
-
|
200
|
-
def default_inventory
|
201
|
-
File.join(boltdir, 'inventory.yaml')
|
202
|
-
end
|
203
|
-
|
204
|
-
def default_hiera
|
205
|
-
File.join(boltdir, 'hiera.yaml')
|
206
|
-
end
|
207
|
-
|
208
|
-
def update_from_cli(options)
|
209
|
-
%i[concurrency compile-concurrency transport format trace modulepath inventoryfile color].each do |key|
|
210
|
-
self[key] = options[key] if options.key?(key)
|
177
|
+
def apply_overrides(options)
|
178
|
+
%i[concurrency transport format trace modulepath inventoryfile color].each do |key|
|
179
|
+
send("#{key}=", options[key]) if options.key?(key)
|
211
180
|
end
|
212
181
|
|
213
182
|
if options[:debug]
|
214
|
-
|
183
|
+
@log['console'][:level] = :debug
|
215
184
|
elsif options[:verbose]
|
216
|
-
|
185
|
+
@log['console'][:level] = :info
|
217
186
|
end
|
218
187
|
|
188
|
+
@compile_concurrency = options[:'compile-concurrency'] if options[:'compile-concurrency']
|
189
|
+
|
219
190
|
TRANSPORTS.each_key do |transport|
|
220
|
-
transport =
|
191
|
+
transport = @transports[transport]
|
221
192
|
TRANSPORT_OPTIONS.each do |key|
|
222
193
|
if options[key]
|
223
194
|
transport[key.to_s] = Bolt::Util.walk_keys(options[key], &:to_s)
|
@@ -226,59 +197,49 @@ module Bolt
|
|
226
197
|
end
|
227
198
|
|
228
199
|
if options.key?(:ssl) # this defaults to true so we need to check the presence of the key
|
229
|
-
|
200
|
+
@transports[:winrm]['ssl'] = options[:ssl]
|
230
201
|
end
|
231
202
|
|
232
203
|
if options.key?(:'ssl-verify') # this defaults to true so we need to check the presence of the key
|
233
|
-
|
204
|
+
@transports[:winrm]['ssl-verify'] = options[:'ssl-verify']
|
234
205
|
end
|
235
206
|
|
236
207
|
if options.key?(:'host-key-check') # this defaults to true so we need to check the presence of the key
|
237
|
-
|
208
|
+
@transports[:ssh]['host-key-check'] = options[:'host-key-check']
|
238
209
|
end
|
239
210
|
end
|
240
211
|
|
241
|
-
# Defaults that do not vary based on boltdir should not be included here.
|
242
|
-
#
|
243
|
-
# Defaults which are treated differently from specified values like
|
244
|
-
# 'inventoryfile' cannot be included here or they will not be handled correctly.
|
245
|
-
def update_from_defaults
|
246
|
-
self[:modulepath] = default_modulepath
|
247
|
-
self[:'hiera-config'] = default_hiera
|
248
|
-
end
|
249
|
-
|
250
|
-
# The order in which config is processed is important
|
251
|
-
def update(options)
|
252
|
-
update_from_defaults
|
253
|
-
load_file(options[:configfile])
|
254
|
-
update_from_cli(options)
|
255
|
-
end
|
256
|
-
|
257
|
-
def load_file(path)
|
258
|
-
data = Bolt::Util.read_config_file(path, [default_config], 'config')
|
259
|
-
update_from_file(data) if data
|
260
|
-
validate_hiera_conf(data ? data['hiera-config'] : nil)
|
261
|
-
end
|
262
|
-
|
263
212
|
def update_from_inventory(data)
|
264
213
|
update_from_file(data)
|
265
214
|
|
266
215
|
if data['transport']
|
267
|
-
|
216
|
+
@transport = data['transport']
|
268
217
|
end
|
269
218
|
end
|
270
219
|
|
271
220
|
def transport_conf
|
272
|
-
{ transport:
|
273
|
-
transports:
|
221
|
+
{ transport: @transport,
|
222
|
+
transports: @transports }
|
223
|
+
end
|
224
|
+
|
225
|
+
def default_inventoryfile
|
226
|
+
[@boltdir.inventory_file]
|
227
|
+
end
|
228
|
+
|
229
|
+
def hiera_config
|
230
|
+
@hiera_config || @boltdir.hiera_config
|
274
231
|
end
|
275
232
|
|
276
|
-
def
|
277
|
-
|
233
|
+
def puppetfile
|
234
|
+
@boltdir.puppetfile
|
235
|
+
end
|
236
|
+
|
237
|
+
def modulepath
|
238
|
+
@modulepath || @boltdir.modulepath
|
278
239
|
end
|
279
240
|
|
280
241
|
def validate
|
281
|
-
|
242
|
+
@log.each_pair do |name, params|
|
282
243
|
if params.key?(:level) && !Bolt::Logger.valid_level?(params[:level])
|
283
244
|
raise Bolt::ValidationError,
|
284
245
|
"level of log #{name} must be one of: #{Bolt::Logger.levels.join(', ')}; received #{params[:level]}"
|
@@ -288,29 +249,33 @@ module Bolt
|
|
288
249
|
end
|
289
250
|
end
|
290
251
|
|
291
|
-
unless
|
252
|
+
unless @concurrency.is_a?(Integer) && @concurrency > 0
|
292
253
|
raise Bolt::ValidationError, 'Concurrency must be a positive integer'
|
293
254
|
end
|
294
255
|
|
295
|
-
unless
|
256
|
+
unless @compile_concurrency.is_a?(Integer) && @compile_concurrency > 0
|
296
257
|
raise Bolt::ValidationError, 'Compile concurrency must be a positive integer'
|
297
258
|
end
|
298
259
|
|
299
260
|
compile_limit = 2 * Concurrent.processor_count
|
300
|
-
unless
|
261
|
+
unless @compile_concurrency < compile_limit
|
301
262
|
raise Bolt::ValidationError, "Compilation is CPU-intensive, set concurrency less than #{compile_limit}"
|
302
263
|
end
|
303
264
|
|
304
|
-
unless %w[human json].include?
|
305
|
-
raise Bolt::ValidationError, "Unsupported format: '#{
|
265
|
+
unless %w[human json].include? @format
|
266
|
+
raise Bolt::ValidationError, "Unsupported format: '#{@format}'"
|
267
|
+
end
|
268
|
+
|
269
|
+
if @hiera_config && !(File.file?(@hiera_config) && File.readable?(@hiera_config))
|
270
|
+
raise Bolt::FileError, "Could not read hiera-config file #{@hiera_config}", @hiera_config
|
306
271
|
end
|
307
272
|
|
308
|
-
unless
|
309
|
-
raise UnknownTransportError,
|
273
|
+
unless @transport.nil? || Bolt::TRANSPORTS.include?(@transport.to_sym)
|
274
|
+
raise UnknownTransportError, @transport
|
310
275
|
end
|
311
276
|
|
312
277
|
TRANSPORTS.each do |transport, impl|
|
313
|
-
impl.validate(
|
278
|
+
impl.validate(@transports[transport])
|
314
279
|
end
|
315
280
|
end
|
316
281
|
end
|
data/lib/bolt/error.rb
CHANGED
@@ -63,6 +63,17 @@ module Bolt
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
+
class ApplyFailure < RunFailure
|
67
|
+
def initialize(result_set)
|
68
|
+
super(result_set, 'apply', 'catalog')
|
69
|
+
@kind = 'bolt/apply-failure'
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
result_set.select(&:error_hash).map { |result| result.error_hash['msg'] }.join("\n")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
66
77
|
class PlanFailure < Error
|
67
78
|
def initialize(*args)
|
68
79
|
super(*args)
|
@@ -77,9 +88,21 @@ module Bolt
|
|
77
88
|
end
|
78
89
|
end
|
79
90
|
|
91
|
+
class PuppetfileError < Error
|
92
|
+
def initialize(err)
|
93
|
+
super("Failed to sync modules from the Puppetfile: #{err}", 'bolt/puppetfile-error')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
80
97
|
class ApplyError < Error
|
81
|
-
def initialize(target
|
82
|
-
super("Apply failed to compile for #{target}
|
98
|
+
def initialize(target)
|
99
|
+
super("Apply failed to compile for #{target}", 'bolt/apply-error')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class ParseError < Error
|
104
|
+
def initialize(msg)
|
105
|
+
super(msg, 'bolt/parse-error')
|
83
106
|
end
|
84
107
|
end
|
85
108
|
|
data/lib/bolt/executor.rb
CHANGED
@@ -18,11 +18,10 @@ module Bolt
|
|
18
18
|
attr_reader :noop, :transports
|
19
19
|
attr_accessor :run_as, :plan_logging
|
20
20
|
|
21
|
-
def initialize(
|
21
|
+
def initialize(concurrency = 1,
|
22
22
|
analytics = Bolt::Analytics::NoopClient.new,
|
23
23
|
noop = nil,
|
24
24
|
bundled_content: nil)
|
25
|
-
@config = config
|
26
25
|
@analytics = analytics
|
27
26
|
@bundled_content = bundled_content
|
28
27
|
@logger = Logging.logger[self]
|
@@ -37,8 +36,8 @@ module Bolt
|
|
37
36
|
|
38
37
|
@noop = noop
|
39
38
|
@run_as = nil
|
40
|
-
@pool = Concurrent::ThreadPoolExecutor.new(max_threads:
|
41
|
-
@logger.debug { "Started with #{
|
39
|
+
@pool = Concurrent::ThreadPoolExecutor.new(max_threads: concurrency)
|
40
|
+
@logger.debug { "Started with #{concurrency} max thread(s)" }
|
42
41
|
@notifier = Bolt::Notifier.new
|
43
42
|
end
|
44
43
|
|