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,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/container_result'
4
+ require 'bolt/error'
5
+ require 'bolt/util'
6
+
7
+ # Run a container and return its output to stdout and stderr.
8
+ #
9
+ # > **Note:** Not available in apply block
10
+ Puppet::Functions.create_function(:run_container) do
11
+ # Run a container.
12
+ # @param image The name of the image to run.
13
+ # @param options A hash of additional options.
14
+ # @option options [Boolean] _catch_errors Whether to catch raised errors.
15
+ # @option options [String] cmd A command to run in the container.
16
+ # @option options [Hash[String, Data]] env_vars Map of environment variables to set.
17
+ # @option options [Hash[Integer, Integer]] ports A map of container ports to
18
+ # publish. Keys are the host port, values are the corresponding container
19
+ # port.
20
+ # @option options [Boolean] rm Whether to remove the container once it exits.
21
+ # @option options [Hash[String, String]] volumes A map of absolute paths on
22
+ # the host to absolute paths on the remote to mount.
23
+ # @option options [String] workdir The working directory within the container.
24
+ # @return Output from the container.
25
+ # @example Run Nginx proxy manager
26
+ # run_container('jc21/nginx-proxy-manager', 'ports' => { 80 => 80, 81 => 81, 443 => 443 })
27
+ dispatch :run_container do
28
+ param 'String[1]', :image
29
+ optional_param 'Hash[String[1], Any]', :options
30
+ return_type 'ContainerResult'
31
+ end
32
+
33
+ def run_container(image, options = {})
34
+ unless Puppet[:tasks]
35
+ raise Puppet::ParseErrorWithIssue
36
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'run_container')
37
+ end
38
+
39
+ # Send Analytics Report
40
+ executor = Puppet.lookup(:bolt_executor)
41
+ executor.report_function_call(self.class.name)
42
+
43
+ options = options.transform_keys { |k| k.sub(/^_/, '').to_sym }
44
+ validate_options(options)
45
+
46
+ if options.key?(:env_vars)
47
+ options[:env_vars] = options[:env_vars].transform_values do |val|
48
+ [Array, Hash].include?(val.class) ? val.to_json : val
49
+ end
50
+ end
51
+
52
+ if options[:ports]
53
+ ports = options[:ports].each_with_object([]) do |(host_port, container_port), acc|
54
+ acc << "-p"
55
+ acc << "#{host_port}:#{container_port}"
56
+ end
57
+ end
58
+
59
+ if options[:volumes]
60
+ volumes = options[:volumes].each_with_object([]) do |(host_path, remote_path), acc|
61
+ begin
62
+ FileUtils.mkdir_p(host_path)
63
+ rescue StandardError => e
64
+ message = "Unable to create host volume directory #{host_path}: #{e.message}"
65
+ raise Bolt::Error.new(message, 'bolt/file-error')
66
+ end
67
+ acc << "-v"
68
+ acc << "#{host_path}:#{remote_path}"
69
+ end
70
+ end
71
+
72
+ # Run the container
73
+ # `docker run` will automatically pull the image if it isn't already downloaded
74
+ cmd = %w[run]
75
+ cmd += Bolt::Util.format_env_vars_for_cli(options[:env_vars]) if options[:env_vars]
76
+ cmd += volumes if volumes
77
+ cmd += ports if ports
78
+ cmd << "--rm" if options[:rm]
79
+ cmd += %W[-w #{options[:workdir]}] if options[:workdir]
80
+ cmd << image
81
+ cmd += Shellwords.shellsplit(options[:cmd]) if options[:cmd]
82
+
83
+ executor.publish_event(type: :container_start, image: image)
84
+ out, err, status = Bolt::Util.exec_docker(cmd)
85
+
86
+ o = out.is_a?(String) ? out.dup.force_encoding('utf-8') : out
87
+ e = err.is_a?(String) ? err.dup.force_encoding('utf-8') : err
88
+
89
+ unless status.exitstatus.zero?
90
+ result = Bolt::ContainerResult.from_exception(e,
91
+ status.exitstatus,
92
+ image,
93
+ position: Puppet::Pops::PuppetStack.top_of_stack)
94
+ executor.publish_event(type: :container_finish, result: result)
95
+ if options[:catch_errors]
96
+ return result
97
+ else
98
+ raise Bolt::ContainerFailure, result
99
+ end
100
+ end
101
+
102
+ value = { 'stdout' => o, 'stderr' => e, 'exit_code' => status.exitstatus }
103
+ result = Bolt::ContainerResult.new(value, object: image)
104
+ executor.publish_event(type: :container_finish, result: result)
105
+ result
106
+ end
107
+
108
+ def validate_options(options)
109
+ if options.key?(:env_vars)
110
+ ev = options[:env_vars]
111
+ unless ev.is_a?(Hash)
112
+ msg = "Option 'env_vars' must be a hash. Received #{ev} which is a #{ev.class}"
113
+ raise Bolt::ValidationError, msg
114
+ end
115
+
116
+ if (bad_keys = ev.keys.reject { |k| k.is_a?(String) }).any?
117
+ msg = "Keys for option 'env_vars' must be strings: #{bad_keys.map(&:inspect).join(', ')}"
118
+ raise Bolt::ValidationError, msg
119
+ end
120
+ end
121
+
122
+ if options.key?(:volumes)
123
+ volumes = options[:volumes]
124
+ unless volumes.is_a?(Hash)
125
+ msg = "Option 'volumes' must be a hash. Received #{volumes} which is a #{volumes.class}"
126
+ raise Bolt::ValidationError, msg
127
+ end
128
+
129
+ if (bad_vs = volumes.reject { |k, v| k.is_a?(String) && v.is_a?(String) }).any?
130
+ msg = "Option 'volumes' only accepts strings for keys and values. "\
131
+ "Received: #{bad_vs.map(&:inspect).join(', ')}"
132
+ raise Bolt::ValidationError, msg
133
+ end
134
+ end
135
+
136
+ if options.key?(:cmd) && !options[:cmd].is_a?(String)
137
+ cmd = options[:cmd]
138
+ msg = "Option 'cmd' must be a string. Received #{cmd} which is a #{cmd.class}"
139
+ raise Bolt::ValidationError, msg
140
+ end
141
+
142
+ if options.key?(:workdir) && !options[:workdir].is_a?(String)
143
+ wd = options[:workdir]
144
+ msg = "Option 'workdir' must be a string. Received #{wd} which is a #{wd.class}"
145
+ raise Bolt::ValidationError, msg
146
+ end
147
+
148
+ if options.key?(:ports)
149
+ ports = options[:ports]
150
+ unless ports.is_a?(Hash)
151
+ msg = "Option 'ports' must be a hash. Received #{ports} which is a #{ports.class}"
152
+ raise Bolt::ValidationError, msg
153
+ end
154
+
155
+ if (bad_ps = ports.reject { |k, v| k.is_a?(Integer) && v.is_a?(Integer) }).any?
156
+ msg = "Option 'ports' only accepts integers for keys and values. "\
157
+ "Received: #{bad_ps.map(&:inspect).join(', ')}"
158
+ raise Bolt::ValidationError, msg
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Runs the `plan` referenced by its name. A plan is autoloaded from `$MODULEROOT/plans`.
6
+ #
7
+ # > **Note:** Not available in apply block
8
+ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction) do
9
+ # Run a plan
10
+ # @param plan_name The plan to run.
11
+ # @param args A hash of arguments to the plan. Can also include additional options.
12
+ # @option args [Boolean] _catch_errors Whether to catch raised errors.
13
+ # @option args [String] _run_as User to run as using privilege escalation.
14
+ # This option sets the [run-as user](privilege_escalation.md) for all
15
+ # targets whenever Bolt connects to a target. This is set for all functions
16
+ # in the called plan, including `run_plan()`.
17
+ # @return [PlanResult] The result of running the plan. Undef if plan does not explicitly return results.
18
+ # @example Run a plan
19
+ # run_plan('canary', 'command' => 'false', 'targets' => $targets, '_catch_errors' => true)
20
+ dispatch :run_plan do
21
+ scope_param
22
+ param 'String', :plan_name
23
+ optional_param 'Hash', :args
24
+ return_type 'Boltlib::PlanResult'
25
+ end
26
+
27
+ # Run a plan, specifying `$nodes` or `$targets` as a positional argument.
28
+ #
29
+ # > **Note:** When running a plan with both a `$nodes` and `$targets` parameter, and using the second
30
+ # positional argument, the plan will fail.
31
+ #
32
+ # @param plan_name The plan to run.
33
+ # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
34
+ # @param args A hash of arguments to the plan. Can also include additional options.
35
+ # @option args [Boolean] _catch_errors Whether to catch raised errors.
36
+ # @option args [String] _run_as User to run as using privilege escalation.
37
+ # This option sets the [run-as user](privilege_escalation.md) for all
38
+ # targets whenever Bolt connects to a target. This is set for all functions
39
+ # in the called plan, including `run_plan()`.
40
+ # @return [PlanResult] The result of running the plan. Undef if plan does not explicitly return results.
41
+ # @example Run a plan
42
+ # run_plan('canary', $targets, 'command' => 'false')
43
+ dispatch :run_plan_with_targetspec do
44
+ scope_param
45
+ param 'String', :plan_name
46
+ param 'Boltlib::TargetSpec', :targets
47
+ optional_param 'Hash', :args
48
+ return_type 'Boltlib::PlanResult'
49
+ end
50
+
51
+ def run_plan_with_targetspec(scope, plan_name, targets, args = {})
52
+ run_inner_plan(scope, plan_name, targets, args)
53
+ end
54
+
55
+ def run_plan(scope, plan_name, args = {})
56
+ run_inner_plan(scope, plan_name, nil, args)
57
+ end
58
+
59
+ def run_inner_plan(scope, plan_name, targets, args = {})
60
+ unless Puppet[:tasks]
61
+ raise Puppet::ParseErrorWithIssue
62
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'run_plan')
63
+ end
64
+
65
+ executor = Puppet.lookup(:bolt_executor)
66
+
67
+ options, params = args.partition { |k, _v| k.start_with?('_') }.map(&:to_h)
68
+ options = options.transform_keys { |k| k.sub(/^_/, '').to_sym }
69
+
70
+ # Bolt calls this function internally to trigger plans from the CLI. We
71
+ # don't want to count those invocations.
72
+ unless options[:bolt_api_call]
73
+ # Send Analytics Report
74
+ executor.report_function_call(self.class.name)
75
+ end
76
+
77
+ # Send Analytics Report for bundled content, this should capture plans run from both CLI and Plans
78
+ executor.report_bundled_content('Plan', plan_name)
79
+
80
+ loaders = closure_scope.compiler.loaders
81
+ # The perspective of the environment is wanted here (for now) to not have to
82
+ # require modules to have dependencies defined in meta data.
83
+ loader = loaders.private_environment_loader
84
+
85
+ # TODO: Why would we not have a private_environment_loader?
86
+ unless loader && (func = loader.load(:plan, plan_name))
87
+ raise Bolt::Error.unknown_plan(plan_name)
88
+ end
89
+
90
+ if (run_as = options[:run_as])
91
+ old_run_as = executor.run_as
92
+ executor.run_as = run_as
93
+ end
94
+
95
+ closure = func.class.dispatcher.dispatchers[0]
96
+ if closure.model.is_a?(Bolt::PAL::YamlPlan)
97
+ executor.report_yaml_plan(closure.model.body)
98
+ end
99
+
100
+ # If a TargetSpec parameter is passed, ensure it is in inventory
101
+ inventory = Puppet.lookup(:bolt_inventory)
102
+
103
+ param_types = closure.parameters.each_with_object({}) do |param, param_acc|
104
+ param_acc[param.name] = extract_parameter_types(param.type_expr)&.flatten
105
+ end
106
+
107
+ targets_to_param(targets, params, param_types) if targets
108
+
109
+ if inventory.version > 1
110
+ params.each do |param, value|
111
+ # Note the safe lookup operator is needed to handle case where a parameter is passed to a
112
+ # plan that the plan is not expecting
113
+ if param_types[param]&.include?('TargetSpec') || param_types[param]&.include?('Boltlib::TargetSpec')
114
+ inventory.get_targets(value)
115
+ end
116
+ end
117
+ end
118
+
119
+ # Wrap Sensitive parameters for plans that are run from the CLI, as it's impossible to pass
120
+ # a Sensitive value that way. We don't do this for plans run from the run_plan function, as
121
+ # it can receive Sensitive values as arguments.
122
+ # This should only happen after expanding target params, otherwise things will blow up if
123
+ # the targets are wrapped as Sensitive. Hopefully nobody does that, though...
124
+ if options[:bolt_api_call]
125
+ params = wrap_sensitive_parameters(params, closure.parameters)
126
+ end
127
+
128
+ # This can be anything as long as it's unique
129
+ plan_instance_id = SecureRandom.uuid
130
+
131
+ # Add the plan invocation ID to the plan_stack for the PlanFuture the plan is
132
+ # running in so that we know the PlanFuture is running in a new plan
133
+ # invocation. This can be nil in test cases and when `wait()` isn't
134
+ # supported.
135
+ current_future = executor.get_current_future(fiber: Fiber.current)
136
+ # Safe operator to make testing easier
137
+ current_future&.plan_stack&.unshift(plan_instance_id)
138
+
139
+ # wrap plan execution in logging messages
140
+ executor.log_plan(plan_name) do
141
+ result = nil
142
+ begin
143
+ # If the plan does not throw :return by calling the return function it's result is
144
+ # undef/nil
145
+ result = catch(:return) do
146
+ scope.with_global_scope do |global_scope|
147
+ executor.run_plan(global_scope, closure, params)
148
+ end
149
+ nil
150
+ end&.value
151
+ # Validate the result is a PlanResult
152
+ unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
153
+ raise Bolt::InvalidPlanResult.new(plan_name, result.to_s)
154
+ end
155
+
156
+ result
157
+ rescue Puppet::PreformattedError => e
158
+ if options[:catch_errors] && e.cause.is_a?(Bolt::Error)
159
+ result = e.cause.to_puppet_error
160
+ else
161
+ raise e
162
+ end
163
+ ensure
164
+ # Pop the plan invocation ID off of the plan_id stack for the Future.
165
+ current_future&.plan_stack&.shift
166
+ if run_as
167
+ executor.run_as = old_run_as
168
+ end
169
+ end
170
+
171
+ result
172
+ end
173
+ end
174
+
175
+ # Recursively examine the type_expr to build a list of types
176
+ def extract_parameter_types(type_expr)
177
+ # No type
178
+ if type_expr.nil?
179
+ []
180
+ # Multiple types to extract (ex. Variant[TargetSpec, String])
181
+ elsif defined?(type_expr.keys)
182
+ type_expr.keys.flat_map { |param| extract_parameter_types(param) }
183
+ # Store cased value
184
+ elsif defined?(type_expr.cased_value)
185
+ [type_expr.cased_value]
186
+ # Type alias, able to resolve alias
187
+ elsif defined?(type_expr.resolved_type.name)
188
+ [type_expr.resolved_type.name]
189
+ # Nested type alias, recurse
190
+ elsif defined?(type_expr.type)
191
+ extract_parameter_types(type_expr.type)
192
+ # Array conatins alias types
193
+ elsif defined?(type_expr.types)
194
+ type_expr.types.flat_map { |param| extract_parameter_types(param) }
195
+ # Each element can be handled by a resolver above
196
+ elsif defined?(type_expr.element_type)
197
+ extract_parameter_types(type_expr.element_type)
198
+ end
199
+ end
200
+
201
+ # Wrap any Sensitive parameters in the Sensitive wrapper type, unless they are already
202
+ # wrapped as Sensitive. This will also raise a helpful warning if the type expression
203
+ # is a complex data type using Sensitive, as we don't handle those cases.
204
+ def wrap_sensitive_parameters(params, param_models)
205
+ models = param_models.each_with_object({}) { |param, acc| acc[param.name] = param }
206
+
207
+ params.each_with_object({}) do |(name, value), acc|
208
+ model = models[name]
209
+
210
+ # Parameters passed to a plan that the plan is not expecting don't have a model,
211
+ # so keep the parameter as-is.
212
+ if model.nil?
213
+ acc[name] = value
214
+ elsif sensitive_type?(model.type_expr)
215
+ acc[name] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(value)
216
+ else
217
+ if model.type_expr.to_s.include?('Sensitive')
218
+ # Include the location for regular plans. YAML plans don't have this info, so
219
+ # the location will be suppressed.
220
+ file = defined?(model.file) ? model.file : :default
221
+ line = defined?(model.line) ? model.line : :default
222
+
223
+ Puppet.warn_once(
224
+ 'unsupported_sensitive_type',
225
+ name,
226
+ "Parameter '#{name}' is a complex type using Sensitive, unable to automatically wrap as Sensitive",
227
+ file,
228
+ line
229
+ )
230
+ end
231
+
232
+ acc[name] = value
233
+ end
234
+ end
235
+ end
236
+
237
+ # Whether the type is a supported Sensitive type. We only support wrapping parameterized
238
+ # and non-parameterized Sensitive types (e.g. Sensitive, Sensitive[String])
239
+ def sensitive_type?(type_expr)
240
+ # Parameterized Sensitive type (e.g. Sensitive[String])
241
+ # left_expr is defined whenever the type is parameterized. If this is a parameterized
242
+ # Sensitive type, then we check the cased_value, which is the stringified version of
243
+ # the left expression's type.
244
+ (defined?(type_expr.left_expr) && type_expr.left_expr.cased_value == 'Sensitive') ||
245
+ # Non-parameterized Sensitive type (Sensitive)
246
+ # cased_value is defined whenever the type is non-parameterized. If the type expression
247
+ # defines cased_value, then this is a simple type and we just need to check that it's
248
+ # Sensitive.
249
+ (defined?(type_expr.cased_value) && type_expr.cased_value == 'Sensitive') ||
250
+ # Sensitive type from YAML plans
251
+ # Type expressions from YAML plans are a different class than those from regular plans.
252
+ # As long as the type expression is PSensitiveType we can be sure that the type is
253
+ # either a parameterized or non-parameterized Sensitive type.
254
+ type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
255
+ end
256
+
257
+ def targets_to_param(targets, params, param_types)
258
+ nodes_param = param_types.include?('nodes')
259
+ targets_param = param_types['targets']&.any? { |p| p.match?(/TargetSpec/) }
260
+
261
+ # Both a 'TargetSpec $nodes' and 'TargetSpec $targets' parameter are present in the plan
262
+ if nodes_param && targets_param
263
+ raise ArgumentError,
264
+ "A plan with both a $nodes and $targets parameter cannot have either parameter specified " \
265
+ "as the second positional argument to run_plan()."
266
+ end
267
+
268
+ # Always populate a $nodes parameter over $targets
269
+ if nodes_param
270
+ if params['nodes']
271
+ raise ArgumentError,
272
+ "A plan's 'nodes' parameter can be specified as the second positional argument to " \
273
+ "run_plan(), but in that case 'nodes' must not be specified in the named arguments " \
274
+ "hash."
275
+ end
276
+ params['nodes'] = targets
277
+ # If there is only a $targets parameter, then populate it
278
+ elsif targets_param
279
+ if params['targets']
280
+ raise ArgumentError,
281
+ "A plan's 'targets' parameter can be specified as the second positional argument to " \
282
+ "run_plan(), but in that case 'targets' must not be specified in the named arguments " \
283
+ "hash."
284
+ end
285
+ params['targets'] = targets
286
+ # If a plan has neither parameter, just fall back to $nodes
287
+ else
288
+ params['nodes'] = targets
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Uploads the given script to the given set of targets and returns the result of having each target execute the script.
4
+ # This function does nothing if the list of targets is empty.
5
+ #
6
+ # > **Note:** Not available in apply block
7
+ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFunction) do
8
+ # Run a script.
9
+ # @param script Path to a script to run on target. Can be an absolute path or a modulename/filename selector for a
10
+ # file in $MODULEROOT/files.
11
+ # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
12
+ # @param options A hash of additional options.
13
+ # @option options [Array[String]] arguments An array of arguments to be passed to the script.
14
+ # Cannot be used with `pwsh_params`.
15
+ # @option options [Hash] pwsh_params Map of named parameters to pass to a PowerShell script.
16
+ # Cannot be used with `arguments`.
17
+ # @option options [Boolean] _catch_errors Whether to catch raised errors.
18
+ # @option options [String] _run_as User to run as using privilege escalation.
19
+ # @option options [Hash[String, Any]] _env_vars Map of environment variables to set.
20
+ # @return A list of results, one entry per target.
21
+ # @example Run a local script on Linux targets as 'root'
22
+ # run_script('/var/tmp/myscript', $targets, '_run_as' => 'root')
23
+ # @example Run a module-provided script with arguments
24
+ # run_script('iis/setup.ps1', $target, 'arguments' => ['/u', 'Administrator'])
25
+ # @example Pass named parameters to a PowerShell script
26
+ # run_script('iis/setup.ps1', $target, 'pwsh_params' => { 'User' => 'Administrator' })
27
+ dispatch :run_script do
28
+ scope_param
29
+ param 'String[1]', :script
30
+ param 'Boltlib::TargetSpec', :targets
31
+ optional_param 'Hash[String[1], Any]', :options
32
+ return_type 'ResultSet'
33
+ end
34
+
35
+ # Run a script, logging the provided description.
36
+ # @param script Path to a script to run on target. Can be an absolute path or a modulename/filename selector for a
37
+ # file in $MODULEROOT/files.
38
+ # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
39
+ # @param description A description to be output when calling this function.
40
+ # @param options A hash of additional options.
41
+ # @option options [Array[String]] arguments An array of arguments to be passed to the script.
42
+ # Cannot be used with `pwsh_params`.
43
+ # @option options [Hash] pwsh_params Map of named parameters to pass to a PowerShell script.
44
+ # Cannot be used with `arguments`.
45
+ # @option options [Boolean] _catch_errors Whether to catch raised errors.
46
+ # @option options [String] _run_as User to run as using privilege escalation.
47
+ # @option options [Hash[String, Any]] _env_vars Map of environment variables to set.
48
+ # @return A list of results, one entry per target.
49
+ # @example Run a script
50
+ # run_script('/var/tmp/myscript', $targets, 'Downloading my application')
51
+ dispatch :run_script_with_description do
52
+ scope_param
53
+ param 'String[1]', :script
54
+ param 'Boltlib::TargetSpec', :targets
55
+ param 'String', :description
56
+ optional_param 'Hash[String[1], Any]', :options
57
+ return_type 'ResultSet'
58
+ end
59
+
60
+ def run_script(scope, script, targets, options = {})
61
+ run_script_with_description(scope, script, targets, nil, options)
62
+ end
63
+
64
+ def run_script_with_description(scope, script, targets, description = nil, options = {})
65
+ unless Puppet[:tasks]
66
+ raise Puppet::ParseErrorWithIssue
67
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'run_script')
68
+ end
69
+
70
+ if options.key?('arguments') && options.key?('pwsh_params')
71
+ raise Bolt::ValidationError, "Cannot specify both 'arguments' and 'pwsh_params'"
72
+ end
73
+
74
+ if options.key?('pwsh_params') && !options['pwsh_params'].is_a?(Hash)
75
+ raise Bolt::ValidationError, "Option 'pwsh_params' must be a hash"
76
+ end
77
+
78
+ if options.key?('arguments') && !options['arguments'].is_a?(Array)
79
+ raise Bolt::ValidationError, "Option 'arguments' must be an array"
80
+ end
81
+
82
+ arguments = options['arguments'] || []
83
+ pwsh_params = options['pwsh_params']
84
+ options = options.select { |opt| opt.start_with?('_') }.transform_keys { |k| k.sub(/^_/, '').to_sym }
85
+ options[:description] = description if description
86
+ options[:pwsh_params] = pwsh_params if pwsh_params
87
+
88
+ # Ensure env_vars is a hash and that each hash value is transformed to JSON
89
+ # so we don't accidentally pass Ruby-style data to the target.
90
+ if options[:env_vars]
91
+ unless options[:env_vars].is_a?(Hash)
92
+ raise Bolt::ValidationError, "Option 'env_vars' must be a hash"
93
+ end
94
+
95
+ if (bad_keys = options[:env_vars].keys.reject { |k| k.is_a?(String) }).any?
96
+ raise Bolt::ValidationError,
97
+ "Keys for option 'env_vars' must be strings: #{bad_keys.map(&:inspect).join(', ')}"
98
+ end
99
+
100
+ options[:env_vars] = options[:env_vars].transform_values do |val|
101
+ [Array, Hash].include?(val.class) ? val.to_json : val
102
+ end
103
+ end
104
+
105
+ executor = Puppet.lookup(:bolt_executor)
106
+ inventory = Puppet.lookup(:bolt_inventory)
107
+
108
+ # Send Analytics Report
109
+ executor.report_function_call(self.class.name)
110
+
111
+ # Find the file path if it exists, otherwise return nil
112
+ found = Bolt::Util.find_file_from_scope(script, scope)
113
+ unless found && Puppet::FileSystem.exist?(found)
114
+ raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
115
+ Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: script
116
+ )
117
+ end
118
+ unless Puppet::FileSystem.file?(found)
119
+ raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
120
+ Puppet::Pops::Issues::NOT_A_FILE, file: script
121
+ )
122
+ end
123
+ executor.report_file_source(self.class.name, script)
124
+ # Ensure that given targets are all Target instances)
125
+ targets = inventory.get_targets(targets)
126
+
127
+ if targets.empty?
128
+ Bolt::ResultSet.new([])
129
+ else
130
+ file_line = Puppet::Pops::PuppetStack.top_of_stack
131
+ r = if executor.in_parallel?
132
+ executor.run_in_thread do
133
+ executor.run_script(targets, found, arguments, options, file_line)
134
+ end
135
+ else
136
+ executor.run_script(targets, found, arguments, options, file_line)
137
+ end
138
+
139
+ if !r.ok && !options[:catch_errors]
140
+ raise Bolt::RunFailure.new(r, 'run_script', script)
141
+ end
142
+ r
143
+ end
144
+ end
145
+ end