bolt 2.37.0 → 2.44.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +17 -17
  3. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +6 -8
  4. data/lib/bolt/analytics.rb +3 -2
  5. data/lib/bolt/applicator.rb +11 -1
  6. data/lib/bolt/bolt_option_parser.rb +20 -13
  7. data/lib/bolt/catalog.rb +10 -29
  8. data/lib/bolt/cli.rb +58 -40
  9. data/lib/bolt/config.rb +134 -119
  10. data/lib/bolt/config/options.rb +142 -77
  11. data/lib/bolt/config/transport/base.rb +2 -2
  12. data/lib/bolt/config/transport/local.rb +1 -0
  13. data/lib/bolt/config/transport/options.rb +18 -68
  14. data/lib/bolt/config/transport/orch.rb +1 -0
  15. data/lib/bolt/config/transport/ssh.rb +0 -5
  16. data/lib/bolt/executor.rb +15 -5
  17. data/lib/bolt/inventory.rb +26 -0
  18. data/lib/bolt/inventory/group.rb +35 -12
  19. data/lib/bolt/inventory/inventory.rb +1 -1
  20. data/lib/bolt/inventory/options.rb +130 -0
  21. data/lib/bolt/inventory/target.rb +10 -11
  22. data/lib/bolt/logger.rb +114 -10
  23. data/lib/bolt/module.rb +10 -2
  24. data/lib/bolt/module_installer.rb +25 -15
  25. data/lib/bolt/module_installer/resolver.rb +65 -12
  26. data/lib/bolt/module_installer/specs/forge_spec.rb +8 -2
  27. data/lib/bolt/module_installer/specs/git_spec.rb +17 -2
  28. data/lib/bolt/outputter.rb +19 -5
  29. data/lib/bolt/outputter/human.rb +24 -1
  30. data/lib/bolt/outputter/json.rb +1 -1
  31. data/lib/bolt/outputter/logger.rb +1 -1
  32. data/lib/bolt/outputter/rainbow.rb +12 -1
  33. data/lib/bolt/pal.rb +93 -14
  34. data/lib/bolt/pal/yaml_plan.rb +8 -2
  35. data/lib/bolt/pal/yaml_plan/evaluator.rb +2 -2
  36. data/lib/bolt/pal/yaml_plan/transpiler.rb +6 -1
  37. data/lib/bolt/plugin.rb +3 -3
  38. data/lib/bolt/plugin/cache.rb +8 -8
  39. data/lib/bolt/plugin/module.rb +1 -1
  40. data/lib/bolt/plugin/puppet_connect_data.rb +35 -0
  41. data/lib/bolt/plugin/puppetdb.rb +2 -2
  42. data/lib/bolt/project.rb +76 -50
  43. data/lib/bolt/project_manager.rb +2 -0
  44. data/lib/bolt/project_manager/config_migrator.rb +9 -1
  45. data/lib/bolt/project_manager/module_migrator.rb +2 -0
  46. data/lib/bolt/puppetdb/client.rb +8 -0
  47. data/lib/bolt/rerun.rb +1 -1
  48. data/lib/bolt/shell/bash.rb +1 -1
  49. data/lib/bolt/shell/bash/tmpdir.rb +4 -1
  50. data/lib/bolt/shell/powershell.rb +7 -5
  51. data/lib/bolt/target.rb +4 -0
  52. data/lib/bolt/task.rb +1 -1
  53. data/lib/bolt/transport/docker/connection.rb +2 -2
  54. data/lib/bolt/transport/local.rb +13 -0
  55. data/lib/bolt/transport/orch/connection.rb +1 -1
  56. data/lib/bolt/transport/ssh.rb +1 -2
  57. data/lib/bolt/transport/ssh/connection.rb +1 -1
  58. data/lib/bolt/validator.rb +227 -0
  59. data/lib/bolt/version.rb +1 -1
  60. data/lib/bolt_server/config.rb +1 -1
  61. data/lib/bolt_server/schemas/partials/task.json +1 -1
  62. data/lib/bolt_server/transport_app.rb +28 -27
  63. data/libexec/bolt_catalog +1 -1
  64. metadata +27 -11
  65. data/lib/bolt/config/validator.rb +0 -231
@@ -153,8 +153,10 @@ module Bolt
153
153
  Dir.children(path).select { |name| Puppet::Module.is_module_directory?(name, path) }
