bolt 2.25.0 → 2.30.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.

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +4 -3
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
  5. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +1 -1
  6. data/lib/bolt/analytics.rb +7 -3
  7. data/lib/bolt/applicator.rb +21 -21
  8. data/lib/bolt/bolt_option_parser.rb +116 -26
  9. data/lib/bolt/catalog.rb +5 -3
  10. data/lib/bolt/cli.rb +194 -185
  11. data/lib/bolt/config.rb +61 -26
  12. data/lib/bolt/config/options.rb +35 -2
  13. data/lib/bolt/executor.rb +2 -2
  14. data/lib/bolt/inventory.rb +8 -1
  15. data/lib/bolt/inventory/group.rb +1 -1
  16. data/lib/bolt/inventory/inventory.rb +1 -1
  17. data/lib/bolt/inventory/target.rb +1 -1
  18. data/lib/bolt/logger.rb +35 -21
  19. data/lib/bolt/module_installer.rb +172 -0
  20. data/lib/bolt/outputter.rb +4 -0
  21. data/lib/bolt/outputter/human.rb +53 -11
  22. data/lib/bolt/outputter/json.rb +7 -1
  23. data/lib/bolt/outputter/logger.rb +3 -3
  24. data/lib/bolt/pal.rb +29 -20
  25. data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
  26. data/lib/bolt/plugin/module.rb +1 -1
  27. data/lib/bolt/plugin/puppetdb.rb +1 -1
  28. data/lib/bolt/project.rb +89 -28
  29. data/lib/bolt/project_migrator.rb +80 -0
  30. data/lib/bolt/project_migrator/base.rb +39 -0
  31. data/lib/bolt/project_migrator/config.rb +67 -0
  32. data/lib/bolt/project_migrator/inventory.rb +67 -0
  33. data/lib/bolt/project_migrator/modules.rb +198 -0
  34. data/lib/bolt/puppetdb/client.rb +1 -1
  35. data/lib/bolt/puppetdb/config.rb +1 -1
  36. data/lib/bolt/puppetfile.rb +142 -0
  37. data/lib/bolt/puppetfile/installer.rb +43 -0
  38. data/lib/bolt/puppetfile/module.rb +90 -0
  39. data/lib/bolt/r10k_log_proxy.rb +1 -1
  40. data/lib/bolt/rerun.rb +2 -2
  41. data/lib/bolt/result.rb +23 -0
  42. data/lib/bolt/shell.rb +1 -1
  43. data/lib/bolt/shell/bash.rb +1 -1
  44. data/lib/bolt/task.rb +1 -1
  45. data/lib/bolt/transport/base.rb +5 -5
  46. data/lib/bolt/transport/docker/connection.rb +1 -1
  47. data/lib/bolt/transport/local/connection.rb +1 -1
  48. data/lib/bolt/transport/ssh.rb +1 -1
  49. data/lib/bolt/transport/ssh/connection.rb +1 -1
  50. data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
  51. data/lib/bolt/transport/winrm.rb +1 -1
  52. data/lib/bolt/transport/winrm/connection.rb +1 -1
  53. data/lib/bolt/util.rb +52 -11
  54. data/lib/bolt/version.rb +1 -1
  55. data/lib/bolt_server/acl.rb +2 -2
  56. data/lib/bolt_server/base_config.rb +3 -3
  57. data/lib/bolt_server/config.rb +1 -1
  58. data/lib/bolt_server/file_cache.rb +12 -12
  59. data/lib/bolt_server/transport_app.rb +125 -26
  60. data/lib/bolt_spec/bolt_context.rb +4 -4
  61. data/lib/bolt_spec/plans/mock_executor.rb +1 -1
  62. metadata +15 -13
  63. data/lib/bolt/project_migrate.rb +0 -138
@@ -14,7 +14,7 @@ module Bolt
14
14
  @target = target
15
15
  ssh_config = Net::SSH::Config.for(target.host)
16
16
  @user = @target.user || ssh_config[:user] || Etc.getlogin
17
- @logger = Logging.logger[self]
17
+ @logger = Bolt::Logger.logger(self)
18
18
  end
19
19
 
20
20
  # This is used to verify we can connect to targets with `connected?`
@@ -11,7 +11,7 @@ module Bolt
11
11
  require 'winrm'
12
12
  require 'winrm-fs'
13
13
 
14
- @transport_logger = Logging.logger[::WinRM]
14
+ @transport_logger = Bolt::Logger.logger(::WinRM)
15
15
  @transport_logger.level = :warn
16
16
  end
17
17
 
@@ -18,7 +18,7 @@ module Bolt
18
18
  @user = @target.user
