bolt 2.30.0 → 2.34.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +12 -12
  3. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +1 -1
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +1 -1
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +1 -1
  10. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +2 -2
  12. data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
  13. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
  14. data/guides/logging.txt +18 -0
  15. data/guides/module.txt +19 -0
  16. data/guides/modulepath.txt +25 -0
  17. data/lib/bolt/bolt_option_parser.rb +6 -1
  18. data/lib/bolt/cli.rb +82 -142
  19. data/lib/bolt/config/modulepath.rb +30 -0
  20. data/lib/bolt/config/options.rb +31 -13
  21. data/lib/bolt/config/transport/options.rb +2 -2
  22. data/lib/bolt/error.rb +13 -3
  23. data/lib/bolt/executor.rb +24 -12
  24. data/lib/bolt/inventory.rb +10 -9
  25. data/lib/bolt/inventory/group.rb +2 -1
  26. data/lib/bolt/module_installer.rb +117 -91
  27. data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
  28. data/lib/bolt/module_installer/puppetfile.rb +117 -0
  29. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  30. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  31. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  32. data/lib/bolt/module_installer/resolver.rb +76 -0
  33. data/lib/bolt/module_installer/specs.rb +93 -0
  34. data/lib/bolt/module_installer/specs/forge_spec.rb +85 -0
  35. data/lib/bolt/module_installer/specs/git_spec.rb +179 -0
  36. data/lib/bolt/outputter.rb +0 -47
  37. data/lib/bolt/outputter/human.rb +46 -16
  38. data/lib/bolt/outputter/json.rb +17 -8
  39. data/lib/bolt/pal.rb +52 -40
  40. data/lib/bolt/pal/yaml_plan.rb +4 -2
  41. data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
  42. data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
  43. data/lib/bolt/plan_creator.rb +160 -0
  44. data/lib/bolt/plugin.rb +2 -2
  45. data/lib/bolt/project.rb +6 -11
  46. data/lib/bolt/project_migrator.rb +1 -1
  47. data/lib/bolt/project_migrator/base.rb +2 -2
  48. data/lib/bolt/project_migrator/config.rb +5 -4
  49. data/lib/bolt/project_migrator/inventory.rb +3 -3
  50. data/lib/bolt/project_migrator/modules.rb +23 -21
  51. data/lib/bolt/puppetdb/config.rb +5 -5
  52. data/lib/bolt/result.rb +23 -11
  53. data/lib/bolt/shell/bash.rb +14 -8
  54. data/lib/bolt/shell/powershell.rb +12 -7
  55. data/lib/bolt/task/run.rb +1 -1
  56. data/lib/bolt/transport/base.rb +18 -18
  57. data/lib/bolt/transport/docker.rb +23 -6
  58. data/lib/bolt/transport/orch.rb +26 -17
  59. data/lib/bolt/transport/remote.rb +3 -3
  60. data/lib/bolt/transport/simple.rb +6 -6
  61. data/lib/bolt/transport/ssh/connection.rb +1 -1
  62. data/lib/bolt/util.rb +5 -0
  63. data/lib/bolt/version.rb +1 -1
  64. data/lib/bolt_server/file_cache.rb +2 -0
  65. data/lib/bolt_server/schemas/partials/task.json +17 -2
  66. data/lib/bolt_server/transport_app.rb +92 -12
  67. data/lib/bolt_spec/bolt_context.rb +4 -2
  68. data/lib/bolt_spec/plans.rb +1 -1
  69. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
  70. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
  71. data/lib/bolt_spec/plans/mock_executor.rb +5 -5
  72. data/lib/bolt_spec/run.rb +1 -1
  73. metadata +24 -9
  74. data/lib/bolt/puppetfile.rb +0 -142
  75. data/lib/bolt/puppetfile/module.rb +0 -90
  76. data/lib/bolt_server/pe/pal.rb +0 -67
  77. data/modules/secure_env_vars/plans/init.pp +0 -20
