bolt 2.42.0 → 3.3.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +21 -19
  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/parallelize.rb +6 -8
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -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/lib/bolt/analytics.rb +3 -2
  12. data/lib/bolt/applicator.rb +11 -1
  13. data/lib/bolt/apply_result.rb +1 -1
  14. data/lib/bolt/bolt_option_parser.rb +9 -116
  15. data/lib/bolt/catalog.rb +10 -29
  16. data/lib/bolt/cli.rb +90 -154
  17. data/lib/bolt/config.rb +66 -239
  18. data/lib/bolt/config/options.rb +79 -102
  19. data/lib/bolt/config/transport/local.rb +1 -0
  20. data/lib/bolt/config/transport/lxd.rb +21 -0
  21. data/lib/bolt/config/transport/options.rb +9 -2
  22. data/lib/bolt/config/transport/orch.rb +1 -0
  23. data/lib/bolt/executor.rb +23 -6
  24. data/lib/bolt/inventory.rb +1 -1
  25. data/lib/bolt/inventory/group.rb +7 -4
  26. data/lib/bolt/logger.rb +123 -11
  27. data/lib/bolt/module_installer.rb +6 -4
  28. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  29. data/lib/bolt/module_installer/resolver.rb +59 -14
  30. data/lib/bolt/module_installer/specs/forge_spec.rb +10 -4
  31. data/lib/bolt/module_installer/specs/git_spec.rb +19 -4
  32. data/lib/bolt/outputter/human.rb +56 -17
  33. data/lib/bolt/outputter/json.rb +16 -16
  34. data/lib/bolt/outputter/rainbow.rb +3 -3
  35. data/lib/bolt/pal.rb +95 -15
  36. data/lib/bolt/pal/yaml_plan.rb +9 -4
  37. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
  38. data/lib/bolt/pal/yaml_plan/step.rb +91 -52
  39. data/lib/bolt/pal/yaml_plan/step/command.rb +16 -16
  40. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  41. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  42. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  43. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  44. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  45. data/lib/bolt/pal/yaml_plan/step/script.rb +32 -17
  46. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  47. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  48. data/lib/bolt/pal/yaml_plan/transpiler.rb +2 -1
  49. data/lib/bolt/plan_creator.rb +1 -1
  50. data/lib/bolt/plugin.rb +2 -2
  51. data/lib/bolt/plugin/cache.rb +7 -7
  52. data/lib/bolt/plugin/module.rb +0 -23
  53. data/lib/bolt/plugin/puppet_connect_data.rb +77 -0
  54. data/lib/bolt/plugin/puppetdb.rb +1 -1
  55. data/lib/bolt/project.rb +54 -81
  56. data/lib/bolt/project_manager.rb +5 -4
  57. data/lib/bolt/project_manager/module_migrator.rb +7 -6
  58. data/lib/bolt/rerun.rb +1 -1
  59. data/lib/bolt/result.rb +6 -1
  60. data/lib/bolt/shell.rb +16 -0
  61. data/lib/bolt/shell/bash.rb +57 -25
  62. data/lib/bolt/shell/bash/tmpdir.rb +6 -3
  63. data/lib/bolt/shell/powershell.rb +33 -10
  64. data/lib/bolt/shell/powershell/snippets.rb +37 -150
  65. data/lib/bolt/task.rb +2 -2
  66. data/lib/bolt/transport/base.rb +0 -9
  67. data/lib/bolt/transport/docker.rb +1 -125
  68. data/lib/bolt/transport/docker/connection.rb +86 -161
  69. data/lib/bolt/transport/local.rb +1 -9
  70. data/lib/bolt/transport/lxd.rb +26 -0
  71. data/lib/bolt/transport/lxd/connection.rb +99 -0
  72. data/lib/bolt/transport/orch/connection.rb +1 -1
  73. data/lib/bolt/transport/ssh.rb +1 -2
  74. data/lib/bolt/transport/ssh/connection.rb +2 -2
  75. data/lib/bolt/transport/winrm/connection.rb +1 -1
  76. data/lib/bolt/validator.rb +2 -2
  77. data/lib/bolt/version.rb +1 -1
  78. data/lib/bolt_server/config.rb +1 -1
  79. data/lib/bolt_server/transport_app.rb +61 -32
  80. data/lib/bolt_spec/bolt_context.rb +9 -4
  81. data/lib/bolt_spec/plans.rb +1 -109
  82. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  83. data/lib/bolt_spec/plans/mock_executor.rb +4 -0
  84. data/libexec/bolt_catalog +1 -1
  85. data/modules/aggregate/plans/count.pp +21 -0
  86. data/modules/aggregate/plans/targets.pp +21 -0
  87. data/modules/puppet_connect/plans/test_input_data.pp +67 -0
  88. data/modules/puppetdb_fact/plans/init.pp +10 -0
  89. metadata +13 -9
  90. data/modules/aggregate/plans/nodes.pp +0 -36