19
19
  # Build set of extensions from extensions config as well as interpreters
20
20
 
21
- @logger = Logging.logger[@target.safe_name]
21
+ @logger = Bolt::Logger.logger(@target.safe_name)
22
22
  logger.trace("Initializing winrm connection to #{@target.safe_name}")
23
23
  @transport_logger = transport_logger
24
24
  end
@@ -3,10 +3,29 @@
3
3
  module Bolt
4
4
  module Util
5
5
  class << self
6
+ # Gets input for an argument.
7
+ def get_arg_input(value)
8
+ if value.start_with?('@')
9
+ file = value.sub(/^@/, '')
10
+ read_arg_file(file)
11
+ elsif value == '-'
12
+ $stdin.read
13
+ else
14
+ value
15
+ end
16
+ end
17
+
18
+ # Reads a file passed as an argument to a command.
19
+ def read_arg_file(file)
20
+ File.read(File.expand_path(file))
21
+ rescue StandardError => e
22
+ raise Bolt::FileError.new("Error attempting to read #{file}: #{e}", file)
23
+ end
24
+
6
25
  def read_yaml_hash(path, file_name)
7
26
  require 'yaml'
8
27
 
9
- logger = Logging.logger[self]
28
+ logger = Bolt::Logger.logger(self)
10
29
  path = File.expand_path(path)
11
30
  content = File.open(path, "r:UTF-8") { |f| YAML.safe_load(f.read) } || {}
12
31
  unless content.is_a?(Hash)
@@ -179,16 +198,16 @@ module Bolt
179
198
  # object was frozen
180
199
  frozen = obj.frozen?
181
200
  cl = begin
182
- obj.clone(freeze: false)
183
- # Some datatypes, such as FalseClass, can't be unfrozen. These
184
- # aren't the types we recurse on, so we can leave them frozen
185
- rescue ArgumentError => e
186
- if e.message =~ /can't unfreeze/
187
- obj.clone
188
- else
189
- raise e
190
- end
191
- end
201
+ obj.clone(freeze: false)
202
+ # Some datatypes, such as FalseClass, can't be unfrozen. These
203
+ # aren't the types we recurse on, so we can leave them frozen
204
+ rescue ArgumentError => e
205
+ if e.message =~ /can't unfreeze/
206
+ obj.clone
207
+ else
208
+ raise e
209
+ end
210
+ end
192
211
  rescue *error_types
193
212
  cloned[obj.object_id] = obj
194
213
  obj
@@ -280,6 +299,28 @@ module Bolt
280
299
  raise Bolt::ValidationError, "path must be a String, received #{path.class} #{path}" unless path.is_a?(String)
281
300
  path.split(%r{[/\\]}).last
282
301
  end
302
+
303
+ # Prompts yes or no, returning true for yes and false for no.
304
+ #
305
+ def prompt_yes_no(prompt, outputter)
306
+ choices = {
307
+ 'y' => true,
308
+ 'yes' => true,
309
+ 'n' => false,
310
+ 'no' => false
311
+ }
312
+
313
+ loop do
314
+ outputter.print_prompt("#{prompt} ([y]es/[n]o) ")
315
+ response = $stdin.gets.to_s.downcase.chomp
316
+
317
+ if choices.key?(response)
318
+ return choices[response]
319
+ else
320
+ outputter.print_prompt_error("Invalid response, must pick [y]es or [n]o")
321
+ end
322
+ end
323
+ end
283
324
  end
284
325
  end
285
326
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.25.0'
4
+ VERSION = '2.30.0'
5
5
  end
@@ -16,9 +16,9 @@ module BoltServer
16
16
  end
17
17
  end
18
18
 
19
- def initialize(app, whitelist)
19
+ def initialize(app, allowlist)
20
20
  acls = []