@@ -5,28 +5,20 @@ require 'bolt/executor'
5
5
  require 'bolt/error'
6
6
  require 'bolt/plan_result'
7
7
  require 'bolt/util'
8
+ require 'bolt/config/modulepath'
8
9
  require 'etc'
9
10
 
10
11
  module Bolt
11
12
  class PAL
12
- BOLTLIB_PATH = File.expand_path('../../bolt-modules', __dir__)
13
- MODULES_PATH = File.expand_path('../../modules', __dir__)
14
-
15
13
  # PALError is used to convert errors from executing puppet code into
16
14
  # Bolt::Errors
17
15
  class PALError < Bolt::Error
18
16
  def self.from_preformatted_error(err)
19
- if err.cause&.is_a? Bolt::Error
20
- err.cause
21
- else
22
- from_error(err)
23
- end
24
- end
25
-
26
- # Generate a Bolt::Pal::PALError for non-bolt errors
27
- def self.from_error(err)
28
- # Use the original error message if available
29
- 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
30
22
 
31
23
  # Provide the location of an error if it came from a plan
32
24
  details = {}
@@ -34,8 +26,15 @@ module Bolt
34
26
  details[:line] = err.line if defined?(err.line)
35
27
  details[:column] = err.pos if defined?(err.pos)
36
28
 
37
- e = new(message, details.compact)
29
+ error.add_filelineno(details)
30
+ error
31
+ end
38
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)
39
38
  e.set_backtrace(err.backtrace)
40
39
  e
41
40
  end
@@ -45,16 +44,16 @@ module Bolt
45
44
  end
46
45
  end
47
46
 
48
- attr_reader :modulepath, :user_modulepath
49
-
50
47
  def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors,
51
48
  trusted_external = nil, apply_settings = {}, project = nil)
49
+ unless modulepath.is_a?(Bolt::Config::Modulepath)
50
+ msg = "Type error in PAL: modulepath must be a Bolt::Config::Modulepath"
51
+ raise Bolt::Error.new(msg, "bolt/execution-error")
52
+ end
52
53
  # Nothing works without initialized this global state. Reinitializing
53
54
  # is safe and in practice only happens in tests
54
55
  self.class.load_puppet
55
-
56
- @user_modulepath = modulepath
57
- @modulepath = [BOLTLIB_PATH, *modulepath, MODULES_PATH]
56
+ @modulepath = modulepath
58
57
  @hiera_config = hiera_config
59
58
  @trusted_external = trusted_external
60
59
  @apply_settings = apply_settings
@@ -63,13 +62,21 @@ module Bolt
63
62
  @project = project
64
63
 
65
64
  @logger = Bolt::Logger.logger(self)
