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,554 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra'
4
+ require 'addressable/uri'
5
+ require 'bolt'
6
+ require 'bolt/error'
7
+ require 'bolt/inventory'
8
+ require 'bolt/target'
9
+ require 'bolt_server/file_cache'
10
+ require 'bolt_server/request_error'
11
+ require 'bolt/task/puppet_server'
12
+ require 'json'
13
+ require 'json-schema'
14
+
15
+ # These are only needed for the `/plans` endpoint.
16
+ require 'puppet'
17
+
18
+ module BoltServer
19
+ class TransportApp < Sinatra::Base
20
+ # This disables Sinatra's error page generation
21
+ set :show_exceptions, false
22
+
23
+ # These partial schemas are reused to build multiple request schemas
24
+ PARTIAL_SCHEMAS = %w[target-any target-ssh target-winrm task].freeze
25
+
26
+ # These schemas combine shared schemas to describe client requests
27
+ REQUEST_SCHEMAS = %w[
28
+ action-check_node_connections
29
+ action-run_command
30
+ action-run_task
31
+ action-run_script
32
+ action-upload_file
33
+ transport-ssh
34
+ transport-winrm
35
+ ].freeze
36
+
37
+ # PE_BOLTLIB_PATH is intended to function exactly like the BOLTLIB_PATH used
38
+ # in Bolt::PAL. Paths and variable names are similar to what exists in
39
+ # Bolt::PAL, but with a 'PE' prefix.
40
+ PE_BOLTLIB_PATH = '/opt/puppetlabs/server/apps/bolt-server/pe-bolt-modules'
41
+
42
+ # For now at least, we maintain an entirely separate codedir from
43
+ # puppetserver by default, so that filesync can work properly. If filesync
44
+ # is not used, this can instead match the usual puppetserver codedir.
45
+ # See the `orchestrator.bolt.codedir` tk config setting.
46
+ DEFAULT_BOLT_CODEDIR = '/opt/puppetlabs/server/data/orchestration-services/code'
47
+
48
+ def initialize(config)
49
+ @config = config
50
+ @schemas = Hash[REQUEST_SCHEMAS.map do |basename|
51
+ [basename, JSON.parse(File.read(File.join(__dir__, ['schemas', "#{basename}.json"])))]
52
+ end]
53
+
54
+ PARTIAL_SCHEMAS.each do |basename|
55
+ schema_content = JSON.parse(File.read(File.join(__dir__, ['schemas', 'partials', "#{basename}.json"])))
56
+ shared_schema = JSON::Schema.new(schema_content, Addressable::URI.parse("partial:#{basename}"))
57
+ JSON::Validator.add_schema(shared_schema)
58
+ end
59
+ @file_cache = BoltServer::FileCache.new(@config).setup
60
+ @executor = Bolt::Executor.new(0)
61
+
62
+ # This is needed until the PAL is threadsafe.
63
+ @pal_mutex = Mutex.new
64
+
65
+ @logger = Bolt::Logger.logger(self)
66
+
67
+ super(nil)
68
+ end
69
+
70
+ def scrub_stack_trace(result)
71
+ if result.dig('value', '_error', 'details', 'stack_trace')
72
+ result['value']['_error']['details'].reject! { |k| k == 'stack_trace' }
73
+ end
74
+ result
75
+ end
76
+
77
+ def validate_schema(schema, body)
78
+ schema_error = JSON::Validator.fully_validate(schema, body)
79
+ if schema_error.any?
80
+ raise BoltServer::RequestError.new("There was an error validating the request body.",
81
+ schema_error)
82
+ end
83
+ end
84
+
85
+ # Turns a Bolt::ResultSet object into a status hash that is fit
86
+ # to return to the client in a response. In the case of every action
87
+ # *except* check_node_connections the response will be a single serialized Result.
88
+ # In the check_node_connections case the response will be a hash with the top level "status"
89
+ # of the result and the serialized individual target results.
90
+ def result_set_to_data(result_set, aggregate: false)
91
+ # use ResultSet#ok method to determine status of a (potentially) aggregate result before serializing
92
+ result_set_status = result_set.ok ? 'success' : 'failure'
93
+ scrubbed_results = result_set.map do |result|
94
+ scrub_stack_trace(result.to_data)
95
+ end
96
+
97
+ if aggregate
98
+ {
99
+ status: result_set_status,
100
+ result: scrubbed_results
101
+ }
102
+ else
103
+ # If there was only one target, return the first result on its own
104
+ scrubbed_results.first
105
+ end
106
+ end
107
+
108
+ def unwrap_sensitive_results(result_set)
109
+ # Take a ResultSet and unwrap sensitive values
110
+ result_set.each do |result|
111
+ value = result.value
112
+ next unless value.is_a?(Hash)
113
+ next unless value.key?('_sensitive')
114
+ value['_sensitive'] = value['_sensitive'].unwrap
115
+ end
116
+ end
117
+
118
+ def task_helper(target, task, parameters, timeout = nil)
119
+ # Wrap parameters marked with '"sensitive": true' in the task metadata with a
120
+ # Sensitive wrapper type. This way it's not shown in logs.
121
+ if (param_spec = task.parameters)
122
+ parameters.each do |k, v|
123
+ if param_spec[k] && param_spec[k]['sensitive']
124
+ parameters[k] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(v)
125
+ end
126
+ end
127
+ end
128
+
129
+ if timeout && timeout > 0
130
+ task_thread = Thread.new do
131
+ unwrap_sensitive_results(@executor.run_task(target, task, parameters))
132
+ end
133
+ # Wait for the timeout for the task to execute in the thread. If `join` times out, result will be nil.
134
+ if task_thread.join(timeout).nil?
135
+ task_thread.kill
136
+ raise Bolt::Error.new("Task execution on #{target.first.safe_name} timed out after #{timeout} seconds",
137
+ 'boltserver/task-timeout')
138
+ else
139
+ task_thread.value
140
+ end
141
+ else
142
+ unwrap_sensitive_results(@executor.run_task(target, task, parameters))
143
+ end
144
+ end
145
+
146
+ def run_task(target, body)
147
+ validate_schema(@schemas["action-run_task"], body)
148
+
149
+ task_data = body['task']
150
+ task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
151
+ task_helper(target, task, body['parameters'] || {}, body['timeout'])
152
+ end
153
+
154
+ def run_command(target, body)
155
+ validate_schema(@schemas["action-run_command"], body)
156
+ command = body['command']
157
+ @executor.run_command(target, command)
158
+ end
159
+
160
+ def check_node_connections(targets, body)
161
+ validate_schema(@schemas["action-check_node_connections"], body)
162
+
163
+ # Puppet Enterprise's orchestrator service uses the
164
+ # check_node_connections endpoint to check whether nodes that should be
165
+ # contacted over SSH or WinRM are responsive. The wait time here is 0
166
+ # because the endpoint is meant to be used for a single check of all
167
+ # nodes; External implementations of wait_until_available (like
168
+ # orchestrator's) should contact the endpoint in their own loop.
169
+ @executor.wait_until_available(targets, wait_time: 0)
170
+ end
171
+
172
+ def upload_file(target, body)
173
+ validate_schema(@schemas["action-upload_file"], body)
174
+ files = body['files']
175
+ destination = body['destination']
176
+ job_id = body['job_id']
177
+ cache_dir = @file_cache.create_cache_dir(job_id.to_s)
178
+ FileUtils.mkdir_p(cache_dir)
179
+ files.each do |file|
180
+ relative_path = file['relative_path']
181
+ uri = file['uri']
182
+ sha256 = file['sha256']
183
+ kind = file['kind']
184
+ path = File.join(cache_dir, relative_path)
185
+ case kind
186
+ when 'file'
187
+ # The parent should already be created by `directory` entries,
188
+ # but this is to be on the safe side.
189
+ parent = File.dirname(path)
190
+ FileUtils.mkdir_p(parent)
191
+ @file_cache.serial_execute { @file_cache.download_file(path, sha256, uri) }
192
+ when 'directory'
193
+ # Create directory in cache so we can move files in.
194
+ FileUtils.mkdir_p(path)
195
+ else
196
+ raise BoltServer::RequestError, "Invalid kind: '#{kind}' supplied. Must be 'file' or 'directory'."
197
+ end
198
+ end
199
+ # We need to special case the scenario where only one file was
200
+ # included in the request to download. Otherwise, the call to upload_file
201
+ # will attempt to upload with a directory as a source and potentially a
202
+ # filename as a destination on the host. In that case the end result will
203
+ # be the file downloaded to a directory with the same name as the source
204
+ # filename, rather than directly to the filename set in the destination.
205
+ upload_source = if files.size == 1 && files[0]['kind'] == 'file'
206
+ File.join(cache_dir, files[0]['relative_path'])
207
+ else
208
+ cache_dir
209
+ end
210
+ @executor.upload_file(target, upload_source, destination)
211
+ end
212
+
213
+ def run_script(target, body)
214
+ validate_schema(@schemas["action-run_script"], body)
215
+ # Download the file onto the machine.
216
+ file_location = @file_cache.update_file(body['script'])
217
+ @executor.run_script(target, file_location, body['arguments'])
218
+ end
219
+
220
+ # This function is nearly identical to Bolt::Pal's `with_puppet_settings` with the
221
+ # one difference that we set the codedir to point to actual code, rather than the
222
+ # tmpdir. We only use this funtion inside the Modulepath initializer so that Puppet
223
+ # is correctly configured to pull environment configuration correctly. If we don't
224
+ # set codedir in this way: when we try to load and interpolate the modulepath it
225
+ # won't correctly load.
226
+ #
227
+ # WARNING: THIS FUNCTION SHOULD ONLY BE CALLED INSIDE A SYNCHRONIZED PAL MUTEX
228
+ def with_pe_pal_init_settings(codedir, environmentpath, basemodulepath)
229
+ Dir.mktmpdir('pe-bolt') do |dir|
230
+ cli = []
231
+ Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
232
+ dir = setting == :codedir ? codedir : dir
233
+ cli << "--#{setting}" << dir
234
+ end
235
+ cli << "--environmentpath" << environmentpath
236
+ cli << "--basemodulepath" << basemodulepath
237
+ Puppet.settings.send(:clear_everything_for_tests)
238
+ Puppet.initialize_settings(cli)
239
+ Puppet[:versioned_environment_dirs] = true
240
+ yield
241
+ end
242
+ end
243
+
244
+ # Use puppet to identify the modulepath from an environment.
245
+ #
246
+ # WARNING: THIS FUNCTION SHOULD ONLY BE CALLED INSIDE A SYNCHRONIZED PAL MUTEX
247
+ def modulepath_from_environment(environment_name)
248
+ codedir = @config['environments-codedir'] || DEFAULT_BOLT_CODEDIR
249
+ environmentpath = @config['environmentpath'] || "#{codedir}/environments"
250
+ basemodulepath = @config['basemodulepath'] || "#{codedir}/modules:/opt/puppetlabs/puppet/modules"
251
+ with_pe_pal_init_settings(codedir, environmentpath, basemodulepath) do
252
+ environment = Puppet.lookup(:environments).get!(environment_name)
253
+ environment.modulepath
254
+ end
255
+ end
256
+
257
+ def in_pe_pal_env(environment)
258
+ raise BoltServer::RequestError, "'environment' is a required argument" if environment.nil?
259
+ @pal_mutex.synchronize do
260
+ modulepath_obj = Bolt::Config::Modulepath.new(
261
+ modulepath_from_environment(environment),
262
+ boltlib_path: [PE_BOLTLIB_PATH, Bolt::Config::Modulepath::BOLTLIB_PATH]
263
+ )
264
+ pal = Bolt::PAL.new(modulepath_obj, nil, nil)
265
+ yield pal
266
+ rescue Puppet::Environments::EnvironmentNotFound
267
+ raise BoltServer::RequestError, "environment: '#{environment}' does not exist"
268
+ end
269
+ end
270
+
271
+ def pe_plan_info(pal, module_name, plan_name)
272
+ # Handle case where plan name is simply module name with special `init.pp` plan
273
+ plan_name = if plan_name == 'init' || plan_name.nil?
274
+ module_name
275
+ else
276
+ "#{module_name}::#{plan_name}"
277
+ end
278
+ plan_info = pal.get_plan_info(plan_name)
279
+ # Path to module is meaningless in PE
280
+ plan_info.delete('module')
281
+ plan_info
282
+ end
283
+
284
+ def build_puppetserver_uri(file_identifier, module_name, parameters)
285
+ segments = file_identifier.split('/', 3)
286
+ if segments.size == 1
287
+ {
288
+ 'path' => "/puppet/v3/file_content/tasks/#{module_name}/#{file_identifier}",
289
+ 'params' => parameters
290
+ }
291
+ else
292
+ module_segment, mount_segment, name_segment = *segments
293
+ {
294
+ 'path' => case mount_segment
295
+ when 'files'
296
+ "/puppet/v3/file_content/modules/#{module_segment}/#{name_segment}"
297
+ when 'scripts'
298
+ "/puppet/v3/file_content/scripts/#{module_segment}/#{name_segment}"
299
+ when 'tasks'
300
+ "/puppet/v3/file_content/tasks/#{module_segment}/#{name_segment}"
301
+ when 'lib'
302
+ "/puppet/v3/file_content/plugins/#{name_segment}"
303
+ end,
304
+ 'params' => parameters
305
+ }
306
+ end
307
+ end
308
+
309
+ def pe_task_info(pal, module_name, task_name, parameters)
310
+ # Handle case where task name is simply module name with special `init` task
311
+ task_name = if task_name == 'init' || task_name.nil?
312
+ module_name
313
+ else
314
+ "#{module_name}::#{task_name}"
315
+ end
316
+ task = pal.get_task(task_name)
317
+ files = task.files.map do |file_hash|
318
+ {
319
+ 'filename' => file_hash['name'],
320
+ 'sha256' => Digest::SHA256.hexdigest(File.read(file_hash['path'])),
321
+ 'size_bytes' => File.size(file_hash['path']),
322
+ 'uri' => build_puppetserver_uri(file_hash['name'], module_name, parameters)
323
+ }
324
+ end
325
+ {
326
+ 'metadata' => task.metadata,
327
+ 'name' => task.name,
328
+ 'files' => files
329
+ }
330
+ end
331
+
332
+ def allowed_helper(pal, metadata, allowlist)
333
+ allowed = !pal.filter_content([metadata['name']], allowlist).empty?
334
+ metadata.merge({ 'allowed' => allowed })
335
+ end
336
+
337
+ def task_list(pal)
338
+ tasks = pal.list_tasks
339
+ tasks.map { |task_name, _description| { 'name' => task_name } }
340
+ end
341
+
342
+ def plan_list(pal)
343
+ plans = pal.list_plans.flatten
344
+ plans.map { |plan_name| { 'name' => plan_name } }
345
+ end
346
+
347
+ get '/' do
348
+ 200
349
+ end
350
+
351
+ if ENV['RACK_ENV'] == 'dev'
352
+ get '/admin/gc' do
353
+ GC.start
354
+ 200
355
+ end
356
+ end
357
+
358
+ get '/admin/gc_stat' do
359
+ [200, GC.stat.to_json]
360
+ end
361
+
362
+ get '/admin/status' do
363
+ stats = Puma.stats
364
+ [200, stats.is_a?(Hash) ? stats.to_json : stats]
365
+ end
366
+
367
+ get '/500_error' do
368
+ raise 'Unexpected error'
369
+ end
370
+
371
+ ACTIONS = %w[
372
+ check_node_connections
373
+ run_command
374
+ run_task
375
+ run_script
376
+ upload_file
377
+ apply
378
+ apply_prep
379
+ ].freeze
380
+
381
+ def make_ssh_target(target_hash)
382
+ defaults = {
383
+ 'host-key-check' => false
384
+ }
385
+
386
+ overrides = {
387
+ 'load-config' => false
388
+ }
389
+
390
+ opts = defaults.merge(target_hash).merge(overrides)
391
+
392
+ if opts['private-key-content']
393
+ private_key_content = opts.delete('private-key-content')
394
+ opts['private-key'] = { 'key-data' => private_key_content }
395
+ end
396
+
397
+ data = {
398
+ 'uri' => target_hash['hostname'],
399
+ 'config' => {
400
+ 'transport' => 'ssh',
401
+ 'ssh' => opts.slice(*Bolt::Config::Transport::SSH.options)
402
+ }
403
+ }
404
+
405
+ inventory = Bolt::Inventory.empty
406
+ Bolt::Target.from_hash(data, inventory)
407
+ end
408
+
409
+ post '/ssh/:action' do
410
+ not_found unless ACTIONS.include?(params[:action])
411
+
412
+ content_type :json
413
+ body = JSON.parse(request.body.read)
414
+
415
+ validate_schema(@schemas["transport-ssh"], body)
416
+
417
+ targets = (body['targets'] || [body['target']]).map do |target|
418
+ make_ssh_target(target)
419
+ end
420
+
421
+ result_set = method(params[:action]).call(targets, body)
422
+
423
+ aggregate = params[:action] == 'check_node_connections'
424
+ [200, result_set_to_data(result_set, aggregate: aggregate).to_json]
425
+ end
426
+
427
+ def make_winrm_target(target_hash)
428
+ defaults = {
429
+ 'ssl' => false,
430
+ 'ssl-verify' => false
431
+ }
432
+
433
+ opts = defaults.merge(target_hash)
434
+
435
+ data = {
436
+ 'uri' => target_hash['hostname'],
437
+ 'config' => {
438
+ 'transport' => 'winrm',
439
+ 'winrm' => opts.slice(*Bolt::Config::Transport::WinRM.options)
440
+ }
441
+ }
442
+
443
+ inventory = Bolt::Inventory.empty
444
+ Bolt::Target.from_hash(data, inventory)
445
+ end
446
+
447
+ post '/winrm/:action' do
448
+ not_found unless ACTIONS.include?(params[:action])
449
+
450
+ content_type :json
451
+ body = JSON.parse(request.body.read)
452
+
453
+ validate_schema(@schemas["transport-winrm"], body)
454
+
455
+ targets = (body['targets'] || [body['target']]).map do |target|
456
+ make_winrm_target(target)
457
+ end
458
+
459
+ result_set = method(params[:action]).call(targets, body)
460
+
461
+ aggregate = params[:action] == 'check_node_connections'
462
+ [200, result_set_to_data(result_set, aggregate: aggregate).to_json]
463
+ end
464
+
465
+ # Fetches the metadata for a single plan
466
+ #
467
+ # @param environment [String] the environment to fetch the plan from
468
+ get '/plans/:module_name/:plan_name' do
469
+ in_pe_pal_env(params['environment']) do |pal|
470
+ plan_info = pe_plan_info(pal, params[:module_name], params[:plan_name])
471
+ [200, plan_info.to_json]
472
+ end
473
+ end
474
+
475
+ # Fetches the metadata for a single task
476
+ #
477
+ # @param environment [String] the environment to fetch the task from
478
+ get '/tasks/:module_name/:task_name' do
479
+ in_pe_pal_env(params['environment']) do |pal|
480
+ ps_parameters = {
481
+ 'environment' => params['environment']
482
+ }
483
+ task_info = pe_task_info(pal, params[:module_name], params[:task_name], ps_parameters)
484
+ [200, task_info.to_json]
485
+ end
486
+ end
487
+
488
+ # Fetches the list of plans for an environment, optionally fetching all metadata for each plan
489
+ #
490
+ # @param environment [String] the environment to fetch the list of plans from
491
+ # @param metadata [Boolean] Set to true to fetch all metadata for each plan. Defaults to false
492
+ get '/plans' do
493
+ in_pe_pal_env(params['environment']) do |pal|
494
+ plans = pal.list_plans.flatten
495
+ if params['metadata']
496
+ plan_info = plans.each_with_object({}) do |full_name, acc|
497
+ # Break apart module name from plan name
498
+ module_name, plan_name = full_name.split('::', 2)
499
+ acc[full_name] = pe_plan_info(pal, module_name, plan_name)
500
+ end
501
+ [200, plan_info.to_json]
502
+ else
503
+ # We structure this array of plans to be an array of hashes so that it matches the structure
504
+ # returned by the puppetserver API that serves data like this. Structuring the output this way
505
+ # makes switching between puppetserver and bolt-server easier, which makes changes to switch
506
+ # to bolt-server smaller/simpler.
507
+ [200, plans.map { |plan| { 'name' => plan } }.to_json]
508
+ end
509
+ end
510
+ end
511
+
512
+ # Fetches the list of tasks for an environment
513
+ #
514
+ # @param environment [String] the environment to fetch the list of tasks from
515
+ get '/tasks' do
516
+ in_pe_pal_env(params['environment']) do |pal|
517
+ tasks_response = task_list(pal).to_json
518
+
519
+ # We structure this array of tasks to be an array of hashes so that it matches the structure
520
+ # returned by the puppetserver API that serves data like this. Structuring the output this way
521
+ # makes switching between puppetserver and bolt-server easier, which makes changes to switch
522
+ # to bolt-server smaller/simpler.
523
+ [200, tasks_response]
524
+ end
525
+ end
526
+
527
+ error 404 do
528
+ err = Bolt::Error.new("Could not find route #{request.path}",
529
+ 'boltserver/not-found')
530
+ [404, err.to_json]
531
+ end
532
+
533
+ error BoltServer::RequestError do |err|
534
+ [400, err.to_json]
535
+ end
536
+
537
+ error Bolt::Error do |err|
538
+ # In order to match the request code pattern, unknown plan/task content should 400. This also
539
+ # gives us an opportunity to trim the message instructing users to use CLI to show available content.
540
+ if ['bolt/unknown-plan', 'bolt/unknown-task'].include?(err.kind)
541
+ [404, BoltServer::RequestError.new(err.msg.split('.').first).to_json]
542
+ else
543
+ [500, err.to_json]
544
+ end
545
+ end
546
+
547
+ error StandardError do
548
+ e = env['sinatra.error']
549
+ err = Bolt::Error.new("500: Unknown error: #{e.message}",
550
+ 'boltserver/server-error')
551
+ [500, err.to_json]
552
+ end
553
+ end
554
+ end