bolt 2.33.1 → 2.37.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 +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -3
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
- data/lib/bolt/analytics.rb +27 -8
- data/lib/bolt/apply_result.rb +3 -3
- data/lib/bolt/bolt_option_parser.rb +48 -16
- data/lib/bolt/cli.rb +95 -227
- data/lib/bolt/config.rb +145 -54
- data/lib/bolt/config/options.rb +76 -10
- data/lib/bolt/config/transport/base.rb +10 -19
- data/lib/bolt/config/transport/local.rb +0 -7
- data/lib/bolt/config/transport/options.rb +1 -1
- data/lib/bolt/config/transport/ssh.rb +8 -14
- data/lib/bolt/config/validator.rb +231 -0
- data/lib/bolt/error.rb +33 -3
- data/lib/bolt/executor.rb +92 -6
- data/lib/bolt/inventory/group.rb +2 -1
- data/lib/bolt/module_installer.rb +1 -1
- data/lib/bolt/module_installer/specs/forge_spec.rb +5 -4
- data/lib/bolt/module_installer/specs/git_spec.rb +4 -3
- data/lib/bolt/outputter/human.rb +21 -9
- data/lib/bolt/outputter/rainbow.rb +1 -1
- data/lib/bolt/pal.rb +19 -7
- data/lib/bolt/pal/yaml_plan.rb +7 -0
- data/lib/bolt/plan_creator.rb +160 -0
- data/lib/bolt/plugin.rb +42 -13
- data/lib/bolt/plugin/cache.rb +76 -0
- data/lib/bolt/plugin/module.rb +4 -4
- data/lib/bolt/project.rb +46 -40
- data/lib/bolt/project_manager.rb +199 -0
- data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +43 -5
- data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +5 -5
- data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
- data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +3 -3
- data/lib/bolt/puppetdb/client.rb +3 -2
- data/lib/bolt/puppetdb/config.rb +9 -8
- data/lib/bolt/rerun.rb +1 -5
- data/lib/bolt/shell/bash.rb +8 -2
- data/lib/bolt/shell/powershell.rb +17 -1
- data/lib/bolt/task/run.rb +1 -1
- data/lib/bolt/transport/orch.rb +0 -5
- data/lib/bolt/transport/orch/connection.rb +10 -3
- data/lib/bolt/transport/remote.rb +1 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
- data/lib/bolt/util.rb +41 -7
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt/yarn.rb +23 -0
- data/lib/bolt_server/base_config.rb +3 -1
- data/lib/bolt_server/config.rb +3 -1
- data/lib/bolt_server/file_cache.rb +2 -0
- 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 +2 -2
- data/lib/bolt_server/transport_app.rb +72 -13
- data/lib/bolt_spec/plans/mock_executor.rb +4 -1
- data/libexec/apply_catalog.rb +1 -1
- data/libexec/custom_facts.rb +1 -1
- data/libexec/query_resources.rb +1 -1
- metadata +15 -13
- data/lib/bolt/project_migrator.rb +0 -80
data/lib/bolt/inventory/group.rb
CHANGED
@@ -241,10 +241,11 @@ module Bolt
|
|
241
241
|
end
|
242
242
|
|
243
243
|
if input.key?('nodes')
|
244
|
+
command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
|
244
245
|
msg = <<~MSG.chomp
|
245
246
|
Found 'nodes' key in group #{@name}. This looks like a v1 inventory file, which is
|
246
247
|
no longer supported by Bolt. Migrate to a v2 inventory file automatically using
|
247
|
-
'
|
248
|
+
'#{command}'.
|
248
249
|
MSG
|
249
250
|
raise ValidationError.new(msg, nil)
|
250
251
|
end
|
@@ -11,7 +11,7 @@ module Bolt
|
|
11
11
|
class ModuleInstaller
|
12
12
|
class Specs
|
13
13
|
class ForgeSpec
|
14
|
-
NAME_REGEX = %r{\A[a-
|
14
|
+
NAME_REGEX = %r{\A[a-zA-Z0-9]+[-/](?<name>[a-z][a-z0-9_]*)\z}.freeze
|
15
15
|
REQUIRED_KEYS = Set.new(%w[name]).freeze
|
16
16
|
KNOWN_KEYS = Set.new(%w[name version_requirement]).freeze
|
17
17
|
|
@@ -33,8 +33,9 @@ module Bolt
|
|
33
33
|
unless (match = name.match(NAME_REGEX))
|
34
34
|
raise Bolt::ValidationError,
|
35
35
|
"Invalid name for Forge module specification: #{name}. Name must match "\
|
36
|
-
"'owner/name'
|
37
|
-
"lowercase
|
36
|
+
"'owner/name'. Owner segment may only include letters or digits. Name "\
|
37
|
+
"segment must start with a lowercase letter and may only include lowercase "\
|
38
|
+
"letters, digits, and underscores."
|
38
39
|
end
|
39
40
|
|
40
41
|
[name.tr('-', '/'), match[:name]]
|
@@ -54,7 +55,7 @@ module Bolt
|
|
54
55
|
#
|
55
56
|
def satisfied_by?(mod)
|
56
57
|
@type == mod.type &&
|
57
|
-
@full_name == mod.full_name &&
|
58
|
+
@full_name.downcase == mod.full_name.downcase &&
|
58
59
|
!mod.version.nil? &&
|
59
60
|
@semantic_version.cover?(mod.version)
|
60
61
|
end
|
@@ -11,7 +11,7 @@ module Bolt
|
|
11
11
|
class ModuleInstaller
|
12
12
|
class Specs
|
13
13
|
class GitSpec
|
14
|
-
NAME_REGEX = %r{\A(?:[a-
|
14
|
+
NAME_REGEX = %r{\A(?:[a-zA-Z0-9]+[-/])?(?<name>[a-z][a-z0-9_]*)\z}.freeze
|
15
15
|
REQUIRED_KEYS = Set.new(%w[git ref]).freeze
|
16
16
|
|
17
17
|
attr_reader :git, :ref, :type
|
@@ -36,8 +36,9 @@ module Bolt
|
|
36
36
|
unless (match = name.match(NAME_REGEX))
|
37
37
|
raise Bolt::ValidationError,
|
38
38
|
"Invalid name for Git module specification: #{name}. Name must match "\
|
39
|
-
"'name' or 'owner/name'
|
40
|
-
"
|
39
|
+
"'name' or 'owner/name'. Owner segment may only include letters or digits. "\
|
40
|
+
"Name segment must start with a lowercase letter and may only include "\
|
41
|
+
"lowercase letters, digits, and underscores."
|
41
42
|
end
|
42
43
|
|
43
44
|
match[:name]
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -214,9 +214,10 @@ module Bolt
|
|
214
214
|
end
|
215
215
|
|
216
216
|
def print_tasks(tasks, modulepath)
|
217
|
-
|
217
|
+
command = Bolt::Util.powershell? ? 'Get-BoltTask -Task <TASK NAME>' : 'bolt task show <TASK NAME>'
|
218
|
+
tasks.any? ? print_table(tasks) : print_message('No available tasks')
|
218
219
|
print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
|
219
|
-
"\nUse
|
220
|
+
"\nUse '#{command}' to view "\
|
220
221
|
"details and parameters for a specific task.")
|
221
222
|
end
|
222
223
|
|
@@ -225,20 +226,26 @@ module Bolt
|
|
225
226
|
# Building lots of strings...
|
226
227
|
pretty_params = +""
|
227
228
|
task_info = +""
|
228
|
-
usage =
|
229
|
+
usage = if Bolt::Util.powershell?
|
230
|
+
+"Invoke-BoltTask -Name #{task.name} -Targets <targets>"
|
231
|
+
else
|
232
|
+
+"bolt task run #{task.name} --targets <targets>"
|
233
|
+
end
|
229
234
|
|
230
235
|
task.parameters&.each do |k, v|
|
231
236
|
pretty_params << "- #{k}: #{v['type'] || 'Any'}\n"
|
232
237
|
pretty_params << " Default: #{v['default'].inspect}\n" if v.key?('default')
|
233
238
|
pretty_params << " #{v['description']}\n" if v['description']
|
234
|
-
usage << if v['type'].
|
239
|
+
usage << if v['type'].start_with?("Optional")
|
235
240
|
" [#{k}=<value>]"
|
236
241
|
else
|
237
242
|
" #{k}=<value>"
|
238
243
|
end
|
239
244
|
end
|
240
245
|
|
241
|
-
|
246
|
+
if task.supports_noop
|
247
|
+
usage << Bolt::Util.powershell? ? '[-Noop]' : '[--noop]'
|
248
|
+
end
|
242
249
|
|
243
250
|
task_info << "\n#{task.name}"
|
244
251
|
task_info << " - #{task.description}" if task.description
|
@@ -261,7 +268,11 @@ module Bolt
|
|
261
268
|
# Building lots of strings...
|
262
269
|
pretty_params = +""
|
263
270
|
plan_info = +""
|
264
|
-
usage =
|
271
|
+
usage = if Bolt::Util.powershell?
|
272
|
+
+"Invoke-BoltPlan -Name #{plan['name']}"
|
273
|
+
else
|
274
|
+
+"bolt plan run #{plan['name']}"
|
275
|
+
end
|
265
276
|
|
266
277
|
plan['parameters'].each do |name, p|
|
267
278
|
pretty_params << "- #{name}: #{p['type']}\n"
|
@@ -287,16 +298,17 @@ module Bolt
|
|
287
298
|
end
|
288
299
|
|
289
300
|
def print_plans(plans, modulepath)
|
290
|
-
|
301
|
+
command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
|
302
|
+
plans.any? ? print_table(plans) : print_message('No available plans')
|
291
303
|
print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
|
292
|
-
"\nUse
|
304
|
+
"\nUse '#{command}' to view "\
|
293
305
|
"details and parameters for a specific plan.")
|
294
306
|
end
|
295
307
|
|
296
308
|
def print_topics(topics)
|
297
309
|
print_message("Available topics are:")
|
298
310
|
print_message(topics.join("\n"))
|
299
|
-
print_message("\nUse
|
311
|
+
print_message("\nUse 'bolt guide <TOPIC>' to view a specific guide.")
|
300
312
|
end
|
301
313
|
|
302
314
|
def print_guide(guide, _topic)
|
data/lib/bolt/pal.rb
CHANGED
@@ -26,7 +26,7 @@ module Bolt
|
|
26
26
|
details[:line] = err.line if defined?(err.line)
|
27
27
|
details[:column] = err.pos if defined?(err.pos)
|
28
28
|
|
29
|
-
error.add_filelineno(details)
|
29
|
+
error.add_filelineno(details.compact)
|
30
30
|
error
|
31
31
|
end
|
32
32
|
|
@@ -286,15 +286,26 @@ module Bolt
|
|
286
286
|
raise Bolt::PAL::PALError, "Failed to parse manifest: #{e}"
|
287
287
|
end
|
288
288
|
|
289
|
-
|
289
|
+
# Filters content by a list of names and glob patterns specified in project
|
290
|
+
# configuration.
|
291
|
+
def filter_content(content, patterns)
|
292
|
+
return content unless content && patterns
|
293
|
+
|
294
|
+
content.select do |name,|
|
295
|
+
patterns.any? { |pattern| File.fnmatch?(pattern, name, File::FNM_EXTGLOB) }
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def list_tasks(filter_content: false)
|
290
300
|
in_bolt_compiler do |compiler|
|
291
|
-
tasks = compiler.list_tasks
|
292
|
-
tasks.map(&:name).sort.each_with_object([]) do |task_name, data|
|
301
|
+
tasks = compiler.list_tasks.map(&:name).sort.each_with_object([]) do |task_name, data|
|
293
302
|
task_sig = compiler.task_signature(task_name)
|
294
303
|
unless task_sig.task_hash['metadata']['private']
|
295
304
|
data << [task_name, task_sig.task_hash['metadata']['description']]
|
296
305
|
end
|
297
306
|
end
|
307
|
+
|
308
|
+
filter_content ? filter_content(tasks, @project&.tasks) : tasks
|
298
309
|
end
|
299
310
|
end
|
300
311
|
|
@@ -346,14 +357,15 @@ module Bolt
|
|
346
357
|
Bolt::Task.from_task_signature(task)
|
347
358
|
end
|
348
359
|
|
349
|
-
def list_plans
|
360
|
+
def list_plans(filter_content: false)
|
350
361
|
in_bolt_compiler do |compiler|
|
351
362
|
errors = []
|
352
363
|
plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
|
353
364
|
errors.each do |error|
|
354
365
|
@logger.warn(error.details['original_error'])
|
355
366
|
end
|
356
|
-
|
367
|
+
|
368
|
+
filter_content ? filter_content(plans, @project&.plans) : plans
|
357
369
|
end
|
358
370
|
end
|
359
371
|
|
@@ -390,7 +402,7 @@ module Bolt
|
|
390
402
|
plan.docstring
|
391
403
|
end
|
392
404
|
|
393
|
-
defaults = plan.parameters.
|
405
|
+
defaults = plan.parameters.to_h.compact
|
394
406
|
signature_params = Set.new(plan.parameters.map(&:first))
|
395
407
|
parameters = plan.tags(:param).each_with_object({}) do |param, params|
|
396
408
|
name = param.name
|
data/lib/bolt/pal/yaml_plan.rb
CHANGED
@@ -45,6 +45,13 @@ module Bolt
|
|
45
45
|
used_names = Set.new(@parameters.map(&:name))
|
46
46
|
|
47
47
|
@steps = plan['steps'].each_with_index.map do |step, index|
|
48
|
+
unless step.is_a?(Hash)
|
49
|
+
raise Bolt::Error.new(
|
50
|
+
"Parse error in step number #{index + 1}: Plan step must be an object with valid step keys.",
|
51
|
+
'bolt/invalid-plan'
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
48
55
|
# Step keys also aren't allowed to be code and neither is the value of "name"
|
49
56
|
stringified_step = Bolt::Util.walk_keys(step) { |key| stringify(key) }
|
50
57
|
stringified_step['name'] = stringify(stringified_step['name']) if stringified_step.key?('name')
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/error'
|
4
|
+
require 'bolt/logger'
|
5
|
+
require 'bolt/module'
|
6
|
+
require 'bolt/util'
|
7
|
+
|
8
|
+
module Bolt
|
9
|
+
module PlanCreator
|
10
|
+
def self.validate_input(project, plan_name)
|
11
|
+
if project.name.nil?
|
12
|
+
raise Bolt::Error.new(
|
13
|
+
"Project directory '#{project.path}' is not a named project. Unable to create "\
|
14
|
+
"a project-level plan. To name a project, set the 'name' key in the 'bolt-project.yaml' "\
|
15
|
+
"configuration file.",
|
16
|
+
"bolt/unnamed-project-error"
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
if plan_name !~ Bolt::Module::CONTENT_NAME_REGEX
|
21
|
+
message = <<~MESSAGE.chomp
|
22
|
+
Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
|
23
|
+
separated by double colons '::'.
|
24
|
+
|
25
|
+
Each name segment must begin with a lowercase letter, and may only include lowercase
|
26
|
+
letters, digits, and underscores.
|
27
|
+
|
28
|
+
Examples of valid plan names:
|
29
|
+
- #{project.name}
|
30
|
+
- #{project.name}::my_plan
|
31
|
+
MESSAGE
|
32
|
+
|
33
|
+
raise Bolt::ValidationError, message
|
34
|
+
end
|
35
|
+
|
36
|
+
prefix, _, basename = segment_plan_name(plan_name)
|
37
|
+
|
38
|
+
unless prefix == project.name
|
39
|
+
message = "First segment of plan name '#{plan_name}' must match project name '#{project.name}'. "\
|
40
|
+
"Did you mean '#{project.name}::#{plan_name}'?"
|
41
|
+
|
42
|
+
raise Bolt::ValidationError, message
|
43
|
+
end
|
44
|
+
|
45
|
+
%w[pp yaml].each do |ext|
|
46
|
+
next unless (path = project.plans_path + "#{basename}.#{ext}").exist?
|
47
|
+
raise Bolt::Error.new(
|
48
|
+
"A plan with the name '#{plan_name}' already exists at '#{path}', nothing to do.",
|
49
|
+
'bolt/existing-plan-error'
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create_plan(plans_path, plan_name, outputter, is_puppet)
|
55
|
+
_, name_segments, basename = segment_plan_name(plan_name)
|
56
|
+
dir_path = plans_path.join(*name_segments)
|
57
|
+
|
58
|
+
begin
|
59
|
+
FileUtils.mkdir_p(dir_path)
|
60
|
+
rescue Errno::EEXIST => e
|
61
|
+
raise Bolt::Error.new(
|
62
|
+
"#{e.message}; unable to create plan directory '#{dir_path}'",
|
63
|
+
'bolt/existing-file-error'
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
type = is_puppet ? 'pp' : 'yaml'
|
68
|
+
plan_path = dir_path + "#{basename}.#{type}"
|
69
|
+
plan_template = is_puppet ? puppet_plan(plan_name) : yaml_plan(plan_name)
|
70
|
+
|
71
|
+
begin
|
72
|
+
File.write(plan_path, plan_template)
|
73
|
+
rescue Errno::EACCES => e
|
74
|
+
raise Bolt::FileError.new(
|
75
|
+
"#{e.message}; unable to create plan",
|
76
|
+
plan_path
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
if Bolt::Util.powershell?
|
81
|
+
show_command = 'Get-BoltPlan -Name '
|
82
|
+
run_command = 'Invoke-BoltPlan -Name '
|
83
|
+
else
|
84
|
+
show_command = 'bolt plan show'
|
85
|
+
run_command = 'bolt plan run'
|
86
|
+
end
|
87
|
+
|
88
|
+
output = <<~OUTPUT
|
89
|
+
Created plan '#{plan_name}' at '#{plan_path}'
|
90
|
+
|
91
|
+
Show this plan with:
|
92
|
+
#{show_command} #{plan_name}
|
93
|
+
Run this plan with:
|
94
|
+
#{run_command} #{plan_name}
|
95
|
+
OUTPUT
|
96
|
+
|
97
|
+
outputter.print_message(output)
|
98
|
+
0
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.segment_plan_name(plan_name)
|
102
|
+
prefix, *name_segments, basename = plan_name.split('::')
|
103
|
+
|
104
|
+
# If the plan name is just the project name, then create an 'init' plan.
|
105
|
+
# Otherwise, use the last name segment for the plan's filename.
|
106
|
+
basename ||= 'init'
|
107
|
+
|
108
|
+
[prefix, name_segments, basename]
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.yaml_plan(plan_name)
|
112
|
+
<<~YAML
|
113
|
+
# This is the structure of a simple plan. To learn more about writing
|
114
|
+
# YAML plans, see the documentation: http://pup.pt/bolt-yaml-plans
|
115
|
+
|
116
|
+
# The description sets the description of the plan that will appear
|
117
|
+
# in 'bolt plan show' output.
|
118
|
+
description: A plan created with bolt plan new
|
119
|
+
|
120
|
+
# The parameters key defines the parameters that can be passed to
|
121
|
+
# the plan.
|
122
|
+
parameters:
|
123
|
+
targets:
|
124
|
+
type: TargetSpec
|
125
|
+
description: A list of targets to run actions on
|
126
|
+
default: localhost
|
127
|
+
|
128
|
+
# The steps key defines the actions the plan will take in order.
|
129
|
+
steps:
|
130
|
+
- message: Hello from #{plan_name}
|
131
|
+
- name: command_step
|
132
|
+
command: whoami
|
133
|
+
targets: $targets
|
134
|
+
|
135
|
+
# The return key sets the return value of the plan.
|
136
|
+
return: $command_step
|
137
|
+
YAML
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.puppet_plan(plan_name)
|
141
|
+
<<~PUPPET
|
142
|
+
# This is the structure of a simple plan. To learn more about writing
|
143
|
+
# Puppet plans, see the documentation: http://pup.pt/bolt-puppet-plans
|
144
|
+
|
145
|
+
# The summary sets the description of the plan that will appear
|
146
|
+
# in 'bolt plan show' output. Bolt uses puppet-strings to parse the
|
147
|
+
# summary and parameters from the plan.
|
148
|
+
# @summary A plan created with bolt plan new.
|
149
|
+
# @param targets The targets to run on.
|
150
|
+
plan #{plan_name} (
|
151
|
+
TargetSpec $targets = "localhost"
|
152
|
+
) {
|
153
|
+
out::message("Hello from #{plan_name}")
|
154
|
+
$command_result = run_command('whoami', $targets)
|
155
|
+
return $command_result
|
156
|
+
}
|
157
|
+
PUPPET
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/bolt/plugin.rb
CHANGED
@@ -4,6 +4,7 @@ require 'bolt/inventory'
|
|
4
4
|
require 'bolt/executor'
|
5
5
|
require 'bolt/module'
|
6
6
|
require 'bolt/pal'
|
7
|
+
require 'bolt/plugin/cache'
|
7
8
|
require 'bolt/plugin/puppetdb'
|
8
9
|
|
9
10
|
module Bolt
|
@@ -36,6 +37,13 @@ module Bolt
|
|
36
37
|
super("Plugin #{plugin_name} does not support #{hook}", 'bolt/unsupported-hook')
|
37
38
|
end
|
38
39
|
end
|
40
|
+
|
41
|
+
class LoadingDisabled < PluginError
|
42
|
+
def initialize(plugin_name)
|
43
|
+
msg = "Cannot load plugin #{plugin_name}: plugin loading is disabled"
|
44
|
+
super(msg, 'bolt/plugin-loading-disabled', { 'plugin_name' => plugin_name })
|
45
|
+
end
|
46
|
+
end
|
39
47
|
end
|
40
48
|
|
41
49
|
class PluginContext
|
@@ -119,15 +127,8 @@ module Bolt
|
|
119
127
|
end
|
120
128
|
end
|
121
129
|
|
122
|
-
def self.setup(config, pal, analytics = Bolt::Analytics::NoopClient.new)
|
123
|
-
plugins = new(config, pal, analytics)
|
124
|
-
|
125
|
-
# Initialize any plugins referenced in plugin config. This will also indirectly
|
126
|
-
# initialize any plugins they depend on.
|
127
|
-
if plugins.reference?(config.plugins)
|
128
|
-
msg = "The 'plugins' setting cannot be set by a plugin reference"
|
129
|
-
raise PluginError.new(msg, 'bolt/plugin-error')
|
130
|
-
end
|
130
|
+
def self.setup(config, pal, analytics = Bolt::Analytics::NoopClient.new, **opts)
|
131
|
+
plugins = new(config, pal, analytics, **opts)
|
131
132
|
|
132
133
|
config.plugins.each_key do |plugin|
|
133
134
|
plugins.by_name(plugin)
|
@@ -148,12 +149,13 @@ module Bolt
|
|
148
149
|
|
149
150
|
private_class_method :new
|
150
151
|
|
151
|
-
def initialize(config, pal, analytics)
|
152
|
+
def initialize(config, pal, analytics, load_plugins: true)
|
152
153
|
@config = config
|
153
154
|
@analytics = analytics
|
154
155
|
@plugin_context = PluginContext.new(config, pal, self)
|
155
156
|
@plugins = {}
|
156
157
|
@pal = pal
|
158
|
+
@load_plugins = load_plugins
|
157
159
|
@unknown = Set.new
|
158
160
|
@resolution_stack = []
|
159
161
|
@unresolved_plugin_configs = config.plugins.dup
|
@@ -176,6 +178,8 @@ module Bolt
|
|
176
178
|
end
|
177
179
|
|
178
180
|
def add_ruby_plugin(plugin_name)
|
181
|
+
raise PluginError::LoadingDisabled, plugin_name unless @load_plugins
|
182
|
+
|
179
183
|
cls_name = Bolt::Util.snake_name_to_class_name(plugin_name)
|
180
184
|
filename = "bolt/plugin/#{plugin_name}"
|
181
185
|
require filename
|
@@ -192,10 +196,17 @@ module Bolt
|
|
192
196
|
def add_module_plugin(plugin_name)
|
193
197
|
opts = {
|
194
198
|
context: @plugin_context,
|
199
|
+
# Make sure that the plugin's config is validated _before_ the unknown-plugin
|
200
|
+
# and loading-disabled checks. This way, we can fail early on invalid plugin
|
201
|
+
# config instead of _after_ loading the modulepath (which can be expensive).
|
195
202
|
config: config_for_plugin(plugin_name)
|
196
203
|
}
|
197
204
|
|
198
|
-
|
205
|
+
mod = modules[plugin_name]
|
206
|
+
raise PluginError::Unknown, plugin_name unless mod&.plugin?
|
207
|
+
raise PluginError::LoadingDisabled, plugin_name unless @load_plugins
|
208
|
+
|
209
|
+
plugin = Bolt::Plugin::Module.load(mod, opts)
|
199
210
|
add_plugin(plugin)
|
200
211
|
end
|
201
212
|
|
@@ -284,27 +295,45 @@ module Bolt
|
|
284
295
|
# Evaluates a single reference. The value returned may be another
|
285
296
|
# reference.
|
286
297
|
def resolve_single_reference(reference)
|
298
|
+
plugin_cache = if cache?(reference)
|
299
|
+
cache = Bolt::Plugin::Cache.new(reference,
|
300
|
+
@config.project.cache_file,
|
301
|
+
@config.plugin_cache)
|
302
|
+
entry = cache.read_and_clean_cache
|
303
|
+
return entry unless entry.nil?
|
304
|
+
|
305
|
+
cache
|
306
|
+
end
|
307
|
+
|
287
308
|
plugin_name = reference['_plugin']
|
288
309
|
hook = get_hook(plugin_name, :resolve_reference)
|
289
310
|
|
290
311
|
begin
|
291
312
|
validate_proc = get_hook(plugin_name, :validate_resolve_reference)
|
292
313
|
rescue PluginError
|
293
|
-
validate_proc = proc { |*args| }
|
314
|
+
validate_proc = proc { |*args| } # Nothing to do
|
294
315
|
end
|
295
316
|
|
296
317
|
validate_proc.call(reference)
|
297
318
|
|
298
|
-
begin
|
319
|
+
result = begin
|
299
320
|
# Evaluate the plugin and then recursively evaluate any plugin returned by it.
|
300
321
|
hook.call(reference)
|
301
322
|
rescue StandardError => e
|
302
323
|
loc = "resolve_reference in #{plugin_name}"
|
303
324
|
raise PluginError::ExecutionError.new(e.message, plugin_name, loc)
|
304
325
|
end
|
326
|
+
|
327
|
+
plugin_cache.write_cache(result) if cache?(reference)
|
328
|
+
|
329
|
+
result
|
305
330
|
end
|
306
331
|
private :resolve_single_reference
|
307
332
|
|
333
|
+
private def cache?(reference)
|
334
|
+
reference.key?('_cache') || @config.plugin_cache.key?('ttl')
|
335
|
+
end
|
336
|
+
|
308
337
|
# Checks whether a given value is a _plugin reference
|
309
338
|
def reference?(input)
|
310
339
|
input.is_a?(Hash) && input.key?('_plugin')
|