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.

@@ -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 = Struct.new(
28
- :concurrency,
29
- :'compile-concurrency',
30
- :format,
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
- BOLTDIR_NAME = 'Boltdir'
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
- def initialize(**kwargs)
79
- super()
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
- DEFAULTS.merge(kwargs).each { |k, v| self[k] = v }
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
- self[:log] ||= {}
87
- self[:log]['console'] ||= {}
96
+ @log = { 'console' => {} }
88
97
 
89
- self[:transports] ||= {}
98
+ @transports = {}
90
99
  TRANSPORTS.each_key do |transport|
91
- self[:transports][transport] ||= {}
100
+ @transports[transport] = TRANSPORT_DEFAULTS.merge(TRANSPORT_SPECIFIC_DEFAULTS[transport])
101
+ end
92
102
 
93
- TRANSPORT_DEFAULTS.each do |k, v|
94
- unless self[:transports][transport][k]
95
- self[:transports][transport][k] = v
96
- end
97
- end
103
+ update_from_file(config_data)
104
+ apply_overrides(overrides)
98
105
 
99
- TRANSPORT_SPECIFIC_DEFAULTS[transport].each do |k, v|
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 update_from_file(data)
118
- if data['log'].is_a?(Hash)
119
- data['log'].each_pair do |k, v|
120
- log = (self[:log][normalize_log(k)] ||= {})
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
- next unless v.is_a?(Hash)
125
+ next unless v.is_a?(Hash)
123
126
 
124
- if v.key?('level')
125
- log[:level] = v['level'].to_s
126
- end
127
+ if v.key?('level')
128
+ log[:level] = v['level'].to_s
129
+ end
127
130
 
128
- if v.key?('append')
129
- log[:append] = v['append']
130
- end
131
+ if v.key?('append')
132
+ log[:append] = v['append']
131
133
  end
132
134
  end
135
+ end
133
136
 
134
- if data['modulepath']
135
- self[:modulepath] = data['modulepath'].split(File::PATH_SEPARATOR)
137
+ def update_from_file(data)
138
+ if data['log'].is_a?(Hash)
139
+ update_logs(data['log'])
136
140
  end
137
141
 
138
- %w[inventoryfile concurrency compile-concurrency format puppetdb hiera-config color transport].each do |key|
139
- if data.key?(key)
140
- self[key.to_sym] = data[key]
141
- end
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
- self[:transports][key].merge!(selected)
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
- @legacy_conf = legacy_paths.find { |path| File.exist?(path) }
186
- @legacy_conf ||= legacy_paths[0]
187
- if @legacy_conf
188
- correct_path = File.join(default_boltdir, 'bolt.yaml')
189
- msg = "Found configfile at deprecated location #{@legacy_conf}. Global config should be in #{correct_path}"
190
- @logger.warn(msg)
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
- @legacy_conf
174
+ legacy_conf
193
175
  end
194
176
 
195
- def default_config
196
- path = File.join(boltdir, 'bolt.yaml')
197
- File.exist?(path) ? path : legacy_conf
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
- self[:log]['console'][:level] = :debug
183
+ @log['console'][:level] = :debug
215
184
  elsif options[:verbose]
216
- self[:log]['console'][:level] = :info
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 = self[:transports][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
- self[:transports][:winrm]['ssl'] = options[:ssl]
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
- self[:transports][:winrm]['ssl-verify'] = options[:'ssl-verify']
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
- self[:transports][:ssh]['host-key-check'] = options[:'host-key-check']
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
- self[:transport] = data['transport']
216
+ @transport = data['transport']
268
217
  end
269
218
  end
270
219
 
271
220
  def transport_conf
272
- { transport: self[:transport],
273
- transports: self[: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 validate_hiera_conf(path)
277
- Bolt::Util.read_config_file(path, [default_hiera], 'hiera-config')
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
- self[:log].each_pair do |name, params|
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 self[:concurrency].is_a?(Integer) && self[:concurrency] > 0
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 self[:'compile-concurrency'].is_a?(Integer) && self[:'compile-concurrency'] > 0
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 self[:'compile-concurrency'] < compile_limit
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? self[:format]
305
- raise Bolt::ValidationError, "Unsupported format: '#{self[: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 self[:transport].nil? || Bolt::TRANSPORTS.include?(self[:transport].to_sym)
309
- raise UnknownTransportError, self[:transport]
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(self[:transports][transport])
278
+ impl.validate(@transports[transport])
314
279
  end
315
280
  end
316
281
  end
@@ -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, err)
82
- super("Apply failed to compile for #{target}: #{err}", 'bolt/apply-error')
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
 
@@ -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(config = Bolt::Config.new,
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: @config[:concurrency])
41
- @logger.debug { "Started with #{@config[:concurrency]} max thread(s)" }
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