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
data/lib/bolt/pal.rb ADDED
@@ -0,0 +1,847 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../bolt/applicator'
4
+ require_relative '../bolt/executor'
5
+ require_relative '../bolt/error'
6
+ require_relative '../bolt/plan_result'
7
+ require_relative '../bolt/util'
8
+ require_relative '../bolt/config/modulepath'
9
+ require 'etc'
10
+
11
+ module Bolt
12
+ class PAL
13
+ # PALError is used to convert errors from executing puppet code into
14
+ # Bolt::Errors
15
+ class PALError < Bolt::Error
16
+ def self.from_preformatted_error(err)
17
+ error = if err.cause.is_a? Bolt::Error
18
+ err.cause
19
+ else
20
+ from_error(err)
21
+ end
22
+
23
+ # Provide the location of an error if it came from a plan
24
+ details = {}
25
+ details[:file] = err.file if defined?(err.file)
26
+ details[:line] = err.line if defined?(err.line)
27
+ details[:column] = err.pos if defined?(err.pos)
28
+
29
+ error.add_filelineno(details.compact)
30
+ error
31
+ end
32
+
33
+ # Generate a Bolt::Pal::PALError for non-bolt errors
34
+ def self.from_error(err)
35
+ # Use the original error message if available
36
+ message = err.cause ? err.cause.message : err.message
37
+ e = new(message)
38
+ e.set_backtrace(err.backtrace)
39
+ e
40
+ end
41
+
42
+ def initialize(msg, details = {})
43
+ super(msg, 'bolt/pal-error', details)
44
+ end
45
+ end
46
+
47
+ def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors,
48
+ trusted_external = nil, apply_settings = {}, project = nil)
49
+ unless modulepath.is_a?(Bolt::Config::Modulepath)
50
+ msg = "Type error in PAL: modulepath must be a Bolt::Config::Modulepath"
51
+ raise Bolt::Error.new(msg, "bolt/execution-error")
52
+ end
53
+ # Nothing works without initialized this global state. Reinitializing
54
+ # is safe and in practice only happens in tests
55
+ self.class.load_puppet
56
+ @modulepath = modulepath
57
+ @hiera_config = hiera_config
58
+ @trusted_external = trusted_external
59
+ @apply_settings = apply_settings
60
+ @max_compiles = max_compiles
61
+ @resource_types = resource_types
62
+ @project = project
63
+
64
+ @logger = Bolt::Logger.logger(self)
65
+ unless user_modulepath.empty?
66
+ @logger.debug("Loading modules from #{full_modulepath.join(File::PATH_SEPARATOR)}")
67
+ end
68
+
69
+ @loaded = false
70
+ end
71
+
72
+ def full_modulepath
73
+ @modulepath.full_modulepath
74
+ end
75
+
76
+ def user_modulepath
77
+ @modulepath.user_modulepath
78
+ end
79
+
80
+ # Puppet logging is global so this is class method to avoid confusion
81
+ def self.configure_logging
82
+ Puppet::Util::Log.destinations.clear
83
+ Puppet::Util::Log.newdestination(Bolt::Logger.logger('Puppet'))
84
+ # Defer all log level decisions to the Logging library by telling Puppet
85
+ # to log everything
86
+ Puppet.settings[:log_level] = 'debug'
87
+ end
88
+
89
+ def self.load_puppet
90
+ if Bolt::Util.windows?
91
+ # Windows 'fix' for openssl behaving strangely. Prevents very slow operation
92
+ # of random_bytes later when establishing winrm connections from a Windows host.
93
+ # See https://github.com/rails/rails/issues/25805 for background.
94
+ require 'openssl'
95
+ OpenSSL::Random.random_bytes(1)
96
+ end
97
+
98
+ begin
99
+ require 'puppet_pal'
100
+ rescue LoadError
101
+ raise Bolt::Error.new("Puppet must be installed to execute tasks", "bolt/puppet-missing")
102
+ end
103
+
104
+ require_relative 'pal/logging'
105
+ require_relative 'pal/issues'
106
+ require_relative 'pal/yaml_plan/loader'
107
+ require_relative 'pal/yaml_plan/transpiler'
108
+
109
+ # Now that puppet is loaded we can include puppet mixins in data types
110
+ Bolt::ResultSet.include_iterable
111
+ end
112
+
113
+ def setup
114
+ unless @loaded
115
+ # This is slow so don't do it until we have to
116
+ Bolt::PAL.load_puppet
117
+
118
+ # Make sure we don't create the puppet directories
119
+ with_puppet_settings { |_| nil }
120
+ @loaded = true
121
+ end
122
+ end
123
+
124
+ # Create a top-level alias for TargetSpec and PlanResult so that users don't have to
125
+ # namespace it with Boltlib, which is just an implementation detail. This
126
+ # allows them to feel like a built-in type in bolt, rather than
127
+ # something has been, no pun intended, "bolted on".
128
+ def alias_types(compiler)
129
+ compiler.evaluate_string('type TargetSpec = Boltlib::TargetSpec')
130
+ compiler.evaluate_string('type PlanResult = Boltlib::PlanResult')
131
+ end
132
+
133
+ # Register all resource types defined in $Project/.resource_types as well as
134
+ # the built in types registered with the runtime_3_init method.
135
+ def register_resource_types(loaders)
136
+ static_loader = loaders.static_loader
137
+ static_loader.runtime_3_init
138
+ if File.directory?(@resource_types)
139
+ Dir.children(@resource_types).each do |resource_pp|
140
+ type_name_from_file = File.basename(resource_pp, '.pp').capitalize
141
+ typed_name = Puppet::Pops::Loader::TypedName.new(:type, type_name_from_file)
142
+ resource_type = Puppet::Pops::Types::TypeFactory.resource(type_name_from_file)
143
+ loaders.static_loader.set_entry(typed_name, resource_type)
144
+ end
145
+ end
146
+ end
147
+
148
+ def detect_project_conflict(project, environment)
149
+ return unless project && project.load_as_module?
150
+ # The environment modulepath has stripped out non-existent directories,
151
+ # so we don't need to check for them
152
+ modules = environment.modulepath.flat_map do |path|
153
+ Dir.children(path).select { |name| Puppet::Module.is_module_directory?(name, path) }
154
+ end
155
+ if modules.include?(project.name)
156
+ Bolt::Logger.warn_once(
157
+ "project_shadows_module",
158
+ "The project '#{project.name}' shadows an existing module of the same name"
159
+ )
160
+ end
161
+ end
162
+
163
+ # Runs a block in a PAL script compiler configured for Bolt. Catches
164
+ # exceptions thrown by the block and re-raises them ensuring they are
165
+ # Bolt::Errors since the script compiler block will squash all exceptions.
166
+ def in_bolt_compiler(compiler_params: {})
167
+ # TODO: If we always call this inside a bolt_executor we can remove this here
168
+ setup
169
+ compiler_params = compiler_params.merge(set_local_facts: false)
170
+ r = Puppet::Pal.in_tmp_environment('bolt', modulepath: full_modulepath, facts: {}) do |pal|
171
+ # Only load the project if it a) exists, b) has a name it can be loaded with
172
+ Puppet.override(bolt_project: @project,
173
+ yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
174
+ # Because this has the side effect of loading and caching the list
175
+ # of modules, it must happen *after* we have overridden
176
+ # bolt_project or the project will be ignored
177
+ detect_project_conflict(@project, Puppet.lookup(:environments).get('bolt'))
178
+ pal.with_script_compiler(**compiler_params) do |compiler|
179
+ alias_types(compiler)
180
+ register_resource_types(Puppet.lookup(:loaders)) if @resource_types
181
+ begin
182
+ yield compiler
183
+ rescue Bolt::Error => e
184
+ e
185
+ rescue Puppet::DataBinding::LookupError => e
186
+ if e.issue_code == :HIERA_UNDEFINED_VARIABLE
187
+ message = "Interpolations are not supported in lookups outside of an apply block: #{e.message}"
188
+ PALError.new(message)
189
+ else
190
+ PALError.from_preformatted_error(e)
191
+ end
192
+ rescue Puppet::PreformattedError => e
193
+ if e.issue_code == :UNKNOWN_VARIABLE &&
194
+ %w[facts trusted server_facts settings].include?(e.arguments[:name])
195
+ message = "Evaluation Error: Variable '#{e.arguments[:name]}' is not available in the current scope "\
196
+ "unless explicitly defined."
197
+ details = { file: e.file, line: e.line, column: e.pos }
198
+ PALError.new(message, details)
199
+ else
200
+ PALError.from_preformatted_error(e)
201
+ end
202
+ rescue StandardError => e
203
+ PALError.from_preformatted_error(e)
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ # Plans may return PuppetError but nothing should be throwing them
210
+ if r.is_a?(StandardError) && !r.is_a?(Bolt::PuppetError)
211
+ raise r
212
+ end
213
+ r
214
+ end
215
+
216
+ def with_bolt_executor(executor, inventory, pdb_client = nil, applicator = nil, &block)
217
+ setup
218
+ opts = {
219
+ bolt_project: @project,
220
+ bolt_executor: executor,
221
+ bolt_inventory: inventory,
222
+ bolt_pdb_client: pdb_client,
223
+ apply_executor: applicator || Applicator.new(
224
+ inventory,
225
+ executor,
226
+ full_modulepath,
227
+ # Skip syncing built-in plugins, since we vendor some Puppet 6
228
+ # versions of "core" types, which are already present on the agent,
229
+ # but may cause issues on Puppet 5 agents.
230
+ user_modulepath,
231
+ @project,
232
+ pdb_client,
233
+ @hiera_config,
234
+ @max_compiles,
235
+ @apply_settings
236
+ )
237
+ }
238
+ Puppet.override(opts, &block)
239
+ end
240
+
241
+ def in_catalog_compiler
242
+ with_puppet_settings do
243
+ Puppet.override(bolt_project: @project) do
244
+ Puppet::Pal.in_tmp_environment('bolt', modulepath: full_modulepath) do |pal|
245
+ pal.with_catalog_compiler do |compiler|
246
+ yield compiler
247
+ end
248
+ end
249
+ end
250
+ rescue Puppet::Error => e
251
+ raise PALError.from_error(e)
252
+ end
253
+ end
254
+
255
+ def in_plan_compiler(executor, inventory, pdb_client, applicator = nil)
256
+ with_bolt_executor(executor, inventory, pdb_client, applicator) do
257
+ # TODO: remove this call and see if anything breaks when
258
+ # settings dirs don't actually exist. Plans shouldn't
259
+ # actually be using them.
260
+ with_puppet_settings do
261
+ in_bolt_compiler do |compiler|
262
+ yield compiler
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ def in_task_compiler(executor, inventory)
269
+ with_bolt_executor(executor, inventory) do
270
+ in_bolt_compiler do |compiler|
271
+ yield compiler
272
+ end
273
+ end
274
+ end
275
+
276
+ # TODO: PUP-8553 should replace this
277
+ def with_puppet_settings
278
+ dir = Dir.mktmpdir('bolt')
279
+
280
+ cli = []
281
+ Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
282
+ cli << "--#{setting}" << dir
283
+ end
284
+ Puppet.settings.send(:clear_everything_for_tests)
285
+ Puppet.initialize_settings(cli)
286
+ Puppet::GettextConfig.create_default_text_domain
287
+ Puppet[:trusted_external_command] = @trusted_external
288
+ Puppet.settings[:hiera_config] = @hiera_config
289
+ self.class.configure_logging
290
+ yield
291
+ ensure
292
+ # Delete the tmpdir if it still exists. This check is needed to
293
+ # prevent Bolt from erroring if the tmpdir is somehow deleted
294
+ # before reaching this point.
295
+ FileUtils.remove_entry_secure(dir) if File.exist?(dir)
296
+ end
297
+
298
+ # Parses a snippet of Puppet manifest code and returns the AST represented
299
+ # in JSON.
300
+ def parse_manifest(code, filename)
301
+ setup
302
+ Puppet::Pops::Parser::EvaluatingParser.new.parse_string(code, filename)
303
+ rescue Puppet::Error => e
304
+ raise Bolt::PAL::PALError, "Failed to parse manifest: #{e}"
305
+ end
306
+
307
+ # Filters content by a list of names and glob patterns specified in project
308
+ # configuration.
309
+ def filter_content(content, patterns)
310
+ return content unless content && patterns
311
+
312
+ content.select do |name,|
313
+ patterns.any? { |pattern| File.fnmatch?(pattern, name, File::FNM_EXTGLOB) }
314
+ end
315
+ end
316
+
317
+ def list_tasks_with_cache(filter_content: false)
318
+ # Don't filter content yet, so that if users update their task filters
319
+ # we don't need to refresh the cache
320
+ task_names = list_tasks(filter_content: false).map(&:first)
321
+ task_cache = if @project
322
+ Bolt::Util.read_optional_json_file(@project.task_cache_file, 'Task cache file')
323
+ else
324
+ {}
325
+ end
326
+ updated = false
327
+
328
+ task_list = task_names.each_with_object([]) do |task_name, list|
329
+ data = task_cache[task_name] || get_task_info(task_name, with_mtime: true)
330
+
331
+ # Make sure all the keys are strings - if we get data from
332
+ # get_task_info they will be symbols
333
+ data = Bolt::Util.walk_keys(data, &:to_s)
334
+
335
+ # If any files in the task were updated, refresh the cache
336
+ if data['files']&.any?
337
+ # For all the files that are part of the task
338
+ data['files'].each do |f|
339
+ # If any file has been updated since we last cached, update the
340
+ # cache
341
+ next unless file_modified?(f['path'], f['mtime'])
342
+ data = get_task_info(task_name, with_mtime: true)
343
+ data = Bolt::Util.walk_keys(data, &:to_s)
344
+ # Tell Bolt to write to the cache file once we're done
345
+ updated = true
346
+ # Update the cache data
347
+ task_cache[task_name] = data
348
+ end
349
+ end
350
+ metadata = data['metadata'] || {}
351
+ # Don't add tasks to the list to return if they are private
352
+ list << [task_name, metadata['description']] unless metadata['private']
353
+ end
354
+
355
+ # Write the cache if any entries were updated
356
+ File.write(@project.task_cache_file, task_cache.to_json) if updated && @project
357
+ filter_content ? filter_content(task_list, @project&.tasks) : task_list
358
+ end
359
+
360
+ def list_tasks(filter_content: false)
361
+ in_bolt_compiler do |compiler|
362
+ tasks = compiler.list_tasks.map(&:name).sort.each_with_object([]) do |task_name, data|
363
+ task_sig = compiler.task_signature(task_name)
364
+ unless task_sig.task_hash['metadata']['private']
365
+ data << [task_name, task_sig.task_hash['metadata']['description']]
366
+ end
367
+ end
368
+
369
+ filter_content ? filter_content(tasks, @project&.tasks) : tasks
370
+ end
371
+ end
372
+
373
+ def parse_params(type, object_name, params)
374
+ in_bolt_compiler do |compiler|
375
+ case type
376
+ when 'task'
377
+ param_spec = compiler.task_signature(object_name)&.task_hash&.dig('parameters')
378
+ when 'plan'
379
+ plan = compiler.plan_signature(object_name)
380
+ param_spec = plan.params_type.elements&.each_with_object({}) { |t, h| h[t.name] = t.value_type } if plan
381
+ end
382
+ param_spec ||= {}
383
+
384
+ params.each_with_object({}) do |(name, str), acc|
385
+ type = param_spec[name]
386
+ begin
387
+ parsed = JSON.parse(str, quirks_mode: true)
388
+ # The type may not exist if the module is remote on orch or if a task
389
+ # defines no parameters. Since we treat no parameters as Any we
390
+ # should parse everything in this case
391
+ acc[name] = if type && !type.instance?(parsed)
392
+ str
393
+ else
394
+ parsed
395
+ end
396
+ rescue JSON::ParserError
397
+ # This value may not be assignable in which case run_* will error
398
+ acc[name] = str
399
+ end
400
+ acc
401
+ end
402
+ end
403
+ end
404
+
405
+ def task_signature(task_name)
406
+ in_bolt_compiler do |compiler|
407
+ compiler.task_signature(task_name)
408
+ end
409
+ end
410
+
411
+ def get_task(task_name, with_mtime: false)
412
+ task = task_signature(task_name)
413
+
414
+ if task.nil?
415
+ raise Bolt::Error.unknown_task(task_name)
416
+ end
417
+
418
+ task = Bolt::Task.from_task_signature(task)
419
+ task.add_mtimes if with_mtime
420
+ task
421
+ end
422
+
423
+ def get_task_info(task_name, with_mtime: false)
424
+ get_task(task_name, with_mtime: with_mtime).to_h
425
+ end
426
+
427
+ def list_plans_with_cache(filter_content: false)
428
+ # Don't filter content yet, so that if users update their plan filters
429
+ # we don't need to refresh the cache
430
+ plan_names = list_plans(filter_content: false).map(&:first)
431
+ plan_cache = if @project
432
+ Bolt::Util.read_optional_json_file(@project.plan_cache_file, 'Plan cache file')
433
+ else
434
+ {}
435
+ end
436
+ updated = false
437
+
438
+ plan_list = plan_names.each_with_object([]) do |plan_name, list|
439
+ data = plan_cache[plan_name] || get_plan_info(plan_name, with_mtime: true)
440
+ # If the plan is a 'local' plan (in the project itself, or the modules/
441
+ # directory) then verify it hasn't been updated since we cached it. If
442
+ # it has been updated, refresh the cache and use the new data.
443
+ if file_modified?(data.dig('file', 'path'), data.dig('file', 'mtime'))
444
+ data = get_plan_info(plan_name, with_mtime: true)
445
+ updated = true
446
+ plan_cache[plan_name] = data
447
+ end
448
+
449
+ list << [plan_name, data['description']] unless data['private']
450
+ end
451
+
452
+ File.write(@project.plan_cache_file, plan_cache.to_json) if updated && @project
453
+
454
+ filter_content ? filter_content(plan_list, @project&.plans) : plan_list
455
+ end
456
+
457
+ # Returns true if a file has been modified or no longer exists, false
458
+ # otherwise.
459
+ #
460
+ # @param path [String] The path to the file.
461
+ # @param mtime [String] The last time the file was modified.
462
+ #
463
+ private def file_modified?(path, mtime)
464
+ path && !(File.exist?(path) && File.mtime(path).to_s == mtime.to_s)
465
+ end
466
+
467
+ def list_plans(filter_content: false)
468
+ in_bolt_compiler do |compiler|
469
+ errors = []
470
+ plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
471
+ errors.each do |error|
472
+ Bolt::Logger.warn("plan_load_error", error.details['original_error'])
473
+ end
474
+
475
+ filter_content ? filter_content(plans, @project&.plans) : plans
476
+ end
477
+ end
478
+
479
+ def get_plan_info(plan_name, with_mtime: false)
480
+ plan_sig = in_bolt_compiler do |compiler|
481
+ compiler.plan_signature(plan_name)
482
+ end
483
+
484
+ if plan_sig.nil?
485
+ raise Bolt::Error.unknown_plan(plan_name)
486
+ end
487
+
488
+ # path may be a Pathname object, so make sure to stringify it
489
+ mod = plan_sig.instance_variable_get(:@plan_func).loader.parent.path.to_s
490
+
491
+ # If it's a Puppet language plan, use strings to extract data. The only
492
+ # way to tell is to check which filename exists in the module.
493
+ plan_subpath = File.join(plan_name.split('::').drop(1))
494
+ plan_subpath = 'init' if plan_subpath.empty?
495
+
496
+ pp_path = File.join(mod, 'plans', "#{plan_subpath}.pp")
497
+ if File.exist?(pp_path)
498
+ require 'puppet-strings'
499
+ require 'puppet-strings/yard'
500
+ PuppetStrings::Yard.setup!
501
+ YARD::Logger.instance.level = if YARD::Logger.const_defined?(:Severity)
502
+ YARD::Logger::Severity::ERROR
503
+ else
504
+ # Backward compatility for YARD < 0.9.37
505
+ YARD::Logger.instance.level = :error
506
+ end
507
+ YARD.parse(pp_path)
508
+
509
+ plan = YARD::Registry.at("puppet_plans::#{plan_name}")
510
+
511
+ description = if plan.tag(:summary)
512
+ plan.tag(:summary).text
513
+ elsif !plan.docstring.empty?
514
+ plan.docstring
515
+ end
516
+
517
+ defaults = plan.parameters.to_h.compact
518
+ signature_params = Set.new(plan.parameters.map(&:first))
519
+ parameters = plan.tags(:param).each_with_object({}) do |param, params|
520
+ name = param.name
521
+ if signature_params.include?(name)
522
+ params[name] = { 'type' => param.types.first }
523
+ params[name]['sensitive'] = param.types.first =~ /\ASensitive(\[.*\])?\z/ ? true : false
524
+ params[name]['default_value'] = defaults[name] if defaults.key?(name)
525
+ params[name]['description'] = param.text if param.text && !param.text.empty?
526
+ else
527
+ Bolt::Logger.warn(
528
+ "missing_plan_parameter",
529
+ "The documented parameter '#{name}' does not exist in signature for plan '#{plan.name}'"
530
+ )
531
+ end
532
+ end
533
+
534
+ pp_info = {
535
+ 'name' => plan_name,
536
+ 'description' => description,
537
+ 'parameters' => parameters,
538
+ 'module' => mod,
539
+ 'private' => private_plan?(plan),
540
+ 'summary' => plan.tag(:summary)&.text,
541
+ 'docstring' => (plan.docstring unless plan.docstring.empty?)
542
+ }
543
+
544
+ pp_info.merge!(get_plan_mtime(plan.file)) if with_mtime
545
+ pp_info
546
+
547
+ # If it's a YAML plan, fall back to limited data
548
+ else
549
+ yaml_path = File.join(mod, 'plans', "#{plan_subpath}.yaml")
550
+ plan_content = File.read(yaml_path)
551
+ plan = Bolt::PAL::YamlPlan::Loader.from_string(plan_name, plan_content, yaml_path)
552
+
553
+ parameters = plan.parameters.each_with_object({}) do |param, params|
554
+ name = param.name
555
+ type_str = case param.type_expr
556
+ when Puppet::Pops::Types::PTypeReferenceType
557
+ param.type_expr.type_string
558
+ when nil
559
+ 'Any'
560
+ else
561
+ param.type_expr
562
+ end
563
+ params[name] = { 'type' => type_str }
564
+ params[name]['sensitive'] = param.type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
565
+ params[name]['default_value'] = param.value unless param.value.nil?
566
+ params[name]['description'] = param.description if param.description
567
+ end
568
+
569
+ yaml_info = {
570
+ 'name' => plan_name,
571
+ 'description' => plan.description,
572
+ 'parameters' => parameters,
573
+ 'module' => mod,
574
+ 'private' => !!plan.private,
575
+ 'docstring' => plan.description,
576
+ 'summary' => nil
577
+ }
578
+
579
+ yaml_info.merge!(get_plan_mtime(yaml_path)) if with_mtime
580
+ yaml_info
581
+ end
582
+ end
583
+
584
+ # Returns true if the plan is private, false otherwise.
585
+ #
586
+ # @param plan [PuppetStrings::Yard::CodeObjects::Plan] The puppet-strings plan documentation.
587
+ # @return [Boolean]
588
+ #
589
+ private def private_plan?(plan)
590
+ if plan.tag(:private)
591
+ value = plan.tag(:private).text
592
+ api_value = value.downcase == 'true' ? 'private' : 'public'
593
+
594
+ Bolt::Logger.deprecate(
595
+ 'plan_private_tag',
596
+ "Tag '@private #{value}' in plan '#{plan.name}' is deprecated, use '@api #{api_value}' instead"
597
+ )
598
+
599
+ unless %w[true false].include?(plan.tag(:private).text.downcase)
600
+ msg = "Value for '@private' tag in plan '#{plan.name}' must be a boolean, received: #{value}"
601
+ raise Bolt::Error.new(msg, 'bolt/invalid-plan')
602
+ end
603
+ end
604
+
605
+ plan.tag(:api).text == 'private' || plan.tag(:private)&.text&.downcase == 'true'
606
+ end
607
+
608
+ def get_plan_mtime(path)
609
+ # If the plan is from the project modules/ directory, or is in the
610
+ # project itself, include the last mtime of the file so we can compare
611
+ # if the plan has been updated since it was cached.
612
+ if @project &&
613
+ File.exist?(path) &&
614
+ (path.include?(File.join(@project.path, 'modules')) ||
615
+ path.include?(@project.plans_path.to_s))
616
+
617
+ { 'file' => { 'mtime' => File.mtime(path),
618
+ 'path' => path } }
619
+ else
620
+ {}
621
+ end
622
+ end
623
+
624
+ def convert_plan(plan)
625
+ path = File.expand_path(plan)
626
+
627
+ # If the path doesn't exist, check if it's a plan name
628
+ unless File.exist?(path)
629
+ in_bolt_compiler do |compiler|
630
+ sig = compiler.plan_signature(plan)
631
+
632
+ # If the plan was loaded, look for it on the module loader
633
+ # There has to be an easier way to do this...
634
+ if sig
635
+ type = compiler.list_plans.find { |p| p.name == plan }
636
+ path = sig.instance_variable_get(:@plan_func)
637
+ .loader
638
+ .find(type)
639
+ .origin
640
+ .first
641
+ end
642
+ end
643
+ end
644
+
645
+ Puppet[:tasks] = true
646
+ transpiler = YamlPlan::Transpiler.new
647
+ transpiler.transpile(path)
648
+ end
649
+
650
+ # Returns a mapping of all modules available to the Bolt compiler
651
+ #
652
+ # @return [Hash{String => Array<Hash{Symbol => String,nil}>}]
653
+ # A hash that associates each directory on the modulepath with an array
654
+ # containing a hash of information for each module in that directory.
655
+ # The information hash provides the name, version, and a string
656
+ # indicating whether the module belongs to an internal module group.
657
+ def list_modules
658
+ internal_module_groups = { Bolt::Config::Modulepath::BOLTLIB_PATH => 'Plan Language Modules',
659
+ Bolt::Config::Modulepath::MODULES_PATH => 'Packaged Modules',
660
+ @project.managed_moduledir.to_s => 'Project Dependencies' }
661
+
662
+ in_bolt_compiler do
663
+ # NOTE: Can replace map+to_h with transform_values when Ruby 2.4
664
+ # is the minimum supported version.
665
+ Puppet.lookup(:current_environment).modules_by_path.map do |path, modules|
666
+ module_group = internal_module_groups[path]
667
+
668
+ values = modules.map do |mod|
669
+ mod_info = { name: (mod.forge_name || mod.name),
670
+ version: mod.version }
671
+ mod_info[:internal_module_group] = module_group unless module_group.nil?
672
+
673
+ mod_info
674
+ end
675
+
676
+ [path, values]
677
+ end.to_h
678
+ end
679
+ end
680
+
681
+ # Return information about a module.
682
+ #
683
+ # @param name [String] The name of the module.
684
+ # @return [Hash]
685
+ #
686
+ def show_module(name)
687
+ name = name.tr('-', '/')
688
+
689
+ data = in_bolt_compiler do |_compiler|
690
+ mod = Puppet.lookup(:current_environment).module(name.split(%r{[/-]}, 2).last)
691
+
692
+ unless mod && (mod.forge_name == name || mod.name == name)
693
+ raise Bolt::Error.new("Could not find module '#{name}' on the modulepath.", 'bolt/unknown-module')
694
+ end
695
+
696
+ {
697
+ name: mod.forge_name || mod.name,
698
+ metadata: mod.metadata,
699
+ path: mod.path,
700
+ plans: mod.plans.map(&:name).sort,
701
+ tasks: mod.tasks.map(&:name).sort
702
+ }
703
+ end
704
+
705
+ data[:plans] = list_plans_with_cache.to_h.slice(*data[:plans]).to_a
706
+ data[:tasks] = list_tasks_with_cache.to_h.slice(*data[:tasks]).to_a
707
+
708
+ data
709
+ end
710
+
711
+ def generate_types(cache: false)
712
+ require 'puppet/face/generate'
713
+ in_bolt_compiler do
714
+ generator = Puppet::Generate::Type
715
+ inputs = generator.find_inputs(:pcore)
716
+ FileUtils.mkdir_p(@resource_types)
717
+ cache_plan_info if @project && cache
718
+ cache_task_info if @project && cache
719
+ generator.generate(inputs, @resource_types, true)
720
+ end
721
+ end
722
+
723
+ def cache_plan_info
724
+ # plan_name is an array here
725
+ plans_info = list_plans(filter_content: false).map do |plan_name,|
726
+ data = get_plan_info(plan_name, with_mtime: true)
727
+ { plan_name => data }
728
+ end.reduce({}, :merge)
729
+
730
+ FileUtils.touch(@project.plan_cache_file)
731
+ File.write(@project.plan_cache_file, plans_info.to_json)
732
+ end
733
+
734
+ def cache_task_info
735
+ # task_name is an array here
736
+ tasks_info = list_tasks(filter_content: false).map do |task_name,|
737
+ data = get_task_info(task_name, with_mtime: true)
738
+ { task_name => data }
739
+ end.reduce({}, :merge)
740
+
741
+ FileUtils.touch(@project.task_cache_file)
742
+ File.write(@project.task_cache_file, tasks_info.to_json)
743
+ end
744
+
745
+ def run_task(task_name, targets, params, executor, inventory, description = nil)
746
+ in_task_compiler(executor, inventory) do |compiler|
747
+ params = params.merge('_bolt_api_call' => true, '_catch_errors' => true)
748
+ compiler.call_function('run_task', task_name, targets, description, params)
749
+ end
750
+ end
751
+
752
+ def run_plan(plan_name, params, executor, inventory = nil, pdb_client = nil, applicator = nil)
753
+ # Start the round robin inside the plan compiler, so that
754
+ # backgrounded tasks can finish once the main plan exits
755
+ in_plan_compiler(executor, inventory, pdb_client, applicator) do |compiler|
756
+ # Create a Fiber for the main plan. This will be run along with any
757
+ # other Fibers created during the plan run in the round_robin, with the
758
+ # main plan always taking precedence in being resumed.
759
+ #
760
+ # Every future except for the main plan needs to have a plan id in
761
+ # order to be tracked for the `wait()` function with no arguments.
762
+ future = executor.create_future(name: plan_name, plan_id: 0) do |_scope|
763
+ r = compiler.call_function('run_plan', plan_name, params.merge('_bolt_api_call' => true))
764
+ Bolt::PlanResult.from_pcore(r, 'success')
765
+ rescue Bolt::Error => e
766
+ Bolt::PlanResult.new(e, 'failure')
767
+ end
768
+
769
+ # Round robin until all Fibers, including the main plan, have finished.
770
+ # This will stay alive until backgrounded tasks have finished.
771
+ executor.round_robin until executor.plan_complete?
772
+
773
+ # Return the result from the main plan
774
+ future.value
775
+ end
776
+ rescue Bolt::Error => e
777
+ Bolt::PlanResult.new(e, 'failure')
778
+ end
779
+
780
+ def plan_hierarchy_lookup(key, plan_vars: {})
781
+ # Do a lookup with a script compiler, which uses the 'plan_hierarchy' key in
782
+ # Hiera config.
783
+ with_puppet_settings do
784
+ # We want all of the setup and teardown that `in_bolt_compiler` does,
785
+ # but also want to pass keys to the script compiler.
786
+ in_bolt_compiler(compiler_params: { variables: plan_vars }) do |compiler|
787
+ compiler.call_function('lookup', key)
788
+ end
789
+ rescue Puppet::Error => e
790
+ raise PALError.from_error(e)
791
+ end
792
+ end
793
+
794
+ def lookup(key, targets, inventory, executor, plan_vars: {})
795
+ # Install the puppet-agent package and collect facts. Facts are
796
+ # automatically added to the targets.
797
+ in_plan_compiler(executor, inventory, nil) do |compiler|
798
+ compiler.call_function('apply_prep', targets)
799
+ end
800
+
801
+ overrides = {
802
+ bolt_inventory: inventory,
803
+ bolt_project: @project
804
+ }
805
+
806
+ # Do a lookup with a catalog compiler, which uses the 'hierarchy' key in
807
+ # Hiera config.
808
+ results = targets.map do |target|
809
+ node = Puppet::Node.from_data_hash(
810
+ 'name' => target.name,
811
+ 'parameters' => { 'clientcert' => target.name }
812
+ )
813
+
814
+ trusted = Puppet::Context::TrustedInformation.local(node).to_h
815
+
816
+ # Separate environment configuration from interpolation data the same
817
+ # way we do when compiling Puppet catalogs.
818
+ env_conf = {
819
+ modulepath: @modulepath.full_modulepath,
820
+ facts: target.facts
821
+ }
822
+
823
+ interpolations = {
824
+ variables: plan_vars,
825
+ target_variables: target.vars
826
+ }
827
+
828
+ with_puppet_settings do
829
+ Puppet::Pal.in_tmp_environment(target.name, **env_conf) do |pal|
830
+ Puppet.override(overrides) do
831
+ Puppet.lookup(:pal_current_node).trusted_data = trusted
832
+ pal.with_catalog_compiler(**interpolations) do |compiler|
833
+ Bolt::Result.for_lookup(target, key, compiler.call_function('lookup', key))
834
+ rescue StandardError => e
835
+ Bolt::Result.from_exception(target, e)
836
+ end
837
+ rescue Puppet::Error => e
838
+ raise PALError.from_error(e)
839
+ end
840
+ end
841
+ end
842
+ end
843
+
844
+ Bolt::ResultSet.new(results)
845
+ end
846
+ end
847
+ end