@@ -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
@@ -5,30 +5,30 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Command < Step
8
- def self.allowed_keys
9
- super + Set['command']
8
+ def self.option_keys
9
+ Set['catch_errors', 'env_vars', 'run_as']
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['targets']
13
+ Set['command', 'targets']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @command = step_body['command']
19
- end
20
-
21
- def transpile
22
- code = String.new(" ")
23
- code << "$#{@name} = " if @name
16
+ # Returns an array of arguments to pass to the step's function call
17
+ #
18
+ private def format_args(body)
19
+ opts = format_options(body)
24
20
 
25
- fn = 'run_command'
26
- args = [@command, @targets]
27
- args << @description if @description
21
+ args = [body['command'], body['targets']]
22
+ args << body['description'] if body['description']
23
+ args << opts if opts.any?
28
24
 
29
- code << function_call(fn, args)
25
+ args
26
+ end
30
27
 
31
- code << "\n"
28
+ # Returns the function corresponding to the step
29
+ #
30
+ private def function
31
+ 'run_command'
32
32
  end
33
33
  end
34
34
  end
@@ -5,31 +5,30 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Download < Step
8
- def self.allowed_keys
9
- super + Set['download', 'destination']
8
+ def self.option_keys
9
+ Set['catch_errors', 'run_as']
10
10
  end
11
11
 
12
12
  def self.required_keys
13
13
  Set['download', 'destination', 'targets']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @source = step_body['download']
19
- @destination = step_body['destination']
20
- end
21
-
22
- def transpile
23
- code = String.new(" ")
24
- code << "$#{@name} = " if @name
16
+ # Returns an array of arguments to pass to the step's function call
17
+ #
18
+ private def format_args(body)
19
+ opts = format_options(body)
25
20
 
26
- fn = 'download_file'
27
- args = [@source, @destination, @targets]
28
- args << @description if @description
21
+ args = [body['download'], body['destination'], body['targets']]
22
+ args << body['description'] if body['description']
23
+ args << opts if opts.any?
29
24
 
30
- code << function_call(fn, args)
25
+ args
26
+ end
31
27
 
32
- code << "\n"
28
+ # Returns the function corresponding to the step
29
+ #
30
+ private def function
31
+ 'download_file'
33
32
  end
34
33
  end
35
34
  end
@@ -5,28 +5,28 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Eval < Step
8
- def self.allowed_keys
9
- super + Set['eval']
10
- end
11
-
12
8
  def self.required_keys
13
- Set.new
9
+ Set['eval']
14
10
  end
15
11
 
16
- def initialize(step_body)
17
- super
18
- @eval = step_body['eval']
12
+ # Evaluates the step
13
+ #
14
+ def evaluate(scope, evaluator)
15
+ evaluated = evaluator.evaluate_code_blocks(scope, body)
16
+ evaluated['eval']
19
17
  end
20
18
 
19
+ # Transpiles the step into the plan language
20
+ #
21
21
  def transpile
22
22
  code = String.new(" ")
23
- code << "$#{@name} = " if @name
23
+ code << "$#{body['name']} = " if body['name']
24
24
 
25
- code_body = Bolt::Util.to_code(@eval)
25
+ code_body = Bolt::Util.to_code(body['eval']) || 'undef'
26
26
 