21
- whitelist.each do |entry|
21
+ allowlist.each do |entry|
22
22
  acls << {
23
23
  'resources' => [
24
24
  {
@@ -7,7 +7,7 @@ module BoltServer
7
7
  class BaseConfig
8
8
  def config_keys
9
9
  %w[host port ssl-cert ssl-key ssl-ca-cert
10
- ssl-cipher-suites loglevel logfile whitelist]
10
+ ssl-cipher-suites loglevel logfile allowlist projects-dir]
11
11
  end
12
12
 
13
13
  def env_keys
@@ -98,8 +98,8 @@ module BoltServer
98
98
  raise Bolt::ValidationError, "Configured 'ssl-cipher-suites' must be an array of cipher suite names"
99
99
  end
100
100
 
101
- unless @data['whitelist'].nil? || @data['whitelist'].is_a?(Array)
102
- raise Bolt::ValidationError, "Configured 'whitelist' must be an array of names"
101
+ unless @data['allowlist'].nil? || @data['allowlist'].is_a?(Array)
102
+ raise Bolt::ValidationError, "Configured 'allowlist' must be an array of names"
103
103
  end
104
104
  end
105
105
 
@@ -7,7 +7,7 @@ require 'bolt/error'
7
7
  module BoltServer
8
8
  class Config < BoltServer::BaseConfig
9
9
  def config_keys
10
- super + %w[concurrency cache-dir file-server-conn-timeout file-server-uri]
10
+ super + %w[concurrency cache-dir file-server-conn-timeout file-server-uri projects-dir]
11
11
  end
12
12
 
13
13
  def env_keys
@@ -33,7 +33,7 @@ module BoltServer
33
33
  @executor = executor
34
34
  @cache_dir = config['cache-dir']
35
35
  @config = config
36
- @logger = Logging.logger[self]
36
+ @logger = Bolt::Logger.logger(self)
37
37
  @cache_dir_mutex = cache_dir_mutex
38
38
 
39
39
  if do_purge
@@ -64,17 +64,17 @@ module BoltServer
64
64
 
65
65
  def client
66
66
  @client ||= begin
67
- uri = URI(@config['file-server-uri'])
68
- https = Net::HTTP.new(uri.host, uri.port)
69
- https.use_ssl = true
70
- https.ssl_version = :TLSv1_2
71
- https.ca_file = @config['ssl-ca-cert']
72
- https.cert = OpenSSL::X509::Certificate.new(ssl_cert)
73
- https.key = OpenSSL::PKey::RSA.new(ssl_key)
74
- https.verify_mode = OpenSSL::SSL::VERIFY_PEER
75
- https.open_timeout = @config['file-server-conn-timeout']
76
- https
77
- end
67
+ uri = URI(@config['file-server-uri'])
68
+ https = Net::HTTP.new(uri.host, uri.port)
69
+ https.use_ssl = true
70
+ https.ssl_version = :TLSv1_2
71
+ https.ca_file = @config['ssl-ca-cert']
72
+ https.cert = OpenSSL::X509::Certificate.new(ssl_cert)
73
+ https.key = OpenSSL::PKey::RSA.new(ssl_key)
74
+ https.verify_mode = OpenSSL::SSL::VERIFY_PEER
75
+ https.open_timeout = @config['file-server-conn-timeout']
76
+ https
77
+ end
78
78
  end
79
79
 
80
80
  def request_file(path, params, file)
@@ -5,6 +5,7 @@ require 'addressable/uri'
5
5
  require 'bolt'
6
6
  require 'bolt/error'
7
7
  require 'bolt/inventory'
8
+ require 'bolt/project'
8
9
  require 'bolt/target'
9
10
  require 'bolt_server/file_cache'
10
11
  require 'bolt/task/puppet_server'
@@ -191,20 +192,44 @@ module BoltServer
191
192
  end
192
193
 
193
194
  def in_pe_pal_env(environment)
194
- if environment.nil?
195
- [400, '`environment` is a required argument']
196
- else
197
- @pal_mutex.synchronize do
198
- pal = BoltServer::PE::PAL.new({}, environment)
199
- yield pal
200
- rescue Puppet::Environments::EnvironmentNotFound
201
- [400, {
202
- "class" => 'bolt/unknown-environment',
203
- "message" => "Environment #{environment} not found"
204
- }.to_json]
205
- rescue Bolt::Error => e
206
- [400, e.to_json]
207
- end
195
+ return [400, '`environment` is a required argument'] if environment.nil?
196
+ @pal_mutex.synchronize do
197
+ pal = BoltServer::PE::PAL.new({}, environment)
198
+ yield pal
199
+ rescue Puppet::Environments::EnvironmentNotFound
200
+ [400, {
201
+ "class" => 'bolt/unknown-environment',
202
+ "message" => "Environment #{environment} not found"
203
+ }.to_json]
204
+ rescue Bolt::Error => e
205
+ [400, e.to_json]
206
+ end
207
+ end
208
+
209
+ def in_bolt_project(bolt_project)
210
+ return [400, '`project_ref` is a required argument'] if bolt_project.nil?
211
+ project_dir = File.join(@config['projects-dir'], bolt_project)
212
+ return [400, "`project_ref`: #{project_dir} does not exist"] unless Dir.exist?(project_dir)
213
+ @pal_mutex.synchronize do
214
+ project = Bolt::Project.create_project(project_dir)
215
+ bolt_config = Bolt::Config.from_project(project, { log: { 'bolt-debug.log' => 'disable' } })
216
+ pal = Bolt::PAL.new(bolt_config.modulepath, nil, nil, nil, nil, nil, bolt_config.project)
217
+ module_path = [
218
+ BoltServer::PE::PAL::PE_BOLTLIB_PATH,
219
+ Bolt::PAL::BOLTLIB_PATH,
220
+ *bolt_config.modulepath,
221
+ Bolt::PAL::MODULES_PATH
222
+ ]
223
+ # CODEREVIEW: I *think* this is the only thing we need to make different between bolt's PAL. Is it acceptable
224
+ # to hack this? Modulepath is currently a readable attribute, could we make it writeable?
225
+ pal.instance_variable_set(:@modulepath, module_path)
226
+ context = {
227
+ pal: pal,
228
+ config: bolt_config
229
+ }
230
+ yield context
231
+ rescue Bolt::Error => e
232
+ [400, e.to_json]
208
233
  end
209
234
  end
210
235
 
@@ -221,14 +246,12 @@ module BoltServer
221
246
  plan_info
222
247
  end
223
248
 
224
- def build_puppetserver_uri(file_identifier, module_name, environment)
249
+ def build_puppetserver_uri(file_identifier, module_name, parameters)
225
250
  segments = file_identifier.split('/', 3)
226
251
  if segments.size == 1
227
252
  {
228
253
  'path' => "/puppet/v3/file_content/tasks/#{module_name}/#{file_identifier}",
229
- 'params' => {
230
- 'environment' => environment
231
- }
254
+ 'params' => parameters
232
255
  }
233
256
  else
234
257
  module_segment, mount_segment, name_segment = *segments
@@ -241,14 +264,12 @@ module BoltServer
241
264
  when 'lib'
242
265
  "/puppet/v3/file_content/plugins/#{name_segment}"
243
266
  end,
244
- 'params' => {
245
- 'environment' => environment
246
- }
267
+ 'params' => parameters
247
268
  }
248
269
  end
249
270
  end
250
271
 
251
- def pe_task_info(pal, module_name, task_name, environment)
272
+ def pe_task_info(pal, module_name, task_name, parameters)
252
273
  # Handle case where task name is simply module name with special `init` task
253
274
  task_name = if task_name == 'init' || task_name.nil?
254
275
  module_name
@@ -261,7 +282,7 @@ module BoltServer
261
282
  'filename' => file_hash['name'],
262
283
  'sha256' => Digest::SHA256.hexdigest(File.read(file_hash['path'])),
263
284
  'size_bytes' => File.size(file_hash['path']),
264
- 'uri' => build_puppetserver_uri(file_hash['name'], module_name, environment)
285
+ 'uri' => build_puppetserver_uri(file_hash['name'], module_name, parameters)
265
286
  }
