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.
- checksums.yaml +7 -0
- data/Puppetfile +52 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +60 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +71 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +55 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +65 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +93 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +33 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +38 -0
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +208 -0
- data/bolt-modules/boltlib/lib/puppet/functions/background.rb +62 -0
- data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +57 -0
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +130 -0
- data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +31 -0
- data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +52 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +87 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +34 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +35 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +74 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +97 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +47 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +52 -0
- data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +40 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +42 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +106 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +291 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +145 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +164 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +211 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +48 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +43 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +145 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +38 -0
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +101 -0
- data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +29 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +131 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +59 -0
- data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +39 -0
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +50 -0
- data/bolt-modules/boltlib/types/planresult.pp +18 -0
- data/bolt-modules/boltlib/types/targetspec.pp +7 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +42 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +20 -0
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
- data/bolt-modules/file/lib/puppet/functions/file/delete.rb +21 -0
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +28 -0
- data/bolt-modules/file/lib/puppet/functions/file/join.rb +20 -0
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +33 -0
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +28 -0
- data/bolt-modules/file/lib/puppet/functions/file/write.rb +24 -0
- data/bolt-modules/log/lib/puppet/functions/log/debug.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/error.rb +40 -0
- data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +40 -0
- data/bolt-modules/log/lib/puppet/functions/log/info.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/trace.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/warn.rb +41 -0
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +36 -0
- data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +35 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +65 -0
- data/bolt-modules/system/lib/puppet/functions/system/env.rb +20 -0
- data/exe/bolt +17 -0
- data/guides/debugging.yaml +27 -0
- data/guides/inventory.yaml +23 -0
- data/guides/links.yaml +12 -0
- data/guides/logging.yaml +17 -0
- data/guides/module.yaml +18 -0
- data/guides/modulepath.yaml +24 -0
- data/guides/project.yaml +21 -0
- data/guides/targets.yaml +28 -0
- data/guides/transports.yaml +22 -0
- data/lib/bolt/analytics.rb +233 -0
- data/lib/bolt/application.rb +806 -0
- data/lib/bolt/applicator.rb +368 -0
- data/lib/bolt/apply_inventory.rb +93 -0
- data/lib/bolt/apply_result.rb +154 -0
- data/lib/bolt/apply_target.rb +90 -0
- data/lib/bolt/bolt_option_parser.rb +1226 -0
- data/lib/bolt/catalog/logging.rb +15 -0
- data/lib/bolt/catalog.rb +144 -0
- data/lib/bolt/cli.rb +949 -0
- data/lib/bolt/config/modulepath.rb +30 -0
- data/lib/bolt/config/options.rb +673 -0
- data/lib/bolt/config/transport/base.rb +133 -0
- data/lib/bolt/config/transport/docker.rb +34 -0
- data/lib/bolt/config/transport/jail.rb +33 -0
- data/lib/bolt/config/transport/local.rb +39 -0
- data/lib/bolt/config/transport/lxd.rb +34 -0
- data/lib/bolt/config/transport/options.rb +431 -0
- data/lib/bolt/config/transport/orch.rb +41 -0
- data/lib/bolt/config/transport/podman.rb +33 -0
- data/lib/bolt/config/transport/remote.rb +24 -0
- data/lib/bolt/config/transport/ssh.rb +138 -0
- data/lib/bolt/config/transport/winrm.rb +63 -0
- data/lib/bolt/config.rb +515 -0
- data/lib/bolt/container_result.rb +105 -0
- data/lib/bolt/error.rb +194 -0
- data/lib/bolt/executor.rb +539 -0
- data/lib/bolt/fiber_executor.rb +190 -0
- data/lib/bolt/inventory/group.rb +446 -0
- data/lib/bolt/inventory/inventory.rb +391 -0
- data/lib/bolt/inventory/options.rb +139 -0
- data/lib/bolt/inventory/target.rb +293 -0
- data/lib/bolt/inventory.rb +120 -0
- data/lib/bolt/logger.rb +252 -0
- data/lib/bolt/module.rb +54 -0
- data/lib/bolt/module_installer/installer.rb +44 -0
- data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
- data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
- data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
- data/lib/bolt/module_installer/puppetfile.rb +131 -0
- data/lib/bolt/module_installer/resolver.rb +129 -0
- data/lib/bolt/module_installer/specs/forge_spec.rb +91 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +150 -0
- data/lib/bolt/module_installer/specs/id/base.rb +116 -0
- data/lib/bolt/module_installer/specs/id/gitclone.rb +120 -0
- data/lib/bolt/module_installer/specs/id/github.rb +90 -0
- data/lib/bolt/module_installer/specs/id/gitlab.rb +92 -0
- data/lib/bolt/module_installer/specs.rb +95 -0
- data/lib/bolt/module_installer.rb +208 -0
- data/lib/bolt/node/errors.rb +55 -0
- data/lib/bolt/node/output.rb +29 -0
- data/lib/bolt/outputter/human.rb +958 -0
- data/lib/bolt/outputter/json.rb +205 -0
- data/lib/bolt/outputter/logger.rb +76 -0
- data/lib/bolt/outputter/rainbow.rb +118 -0
- data/lib/bolt/outputter.rb +57 -0
- data/lib/bolt/pal/issues.rb +19 -0
- data/lib/bolt/pal/logging.rb +17 -0
- data/lib/bolt/pal/yaml_plan/evaluator.rb +83 -0
- data/lib/bolt/pal/yaml_plan/loader.rb +94 -0
- data/lib/bolt/pal/yaml_plan/parameter.rb +63 -0
- data/lib/bolt/pal/yaml_plan/step/command.rb +45 -0
- data/lib/bolt/pal/yaml_plan/step/download.rb +37 -0
- data/lib/bolt/pal/yaml_plan/step/eval.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/message.rb +31 -0
- data/lib/bolt/pal/yaml_plan/step/plan.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/resources.rb +170 -0
- data/lib/bolt/pal/yaml_plan/step/script.rb +62 -0
- data/lib/bolt/pal/yaml_plan/step/task.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/upload.rb +37 -0
- data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
- data/lib/bolt/pal/yaml_plan/step.rb +223 -0
- data/lib/bolt/pal/yaml_plan/transpiler.rb +90 -0
- data/lib/bolt/pal/yaml_plan.rb +172 -0
- data/lib/bolt/pal.rb +847 -0
- data/lib/bolt/plan_creator.rb +219 -0
- data/lib/bolt/plan_future.rb +86 -0
- data/lib/bolt/plan_result.rb +44 -0
- data/lib/bolt/plugin/cache.rb +76 -0
- data/lib/bolt/plugin/env_var.rb +54 -0
- data/lib/bolt/plugin/module.rb +276 -0
- data/lib/bolt/plugin/prompt.rb +36 -0
- data/lib/bolt/plugin/puppet_connect_data.rb +84 -0
- data/lib/bolt/plugin/puppetdb.rb +124 -0
- data/lib/bolt/plugin/task.rb +72 -0
- data/lib/bolt/plugin.rb +380 -0
- data/lib/bolt/project.rb +219 -0
- data/lib/bolt/project_manager/config_migrator.rb +113 -0
- data/lib/bolt/project_manager/inventory_migrator.rb +67 -0
- data/lib/bolt/project_manager/migrator.rb +39 -0
- data/lib/bolt/project_manager/module_migrator.rb +203 -0
- data/lib/bolt/project_manager.rb +221 -0
- data/lib/bolt/puppetdb/client.rb +153 -0
- data/lib/bolt/puppetdb/config.rb +176 -0
- data/lib/bolt/puppetdb/instance.rb +146 -0
- data/lib/bolt/puppetdb.rb +15 -0
- data/lib/bolt/r10k_log_proxy.rb +30 -0
- data/lib/bolt/rerun.rb +55 -0
- data/lib/bolt/resource_instance.rb +133 -0
- data/lib/bolt/result.rb +247 -0
- data/lib/bolt/result_set.rb +128 -0
- data/lib/bolt/shell/bash/tmpdir.rb +62 -0
- data/lib/bolt/shell/bash.rb +516 -0
- data/lib/bolt/shell/powershell/snippets.rb +181 -0
- data/lib/bolt/shell/powershell.rb +365 -0
- data/lib/bolt/shell.rb +105 -0
- data/lib/bolt/target.rb +174 -0
- data/lib/bolt/task/puppet_server.rb +27 -0
- data/lib/bolt/task/run.rb +55 -0
- data/lib/bolt/task.rb +163 -0
- data/lib/bolt/transport/base.rb +252 -0
- data/lib/bolt/transport/docker/connection.rb +150 -0
- data/lib/bolt/transport/docker.rb +23 -0
- data/lib/bolt/transport/jail/connection.rb +81 -0
- data/lib/bolt/transport/jail.rb +21 -0
- data/lib/bolt/transport/local/connection.rb +106 -0
- data/lib/bolt/transport/local.rb +20 -0
- data/lib/bolt/transport/lxd/connection.rb +115 -0
- data/lib/bolt/transport/lxd.rb +26 -0
- data/lib/bolt/transport/orch/connection.rb +111 -0
- data/lib/bolt/transport/orch.rb +271 -0
- data/lib/bolt/transport/podman/connection.rb +102 -0
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/remote.rb +41 -0
- data/lib/bolt/transport/simple.rb +54 -0
- data/lib/bolt/transport/ssh/connection.rb +321 -0
- data/lib/bolt/transport/ssh/exec_connection.rb +140 -0
- data/lib/bolt/transport/ssh.rb +48 -0
- data/lib/bolt/transport/winrm/connection.rb +378 -0
- data/lib/bolt/transport/winrm.rb +33 -0
- data/lib/bolt/util/format.rb +68 -0
- data/lib/bolt/util/puppet_log_level.rb +21 -0
- data/lib/bolt/util.rb +465 -0
- data/lib/bolt/validator.rb +227 -0
- data/lib/bolt/version.rb +5 -0
- data/lib/bolt.rb +8 -0
- data/lib/bolt_server/acl.rb +39 -0
- data/lib/bolt_server/base_config.rb +112 -0
- data/lib/bolt_server/config.rb +64 -0
- data/lib/bolt_server/file_cache.rb +200 -0
- data/lib/bolt_server/request_error.rb +11 -0
- data/lib/bolt_server/schemas/action-check_node_connections.json +14 -0
- data/lib/bolt_server/schemas/action-run_command.json +12 -0
- data/lib/bolt_server/schemas/action-run_script.json +47 -0
- data/lib/bolt_server/schemas/action-run_task.json +20 -0
- data/lib/bolt_server/schemas/action-upload_file.json +47 -0
- data/lib/bolt_server/schemas/partials/target-any.json +10 -0
- data/lib/bolt_server/schemas/partials/target-ssh.json +88 -0
- data/lib/bolt_server/schemas/partials/target-winrm.json +67 -0
- data/lib/bolt_server/schemas/partials/task.json +94 -0
- data/lib/bolt_server/schemas/transport-ssh.json +25 -0
- data/lib/bolt_server/schemas/transport-winrm.json +19 -0
- data/lib/bolt_server/transport_app.rb +554 -0
- data/lib/bolt_spec/bolt_context.rb +226 -0
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +51 -0
- data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
- data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +55 -0
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +59 -0
- data/lib/bolt_spec/plans/action_stubs/task_stub.rb +57 -0
- data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +65 -0
- data/lib/bolt_spec/plans/action_stubs.rb +196 -0
- data/lib/bolt_spec/plans/mock_executor.rb +361 -0
- data/lib/bolt_spec/plans/publish_stub.rb +49 -0
- data/lib/bolt_spec/plans.rb +190 -0
- data/lib/bolt_spec/run.rb +246 -0
- data/lib/logging_extensions/logging.rb +13 -0
- data/libexec/apply_catalog.rb +130 -0
- data/libexec/bolt_catalog +68 -0
- data/libexec/custom_facts.rb +63 -0
- data/libexec/query_resources.rb +75 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/count.rb +21 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/nodes.rb +22 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/targets.rb +21 -0
- data/modules/aggregate/plans/count.pp +56 -0
- data/modules/aggregate/plans/targets.pp +56 -0
- data/modules/canary/lib/puppet/functions/canary/merge.rb +13 -0
- data/modules/canary/lib/puppet/functions/canary/random_split.rb +22 -0
- data/modules/canary/lib/puppet/functions/canary/skip.rb +25 -0
- data/modules/canary/plans/init.pp +100 -0
- data/modules/puppet_connect/plans/test_input_data.pp +94 -0
- data/modules/puppetdb_fact/plans/init.pp +20 -0
- data/resources/bolt_bash_completion.sh +214 -0
- 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
|