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.
- checksums.yaml +4 -4
- data/Puppetfile +8 -8
- data/lib/bolt/bolt_option_parser.rb +7 -3
- data/lib/bolt/cli.rb +67 -23
- data/lib/bolt/config.rb +70 -45
- data/lib/bolt/config/options.rb +104 -79
- data/lib/bolt/config/transport/base.rb +2 -2
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/options.rb +11 -68
- data/lib/bolt/config/transport/ssh.rb +0 -5
- data/lib/bolt/inventory.rb +26 -0
- data/lib/bolt/inventory/group.rb +29 -9
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/inventory/options.rb +130 -0
- data/lib/bolt/inventory/target.rb +10 -11
- data/lib/bolt/module.rb +10 -2
- data/lib/bolt/module_installer.rb +21 -13
- data/lib/bolt/module_installer/resolver.rb +13 -5
- data/lib/bolt/outputter.rb +19 -5
- data/lib/bolt/outputter/human.rb +20 -1
- data/lib/bolt/outputter/json.rb +1 -1
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/outputter/rainbow.rb +12 -1
- data/lib/bolt/pal/yaml_plan/transpiler.rb +5 -1
- data/lib/bolt/plugin.rb +42 -6
- data/lib/bolt/plugin/cache.rb +76 -0
- data/lib/bolt/plugin/module.rb +4 -4
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +38 -13
- data/lib/bolt/project_manager.rb +2 -0
- data/lib/bolt/project_manager/config_migrator.rb +9 -1
- data/lib/bolt/project_manager/module_migrator.rb +2 -0
- data/lib/bolt/puppetdb/client.rb +8 -0
- data/lib/bolt/rerun.rb +1 -5
- data/lib/bolt/shell/bash.rb +7 -1
- data/lib/bolt/shell/powershell.rb +21 -3
- data/lib/bolt/target.rb +4 -0
- data/lib/bolt/transport/local.rb +13 -0
- data/lib/bolt/util.rb +22 -0
- data/lib/bolt/validator.rb +227 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/plugin.rb +13 -0
- data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
- data/lib/bolt_server/schemas/connect-data.json +22 -0
- data/lib/bolt_server/schemas/partials/task.json +1 -1
- data/lib/bolt_server/transport_app.rb +64 -36
- metadata +24 -5
- data/lib/bolt/config/validator.rb +0 -231
data/lib/bolt/version.rb
CHANGED
@@ -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
|
+
}
|
@@ -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
|
-
|
52
|
-
400, Bolt::ValidationError.new('`
|
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(
|
269
|
-
project_dir = File.join(@config['projects-dir'],
|
270
|
-
raise Bolt::ValidationError, "`
|
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(
|
278
|
+
def in_bolt_project(versioned_project)
|
276
279
|
@pal_mutex.synchronize do
|
277
|
-
bolt_config = config_from_project(
|
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
|
517
|
+
# @param versioned_project [String] the project to fetch the plan from
|
517
518
|
get '/project_plans/:module_name/:plan_name' do
|
518
|
-
return
|
519
|
-
in_bolt_project(params['
|
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
|
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
|
544
|
-
in_bolt_project(params['
|
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['
|
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
|
585
|
+
# @param versioned_project [String] the project to fetch the list of plans from
|
581
586
|
get '/project_plans' do
|
582
|
-
return
|
583
|
-
in_bolt_project(params['
|
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
|
621
|
+
# @param versioned_project [String] the project to fetch the list of tasks from
|
615
622
|
get '/project_tasks' do
|
616
|
-
return
|
617
|
-
in_bolt_project(params['
|
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
|
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
|
636
|
-
in_bolt_project(params['
|
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
|
658
|
+
# @param versioned_project [String] the versioned_project to compute the inventory from
|
648
659
|
post '/project_inventory_targets' do
|
649
|
-
return
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
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
|
-
|
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.
|
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:
|
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.
|
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.
|
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
|