bolt 2.44.0 → 3.4.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +11 -9
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +25 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
  8. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +7 -3
  10. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
  11. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  12. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  13. data/lib/bolt/apply_result.rb +1 -1
  14. data/lib/bolt/bolt_option_parser.rb +9 -123
  15. data/lib/bolt/cli.rb +125 -127
  16. data/lib/bolt/config.rb +39 -214
  17. data/lib/bolt/config/options.rb +34 -125
  18. data/lib/bolt/config/transport/local.rb +1 -0
  19. data/lib/bolt/config/transport/lxd.rb +23 -0
  20. data/lib/bolt/config/transport/options.rb +9 -2
  21. data/lib/bolt/executor.rb +20 -5
  22. data/lib/bolt/logger.rb +9 -1
  23. data/lib/bolt/module_installer.rb +2 -2
  24. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  25. data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
  26. data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
  27. data/lib/bolt/node/output.rb +14 -4
  28. data/lib/bolt/outputter/human.rb +52 -24
  29. data/lib/bolt/outputter/json.rb +16 -16
  30. data/lib/bolt/pal.rb +26 -5
  31. data/lib/bolt/pal/yaml_plan.rb +1 -2
  32. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
  33. data/lib/bolt/pal/yaml_plan/step.rb +91 -52
  34. data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
  35. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  36. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  37. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  38. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  39. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  40. data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
  41. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  42. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  43. data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
  44. data/lib/bolt/plan_creator.rb +1 -1
  45. data/lib/bolt/plugin/module.rb +0 -23
  46. data/lib/bolt/plugin/puppet_connect_data.rb +45 -3
  47. data/lib/bolt/project.rb +16 -56
  48. data/lib/bolt/project_manager.rb +5 -4
  49. data/lib/bolt/project_manager/module_migrator.rb +7 -6
  50. data/lib/bolt/result.rb +10 -11
  51. data/lib/bolt/shell.rb +16 -0
  52. data/lib/bolt/shell/bash.rb +61 -31
  53. data/lib/bolt/shell/bash/tmpdir.rb +2 -2
  54. data/lib/bolt/shell/powershell.rb +35 -14
  55. data/lib/bolt/shell/powershell/snippets.rb +37 -150
  56. data/lib/bolt/task.rb +1 -1
  57. data/lib/bolt/transport/base.rb +0 -9
  58. data/lib/bolt/transport/docker.rb +1 -125
  59. data/lib/bolt/transport/docker/connection.rb +86 -161
  60. data/lib/bolt/transport/local.rb +1 -9
  61. data/lib/bolt/transport/lxd.rb +26 -0
  62. data/lib/bolt/transport/lxd/connection.rb +99 -0
  63. data/lib/bolt/transport/orch.rb +13 -5
  64. data/lib/bolt/transport/ssh/connection.rb +1 -1
  65. data/lib/bolt/transport/winrm/connection.rb +1 -1
  66. data/lib/bolt/util.rb +8 -0
  67. data/lib/bolt/version.rb +1 -1
  68. data/lib/bolt_server/transport_app.rb +61 -33
  69. data/lib/bolt_spec/bolt_context.rb +9 -4
  70. data/lib/bolt_spec/plans.rb +1 -109
  71. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  72. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  73. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  74. data/lib/bolt_spec/plans/mock_executor.rb +4 -0
  75. data/modules/aggregate/plans/count.pp +21 -0
  76. data/modules/aggregate/plans/targets.pp +21 -0
  77. data/modules/puppet_connect/plans/test_input_data.pp +67 -0
  78. data/modules/puppetdb_fact/plans/init.pp +10 -0
  79. metadata +7 -3
  80. data/modules/aggregate/plans/nodes.pp +0 -36
data/lib/bolt/pal.rb CHANGED
@@ -215,6 +215,7 @@ module Bolt
215
215
  def with_bolt_executor(executor, inventory, pdb_client = nil, applicator = nil, &block)
216
216
  setup