266
287
  end
267
288
  {
@@ -271,6 +292,21 @@ module BoltServer
271
292
  }
272
293
  end
273
294
 
295
+ def allowed_helper(metadata, allowlist)
296
+ allowed = allowlist.nil? || allowlist.include?(metadata['name']) ? true : false
297
+ metadata.merge({ 'allowed' => allowed })
298
+ end
299
+
300
+ def task_list(pal)
301
+ tasks = pal.list_tasks
302
+ tasks.map { |task_name, _description| { 'name' => task_name } }
303
+ end
304
+
305
+ def plan_list(pal)
306
+ plans = pal.list_plans.flatten
307
+ plans.map { |plan_name| { 'name' => plan_name } }
308
+ end
309
+
274
310
  get '/' do
275
311
  200
276
312
  end
@@ -401,12 +437,40 @@ module BoltServer
401
437
  end
402
438
  end
403
439
 
440
+ # Fetches the metadata for a single plan
441
+ #
442
+ # @param project_ref [String] the project to fetch the plan from
443
+ get '/project_plans/:module_name/:plan_name' do
444
+ in_bolt_project(params['project_ref']) do |context|
445
+ plan_info = pe_plan_info(context[:pal], params[:module_name], params[:plan_name])
446
+ plan_info = allowed_helper(plan_info, context[:config].project.plans)
447
+ [200, plan_info.to_json]
448
+ end
449
+ end
450
+
404
451
  # Fetches the metadata for a single task
405
452
  #
406
453
  # @param environment [String] the environment to fetch the task from
407
454
  get '/tasks/:module_name/:task_name' do
408
455
  in_pe_pal_env(params['environment']) do |pal|
