bolt 3.8.1 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +4 -4
- data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
- data/bolt-modules/boltlib/lib/puppet/functions/background.rb +61 -0
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +5 -9
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +28 -13
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +5 -15
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +5 -17
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +8 -17
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +8 -15
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +5 -17
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +91 -0
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/guides/debugging.txt +28 -0
- data/guides/inventory.txt +5 -0
- data/lib/bolt/applicator.rb +3 -2
- data/lib/bolt/bolt_option_parser.rb +51 -4
- data/lib/bolt/cli.rb +38 -9
- data/lib/bolt/config/transport/docker.rb +1 -1
- data/lib/bolt/config/transport/lxd.rb +1 -1
- data/lib/bolt/config/transport/podman.rb +1 -1
- data/lib/bolt/error.rb +11 -1
- data/lib/bolt/executor.rb +55 -72
- data/lib/bolt/fiber_executor.rb +141 -0
- data/lib/bolt/module_installer/installer.rb +1 -1
- data/lib/bolt/outputter/human.rb +46 -2
- data/lib/bolt/outputter/json.rb +9 -0
- data/lib/bolt/pal.rb +117 -17
- data/lib/bolt/plan_future.rb +66 -0
- data/lib/bolt/plugin.rb +38 -0
- data/lib/bolt/plugin/env_var.rb +8 -1
- data/lib/bolt/plugin/module.rb +1 -1
- data/lib/bolt/plugin/prompt.rb +8 -1
- data/lib/bolt/plugin/puppet_connect_data.rb +8 -1
- data/lib/bolt/plugin/puppetdb.rb +7 -1
- data/lib/bolt/plugin/task.rb +9 -1
- data/lib/bolt/project.rb +2 -1
- data/lib/bolt/task.rb +7 -0
- data/lib/bolt/transport/docker/connection.rb +5 -2
- data/lib/bolt/transport/lxd/connection.rb +4 -0
- data/lib/bolt/transport/podman/connection.rb +4 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/request_error.rb +11 -0
- data/lib/bolt_server/transport_app.rb +133 -95
- data/lib/bolt_spec/plans/mock_executor.rb +40 -45
- data/lib/bolt_spec/run.rb +4 -1
- data/modules/puppet_connect/plans/test_input_data.pp +8 -3
- data/resources/bolt_bash_completion.sh +214 -0
- metadata +10 -3
- data/lib/bolt/yarn.rb +0 -23
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class PlanFuture
|
7
|
+
attr_reader :fiber, :id
|
8
|
+
attr_accessor :value
|
9
|
+
|
10
|
+
def initialize(fiber, id, name = nil)
|
11
|
+
@fiber = fiber
|
12
|
+
@id = id
|
13
|
+
@name = name
|
14
|
+
@value = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@name || @id
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"Future '#{name}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
def alive?
|
26
|
+
fiber.alive?
|
27
|
+
end
|
28
|
+
|
29
|
+
def raise(exception)
|
30
|
+
# Make sure the value gets set
|
31
|
+
@value = exception
|
32
|
+
# This was introduced in Ruby 2.7
|
33
|
+
begin
|
34
|
+
# Raise an exception to kill the Fiber. If the Fiber has not been
|
35
|
+
# resumed yet, or is already terminated this will raise a FiberError.
|
36
|
+
# We don't especially care about the FiberError, as long as the Fiber
|
37
|
+
# doesn't report itself as alive.
|
38
|
+
fiber.raise(exception)
|
39
|
+
rescue FiberError
|
40
|
+
# If the Fiber is still alive, resume it with a block to raise the
|
41
|
+
# exception which will terminate it.
|
42
|
+
if fiber.alive?
|
43
|
+
fiber.resume { raise(exception) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def resume
|
49
|
+
if fiber.alive?
|
50
|
+
@value = fiber.resume
|
51
|
+
else
|
52
|
+
@value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def state
|
57
|
+
if fiber.alive?
|
58
|
+
"running"
|
59
|
+
elsif value.is_a?(Exception)
|
60
|
+
"error"
|
61
|
+
else
|
62
|
+
"done"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/bolt/plugin.rb
CHANGED
@@ -250,6 +250,44 @@ module Bolt
|
|
250
250
|
end
|
251
251
|
end
|
252
252
|
|
253
|
+
# Loads all plugins and returns a map of plugin names to hooks.
|
254
|
+
#
|
255
|
+
def list_plugins
|
256
|
+
load_all_plugins
|
257
|
+
|
258
|
+
hooks = KNOWN_HOOKS.map { |hook| [hook, {}] }.to_h
|
259
|
+
|
260
|
+
@plugins.sort.each do |name, plugin|
|
261
|
+
# Don't show the Puppet Connect plugin for now.
|
262
|
+
next if name == 'puppet_connect_data'
|
263
|
+
|
264
|
+
case plugin
|
265
|
+
when Bolt::Plugin::Module
|
266
|
+
plugin.hook_map.each do |hook, spec|
|
267
|
+
next unless hooks.include?(hook)
|
268
|
+
hooks[hook][name] = spec['task'].description
|
269
|
+
end
|
270
|
+
else
|
271
|
+
plugin.hook_descriptions.each do |hook, description|
|
272
|
+
hooks[hook][name] = description
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
hooks
|
278
|
+
end
|
279
|
+
|
280
|
+
# Loads all plugins available to the project.
|
281
|
+
#
|
282
|
+
private def load_all_plugins
|
283
|
+
modules.each do |name, mod|
|
284
|
+
next unless mod.plugin?
|
285
|
+
by_name(name)
|
286
|
+
end
|
287
|
+
|
288
|
+
RUBY_PLUGINS.each { |name| by_name(name) }
|
289
|
+
end
|
290
|
+
|
253
291
|
def puppetdb_client
|
254
292
|
by_name('puppetdb').puppetdb_client
|
255
293
|
end
|
data/lib/bolt/plugin/env_var.rb
CHANGED
@@ -10,7 +10,14 @@ module Bolt
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def hooks
|
13
|
-
|
13
|
+
hook_descriptions.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def hook_descriptions
|
17
|
+
{
|
18
|
+
resolve_reference: 'Read values stored in environment variables.',
|
19
|
+
validate_resolve_reference: nil
|
20
|
+
}
|
14
21
|
end
|
15
22
|
|
16
23
|
def validate_resolve_reference(opts)
|
data/lib/bolt/plugin/module.rb
CHANGED
data/lib/bolt/plugin/prompt.rb
CHANGED
@@ -10,7 +10,14 @@ module Bolt
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def hooks
|
13
|
-
|
13
|
+
hook_descriptions.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def hook_descriptions
|
17
|
+
{
|
18
|
+
resolve_reference: 'Prompt the user for a sensitive value.',
|
19
|
+
validate_resolve_reference: nil
|
20
|
+
}
|
14
21
|
end
|
15
22
|
|
16
23
|
def validate_resolve_reference(opts)
|
@@ -49,7 +49,14 @@ module Bolt
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def hooks
|
52
|
-
|
52
|
+
hook_descriptions.keys
|
53
|
+
end
|
54
|
+
|
55
|
+
def hook_descriptions
|
56
|
+
{
|
57
|
+
resolve_reference: nil,
|
58
|
+
validate_resolve_reference: nil
|
59
|
+
}
|
53
60
|
end
|
54
61
|
|
55
62
|
def resolve_reference(opts)
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
data/lib/bolt/plugin/task.rb
CHANGED
@@ -4,7 +4,15 @@ module Bolt
|
|
4
4
|
class Plugin
|
5
5
|
class Task
|
6
6
|
def hooks
|
7
|
-
|
7
|
+
hook_descriptions.keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def hook_descriptions
|
11
|
+
{
|
12
|
+
puppet_library: 'Run a task to install the Puppet agent package.',
|
13
|
+
resolve_reference: 'Run a task as a plugin.',
|
14
|
+
validate_resolve_reference: nil
|
15
|
+
}
|
8
16
|
end
|
9
17
|
|
10
18
|
def name
|
data/lib/bolt/project.rb
CHANGED
@@ -14,7 +14,7 @@ module Bolt
|
|
14
14
|
attr_reader :path, :data, :inventory_file, :hiera_config,
|
15
15
|
:puppetfile, :rerunfile, :type, :resource_types, :project_file,
|
16
16
|
:downloads, :plans_path, :modulepath, :managed_moduledir,
|
17
|
-
:backup_dir, :plugin_cache_file, :plan_cache_file
|
17
|
+
:backup_dir, :plugin_cache_file, :plan_cache_file, :task_cache_file
|
18
18
|
|
19
19
|
def self.default_project
|
20
20
|
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
|
@@ -114,6 +114,7 @@ module Bolt
|
|
114
114
|
@backup_dir = @path + '.bolt-bak'
|
115
115
|
@plugin_cache_file = @path + '.plugin_cache.json'
|
116
116
|
@plan_cache_file = @path + '.plan_cache.json'
|
117
|
+
@task_cache_file = @path + '.task_cache.json'
|
117
118
|
@modulepath = [(@path + 'modules').to_s]
|
118
119
|
|
119
120
|
if (tc = Bolt::Config::INVENTORY_OPTIONS.keys & data.keys).any?
|
data/lib/bolt/task.rb
CHANGED
@@ -17,6 +17,7 @@ module Bolt
|
|
17
17
|
remote supports_noop].freeze
|
18
18
|
|
19
19
|
attr_reader :name, :files, :metadata, :remote
|
20
|
+
attr_accessor :mtime
|
20
21
|
|
21
22
|
# name [String] name of the task
|
22
23
|
# files [Array<Hash>] where each entry includes `name` and `path`
|
@@ -77,6 +78,12 @@ module Bolt
|
|
77
78
|
file_map[file_name]['path']
|
78
79
|
end
|
79
80
|
|
81
|
+
def add_mtimes
|
82
|
+
@files.each do |f|
|
83
|
+
f['mtime'] = File.mtime(f['path']) if File.exist?(f['path'])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
80
87
|
def implementations
|
81
88
|
metadata['implementations']
|
82
89
|
end
|
@@ -27,6 +27,10 @@ module Bolt
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
def reset_cwd?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
30
34
|
# The full ID of the target container
|
31
35
|
#
|
32
36
|
# @return [String] The full ID of the target container
|
@@ -74,7 +78,6 @@ module Bolt
|
|
74
78
|
# CODEREVIEW: Is it always safe to pass --interactive?
|
75
79
|
args += %w[--interactive]
|
76
80
|
args += %w[--tty] if target.options['tty']
|
77
|
-
args += %W[--env DOCKER_HOST=#{@docker_host}] if @docker_host
|
78
81
|
args += @env_vars if @env_vars
|
79
82
|
|
80
83
|
if target.options['shell-command'] && !target.options['shell-command'].empty?
|
@@ -86,7 +89,7 @@ module Bolt
|
|
86
89
|
docker_command = %w[docker exec] + args + [container_id] + Shellwords.split(command)
|
87
90
|
@logger.trace { "Executing: #{docker_command.join(' ')}" }
|
88
91
|
|
89
|
-
Open3.popen3(*docker_command)
|
92
|
+
Open3.popen3(env_hash, *docker_command)
|
90
93
|
rescue StandardError
|
91
94
|
@logger.trace { "Command aborted" }
|
92
95
|
raise
|
data/lib/bolt/version.rb
CHANGED
data/lib/bolt_server/config.rb
CHANGED
@@ -10,6 +10,7 @@ require 'bolt/target'
|
|
10
10
|
require 'bolt_server/file_cache'
|
11
11
|
require 'bolt_server/plugin'
|
12
12
|
require 'bolt_server/plugin/puppet_connect_data'
|
13
|
+
require 'bolt_server/request_error'
|
13
14
|
require 'bolt/task/puppet_server'
|
14
15
|
require 'json'
|
15
16
|
require 'json-schema'
|
@@ -20,6 +21,10 @@ require 'puppet'
|
|
20
21
|
# Needed by the `/project_file_metadatas` endpoint
|
21
22
|
require 'puppet/file_serving/fileset'
|
22
23
|
|
24
|
+
# Needed by the 'project_facts_plugin_tarball' endpoint
|
25
|
+
require 'minitar'
|
26
|
+
require 'zlib'
|
27
|
+
|
23
28
|
module BoltServer
|
24
29
|
class TransportApp < Sinatra::Base
|
25
30
|
# This disables Sinatra's error page generation
|
@@ -51,10 +56,6 @@ module BoltServer
|
|
51
56
|
# See the `orchestrator.bolt.codedir` tk config setting.
|
52
57
|
DEFAULT_BOLT_CODEDIR = '/opt/puppetlabs/server/data/orchestration-services/code'
|
53
58
|
|
54
|
-
MISSING_VERSIONED_PROJECT_RESPONSE = [
|
55
|
-
400, Bolt::ValidationError.new('`versioned_project` is a required argument').to_json
|
56
|
-
].freeze
|
57
|
-
|
58
59
|
def initialize(config)
|
59
60
|
@config = config
|
60
61
|
@schemas = Hash[REQUEST_SCHEMAS.map do |basename|
|
@@ -74,6 +75,8 @@ module BoltServer
|
|
74
75
|
# This is needed until the PAL is threadsafe.
|
75
76
|
@pal_mutex = Mutex.new
|
76
77
|
|
78
|
+
@logger = Bolt::Logger.logger(self)
|
79
|
+
|
77
80
|
super(nil)
|
78
81
|
end
|
79
82
|
|
@@ -84,40 +87,29 @@ module BoltServer
|
|
84
87
|
result
|
85
88
|
end
|
86
89
|
|
87
|
-
def error_result(error)
|
88
|
-
{
|
89
|
-
'status' => 'failure',
|
90
|
-
'value' => { '_error' => error.to_h }
|
91
|
-
}
|
92
|
-
end
|
93
|
-
|
94
90
|
def validate_schema(schema, body)
|
95
91
|
schema_error = JSON::Validator.fully_validate(schema, body)
|
96
92
|
if schema_error.any?
|
97
|
-
|
98
|
-
|
99
|
-
schema_error)
|
93
|
+
raise BoltServer::RequestError.new("There was an error validating the request body.",
|
94
|
+
schema_error)
|
100
95
|
end
|
101
96
|
end
|
102
97
|
|
103
98
|
# Turns a Bolt::ResultSet object into a status hash that is fit
|
104
|
-
# to return to the client in a response.
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
# If the `result_set` contains only one item, it will be returned
|
109
|
-
# as a single result object. Set `aggregate` to treat it as a set
|
110
|
-
# of results with length 1 instead.
|
99
|
+
# to return to the client in a response. In the case of every action
|
100
|
+
# *except* check_node_connections the response will be a single serialized Result.
|
101
|
+
# In the check_node_connections case the response will be a hash with the top level "status"
|
102
|
+
# of the result and the serialized individual target results.
|
111
103
|
def result_set_to_data(result_set, aggregate: false)
|
104
|
+
# use ResultSet#ok method to determine status of a (potentially) aggregate result before serializing
|
105
|
+
result_set_status = result_set.ok ? 'success' : 'failure'
|
112
106
|
scrubbed_results = result_set.map do |result|
|
113
107
|
scrub_stack_trace(result.to_data)
|
114
108
|
end
|
115
109
|
|
116
|
-
if aggregate
|
117
|
-
# For actions that act on multiple targets, construct a status hash for the aggregate result
|
118
|
-
all_succeeded = scrubbed_results.all? { |r| r['status'] == 'success' }
|
110
|
+
if aggregate
|
119
111
|
{
|
120
|
-
status:
|
112
|
+
status: result_set_status,
|
121
113
|
result: scrubbed_results
|
122
114
|
}
|
123
115
|
else
|
@@ -127,33 +119,26 @@ module BoltServer
|
|
127
119
|
end
|
128
120
|
|
129
121
|
def run_task(target, body)
|
130
|
-
|
131
|
-
return [], error unless error.nil?
|
132
|
-
|
122
|
+
validate_schema(@schemas["action-run_task"], body)
|
133
123
|
task_data = body['task']
|
134
124
|
task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
|
135
125
|
parameters = body['parameters'] || {}
|
136
|
-
|
137
|
-
task_result.each do |result|
|
126
|
+
@executor.run_task(target, task, parameters).each do |result|
|
138
127
|
value = result.value
|
139
128
|
next unless value.is_a?(Hash)
|
140
129
|
next unless value.key?('_sensitive')
|
141
130
|
value['_sensitive'] = value['_sensitive'].unwrap
|
142
131
|
end
|
143
|
-
[task_result, nil]
|
144
132
|
end
|
145
133
|
|
146
134
|
def run_command(target, body)
|
147
|
-
|
148
|
-
return [], error unless error.nil?
|
149
|
-
|
135
|
+
validate_schema(@schemas["action-run_command"], body)
|
150
136
|
command = body['command']
|
151
|
-
|
137
|
+
@executor.run_command(target, command)
|
152
138
|
end
|
153
139
|
|
154
140
|
def check_node_connections(targets, body)
|
155
|
-
|
156
|
-
return [], error unless error.nil?
|
141
|
+
validate_schema(@schemas["action-check_node_connections"], body)
|
157
142
|
|
158
143
|
# Puppet Enterprise's orchestrator service uses the
|
159
144
|
# check_node_connections endpoint to check whether nodes that should be
|
@@ -161,13 +146,11 @@ module BoltServer
|
|
161
146
|
# because the endpoint is meant to be used for a single check of all
|
162
147
|
# nodes; External implementations of wait_until_available (like
|
163
148
|
# orchestrator's) should contact the endpoint in their own loop.
|
164
|
-
|
149
|
+
@executor.wait_until_available(targets, wait_time: 0)
|
165
150
|
end
|
166
151
|
|
167
152
|
def upload_file(target, body)
|
168
|
-
|
169
|
-
return [], error unless error.nil?
|
170
|
-
|
153
|
+
validate_schema(@schemas["action-upload_file"], body)
|
171
154
|
files = body['files']
|
172
155
|
destination = body['destination']
|
173
156
|
job_id = body['job_id']
|
@@ -190,8 +173,7 @@ module BoltServer
|
|
190
173
|
# Create directory in cache so we can move files in.
|
191
174
|
FileUtils.mkdir_p(path)
|
192
175
|
else
|
193
|
-
|
194
|
-
'boltserver/schema-error').to_json]
|
176
|
+
raise BoltServer::RequestError, "Invalid kind: '#{kind}' supplied. Must be 'file' or 'directory'."
|
195
177
|
end
|
196
178
|
end
|
197
179
|
# We need to special case the scenario where only one file was
|
@@ -205,17 +187,14 @@ module BoltServer
|
|
205
187
|
else
|
206
188
|
cache_dir
|
207
189
|
end
|
208
|
-
|
190
|
+
@executor.upload_file(target, upload_source, destination)
|
209
191
|
end
|
210
192
|
|
211
193
|
def run_script(target, body)
|
212
|
-
|
213
|
-
return [], error unless error.nil?
|
214
|
-
|
194
|
+
validate_schema(@schemas["action-run_script"], body)
|
215
195
|
# Download the file onto the machine.
|
216
196
|
file_location = @file_cache.update_file(body['script'])
|
217
|
-
|
218
|
-
[@executor.run_script(target, file_location, body['arguments'])]
|
197
|
+
@executor.run_script(target, file_location, body['arguments'])
|
219
198
|
end
|
220
199
|
|
221
200
|
# This function is nearly identical to Bolt::Pal's `with_puppet_settings` with the
|
@@ -248,16 +227,14 @@ module BoltServer
|
|
248
227
|
codedir = @config['environments-codedir'] || DEFAULT_BOLT_CODEDIR
|
249
228
|
environmentpath = @config['environmentpath'] || "#{codedir}/environments"
|
250
229
|
basemodulepath = @config['basemodulepath'] || "#{codedir}/modules:/opt/puppetlabs/puppet/modules"
|
251
|
-
modulepath_dirs = nil
|
252
230
|
with_pe_pal_init_settings(codedir, environmentpath, basemodulepath) do
|
253
231
|
environment = Puppet.lookup(:environments).get!(environment_name)
|
254
|
-
|
232
|
+
environment.modulepath
|
255
233
|
end
|
256
|
-
modulepath_dirs
|
257
234
|
end
|
258
235
|
|
259
236
|
def in_pe_pal_env(environment)
|
260
|
-
|
237
|
+
raise BoltServer::RequestError, "'environment' is a required argument" if environment.nil?
|
261
238
|
@pal_mutex.synchronize do
|
262
239
|
modulepath_obj = Bolt::Config::Modulepath.new(
|
263
240
|
modulepath_from_environment(environment),
|
@@ -266,18 +243,16 @@ module BoltServer
|
|
266
243
|
pal = Bolt::PAL.new(modulepath_obj, nil, nil)
|
267
244
|
yield pal
|
268
245
|
rescue Puppet::Environments::EnvironmentNotFound
|
269
|
-
|
270
|
-
"class" => 'bolt/unknown-environment',
|
271
|
-
"message" => "Environment #{environment} not found"
|
272
|
-
}.to_json]
|
273
|
-
rescue Bolt::Error => e
|
274
|
-
[400, e.to_json]
|
246
|
+
raise BoltServer::RequestError, "environment: '#{environment}' does not exist"
|
275
247
|
end
|
276
248
|
end
|
277
249
|
|
278
250
|
def config_from_project(versioned_project)
|
279
251
|
project_dir = File.join(@config['projects-dir'], versioned_project)
|
280
|
-
|
252
|
+
unless Dir.exist?(project_dir)
|
253
|
+
raise BoltServer::RequestError,
|
254
|
+
"versioned_project: '#{project_dir}' does not exist"
|
255
|
+
end
|
281
256
|
project = Bolt::Project.create_project(project_dir)
|
282
257
|
Bolt::Config.from_project(project, { log: { 'bolt-debug.log' => 'disable' } })
|
283
258
|
end
|
@@ -383,12 +358,15 @@ module BoltServer
|
|
383
358
|
pal = pal_from_project_bolt_config(bolt_config)
|
384
359
|
pal.in_bolt_compiler do
|
385
360
|
mod = Puppet.lookup(:current_environment).module(module_name)
|
386
|
-
raise
|
361
|
+
raise BoltServer::RequestError, "module_name: '#{module_name}' does not exist" unless mod
|
387
362
|
mod.file(file)
|
388
363
|
end
|
389
364
|
end
|
390
365
|
|
391
|
-
|
366
|
+
unless abs_file_path
|
367
|
+
raise BoltServer::RequestError,
|
368
|
+
"file: '#{file}' does not exist inside the module's 'files' directory"
|
369
|
+
end
|
392
370
|
|
393
371
|
fileset = Puppet::FileServing::Fileset.new(abs_file_path, 'recurse' => 'yes')
|
394
372
|
Puppet::FileServing::Fileset.merge(fileset).collect do |relative_file_path, base_path|
|
@@ -466,17 +444,15 @@ module BoltServer
|
|
466
444
|
content_type :json
|
467
445
|
body = JSON.parse(request.body.read)
|
468
446
|
|
469
|
-
|
470
|
-
return [400, error_result(error).to_json] unless error.nil?
|
447
|
+
validate_schema(@schemas["transport-ssh"], body)
|
471
448
|
|
472
449
|
targets = (body['targets'] || [body['target']]).map do |target|
|
473
450
|
make_ssh_target(target)
|
474
451
|
end
|
475
452
|
|
476
|
-
result_set
|
477
|
-
return [400, error.to_json] unless error.nil?
|
453
|
+
result_set = method(params[:action]).call(targets, body)
|
478
454
|
|
479
|
-
aggregate =
|
455
|
+
aggregate = params[:action] == 'check_node_connections'
|
480
456
|
[200, result_set_to_data(result_set, aggregate: aggregate).to_json]
|
481
457
|
end
|
482
458
|
|
@@ -506,17 +482,15 @@ module BoltServer
|
|
506
482
|
content_type :json
|
507
483
|
body = JSON.parse(request.body.read)
|
508
484
|
|
509
|
-
|
510
|
-
return [400, error_result(error).to_json] unless error.nil?
|
485
|
+
validate_schema(@schemas["transport-winrm"], body)
|
511
486
|
|
512
487
|
targets = (body['targets'] || [body['target']]).map do |target|
|
513
488
|
make_winrm_target(target)
|
514
489
|
end
|
515
490
|
|
516
|
-
result_set
|
517
|
-
return [400, error.to_json] if error
|
491
|
+
result_set = method(params[:action]).call(targets, body)
|
518
492
|
|
519
|
-
aggregate =
|
493
|
+
aggregate = params[:action] == 'check_node_connections'
|
520
494
|
[200, result_set_to_data(result_set, aggregate: aggregate).to_json]
|
521
495
|
end
|
522
496
|
|
@@ -534,14 +508,12 @@ module BoltServer
|
|
534
508
|
#
|
535
509
|
# @param versioned_project [String] the project to fetch the plan from
|
536
510
|
get '/project_plans/:module_name/:plan_name' do
|
537
|
-
|
511
|
+
raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
|
538
512
|
in_bolt_project(params['versioned_project']) do |context|
|
539
513
|
plan_info = pe_plan_info(context[:pal], params[:module_name], params[:plan_name])
|
540
514
|
plan_info = allowed_helper(context[:pal], plan_info, context[:config].project.plans)
|
541
515
|
[200, plan_info.to_json]
|
542
516
|
end
|
543
|
-
rescue Bolt::Error => e
|
544
|
-
[400, e.to_json]
|
545
517
|
end
|
546
518
|
|
547
519
|
# Fetches the metadata for a single task
|
@@ -561,7 +533,7 @@ module BoltServer
|
|
561
533
|
#
|
562
534
|
# @param bolt_versioned_project [String] the reference to the bolt-project directory to load task metadata from
|
563
535
|
get '/project_tasks/:module_name/:task_name' do
|
564
|
-
|
536
|
+
raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
|
565
537
|
in_bolt_project(params['versioned_project']) do |context|
|
566
538
|
ps_parameters = {
|
567
539
|
'versioned_project' => params['versioned_project']
|
@@ -570,8 +542,6 @@ module BoltServer
|
|
570
542
|
task_info = allowed_helper(context[:pal], task_info, context[:config].project.tasks)
|
571
543
|
[200, task_info.to_json]
|
572
544
|
end
|
573
|
-
rescue Bolt::Error => e
|
574
|
-
[400, e.to_json]
|
575
545
|
end
|
576
546
|
|
577
547
|
# Fetches the list of plans for an environment, optionally fetching all metadata for each plan
|
@@ -602,7 +572,7 @@ module BoltServer
|
|
602
572
|
#
|
603
573
|
# @param versioned_project [String] the project to fetch the list of plans from
|
604
574
|
get '/project_plans' do
|
605
|
-
|
575
|
+
raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
|
606
576
|
in_bolt_project(params['versioned_project']) do |context|
|
607
577
|
plans_response = plan_list(context[:pal])
|
608
578
|
|
@@ -615,8 +585,6 @@ module BoltServer
|
|
615
585
|
# to bolt-server smaller/simpler.
|
616
586
|
[200, plans_response.to_json]
|
617
587
|
end
|
618
|
-
rescue Bolt::Error => e
|
619
|
-
[400, e.to_json]
|
620
588
|
end
|
621
589
|
|
622
590
|
# Fetches the list of tasks for an environment
|
@@ -638,7 +606,7 @@ module BoltServer
|
|
638
606
|
#
|
639
607
|
# @param versioned_project [String] the project to fetch the list of tasks from
|
640
608
|
get '/project_tasks' do
|
641
|
-
|
609
|
+
raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
|
642
610
|
in_bolt_project(params['versioned_project']) do |context|
|
643
611
|
tasks_response = task_list(context[:pal])
|
644
612
|
|
@@ -651,34 +619,28 @@ module BoltServer
|
|
651
619
|
# to bolt-server smaller/simpler.
|
652
620
|
[200, tasks_response.to_json]
|
653
621
|
end
|
654
|
-
rescue Bolt::Error => e
|
655
|
-
[400, e.to_json]
|
656
622
|
end
|
657
623
|
|
658
624
|
# Implements puppetserver's file_metadatas endpoint for projects.
|
659
625
|
#
|
660
626
|
# @param versioned_project [String] the versioned_project to fetch the file metadatas from
|
661
627
|
get '/project_file_metadatas/:module_name/*' do
|
662
|
-
versioned_project
|
663
|
-
return MISSING_VERSIONED_PROJECT_RESPONSE if versioned_project.nil?
|
628
|
+
raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
|
664
629
|
file = params[:splat].first
|
665
|
-
metadatas = file_metadatas(versioned_project, params[:module_name], file)
|
630
|
+
metadatas = file_metadatas(params['versioned_project'], params[:module_name], file)
|
666
631
|
[200, metadatas.to_json]
|
667
|
-
rescue Bolt::Error => e
|
668
|
-
[400, e.to_json]
|
669
632
|
rescue ArgumentError => e
|
670
|
-
[
|
633
|
+
[500, e.message]
|
671
634
|
end
|
672
635
|
|
673
636
|
# Returns a list of targets parsed from a Project inventory
|
674
637
|
#
|
675
638
|
# @param versioned_project [String] the versioned_project to compute the inventory from
|
676
639
|
post '/project_inventory_targets' do
|
677
|
-
|
640
|
+
raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
|
678
641
|
content_type :json
|
679
642
|
body = JSON.parse(request.body.read)
|
680
|
-
|
681
|
-
return [400, error_result(error).to_json] unless error.nil?
|
643
|
+
validate_schema(@schemas["connect-data"], body)
|
682
644
|
in_bolt_project(params['versioned_project']) do |context|
|
683
645
|
if context[:config].inventoryfile &&
|
684
646
|
context[:config].project.inventory_file.to_s !=
|
@@ -717,8 +679,70 @@ module BoltServer
|
|
717
679
|
|
718
680
|
[200, target_list.to_json]
|
719
681
|
end
|
720
|
-
|
721
|
-
|
682
|
+
end
|
683
|
+
|
684
|
+
# Returns the base64 encoded tar archive of plugin code that is needed to calculate
|
685
|
+
# custom facts
|
686
|
+
#
|
687
|
+
# @param versioned_project [String] the versioned_project to build the plugin tarball from
|
688
|
+
get '/project_facts_plugin_tarball' do
|
689
|
+
raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
|
690
|
+
content_type :json
|
691
|
+
|
692
|
+
# Inspired by Bolt::Applicator.build_plugin_tarball
|
693
|
+
start_time = Time.now
|
694
|
+
|
695
|
+
# Fetch the plugin files
|
696
|
+
plugin_files = in_bolt_project(params['versioned_project']) do |context|
|
697
|
+
files = {}
|
698
|
+
|
699
|
+
# Bolt also sets plugin_modulepath to user modulepath so do it here too for
|
700
|
+
# consistency
|
701
|
+
plugin_modulepath = context[:pal].user_modulepath
|
702
|
+
Puppet.lookup(:current_environment).override_with(modulepath: plugin_modulepath).modules.each do |mod|
|
703
|
+
search_dirs = []
|
704
|
+
search_dirs << mod.plugins if mod.plugins?
|
705
|
+
search_dirs << mod.pluginfacts if mod.pluginfacts?
|
706
|
+
|
707
|
+
files[mod] ||= []
|
708
|
+
Find.find(*search_dirs).each do |file|
|
709
|
+
files[mod] << file if File.file?(file)
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
files
|
714
|
+
end
|
715
|
+
|
716
|
+
# Pack the plugin files
|
717
|
+
sio = StringIO.new
|
718
|
+
begin
|
719
|
+
output = Minitar::Output.new(Zlib::GzipWriter.new(sio))
|
720
|
+
|
721
|
+
plugin_files.each do |mod, files|
|
722
|
+
tar_dir = Pathname.new(mod.name)
|
723
|
+
mod_dir = Pathname.new(mod.path)
|
724
|
+
|
725
|
+
files.each do |file|
|
726
|
+
tar_path = tar_dir + Pathname.new(file).relative_path_from(mod_dir)
|
727
|
+
stat = File.stat(file)
|
728
|
+
content = File.binread(file)
|
729
|
+
output.tar.add_file_simple(
|
730
|
+
tar_path.to_s,
|
731
|
+
data: content,
|
732
|
+
size: content.size,
|
733
|
+
mode: stat.mode & 0o777,
|
734
|
+
mtime: stat.mtime
|
735
|
+
)
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
duration = Time.now - start_time
|
740
|
+
@logger.trace("Packed plugins in #{duration * 1000} ms")
|
741
|
+
ensure
|
742
|
+
output.close
|
743
|
+
end
|
744
|
+
|
745
|
+
[200, Base64.encode64(sio.string).to_json]
|
722
746
|
end
|
723
747
|
|
724
748
|
error 404 do
|
@@ -727,6 +751,20 @@ module BoltServer
|
|
727
751
|
[404, err.to_json]
|
728
752
|
end
|
729
753
|
|
754
|
+
error BoltServer::RequestError do |err|
|
755
|
+
[400, err.to_json]
|
756
|
+
end
|
757
|
+
|
758
|
+
error Bolt::Error do |err|
|
759
|
+
# In order to match the request code pattern, unknown plan/task content should 400. This also
|
760
|
+
# gives us an opportunity to trim the message instructing users to use CLI to show available content.
|
761
|
+
if ['bolt/unknown-plan', 'bolt/unknown-task'].include?(err.kind)
|
762
|
+
[404, BoltServer::RequestError.new(err.msg.split('.').first).to_json]
|
763
|
+
else
|
764
|
+
[500, err.to_json]
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
730
768
|
error StandardError do
|
731
769
|
e = env['sinatra.error']
|
732
770
|
err = Bolt::Error.new("500: Unknown error: #{e.message}",
|