bolt 3.0.1 → 3.6.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +13 -11
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
  9. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  10. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  11. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
  12. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  13. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  14. data/lib/bolt/analytics.rb +4 -8
  15. data/lib/bolt/apply_result.rb +1 -1
  16. data/lib/bolt/bolt_option_parser.rb +6 -3
  17. data/lib/bolt/cli.rb +123 -37
  18. data/lib/bolt/config.rb +15 -7
  19. data/lib/bolt/config/options.rb +62 -12
  20. data/lib/bolt/config/transport/lxd.rb +23 -0
  21. data/lib/bolt/config/transport/options.rb +8 -1
  22. data/lib/bolt/config/transport/podman.rb +33 -0
  23. data/lib/bolt/container_result.rb +105 -0
  24. data/lib/bolt/error.rb +15 -0
  25. data/lib/bolt/executor.rb +37 -18
  26. data/lib/bolt/inventory/options.rb +9 -0
  27. data/lib/bolt/inventory/target.rb +16 -0
  28. data/lib/bolt/logger.rb +8 -0
  29. data/lib/bolt/module_installer.rb +2 -2
  30. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  31. data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
  32. data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
  33. data/lib/bolt/node/output.rb +14 -4
  34. data/lib/bolt/outputter/human.rb +259 -90
  35. data/lib/bolt/outputter/json.rb +3 -1
  36. data/lib/bolt/outputter/logger.rb +17 -0
  37. data/lib/bolt/pal.rb +25 -4
  38. data/lib/bolt/pal/yaml_plan.rb +1 -2
  39. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -141
  40. data/lib/bolt/pal/yaml_plan/step.rb +91 -31
  41. data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
  42. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  43. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  44. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  45. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  46. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  47. data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
  48. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  49. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  50. data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
  51. data/lib/bolt/plan_creator.rb +1 -1
  52. data/lib/bolt/plugin.rb +13 -11
  53. data/lib/bolt/project_manager.rb +1 -1
  54. data/lib/bolt/project_manager/module_migrator.rb +1 -1
  55. data/lib/bolt/result.rb +11 -15
  56. data/lib/bolt/shell.rb +16 -0
  57. data/lib/bolt/shell/bash.rb +61 -31
  58. data/lib/bolt/shell/bash/tmpdir.rb +2 -2
  59. data/lib/bolt/shell/powershell.rb +34 -12
  60. data/lib/bolt/shell/powershell/snippets.rb +30 -3
  61. data/lib/bolt/task.rb +1 -1
  62. data/lib/bolt/transport/base.rb +0 -9
  63. data/lib/bolt/transport/docker.rb +2 -126
  64. data/lib/bolt/transport/docker/connection.rb +81 -167
  65. data/lib/bolt/transport/lxd.rb +26 -0
  66. data/lib/bolt/transport/lxd/connection.rb +99 -0
  67. data/lib/bolt/transport/orch.rb +13 -5
  68. data/lib/bolt/transport/podman.rb +19 -0
  69. data/lib/bolt/transport/podman/connection.rb +98 -0
  70. data/lib/bolt/transport/ssh/connection.rb +1 -1
  71. data/lib/bolt/transport/winrm/connection.rb +1 -1
  72. data/lib/bolt/util.rb +42 -0
  73. data/lib/bolt/version.rb +1 -1
  74. data/lib/bolt_server/transport_app.rb +64 -33
  75. data/lib/bolt_spec/bolt_context.rb +9 -4
  76. data/lib/bolt_spec/plans.rb +1 -109
  77. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  78. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  79. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  80. data/lib/bolt_spec/plans/mock_executor.rb +91 -7
  81. data/modules/puppet_connect/plans/test_input_data.pp +65 -7
  82. metadata +12 -2
@@ -116,7 +116,9 @@ module Bolt
116
116
  )
117
117
  end
118
118
 
119
- def print_target_info(targets)
119
+ def print_target_info(target_list, _inventoryfile)
120
+ targets = target_list.values.flatten
121
+
120
122
  @stream.puts ::JSON.pretty_generate(
121
123
  targets: targets.map(&:detail),
122
124
  count: targets.count
@@ -20,6 +20,10 @@ module Bolt
20
20
  log_plan_start(event)
21
21
  when :plan_finish
22
22
  log_plan_finish(event)
23
+ when :container_start
24
+ log_container_start(event)
25
+ when :container_finish
26
+ log_container_finish(event)
23
27
  end
24
28
  end
25
29
 
@@ -48,6 +52,19 @@ module Bolt
48
52
  duration = event[:duration]
49
53
  @logger.info("Finished: plan #{plan} in #{duration.round(2)} sec")
50
54
  end
55
+
56
+ def log_container_start(event)
57
+ @logger.info("Starting: run container '#{event[:image]}'")
58
+ end
59
+
60
+ def log_container_finish(event)
61
+ result = event[:result]
62
+ if result.success?
63
+ @logger.info("Finished: run container '#{result.object}' succeeded.")
64
+ else
65
+ @logger.info("Finished: run container '#{result.object}' failed.")
66
+ end
67
+ end
51
68
  end
52
69
  end
53
70
  end
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,7 +446,7 @@ 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",
@@ -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,153 +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']
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']
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']
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']
77
- destination = step['destination']
78
- targets = step['targets']
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']
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']
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
19
  plan_result = closure_scope.with_local_scope(args_hash) do |scope|
158
20
  plan.steps.each do |step|
159
- step_result = dispatch_step(scope, step)
21
+ step_result = step.evaluate(scope, self)
160
22
 
161
- scope.setvar(step.name, step_result) if step.name
23
+ scope.setvar(step.body['name'], step_result) if step.body['name']
162
24
  end
163
25
 
164
26
  evaluate_code_blocks(scope, plan.return)
@@ -168,6 +30,7 @@ module Bolt
168
30
  end
169
31
 
170
32
  # Recursively evaluate any EvaluableString instances in the object.
33
+ #
171
34
  def evaluate_code_blocks(scope, value)
172
35
  # XXX We should establish a local scope here probably
173
36
  case value
@@ -192,6 +55,7 @@ module Bolt
192
55
  # Occasionally the Closure will ask us to evaluate what it assumes are
193
56
  # AST objects. Because we've sidestepped the AST, they aren't, so just
194
57
  # return the values as already evaluated.
58
+ #
195
59
  def evaluate(value, _scope)
196
60
  value
197
61
  end
@@ -6,11 +6,7 @@ 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', 'targets']
13
- end
9
+ attr_reader :body
14
10
 
15
11
  STEP_KEYS = %w[
16
12
  command
@@ -24,15 +20,42 @@ module Bolt
24
20
  upload
25
21
  ].freeze
26
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
+
27
50
  def self.create(step_body, step_number)
28
51
  type_keys = (STEP_KEYS & step_body.keys)
29
52
  case type_keys.length
30
53
  when 0
31
- 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)
32
55
  when 1