27
27
  # If we're trying to assign the result of a multi-line eval to a name
28
28
  # variable, we need to wrap it in `with()`.
29
- if @name && code_body.lines.count > 1
29
+ if body['name'] && code_body.lines.count > 1
30
30
  indented = code_body.gsub(/\n/, "\n ").chomp(" ")
31
31
  code << "with() || {\n #{indented}}"
32
32
  else
@@ -13,14 +13,23 @@ module Bolt
13
13
  Set['message']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @message = step_body['message']
16
+ # Returns an array of arguments to pass to the step's function call
17
+ #
18
+ private def format_args(body)
19
+ [body['message']]
19
20
  end
20
21
 
22
+ # Returns the function corresponding to the step
23
+ #
24
+ private def function
25
+ 'out::message'
26
+ end
27
+
28
+ # Transpiles the step into the plan language
29
+ #
21
30
  def transpile
22
31
  code = String.new(" ")
23
- code << function_call('out::message', [@message])
32
+ code << function_call(function, format_args(body))
24
33
  code << "\n"
25
34
  end
26
35
  end
@@ -6,30 +6,34 @@ module Bolt
6
6
  class Step
7
7
  class Plan < Step
8
8
  def self.allowed_keys
9
- super + Set['plan', 'parameters']
9
+ super + Set['parameters']
10
10
  end
11
11
 
12
- def self.required_keys
13
- Set.new
12
+ def self.option_keys
13
+ Set['catch_errors', 'run_as']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @plan = step_body['plan']
19
- @parameters = step_body.fetch('parameters', {})
16
+ def self.required_keys
17
+ Set['plan']
20
18
  end
21
19
 
22
- def transpile
23
- code = String.new(" ")
24
- code << "$#{@name} = " if @name
20
+ # Returns an array of arguments to pass to the step's function call
21
+ #
22
+ private def format_args(body)
23
+ opts = format_options(body)
24
+ params = (body['parameters'] || {}).merge(opts)
25
25
 
26
- fn = 'run_plan'
27
- args = [@plan]
28
- args << @parameters unless @parameters.empty?
26
+ args = [body['plan']]
27
+ args << body['targets'] if body['targets']
28
+ args << params if params.any?
29
29
 
30
- code << function_call(fn, args)
30
+ args
31
+ end
31
32
 
32
- code << "\n"
33
+ # Returns the function corresponding to the step
34
+ #
35
+ private def function
36
+ 'run_plan'
33
37
  end
34
38
  end
35
39
  end
@@ -5,18 +5,81 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Resources < Step
8
- def self.allowed_keys
9
- super + Set['resources']
8
+ def self.option_keys
9
+ Set['catch_errors', 'description', 'noop', 'run_as']
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['targets']
13
+ Set['resources', 'targets']
14
14
  end
15
15
 
16
- def initialize(step_body)
16
+ def initialize(body)
17
17
  super
