bolt 2.36.0 → 2.42.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +8 -8
  3. data/lib/bolt/bolt_option_parser.rb +7 -3
  4. data/lib/bolt/cli.rb +67 -23
  5. data/lib/bolt/config.rb +70 -45
  6. data/lib/bolt/config/options.rb +104 -79
  7. data/lib/bolt/config/transport/base.rb +2 -2
  8. data/lib/bolt/config/transport/local.rb +1 -0
  9. data/lib/bolt/config/transport/options.rb +11 -68
  10. data/lib/bolt/config/transport/ssh.rb +0 -5
  11. data/lib/bolt/inventory.rb +26 -0
  12. data/lib/bolt/inventory/group.rb +29 -9
  13. data/lib/bolt/inventory/inventory.rb +1 -1
  14. data/lib/bolt/inventory/options.rb +130 -0
  15. data/lib/bolt/inventory/target.rb +10 -11
  16. data/lib/bolt/module.rb +10 -2
  17. data/lib/bolt/module_installer.rb +21 -13
  18. data/lib/bolt/module_installer/resolver.rb +13 -5
  19. data/lib/bolt/outputter.rb +19 -5
  20. data/lib/bolt/outputter/human.rb +20 -1
  21. data/lib/bolt/outputter/json.rb +1 -1
  22. data/lib/bolt/outputter/logger.rb +1 -1
  23. data/lib/bolt/outputter/rainbow.rb +12 -1
  24. data/lib/bolt/pal/yaml_plan/transpiler.rb +5 -1
  25. data/lib/bolt/plugin.rb +42 -6
  26. data/lib/bolt/plugin/cache.rb +76 -0
  27. data/lib/bolt/plugin/module.rb +4 -4
  28. data/lib/bolt/plugin/puppetdb.rb +1 -1
  29. data/lib/bolt/project.rb +38 -13
  30. data/lib/bolt/project_manager.rb +2 -0
  31. data/lib/bolt/project_manager/config_migrator.rb +9 -1
  32. data/lib/bolt/project_manager/module_migrator.rb +2 -0
  33. data/lib/bolt/puppetdb/client.rb +8 -0
  34. data/lib/bolt/rerun.rb +1 -5
  35. data/lib/bolt/shell/bash.rb +7 -1
  36. data/lib/bolt/shell/powershell.rb +21 -3
  37. data/lib/bolt/target.rb +4 -0
  38. data/lib/bolt/transport/local.rb +13 -0
  39. data/lib/bolt/util.rb +22 -0
  40. data/lib/bolt/validator.rb +227 -0
  41. data/lib/bolt/version.rb +1 -1
  42. data/lib/bolt_server/plugin.rb +13 -0
  43. data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
  44. data/lib/bolt_server/schemas/connect-data.json +22 -0
  45. data/lib/bolt_server/schemas/partials/task.json +1 -1
  46. data/lib/bolt_server/transport_app.rb +64 -36
  47. metadata +24 -5
  48. data/lib/bolt/config/validator.rb +0 -231
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.36.0'
4
+ VERSION = '2.42.0'
5
5
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ module BoltServer
6
+ class Plugin
7
+ class PluginNotSupported < Bolt::Error
8
+ def initialize(msg, plugin_name)
9
+ super(msg, 'bolt/plugin-not-supported', { "plugin_name" => plugin_name })
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BoltServer
4
+ class Plugin
5
+ class PuppetConnectData
6
+ def initialize(data, **_opts)
7
+ @data = data
8
+ end
9
+
10
+ def name
11
+ 'puppet_connect_data'
12
+ end
13
+
14
+ def hooks
15
+ %i[resolve_reference validate_resolve_reference]
16
+ end
17
+
18
+ def resolve_reference(opts)
19
+ key = opts['key']
20
+
21
+ @data.dig(key, 'value')
22
+ end
23
+
24
+ def validate_resolve_reference(opts)
25
+ unless opts['key']
26
+ raise Bolt::ValidationError,
27
+ "puppet_connect_data plugin requires that 'key' be specified"
28
+ end
29
+
30
+ unless @data.key?(opts['key'])
31
+ raise Bolt::ValidationError,
32
+ "puppet_connect_data plugin tried to lookup key '#{opts['key']}' but no value was found"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "project_inventory_targets connect plugin data",
4
+ "description": "POST project_inventory_targets connect plugin data",
5
+ "type": "object",
6
+ "properties": {
7
+ "puppet_connect_data": {
8
+ "type": "object",
9
+ "patternProperties": {
10
+ ".*": {
11
+ "type": "object",
12
+ "properties": {
13
+ "value": {}
14
+ },
15
+ "required": ["value"]
16
+ }
17
+ },
18
+ "additionalProperties": false
19
+ }
20
+ },
21
+ "required": ["puppet_connect_data"]
22
+ }
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "input_method": {
39
39
  "type": "string",
40
- "enum": ["stdin", "environment", "powershell"],
40
+ "enum": ["stdin", "environment", "powershell", "both"],
41
41
  "description": "What input method should be used to pass params to the task"
42
42
  }
