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,539 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Used for $ERROR_INFO. This *must* be capitalized!
4
+ require 'English'
5
+ require 'json'
6
+ require 'logging'
7
+ require 'pathname'
8
+ require 'set'
9
+ require_relative '../bolt/analytics'
10
+ require_relative '../bolt/config'
11
+ require_relative '../bolt/fiber_executor'
12
+ require_relative '../bolt/puppetdb'
13
+ require_relative '../bolt/result'
14
+ require_relative '../bolt/result_set'
15
+ # Load transports
16
+ require_relative '../bolt/transport/docker'
17
+ require_relative '../bolt/transport/jail'
18
+ require_relative '../bolt/transport/local'
19
+ require_relative '../bolt/transport/lxd'
20
+ require_relative '../bolt/transport/orch'
21
+ require_relative '../bolt/transport/podman'
22
+ require_relative '../bolt/transport/remote'
23
+ require_relative '../bolt/transport/ssh'
24
+ require_relative '../bolt/transport/winrm'
25
+
26
+ module Bolt
27
+ TRANSPORTS = {
28
+ docker: Bolt::Transport::Docker,
29
+ jail: Bolt::Transport::Jail,
30
+ local: Bolt::Transport::Local,
31
+ lxd: Bolt::Transport::LXD,
32
+ pcp: Bolt::Transport::Orch,
33
+ podman: Bolt::Transport::Podman,
34
+ remote: Bolt::Transport::Remote,
35
+ ssh: Bolt::Transport::SSH,
36
+ winrm: Bolt::Transport::WinRM
37
+ }.freeze
38
+
39
+ class Executor
40
+ attr_reader :noop, :transports, :future
41
+ attr_accessor :run_as
42
+
43
+ def initialize(concurrency = 1,
44
+ analytics = Bolt::Analytics::NoopClient.new,
45
+ noop = false,
46
+ modified_concurrency = false,
47
+ future = {})
48
+ # lazy-load expensive gem code
49
+ require 'concurrent'
50
+ @analytics = analytics
51
+ @logger = Bolt::Logger.logger(self)
52
+
53
+ @transports = Bolt::TRANSPORTS.each_with_object({}) do |(key, val), coll|
54
+ coll[key.to_s] = if key == :remote
55
+ Concurrent::Delay.new do
56
+ val.new(self)
57
+ end
58
+ else
59
+ Concurrent::Delay.new do
60
+ val.new
61
+ end
62
+ end
63
+ end
64
+ @reported_transports = Set.new
65
+ @subscribers = {}
66
+ @publisher = Concurrent::SingleThreadExecutor.new
67
+ @publisher.post { Thread.current[:name] = 'event-publisher' }
68
+
69
+ @noop = noop
70
+ @run_as = nil
71
+ @future = future
72
+ @pool = if concurrency > 0
73
+ Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
74
+ else
75
+ Concurrent.global_immediate_executor
76
+ end
77
+ @logger.debug { "Started with #{concurrency} max thread(s)" }
78
+
79
+ @concurrency = concurrency
80
+ @warn_concurrency = modified_concurrency
81
+ @fiber_executor = Bolt::FiberExecutor.new
82
+ end
83
+
84
+ def transport(transport)
85
+ impl = @transports[transport || 'ssh']
86
+ raise(Bolt::UnknownTransportError, transport) unless impl
87
+ # If there was an error creating the transport, ensure it gets thrown
88
+ impl.no_error!
89
+ impl.value
90
+ end
91
+
92
+ def subscribe(subscriber, types = nil)
93
+ @subscribers[subscriber] = types
94
+ self
95
+ end
96
+
97
+ def unsubscribe(subscriber, types = nil)
98
+ if types.nil? || types.sort == @subscribers[subscriber]&.sort
99
+ @subscribers.delete(subscriber)
100
+ elsif @subscribers[subscriber].is_a?(Array)
101
+ @subscribers[subscriber] = @subscribers[subscriber] - types
102
+ end
103
+ end
104
+
105
+ def publish_event(event)
106
+ @subscribers.each do |subscriber, types|
107
+ # If types isn't set or if the subscriber is subscribed to
108
+ # that type of event, publish the event
109
+ next unless types.nil? || types.include?(event[:type])
110
+ @publisher.post(subscriber) do |sub|
111
+ # Wait for user to input to prompt before printing anything
112
+ sleep(0.1) while @prompting
113
+ sub.handle_event(event)
114
+ end
115
+ end
116
+ end
117
+
118
+ def shutdown
119
+ @publisher.shutdown
120
+ @publisher.wait_for_termination
121
+ end
122
+
123
+ # Starts executing the given block on a list of nodes in parallel, one thread per "batch".
124
+ #
125
+ # This is the main driver of execution on a list of targets. It first
126
+ # groups targets by transport, then divides each group into batches as
127
+ # defined by the transport. Yields each batch, along with the corresponding
128
+ # transport, to the block in turn and returns an array of result promises.
129
+ def queue_execute(targets)
130
+ if @warn_concurrency && targets.length > @concurrency
131
+ @warn_concurrency = false
132
+ msg = "The ulimit is low, which might cause file limit issues. Default concurrency has been set to "\
133
+ "'#{@concurrency}' to mitigate those issues, which might cause Bolt to run slow. "\
134
+ "Disable this warning by configuring ulimit using 'ulimit -n <limit>' in your shell "\
135
+ "configuration, or by configuring Bolt's concurrency. "\
136
+ "See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details."
137
+ Bolt::Logger.warn("low_ulimit", msg)
138
+ end
139
+
140
+ targets.group_by(&:transport).flat_map do |protocol, protocol_targets|
141
+ transport = transport(protocol)
142
+ report_transport(transport, protocol_targets.count)
143
+ transport.batches(protocol_targets).flat_map do |batch|
144
+ batch_promises = Array(batch).each_with_object({}) do |target, h|
145
+ h[target] = Concurrent::Promise.new(executor: :immediate)
146
+ end
147
+ # Pass this argument through to avoid retaining a reference to a
148
+ # local variable that will change on the next iteration of the loop.
149
+ @pool.post(batch_promises) do |result_promises|
150
+ Thread.current[:name] ||= Thread.current.name
151
+ results = yield transport, batch
152
+ Array(results).each do |result|
153
+ result_promises[result.target].set(result)
154
+ end
155
+ # NotImplementedError can be thrown if the transport is not implemented improperly
156
+ rescue StandardError, NotImplementedError => e
157
+ result_promises.each do |target, promise|
158
+ # If an exception happens while running, the result won't be logged
159
+ # by the CLI. Log a warning, as this is probably a problem with the transport.
160
+ # If batch_* commands are used from the Base transport, then exceptions
161
+ # normally shouldn't reach here.
162
+ @logger.warn(e)
163
+ promise.set(Bolt::Result.from_exception(target, e))
164
+ end
165
+ ensure
166
+ # Make absolutely sure every promise gets a result to avoid a
167
+ # deadlock. Use whatever exception is causing this block to
168
+ # execute, or generate one if we somehow got here without an
169
+ # exception and some promise is still missing a result.
170
+ result_promises.each do |target, promise|
171
+ next if promise.fulfilled?
172
+ error = $ERROR_INFO || Bolt::Error.new("No result was returned for #{target.uri}",
173
+ "puppetlabs.bolt/missing-result-error")
174
+ promise.set(Bolt::Result.from_exception(target, error))
175
+ end
176
+ end
177
+ batch_promises.values
178
+ end
179
+ end
180
+ end
181
+
182
+ # Create a ResultSet from the results of all promises.
183
+ def await_results(promises)
184
+ ResultSet.new(promises.map(&:value))
185
+ end
186
+
187
+ # Execute the given block on a list of nodes in parallel, one thread per "batch".
188
+ #
189
+ # This is the main driver of execution on a list of targets. It first
190
+ # groups targets by transport, then divides each group into batches as
191
+ # defined by the transport. Each batch, along with the corresponding
192
+ # transport, is yielded to the block in turn and the results all collected
193
+ # into a single ResultSet.
194
+ def batch_execute(targets, &block)
195
+ promises = queue_execute(targets, &block)
196
+ await_results(promises)
197
+ end
198
+
199
+ def log_action(description, targets)
200
+ publish_event(type: :step_start, description: description, targets: targets)
201
+
202
+ start_time = Time.now
203
+ results = yield
204
+ duration = Time.now - start_time
205
+
206
+ publish_event(type: :step_finish, description: description, result: results, duration: duration)
207
+
208
+ results
209
+ end
210
+
211
+ def log_plan(plan_name)
212
+ publish_event(type: :plan_start, plan: plan_name)
213
+ start_time = Time.now
214
+
215
+ results = nil
216
+ begin
217
+ results = yield
218
+ ensure
219
+ duration = Time.now - start_time
220
+ publish_event(type: :plan_finish, plan: plan_name, duration: duration)
221
+ end
222
+
223
+ results
224
+ end
225
+
226
+ private def report_transport(transport, count)
227
+ name = transport.class.name.split('::').last.downcase
228
+ unless @reported_transports.include?(name)
229
+ @analytics&.event('Transport', 'initialize', label: name, value: count)
230
+ end
231
+ @reported_transports.add(name)
232
+ end
233
+
234
+ def report_function_call(function)
235
+ @analytics&.event('Plan', 'call_function', label: function)
236
+ end
237
+
238
+ def report_bundled_content(mode, name)
239
+ @analytics.report_bundled_content(mode, name)
240
+ end
241
+
242
+ def report_file_source(plan_function, source)
243
+ label = Pathname.new(source).absolute? ? 'absolute' : 'module'
244
+ @analytics&.event('Plan', plan_function, label: label)
245
+ end
246
+
247
+ def report_noop_mode(noop)
248
+ @analytics&.event('Task', 'noop', label: (!!noop).to_s)
249
+ end
250
+
251
+ def report_apply(statement_count, resource_counts)
252
+ data = { statement_count: statement_count }
253
+
254
+ unless resource_counts.empty?
255
+ sum = resource_counts.inject(0) { |accum, i| accum + i }
256
+ # Intentionally rounded to an integer. High precision isn't useful.
257
+ data[:resource_mean] = sum / resource_counts.length
258
+ end
259
+
260
+ @analytics&.event('Apply', 'ast', **data)
261
+ end
262
+
263
+ def report_yaml_plan(plan)
264
+ steps = plan.steps.count
265
+ return_type = case plan.return
266
+ when Bolt::PAL::YamlPlan::EvaluableString
267
+ 'expression'
268
+ when nil
269
+ nil
270
+ else
271
+ 'value'
272
+ end
273
+
274
+ @analytics&.event('Plan', 'yaml', plan_steps: steps, return_type: return_type)
275
+ rescue StandardError => e
276
+ @logger.trace { "Failed to submit analytics event: #{e.message}" }
277
+ end
278
+
279
+ def with_node_logging(description, batch, log_level = :info)
280
+ @logger.send(log_level, "#{description} on #{batch.map(&:safe_name)}")
281
+ publish_event(type: :start_spin)
282
+ result = yield
283
+ publish_event(type: :stop_spin)
284
+ @logger.send(log_level, result.to_json)
285
+ result
286
+ end
287
+
288
+ def run_command(targets, command, options = {}, position = [])
289
+ description = options.fetch(:description, "command '#{command}'")
290
+ log_action(description, targets) do
291
+ options[:run_as] = run_as if run_as && !options.key?(:run_as)
292
+
293
+ batch_execute(targets) do |transport, batch|
294
+ with_node_logging("Running command '#{command}'", batch) do
295
+ transport.batch_command(batch, command, options, position, &method(:publish_event))
296
+ end
297
+ end
298
+ end
299
+ end
300
+
301
+ def run_script(targets, script, arguments, options = {}, position = [])
302
+ description = options.fetch(:description, "script #{script}")
303
+ log_action(description, targets) do
304
+ options[:run_as] = run_as if run_as && !options.key?(:run_as)
305
+ options[:script_interpreter] = (future || {}).fetch('script_interpreter', false)
306
+
307
+ @analytics&.event('Future', 'script_interpreter', label: options[:script_interpreter].to_s)
308
+
309
+ batch_execute(targets) do |transport, batch|
310
+ with_node_logging("Running script #{script} with '#{arguments.to_json}'", batch) do
311
+ transport.batch_script(batch, script, arguments, options, position, &method(:publish_event))
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ def run_task(targets, task, arguments, options = {}, position = [], log_level = :info)
318
+ description = options.fetch(:description, "task #{task.name}")
319
+ log_action(description, targets) do
320
+ options[:run_as] = run_as if run_as && !options.key?(:run_as)
321
+ arguments['_task'] = task.name
322
+
323
+ batch_execute(targets) do |transport, batch|
324
+ with_node_logging("Running task #{task.name} with '#{arguments.to_json}'", batch, log_level) do
325
+ transport.batch_task(batch, task, arguments, options, position, &method(:publish_event))
326
+ end
327
+ end
328
+ end
329
+ end
330
+
331
+ def run_task_with(target_mapping, task, options = {}, position = [])
332
+ targets = target_mapping.keys
333
+ description = options.fetch(:description, "task #{task.name}")
334
+
335
+ log_action(description, targets) do
336
+ options[:run_as] = run_as if run_as && !options.key?(:run_as)
337
+ target_mapping.each_value { |arguments| arguments['_task'] = task.name }
338
+
339
+ batch_execute(targets) do |transport, batch|
340
+ with_node_logging("Running task #{task.name}'", batch) do
341
+ transport.batch_task_with(batch, task, target_mapping, options, position, &method(:publish_event))
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ def upload_file(targets, source, destination, options = {}, position = [])
348
+ description = options.fetch(:description, "file upload from #{source} to #{destination}")
349
+ log_action(description, targets) do
350
+ options[:run_as] = run_as if run_as && !options.key?(:run_as)
351
+
352
+ batch_execute(targets) do |transport, batch|
353
+ with_node_logging("Uploading file #{source} to #{destination}", batch) do
354
+ transport.batch_upload(batch, source, destination, options, position, &method(:publish_event))
355
+ end
356
+ end
357
+ end
358
+ end
359
+
360
+ def download_file(targets, source, destination, options = {}, position = [])
361
+ description = options.fetch(:description, "file download from #{source} to #{destination}")
362
+
363
+ begin
364
+ FileUtils.mkdir_p(destination)
365
+ rescue Errno::EEXIST => e
366
+ message = "#{e.message}; unable to create destination directory #{destination}"
367
+ raise Bolt::Error.new(message, 'bolt/file-exist-error')
368
+ end
369
+
370
+ log_action(description, targets) do
371
+ options[:run_as] = run_as if run_as && !options.key?(:run_as)
372
+
373
+ batch_execute(targets) do |transport, batch|
374
+ with_node_logging("Downloading file #{source} to #{destination}", batch) do
375
+ transport.batch_download(batch, source, destination, options, position, &method(:publish_event))
376
+ end
377
+ end
378
+ end
379
+ end
380
+
381
+ def run_plan(scope, plan, params)
382
+ plan.call_by_name_with_scope(scope, params, true)
383
+ end
384
+
385
+ # Call into FiberExecutor to avoid this class getting
386
+ # overloaded while also minimizing the Puppet lookups needed from plan
387
+ # functions
388
+ #
389
+ def create_future(plan_id:, scope: nil, name: nil, &block)
390
+ @fiber_executor.create_future(scope: scope, name: name, plan_id: plan_id, &block)
391
+ end
392
+
393
+ def get_current_future(fiber:)
394
+ @fiber_executor.get_current_future(fiber: fiber)
395
+ end
396
+
397
+ def get_current_plan_id(fiber:)
398
+ @fiber_executor.get_current_plan_id(fiber: fiber)
399
+ end
400
+
401
+ def plan_complete?
402
+ @fiber_executor.plan_complete?
403
+ end
404
+
405
+ def round_robin
406
+ @fiber_executor.round_robin
407
+ end
408
+
409
+ def in_parallel?
410
+ @fiber_executor.in_parallel?
411
+ end
412
+
413
+ def wait(futures, **opts)
414
+ @fiber_executor.wait(futures, **opts)
415
+ end
416
+
417
+ def get_futures_for_plan(plan_id:)
418
+ @fiber_executor.get_futures_for_plan(plan_id: plan_id)
419
+ end
420
+
421
+ # Execute a plan function concurrently. This function accepts the executor
422
+ # function to be run and the parameters to pass to it, and returns the
423
+ # result of running the executor function.
424
+ #
425
+ def run_in_thread
426
+ require 'concurrent'
427
+ require 'fiber'
428
+ future = Concurrent::Future.execute do
429
+ yield
430
+ end
431
+
432
+ # Used to track how often we resume the same executor function
433
+ still_running = 0
434
+ # While the thread is still running
435
+ while future.incomplete?
436
+ # If the Fiber gets resumed, increment the resume tracker. This means
437
+ # the tracker starts at 1 since it needs to increment before yielding,
438
+ # since it can't yield then increment.
439
+ still_running += 1
440
+ # If the Fiber has been resumed before, still_running will be 2 or
441
+ # more. Yield different values for when the same Fiber is resumed
442
+ # multiple times and when it's resumed the first time in order to know
443
+ # if progress was made in the plan.
444
+ Fiber.yield(still_running < 2 ? :something_happened : :returned_immediately)
445
+ end
446
+
447
+ # Once the thread completes, return the result.
448
+ future.value || future.reason
449
+ end
450
+
451
+ class TimeoutError < RuntimeError; end
452
+
453
+ def wait_until_available(targets,
454
+ description: 'wait until available',
455
+ wait_time: 120,
456
+ retry_interval: 1)
457
+ log_action(description, targets) do
458
+ batch_execute(targets) do |transport, batch|
459
+ with_node_logging('Waiting until available', batch) do
460
+ wait_until(wait_time, retry_interval) { transport.batch_connected?(batch) }
461
+ batch.map { |target| Result.new(target, action: 'wait_until_available', object: description) }
462
+ rescue TimeoutError => e
463
+ available, unavailable = batch.partition { |target| transport.batch_connected?([target]) }
464
+ (
465
+ available.map { |target| Result.new(target, action: 'wait_until_available', object: description) } +
466
+ unavailable.map { |target| Result.from_exception(target, e, action: 'wait_until_available') }
467
+ )
468
+ end
469
+ end
470
+ end
471
+ end
472
+
473
+ # Used to simplify unit testing, to avoid having to mock other calls to Time.now.
474
+ private def wait_now
475
+ Time.now
476
+ end
477
+
478
+ private def wait_until(timeout, retry_interval)
479
+ start = wait_now
480
+ until yield
481
+ raise(TimeoutError, 'Timed out waiting for target') if (wait_now - start).to_i >= timeout
482
+ sleep(retry_interval)
483
+ end
484
+ end
485
+
486
+ def prompt(prompt, options)
487
+ unless $stdin.tty?
488
+ return options[:default] if options[:default]
489
+ raise Bolt::Error.new('STDIN is not a tty, unable to prompt', 'bolt/no-tty-error')
490
+ end
491
+
492
+ @prompting = true
493
+
494
+ if options[:default] && !options[:sensitive]
495
+ $stderr.print("#{prompt} [#{options[:default]}]: ")
496
+ else
497
+ $stderr.print("#{prompt}: ")
498
+ end
499
+
500
+ value = if options[:sensitive]
501
+ $stdin.noecho(&:gets).to_s.chomp
502
+ else
503
+ $stdin.gets.to_s.chomp
504
+ end
505
+
506
+ @prompting = false
507
+
508
+ $stderr.puts if options[:sensitive]
509
+
510
+ value = options[:default] if value.empty?
511
+ value
512
+ end
513
+
514
+ # Plan context doesn't make sense for most transports but it is tightly
515
+ # coupled with the orchestrator transport since the transport behaves
516
+ # differently when a plan is running. In order to limit how much this
517
+ # pollutes the transport API we only handle the orchestrator transport here.
518
+ # Since we call this function without resolving targets this will result
519
+ # in the orchestrator transport always being initialized during plan runs.
520
+ # For now that's ok.
521
+ #
522
+ # In the future if other transports need this or if we want a plan stack
523
+ # we'll need to refactor.
524
+ def start_plan(plan_context)
525
+ transport('pcp').plan_context = plan_context
526
+ end
527
+
528
+ def finish_plan(plan_result)
529
+ transport('pcp').finish_plan(plan_result)
530
+ end
531
+
532
+ def without_default_logging
533
+ publish_event(type: :disable_default_output)
534
+ yield
535
+ ensure
536
+ publish_event(type: :enable_default_output)
537
+ end
538
+ end
539
+ end