154
154
  end
155
155
  if modules.include?(project.name)
156
- Bolt::Logger.warn_once("project shadows module",
157
- "The project '#{project.name}' shadows an existing module of the same name")
156
+ Bolt::Logger.warn_once(
157
+ "project_shadows_module",
158
+ "The project '#{project.name}' shadows an existing module of the same name"
159
+ )
158
160
  end
159
161
  end
160
162
 
@@ -357,19 +359,52 @@ module Bolt
357
359
  Bolt::Task.from_task_signature(task)
358
360
  end
359
361
 
362
+ def list_plans_with_cache(filter_content: false)
363
+ # Don't filter content yet, so that if users update their plan filters
364
+ # we don't need to refresh the cache
365
+ plan_names = list_plans(filter_content: false).map(&:first)
366
+ plan_cache = if @project
367
+ Bolt::Util.read_optional_json_file(@project.plan_cache_file, 'Plan cache file')
368
+ else
369
+ {}
370
+ end
371
+ updated = false
372
+
373
+ plan_list = plan_names.each_with_object([]) do |plan_name, list|
374
+ info = plan_cache[plan_name] || get_plan_info(plan_name, with_mtime: true)
375
+
376
+ # If the plan is a 'local' plan (in the project itself, or the
377
+ # modules/ directory) then verify it hasn't been updated since we
378
+ # cached it. If it has been updated, refresh the cache and use the
379
+ # new data.
380
+ if info['file'] &&
381
+ (File.mtime(info.dig('file', 'path')) <=> info.dig('file', 'mtime')) != 0
382
+ info = get_plan_info(plan_name, with_mtime: true)
383
+ updated = true
384
+ plan_cache[plan_name] = info
385
+ end
386
+
387
+ list << [plan_name] unless info['private']
388
+ end
389
+
390
+ File.write(@project.plan_cache_file, plan_cache.to_json) if updated
391
+
392
+ filter_content ? filter_content(plan_list, @project&.plans) : plan_list
393
+ end
394
+
360
395
  def list_plans(filter_content: false)
361
396
  in_bolt_compiler do |compiler|
362
397
  errors = []
363
398
  plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
364
399
  errors.each do |error|
365
- @logger.warn(error.details['original_error'])
400
+ Bolt::Logger.warn("plan_load_error", error.details['original_error'])
366
401
  end
367
402
 
368
403
  filter_content ? filter_content(plans, @project&.plans) : plans
369
404
  end
370
405
  end
371
406
 
372
- def get_plan_info(plan_name)
407
+ def get_plan_info(plan_name, with_mtime: false)
373
408
  plan_sig = in_bolt_compiler do |compiler|
374
409
  compiler.plan_signature(plan_name)
375
410
  end
@@ -412,16 +447,28 @@ module Bolt
412
447
  params[name]['default_value'] = defaults[name] if defaults.key?(name)
413
448
  params[name]['description'] = param.text unless param.text.empty?
414
449
  else
415
- @logger.warn("The documented parameter '#{name}' does not exist in plan signature")
450
+ Bolt::Logger.warn(
451
+ "missing_plan_parameter",
452
+ "The documented parameter '#{name}' does not exist in plan signature"
453
+ )
416
454
  end
417
455
  end
418
456
 
