bolt 2.19.0 → 2.24.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 +3 -1
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +123 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +6 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +12 -6
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +1 -1
- data/exe/bolt +1 -0
- data/guides/inventory.txt +19 -0
- data/guides/project.txt +22 -0
- data/lib/bolt/analytics.rb +5 -5
- data/lib/bolt/applicator.rb +4 -3
- data/lib/bolt/bolt_option_parser.rb +100 -27
- data/lib/bolt/catalog.rb +12 -3
- data/lib/bolt/cli.rb +356 -156
- data/lib/bolt/config.rb +2 -2
- data/lib/bolt/config/options.rb +18 -4
- data/lib/bolt/executor.rb +30 -7
- data/lib/bolt/inventory/group.rb +6 -5
- data/lib/bolt/inventory/inventory.rb +4 -3
- data/lib/bolt/logger.rb +3 -4
- data/lib/bolt/module.rb +2 -1
- data/lib/bolt/outputter.rb +56 -0
- data/lib/bolt/outputter/human.rb +10 -9
- data/lib/bolt/outputter/json.rb +11 -4
- data/lib/bolt/outputter/logger.rb +2 -2
- data/lib/bolt/outputter/rainbow.rb +18 -2
- data/lib/bolt/pal.rb +13 -11
- data/lib/bolt/pal/yaml_plan/evaluator.rb +22 -1
- data/lib/bolt/pal/yaml_plan/step.rb +24 -2
- data/lib/bolt/pal/yaml_plan/step/download.rb +38 -0
- data/lib/bolt/pal/yaml_plan/step/message.rb +30 -0
- data/lib/bolt/pal/yaml_plan/step/upload.rb +3 -3
- data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
- data/lib/bolt/plugin/prompt.rb +3 -3
- data/lib/bolt/plugin/puppetdb.rb +3 -2
- data/lib/bolt/project.rb +7 -4
- data/lib/bolt/project_migrate.rb +138 -0
- data/lib/bolt/puppetdb/client.rb +2 -0
- data/lib/bolt/puppetdb/config.rb +16 -0
- data/lib/bolt/result.rb +7 -0
- data/lib/bolt/shell/bash.rb +31 -11
- data/lib/bolt/shell/powershell.rb +10 -4
- data/lib/bolt/transport/base.rb +24 -0
- data/lib/bolt/transport/docker.rb +8 -0
- data/lib/bolt/transport/docker/connection.rb +28 -10
- data/lib/bolt/transport/local/connection.rb +15 -2
- data/lib/bolt/transport/orch.rb +15 -3
- data/lib/bolt/transport/simple.rb +6 -0
- data/lib/bolt/transport/ssh/connection.rb +13 -5
- data/lib/bolt/transport/ssh/exec_connection.rb +24 -3
- data/lib/bolt/transport/winrm/connection.rb +125 -15
- data/lib/bolt/util.rb +27 -12
- data/lib/bolt/util/puppet_log_level.rb +4 -3
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +1 -1
- data/lib/bolt_server/pe/pal.rb +1 -1
- data/lib/bolt_server/transport_app.rb +79 -2
- data/lib/bolt_spec/bolt_context.rb +7 -2
- data/lib/bolt_spec/plans.rb +16 -3
- data/lib/bolt_spec/plans/action_stubs.rb +3 -2
- data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
- data/lib/bolt_spec/plans/mock_executor.rb +14 -1
- data/lib/bolt_spec/run.rb +22 -0
- data/libexec/apply_catalog.rb +2 -2
- data/libexec/bolt_catalog +4 -3
- data/libexec/custom_facts.rb +1 -1
- data/libexec/query_resources.rb +1 -1
- data/modules/secure_env_vars/plans/init.pp +20 -0
- metadata +11 -2
@@ -65,8 +65,8 @@ module Bolt
|
|
65
65
|
ssh_cmd << command
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
69
|
-
@logger.
|
68
|
+
def upload_file(source, dest)
|
69
|
+
@logger.trace { "Uploading #{source} to #{dest}" } unless source.is_a?(StringIO)
|
70
70
|
|
71
71
|
cp_conf = @target.transport_config['copy-command'] || ["scp", "-r"]
|
72
72
|
cp_cmd = Array(cp_conf)
|
@@ -87,7 +87,28 @@ module Bolt
|
|
87
87
|
end
|
88
88
|
|
89
89
|
if stat.success?
|
90
|
-
@logger.
|
90
|
+
@logger.trace "Successfully uploaded #{source} to #{dest}"
|
91
|
+
else
|
92
|
+
message = "Could not copy file to #{dest}: #{err}"
|
93
|
+
raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def download_file(source, dest, _download)
|
98
|
+
@logger.trace { "Downloading #{userhost}:#{source} to #{dest}" }
|
99
|
+
|
100
|
+
FileUtils.mkdir_p(dest)
|
101
|
+
|
102
|
+
cp_conf = @target.transport_config['copy-command'] || ["scp", "-r"]
|
103
|
+
cp_cmd = Array(cp_conf)
|
104
|
+
cp_cmd += ssh_opts
|
105
|
+
cp_cmd << "#{userhost}:#{Shellwords.escape(source)}"
|
106
|
+
cp_cmd << dest
|
107
|
+
|
108
|
+
_, err, stat = Open3.capture3(*cp_cmd)
|
109
|
+
|
110
|
+
if stat.success?
|
111
|
+
@logger.trace "Successfully downloaded #{userhost}:#{source} to #{dest}"
|
91
112
|
else
|
92
113
|
message = "Could not copy file to #{dest}: #{err}"
|
93
114
|
raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
|
@@ -19,7 +19,7 @@ module Bolt
|
|
19
19
|
# Build set of extensions from extensions config as well as interpreters
|
20
20
|
|
21
21
|
@logger = Logging.logger[@target.safe_name]
|
22
|
-
logger.
|
22
|
+
logger.trace("Initializing winrm connection to #{@target.safe_name}")
|
23
23
|
@transport_logger = transport_logger
|
24
24
|
end
|
25
25
|
|
@@ -55,7 +55,7 @@ module Bolt
|
|
55
55
|
|
56
56
|
@session = @connection.shell(:powershell)
|
57
57
|
@session.run('$PSVersionTable.PSVersion')
|
58
|
-
@logger.
|
58
|
+
@logger.trace { "Opened session" }
|
59
59
|
end
|
60
60
|
rescue Timeout::Error
|
61
61
|
# If we're using the default port with SSL, a timeout probably means the
|
@@ -97,11 +97,11 @@ module Bolt
|
|
97
97
|
def disconnect
|
98
98
|
@session&.close
|
99
99
|
@client&.disconnect!
|
100
|
-
@logger.
|
100
|
+
@logger.trace { "Closed session" }
|
101
101
|
end
|
102
102
|
|
103
103
|
def execute(command)
|
104
|
-
@logger.
|
104
|
+
@logger.trace { "Executing command: #{command}" }
|
105
105
|
|
106
106
|
inp = StringIO.new
|
107
107
|
# This transport doesn't accept stdin, so close the stream to ensure
|
@@ -111,12 +111,21 @@ module Bolt
|
|
111
111
|
out_rd, out_wr = IO.pipe('UTF-8')
|
112
112
|
err_rd, err_wr = IO.pipe('UTF-8')
|
113
113
|
th = Thread.new do
|
114
|
+
# By default, any exception raised in a thread will be reported to
|
115
|
+
# stderr as a stacktrace. Since we know these errors are going to
|
116
|
+
# propagate to the main thread via the shell, there's no chance
|
117
|
+
# they will be unhandled, so the default stack trace is unneeded.
|
118
|
+
Thread.current.report_on_exception = false
|
114
119
|
result = @session.run(command)
|
115
120
|
out_wr << result.stdout
|
116
121
|
err_wr << result.stderr
|
117
122
|
out_wr.close
|
118
123
|
err_wr.close
|
119
124
|
result.exitcode
|
125
|
+
ensure
|
126
|
+
# Close the streams to avoid the caller deadlocking
|
127
|
+
out_wr.close
|
128
|
+
err_wr.close
|
120
129
|
end
|
121
130
|
|
122
131
|
[inp, out_rd, err_rd, th]
|
@@ -125,27 +134,27 @@ module Bolt
|
|
125
134
|
"with 'ulimit -n 1024'. See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details."
|
126
135
|
raise Bolt::Error.new(msg, 'bolt/too-many-files')
|
127
136
|
rescue StandardError
|
128
|
-
@logger.
|
137
|
+
@logger.trace { "Command aborted" }
|
129
138
|
raise
|
130
139
|
end
|
131
140
|
|
132
|
-
def
|
133
|
-
@logger.
|
141
|
+
def upload_file(source, destination)
|
142
|
+
@logger.trace { "Uploading #{source} to #{destination}" }
|
134
143
|
if target.options['file-protocol'] == 'smb'
|
135
|
-
|
144
|
+
upload_file_smb(source, destination)
|
136
145
|
else
|
137
|
-
|
146
|
+
upload_file_winrm(source, destination)
|
138
147
|
end
|
139
148
|
end
|
140
149
|
|
141
|
-
def
|
150
|
+
def upload_file_winrm(source, destination)
|
142
151
|
fs = ::WinRM::FS::FileManager.new(@connection)
|
143
152
|
fs.upload(source, destination)
|
144
153
|
rescue StandardError => e
|
145
154
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
146
155
|
end
|
147
156
|
|
148
|
-
def
|
157
|
+
def upload_file_smb(source, destination)
|
149
158
|
# lazy-load expensive gem code
|
150
159
|
require 'ruby_smb'
|
151
160
|
|
@@ -165,7 +174,7 @@ module Bolt
|
|
165
174
|
client = smb_client_login
|
166
175
|
tree = client.tree_connect(path)
|
167
176
|
begin
|
168
|
-
|
177
|
+
upload_file_smb_recursive(tree, source, dest)
|
169
178
|
ensure
|
170
179
|
tree.disconnect!
|
171
180
|
end
|
@@ -175,6 +184,61 @@ module Bolt
|
|
175
184
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
176
185
|
end
|
177
186
|
|
187
|
+
def download_file(source, destination, download)
|
188
|
+
@logger.trace { "Downloading #{source} to #{destination}" }
|
189
|
+
if target.options['file-protocol'] == 'smb'
|
190
|
+
download_file_smb(source, destination)
|
191
|
+
else
|
192
|
+
download_file_winrm(source, destination, download)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def download_file_winrm(source, destination, download)
|
197
|
+
# The winrm gem doesn't create the destination directory if it's missing,
|
198
|
+
# so create it here
|
199
|
+
FileUtils.mkdir_p(destination)
|
200
|
+
fs = ::WinRM::FS::FileManager.new(@connection)
|
201
|
+
# params: source, destination, chunksize, first
|
202
|
+
# first needs to be set to false, otherwise if the source is a directory it
|
203
|
+
# will be nested inside a directory with the same name
|
204
|
+
fs.download(source, download, 1024 * 1024, false)
|
205
|
+
rescue StandardError => e
|
206
|
+
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
207
|
+
end
|
208
|
+
|
209
|
+
def download_file_smb(source, destination)
|
210
|
+
# lazy-load expensive gem code
|
211
|
+
require 'ruby_smb'
|
212
|
+
|
213
|
+
win_source = source.tr('/', '\\')
|
214
|
+
if (md = win_source.match(/^([a-z]):\\(.*)/i))
|
215
|
+
# if drive, use admin share for that drive, so path is '\\host\C$'
|
216
|
+
path = "\\\\#{@target.host}\\#{md[1]}$"
|
217
|
+
src = md[2]
|
218
|
+
elsif (md = win_source.match(/^(\\\\[^\\]+\\[^\\]+)\\(.*)/))
|
219
|
+
# if unc, path is '\\host\share'
|
220
|
+
path = md[1]
|
221
|
+
src = md[2]
|
222
|
+
else
|
223
|
+
raise ArgumentError, "Unknown source '#{source}'"
|
224
|
+
end
|
225
|
+
|
226
|
+
client = smb_client_login
|
227
|
+
tree = client.tree_connect(path)
|
228
|
+
|
229
|
+
begin
|
230
|
+
# Make sure the root download directory for the target exists
|
231
|
+
FileUtils.mkdir_p(destination)
|
232
|
+
download_file_smb_recursive(tree, src, destination)
|
233
|
+
ensure
|
234
|
+
tree.disconnect!
|
235
|
+
end
|
236
|
+
rescue ::RubySMB::Error::UnexpectedStatusCode => e
|
237
|
+
raise Bolt::Node::FileError.new("SMB Error: #{e.message}", 'DOWNLOAD_ERROR')
|
238
|
+
rescue StandardError => e
|
239
|
+
raise Bolt::Node::FileError.new(e.message, 'DOWNLOAD_ERROR')
|
240
|
+
end
|
241
|
+
|
178
242
|
def shell
|
179
243
|
@shell ||= Bolt::Shell::Powershell.new(target, self)
|
180
244
|
end
|
@@ -193,7 +257,7 @@ module Bolt
|
|
193
257
|
status = @client.login
|
194
258
|
case status
|
195
259
|
when WindowsError::NTStatus::STATUS_SUCCESS
|
196
|
-
@logger.
|
260
|
+
@logger.trace { "Connected to #{@client.dns_host_name}" }
|
197
261
|
when WindowsError::NTStatus::STATUS_LOGON_FAILURE
|
198
262
|
raise Bolt::Node::ConnectError.new(
|
199
263
|
"SMB authentication failed for #{target.safe_name}",
|
@@ -230,13 +294,13 @@ module Bolt
|
|
230
294
|
)
|
231
295
|
end
|
232
296
|
|
233
|
-
def
|
297
|
+
def upload_file_smb_recursive(tree, source, dest)
|
234
298
|
if Dir.exist?(source)
|
235
299
|
tree.open_directory(directory: dest, write: true, disposition: ::RubySMB::Dispositions::FILE_OPEN_IF)
|
236
300
|
|
237
301
|
Dir.children(source).each do |child|
|
238
302
|
child_dest = dest + '\\' + child
|
239
|
-
|
303
|
+
upload_file_smb_recursive(tree, File.join(source, child), child_dest)
|
240
304
|
end
|
241
305
|
return
|
242
306
|
end
|
@@ -255,6 +319,52 @@ module Bolt
|
|
255
319
|
file.close
|
256
320
|
end
|
257
321
|
end
|
322
|
+
|
323
|
+
def download_file_smb_recursive(tree, source, destination)
|
324
|
+
dest = File.expand_path(Bolt::Util.windows_basename(source), destination)
|
325
|
+
|
326
|
+
# Check if the source is a directory by attempting to list its children.
|
327
|
+
# If the source is a directory, create the directory on the host and then
|
328
|
+
# recurse through the children.
|
329
|
+
if (children = list_directory_children_smb(tree, source))
|
330
|
+
FileUtils.mkdir_p(dest)
|
331
|
+
|
332
|
+
children.each do |child|
|
333
|
+
# File names are encoded UTF_16LE.
|
334
|
+
filename = child.file_name.encode(Encoding::UTF_8)
|
335
|
+
|
336
|
+
next if %w[. ..].include?(filename)
|
337
|
+
|
338
|
+
src = source + '\\' + filename
|
339
|
+
download_file_smb_recursive(tree, src, dest)
|
340
|
+
end
|
341
|
+
# If the source wasn't a directory and just returns 'STATUS_NOT_A_DIRECTORY, then
|
342
|
+
# it is a file. Write it to the host.
|
343
|
+
else
|
344
|
+
begin
|
345
|
+
file = tree.open_file(filename: source)
|
346
|
+
data = file.read
|
347
|
+
|
348
|
+
# Files may be encoded UTF_16LE
|
349
|
+
data = data.encode(Encoding::UTF_8) if data.encoding == Encoding::UTF_16LE
|
350
|
+
|
351
|
+
File.write(dest, data)
|
352
|
+
ensure
|
353
|
+
file.close
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Lists the children of a directory using rb_smb
|
359
|
+
# Returns an array of RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation objects
|
360
|
+
# if the source is a directory, or raises RubySMB::Error::UnexpectedStatusCode otherwise.
|
361
|
+
def list_directory_children_smb(tree, source)
|
362
|
+
tree.list(directory: source)
|
363
|
+
rescue RubySMB::Error::UnexpectedStatusCode => e
|
364
|
+
unless e.message == 'STATUS_NOT_A_DIRECTORY'
|
365
|
+
raise e
|
366
|
+
end
|
367
|
+
end
|
258
368
|
end
|
259
369
|
end
|
260
370
|
end
|
data/lib/bolt/util.rb
CHANGED
@@ -13,7 +13,7 @@ module Bolt
|
|
13
13
|
msg = "Invalid content for #{file_name} file: #{path} should be a Hash or empty, not #{content.class}"
|
14
14
|
raise Bolt::FileError.new(msg, path)
|
15
15
|
end
|
16
|
-
logger.
|
16
|
+
logger.trace("Loaded #{file_name} from #{path}")
|
17
17
|
content
|
18
18
|
rescue Errno::ENOENT
|
19
19
|
raise Bolt::FileError.new("Could not read #{file_name} file: #{path}", path)
|
@@ -107,12 +107,13 @@ module Bolt
|
|
107
107
|
# Accepts a Data object and returns a copy with all hash keys
|
108
108
|
# modified by block. use &:to_s to stringify keys or &:to_sym to symbolize them
|
109
109
|
def walk_keys(data, &block)
|
110
|
-
|
110
|
+
case data
|
111
|
+
when Hash
|
111
112
|
data.each_with_object({}) do |(k, v), acc|
|
112
113
|
v = walk_keys(v, &block)
|
113
114
|
acc[yield(k)] = v
|
114
115
|
end
|
115
|
-
|
116
|
+
when Array
|
116
117
|
data.map { |v| walk_keys(v, &block) }
|
117
118
|
else
|
118
119
|
data
|
@@ -124,9 +125,10 @@ module Bolt
|
|
124
125
|
# their descendants are.
|
125
126
|
def walk_vals(data, skip_top = false, &block)
|
126
127
|
data = yield(data) unless skip_top
|
127
|
-
|
128
|
+
case data
|
129
|
+
when Hash
|
128
130
|
data.transform_values { |v| walk_vals(v, &block) }
|
129
|
-
|
131
|
+
when Array
|
130
132
|
data.map { |v| walk_vals(v, &block) }
|
131
133
|
else
|
132
134
|
data
|
@@ -137,9 +139,10 @@ module Bolt
|
|
137
139
|
# modified by the given block. Descendants are modified before their
|
138
140
|
# parents.
|
139
141
|
def postwalk_vals(data, skip_top = false, &block)
|
140
|
-
new_data =
|
142
|
+
new_data = case data
|
143
|
+
when Hash
|
141
144
|
data.transform_values { |v| postwalk_vals(v, &block) }
|
142
|
-
|
145
|
+
when Array
|
143
146
|
data.map { |v| postwalk_vals(v, &block) }
|
144
147
|
else
|
145
148
|
data
|
@@ -193,11 +196,12 @@ module Bolt
|
|
193
196
|
cloned[obj.object_id] = cl
|
194
197
|
cloned[cl.object_id] = cl
|
195
198
|
|
196
|
-
|
199
|
+
case cl
|
200
|
+
when Hash
|
197
201
|
obj.each { |k, v| cl[k] = deep_clone(v, cloned) }
|
198
|
-
|
202
|
+
when Array
|
199
203
|
cl.collect! { |v| deep_clone(v, cloned) }
|
200
|
-
|
204
|
+
when Struct
|
201
205
|
obj.each_pair { |k, v| cl[k] = deep_clone(v, cloned) }
|
202
206
|
end
|
203
207
|
|
@@ -257,14 +261,25 @@ module Bolt
|
|
257
261
|
|
258
262
|
# Recursively searches a data structure for plugin references
|
259
263
|
def references?(input)
|
260
|
-
|
264
|
+
case input
|
265
|
+
when Hash
|
261
266
|
input.key?('_plugin') || input.values.any? { |v| references?(v) }
|
262
|
-
|
267
|
+
when Array
|
263
268
|
input.any? { |v| references?(v) }
|
264
269
|
else
|
265
270
|
false
|
266
271
|
end
|
267
272
|
end
|
273
|
+
|
274
|
+
def unix_basename(path)
|
275
|
+
raise Bolt::ValidationError, "path must be a String, received #{path.class} #{path}" unless path.is_a?(String)
|
276
|
+
path.split('/').last
|
277
|
+
end
|
278
|
+
|
279
|
+
def windows_basename(path)
|
280
|
+
raise Bolt::ValidationError, "path must be a String, received #{path.class} #{path}" unless path.is_a?(String)
|
281
|
+
path.split(%r{[/\\]}).last
|
282
|
+
end
|
268
283
|
end
|
269
284
|
end
|
270
285
|
end
|
@@ -4,9 +4,10 @@ module Bolt
|
|
4
4
|
module Util
|
5
5
|
module PuppetLogLevel
|
6
6
|
MAPPING = {
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
# Demote Puppet's logs by one level, since Puppet is an implementation detail of Bolt
|
8
|
+
debug: :trace,
|
9
|
+
info: :debug,
|
10
|
+
notice: :info,
|
10
11
|
warning: :warn,
|
11
12
|
err: :error,
|
12
13
|
# The following are used by Puppet functions of the same name, and are all treated as
|
data/lib/bolt/version.rb
CHANGED
data/lib/bolt_server/pe/pal.rb
CHANGED
@@ -58,7 +58,7 @@ module BoltServer
|
|
58
58
|
# Bolt::PAL::MODULES_PATH which would be more complex if we tried to use @modulepath since
|
59
59
|
# we need to append our modulepaths and exclude modules shiped in bolt gem code
|
60
60
|
modulepath_dirs = environment.modulepath
|
61
|
-
@
|
61
|
+
@user_modulepath = modulepath_dirs
|
62
62
|
@modulepath = [PE_BOLTLIB_PATH, Bolt::PAL::BOLTLIB_PATH, *modulepath_dirs]
|
63
63
|
end
|
64
64
|
end
|
@@ -151,13 +151,14 @@ module BoltServer
|
|
151
151
|
sha256 = file['sha256']
|
152
152
|
kind = file['kind']
|
153
153
|
path = File.join(cache_dir, relative_path)
|
154
|
-
|
154
|
+
case kind
|
155
|
+
when 'file'
|
155
156
|
# The parent should already be created by `directory` entries,
|
156
157
|
# but this is to be on the safe side.
|
157
158
|
parent = File.dirname(path)
|
158
159
|
FileUtils.mkdir_p(parent)
|
159
160
|
@file_cache.serial_execute { @file_cache.download_file(path, sha256, uri) }
|
160
|
-
|
161
|
+
when 'directory'
|
161
162
|
# Create directory in cache so we can move files in.
|
162
163
|
FileUtils.mkdir_p(path)
|
163
164
|
else
|
@@ -220,6 +221,56 @@ module BoltServer
|
|
220
221
|
plan_info
|
221
222
|
end
|
222
223
|
|
224
|
+
def build_puppetserver_uri(file_identifier, module_name, environment)
|
225
|
+
segments = file_identifier.split('/', 3)
|
226
|
+
if segments.size == 1
|
227
|
+
{
|
228
|
+
'path' => "/puppet/v3/file_content/tasks/#{module_name}/#{file_identifier}",
|
229
|
+
'params' => {
|
230
|
+
'environment' => environment
|
231
|
+
}
|
232
|
+
}
|
233
|
+
else
|
234
|
+
module_segment, mount_segment, name_segment = *segments
|
235
|
+
{
|
236
|
+
'path' => case mount_segment
|
237
|
+
when 'files'
|
238
|
+
"/puppet/v3/file_content/modules/#{module_segment}/#{name_segment}"
|
239
|
+
when 'tasks'
|
240
|
+
"/puppet/v3/file_content/tasks/#{module_segment}/#{name_segment}"
|
241
|
+
when 'lib'
|
242
|
+
"/puppet/v3/file_content/plugins/#{name_segment}"
|
243
|
+
end,
|
244
|
+
'params' => {
|
245
|
+
'environment' => environment
|
246
|
+
}
|
247
|
+
}
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def pe_task_info(pal, module_name, task_name, environment)
|
252
|
+
# Handle case where task name is simply module name with special `init` task
|
253
|
+
task_name = if task_name == 'init' || task_name.nil?
|
254
|
+
module_name
|
255
|
+
else
|
256
|
+
"#{module_name}::#{task_name}"
|
257
|
+
end
|
258
|
+
task = pal.get_task(task_name)
|
259
|
+
files = task.files.map do |file_hash|
|
260
|
+
{
|
261
|
+
'filename' => file_hash['name'],
|
262
|
+
'sha256' => Digest::SHA256.hexdigest(File.read(file_hash['path'])),
|
263
|
+
'size_bytes' => File.size(file_hash['path']),
|
264
|
+
'uri' => build_puppetserver_uri(file_hash['name'], module_name, environment)
|
265
|
+
}
|
266
|
+
end
|
267
|
+
{
|
268
|
+
'metadata' => task.metadata,
|
269
|
+
'name' => task.name,
|
270
|
+
'files' => files
|
271
|
+
}
|
272
|
+
end
|
273
|
+
|
223
274
|
get '/' do
|
224
275
|
200
|
225
276
|
end
|
@@ -350,6 +401,16 @@ module BoltServer
|
|
350
401
|
end
|
351
402
|
end
|
352
403
|
|
404
|
+
# Fetches the metadata for a single task
|
405
|
+
#
|
406
|
+
# @param environment [String] the environment to fetch the task from
|
407
|
+
get '/tasks/:module_name/:task_name' do
|
408
|
+
in_pe_pal_env(params['environment']) do |pal|
|
409
|
+
task_info = pe_task_info(pal, params[:module_name], params[:task_name], params['environment'])
|
410
|
+
[200, task_info.to_json]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
353
414
|
# Fetches the list of plans for an environment, optionally fetching all metadata for each plan
|
354
415
|
#
|
355
416
|
# @param environment [String] the environment to fetch the list of plans from
|
@@ -374,6 +435,22 @@ module BoltServer
|
|
374
435
|
end
|
375
436
|
end
|
376
437
|
|
438
|
+
# Fetches the list of tasks for an environment
|
439
|
+
#
|
440
|
+
# @param environment [String] the environment to fetch the list of tasks from
|
441
|
+
get '/tasks' do
|
442
|
+
in_pe_pal_env(params['environment']) do |pal|
|
443
|
+
tasks = pal.list_tasks
|
444
|
+
tasks_response = tasks.map { |task_name, _description| { 'name' => task_name } }.to_json
|
445
|
+
|
446
|
+
# We structure this array of tasks to be an array of hashes so that it matches the structure
|
447
|
+
# returned by the puppetserver API that serves data like this. Structuring the output this way
|
448
|
+
# makes switching between puppetserver and bolt-server easier, which makes changes to switch
|
449
|
+
# to bolt-server smaller/simpler.
|
450
|
+
[200, tasks_response]
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
377
454
|
error 404 do
|
378
455
|
err = Bolt::Error.new("Could not find route #{request.path}",
|
379
456
|
'boltserver/not-found')
|