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,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/plan_result'
4
+ require 'bolt/result'
5
+ require 'bolt/util'
6
+
7
+ module BoltSpec
8
+ module Plans
9
+ # Nothing in the ActionDouble is 'public'
10
+ class ActionDouble
11
+ def initialize(action_stub)
12
+ @stubs = []
13
+ @action_stub = action_stub
14
+ end
15
+
16
+ def process(*args)
17
+ matches = @stubs.select { |s| s.matches(*args) }
18
+ unless matches.empty?
19
+ matches[0].call(*args)
20
+ end
21
+ end
22
+
23
+ def assert_called(object)
24
+ @stubs.each { |s| s.assert_called(object) }
25
+ end
26
+
27
+ def add_stub(inventory = nil)
28
+ stub = Plans.const_get(@action_stub).new(false, inventory)
29
+ @stubs.unshift stub
30
+ stub
31
+ end
32
+ end
33
+
34
+ class ActionStub
35
+ attr_reader :invocation
36
+
37
+ def initialize(expect = false, inventory = nil)
38
+ @calls = 0
39
+ @expect = expect
40
+ @expected_calls = nil
41
+ # invocation spec
42
+ @invocation = {}
43
+ # return value
44
+ @data = { default: {} }
45
+ @inventory = inventory
46
+ end
47
+
48
+ def assert_called(object)
49
+ satisfied = if @expect
50
+ (@expected_calls.nil? && @calls > 0) || @calls == @expected_calls
51
+ else
52
+ @expected_calls.nil? || @calls <= @expected_calls
53
+ end
54
+ unless satisfied
55
+ unless (times = @expected_calls)
56
+ times = @expect ? "at least one" : "any number of"
57
+ end
58
+ message = "Expected #{object} to be called #{times} times"
59
+ message += " with targets #{@invocation[:targets]}" if @invocation[:targets]
60
+ if parameters
61
+ # Print the parameters hash by converting it to JSON and then re-parsing.
62
+ # This prevents issues in Bolt data types, such as Targets, from generating
63
+ # gigantic, unreadable, data when converted to string by interpolation.
64
+ # Targets exhibit this behavior because they have a reference to @inventory.
65
+ # When the target is converted into a string, it converts the full Inventory
66
+ # into a string recursively.
67
+ parameters_str = JSON.parse(parameters.to_json)
68
+ message += " with parameters #{parameters_str}"
69
+ end
70
+ raise message
71
+ end
72
+ end
73
+
74
+ # This changes the stub from an allow to an expect which will validate
75
+ # that it has been called.
76
+ def expect_call
77
+ @expected_calls = 1
78
+ @expect = true
79
+ self
80
+ end
81
+
82
+ # Used to create a valid Bolt::Result object from result data.
83
+ def default_for(target)
84
+ case @data[:default]
85
+ when Bolt::PlanFailure
86
+ # Bolt::PlanFailure needs to be declared before Bolt::Error because
87
+ # Bolt::PlanFailure is an instance of Bolt::Error, so it can match both
88
+ # in this case we need to treat Bolt::PlanFailure's in a different way
89
+ #
90
+ # raise Bolt::PlanFailure errors so that the PAL can catch them and wrap
91
+ # them into Bolt::PlanResult's for us.
92
+ raise @data[:default]
93
+ when Bolt::Error
94
+ Bolt::Result.from_exception(target, @data[:default])
95
+ when Hash
96
+ result_for(target, **Bolt::Util.walk_keys(@data[:default], &:to_sym))
97
+ else
98
+ raise 'Default result must be a Hash'
99
+ end
100
+ end
101
+
102
+ def check_resultset(result_set, object)
103
+ unless result_set.is_a?(Bolt::ResultSet)
104
+ raise "Return block for #{object} did not return a Bolt::ResultSet"
105
+ end
106
+ result_set
107
+ end
108
+
109
+ def check_plan_result(plan_result, plan_clj)
110
+ unless plan_result.is_a?(Bolt::PlanResult)
111
+ raise "Return block for #{plan_clj.closure_name} did not return a Bolt::PlanResult"
112
+ end
113
+ plan_result
114
+ end
115
+
116
+ # Below here are the intended 'public' methods of the stub
117
+
118
+ # Restricts the stub to only match invocations with
119
+ # the correct targets
120
+ def with_targets(targets)
121
+ targets = Array(targets)
122
+ @invocation[:targets] = targets.map do |target|
123
+ if target.is_a? String
124
+ target
125
+ else
126
+ target.name
127
+ end
128
+ end
129
+ self
130
+ end
131
+
132
+ # limit the maximum number of times an allow stub may be called or
133
+ # specify how many times an expect stub must be called.
134
+ def be_called_times(times)
135
+ @expected_calls = times
136
+ self
137
+ end
138
+
139
+ # error if the stub is called at all.
140
+ def not_be_called
141
+ @expected_calls = 0
142
+ self
143
+ end
144
+
145
+ def return(&block)
146
+ raise "Cannot set return values and return block." if @data_set
147
+ @return_block = block
148
+ self
149
+ end
150
+
151
+ # Set different result values for each target. May use string or symbol keys, but allowed key names
152
+ # are restricted based on action.
153
+ def return_for_targets(data)
154
+ data.each_with_object(@data) do |(target, result), hsh|
155
+ raise "Mocked results must be hashes: #{target}: #{result}" unless result.is_a? Hash
156
+ # set the inventory from the BoltSpec::Plans, otherwise if we try to convert
157
+ # this target to a string, it will fail to string conversion because the
158
+ # inventory is nil
159
+ hsh[target] = result_for(Bolt::Target.new(target, @inventory), **Bolt::Util.walk_keys(result, &:to_sym))
160
+ end
161
+ raise "Cannot set return values and return block." if @return_block
162
+ @data_set = true
163
+ self
164
+ end
165
+
166
+ # Set a default return value for all targets, specific targets may be overridden with return_for_targets.
167
+ # Follows the same rules for data as return_for_targets.
168
+ def always_return(data)
169
+ @data[:default] = data
170
+ @data_set = true
171
+ self
172
+ end
173
+
174
+ # Set a default error result for all targets.
175
+ def error_with(data, clazz = Bolt::Error)
176
+ data = Bolt::Util.walk_keys(data, &:to_s)
177
+ if data['msg'] && data['kind'] && (data.keys - %w[msg kind details issue_code]).empty?
178
+ @data[:default] = clazz.new(data['msg'], data['kind'], data['details'], data['issue_code'])
179
+ else
180
+ $stderr.puts "In the future 'error_with()' might require msg and kind, and " \
181
+ "optionally accept only details and issue_code."
182
+ @data[:default] = data
183
+ end
184
+ @data_set = true
185
+ self
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ require_relative 'action_stubs/command_stub'
192
+ require_relative 'action_stubs/plan_stub'
193
+ require_relative 'action_stubs/script_stub'
194
+ require_relative 'action_stubs/task_stub'
195
+ require_relative 'action_stubs/upload_stub'
196
+ require_relative 'action_stubs/download_stub'
@@ -0,0 +1,361 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt_spec/plans/action_stubs'
4
+ require 'bolt_spec/plans/publish_stub'
5
+ require 'bolt/error'
6
+ require 'bolt/executor'
7
+ require 'bolt/result_set'
8
+ require 'bolt/result'
9
+ require 'pathname'
10
+ require 'set'
11
+
12
+ module BoltSpec
13
+ module Plans
14
+ MOCKED_ACTIONS = %i[command download plan script task upload].freeze
15
+
16
+ class UnexpectedInvocation < ArgumentError; end
17
+
18
+ # Nothing on the executor is 'public'
19
+ class MockExecutor
20
+ attr_reader :noop, :error_message, :transports, :future
21
+ attr_accessor :run_as, :transport_features, :execute_any_plan
22
+
23
+ def initialize(modulepath)
24
+ @noop = false
25
+ @run_as = nil
26
+ @future = {}
27
+ @error_message = nil
28
+ @allow_apply = false
29
+ @modulepath = [modulepath].flatten.map { |path| File.absolute_path(path) }
30
+ MOCKED_ACTIONS.each { |action| instance_variable_set(:"@#{action}_doubles", {}) }
31
+ @stub_out_message = nil
32
+ @stub_out_verbose = nil
33
+ @transport_features = ['puppet-agent']
34
+ @executor_real = Bolt::Executor.new
35
+ # by default, we want to execute any plan that we come across without error
36
+ # or mocking. users can toggle this behavior so that plans will either need to
37
+ # be mocked out, or an error will be thrown.
38
+ @execute_any_plan = true
39
+ # plans that are allowed to be executed by the @executor_real
40
+ @allowed_exec_plans = {}
41
+ @id = 0
42
+ @plan_futures = []
43
+ end
44
+
45
+ def module_file_id(file)
46
+ modpath = @modulepath.select { |path| file =~ /^#{path}/ }
47
+ return nil unless modpath.size == 1
48
+
49
+ path = Pathname.new(file)
50
+ relative = path.relative_path_from(Pathname.new(modpath.first))
51
+ segments = relative.to_path.split('/')
52
+ ([segments[0]] + segments[2..-1]).join('/')
53
+ end
54
+
55
+ def run_command(targets, command, options = {}, _position = [])
56
+ result = nil
57
+ if (doub = @command_doubles[command] || @command_doubles[:default])
58
+ result = doub.process(targets, command, options)
59
+ end
60
+ unless result
61
+ targets = targets.map(&:name)
62
+ @error_message = "Unexpected call to 'run_command(#{command}, #{targets}, #{options})'"
63
+ raise UnexpectedInvocation, @error_message
64
+ end
65
+ result
66
+ end
67
+
68
+ def run_script(targets, script_path, arguments, options = {}, _position = [])
69
+ script = module_file_id(script_path) || script_path
70
+ result = nil
71
+ if (doub = @script_doubles[script] || @script_doubles[:default])
72
+ result = doub.process(targets, script, arguments, options)
73
+ end
74
+ unless result
75
+ targets = targets.map(&:name)
76
+ params = options.merge('arguments' => arguments)
77
+ @error_message = "Unexpected call to 'run_script(#{script}, #{targets}, #{params})'"
78
+ raise UnexpectedInvocation, @error_message
79
+ end
80
+ result
81
+ end
82
+
83
+ def run_task(targets, task, arguments, options = {}, _position = [])
84
+ result = nil
85
+ if (doub = @task_doubles[task.name] || @task_doubles[:default])
86
+ result = doub.process(targets, task.name, arguments, options)
87
+ end
88
+ unless result
89
+ targets = targets.map(&:name)
90
+ params = arguments.merge(options)
91
+ @error_message = "Unexpected call to 'run_task(#{task.name}, #{targets}, #{params})'"
92
+ raise UnexpectedInvocation, @error_message
93
+ end
94
+ result
95
+ end
96
+
97
+ def run_task_with(target_mapping, task, options = {}, _position = [])
98
+ resultsets = target_mapping.map do |target, arguments|
99
+ run_task([target], task, arguments, options)
100
+ end.compact
101
+
102
+ Bolt::ResultSet.new(resultsets.map(&:results).flatten)
103
+ end
104
+
105
+ def download_file(targets, source, destination, options = {}, _position = [])
106
+ result = nil
107
+ if (doub = @download_doubles[source] || @download_doubles[:default])
108
+ result = doub.process(targets, source, destination, options)
109
+ end
110
+ unless result
111
+ targets = targets.map(&:name)
112
+ @error_message = "Unexpected call to 'download_file(#{source}, #{destination}, #{targets}, #{options})'"
113
+ raise UnexpectedInvocation, @error_message
114
+ end
115
+ result
116
+ end
117
+
118
+ def upload_file(targets, source_path, destination, options = {}, _position = [])
119
+ source = module_file_id(source_path) || source_path
120
+ result = nil
121
+ if (doub = @upload_doubles[source] || @upload_doubles[:default])
122
+ result = doub.process(targets, source, destination, options)
123
+ end
124
+ unless result
125
+ targets = targets.map(&:name)
126
+ @error_message = "Unexpected call to 'upload_file(#{source}, #{destination}, #{targets}, #{options})'"
127
+ raise UnexpectedInvocation, @error_message
128
+ end
129
+ result
130
+ end
131
+
132
+ def with_plan_allowed_exec(plan_name, params)
133
+ @allowed_exec_plans[plan_name] = params
134
+ result = yield
135
+ @allowed_exec_plans.delete(plan_name)
136
+ result
137
+ end
138
+
139
+ def run_plan(scope, plan_clj, params)
140
+ result = nil
141
+ plan_name = plan_clj.closure_name
142
+
143
+ # get the mock object either by plan name, or the default in case allow_any_plan
144
+ # was called, if both are nil / don't exist, then dub will be nil and we'll fall
145
+ # through to another conditional statement
146
+ doub = @plan_doubles[plan_name] || @plan_doubles[:default]
147
+
148
+ # rubocop:disable Lint/DuplicateBranch
149
+ # High level:
150
+ # - If we've explicitly allowed execution of the plan (normally the main plan
151
+ # passed into BoltSpec::Plan::run_plan()), then execute it
152
+ # - If we've explicitly "allowed/expected" the plan (mocked),
153
+ # then run it through the mock object
154
+ # - If we're allowing "any" plan to be executed,
155
+ # then execute it
156
+ # - Otherwise we have an error
157
+ if @allowed_exec_plans.key?(plan_name) && @allowed_exec_plans[plan_name] == params
158
+ # This plan's name + parameters were explicitly allowed to be executed.
159
+ # run it with the real executor.
160
+ # We require this functionality so that the BoltSpec::Plans.run_plan()
161
+ # function can kick off the initial plan. In reality, no other plans should
162
+ # be in this hash.
163
+ result = @executor_real.run_plan(scope, plan_clj, params)
164
+ elsif doub
165
+ result = doub.process(scope, plan_clj, params)
166
+ # the throw here is how Puppet exits out of a closure and returns a result
167
+ # it throws this special symbol with a result object that is captured by
168
+ # the run_plan Puppet function
169
+ throw :return, result
170
+ elsif @execute_any_plan
171
+ # if the plan wasn't allowed or mocked out, and we're allowing any plan to be
172
+ # executed, then execute the plan
173
+ result = @executor_real.run_plan(scope, plan_clj, params)
174
+ else
175
+ # convert to JSON and back so that we get the ruby representation with all keys and
176
+ # values converted to a string .to_s instead of their ruby object notation
177
+ params_str = JSON.parse(params.to_json)
178
+ @error_message = "Unexpected call to 'run_plan(#{plan_name}, #{params_str})'"
179
+ raise UnexpectedInvocation, @error_message
180
+ end
181
+ # rubocop:enable Lint/DuplicateBranch
182
+ result
183
+ end
184
+
185
+ def assert_call_expectations
186
+ MOCKED_ACTIONS.each do |action|
187
+ instance_variable_get(:"@#{action}_doubles").map do |object, doub|
188
+ doub.assert_called(object)
189
+ end
190
+ end
191
+ @stub_out_message.assert_called('out::message') if @stub_out_message
192
+ @stub_out_verbose.assert_called('out::verbose') if @stub_out_verbose
193
+ end
194
+
195
+ MOCKED_ACTIONS.each do |action|
196
+ define_method(:"stub_#{action}") do |object|
197
+ instance_variable_get(:"@#{action}_doubles")[object] ||= ActionDouble.new(:"#{action.capitalize}Stub")
198
+ end
199
+ end
200
+
201
+ def stub_out_message
202
+ @stub_out_message ||= ActionDouble.new(:PublishStub)
203
+ end
204
+
205
+ def stub_out_verbose
206
+ @stub_out_verbose ||= ActionDouble.new(:PublishStub)
207
+ end
208
+
209
+ def stub_apply
210
+ @allow_apply = true
211
+ end
212
+
213
+ def wait_until_available(targets, **_options)
214
+ Bolt::ResultSet.new(targets.map { |target| Bolt::Result.new(target) })
215
+ end
216
+
217
+ def log_action(*_args)
218
+ yield
219
+ end
220
+
221
+ def log_plan(_plan_name)
222
+ yield
223
+ end
224
+
225
+ def without_default_logging
226
+ yield
227
+ end
228
+
229
+ def publish_event(event)
230
+ case event[:type]
231
+ when :message
232
+ unless @stub_out_message
233
+ @error_message = "Unexpected call to 'out::message(#{event[:message]})'"
234
+ raise UnexpectedInvocation, @error_message
235
+ end
236
+ @stub_out_message.process(event[:message])
237
+
238
+ when :verbose
239
+ unless @stub_out_verbose
240
+ @error_message = "Unexpected call to 'out::verbose(#{event[:message]})'"
241
+ raise UnexpectedInvocation, @error_message
242
+ end
243
+ @stub_out_verbose.process(event[:message])
244
+ end
245
+ end
246
+
247
+ # Mocked for Apply so it does not compile and execute.
248
+ def with_node_logging(_description, targets)
249
+ raise "Unexpected call to apply(#{targets})" unless @allow_apply
250
+ end
251
+
252
+ def queue_execute(targets)
253
+ raise "Unexpected call to apply(#{targets})" unless @allow_apply
254
+ targets
255
+ end
256
+
257
+ def await_results(promises)
258
+ raise "Unexpected call to apply(#{targets})" unless @allow_apply
259
+ Bolt::ResultSet.new(promises.map { |target| Bolt::ApplyResult.new(target) })
260
+ end
261
+ # End Apply mocking
262
+
263
+ # Mocked for apply_prep
264
+ def transport(_protocol)
265
+ Class.new do
266
+ attr_reader :provided_features
267
+
268
+ def initialize(features)
269
+ @provided_features = features
270
+ end
271
+ end.new(transport_features)
272
+ end
273
+ # End apply_prep mocking
274
+
275
+ # Parallel function mocking
276
+ def run_in_thread
277
+ yield
278
+ end
279
+
280
+ def in_parallel?
281
+ false
282
+ end
283
+
284
+ def create_future(plan_id:, scope: nil, name: nil)
285
+ newscope = nil
286
+ if scope
287
+ # Create the new scope
288
+ newscope = Puppet::Parser::Scope.new(scope.compiler)
289
+ local = Puppet::Parser::Scope::LocalScope.new
290
+
291
+ # Compress the current scopes into a single vars hash to add to the new scope
292
+ scope.to_hash(true, true).each_pair { |k, v| local[k] = v }
293
+ newscope.push_ephemerals([local])
294
+ end
295
+
296
+ # Execute "futures" serially when running in BoltSpec
297
+ result = yield newscope
298
+ @id += 1
299
+ future = Bolt::PlanFuture.new(nil, @id, name: name, plan_id: plan_id)
300
+ future.value = result
301
+ @plan_futures << future
302
+ future
303
+ end
304
+
305
+ def get_futures_for_plan(plan_id:)
306
+ @plan_futures.select { |future| future.plan_id == plan_id }
307
+ end
308
+
309
+ def wait(futures, **_kwargs)
310
+ futures.map(&:value)
311
+ end
312
+
313
+ # Since Futures are executed immediately once created, this will always
314
+ # be true by the time it's called.
315
+ def plan_complete?
316
+ true
317
+ end
318
+
319
+ def get_current_future(fiber)
320
+ @plan_futures.select { |f| f.fiber == fiber }&.first
321
+ end
322
+
323
+ def get_current_plan_id(fiber)
324
+ get_current_future(fiber)&.current_plan
325
+ end
326
+
327
+ # Public methods on Bolt::Executor that need to be mocked so there aren't
328
+ # "undefined method" errors.
329
+
330
+ def batch_execute(_targets); end
331
+
332
+ def finish_plan(_plan_result); end
333
+
334
+ def handle_event(_event); end
335
+
336
+ def prompt(_prompt, _options); end
337
+
338
+ def report_function_call(_function); end
339
+
340
+ def report_bundled_content(_mode, _name); end
341
+
342
+ def report_file_source(_plan_function, _source); end
343
+
344
+ def report_apply(_statements, _resources); end
345
+
346
+ def report_yaml_plan(_plan); end
347
+
348
+ def report_noop_mode(_mode); end
349
+
350
+ def shutdown; end
351
+
352
+ def start_plan(_plan_context); end
353
+
354
+ def subscribe(_subscriber, _types = nil); end
355
+
356
+ def unsubscribe(_subscriber, _types = nil); end
357
+
358
+ def round_robin; end
359
+ end
360
+ end
361
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/result'
4
+ require 'bolt/util'
5
+
6
+ module BoltSpec
7
+ module Plans
8
+ class PublishStub < ActionStub
9
+ def return
10
+ raise "return is not implemented for out module functions"
11
+ end
12
+
13
+ def return_for_targets(_data)
14
+ raise "return_for_targets is not implemented for out module functions"
15
+ end
16
+
17
+ def always_return(_data)
18
+ raise "always_return is not implemented for out module functions"
19
+ end
20
+
21
+ def error_with(_data)
22
+ raise "error_with is not implemented for out module functions"
23
+ end
24
+
25
+ def matches(message)
26
+ if @invocation[:options] && message != @invocation[:options]
27
+ return false
28
+ end
29
+
30
+ true
31
+ end
32
+
33
+ def call(_event)
34
+ @calls += 1
35
+ end
36
+
37
+ def parameters
38
+ @invocation[:options]
39
+ end
40
+
41
+ # Public methods
42
+
43
+ def with_params(params)
44
+ @invocation[:options] = params
45
+ self
46
+ end
47
+ end
48
+ end
49
+ end