18
- @resources = step_body['resources']
19
- @normalized_resources = normalize_resources(@resources)
18
+ @body['resources'] = normalize_resources(@body['resources'])
19
+ end
20
+
21
+ # Returns an array of arguments to pass to the apply function call
22
+ #
23
+ private def format_args(body)
24
+ opts = format_options(body)
25
+
26
+ args = [body['targets']]
27
+ args << opts if opts.any?
28
+
29
+ args
30
+ end
31
+
32
+ def evaluate(scope, evaluator)
33
+ evaluated = evaluator.evaluate_code_blocks(scope, body)
34
+
35
+ scope.call_function('apply_prep', evaluated['targets'])
36
+
37
+ apply_args = format_args(evaluated)
38
+ manifest = generate_manifest(evaluated['resources'])
39
+ apply_manifest(scope, apply_args, manifest)
40
+ end
41
+
42
+ # Generates a manifest from the resources
43
+ #
44
+ private def generate_manifest(resources)
45
+ # inspect returns the Ruby representation of the resource hashes,
46
+ # which happens to be the same as the Puppet representation
47
+ puppet_resources = resources.inspect
48
+
49
+ # Because the :tasks setting globally controls which mode the parser
50
+ # is in, we need to make this snippet of non-tasks manifest code
51
+ # parseable in tasks mode. The way to do that is by putting it in an
52
+ # apply statement and taking the body.
53
+ <<~MANIFEST
54
+ apply('placeholder') {
55
+ $resources = #{puppet_resources}
56
+ $resources.each |$res| {
57
+ Resource[$res['type']] { $res['title']:
58
+ * => $res['parameters'],
59
+ }
60
+ }
61
+
62
+ # Add relationships if there is more than one resource
63
+ if $resources.length > 1 {
64
+ ($resources.length - 1).each |$index| {
65
+ $lhs = $resources[$index]
66
+ $rhs = $resources[$index+1]
67
+ $lhs_resource = Resource[$lhs['type'] , $lhs['title']]
68
+ $rhs_resource = Resource[$rhs['type'] , $rhs['title']]
69
+ $lhs_resource -> $rhs_resource
70
+ }
71
+ }
72
+ }
73
+ MANIFEST
74
+ end
75
+
76
+ # Applies the manifest block on the targets
77
+ #
78
+ private def apply_manifest(scope, args, manifest)
79
+ ast = self.class.parse_code_string(manifest)
80
+ apply_block = ast.body.body
81
+ applicator = Puppet.lookup(:apply_executor)
82
+ applicator.apply(args, apply_block, scope)
20
83
  end
21
84
 
22
85
  def self.validate(body, step_number)
@@ -26,26 +89,28 @@ module Bolt
26
89
  if resource['type'] || resource['title']
27
90
  if !resource['type']
28
91
  err = "Resource declaration must include type key if title key is set"
29
- raise step_error(err, body['name'], step_number)
92
+ raise StepError.new(err, body['name'], step_number)
30
93
  elsif !resource['title']
31
94
  err = "Resource declaration must include title key if type key is set"
32
- raise step_error(err, body['name'], step_number)
95
+ raise StepError.new(err, body['name'], step_number)
33
96
  end
34
97
  else
35
98
  type_keys = (resource.keys - ['parameters'])
36
99
  if type_keys.empty?
37
100
  err = "Resource declaration is missing a type"
38
- raise step_error(err, body['name'], step_number)
101
+ raise StepError.new(err, body['name'], step_number)
39
102
  elsif type_keys.length > 1
40
103
  err = "Resource declaration has ambiguous type: could be #{type_keys.join(' or ')}"
41
- raise step_error(err, body['name'], step_number)
104
+ raise StepError.new(err, body['name'], step_number)
42
105
  end
43
106
  end
44
107
  end
45
108
  end
46
109
 
110
+ # Normalizes the resources so they are in a format compatible with apply blocks
47
111
  # What if this comes from a code block?
48
- def normalize_resources(resources)
112
+ #
113
+ private def normalize_resources(resources)
49
114
  resources.map do |resource|
50
115
  if resource['type'] && resource['title']
51
116
  type = resource['type']
@@ -59,25 +124,21 @@ module Bolt
59
124
  end
60
125
  end
61
126
 
62
- def body
63
- @body.merge('resources' => @normalized_resources)
64
- end
65
-
66
127
  def transpile
67
128
  code = StringIO.new
68
129
 
69
130
  code.print " "
70
- fn = 'apply_prep'
71
- args = [@targets]
72
- code << function_call(fn, args)
131
+ code << function_call('apply_prep', [body['targets']])
73
132
  code.print "\n"
74
133
 
75
134
  code.print " "
76
- code.print "$#{@name} = " if @name
135
+ code.print "$#{body['name']} = " if body['name']
136
+
137
+ code << function_call('apply', format_args(body))
77
138
 
78
- code.puts "apply(#{Bolt::Util.to_code(@targets)}) {"
139
+ code.print " {\n"
79
140
 
80
- declarations = @normalized_resources.map do |resource|
141
+ declarations = body['resources'].map do |resource|
81
142
  type = resource['type'].is_a?(EvaluableString) ? resource['type'].value : resource['type']
82
143
  title = Bolt::Util.to_code(resource['title'])
83
144
  parameters = resource['parameters'].transform_values do |val|