217
217
  opts = {
218
+ bolt_project: @project,
218
219
  bolt_executor: executor,
219
220
  bolt_inventory: inventory,
220
221
  bolt_pdb_client: pdb_client,
@@ -384,7 +385,7 @@ module Bolt
384
385
  plan_cache[plan_name] = info
385
386
  end
386
387
 
387
- list << [plan_name] unless info['private']
388
+ list << [plan_name, info['description']] unless info['private']
388
389
  end
389
390
 
390
391
  File.write(@project.plan_cache_file, plan_cache.to_json) if updated
@@ -445,11 +446,11 @@ module Bolt
445
446
  params[name] = { 'type' => param.types.first }
446
447
  params[name]['sensitive'] = param.types.first =~ /\ASensitive(\[.*\])?\z/ ? true : false
447
448
  params[name]['default_value'] = defaults[name] if defaults.key?(name)
448
- params[name]['description'] = param.text unless param.text.empty?
449
+ params[name]['description'] = param.text if param.text && !param.text.empty?
449
450
  else
450
451
  Bolt::Logger.warn(
451
452
  "missing_plan_parameter",
452
- "The documented parameter '#{name}' does not exist in plan signature"
453
+ "The documented parameter '#{name}' does not exist in signature for plan '#{plan.name}'"
453
454
  )
454
455
  end
455
456
  end
@@ -520,10 +521,30 @@ module Bolt
520
521
  end
521
522
  end
522
523
 
523
- def convert_plan(plan_path)
524
+ def convert_plan(plan)
525
+ path = File.expand_path(plan)
526
+
527
+ # If the path doesn't exist, check if it's a plan name
528
+ unless File.exist?(path)
529
+ in_bolt_compiler do |compiler|
530
+ sig = compiler.plan_signature(plan)
531
+
532
+ # If the plan was loaded, look for it on the module loader
533
+ # There has to be an easier way to do this...
534
+ if sig
535
+ type = compiler.list_plans.find { |p| p.name == plan }
536
+ path = sig.instance_variable_get(:@plan_func)
537
+ .loader
538
+ .find(type)
539
+ .origin
540
+ .first
541
+ end
542
+ end
543
+ end
544
+
524
545
  Puppet[:tasks] = true
525
546
  transpiler = YamlPlan::Transpiler.new
526
- transpiler.transpile(plan_path)
547
+ transpiler.transpile(path)
527
548
  end
528
549
 
529
550
  # Returns a mapping of all modules available to the Bolt compiler
@@ -73,8 +73,7 @@ module Bolt
73
73
  def duplicate_check(used_names, name, step_number)
74
74
  if used_names.include?(name)
75
75
  error_message = "Duplicate step name or parameter detected: #{name.inspect}"
76
- err = Step.step_error(error_message, name, step_number)
77
- raise Bolt::Error.new(err, "bolt/invalid-plan")
76
+ raise Step::StepError.new(error_message, name, step_number)
78
77
  end
79
78
  end
80
79
 
@@ -12,165 +12,15 @@ module Bolt
12
12
  @evaluator = Puppet::Pops::Parser::EvaluatingParser.new
13
13
  end
14
14
 
15
- def dispatch_step(scope, step)
16
- step_body = evaluate_code_blocks(scope, step.body)
17
-
18
- # Dispatch based on the step class name
19
- step_type = step.class.name.split('::').last.downcase
20
- method = "#{step_type}_step"
21
-
22
- send(method, scope, step_body)
23
- end
24
-
25
- def task_step(scope, step)
26
- task = step['task']
27
- targets = step['targets'] || step['target']
28
- description = step['description']
29
- params = step['parameters'] || {}
30
-
31
- args = if description
32
- [task, targets, description, params]
33
- else
34
- [task, targets, params]
35
- end
36
-
37
- scope.call_function('run_task', args)
38
- end
39
-
40
- def plan_step(scope, step)
41
- plan = step['plan']
42
- parameters = step['parameters'] || {}
43
-
44
- args = [plan, parameters]
45
-
46
- scope.call_function('run_plan', args)
47
- end
48
-
49
- def script_step(scope, step)
50
- script = step['script']
51
- targets = step['targets'] || step['target']
52
- description = step['description']
53
- arguments = step['arguments'] || []
54
-
55
- options = { 'arguments' => arguments }
56
- args = if description
57
- [script, targets, description, options]
58
- else
59
- [script, targets, options]
60
- end
61
-
62
- scope.call_function('run_script', args)
63
- end
64
-
65
- def command_step(scope, step)
66
- command = step['command']
67
- targets = step['targets'] || step['target']
68
- description = step['description']
69
-
70
- args = [command, targets]
71
- args << description if description
72
- scope.call_function('run_command', args)
73
- end
74
-
75
- def upload_step(scope, step)
76
- source = step['upload'] || step['source']
77
- destination = step['destination']
78
- targets = step['targets'] || step['target']
79
- description = step['description']
80
-
81
- args = [source, destination, targets]
82
- args << description if description
83
- scope.call_function('upload_file', args)
84
- end
85
-
86
- def download_step(scope, step)
87
- source = step['download']
88
- destination = step['destination']
89
- targets = step['targets'] || step['target']
90
- description = step['description']
91
-
92
- args = [source, destination, targets]
93
- args << description if description
94
- scope.call_function('download_file', args)
95
- end
96
-
97
- def eval_step(_scope, step)
98
- step['eval']
99
- end
100
-
101
- def resources_step(scope, step)
102
- targets = step['targets'] || step['target']
103
-
104
- # TODO: Only call apply_prep when needed
105
- scope.call_function('apply_prep', targets)
106
- manifest = generate_manifest(step['resources'])
107
-
108
- apply_manifest(scope, targets, manifest)
109
- end
110
-
111
- def message_step(scope, step)
112
- scope.call_function('out::message', [step['message']])
113
- end
114
-
115
- def generate_manifest(resources)
116
- # inspect returns the Ruby representation of the resource hashes,
117
- # which happens to be the same as the Puppet representation
118
- puppet_resources = resources.inspect
119
-
120
- # Because the :tasks setting globally controls which mode the parser
121
- # is in, we need to make this snippet of non-tasks manifest code
122
- # parseable in tasks mode. The way to do that is by putting it in an
123
- # apply statement and taking the body.
124
- <<~MANIFEST
125
- apply('placeholder') {
126
- $resources = #{puppet_resources}
127
- $resources.each |$res| {
128
- Resource[$res['type']] { $res['title']:
129
- * => $res['parameters'],
130
- }
131
- }
132
-
133
- # Add relationships if there is more than one resource
134
- if $resources.length > 1 {
135
- ($resources.length - 1).each |$index| {
136
- $lhs = $resources[$index]
137
- $rhs = $resources[$index+1]
138
- $lhs_resource = Resource[$lhs['type'] , $lhs['title']]
139
- $rhs_resource = Resource[$rhs['type'] , $rhs['title']]
140
- $lhs_resource -> $rhs_resource
141
- }
142
- }
143
- }
144
- MANIFEST
145
- end
146
-
147
- def apply_manifest(scope, targets, manifest)
148
- ast = @evaluator.parse_string(manifest)
149
- apply_block = ast.body.body
150
- applicator = Puppet.lookup(:apply_executor)
151
- applicator.apply([targets], apply_block, scope)
152
- end
153
-
154
15
  # This is the method that Puppet calls to evaluate the plan. The name
155
16
  # makes more sense for .pp plans.
17
+ #
156
18
  def evaluate_block_with_bindings(closure_scope, args_hash, plan)
157
- if plan.steps.any? { |step| step.body.key?('target') }
158
- msg = "The 'target' parameter for YAML plan steps is deprecated and will be removed "\
159
- "in a future version of Bolt. Use the 'targets' parameter instead."
160
- Bolt::Logger.deprecate("yaml_plan_target", msg)
161
- end
162
-
163
- if plan.steps.any? { |step| step.body.key?('source') }
164
- msg = "The 'source' parameter for YAML plan upload steps is deprecated and will be removed "\
165
- "in a future version of Bolt. Use the 'upload' parameter instead."
166
- Bolt::Logger.deprecate("yaml_plan_source", msg)
167
- end
168
-
169
19
  plan_result = closure_scope.with_local_scope(args_hash) do |scope|
170
20
  plan.steps.each do |step|
171
- step_result = dispatch_step(scope, step)
21
+ step_result = step.evaluate(scope, self)
172
22
 
173
- scope.setvar(step.name, step_result) if step.name
23
+ scope.setvar(step.body['name'], step_result) if step.body['name']
174
24
  end
175
25
 
176
26
  evaluate_code_blocks(scope, plan.return)
@@ -180,6 +30,7 @@ module Bolt
180
30
  end
181
31
 
182
32
  # Recursively evaluate any EvaluableString instances in the object.
33
+ #
183
34
  def evaluate_code_blocks(scope, value)
184
35
  # XXX We should establish a local scope here probably
185
36
  case value
@@ -204,6 +55,7 @@ module Bolt
204
55
  # Occasionally the Closure will ask us to evaluate what it assumes are
205
56
  # AST objects. Because we've sidestepped the AST, they aren't, so just
206
57
  # return the values as already evaluated.
58
+ #
207
59
  def evaluate(value, _scope)
208
60
  value
209
61
  end
@@ -6,41 +6,56 @@ module Bolt
6
6
  class PAL
7
7
  class YamlPlan
8
8
  class Step
9
- attr_reader :name, :type, :body, :targets
10
-
11
- def self.allowed_keys
12
- Set['name', 'description', 'target', 'targets']
13
- end
9
+ attr_reader :body
14
10
 
15
11
  STEP_KEYS = %w[
16
12
  command
17
- destination
18
13
  download
19
14
  eval
20
15
  message
21
16
  plan
22
17
  resources
23
18
  script
24
- source
25
19
  task
26
20
  upload
27
21
  ].freeze
28
22
 
23
+ class StepError < Bolt::Error
24
+ def initialize(message, name, step_number)
25
+ identifier = name ? name.inspect : "number #{step_number}"
26
+ error = "Parse error in step #{identifier}: \n #{message}"
27
+
28
+ super(error, 'bolt/invalid-plan')
29
+ end
30
+ end
31
+
32
+ # Keys that are allowed for the step
33
+ #
34
+ def self.allowed_keys
35
+ required_keys + option_keys + Set['name', 'description', 'targets']
36
+ end
37
+
38
+ # Keys that translate to metaparameters for the plan step's function call
39
+ #
40
+ def self.option_keys
41
+ Set.new
42
+ end
43
+
44
+ # Keys that are required for the step
45
+ #
46
+ def self.required_keys
47
+ Set.new
48
+ end
49
+
29
50
  def self.create(step_body, step_number)
30
51
  type_keys = (STEP_KEYS & step_body.keys)
31
52
  case type_keys.length
32
53
  when 0
33
- raise step_error("No valid action detected", step_body['name'], step_number)
54
+ raise StepError.new("No valid action detected", step_body['name'], step_number)
34
55
  when 1
35
56
  type = type_keys.first
36
57
  else
37
- if [Set['source', 'destination'], Set['upload', 'destination']].include?(type_keys.to_set)
38
- type = 'upload'
39
- elsif type_keys.to_set == Set['download', 'destination']
40
- type = 'download'
41
- else
42
- raise step_error("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
43
- end
58
+ raise StepError.new("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
44
59
  end
45
60
 
46
61
  step_class = const_get("Bolt::PAL::YamlPlan::Step::#{type.capitalize}")
@@ -48,15 +63,49 @@ module Bolt
48
63
  step_class.new(step_body)
49
64
  end
50
65
 
51
- def initialize(step_body)
52
- @name = step_body['name']
53
- @description = step_body['description']
54
- @targets = step_body['targets'] || step_body['target']
55
- @body = step_body
66
+ def initialize(body)
67
+ @body = body
56
68
  end
57
69
 
70
+ # Transpiles the step into the plan language
71
+ #
58
72
  def transpile
59
- raise NotImplementedError, "Step #{@name} does not supported conversion to Puppet plan language"
73
+ code = String.new(" ")
74
+ code << "$#{body['name']} = " if body['name']
75
+ code << function_call(function, format_args(body))
76
+ code << "\n"
77
+ end
78
+
79
+ # Evaluates the step
80
+ #
81
+ def evaluate(scope, evaluator)
82
+ evaluated = evaluator.evaluate_code_blocks(scope, body)
83
+ scope.call_function(function, format_args(evaluated))
84
+ end
85
+
86
+ # Formats a list of args from the provided body
87
+ #
88
+ private def format_args(_body)
89
+ raise NotImplementedError, "Step class #{self.class} does not implement #format_args"
90
+ end
91
+
92
+ # Returns the step's corresponding Puppet language function call
93
+ #
94
+ private def function_call(function, args)
95
+ code_args = args.map { |arg| Bolt::Util.to_code(arg) }
96
+ "#{function}(#{code_args.join(', ')})"
97
+ end
98
+
99
+ # The function that corresponds to the step
100
+ #
101
+ private def function
102
+ raise NotImplementedError, "Step class #{self.class} does not implement #function"
103
+ end
104
+
105
+ # Returns a hash of options formatted for function calls
106
+ #
107
+ private def format_options(body)
108
+ body.slice(*self.class.option_keys).transform_keys { |key| "_#{key}" }
60
109
  end
61
110
 
62
111
  def self.validate(body, step_number)
@@ -65,19 +114,35 @@ module Bolt
65
114
  begin
66
115
  body.each { |k, v| validate_puppet_code(k, v) }
67
116
  rescue Bolt::Error => e
68
- raise step_error(e.msg, body['name'], step_number)
117
+ raise StepError.new(e.msg, body['name'], step_number)
118
+ end
119
+
120
+ if body.key?('parameters')
121
+ unless body['parameters'].is_a?(Hash)
122
+ raise StepError.new("Parameters key must be a hash", body['name'], step_number)
123
+ end
124
+
125
+ metaparams = option_keys.map { |key| "_#{key}" }
126
+
127
+ if (dups = body['parameters'].keys & metaparams).any?
128
+ raise StepError.new(
129
+ "Cannot specify metaparameters when using top-level keys with same name: #{dups.join(', ')}",
130
+ body['name'],
131
+ step_number
132
+ )
133
+ end
69
134
  end
70
135
 
71
136
  unless body.fetch('parameters', {}).is_a?(Hash)
72
137
  msg = "Parameters key must be a hash"
73
- raise step_error(msg, body['name'], step_number)
138
+ raise StepError.new(msg, body['name'], step_number)
74
139
  end
75
140
 
76
141
  if body.key?('name')
77
142
  name = body['name']
78
143
  unless name.is_a?(String) && name.match?(Bolt::PAL::YamlPlan::VAR_NAME_PATTERN)
79
144
  error_message = "Invalid step name: #{name.inspect}"
80
- raise step_error(error_message, body['name'], step_number)
145
+ raise StepError.new(error_message, body['name'], step_number)
81
146
  end
82
147
  end
83
148
  end
@@ -89,30 +154,15 @@ module Bolt
89
154
  illegal_keys = body.keys.to_set - allowed_keys
90
155
  if illegal_keys.any?
91
156
  error_message = "The #{step_type.inspect} step does not support: #{illegal_keys.to_a.inspect} key(s)"
92
- err = step_error(error_message, body['name'], step_number)
93
- raise Bolt::Error.new(err, "bolt/invalid-plan")
157
+ raise StepError.new(error_message, body['name'], step_number)
94
158
  end
95
159
 
96
160
  # Ensure all required keys are present
97
161
  missing_keys = required_keys - body.keys
98
162
 
99
- # Handle cases where steps with a required 'targets' key are using the deprecated
100
- # 'target' key instead.
101
- # TODO: Remove this when 'target' is removed
102
- if body.include?('target')
103
- missing_keys -= ['targets']
104
- end
105
-
106
- # Handle cases where upload step uses deprecated 'source' key instead of 'upload'
107
- # TODO: Remove when 'source' is removed
108
- if body.include?('source')
109
- missing_keys -= ['upload']
110
- end
111
-
112
163
  if missing_keys.any?
113
164
  error_message = "The #{step_type.inspect} step requires: #{missing_keys.to_a.inspect} key(s)"
114
- err = step_error(error_message, body['name'], step_number)
115
- raise Bolt::Error.new(err, "bolt/invalid-plan")
165
+ raise StepError.new(error_message, body['name'], step_number)
116
166
  end
117
167
  end
118
168
 
@@ -144,12 +194,6 @@ module Bolt
144
194
  raise Bolt::Error.new("Error parsing #{step_key.inspect}: #{e.basic_message}", "bolt/invalid-plan")
145
195
  end
146
196
 
147
- def self.step_error(message, name, step_number)
148
- identifier = name ? name.inspect : "number #{step_number}"
149
- error = "Parse error in step #{identifier}: \n #{message}"
150
- Bolt::Error.new(error, 'bolt/invalid-plan')
151
- end
152
-
153
197
  # Parses the an evaluable string, optionally quote it before parsing
154
198
  def self.parse_code_string(code, quote = false)
155
199
  if quote
@@ -159,11 +203,6 @@ module Bolt
159
203
  Puppet::Pops::Parser::EvaluatingParser.new.parse_string(code)
160
204
  end
161
205
  end
162
-
163
- def function_call(function, args)
164
- code_args = args.map { |arg| Bolt::Util.to_code(arg) }
165
- "#{function}(#{code_args.join(', ')})"
166
- end
167
206
  end
168
207
  end
169
208
  end