bolt 2.32.0 → 2.36.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 +6 -6
- 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/facts.rb +6 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
- 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/guides/logging.txt +18 -0
- data/lib/bolt/analytics.rb +27 -8
- data/lib/bolt/apply_result.rb +3 -3
- data/lib/bolt/bolt_option_parser.rb +43 -15
- data/lib/bolt/cli.rb +79 -227
- data/lib/bolt/config.rb +131 -52
- data/lib/bolt/config/options.rb +46 -8
- 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 +37 -3
- data/lib/bolt/executor.rb +103 -17
- data/lib/bolt/inventory/group.rb +2 -1
- data/lib/bolt/module_installer.rb +2 -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 +48 -30
- data/lib/bolt/pal/yaml_plan.rb +11 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
- data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
- data/lib/bolt/plan_creator.rb +160 -0
- data/lib/bolt/plugin.rb +1 -8
- data/lib/bolt/project.rb +30 -36
- 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/result.rb +23 -11
- data/lib/bolt/shell/bash.rb +12 -7
- data/lib/bolt/shell/powershell.rb +12 -7
- data/lib/bolt/task/run.rb +1 -1
- data/lib/bolt/transport/base.rb +18 -18
- data/lib/bolt/transport/docker.rb +23 -6
- data/lib/bolt/transport/orch.rb +23 -19
- data/lib/bolt/transport/orch/connection.rb +10 -3
- data/lib/bolt/transport/remote.rb +3 -3
- data/lib/bolt/transport/simple.rb +6 -6
- data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
- data/lib/bolt/util.rb +19 -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/schemas/partials/task.json +2 -2
- data/lib/bolt_server/transport_app.rb +42 -11
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +9 -6
- data/libexec/apply_catalog.rb +1 -1
- data/libexec/custom_facts.rb +1 -1
- data/libexec/query_resources.rb +1 -1
- metadata +12 -14
- data/lib/bolt/project_migrator.rb +0 -80
- data/modules/secure_env_vars/plans/init.pp +0 -20
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
|
@@ -63,7 +63,7 @@ module Bolt
|
|
63
63
|
|
64
64
|
data = Bolt::Util.read_yaml_hash(config_path, 'project')
|
65
65
|
data['modules'] ||= []
|
66
|
-
data['modules'] << name
|
66
|
+
data['modules'] << name.tr('-', '/')
|
67
67
|
|
68
68
|
begin
|
69
69
|
File.write(config_path, data.to_yaml)
|
@@ -187,6 +187,7 @@ module Bolt
|
|
187
187
|
ok = Installer.new(config).install(path, moduledir)
|
188
188
|
|
189
189
|
# Automatically generate types after installing modules
|
190
|
+
@outputter.print_action_step("Generating type references")
|
190
191
|
@pal.generate_types
|
191
192
|
|
192
193
|
@outputter.print_puppetfile_result(ok, path, moduledir)
|
@@ -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
@@ -14,17 +14,11 @@ module Bolt
|
|
14
14
|
# Bolt::Errors
|
15
15
|
class PALError < Bolt::Error
|
16
16
|
def self.from_preformatted_error(err)
|
17
|
-
if err.cause.is_a? Bolt::Error
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
# Generate a Bolt::Pal::PALError for non-bolt errors
|
25
|
-
def self.from_error(err)
|
26
|
-
# Use the original error message if available
|
27
|
-
message = err.cause ? err.cause.message : err.message
|
17
|
+
error = if err.cause.is_a? Bolt::Error
|
18
|
+
err.cause
|
19
|
+
else
|
20
|
+
from_error(err)
|
21
|
+
end
|
28
22
|
|
29
23
|
# Provide the location of an error if it came from a plan
|
30
24
|
details = {}
|
@@ -32,8 +26,15 @@ module Bolt
|
|
32
26
|
details[:line] = err.line if defined?(err.line)
|
33
27
|
details[:column] = err.pos if defined?(err.pos)
|
34
28
|
|
35
|
-
|
29
|
+
error.add_filelineno(details.compact)
|
30
|
+
error
|
31
|
+
end
|
36
32
|
|
33
|
+
# Generate a Bolt::Pal::PALError for non-bolt errors
|
34
|
+
def self.from_error(err)
|
35
|
+
# Use the original error message if available
|
36
|
+
message = err.cause ? err.cause.message : err.message
|
37
|
+
e = new(message)
|
37
38
|
e.set_backtrace(err.backtrace)
|
38
39
|
e
|
39
40
|
end
|
@@ -256,19 +257,24 @@ module Bolt
|
|
256
257
|
|
257
258
|
# TODO: PUP-8553 should replace this
|
258
259
|
def with_puppet_settings
|
259
|
-
Dir.mktmpdir('bolt')
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
Puppet.settings.send(:clear_everything_for_tests)
|
265
|
-
Puppet.initialize_settings(cli)
|
266
|
-
Puppet::GettextConfig.create_default_text_domain
|
267
|
-
Puppet[:trusted_external_command] = @trusted_external
|
268
|
-
Puppet.settings[:hiera_config] = @hiera_config
|
269
|
-
self.class.configure_logging
|
270
|
-
yield
|
260
|
+
dir = Dir.mktmpdir('bolt')
|
261
|
+
|
262
|
+
cli = []
|
263
|
+
Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
|
264
|
+
cli << "--#{setting}" << dir
|
271
265
|
end
|
266
|
+
Puppet.settings.send(:clear_everything_for_tests)
|
267
|
+
Puppet.initialize_settings(cli)
|
268
|
+
Puppet::GettextConfig.create_default_text_domain
|
269
|
+
Puppet[:trusted_external_command] = @trusted_external
|
270
|
+
Puppet.settings[:hiera_config] = @hiera_config
|
271
|
+
self.class.configure_logging
|
272
|
+
yield
|
273
|
+
ensure
|
274
|
+
# Delete the tmpdir if it still exists. This check is needed to
|
275
|
+
# prevent Bolt from erroring if the tmpdir is somehow deleted
|
276
|
+
# before reaching this point.
|
277
|
+
FileUtils.remove_entry_secure(dir) if File.exist?(dir)
|
272
278
|
end
|
273
279
|
|
274
280
|
# Parses a snippet of Puppet manifest code and returns the AST represented
|
@@ -280,15 +286,26 @@ module Bolt
|
|
280
286
|
raise Bolt::PAL::PALError, "Failed to parse manifest: #{e}"
|
281
287
|
end
|
282
288
|
|
283
|
-
|
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)
|
284
300
|
in_bolt_compiler do |compiler|
|
285
|
-
tasks = compiler.list_tasks
|
286
|
-
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|
|
287
302
|
task_sig = compiler.task_signature(task_name)
|
288
303
|
unless task_sig.task_hash['metadata']['private']
|
289
304
|
data << [task_name, task_sig.task_hash['metadata']['description']]
|
290
305
|
end
|
291
306
|
end
|
307
|
+
|
308
|
+
filter_content ? filter_content(tasks, @project&.tasks) : tasks
|
292
309
|
end
|
293
310
|
end
|
294
311
|
|
@@ -340,14 +357,15 @@ module Bolt
|
|
340
357
|
Bolt::Task.from_task_signature(task)
|
341
358
|
end
|
342
359
|
|
343
|
-
def list_plans
|
360
|
+
def list_plans(filter_content: false)
|
344
361
|
in_bolt_compiler do |compiler|
|
345
362
|
errors = []
|
346
363
|
plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
|
347
364
|
errors.each do |error|
|
348
365
|
@logger.warn(error.details['original_error'])
|
349
366
|
end
|
350
|
-
|
367
|
+
|
368
|
+
filter_content ? filter_content(plans, @project&.plans) : plans
|
351
369
|
end
|
352
370
|
end
|
353
371
|
|
@@ -384,7 +402,7 @@ module Bolt
|
|
384
402
|
plan.docstring
|
385
403
|
end
|
386
404
|
|
387
|
-
defaults = plan.parameters.
|
405
|
+
defaults = plan.parameters.to_h.compact
|
388
406
|
signature_params = Set.new(plan.parameters.map(&:first))
|
389
407
|
parameters = plan.tags(:param).each_with_object({}) do |param, params|
|
390
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')
|
@@ -98,10 +105,12 @@ module Bolt
|
|
98
105
|
# subclasses this parent class in order to implement its own evaluation
|
99
106
|
# logic.
|
100
107
|
class EvaluableString
|
101
|
-
attr_reader :value
|
108
|
+
attr_reader :file, :line, :value
|
102
109
|
|
103
|
-
def initialize(value)
|
110
|
+
def initialize(value, file = nil, line = nil)
|
104
111
|
@value = value
|
112
|
+
@file = file
|
113
|
+
@line = line
|
105
114
|
end
|
106
115
|
|
107
116
|
def ==(other)
|
@@ -191,7 +191,11 @@ module Bolt
|
|
191
191
|
o[key] = evaluate_code_blocks(scope, v)
|
192
192
|
end
|
193
193
|
when EvaluableString
|
194
|
-
|
194
|
+
begin
|
195
|
+
value.evaluate(scope, @evaluator)
|
196
|
+
rescue StandardError => e
|
197
|
+
raise format_evaluate_error(e, value)
|
198
|
+
end
|
195
199
|
else
|
196
200
|
value
|
197
201
|
end
|
@@ -203,6 +207,24 @@ module Bolt
|
|
203
207
|
def evaluate(value, _scope)
|
204
208
|
value
|
205
209
|
end
|
210
|
+
|
211
|
+
def format_evaluate_error(error, value)
|
212
|
+
# The Puppet::PreformattedError includes the line number of the
|
213
|
+
# evaluable string that caused the error, while the value includes the
|
214
|
+
# line number of the YAML plan that the string began on. To get the
|
215
|
+
# actual line number of the error, add these two numbers together.
|
216
|
+
line = error.line + value.line
|
217
|
+
|
218
|
+
# If the evaluable string is not a scalar literal, correct for it
|
219
|
+
# being on the same line as the step key.
|
220
|
+
line -= 1 if value.is_a?(BareString)
|
221
|
+
|
222
|
+
Bolt::PlanFailure.new(
|
223
|
+
error.basic_message,
|
224
|
+
'bolt/evaluation-error',
|
225
|
+
{ file: value.file, line: line }
|
226
|
+
)
|
227
|
+
end
|
206
228
|
end
|
207
229
|
end
|
208
230
|
end
|
@@ -9,10 +9,15 @@ module Bolt
|
|
9
9
|
class YamlPlan
|
10
10
|
class Loader
|
11
11
|
class PuppetVisitor < Psych::Visitors::NoAliasRuby
|
12
|
-
def
|
12
|
+
def initialize(scanner, class_loader, file)
|
13
|
+
super(scanner, class_loader)
|
14
|
+
@file = file
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.create_visitor(source_ref)
|
13
18
|
class_loader = Psych::ClassLoader::Restricted.new([], [])
|
14
19
|
scanner = Psych::ScalarScanner.new(class_loader)
|
15
|
-
new(scanner, class_loader)
|
20
|
+
new(scanner, class_loader, source_ref)
|
16
21
|
end
|
17
22
|
|
18
23
|
def deserialize(node)
|
@@ -23,18 +28,18 @@ module Bolt
|
|
23
28
|
# @ss is a ScalarScanner, from the base ToRuby visitor class
|
24
29
|
node.value
|
25
30
|
when Psych::Nodes::Scalar::DOUBLE_QUOTED
|
26
|
-
DoubleQuotedString.new(node.value)
|
27
|
-
# | style string
|
28
|
-
when Psych::Nodes::Scalar::LITERAL
|
29
|
-
CodeLiteral.new(node.value)
|
30
|
-
#
|
31
|
+
DoubleQuotedString.new(node.value, @file, node.start_line + 1)
|
32
|
+
# | style string
|
33
|
+
when Psych::Nodes::Scalar::LITERAL
|
34
|
+
CodeLiteral.new(node.value, @file, node.start_line + 1)
|
35
|
+
# > style string
|
31
36
|
else
|
32
37
|
@ss.tokenize(node.value)
|
33
38
|
end
|
34
39
|
else
|
35
40
|
value = @ss.tokenize(node.value)
|
36
41
|
if value.is_a?(String)
|
37
|
-
BareString.new(value)
|
42
|
+
BareString.new(value, @file, node.start_line + 1)
|
38
43
|
else
|
39
44
|
value
|
40
45
|
end
|
@@ -50,7 +55,7 @@ module Bolt
|
|
50
55
|
else
|
51
56
|
Psych.parse(yaml_string, source_ref)
|
52
57
|
end
|
53
|
-
PuppetVisitor.create_visitor.accept(parse_tree)
|
58
|
+
PuppetVisitor.create_visitor(source_ref).accept(parse_tree)
|
54
59
|
end
|
55
60
|
|
56
61
|
def self.from_string(name, yaml_string, source_ref)
|
@@ -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
|