409
- task_info = pe_task_info(pal, params[:module_name], params[:task_name], params['environment'])
456
+ ps_parameters = {
457
+ 'environment' => params['environment']
458
+ }
459
+ task_info = pe_task_info(pal, params[:module_name], params[:task_name], ps_parameters)
460
+ [200, task_info.to_json]
461
+ end
462
+ end
463
+
464
+ # Fetches the metadata for a single task
465
+ #
466
+ # @param bolt_project_ref [String] the reference to the bolt-project directory to load task metadata from
467
+ get '/project_tasks/:module_name/:task_name' do
468
+ in_bolt_project(params['project_ref']) do |context|
469
+ ps_parameters = {
470
+ 'project' => params['project_ref']
471
+ }
472
+ task_info = pe_task_info(context[:pal], params[:module_name], params[:task_name], ps_parameters)
473
+ task_info = allowed_helper(task_info, context[:config].project.tasks)
410
474
  [200, task_info.to_json]
411
475
  end
412
476
  end
@@ -435,13 +499,30 @@ module BoltServer
435
499
  end
436
500
  end
437
501
 
502
+ # Fetches the list of plans for a project
503
+ #
504
+ # @param project_ref [String] the project to fetch the list of plans from
505
+ get '/project_plans' do
506
+ in_bolt_project(params['project_ref']) do |context|
507
+ plans_response = plan_list(context[:pal])
508
+
509
+ # Dig in context for the allowlist of plans from project object
510
+ plans_response.map! { |metadata| allowed_helper(metadata, context[:config].project.plans) }
511
+
512
+ # We structure this array of plans to be an array of hashes so that it matches the structure
513
+ # returned by the puppetserver API that serves data like this. Structuring the output this way
514
+ # makes switching between puppetserver and bolt-server easier, which makes changes to switch
515
+ # to bolt-server smaller/simpler.
516
+ [200, plans_response.to_json]
517
+ end
518
+ end
519
+
438
520
  # Fetches the list of tasks for an environment
439
521
  #
440
522
  # @param environment [String] the environment to fetch the list of tasks from
441
523
  get '/tasks' do
442
524
  in_pe_pal_env(params['environment']) do |pal|
443
- tasks = pal.list_tasks
444
- tasks_response = tasks.map { |task_name, _description| { 'name' => task_name } }.to_json
525
+ tasks_response = task_list(pal).to_json
445
526
 
446
527
  # We structure this array of tasks to be an array of hashes so that it matches the structure
447
528
  # returned by the puppetserver API that serves data like this. Structuring the output this way
@@ -451,6 +532,24 @@ module BoltServer
451
532
  end
452
533
  end
453
534
 
535
+ # Fetches the list of tasks for a bolt-project
536
+ #
537
+ # @param project_ref [String] the project to fetch the list of tasks from
538
+ get '/project_tasks' do
539
+ in_bolt_project(params['project_ref']) do |context|
540
+ tasks_response = task_list(context[:pal])
541
+
542
+ # Dig in context for the allowlist of tasks from project object
543
+ tasks_response.map! { |metadata| allowed_helper(metadata, context[:config].project.tasks) }
544
+
545
+ # We structure this array of tasks to be an array of hashes so that it matches the structure
546
+ # returned by the puppetserver API that serves data like this. Structuring the output this way
547
+ # makes switching between puppetserver and bolt-server easier, which makes changes to switch
548
+ # to bolt-server smaller/simpler.
549
+ [200, tasks_response.to_json]
550
+ end
551
+ end
552
+
454
553
  error 404 do
455
554
  err = Bolt::Error.new("Could not find route #{request.path}",
456
555
  'boltserver/not-found')
@@ -142,10 +142,10 @@ module BoltSpec
142
142
  # Override in your tests
143
143
  def config
144
144
  @config ||= begin
145
- conf = Bolt::Config.default
146
- conf.modulepath = [modulepath].flatten
147
- conf
148
- end
145
+ conf = Bolt::Config.default
146
+ conf.modulepath = [modulepath].flatten
147
+ conf
148
+ end
149
149
  end
150
150
 
151
151
  def plugins
@@ -40,7 +40,7 @@ module BoltSpec
40
40
 
41
41
  def module_file_id(file)
42
42
  modpath = @modulepath.select { |path| file =~ /^#{path}/ }
43
- raise "Could not identify module path containing #{file}: #{modpath}" unless modpath.size == 1
43
+ raise "Could not identify modulepath containing #{file}: #{modpath}" unless modpath.size == 1
44
44
 
45
45
  path = Pathname.new(file)
46
46
  relative = path.relative_path_from(Pathname.new(modpath.first))