33
56
  type = type_keys.first
34
57
  else
35
- raise step_error("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
58
+ raise StepError.new("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
36
59
  end
37
60
 
38
61
  step_class = const_get("Bolt::PAL::YamlPlan::Step::#{type.capitalize}")
@@ -40,15 +63,49 @@ module Bolt
40
63
  step_class.new(step_body)
41
64
  end
42
65
 
43
- def initialize(step_body)
44
- @name = step_body['name']
45
- @description = step_body['description']
46
- @targets = step_body['targets']
47
- @body = step_body
66
+ def initialize(body)
67
+ @body = body
48
68
  end
49
69
 
70
+ # Transpiles the step into the plan language
71
+ #
50
72
  def transpile
51
- 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}" }
52
109
  end
53
110
 
54
111
  def self.validate(body, step_number)
@@ -57,19 +114,35 @@ module Bolt
57
114
  begin
58
115
  body.each { |k, v| validate_puppet_code(k, v) }
59
116
  rescue Bolt::Error => e
60
- 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
61
134
  end
62
135
 
63
136
  unless body.fetch('parameters', {}).is_a?(Hash)
64
137
  msg = "Parameters key must be a hash"
65
- raise step_error(msg, body['name'], step_number)
138
+ raise StepError.new(msg, body['name'], step_number)
66
139
  end
67
140
 
68
141
  if body.key?('name')
69
142
  name = body['name']
70
143
  unless name.is_a?(String) && name.match?(Bolt::PAL::YamlPlan::VAR_NAME_PATTERN)
71
144
  error_message = "Invalid step name: #{name.inspect}"
72
- raise step_error(error_message, body['name'], step_number)
145
+ raise StepError.new(error_message, body['name'], step_number)
73
146
  end
74
147
  end
75
148
  end
@@ -81,8 +154,7 @@ module Bolt
81
154
  illegal_keys = body.keys.to_set - allowed_keys
82
155
  if illegal_keys.any?
83
156
  error_message = "The #{step_type.inspect} step does not support: #{illegal_keys.to_a.inspect} key(s)"
84
- err = step_error(error_message, body['name'], step_number)
85
- raise Bolt::Error.new(err, "bolt/invalid-plan")
157
+ raise StepError.new(error_message, body['name'], step_number)
86
158
  end
87
159
 
88
160
  # Ensure all required keys are present
@@ -90,8 +162,7 @@ module Bolt
90
162
 
91
163
  if missing_keys.any?
92
164
  error_message = "The #{step_type.inspect} step requires: #{missing_keys.to_a.inspect} key(s)"
93
- err = step_error(error_message, body['name'], step_number)
94
- raise Bolt::Error.new(err, "bolt/invalid-plan")
165
+ raise StepError.new(error_message, body['name'], step_number)
95
166
  end
96
167
  end
97
168
 
@@ -123,12 +194,6 @@ module Bolt
123
194
  raise Bolt::Error.new("Error parsing #{step_key.inspect}: #{e.basic_message}", "bolt/invalid-plan")
124
195
  end
125
196
 
126
- def self.step_error(message, name, step_number)
127
- identifier = name ? name.inspect : "number #{step_number}"
128
- error = "Parse error in step #{identifier}: \n #{message}"
129
- Bolt::Error.new(error, 'bolt/invalid-plan')
130
- end
131
-
132
197
  # Parses the an evaluable string, optionally quote it before parsing
133
198
  def self.parse_code_string(code, quote = false)
134
199
  if quote
@@ -138,11 +203,6 @@ module Bolt
138
203
  Puppet::Pops::Parser::EvaluatingParser.new.parse_string(code)
139
204
  end
140
205
  end
141
-
142
- def function_call(function, args)
143
- code_args = args.map { |arg| Bolt::Util.to_code(arg) }
144
- "#{function}(#{code_args.join(', ')})"
145
- end
146
206
  end
147
207
  end
148
208
  end