43
43
  }
@@ -8,6 +8,8 @@ require 'bolt/inventory'
8
8
  require 'bolt/project'
9
9
  require 'bolt/target'
10
10
  require 'bolt_server/file_cache'
11
+ require 'bolt_server/plugin'
12
+ require 'bolt_server/plugin/puppet_connect_data'
11
13
  require 'bolt/task/puppet_server'
12
14
  require 'json'
13
15
  require 'json-schema'
@@ -35,6 +37,7 @@ module BoltServer
35
37
  action-upload_file
36
38
  transport-ssh
37
39
  transport-winrm
40
+ connect-data
38
41
  ].freeze
39
42
 
40
43
  # PE_BOLTLIB_PATH is intended to function exactly like the BOLTLIB_PATH used
@@ -48,8 +51,8 @@ module BoltServer
48
51
  # See the `orchestrator.bolt.codedir` tk config setting.
49
52
  DEFAULT_BOLT_CODEDIR = '/opt/puppetlabs/server/data/orchestration-services/code'
50
53
 
51
- MISSING_PROJECT_REF_RESPONSE = [
52
- 400, Bolt::ValidationError.new('`project_ref` is a required argument').to_json
54
+ MISSING_VERSIONED_PROJECT_RESPONSE = [
55
+ 400, Bolt::ValidationError.new('`versioned_project` is a required argument').to_json
53
56
  ].freeze
54
57
 
55
58
  def initialize(config)
@@ -265,16 +268,16 @@ module BoltServer
265
268
  end
266
269
  end
267
270
 
268
- def config_from_project(project_ref)
269
- project_dir = File.join(@config['projects-dir'], project_ref)
270
- raise Bolt::ValidationError, "`project_ref`: #{project_dir} does not exist" unless Dir.exist?(project_dir)
271
+ def config_from_project(versioned_project)
272
+ project_dir = File.join(@config['projects-dir'], versioned_project)
273
+ raise Bolt::ValidationError, "`versioned_project`: #{project_dir} does not exist" unless Dir.exist?(project_dir)
271
274
  project = Bolt::Project.create_project(project_dir)
272
275
  Bolt::Config.from_project(project, { log: { 'bolt-debug.log' => 'disable' } })
273
276
  end
274
277
 
275
- def in_bolt_project(project_ref)
278
+ def in_bolt_project(versioned_project)
276
279
  @pal_mutex.synchronize do
277
- bolt_config = config_from_project(project_ref)
280
+ bolt_config = config_from_project(versioned_project)
278
281
  modulepath_object = Bolt::Config::Modulepath.new(
279
282
  bolt_config.modulepath,
280
283
  boltlib_path: [PE_BOLTLIB_PATH, Bolt::Config::Modulepath::BOLTLIB_PATH]
@@ -285,8 +288,6 @@ module BoltServer
285
288
  config: bolt_config
286
289
  }
287
290
  yield context
288
- rescue Bolt::Error => e
289
- [400, e.to_json]
290
291
  end
291
292
  end
292
293
 
@@ -513,14 +514,16 @@ module BoltServer
513
514
 
514
515
  # Fetches the metadata for a single plan
515
516
  #
516
- # @param project_ref [String] the project to fetch the plan from
517
+ # @param versioned_project [String] the project to fetch the plan from
517
518
  get '/project_plans/:module_name/:plan_name' do
518
- return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
519
- in_bolt_project(params['project_ref']) do |context|
519
+ return MISSING_VERSIONED_PROJECT_RESPONSE if params['versioned_project'].nil?
520
+ in_bolt_project(params['versioned_project']) do |context|
520
521
  plan_info = pe_plan_info(context[:pal], params[:module_name], params[:plan_name])
521
522
  plan_info = allowed_helper(plan_info, context[:config].project.plans)
522
523
  [200, plan_info.to_json]
523
524
  end
525
+ rescue Bolt::Error => e
526
+ [400, e.to_json]
524
527
  end
525
528
 
526
529
  # Fetches the metadata for a single task
@@ -538,17 +541,19 @@ module BoltServer
538
541
 
539
542
  # Fetches the metadata for a single task
540
543
  #
541
- # @param bolt_project_ref [String] the reference to the bolt-project directory to load task metadata from
544
+ # @param bolt_versioned_project [String] the reference to the bolt-project directory to load task metadata from
542
545
  get '/project_tasks/:module_name/:task_name' do
543
- return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
544
- in_bolt_project(params['project_ref']) do |context|
546
+ return MISSING_VERSIONED_PROJECT_RESPONSE if params['versioned_project'].nil?
547
+ in_bolt_project(params['versioned_project']) do |context|
545
548
  ps_parameters = {
546
- 'versioned_project' => params['project_ref']
549
+ 'versioned_project' => params['versioned_project']
547
550
  }
548
551
  task_info = pe_task_info(context[:pal], params[:module_name], params[:task_name], ps_parameters)
549
552
  task_info = allowed_helper(task_info, context[:config].project.tasks)
550
553
  [200, task_info.to_json]
551
554
  end
555
+ rescue Bolt::Error => e
556
+ [400, e.to_json]
552
557
  end
553
558
 
554
559
  # Fetches the list of plans for an environment, optionally fetching all metadata for each plan
@@ -577,10 +582,10 @@ module BoltServer
577
582
 
578
583
  # Fetches the list of plans for a project
579
584
  #
580
- # @param project_ref [String] the project to fetch the list of plans from
585
+ # @param versioned_project [String] the project to fetch the list of plans from
581
586
  get '/project_plans' do
582
- return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
583
- in_bolt_project(params['project_ref']) do |context|
587
+ return MISSING_VERSIONED_PROJECT_RESPONSE if params['versioned_project'].nil?
588
+ in_bolt_project(params['versioned_project']) do |context|
584
589
  plans_response = plan_list(context[:pal])
585
590
 
586
591
  # Dig in context for the allowlist of plans from project object
@@ -592,6 +597,8 @@ module BoltServer
592
597
  # to bolt-server smaller/simpler.
593
598
  [200, plans_response.to_json]
594
599
  end
600
+ rescue Bolt::Error => e
601
+ [400, e.to_json]
595
602
  end
596
603
 
597
604
  # Fetches the list of tasks for an environment
@@ -611,10 +618,10 @@ module BoltServer
611
618
 
612
619
  # Fetches the list of tasks for a bolt-project
613
620
  #
614
- # @param project_ref [String] the project to fetch the list of tasks from
621
+ # @param versioned_project [String] the project to fetch the list of tasks from
615
622
  get '/project_tasks' do
616
- return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
617
- in_bolt_project(params['project_ref']) do |context|
623
+ return MISSING_VERSIONED_PROJECT_RESPONSE if params['versioned_project'].nil?
624
+ in_bolt_project(params['versioned_project']) do |context|
618
625
  tasks_response = task_list(context[:pal])
619
626
 
620
627
  # Dig in context for the allowlist of tasks from project object
@@ -626,37 +633,58 @@ module BoltServer
626
633
  # to bolt-server smaller/simpler.
627
634
  [200, tasks_response.to_json]
628
635
  end
636
+ rescue Bolt::Error => e
637
+ [400, e.to_json]
629
638
  end
630
639
 
631
640
  # Implements puppetserver's file_metadatas endpoint for projects.
632
641
  #
633
- # @param project_ref [String] the project_ref to fetch the file metadatas from
642
+ # @param versioned_project [String] the versioned_project to fetch the file metadatas from
634
643
  get '/project_file_metadatas/:module_name/*' do
635
- return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
636
- in_bolt_project(params['project_ref']) do |context|
644
+ return MISSING_VERSIONED_PROJECT_RESPONSE if params['versioned_project'].nil?
645
+ in_bolt_project(params['versioned_project']) do |context|
637
646
  file = params[:splat].first
638
647
  metadatas = file_metadatas(context[:pal], params[:module_name], file)
639
648
  [200, metadatas.to_json]
640
649
  end
650
+ rescue Bolt::Error => e
651
+ [400, e.to_json]
641
652
  rescue ArgumentError => e
642
653
  [400, e.message]
643
654
  end
644
655
 
645
656
  # Returns a list of targets parsed from a Project inventory
646
657
  #
647
- # @param project_ref [String] the project_ref to compute the inventory from
658
+ # @param versioned_project [String] the versioned_project to compute the inventory from
648
659
  post '/project_inventory_targets' do
649
- return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
650
- bolt_config = config_from_project(params['project_ref'])
651
- if bolt_config.inventoryfile && bolt_config.project.inventory_file.to_s != bolt_config.inventoryfile
652
- raise Bolt::ValidationError, "Project inventory must be defined in the " \
653
- "inventory.yaml file at the root of the project directory"
654
- end
655
- plugins = Bolt::Plugin.setup(bolt_config, nil)
656
- inventory = Bolt::Inventory.from_config(bolt_config, plugins)
657
- target_list = inventory.get_targets('all').map { |targ| targ.to_h.merge({ 'transport' => targ.transport }) }
660
+ return MISSING_VERSIONED_PROJECT_RESPONSE if params['versioned_project'].nil?
661
+ content_type :json
662
+ body = JSON.parse(request.body.read)
663
+ error = validate_schema(@schemas["connect-data"], body)
664
+ return [400, error_result(error).to_json] unless error.nil?
665
+ in_bolt_project(params['versioned_project']) do |context|
666
+ if context[:config].inventoryfile &&
667
+ context[:config].project.inventory_file.to_s !=
668
+ context[:config].inventoryfile
669
+ raise Bolt::ValidationError, "Project inventory must be defined in the " \
670
+ "inventory.yaml file at the root of the project directory"
671
+ end
658
672
 
659
- [200, target_list.to_json]
673
+ Bolt::Util.validate_file('inventory file', context[:config].project.inventory_file)
674
+
675
+ begin
676
+ connect_plugin = BoltServer::Plugin::PuppetConnectData.new(body['puppet_connect_data'])
677
+ plugins = Bolt::Plugin.setup(context[:config], context[:pal], load_plugins: false)
678
+ plugins.add_plugin(connect_plugin)
679
+ inventory = Bolt::Inventory.from_config(context[:config], plugins)
680
+ target_list = inventory.get_targets('all').map { |targ| targ.to_h.merge({ 'transport' => targ.transport }) }
681
+ rescue Bolt::Plugin::PluginError::LoadingDisabled => e
682
+ msg = "Cannot load plugin #{e.details['plugin_name']}: plugin not supported"
683
+ raise BoltServer::Plugin::PluginNotSupported.new(msg, e.details['plugin_name'])
684
+ end
685
+
686
+ [200, target_list.to_json]
687
+ end
660
688
  rescue Bolt::Error => e
661
689
  [500, e.to_json]
662
690
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.36.0
4
+ version: 2.42.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-30 00:00:00.000000000 Z
11
+ date: 2021-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ffi
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.14.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.14.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: hiera-eyaml
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -198,14 +212,14 @@ dependencies:
198
212
  requirements:
199
213
  - - "~>"
200
214
  - !ruby/object:Gem::Version
201
- version: '0.4'
215
+ version: '0.5'
202
216
  type: :runtime
203
217
  prerelease: false
204
218
  version_requirements: !ruby/object:Gem::Requirement
205
219
  requirements:
206
220
  - - "~>"
207
221
  - !ruby/object:Gem::Version
208
- version: '0.4'
222
+ version: '0.5'
209
223
  - !ruby/object:Gem::Dependency
210
224
  name: puppet-resource_api
211
225
  requirement: !ruby/object:Gem::Requirement
@@ -458,12 +472,12 @@ files:
458
472
  - lib/bolt/config/transport/remote.rb
459
473
  - lib/bolt/config/transport/ssh.rb
460
474
  - lib/bolt/config/transport/winrm.rb
461
- - lib/bolt/config/validator.rb
462
475
  - lib/bolt/error.rb
463
476
  - lib/bolt/executor.rb
464
477
  - lib/bolt/inventory.rb
465
478
  - lib/bolt/inventory/group.rb
466
479
  - lib/bolt/inventory/inventory.rb
480
+ - lib/bolt/inventory/options.rb
467
481
  - lib/bolt/inventory/target.rb
468
482
  - lib/bolt/logger.rb
469
483
  - lib/bolt/module.rb
@@ -505,6 +519,7 @@ files:
505
519
  - lib/bolt/plan_creator.rb
506
520
  - lib/bolt/plan_result.rb
507
521
  - lib/bolt/plugin.rb
522
+ - lib/bolt/plugin/cache.rb
508
523
  - lib/bolt/plugin/env_var.rb
509
524
  - lib/bolt/plugin/module.rb
510
525
  - lib/bolt/plugin/prompt.rb
@@ -550,17 +565,21 @@ files:
550
565
  - lib/bolt/transport/winrm/connection.rb
551
566
  - lib/bolt/util.rb
552
567
  - lib/bolt/util/puppet_log_level.rb
568
+ - lib/bolt/validator.rb
553
569
  - lib/bolt/version.rb
554
570
  - lib/bolt/yarn.rb
555
571
  - lib/bolt_server/acl.rb
556
572
  - lib/bolt_server/base_config.rb
557
573
  - lib/bolt_server/config.rb
558
574
  - lib/bolt_server/file_cache.rb
575
+ - lib/bolt_server/plugin.rb
576
+ - lib/bolt_server/plugin/puppet_connect_data.rb
559
577
  - lib/bolt_server/schemas/action-check_node_connections.json
560
578
  - lib/bolt_server/schemas/action-run_command.json
561
579
  - lib/bolt_server/schemas/action-run_script.json
562
580
  - lib/bolt_server/schemas/action-run_task.json
563
581
  - lib/bolt_server/schemas/action-upload_file.json
582
+ - lib/bolt_server/schemas/connect-data.json
564
583
  - lib/bolt_server/schemas/partials/target-any.json
565
584
  - lib/bolt_server/schemas/partials/target-ssh.json
566
585
  - lib/bolt_server/schemas/partials/target-winrm.json
@@ -1,231 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bolt/error'
4
-
5
- # This class validates config against a schema, raising an error that includes
6
- # details about any invalid configuration.
7
- #
8
- module Bolt
9
- class Config
10
- class Validator
11
- attr_reader :deprecations, :warnings
12
-
13
- def initialize
14
- @errors = []
15
- @deprecations = []
16
- @warnings = []
17
- @path = []
18
- end
19
-
20
- # This is the entry method for validating data against the schema.
21
- # It loops over each key-value pair in the data hash and validates
22
- # the value against the relevant schema definition.
23
- #
24
- def validate(data, schema, location = nil)
25
- @location = location
26
-
27
- validate_keys(data.keys, schema.keys)
28
-
29
- data.each_pair do |key, value|
30
- next unless schema.key?(key)
31
-
32
- @path.push(key)
33
-
34
- check_deprecated(key, schema[key], location)
35
- validate_value(value, schema[key])
36
- ensure
37
- @path.pop
38
- end
39
-
40
- raise_error
41
- end
42
-
43
- # Adds a warning if the given option is deprecated.
44
- #
45
- def check_deprecated(key, definition, location)
46
- if definition.key?(:_deprecation)
47
- message = "Option '#{path}' "
48
- message += "at #{location} " if location
49
- message += "is deprecated. #{definition[:_deprecation]}"
50
- @deprecations << { option: key, message: message }
51
- end
52
- end
53
-
54
- # Raises a ValidationError if there are any errors. All error messages
55
- # created during validation are concatenated into a single error
56
- # message.
57
- #
58
- private def raise_error
59
- return unless @errors.any?
60
-
61
- message = "Invalid configuration"
62
- message += " at #{@location}" if @location
63
- message += ":\n"
64
- message += @errors.map { |error| "\s\s#{error}" }.join("\n")
65
-
66
- raise Bolt::ValidationError, message
67
- end
68
-
69
- # Validate an individual value. This performs validation that is
70
- # common to all values, including type validation. After validating
71
- # the value's type, the value is passed off to an individual
72
- # validation method for the value's type.
73
- #
74
- private def validate_value(value, definition)
75
- return if plugin_reference?(value, definition)
76
- return unless valid_type?(value, definition)
77
-
78
- case value
79
- when Hash
80
- validate_hash(value, definition)
81
- when Array
82
- validate_array(value, definition)
83
- when String
84
- validate_string(value, definition)
85
- when Numeric
86
- validate_number(value, definition)
87
- end
88
- end
89
-
90
- # Validates a hash value, logging errors for any validations that fail.
91
- # This will enumerate each key-value pair in the hash and validate each
92
- # value individually.
93
- #
94
- private def validate_hash(value, definition)
95
- properties = definition[:properties] ? definition[:properties].keys : []
96
-
97
- if definition[:properties] && definition[:additionalProperties].nil?
98
- validate_keys(value.keys, properties)
99
- end
100
-
101
- if definition[:required] && (definition[:required] - value.keys).any?
102
- missing = definition[:required] - value.keys
103
- @errors << "Value at '#{path}' is missing required keys #{missing.join(', ')}"
104
- end
105
-
106
- value.each_pair do |key, val|
107
- @path.push(key)
108
-
109
- if properties.include?(key)
110
- validate_value(val, definition[:properties][key])
111
- elsif definition[:additionalProperties]
112
- validate_value(val, definition[:additionalProperties])
113
- end
114
- ensure
115
- @path.pop
116
- end
117
- end
118
-
119
- # Validates an array value, logging errors for any validations that fail.
120
- # This will enumerate the items in the array and validate each item
121
- # individually.
122
- #
123
- private def validate_array(value, definition)
124
- if definition[:uniqueItems] && value.size != value.uniq.size
125
- @errors << "Value at '#{path}' must not include duplicate elements"
126
- return
127
- end
128
-
129
- return unless definition.key?(:items)
130
-
131
- value.each_with_index do |item, index|
132
- @path.push(index)
133
- validate_value(item, definition[:items])
134
- ensure
135
- @path.pop
136
- end
137
- end
138
-
139
- # Validates a string value, logging errors for any validations that fail.
140
- #
141
- private def validate_string(value, definition)
142
- if definition.key?(:enum) && !definition[:enum].include?(value)
143
- message = "Value at '#{path}' must be "
144
- message += "one of " if definition[:enum].count > 1
145
- message += definition[:enum].join(', ')
146
- multitype_error(message, value, definition)
147
- end
148
- end
149
-
150
- # Validates a numeric value, logging errors for any validations that fail.
151
- #
152
- private def validate_number(value, definition)
153
- if definition.key?(:minimum) && value < definition[:minimum]
154
- @errors << "Value at '#{path}' must be a minimum of #{definition[:minimum]}"
155
- end
156
- end
157
-
158
- # Adds warnings for unknown config options.
159
- #
160
- private def validate_keys(keys, known_keys)
161
- (keys - known_keys).each do |key|
162
- message = "Unknown option '#{key}'"
163
- message += " at '#{path}'" if @path.any?
164
- message += " at #{@location}" if @location
165
- message += "."
166
- @warnings << message
167
- end
168
- end
169
-
170
- # Returns true if a value is a plugin reference. This also validates whether
171
- # a value can be a plugin reference in the first place. If the value is a
172
- # plugin reference but cannot be one according to the schema, then this will
173
- # log an error.
174
- #
175
- private def plugin_reference?(value, definition)
176
- if value.is_a?(Hash) && value.key?('_plugin')
177
- unless definition[:_plugin]
178
- @errors << "Value at '#{path}' is a plugin reference, which is unsupported at "\
179
- "this location"
180
- end
181
-
182
- true
183
- else
184
- false
185
- end
186
- end
187
-
188
- # Asserts the type for each option against the type specified in the schema
189
- # definition. The schema definition can specify multiple valid types, so the
190
- # value needs to only match one of the types to be valid. Returns early if
191
- # there is no type in the definition (in practice this shouldn't happen, but
192
- # this will safeguard against any dev mistakes).
193
- #
194
- private def valid_type?(value, definition)
195
- return unless definition.key?(:type)
196
-
197
- types = Array(definition[:type])
198
-
199
- if types.include?(value.class)
200
- true
201
- else
202
- if types.include?(TrueClass) || types.include?(FalseClass)
203
- types = types - [TrueClass, FalseClass] + ['Boolean']
204
- end
205
-
206
- @errors << "Value at '#{path}' must be of type #{types.join(' or ')}"
207
-
208
- false
209
- end
210
- end
211
-
212
- # Adds an error that includes additional helpful information for values
213
- # that accept multiple types.
214
- #
215
- private def multitype_error(message, value, definition)
216
- if Array(definition[:type]).count > 1
217
- types = Array(definition[:type]) - [value.class]
218
- message += " or must be of type #{types.join(' or ')}"
219
- end
220
-
221
- @errors << message
222
- end
223
-
224
- # Returns the formatted path for the key.
225
- #
226
- private def path
227
- @path.join('.')
228
- end
229
- end
230
- end
231
- end