openbolt 5.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. checksums.yaml +7 -0
  2. data/Puppetfile +52 -0
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +60 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
  5. data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
  6. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +71 -0
  7. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +55 -0
  8. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +65 -0
  9. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +93 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +33 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +38 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +208 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/background.rb +62 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +57 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +130 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +31 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +52 -0
  18. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +87 -0
  19. data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +34 -0
  20. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +35 -0
  21. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +74 -0
  22. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +97 -0
  23. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +47 -0
  24. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +52 -0
  25. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +40 -0
  26. data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +42 -0
  27. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
  28. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +106 -0
  29. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  30. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +291 -0
  31. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +145 -0
  32. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +164 -0
  33. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +211 -0
  34. data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +48 -0
  35. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +43 -0
  36. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +145 -0
  37. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +38 -0
  38. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +101 -0
  39. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +29 -0
  40. data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +131 -0
  41. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +59 -0
  42. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +39 -0
  43. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +50 -0
  44. data/bolt-modules/boltlib/types/planresult.pp +18 -0
  45. data/bolt-modules/boltlib/types/targetspec.pp +7 -0
  46. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +42 -0
  47. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +20 -0
  48. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
  49. data/bolt-modules/file/lib/puppet/functions/file/delete.rb +21 -0
  50. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +28 -0
  51. data/bolt-modules/file/lib/puppet/functions/file/join.rb +20 -0
  52. data/bolt-modules/file/lib/puppet/functions/file/read.rb +33 -0
  53. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +28 -0
  54. data/bolt-modules/file/lib/puppet/functions/file/write.rb +24 -0
  55. data/bolt-modules/log/lib/puppet/functions/log/debug.rb +39 -0
  56. data/bolt-modules/log/lib/puppet/functions/log/error.rb +40 -0
  57. data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +40 -0
  58. data/bolt-modules/log/lib/puppet/functions/log/info.rb +39 -0
  59. data/bolt-modules/log/lib/puppet/functions/log/trace.rb +39 -0
  60. data/bolt-modules/log/lib/puppet/functions/log/warn.rb +41 -0
  61. data/bolt-modules/out/lib/puppet/functions/out/message.rb +36 -0
  62. data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +35 -0
  63. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  64. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +65 -0
  65. data/bolt-modules/system/lib/puppet/functions/system/env.rb +20 -0
  66. data/exe/bolt +17 -0
  67. data/guides/debugging.yaml +27 -0
  68. data/guides/inventory.yaml +23 -0
  69. data/guides/links.yaml +12 -0
  70. data/guides/logging.yaml +17 -0
  71. data/guides/module.yaml +18 -0
  72. data/guides/modulepath.yaml +24 -0
  73. data/guides/project.yaml +21 -0
  74. data/guides/targets.yaml +28 -0
  75. data/guides/transports.yaml +22 -0
  76. data/lib/bolt/analytics.rb +233 -0
  77. data/lib/bolt/application.rb +806 -0
  78. data/lib/bolt/applicator.rb +368 -0
  79. data/lib/bolt/apply_inventory.rb +93 -0
  80. data/lib/bolt/apply_result.rb +154 -0
  81. data/lib/bolt/apply_target.rb +90 -0
  82. data/lib/bolt/bolt_option_parser.rb +1226 -0
  83. data/lib/bolt/catalog/logging.rb +15 -0
  84. data/lib/bolt/catalog.rb +144 -0
  85. data/lib/bolt/cli.rb +949 -0
  86. data/lib/bolt/config/modulepath.rb +30 -0
  87. data/lib/bolt/config/options.rb +673 -0
  88. data/lib/bolt/config/transport/base.rb +133 -0
  89. data/lib/bolt/config/transport/docker.rb +34 -0
  90. data/lib/bolt/config/transport/jail.rb +33 -0
  91. data/lib/bolt/config/transport/local.rb +39 -0
  92. data/lib/bolt/config/transport/lxd.rb +34 -0
  93. data/lib/bolt/config/transport/options.rb +431 -0
  94. data/lib/bolt/config/transport/orch.rb +41 -0
  95. data/lib/bolt/config/transport/podman.rb +33 -0
  96. data/lib/bolt/config/transport/remote.rb +24 -0
  97. data/lib/bolt/config/transport/ssh.rb +138 -0
  98. data/lib/bolt/config/transport/winrm.rb +63 -0
  99. data/lib/bolt/config.rb +515 -0
  100. data/lib/bolt/container_result.rb +105 -0
  101. data/lib/bolt/error.rb +194 -0
  102. data/lib/bolt/executor.rb +539 -0
  103. data/lib/bolt/fiber_executor.rb +190 -0
  104. data/lib/bolt/inventory/group.rb +446 -0
  105. data/lib/bolt/inventory/inventory.rb +391 -0
  106. data/lib/bolt/inventory/options.rb +139 -0
  107. data/lib/bolt/inventory/target.rb +293 -0
  108. data/lib/bolt/inventory.rb +120 -0
  109. data/lib/bolt/logger.rb +252 -0
  110. data/lib/bolt/module.rb +54 -0
  111. data/lib/bolt/module_installer/installer.rb +44 -0
  112. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  113. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  114. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  115. data/lib/bolt/module_installer/puppetfile.rb +131 -0
  116. data/lib/bolt/module_installer/resolver.rb +129 -0
  117. data/lib/bolt/module_installer/specs/forge_spec.rb +91 -0
  118. data/lib/bolt/module_installer/specs/git_spec.rb +150 -0
  119. data/lib/bolt/module_installer/specs/id/base.rb +116 -0
  120. data/lib/bolt/module_installer/specs/id/gitclone.rb +120 -0
  121. data/lib/bolt/module_installer/specs/id/github.rb +90 -0
  122. data/lib/bolt/module_installer/specs/id/gitlab.rb +92 -0
  123. data/lib/bolt/module_installer/specs.rb +95 -0
  124. data/lib/bolt/module_installer.rb +208 -0
  125. data/lib/bolt/node/errors.rb +55 -0
  126. data/lib/bolt/node/output.rb +29 -0
  127. data/lib/bolt/outputter/human.rb +958 -0
  128. data/lib/bolt/outputter/json.rb +205 -0
  129. data/lib/bolt/outputter/logger.rb +76 -0
  130. data/lib/bolt/outputter/rainbow.rb +118 -0
  131. data/lib/bolt/outputter.rb +57 -0
  132. data/lib/bolt/pal/issues.rb +19 -0
  133. data/lib/bolt/pal/logging.rb +17 -0
  134. data/lib/bolt/pal/yaml_plan/evaluator.rb +83 -0
  135. data/lib/bolt/pal/yaml_plan/loader.rb +94 -0
  136. data/lib/bolt/pal/yaml_plan/parameter.rb +63 -0
  137. data/lib/bolt/pal/yaml_plan/step/command.rb +45 -0
  138. data/lib/bolt/pal/yaml_plan/step/download.rb +37 -0
  139. data/lib/bolt/pal/yaml_plan/step/eval.rb +42 -0
  140. data/lib/bolt/pal/yaml_plan/step/message.rb +31 -0
  141. data/lib/bolt/pal/yaml_plan/step/plan.rb +42 -0
  142. data/lib/bolt/pal/yaml_plan/step/resources.rb +170 -0
  143. data/lib/bolt/pal/yaml_plan/step/script.rb +62 -0
  144. data/lib/bolt/pal/yaml_plan/step/task.rb +42 -0
  145. data/lib/bolt/pal/yaml_plan/step/upload.rb +37 -0
  146. data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
  147. data/lib/bolt/pal/yaml_plan/step.rb +223 -0
  148. data/lib/bolt/pal/yaml_plan/transpiler.rb +90 -0
  149. data/lib/bolt/pal/yaml_plan.rb +172 -0
  150. data/lib/bolt/pal.rb +847 -0
  151. data/lib/bolt/plan_creator.rb +219 -0
  152. data/lib/bolt/plan_future.rb +86 -0
  153. data/lib/bolt/plan_result.rb +44 -0
  154. data/lib/bolt/plugin/cache.rb +76 -0
  155. data/lib/bolt/plugin/env_var.rb +54 -0
  156. data/lib/bolt/plugin/module.rb +276 -0
  157. data/lib/bolt/plugin/prompt.rb +36 -0
  158. data/lib/bolt/plugin/puppet_connect_data.rb +84 -0
  159. data/lib/bolt/plugin/puppetdb.rb +124 -0
  160. data/lib/bolt/plugin/task.rb +72 -0
  161. data/lib/bolt/plugin.rb +380 -0
  162. data/lib/bolt/project.rb +219 -0
  163. data/lib/bolt/project_manager/config_migrator.rb +113 -0
  164. data/lib/bolt/project_manager/inventory_migrator.rb +67 -0
  165. data/lib/bolt/project_manager/migrator.rb +39 -0
  166. data/lib/bolt/project_manager/module_migrator.rb +203 -0
  167. data/lib/bolt/project_manager.rb +221 -0
  168. data/lib/bolt/puppetdb/client.rb +153 -0
  169. data/lib/bolt/puppetdb/config.rb +176 -0
  170. data/lib/bolt/puppetdb/instance.rb +146 -0
  171. data/lib/bolt/puppetdb.rb +15 -0
  172. data/lib/bolt/r10k_log_proxy.rb +30 -0
  173. data/lib/bolt/rerun.rb +55 -0
  174. data/lib/bolt/resource_instance.rb +133 -0
  175. data/lib/bolt/result.rb +247 -0
  176. data/lib/bolt/result_set.rb +128 -0
  177. data/lib/bolt/shell/bash/tmpdir.rb +62 -0
  178. data/lib/bolt/shell/bash.rb +516 -0
  179. data/lib/bolt/shell/powershell/snippets.rb +181 -0
  180. data/lib/bolt/shell/powershell.rb +365 -0
  181. data/lib/bolt/shell.rb +105 -0
  182. data/lib/bolt/target.rb +174 -0
  183. data/lib/bolt/task/puppet_server.rb +27 -0
  184. data/lib/bolt/task/run.rb +55 -0
  185. data/lib/bolt/task.rb +163 -0
  186. data/lib/bolt/transport/base.rb +252 -0
  187. data/lib/bolt/transport/docker/connection.rb +150 -0
  188. data/lib/bolt/transport/docker.rb +23 -0
  189. data/lib/bolt/transport/jail/connection.rb +81 -0
  190. data/lib/bolt/transport/jail.rb +21 -0
  191. data/lib/bolt/transport/local/connection.rb +106 -0
  192. data/lib/bolt/transport/local.rb +20 -0
  193. data/lib/bolt/transport/lxd/connection.rb +115 -0
  194. data/lib/bolt/transport/lxd.rb +26 -0
  195. data/lib/bolt/transport/orch/connection.rb +111 -0
  196. data/lib/bolt/transport/orch.rb +271 -0
  197. data/lib/bolt/transport/podman/connection.rb +102 -0
  198. data/lib/bolt/transport/podman.rb +19 -0
  199. data/lib/bolt/transport/remote.rb +41 -0
  200. data/lib/bolt/transport/simple.rb +54 -0
  201. data/lib/bolt/transport/ssh/connection.rb +321 -0
  202. data/lib/bolt/transport/ssh/exec_connection.rb +140 -0
  203. data/lib/bolt/transport/ssh.rb +48 -0
  204. data/lib/bolt/transport/winrm/connection.rb +378 -0
  205. data/lib/bolt/transport/winrm.rb +33 -0
  206. data/lib/bolt/util/format.rb +68 -0
  207. data/lib/bolt/util/puppet_log_level.rb +21 -0
  208. data/lib/bolt/util.rb +465 -0
  209. data/lib/bolt/validator.rb +227 -0
  210. data/lib/bolt/version.rb +5 -0
  211. data/lib/bolt.rb +8 -0
  212. data/lib/bolt_server/acl.rb +39 -0
  213. data/lib/bolt_server/base_config.rb +112 -0
  214. data/lib/bolt_server/config.rb +64 -0
  215. data/lib/bolt_server/file_cache.rb +200 -0
  216. data/lib/bolt_server/request_error.rb +11 -0
  217. data/lib/bolt_server/schemas/action-check_node_connections.json +14 -0
  218. data/lib/bolt_server/schemas/action-run_command.json +12 -0
  219. data/lib/bolt_server/schemas/action-run_script.json +47 -0
  220. data/lib/bolt_server/schemas/action-run_task.json +20 -0
  221. data/lib/bolt_server/schemas/action-upload_file.json +47 -0
  222. data/lib/bolt_server/schemas/partials/target-any.json +10 -0
  223. data/lib/bolt_server/schemas/partials/target-ssh.json +88 -0
  224. data/lib/bolt_server/schemas/partials/target-winrm.json +67 -0
  225. data/lib/bolt_server/schemas/partials/task.json +94 -0
  226. data/lib/bolt_server/schemas/transport-ssh.json +25 -0
  227. data/lib/bolt_server/schemas/transport-winrm.json +19 -0
  228. data/lib/bolt_server/transport_app.rb +554 -0
  229. data/lib/bolt_spec/bolt_context.rb +226 -0
  230. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +51 -0
  231. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
  232. data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +55 -0
  233. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +59 -0
  234. data/lib/bolt_spec/plans/action_stubs/task_stub.rb +57 -0
  235. data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +65 -0
  236. data/lib/bolt_spec/plans/action_stubs.rb +196 -0
  237. data/lib/bolt_spec/plans/mock_executor.rb +361 -0
  238. data/lib/bolt_spec/plans/publish_stub.rb +49 -0
  239. data/lib/bolt_spec/plans.rb +190 -0
  240. data/lib/bolt_spec/run.rb +246 -0
  241. data/lib/logging_extensions/logging.rb +13 -0
  242. data/libexec/apply_catalog.rb +130 -0
  243. data/libexec/bolt_catalog +68 -0
  244. data/libexec/custom_facts.rb +63 -0
  245. data/libexec/query_resources.rb +75 -0
  246. data/modules/aggregate/lib/puppet/functions/aggregate/count.rb +21 -0
  247. data/modules/aggregate/lib/puppet/functions/aggregate/nodes.rb +22 -0
  248. data/modules/aggregate/lib/puppet/functions/aggregate/targets.rb +21 -0
  249. data/modules/aggregate/plans/count.pp +56 -0
  250. data/modules/aggregate/plans/targets.pp +56 -0
  251. data/modules/canary/lib/puppet/functions/canary/merge.rb +13 -0
  252. data/modules/canary/lib/puppet/functions/canary/random_split.rb +22 -0
  253. data/modules/canary/lib/puppet/functions/canary/skip.rb +25 -0
  254. data/modules/canary/plans/init.pp +100 -0
  255. data/modules/puppet_connect/plans/test_input_data.pp +94 -0
  256. data/modules/puppetdb_fact/plans/init.pp +20 -0
  257. data/resources/bolt_bash_completion.sh +214 -0
  258. metadata +735 -0
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../bolt/util'
4
+
5
+ module Bolt
6
+ class PAL
7
+ class YamlPlan
8
+ class Step
9
+ attr_reader :body
10
+
11
+ STEP_KEYS = %w[
12
+ command
13
+ download
14
+ eval
15
+ message
16
+ verbose
17
+ plan
18
+ resources
19
+ script
20
+ task
21
+ upload
22
+ ].freeze
23
+
24
+ class StepError < Bolt::Error
25
+ def initialize(message, name, step_number)
26
+ identifier = name ? name.inspect : "number #{step_number}"
27
+ error = "Parse error in step #{identifier}: \n #{message}"
28
+
29
+ super(error, 'bolt/invalid-plan')
30
+ end
31
+ end
32
+
33
+ # Keys that are allowed for the step
34
+ #
35
+ def self.allowed_keys
36
+ required_keys + option_keys + Set['name', 'description', 'targets']
37
+ end
38
+
39
+ # Keys that translate to metaparameters for the plan step's function call
40
+ #
41
+ def self.option_keys
42
+ Set.new
43
+ end
44
+
45
+ # Keys that are required for the step
46
+ #
47
+ def self.required_keys
48
+ Set.new
49
+ end
50
+
51
+ def self.create(step_body, step_number)
52
+ type_keys = (STEP_KEYS & step_body.keys)
53
+ case type_keys.length
54
+ when 0
55
+ raise StepError.new("No valid action detected", step_body['name'], step_number)
56
+ when 1
57
+ type = type_keys.first
58
+ else
59
+ raise StepError.new("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
60
+ end
61
+
62
+ step_class = const_get("Bolt::PAL::YamlPlan::Step::#{type.capitalize}")
63
+ step_class.validate(step_body, step_number)
64
+ step_class.new(step_body)
65
+ end
66
+
67
+ def initialize(body)
68
+ @body = body
69
+ end
70
+
71
+ # Transpiles the step into the plan language
72
+ #
73
+ def transpile
74
+ code = String.new(" ")
75
+ code << "$#{body['name']} = " if body['name']
76
+ code << function_call(function, format_args(body))
77
+ code << "\n"
78
+ end
79
+
80
+ # Evaluates the step
81
+ #
82
+ def evaluate(scope, evaluator)
83
+ evaluated = evaluator.evaluate_code_blocks(scope, body)
84
+ scope.call_function(function, format_args(evaluated))
85
+ end
86
+
87
+ # Formats a list of args from the provided body
88
+ #
89
+ private def format_args(_body)
90
+ raise NotImplementedError, "Step class #{self.class} does not implement #format_args"
91
+ end
92
+
93
+ # Returns the step's corresponding Puppet language function call
94
+ #
95
+ private def function_call(function, args)
96
+ code_args = args.map { |arg| Bolt::Util.to_code(arg) }
97
+ "#{function}(#{code_args.join(', ')})"
98
+ end
99
+
100
+ # The function that corresponds to the step
101
+ #
102
+ private def function
103
+ raise NotImplementedError, "Step class #{self.class} does not implement #function"
104
+ end
105
+
106
+ # Returns a hash of options formatted for function calls
107
+ #
108
+ private def format_options(body)
109
+ body.slice(*self.class.option_keys).transform_keys { |key| "_#{key}" }
110
+ end
111
+
112
+ def self.validate(body, step_number)
113
+ validate_step_keys(body, step_number)
114
+
115
+ begin
116
+ body.each { |k, v| validate_puppet_code(k, v) }
117
+ rescue Bolt::Error => e
118
+ raise StepError.new(e.msg, body['name'], step_number)
119
+ end
120
+
121
+ if body.key?('parameters')
122
+ unless body['parameters'].is_a?(Hash)
123
+ raise StepError.new("Parameters key must be a hash", body['name'], step_number)
124
+ end
125
+
126
+ metaparams = body['parameters'].keys
127
+ .select { |key| key.start_with?('_') }
128
+ .map { |key| key.sub(/^_/, '') }
129
+
130
+ if (dups = body.keys & metaparams).any?
131
+ raise StepError.new(
132
+ "Cannot specify metaparameters when using top-level keys with same name: #{dups.join(', ')}",
133
+ body['name'],
134
+ step_number
135
+ )
136
+ end
137
+ end
138
+
139
+ unless body.fetch('parameters', {}).is_a?(Hash)
140
+ msg = "Parameters key must be a hash"
141
+ raise StepError.new(msg, body['name'], step_number)
142
+ end
143
+
144
+ if body.key?('name')
145
+ name = body['name']
146
+ unless name.is_a?(String) && name.match?(Bolt::PAL::YamlPlan::VAR_NAME_PATTERN)
147
+ error_message = "Invalid step name: #{name.inspect}"
148
+ raise StepError.new(error_message, body['name'], step_number)
149
+ end
150
+ end
151
+ end
152
+
153
+ def self.validate_step_keys(body, step_number)
154
+ step_type = name.split('::').last.downcase
155
+
156
+ # For validated step action, ensure only valid keys
157
+ illegal_keys = body.keys.to_set - allowed_keys
158
+ if illegal_keys.any?
159
+ error_message = "The #{step_type.inspect} step does not support: #{illegal_keys.to_a.inspect} key(s)"
160
+ raise StepError.new(error_message, body['name'], step_number)
161
+ end
162
+
163
+ # Ensure all required keys are present
164
+ missing_keys = required_keys - body.keys
165
+
166
+ if missing_keys.any?
167
+ error_message = "The #{step_type.inspect} step requires: #{missing_keys.to_a.inspect} key(s)"
168
+ raise StepError.new(error_message, body['name'], step_number)
169
+ end
170
+ end
171
+
172
+ # Recursively ensure all puppet code can be parsed
173
+ def self.validate_puppet_code(step_key, value)
174
+ case value
175
+ when Array
176
+ value.map { |element| validate_puppet_code(step_key, element) }
177
+ when Hash
178
+ value.each_with_object({}) do |(k, v), o|
179
+ key = k.is_a?(Bolt::PAL::YamlPlan::EvaluableString) ? k.value : k
180
+ o[key] = validate_puppet_code(key, v)
181
+ end
182
+ # CodeLiterals can be parsed directly
183
+ when Bolt::PAL::YamlPlan::CodeLiteral
184
+ parse_code_string(value.value)
185
+ # BareString is parsed directly if it starts with '$'
186
+ when Bolt::PAL::YamlPlan::BareString
187
+ if value.value.start_with?('$')
188
+ parse_code_string(value.value)
189
+ else
190
+ parse_code_string(value.value, true)
191
+ end
192
+ when Bolt::PAL::YamlPlan::EvaluableString
193
+ # Must quote parsed strings to evaluate them
194
+ parse_code_string(value.value, true)
195
+ end
196
+ rescue Puppet::Error => e
197
+ raise Bolt::Error.new("Error parsing #{step_key.inspect}: #{e.basic_message}", "bolt/invalid-plan")
198
+ end
199
+
200
+ # Parses the an evaluable string, optionally quote it before parsing
201
+ def self.parse_code_string(code, quote = false)
202
+ if quote
203
+ quoted = Puppet::Pops::Parser::EvaluatingParser.quote(code)
204
+ Puppet::Pops::Parser::EvaluatingParser.new.parse_string(quoted)
205
+ else
206
+ Puppet::Pops::Parser::EvaluatingParser.new.parse_string(code)
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ require_relative 'step/command'
215
+ require_relative 'step/eval'
216
+ require_relative 'step/plan'
217
+ require_relative 'step/resources'
218
+ require_relative 'step/script'
219
+ require_relative 'step/task'
220
+ require_relative 'step/upload'
221
+ require_relative 'step/download'
222
+ require_relative 'step/message'
223
+ require_relative 'step/verbose'
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../bolt/error'
4
+ require_relative '../../../bolt/pal/yaml_plan/loader'
5
+ require_relative '../../../bolt/util'
6
+
7
+ module Bolt
8
+ class PAL
9
+ class YamlPlan
10
+ class Transpiler
11
+ class ConvertError < Bolt::Error
12
+ def initialize(msg, plan_path)
13
+ super(msg, 'bolt/convert-error', { "plan_path" => plan_path })
14
+ end
15
+ end
16
+
17
+ def transpile(plan_path)
18
+ @plan_path = plan_path
19
+ @modulename = Bolt::Util.module_name(@plan_path)
20
+ @filename = @plan_path.split(File::SEPARATOR)[-1]
21
+ validate_path
22
+
23
+ plan_object = parse_plan
24
+ param_descriptions = plan_object.parameters.map do |param|
25
+ str = String.new("# @param #{param.name}")
26
+ str << " #{param.description}" if param.description
27
+ str
28
+ end.join("\n")
29
+
30
+ plan_string = String.new('')
31
+ plan_string << "# #{plan_object.description}\n" if plan_object.description
32
+ plan_string << "# WARNING: This is an autogenerated plan. It might not behave as expected.\n"
33
+ plan_string << "# @api #{plan_object.private ? 'private' : 'public'}\n" unless plan_object.private.nil?
34
+ plan_string << "#{param_descriptions}\n" unless param_descriptions.empty?
35
+
36
+ plan_string << "plan #{plan_object.name}("
37
+ # Parameters are Bolt::PAL::YamlPlan::Parameter
38
+ plan_object.parameters&.each_with_index do |param, i|
39
+ plan_string << param.transpile
40
+
41
+ # If it's the last parameter add a newline and no comma
42
+ last = i + 1 == plan_object.parameters.length ? "\n" : ","
43
+ # This encodes strangely if we << directly to plan_string
44
+ plan_string << last
45
+ end
46
+ plan_string << ") {\n"
47
+
48
+ plan_object.steps&.each do |step|
49
+ plan_string << step.transpile
50
+ end
51
+
52
+ plan_string << "\n return #{Bolt::Util.to_code(plan_object.return)}\n" if plan_object.return
53
+ plan_string << "}"
54
+ # We always print the plan, even if there's an error
55
+ puts plan_string
56
+ validate_plan(plan_string)
57
+ plan_string
58
+ end
59
+
60
+ def parse_plan
61
+ begin
62
+ file_contents = File.read(@plan_path)
63
+ rescue Errno::ENOENT
64
+ msg = "Could not read yaml plan file: #{@plan_path}"
65
+ raise Bolt::FileError.new(msg, @plan_path)
66
+ end
67
+
68
+ begin
69
+ Bolt::PAL::YamlPlan::Loader.from_string(@modulename, file_contents, @plan_path)
70
+ rescue Puppet::PreformattedError, StandardError => e
71
+ raise PALError.from_preformatted_error(e)
72
+ end
73
+ end
74
+
75
+ def validate_path
76
+ unless File.extname(@filename) == ".yaml"
77
+ raise ConvertError.new("You can only convert plans written in yaml", @plan_path)
78
+ end
79
+ end
80
+
81
+ def validate_plan(plan)
82
+ Puppet::Pops::Parser::EvaluatingParser.new.parse_string(plan)
83
+ rescue Puppet::Error => e
84
+ $stderr.puts "The converted puppet plan contains invalid puppet code: #{e.message}"
85
+ exit 1
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'yaml_plan/parameter'
4
+ require_relative 'yaml_plan/step'
5
+
6
+ module Bolt
7
+ class PAL
8
+ class YamlPlan
9
+ PLAN_KEYS = Set['parameters', 'private', 'steps', 'return', 'version', 'description']
10
+ VAR_NAME_PATTERN = /\A[a-z_][a-z0-9_]*\z/.freeze
11
+
12
+ attr_reader :name, :parameters, :private, :steps, :return, :description
13
+
14
+ def initialize(name, plan)
15
+ # Top-level plan keys aren't allowed to be Puppet code, so force them
16
+ # all to strings.
17
+ plan = Bolt::Util.walk_keys(plan) { |key| stringify(key) }
18
+ @name = name.freeze
19
+ @description = stringify(plan['description']) if plan['description']
20
+
21
+ params_hash = stringify(plan.fetch('parameters', {}))
22
+ # Ensure params is a hash
23
+ unless params_hash.is_a?(Hash)
24
+ raise Bolt::Error.new("Plan parameters must be a Hash", "bolt/invalid-plan")
25
+ end
26
+
27
+ # Munge parameters into an array of Parameter objects, which is what
28
+ # the Puppet API expects
29
+ @parameters = params_hash.map do |param, definition|
30
+ Parameter.new(param, definition)
31
+ end.freeze
32
+
33
+ @private = plan['private']
34
+ unless @private.nil? || @private.is_a?(TrueClass) || @private.is_a?(FalseClass)
35
+ msg = "Plan #{@name} key 'private' must be a boolean, received: #{@private.inspect}"
36
+ raise Bolt::Error.new(msg, "bolt/invalid-plan")
37
+ end
38
+
39
+ # Validate top level plan keys
40
+ top_level_keys = plan.keys.to_set
41
+ unless PLAN_KEYS.superset?(top_level_keys)
42
+ invalid_keys = top_level_keys - PLAN_KEYS
43
+ raise Bolt::Error.new("Plan contains illegal key(s) #{invalid_keys.to_a.inspect}",
44
+ "bolt/invalid-plan")
45
+ end
46
+
47
+ unless plan['steps'].is_a?(Array)
48
+ raise Bolt::Error.new("Plan must specify an array of steps", "bolt/invalid-plan")
49
+ end
50
+
51
+ used_names = Set.new(@parameters.map(&:name))
52
+
53
+ @steps = plan['steps'].each_with_index.map do |step, index|
54
+ unless step.is_a?(Hash)
55
+ raise Bolt::Error.new(
56
+ "Parse error in step number #{index + 1}: Plan step must be an object with valid step keys.",
57
+ 'bolt/invalid-plan'
58
+ )
59
+ end
60
+
61
+ # Step keys also aren't allowed to be code and neither is the value of "name"
62
+ stringified_step = Bolt::Util.walk_keys(step) { |key| stringify(key) }
63
+ stringified_step['name'] = stringify(stringified_step['name']) if stringified_step.key?('name')
64
+
65
+ step = Step.create(stringified_step, index + 1)
66
+ duplicate_check(used_names, stringified_step['name'], index + 1)
67
+ used_names << stringified_step['name'] if stringified_step['name']
68
+ step
69
+ end.freeze
70
+ @return = plan['return']
71
+ end
72
+
73
+ def duplicate_check(used_names, name, step_number)
74
+ if used_names.include?(name)
75
+ error_message = "Duplicate step name or parameter detected: #{name.inspect}"
76
+ raise Step::StepError.new(error_message, name, step_number)
77
+ end
78
+ end
79
+
80
+ def body
81
+ self
82
+ end
83
+
84
+ def return_type
85
+ Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult')
86
+ end
87
+
88
+ # Turn all "potential" strings in the object into actual strings.
89
+ # Because we interpret bare strings as potential Puppet code, even in
90
+ # places where Puppet code isn't allowed (like some hash keys), we need
91
+ # to be able to force them back into regular strings, as if we had
92
+ # parsed them normally.
93
+ def stringify(value)
94
+ case value
95
+ when Array
96
+ value.map { |element| stringify(element) }
97
+ when Hash
98
+ value.each_with_object({}) do |(k, v), o|
99
+ o[stringify(k)] = stringify(v)
100
+ end
101
+ when EvaluableString
102
+ value.value
103
+ else
104
+ value
105
+ end
106
+ end
107
+
108
+ # This class wraps a value parsed from YAML which may be Puppet code.
109
+ # That includes double-quoted strings and string literals, each of which
110
+ # subclasses this parent class in order to implement its own evaluation
111
+ # logic.
112
+ class EvaluableString
113
+ attr_reader :file, :line, :value
114
+
115
+ def initialize(value, file = nil, line = nil)
116
+ @value = value
117
+ @file = file
118
+ @line = line
119
+ end
120
+
121
+ def ==(other)
122
+ self.class == other.class && @value == other.value
123
+ end
124
+ end
125
+
126
+ # This class represents a double-quoted YAML string, which is interpreted
127
+ # as though it were a double-quoted Puppet string (with associated
128
+ # variable interpolations)
129
+ class DoubleQuotedString < EvaluableString
130
+ def evaluate(scope, evaluator)
131
+ # "inspect" allows us to get back a double-quoted string literal with
132
+ # special characters escaped. This is based on the assumption that
133
+ # YAML, Ruby and Puppet all support similar escape sequences.
134
+ parse_result = evaluator.parse_string(@value.inspect)
135
+
136
+ scope.with_local_scope({}) do
137
+ evaluator.evaluate(scope, parse_result)
138
+ end
139
+ end
140
+ end
141
+
142
+ # This represents a literal snippet of Puppet code
143
+ class CodeLiteral < EvaluableString
144
+ def evaluate(scope, evaluator)
145
+ parse_result = evaluator.parse_string(@value)
146
+
147
+ scope.with_local_scope({}) do
148
+ evaluator.evaluate(scope, parse_result)
149
+ end
150
+ end
151
+ end
152
+
153
+ # This class stores a bare YAML string, which is fuzzily interpreted as
154
+ # either Puppet code or a literal string, depending on whether it starts
155
+ # with a variable reference.
156
+ class BareString < EvaluableString
157
+ def evaluate(scope, evaluator)
158
+ if @value.start_with?('$')
159
+ # Try to parse the string as Puppet code. If it's invalid code,
160
+ # return the original string.
161
+ parse_result = evaluator.parse_string(@value)
162
+ scope.with_local_scope({}) do
163
+ evaluator.evaluate(scope, parse_result)
164
+ end
165
+ else
166
+ @value
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end