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,368 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require_relative '../bolt/apply_result'
5
+ require_relative '../bolt/apply_target'
6
+ require_relative '../bolt/config'
7
+ require_relative '../bolt/error'
8
+ require_relative '../bolt/task'
9
+ require_relative '../bolt/util/puppet_log_level'
10
+ require 'find'
11
+ require 'json'
12
+ require 'logging'
13
+ require 'open3'
14
+
15
+ module Bolt
16
+ class Applicator
17
+ def initialize(inventory, executor, modulepath, plugin_dirs, project,
18
+ pdb_client, hiera_config, max_compiles, apply_settings)
19
+ # lazy-load expensive gem code
20
+ require 'concurrent'
21
+ @inventory = inventory
22
+ @executor = executor
23
+ @modulepath = modulepath || []
24
+ @plugin_dirs = plugin_dirs
25
+ @project = project
26
+ @pdb_client = pdb_client
27
+ @hiera_config = hiera_config ? validate_hiera_config(hiera_config) : nil
28
+ @apply_settings = apply_settings || {}
29
+
30
+ @pool = Concurrent::ThreadPoolExecutor.new(name: 'apply', max_threads: max_compiles)
31
+ @logger = Bolt::Logger.logger(self)
32
+ end
33
+
34
+ private def libexec
35
+ @libexec ||= File.join(Gem::Specification.find_by_name('openbolt').gem_dir, 'libexec')
36
+ end
37
+
38
+ def custom_facts_task
39
+ @custom_facts_task ||= begin
40
+ path = File.join(libexec, 'custom_facts.rb')
41
+ file = { 'name' => 'custom_facts.rb', 'path' => path }
42
+ metadata = { 'supports_noop' => true, 'input_method' => 'stdin',
43
+ 'implementations' => [
44
+ { 'name' => 'custom_facts.rb' },
45
+ { 'name' => 'custom_facts.rb', 'remote' => true }
46
+ ] }
47
+ Bolt::Task.new('apply_helpers::custom_facts', metadata, [file])
48
+ end
49
+ end
50
+
51
+ def catalog_apply_task
52
+ @catalog_apply_task ||= begin
53
+ path = File.join(libexec, 'apply_catalog.rb')
54
+ file = { 'name' => 'apply_catalog.rb', 'path' => path }
55
+ metadata = { 'supports_noop' => true, 'input_method' => 'stdin',
56
+ 'implementations' => [
57
+ { 'name' => 'apply_catalog.rb' },
58
+ { 'name' => 'apply_catalog.rb', 'remote' => true }
59
+ ] }
60
+ Bolt::Task.new('apply_helpers::apply_catalog', metadata, [file])
61
+ end
62
+ end
63
+
64
+ def query_resources_task
65
+ @query_resources_task ||= begin
66
+ path = File.join(libexec, 'query_resources.rb')
67
+ file = { 'name' => 'query_resources.rb', 'path' => path }
68
+ metadata = { 'supports_noop' => true, 'input_method' => 'stdin',
69
+ 'implementations' => [
70
+ { 'name' => 'query_resources.rb' },
71
+ { 'name' => 'query_resources.rb', 'remote' => true }
72
+ ] }
73
+
74
+ Bolt::Task.new('apply_helpers::query_resources', metadata, [file])
75
+ end
76
+ end
77
+
78
+ def compile(target, scope)
79
+ # This simplified Puppet node object is what .local uses to determine the
80
+ # certname of the target
81
+ node = Puppet::Node.from_data_hash('name' => target.name,
82
+ 'parameters' => { 'clientcert' => target.name })
83
+ trusted = Puppet::Context::TrustedInformation.local(node)
84
+ target_data = {
85
+ name: target.name,
86
+ facts: @inventory.facts(target).merge('bolt' => true),
87
+ variables: @inventory.vars(target),
88
+ trusted: trusted.to_h
89
+ }
90
+ catalog_request = scope.merge(target: target_data).merge(future: @executor.future || {})
91
+
92
+ bolt_catalog_exe = File.join(libexec, 'bolt_catalog')
93
+ old_path = ENV['PATH']
94
+ ENV['PATH'] = "#{RbConfig::CONFIG['bindir']}#{File::PATH_SEPARATOR}#{old_path}"
95
+ out, err, stat = Open3.capture3('ruby', bolt_catalog_exe, 'compile', stdin_data: catalog_request.to_json)
96
+ ENV['PATH'] = old_path
97
+
98
+ # If bolt_catalog does not return valid JSON, we should print stderr to
99
+ # see what happened
100
+ print_logs = stat.success?
101
+ result = begin
102
+ JSON.parse(out)
103
+ rescue JSON::ParserError
104
+ print_logs = true
105
+ { 'message' => "Something's gone terribly wrong! STDERR is logged." }
106
+ end
107
+
108
+ # Any messages logged by Puppet will be on stderr as JSON hashes, so we
109
+ # parse those and store them here. Any message on stderr that is not
110
+ # properly JSON formatted is assumed to be an error message. If
111
+ # compilation was successful, we print the logs as they may include
112
+ # important warnings. If compilation failed, we don't print the logs as
113
+ # they are likely redundant with the error that caused the failure, which
114
+ # will be handled separately.
115
+ logs = err.lines.map do |line|
116
+ JSON.parse(line)
117
+ rescue JSON::ParserError
118
+ { 'level' => 'err', 'message' => line }
119
+ end
120
+
121
+ if print_logs
122
+ logs.each do |log|
123
+ bolt_level = Bolt::Util::PuppetLogLevel::MAPPING[log['level'].to_sym]
124
+ message = log['message'].chomp
125
+
126
+ case bolt_level
127
+ when :warn
128
+ handle_warning(target, message)
129
+ else
130
+ @logger.send(bolt_level, "#{target.name}: #{message}")
131
+ end
132
+ end
133
+ end
134
+
135
+ unless stat.success?
136
+ message = if @apply_settings['trace'] && result['backtrace']
137
+ ([result['message']] + result['backtrace']).join("\n ")
138
+ else
139
+ result['message']
140
+ end
141
+ raise ApplyError.new(target.name, message)
142
+ end
143
+
144
+ result
145
+ end
146
+
147
+ # Handles logging Puppet warnings, some of which are suppressable.
148
+ #
149
+ # @param target [Bolt::Target] The target the apply ran on.
150
+ # @param message [String] The log message.
151
+ #
152
+ private def handle_warning(target, message)
153
+ # Messages about exported resource declaration and collection, which are
154
+ # not supported in manifest blocks.
155
+ if message.include?(Puppet::Pops::Issues::RT_NO_STORECONFIGS_EXPORT.format) ||
156
+ message.include?(Puppet::Pops::Issues::RT_NO_STORECONFIGS.format)
157
+ Bolt::Logger.warn('exported_resources', "#{target.name}: #{message}")
158
+ else
159
+ @logger.send(:warn, "#{target.name}: #{message}")
160
+ end
161
+ end
162
+
163
+ def validate_hiera_config(hiera_config)
164
+ if File.exist?(File.path(hiera_config))
165
+ data = File.open(File.path(hiera_config), "r:UTF-8") do |f|
166
+ if Psych.method(:safe_load).parameters.rassoc(:permitted_classes)
167
+ YAML.safe_load(f.read, permitted_classes: [Symbol])
168
+ else
169
+ YAML.safe_load(f.read, [Symbol])
170
+ end
171
+ end
172
+ if data.nil?
173
+ return nil
174
+ elsif data['version'] != 5
175
+ raise Bolt::ParseError, "Hiera v5 is required, found v#{data['version'] || 3} in #{hiera_config}"
176
+ end
177
+ hiera_config
178
+ end
179
+ end
180
+
181
+ def apply(args, apply_body, scope)
182
+ raise(ArgumentError, 'apply requires a TargetSpec') if args.empty?
183
+ raise(ArgumentError, 'apply requires at least one statement in the apply block') if apply_body.nil?
184
+ type0 = Puppet.lookup(:pal_script_compiler).type('TargetSpec')
185
+ Puppet::Pal.assert_type(type0, args[0], 'apply targets')
186
+
187
+ @executor.report_function_call('apply')
188
+
189
+ options = {}
190
+ if args.count > 1
191
+ type1 = Puppet.lookup(:pal_script_compiler).type('Hash[String, Data]')
192
+ Puppet::Pal.assert_type(type1, args[1], 'apply options')
193
+ options = args[1].transform_keys { |k| k.sub(/^_/, '').to_sym }
194
+ end
195
+
196
+ plan_vars = scope.to_hash(true, true)
197
+
198
+ targets = @inventory.get_targets(args[0])
199
+
200
+ apply_ast(apply_body, targets, options, plan_vars)
201
+ end
202
+
203
+ # Count the number of top-level statements in the AST.
204
+ def count_statements(ast)
205
+ case ast
206
+ when Puppet::Pops::Model::Program
207
+ count_statements(ast.body)
208
+ when Puppet::Pops::Model::BlockExpression
209
+ ast.statements.count
210
+ else
211
+ 1
212
+ end
213
+ end
214
+
215
+ def apply_ast(raw_ast, targets, options, plan_vars = {})
216
+ ast = Puppet::Pops::Serialization::ToDataConverter.convert(raw_ast, rich_data: true, symbol_to_string: true)
217
+ # Serialize as pcore for *Result* objects
218
+ plan_vars = Puppet::Pops::Serialization::ToDataConverter.convert(plan_vars,
219
+ rich_data: true,
220
+ symbol_as_string: true,
221
+ type_by_reference: true,
222
+ local_reference: true)
223
+
224
+ scope = {
225
+ code_ast: ast,
226
+ modulepath: @modulepath,
227
+ project: @project.to_h,
228
+ pdb_config: @pdb_client.instance(options[:puppetdb]).config.to_hash,
229
+ hiera_config: @hiera_config,
230
+ plan_vars: plan_vars,
231
+ # This data isn't available on the target config hash
232
+ config: @inventory.transport_data_get
233
+ }.freeze
234
+ description = options[:description] || 'apply catalog'
235
+
236
+ required_modules = options[:required_modules].nil? ? nil : Array(options[:required_modules])
237
+ if required_modules&.any?
238
+ @logger.debug("Syncing only required modules: #{required_modules.join(',')}.")
239
+ end
240
+
241
+ @plugin_tarball = Concurrent::Delay.new do
242
+ build_plugin_tarball do |mod|
243
+ next unless required_modules.nil? || required_modules.include?(mod.name)
244
+ search_dirs = []
245
+ search_dirs << mod.plugins if mod.plugins?
246
+ search_dirs << mod.pluginfacts if mod.pluginfacts?
247
+ search_dirs << mod.files if mod.files?
248
+ search_dirs << mod.scripts if mod.scripts?
249
+ type_files = "#{mod.path}/types"
250
+ search_dirs << type_files if File.exist?(type_files)
251
+ search_dirs
252
+ end
253
+ end
254
+
255
+ r = @executor.log_action(description, targets) do
256
+ futures = targets.map do |target|
257
+ Concurrent::Future.execute(executor: @pool) do
258
+ Thread.current[:name] ||= Thread.current.name
259
+ @executor.with_node_logging("Compiling manifest block", [target], :trace) do
260
+ compile(target, scope)
261
+ end
262
+ end
263
+ end
264
+
265
+ result_promises = targets.zip(futures).flat_map do |target, future|
266
+ @executor.queue_execute([target]) do |transport, batch|
267
+ @executor.with_node_logging("Applying manifest block", batch) do
268
+ catalog = future.value
269
+ if future.rejected?
270
+ batch.map do |batch_target|
271
+ # If an unhandled exception occurred, wrap it in an ApplyError
272
+ error = if future.reason.is_a?(Bolt::ApplyError)
273
+ future.reason
274
+ else
275
+ Bolt::ApplyError.new(batch_target, future.reason.message)
276
+ end
277
+
278
+ result = Bolt::ApplyResult.new(batch_target, error: error.to_h)
279
+ @executor.publish_event(type: :node_result, result: result)
280
+ result
281
+ end
282
+ else
283
+ arguments = {
284
+ 'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
285
+ 'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
286
+ 'apply_settings' => @apply_settings,
287
+ # This should just be boltlib and modules dirs shipped with bolt packages
288
+ # The apply_catalog task uses them to load core types if they exist
289
+ 'bolt_builtin_content' => @modulepath - @plugin_dirs,
290
+ '_task' => catalog_apply_task.name,
291
+ '_noop' => options[:noop]
292
+ }
293
+
294
+ callback = proc do |event|
295
+ if event[:type] == :node_result
296
+ event = event.merge(result: ApplyResult.from_task_result(event[:result], catalog))
297
+ end
298
+ @executor.publish_event(event)
299
+ end
300
+ # Respect the run_as default set on the executor
301
+ options[:run_as] = @executor.run_as if @executor.run_as && !options.key?(:run_as)
302
+
303
+ results = transport.batch_task(batch, catalog_apply_task, arguments, options, &callback)
304
+ Array(results).map { |result| ApplyResult.from_task_result(result, catalog) }
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ @executor.await_results(result_promises)
311
+ end
312
+
313
+ # Allow for report to exclude event metrics (apply_result doesn't require it to be present)
314
+ resource_counts = r.ok_set.map { |result| result.event_metrics&.fetch('total') }.compact
315
+ @executor.report_apply(count_statements(raw_ast), resource_counts)
316
+
317
+ if !r.ok && !options[:catch_errors]
318
+ raise Bolt::ApplyFailure, r
319
+ end
320
+ r
321
+ end
322
+
323
+ def plugins
324
+ @plugin_tarball.value ||
325
+ raise(Bolt::Error.new("Failed to pack module plugins: #{@plugin_tarball.reason}", 'bolt/plugin-error'))
326
+ end
327
+
328
+ def build_plugin_tarball
329
+ # lazy-load expensive gem code
330
+ require 'minitar'
331
+ require 'zlib'
332
+
333
+ start_time = Time.now
334
+ sio = StringIO.new
335
+ output = Minitar::Output.new(Zlib::GzipWriter.new(sio))
336
+
337
+ Puppet.lookup(:current_environment).override_with(modulepath: @plugin_dirs).modules.each do |mod|
338
+ search_dirs = yield mod
339
+
340
+ tar_dir = Pathname.new(mod.name) # goes great with fish
341
+ mod_dir = Pathname.new(mod.path)
342
+ files = Find.find(*search_dirs).select { |file| File.file?(file) }
343
+
344
+ files.each do |file|
345
+ tar_path = tar_dir + Pathname.new(file).relative_path_from(mod_dir)
346
+ @logger.trace("Packing plugin #{file} to #{tar_path}")
347
+ stat = File.stat(file)
348
+ content = File.binread(file)
349
+ output.tar.add_file_simple(
350
+ tar_path.to_s,
351
+ data: content,
352
+ size: content.size,
353
+ mode: stat.mode & 0o777,
354
+ mtime: stat.mtime
355
+ )
356
+ end
357
+ end
358
+
359
+ duration = Time.now - start_time
360
+ @logger.trace("Packed plugins in #{duration * 1000} ms")
361
+
362
+ output.close
363
+ Base64.encode64(sio.string)
364
+ ensure
365
+ output&.close
366
+ end
367
+ end
368
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../bolt/project'
4
+ require_relative '../bolt/config'
5
+ require_relative '../bolt/error'
6
+
7
+ module Bolt
8
+ class ApplyInventory
9
+ class InvalidFunctionCall < Bolt::Error
10
+ def initialize(function)
11
+ super("The function '#{function}' is not callable within an apply block",
12
+ 'bolt.inventory/invalid-function-call')
13
+ end
14
+ end
15
+
16
+ attr_reader :config_hash
17
+
18
+ def initialize(config_hash = {})
19
+ @config_hash = config_hash
20
+ @targets = {}
21
+ end
22
+
23
+ def create_apply_target(target)
24
+ @targets[target.name] = target
25
+ end
26
+
27
+ def validate
28
+ @groups.validate
29
+ end
30
+
31
+ def version
32
+ 2
33
+ end
34
+
35
+ def target_implementation_class
36
+ Bolt::ApplyTarget
37
+ end
38
+
39
+ def get_targets(*_params)
40
+ raise InvalidFunctionCall, 'get_targets'
41
+ end
42
+
43
+ def get_target(*_params)
44
+ raise InvalidFunctionCall, 'get_target'
45
+ end
46
+
47
+ # rubocop:disable Naming/AccessorMethodName
48
+ def set_var(*_params)
49
+ raise InvalidFunctionCall, 'set_var'
50
+ end
51
+
52
+ def set_feature(*_params)
53
+ raise InvalidFunctionCall, 'set_feature'
54
+ end
55
+ # rubocop:enable Naming/AccessorMethodName
56
+
57
+ def vars(target)
58
+ @targets[target.name].vars
59
+ end
60
+
61
+ def add_facts(*_params)
62
+ raise InvalidFunctionCall, 'add_facts'
63
+ end
64
+
65
+ def facts(target)
66
+ @targets[target.name].facts
67
+ end
68
+
69
+ def features(target)
70
+ @targets[target.name].features
71
+ end
72
+
73
+ def resource(target, type, title)
74
+ @targets[target.name].resource(type, title)
75
+ end
76
+
77
+ def add_to_group(*_params)
78
+ raise InvalidFunctionCall, 'add_to_group'
79
+ end
80
+
81
+ def plugin_hooks(target)
82
+ @targets[target.name].plugin_hooks
83
+ end
84
+
85
+ def set_config(_target, _key_or_key_path, _value)
86
+ raise InvalidFunctionCall, 'set_config'
87
+ end
88
+
89
+ def target_config(target)
90
+ @targets[target.name].config
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative '../bolt/error'
5
+ require_relative '../bolt/result'
6
+
7
+ module Bolt
8
+ class ApplyResult < Result
9
+ def self.puppet_missing_error(result)
10
+ error_hash = result.error_hash
11
+ exit_code = error_hash['details']['exit_code'] if error_hash && error_hash['details']
12
+ # If we get exit code 126 or 127 back, it means the shebang command wasn't found; Puppet isn't present
13
+ if [126, 127].include?(exit_code)
14
+ {
15
+ 'msg' => "Puppet is not installed on the target, please install it to enable 'apply'",
16
+ 'kind' => 'bolt/apply-error'
17
+ }
18
+ elsif exit_code == 1 &&
19
+ (error_hash['msg'] =~ /Could not find executable 'ruby.exe'/ ||
20
+ error_hash['msg'] =~ /The term 'ruby.exe' is not recognized as the name of a cmdlet/)
21
+ # Windows does not have Ruby present
22
+ {
23
+ 'msg' => "Puppet was not found on the target or in $env:ProgramFiles, please install it to enable 'apply'",
24
+ 'kind' => 'bolt/apply-error'
25
+ }
26
+ elsif exit_code == 1 && error_hash['msg'] =~ /cannot load such file -- puppet \(LoadError\)/
27
+ # Windows uses a Ruby that doesn't have Puppet installed
28
+ # TODO: fix so we don't find other Rubies, or point to a known issues URL for more info
29
+ { 'msg' => 'Found a Ruby without Puppet present, please install Puppet ' \
30
+ "or remove Ruby from $env:Path to enable 'apply'",
31
+ 'kind' => 'bolt/apply-error' }
32
+ end
33
+ end
34
+
35
+ def self.resource_error(result)
36
+ if result.value['status'] == 'failed'
37
+ resources = result.value['resource_statuses']
38
+ failed = resources.select { |_, r| r['failed'] }.flat_map do |key, resource|
39
+ resource['events'].select { |e| e['status'] == 'failure' }.map do |event|
40
+ "\n #{key}: #{event['message']}"
41
+ end
42
+ end
43
+
44
+ { 'msg' => "Resources failed to apply for #{result.target.name}#{failed.join}",
45
+ 'kind' => 'bolt/resource-failure' }
46
+ end
47
+ end
48
+
49
+ def self.invalid_report_error(result)
50
+ # These are the keys ApplyResult methods rely on.
51
+ expected_report_keys = %w[metrics resource_statuses status]
52
+ missing_keys = expected_report_keys.reject { |k| result.value.include?(k) }
53
+
54
+ unless missing_keys.empty?
55
+ if result['_output']
56
+ # rubocop:disable Layout/LineLength
57
+ msg = "Report result contains an '_output' key. Catalog application might have printed extraneous output to stdout: #{result['_output']}"
58
+ # rubocop:enable Layout/LineLength
59
+ else
60
+ msg = "Report did not contain all expected keys missing: #{missing_keys.join(', ')}"
61
+ end
62
+
63
+ { 'msg' => msg,
64
+ 'kind' => 'bolt/invalid-report' }
65
+ end
66
+ end
67
+
68
+ def self.from_task_result(result, catalog = nil)
69
+ if (puppet_missing = puppet_missing_error(result))
70
+ new(result.target,
71
+ error: puppet_missing,
72
+ report: result.value.reject { |k| k == '_error' },
73
+ catalog: catalog)
74
+ elsif !result.ok?
75
+ new(result.target,
76
+ error: result.error_hash,
77
+ catalog: catalog)
78
+ elsif (invalid_report = invalid_report_error(result))
79
+ new(result.target,
80
+ error: invalid_report,
81
+ report: result.value.reject { |k| %w[_error _output].include?(k) },
82
+ catalog: catalog)
83
+ elsif (resource_error = resource_error(result))
84
+ new(result.target,
85
+ error: resource_error,
86
+ report: result.value.reject { |k| k == '_error' },
87
+ catalog: catalog)
88
+ else
89
+ new(result.target,
90
+ report: result.value,
91
+ catalog: catalog)
92
+ end
93
+ end
94
+
95
+ # Other pcore methods are inherited from Result
96
+ def _pcore_init_hash
97
+ { 'target' => @target,
98
+ 'error' => value['_error'],
99
+ 'report' => value['report'],
100
+ 'catalog' => catalog }
101
+ end
102
+
103
+ def initialize(target, error: nil, report: nil, catalog: nil)
104
+ @target = target
105
+ @value = {}
106
+ @action = 'apply'
107
+ @value['report'] = report if report
108
+ @value['_error'] = error if error
109
+ @value['_output'] = metrics_message if metrics_message
110
+
111
+ if catalog
112
+ @value['_sensitive'] = Puppet::Pops::Types::PSensitiveType::Sensitive.new({ 'catalog' => catalog })
113
+ end
114
+ end
115
+
116
+ def event_metrics
117
+ if (events = value.dig('report', 'metrics', 'resources', 'values'))
118
+ events.each_with_object({}) { |ev, h| h[ev[0]] = ev[2] }
119
+ end
120
+ end
121
+
122
+ def logs
123
+ value.dig('report', 'logs') || []
124
+ end
125
+
126
+ # Return only log messages associated with resources
127
+ def resource_logs
128
+ logs.reject { |log| log['source'] == 'Puppet' }
129
+ end
130
+
131
+ def metrics_message
132
+ if (metrics = event_metrics)
133
+ changed = metrics['changed']
134
+ failed = metrics['failed']
135
+ skipped = metrics['skipped']
136
+ unchanged = metrics['total'] - changed - failed - skipped
137
+ noop = metrics['out_of_sync'] - changed - failed
138
+ "changed: #{changed}, failed: #{failed}, unchanged: #{unchanged} skipped: #{skipped}, noop: #{noop}"
139
+ end
140
+ end
141
+
142
+ def report
143
+ @value['report']
144
+ end
145
+
146
+ def catalog
147
+ sensitive.unwrap['catalog'] if sensitive
148
+ end
149
+
150
+ def generic_value
151
+ {}
152
+ end
153
+ end
154
+ end