66
- if modulepath && !modulepath.empty?
67
- @logger.debug("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
65
+ unless user_modulepath.empty?
66
+ @logger.debug("Loading modules from #{full_modulepath.join(File::PATH_SEPARATOR)}")
68
67
  end
69
68
 
70
69
  @loaded = false
71
70
  end
72
71
 
72
+ def full_modulepath
73
+ @modulepath.full_modulepath
74
+ end
75
+
76
+ def user_modulepath
77
+ @modulepath.user_modulepath
78
+ end
79
+
73
80
  # Puppet logging is global so this is class method to avoid confusion
74
81
  def self.configure_logging
75
82
  Puppet::Util::Log.destinations.clear
@@ -157,7 +164,7 @@ module Bolt
157
164
  def in_bolt_compiler
158
165
  # TODO: If we always call this inside a bolt_executor we can remove this here
159
166
  setup
160
- r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
167
+ r = Puppet::Pal.in_tmp_environment('bolt', modulepath: full_modulepath, facts: {}) do |pal|
161
168
  # Only load the project if it a) exists, b) has a name it can be loaded with
162
169
  Puppet.override(bolt_project: @project,
163
170
  yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
@@ -212,11 +219,11 @@ module Bolt
212
219
  apply_executor: applicator || Applicator.new(
213
220
  inventory,
214
221
  executor,
215
- @modulepath,
222
+ full_modulepath,
216
223
  # Skip syncing built-in plugins, since we vendor some Puppet 6
217
224
  # versions of "core" types, which are already present on the agent,
218
225
  # but may cause issues on Puppet 5 agents.
219
- @user_modulepath,
226
+ user_modulepath,
220
227
  @project,
221
228
  pdb_client,
222
229
  @hiera_config,
@@ -250,19 +257,24 @@ module Bolt
250
257
 
251
258
  # TODO: PUP-8553 should replace this
252
259
  def with_puppet_settings
253
- Dir.mktmpdir('bolt') do |dir|
254
- cli = []
255
- Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
256
- cli << "--#{setting}" << dir
257
- end
258
- Puppet.settings.send(:clear_everything_for_tests)
259
- Puppet.initialize_settings(cli)
260
- Puppet::GettextConfig.create_default_text_domain
261
- Puppet[:trusted_external_command] = @trusted_external
262
- Puppet.settings[:hiera_config] = @hiera_config
263
- self.class.configure_logging
264
- yield
260
+ dir = Dir.mktmpdir('bolt')
261
+
262
+ cli = []
263
+ Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
264
+ cli << "--#{setting}" << dir
265
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)
266
278
  end
267
279
 
268
280
  # Parses a snippet of Puppet manifest code and returns the AST represented
@@ -378,7 +390,7 @@ module Bolt
378
390
  plan.docstring
379
391
  end
380
392
 
381
- defaults = plan.parameters.reject { |_, value| value.nil? }.to_h
393
+ defaults = plan.parameters.to_h.compact
382
394
  signature_params = Set.new(plan.parameters.map(&:first))
383
395
  parameters = plan.tags(:param).each_with_object({}) do |param, params|
384
396
  name = param.name
@@ -443,8 +455,8 @@ module Bolt
443
455
  # The information hash provides the name, version, and a string
444
456
  # indicating whether the module belongs to an internal module group.
445
457
  def list_modules
446
- internal_module_groups = { BOLTLIB_PATH => 'Plan Language Modules',
447
- MODULES_PATH => 'Packaged Modules',
458
+ internal_module_groups = { Bolt::Config::Modulepath::BOLTLIB_PATH => 'Plan Language Modules',
459
+ Bolt::Config::Modulepath::MODULES_PATH => 'Packaged Modules',
448
460
  @project.managed_moduledir.to_s => 'Project Dependencies' }
449
461
 
450
462
  in_bolt_compiler do
@@ -98,10 +98,12 @@ module Bolt
98
98
  # subclasses this parent class in order to implement its own evaluation
99
99
  # logic.
100
100
  class EvaluableString
101
- attr_reader :value
101
+ attr_reader :file, :line, :value
102
102
 
103
- def initialize(value)
103
+ def initialize(value, file = nil, line = nil)
104
104
  @value = value
105
+ @file = file
106
+ @line = line
105
107
  end
106
108
 
107
109
  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
- value.evaluate(scope, @evaluator)
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 self.create_visitor
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 or > style string
28
- when Psych::Nodes::Scalar::LITERAL, Psych::Nodes::Scalar::FOLDED
29
- CodeLiteral.new(node.value)
30
- # This one shouldn't be possible
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
@@ -168,7 +168,7 @@ module Bolt
168
168
  end
169
169
 
170
170
  def modules
171
- @modules ||= Bolt::Module.discover(@pal.modulepath)
171
+ @modules ||= Bolt::Module.discover(@pal.full_modulepath)
172
172
  end
173
173
 
174
174
  def add_plugin(plugin)
@@ -290,7 +290,7 @@ module Bolt
290
290
  begin
291
291
  validate_proc = get_hook(plugin_name, :validate_resolve_reference)
292
292
  rescue PluginError
293
- validate_proc = proc { |*args| }
293
+ validate_proc = proc { |*args| } # Nothing to do
294
294
  end
295
295
 
296
296
  validate_proc.call(reference)