419
- {
420
- 'name' => plan_name,
457
+ privie = plan.tag(:private)&.text
458
+ unless privie.nil? || %w[true false].include?(privie.downcase)
459
+ msg = "Plan #{plan_name} key 'private' must be a boolean, received: #{privie}"
460
+ raise Bolt::Error.new(msg, 'bolt/invalid-plan')
461
+ end
462
+
463
+ pp_info = {
464
+ 'name' => plan_name,
421
465
  'description' => description,
422
- 'parameters' => parameters,
423
- 'module' => mod
466
+ 'parameters' => parameters,
467
+ 'module' => mod
424
468
  }
469
+ pp_info.merge!({ 'private' => privie&.downcase == 'true' }) unless privie.nil?
470
+ pp_info.merge!(get_plan_mtime(plan.file)) if with_mtime
471
+ pp_info
425
472
 
426
473
  # If it's a YAML plan, fall back to limited data
427
474
  else
@@ -444,12 +491,32 @@ module Bolt
444
491
  params[name]['default_value'] = param.value unless param.value.nil?
445
492
  params[name]['description'] = param.description if param.description
446
493
  end
447
- {
448
- 'name' => plan_name,
494
+
495
+ yaml_info = {
496
+ 'name' => plan_name,
449
497
  'description' => plan.description,
450
- 'parameters' => parameters,
451
- 'module' => mod
498
+ 'parameters' => parameters,
499
+ 'module' => mod
452
500
  }
501
+ yaml_info.merge!({ 'private' => plan.private }) unless plan.private.nil?
502
+ yaml_info.merge!(get_plan_mtime(yaml_path)) if with_mtime
503
+ yaml_info
504
+ end
505
+ end
506
+
507
+ def get_plan_mtime(path)
508
+ # If the plan is from the project modules/ directory, or is in the
509
+ # project itself, include the last mtime of the file so we can compare
510
+ # if the plan has been updated since it was cached.
511
+ if @project &&
512
+ File.exist?(path) &&
513
+ (path.include?(File.join(@project.path, 'modules')) ||
514
+ path.include?(@project.plans_path.to_s))
515
+
516
+ { 'file' => { 'mtime' => File.mtime(path),
517
+ 'path' => path } }
518
+ else
519
+ {}
453
520
  end
454
521
  end
455
522
 
@@ -490,16 +557,28 @@ module Bolt
490
557
  end
491
558
  end
492
559
 
493
- def generate_types
560
+ def generate_types(cache: false)
494
561
  require 'puppet/face/generate'
495
562
  in_bolt_compiler do
496
563
  generator = Puppet::Generate::Type
497
564
  inputs = generator.find_inputs(:pcore)
498
565
  FileUtils.mkdir_p(@resource_types)
566
+ cache_plan_info if @project && cache
499
567
  generator.generate(inputs, @resource_types, true)
500
568
  end
501
569
  end
502
570
 
571
+ def cache_plan_info
572
+ # plan_name is an array here
573
+ plans_info = list_plans(filter_content: false).map do |plan_name,|
574
+ data = get_plan_info(plan_name, with_mtime: true)
575
+ { plan_name => data }
576
+ end.reduce({}, :merge)
577
+
578
+ FileUtils.touch(@project.plan_cache_file)
579
+ File.write(@project.plan_cache_file, plans_info.to_json)
580
+ end
581
+
503
582
  def run_task(task_name, targets, params, executor, inventory, description = nil)
504
583
  in_task_compiler(executor, inventory) do |compiler|
505
584
  params = params.merge('_bolt_api_call' => true, '_catch_errors' => true)
@@ -6,10 +6,10 @@ require 'bolt/pal/yaml_plan/step'
6
6
  module Bolt
7
7
  class PAL
8
8
  class YamlPlan
9
- PLAN_KEYS = Set['parameters', 'steps', 'return', 'version', 'description']
9
+ PLAN_KEYS = Set['parameters', 'private', 'steps', 'return', 'version', 'description']
10
10
  VAR_NAME_PATTERN = /\A[a-z_][a-z0-9_]*\z/.freeze
11
11
 
12
- attr_reader :name, :parameters, :steps, :return, :description
12
+ attr_reader :name, :parameters, :private, :steps, :return, :description
13
13
 
14
14
  def initialize(name, plan)
15
15
  # Top-level plan keys aren't allowed to be Puppet code, so force them
@@ -30,6 +30,12 @@ module Bolt
30
30
  Parameter.new(param, definition)
31
31
  end.freeze
32
32
 
33
+ @private = plan['private']
34
+ unless @private.nil? || @private.is_a?(TrueClass) || @private.is_a?(FalseClass)
35
+ msg = "Plan #{@name} key 'private' must be a boolean, received: #{@private.inspect}"
36
+ raise Bolt::Error.new(msg, "bolt/invalid-plan")
37
+ end
38
+
33
39
  # Validate top level plan keys
34
40
  top_level_keys = plan.keys.to_set
35
41
  unless PLAN_KEYS.superset?(top_level_keys)
@@ -157,13 +157,13 @@ module Bolt
157
157
  if plan.steps.any? { |step| step.body.key?('target') }
158
158
  msg = "The 'target' parameter for YAML plan steps is deprecated and will be removed "\
159
159
  "in a future version of Bolt. Use the 'targets' parameter instead."
160
- Bolt::Logger.deprecation_warning("Using 'target' parameter for YAML plan steps, not 'targets'", msg)
160
+ Bolt::Logger.deprecate("yaml_plan_target", msg)
161
161
  end
162
162
 
163
163
  if plan.steps.any? { |step| step.body.key?('source') }
164
164
  msg = "The 'source' parameter for YAML plan upload steps is deprecated and will be removed "\
165
165
  "in a future version of Bolt. Use the 'upload' parameter instead."
166
- Bolt::Logger.deprecation_warning("Using 'source' parameter for YAML upload steps, not 'upload'", msg)
166
+ Bolt::Logger.deprecate("yaml_plan_source", msg)
167
167
  end
168
168
 
169
169
  plan_result = closure_scope.with_local_scope(args_hash) do |scope|
@@ -30,6 +30,7 @@ module Bolt
30
30
  plan_string = String.new('')
31
31
  plan_string << "# #{plan_object.description}\n" if plan_object.description
32
32
  plan_string << "# WARNING: This is an autogenerated plan. It may not behave as expected.\n"
33
+ plan_string << "# @private #{plan_object.private}\n" unless plan_object.private.nil?
33
34
  plan_string << "#{param_descriptions}\n" unless param_descriptions.empty?
34
35
 
35
36
  plan_string << "plan #{plan_object.name}("
@@ -64,7 +65,11 @@ module Bolt
64
65
  raise Bolt::FileError.new(msg, @plan_path)
65
66
  end
66
67
 
67
- Bolt::PAL::YamlPlan::Loader.from_string(@modulename, file_contents, @plan_path)
68
+ begin
69
+ Bolt::PAL::YamlPlan::Loader.from_string(@modulename, file_contents, @plan_path)
70
+ rescue Puppet::PreformattedError, StandardError => e
71
+ raise PALError.from_preformatted_error(e)
72
+ end
68
73
  end
69
74
 
70
75
  def validate_path
@@ -139,7 +139,7 @@ module Bolt
139
139
  plugins
140
140
  end
141
141
 
142
- RUBY_PLUGINS = %w[task prompt env_var puppetdb].freeze
142
+ RUBY_PLUGINS = %w[task prompt env_var puppetdb puppet_connect_data].freeze
143
143
  BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory
144
144
  yaml env_var gcloud_inventory].freeze
145
145
  DEFAULT_PLUGIN_HOOKS = { 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }.freeze
@@ -170,7 +170,7 @@ module Bolt
170
170
  end
171
171
 
172
172
  def modules
173
- @modules ||= Bolt::Module.discover(@pal.full_modulepath)
173
+ @modules ||= Bolt::Module.discover(@pal.full_modulepath, @config.project)
174
174
  end
175
175
 
176
176
  def add_plugin(plugin)
@@ -297,7 +297,7 @@ module Bolt
297
297
  def resolve_single_reference(reference)
298
298
  plugin_cache = if cache?(reference)
299
299
  cache = Bolt::Plugin::Cache.new(reference,
300
- @config.project.cache_file,
300
+ @config.project.plugin_cache_file,
301
301
  @config.plugin_cache)
302
302
  entry = cache.read_and_clean_cache
303
303
  return entry unless entry.nil?
@@ -7,11 +7,11 @@ require 'bolt/util'
7
7
  module Bolt
8
8
  class Plugin
9
9
  class Cache
10
- attr_reader :reference, :cache_file, :default_config, :id
10
+ attr_reader :reference, :plugin_cache_file, :default_config, :id
11
11
 
12
- def initialize(reference, cache_file, default_config)
12
+ def initialize(reference, plugin_cache_file, default_config)
13
13
  @reference = reference
14
- @cache_file = cache_file
14
+ @plugin_cache_file = plugin_cache_file
15
15
  @default_config = default_config
16
16
  end
17
17
 
@@ -21,7 +21,7 @@ module Bolt
21
21
 
22
22
  # Luckily we don't need to use a serious hash algorithm
23
23
  require 'digest/bubblebabble'
24
- r = reference.select { |k, _| k == '_cache' }.sort.to_s
24
+ r = reference.reject { |k, _| k == '_cache' }.sort.to_s
25
25
  @id = Digest::SHA2.bubblebabble(r)[0..20]
26
26
 
27
27
  unmodified = true
@@ -32,21 +32,21 @@ module Bolt
32
32
  unmodified = false if expired
33
33
  expired
34
34
  end
35
- File.write(cache_file, cache.to_json) unless cache.empty? || unmodified
35
+ File.write(plugin_cache_file, cache.to_json) unless cache.empty? || unmodified
36
36
 
37
37
  cache.dig(id, 'result')
38
38
  end
39
39
 
40
40
  private def cache
41
- @cache ||= Bolt::Util.read_optional_json_file(@cache_file, 'cache')
41
+ @cache ||= Bolt::Util.read_optional_json_file(@plugin_cache_file, 'cache')
42
42
  end
43
43
 
44
44
  def write_cache(result)
45
45
  cache.merge!({ id => { 'result' => result,
46
46
  'mtime' => Time.now,
47
47
  'ttl' => ttl } })
48
- FileUtils.touch(cache_file)
49
- File.write(cache_file, cache.to_json)
48
+ FileUtils.touch(plugin_cache_file)
49
+ File.write(plugin_cache_file, cache.to_json)
50
50
  end
51
51
 
52
52
  def validate
@@ -187,7 +187,7 @@ module Bolt
187
187
  if params.key?('private-key') || params.key?('public-key')
188
188
  message = "pkcs7 keys 'private-key' and 'public-key' have been deprecated and will be "\
189
189
  "removed in a future version of Bolt; use 'private_key' and 'public_key' instead."
190
- Bolt::Logger.deprecation_warning('PKCS7 keys using hyphens, not underscores', message)
190
+ Bolt::Logger.deprecate("pkcs7_params", message)
191
191
  end
192
192
 
193
193
  params['private_key'] = params.delete('private-key') if params.key?('private-key')
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ class Plugin
5
+ class PuppetConnectData
6
+ def initialize(context:, **_opts)
7
+ puppet_connect_data_yaml_path = File.join(context.boltdir, 'puppet_connect_data.yaml')
8
+ @data = Bolt::Util.read_optional_yaml_hash(
9
+ puppet_connect_data_yaml_path,
10
+ 'puppet_connect_data.yaml'
11
+ )
12
+ end
13
+
14
+ def name
15
+ 'puppet_connect_data'
16
+ end
17
+
18
+ def hooks
19
+ %i[resolve_reference validate_resolve_reference]
20
+ end
21
+
22
+ def resolve_reference(opts)
23
+ key = opts['key']
24
+ @data[key]
25
+ end
26
+
27
+ def validate_resolve_reference(opts)
28
+ unless opts['key']
29
+ raise Bolt::ValidationError,
30
+ "puppet_connect_data plugin requires that 'key' be specified"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -12,7 +12,7 @@ module Bolt
12
12
  end
13
13
 
14
14
  TEMPLATE_OPTS = %w[alias config facts features name uri vars].freeze
15
- PLUGIN_OPTS = %w[_plugin query target_mapping].freeze
15
+ PLUGIN_OPTS = %w[_plugin _cache query target_mapping].freeze
16
16
 
17
17
  attr_reader :puppetdb_client
18
18
 
@@ -31,7 +31,7 @@ module Bolt
31
31
  end
32
32
 
33
33
  def warn_missing_fact(certname, fact)
34
- @logger.warn("Could not find fact #{fact} for node #{certname}")
34
+ Bolt::Logger.warn("puppetdb_missing_fact", "Could not find fact #{fact} for node #{certname}")
35
35
  end
36
36
 
37
37
  def fact_path(raw_fact)
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'bolt/config'
5
- require 'bolt/config/validator'
5
+ require 'bolt/validator'
6
6
  require 'bolt/pal'
7
7
  require 'bolt/module'
8
8
 
@@ -12,46 +12,49 @@ module Bolt
12
12
  CONFIG_NAME = 'bolt-project.yaml'
13
13
 
14
14
  attr_reader :path, :data, :config_file, :inventory_file, :hiera_config,
15
- :puppetfile, :rerunfile, :type, :resource_types, :logs, :project_file,
16
- :deprecations, :downloads, :plans_path, :modulepath, :managed_moduledir,
17
- :backup_dir, :cache_file
15
+ :puppetfile, :rerunfile, :type, :resource_types, :project_file,
16
+ :downloads, :plans_path, :modulepath, :managed_moduledir,
17
+ :backup_dir, :plugin_cache_file, :plan_cache_file
18
18
 
19
- def self.default_project(logs = [])
20
- create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user', logs)
19
+ def self.default_project
20
+ create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
21
21
  # If homedir isn't defined use the system config path
22
22
  rescue ArgumentError
23
- create_project(Bolt::Config.system_path, 'system', logs)
23
+ create_project(Bolt::Config.system_path, 'system')
24
24
  end
25
25
 
26
26
  # Search recursively up the directory hierarchy for the Project. Look for a
27
27
  # directory called Boltdir or a file called bolt.yaml (for a control repo
28
28
  # type Project). Otherwise, repeat the check on each directory up the
29
29
  # hierarchy, falling back to the default if we reach the root.
30
- def self.find_boltdir(dir, logs = [], deprecations = [])
30
+ def self.find_boltdir(dir)
31
31
  dir = Pathname.new(dir)
32
32
 
33
33
  if (dir + BOLTDIR_NAME).directory?
34
- create_project(dir + BOLTDIR_NAME, 'embedded', logs)
34
+ create_project(dir + BOLTDIR_NAME, 'embedded')
35
35
  elsif (dir + 'bolt.yaml').file?
36
36
  command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
37
- msg = "Configuration file #{dir + 'bolt.yaml'} is deprecated and will be "\
37
+ Bolt::Logger.deprecate(
38
+ "bolt_yaml",
39
+ "Configuration file #{dir + 'bolt.yaml'} is deprecated and will be "\
38
40
  "removed in Bolt 3.0.\nUpdate your Bolt project to the latest Bolt practices "\
39
- "using #{command}"
40
- deprecations << { type: "Project level bolt.yaml",
41
- msg: msg }
42
- create_project(dir, 'local', logs, deprecations)
41
+ "using #{command}."
42
+ )
43
+ create_project(dir, 'local')
43
44
  elsif (dir + CONFIG_NAME).file?
44
- create_project(dir, 'local', logs)
45
+ create_project(dir, 'local')
45
46
  elsif dir.root?
46
- default_project(logs)
47
+ default_project
47
48
  else
48
- logs << { debug: "Did not detect Boltdir, bolt.yaml, or bolt-project.yaml at '#{dir}'. "\
49
- "This directory won't be loaded as a project." }
50
- find_boltdir(dir.parent, logs, deprecations)
49
+ Bolt::Logger.debug(
50
+ "Did not detect Boltdir, bolt.yaml, or bolt-project.yaml at '#{dir}'. "\
51
+ "This directory won't be loaded as a project."
52
+ )
53
+ find_boltdir(dir.parent)
51
54
  end
52
55
  end
53
56
 
54
- def self.create_project(path, type = 'option', logs = [], deprecations = [])
57
+ def self.create_project(path, type = 'option')
55
58
  fullpath = Pathname.new(path).expand_path
56
59
 
57
60
  if type == 'user'
@@ -59,8 +62,11 @@ module Bolt
59
62
  # This is already expanded if the type is user
60
63
  FileUtils.mkdir_p(path)
61
64
  rescue StandardError
62
- logs << { warn: "Could not create default project at #{path}. Continuing without a writeable project. "\
63
- "Log and rerun files will not be written." }
65
+ Bolt::Logger.warn(
66
+ "non_writeable_project",
67
+ "Could not create default project at #{path}. Continuing without a writeable project. "\
68
+ "Log and rerun files will not be written."
69
+ )
64
70
  end
65
71
  end
66
72
 
@@ -81,36 +87,40 @@ module Bolt
81
87
  default = type =~ /user|system/ ? 'default ' : ''
82
88
  exist = File.exist?(File.expand_path(project_file))
83
89
 
84
- logs << { info: "Loaded #{default}project from '#{fullpath}'" } if exist
85
-
86
- # Validate the config against the schema. This will raise a single error
87
- # with all validation errors.
88
- schema = Bolt::Config::OPTIONS.slice(*Bolt::Config::BOLT_PROJECT_OPTIONS)
90
+ if exist
91
+ Bolt::Logger.info("Loaded #{default}project from '#{fullpath}'")
92
+ end
89
93
 
90
- Bolt::Config::Validator.new.tap do |validator|
94
+ Bolt::Validator.new.tap do |validator|
91
95
  validator.validate(data, schema, project_file)
92
-
93
- validator.warnings.each { |warning| logs << { warn: warning } }
94
-
95
- validator.deprecations.each do |dep|
96
- deprecations << { type: "#{CONFIG_NAME} #{dep[:option]}", msg: dep[:message] }
97
- end
96
+ validator.warnings.each { |warning| Bolt::Logger.warn(warning[:id], warning[:msg]) }
97
+ validator.deprecations.each { |dep| Bolt::Logger.deprecate(dep[:id], dep[:msg]) }
98
98
  end
99
99
 
100
- new(data, path, type, logs, deprecations)
100
+ new(data, path, type)
101
+ end
102
+
103
+ # Builds the schema for bolt-project.yaml used by the validator.
104
+ #
105
+ def self.schema
106
+ {
107
+ type: Hash,
108
+ properties: Bolt::Config::BOLT_PROJECT_OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
109
+ definitions: Bolt::Config::OPTIONS
110
+ }
101
111
  end
102
112
 
103
- def initialize(raw_data, path, type = 'option', logs = [], deprecations = [])
113
+ def initialize(raw_data, path, type = 'option')
104
114
  @path = Pathname.new(path).expand_path
105
115
  @project_file = @path + CONFIG_NAME
106
- @logs = logs
107
- @deprecations = deprecations
108
116
 
109
117
  if (@path + 'bolt.yaml').file? && project_file?
110
- msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
118
+ Bolt::Logger.deprecate(
119
+ "bolt_yaml",
120
+ "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
111
121
  "Transport config should be set in inventory.yaml, all other config should be set in "\
112
122
  "bolt-project.yaml."
113
- @deprecations << { type: 'Using bolt.yaml for project configuration', msg: msg }
123
+ )
114
124
  end
115
125
 
116
126
  @inventory_file = @path + 'inventory.yaml'
@@ -123,12 +133,14 @@ module Bolt
123
133
  @plans_path = @path + 'plans'
124
134
  @managed_moduledir = @path + '.modules'
125
135
  @backup_dir = @path + '.bolt-bak'
126
- @cache_file = @path + '.plugin_cache.json'
136
+ @plugin_cache_file = @path + '.plugin_cache.json'
137
+ @plan_cache_file = @path + '.plan_cache.json'
127
138
 
128
- tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
129
- if tc.any?
130
- msg = "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}"
131
- @logs << { warn: msg }
139
+ if (tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys).any?
140
+ Bolt::Logger.warn(
141
+ "project_transport_config",
142
+ "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}."
143
+ )
132
144
  end
133
145
 
134
146
  @data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
@@ -145,8 +157,10 @@ module Bolt
145
157
  # and replaced with .project_file in lib/bolt/config.rb
146
158
  @config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
147
159
  if (@path + 'bolt.yaml').file?
148
- msg = "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
149
- @logs << { warn: msg }
160
+ Bolt::Logger.warn(
161
+ "project_config_conflict",
162
+ "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
163
+ )
150
164
  end
151
165
  @project_file
152
166
  else
@@ -196,6 +210,14 @@ module Bolt
196
210
  @data['plugin-cache']
197
211
  end
198
212
 
213
+ def module_install
214
+ @data['module-install']
215
+ end
216
+
217
+ def disable_warnings
218
+ @data['disable-warnings'] || []
219
+ end
220
+
199
221
  def modules
200
222
  @modules ||= @data['modules']&.map do |mod|
201
223
  if mod.is_a?(String)
@@ -217,16 +239,20 @@ module Bolt
217
239
  raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
218
240
  "with a built-in Bolt module of the same name."
219
241
  end
220
- else
242
+ elsif name.nil? &&
243
+ (File.directory?(plans_path) ||
244
+ File.directory?(@path + 'tasks') ||
245
+ File.directory?(@path + 'files'))
221
246
  message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
222
- @logs << { warn: message }
247
+
248
+ Bolt::Logger.warn("missing_project_name", message)
223
249
  end
224
250
  end
225
251
 
226
252
  def check_deprecated_file
227
253
  if (@path + 'project.yaml').file?
228
254
  msg = "Project configuration file 'project.yaml' is deprecated; use 'bolt-project.yaml' instead."
229
- Bolt::Logger.deprecation_warning('Using project.yaml instead of bolt-project.yaml', msg)
255
+ Bolt::Logger.warn("project_yaml", msg)
230
256
  end
231
257
  end
232
258
  end