bolt 2.18.0 → 2.23.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/lib/bolt/analytics.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +74 -24
- data/lib/bolt/catalog.rb +12 -3
- data/lib/bolt/cli.rb +305 -108
- data/lib/bolt/config.rb +18 -10
- data/lib/bolt/config/options.rb +14 -0
- data/lib/bolt/executor.rb +26 -5
- data/lib/bolt/inventory/group.rb +3 -2
- data/lib/bolt/inventory/inventory.rb +4 -3
- data/lib/bolt/logger.rb +9 -0
- data/lib/bolt/module.rb +2 -1
- data/lib/bolt/outputter.rb +56 -0
- data/lib/bolt/outputter/human.rb +0 -9
- data/lib/bolt/outputter/json.rb +0 -4
- data/lib/bolt/outputter/rainbow.rb +9 -2
- data/lib/bolt/pal.rb +11 -9
- data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -2
- 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/plugin/module.rb +2 -4
- data/lib/bolt/plugin/prompt.rb +3 -3
- data/lib/bolt/plugin/puppetdb.rb +3 -2
- data/lib/bolt/project.rb +14 -9
- 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 +24 -4
- 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 +20 -2
- data/lib/bolt/transport/local/connection.rb +14 -1
- data/lib/bolt/transport/orch.rb +12 -0
- data/lib/bolt/transport/simple.rb +6 -0
- data/lib/bolt/transport/ssh/connection.rb +9 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +22 -1
- data/lib/bolt/transport/winrm/connection.rb +118 -8
- data/lib/bolt/util.rb +26 -11
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/pe/pal.rb +1 -1
- data/lib/bolt_server/transport_app.rb +3 -2
- data/lib/bolt_spec/bolt_context.rb +7 -2
- data/lib/bolt_spec/plans.rb +15 -2
- 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 +8 -2
@@ -38,6 +38,14 @@ module Bolt
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
def download(target, source, destination, _options = {})
|
42
|
+
with_connection(target) do |conn|
|
43
|
+
download = File.join(destination, Bolt::Util.unix_basename(source))
|
44
|
+
conn.download_remote_content(source, destination)
|
45
|
+
Bolt::Result.for_download(target, source, destination, download)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
41
49
|
def run_command(target, command, options = {})
|
42
50
|
execute_options = {}
|
43
51
|
execute_options[:tty] = target.options['tty']
|
@@ -82,7 +82,9 @@ module Bolt
|
|
82
82
|
def write_remote_file(source, destination)
|
83
83
|
@logger.debug { "Uploading #{source}, to #{destination}" }
|
84
84
|
_, stdout_str, status = execute_local_docker_command('cp', [source, "#{container_id}:#{destination}"])
|
85
|
-
|
85
|
+
unless status.exitstatus.zero?
|
86
|
+
raise "Error writing file to container #{@container_id}: #{stdout_str}"
|
87
|
+
end
|
86
88
|
rescue StandardError => e
|
87
89
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
88
90
|
end
|
@@ -90,7 +92,23 @@ module Bolt
|
|
90
92
|
def write_remote_directory(source, destination)
|
91
93
|
@logger.debug { "Uploading #{source}, to #{destination}" }
|
92
94
|
_, stdout_str, status = execute_local_docker_command('cp', [source, "#{container_id}:#{destination}"])
|
93
|
-
|
95
|
+
unless status.exitstatus.zero?
|
96
|
+
raise "Error writing directory to container #{@container_id}: #{stdout_str}"
|
97
|
+
end
|
98
|
+
rescue StandardError => e
|
99
|
+
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
100
|
+
end
|
101
|
+
|
102
|
+
def download_remote_content(source, destination)
|
103
|
+
@logger.debug { "Downloading #{source} to #{destination}" }
|
104
|
+
# Create the destination directory, otherwise copying a source directory with Docker will
|
105
|
+
# copy the *contents* of the directory.
|
106
|
+
# https://docs.docker.com/engine/reference/commandline/cp/
|
107
|
+
FileUtils.mkdir_p(destination)
|
108
|
+
_, stdout_str, status = execute_local_docker_command('cp', ["#{container_id}:#{source}", destination])
|
109
|
+
unless status.exitstatus.zero?
|
110
|
+
raise "Error downloading content from container #{@container_id}: #{stdout_str}"
|
111
|
+
end
|
94
112
|
rescue StandardError => e
|
95
113
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
96
114
|
end
|
@@ -27,7 +27,7 @@ module Bolt
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
30
|
+
def upload_file(source, dest)
|
31
31
|
@logger.debug { "Uploading #{source}, to #{dest}" }
|
32
32
|
if source.is_a?(StringIO)
|
33
33
|
Tempfile.create(File.basename(dest)) do |f|
|
@@ -45,6 +45,19 @@ module Bolt
|
|
45
45
|
raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
|
46
46
|
end
|
47
47
|
|
48
|
+
def download_file(source, dest, _download)
|
49
|
+
@logger.debug { "Downloading #{source} to #{dest}" }
|
50
|
+
# Create the destination directory for the target, or the
|
51
|
+
# copied file will have the target's name
|
52
|
+
FileUtils.mkdir_p(dest)
|
53
|
+
# Mimic the behavior of `cp --remove-destination`
|
54
|
+
# since the flag isn't supported on MacOS
|
55
|
+
FileUtils.cp_r(source, dest, remove_destination: true)
|
56
|
+
rescue StandardError => e
|
57
|
+
message = "Could not download file to #{dest}: #{e}"
|
58
|
+
raise Bolt::Node::FileError.new(message, 'DOWNLOAD_ERROR')
|
59
|
+
end
|
60
|
+
|
48
61
|
def execute(command)
|
49
62
|
if Bolt::Util.windows?
|
50
63
|
# If it's already a powershell command then invoke it normally.
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -184,6 +184,18 @@ module Bolt
|
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
187
|
+
def batch_download(targets, *_args)
|
188
|
+
error = {
|
189
|
+
'kind' => 'bolt/not-supported-error',
|
190
|
+
'msg' => 'pcp transport does not support downloading files',
|
191
|
+
'details' => {}
|
192
|
+
}
|
193
|
+
|
194
|
+
targets.map do |target|
|
195
|
+
Bolt::Result.new(target, error: error, action: 'download')
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
187
199
|
def batches(targets)
|
188
200
|
targets.group_by { |target| Connection.get_key(target.options) }.values
|
189
201
|
end
|
@@ -32,6 +32,12 @@ module Bolt
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
def download(target, source, destination, options = {})
|
36
|
+
with_connection(target) do |conn|
|
37
|
+
conn.shell.download(source, destination, options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
35
41
|
def run_script(target, script, arguments, options = {})
|
36
42
|
with_connection(target) do |conn|
|
37
43
|
conn.shell.run_script(script, arguments, options)
|
@@ -235,7 +235,7 @@ module Bolt
|
|
235
235
|
raise Bolt::Error.new(msg, 'bolt/too-many-files')
|
236
236
|
end
|
237
237
|
|
238
|
-
def
|
238
|
+
def upload_file(source, destination)
|
239
239
|
# Do not log wrapper script content
|
240
240
|
@logger.debug { "Uploading #{source}, to #{destination}" } unless source.is_a?(StringIO)
|
241
241
|
@session.scp.upload!(source, destination, recursive: true)
|
@@ -243,6 +243,14 @@ module Bolt
|
|
243
243
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
244
244
|
end
|
245
245
|
|
246
|
+
def download_file(source, destination, _download)
|
247
|
+
# Do not log wrapper script content
|
248
|
+
@logger.debug { "Downloading #{source} to #{destination}" }
|
249
|
+
@session.scp.download!(source, destination, recursive: true)
|
250
|
+
rescue StandardError => e
|
251
|
+
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
252
|
+
end
|
253
|
+
|
246
254
|
# This handles renaming Net::SSH verifiers between version 4.x and 5.x
|
247
255
|
# of the gem
|
248
256
|
def net_ssh_verifier(verifier)
|
@@ -65,7 +65,7 @@ module Bolt
|
|
65
65
|
ssh_cmd << command
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
68
|
+
def upload_file(source, dest)
|
69
69
|
@logger.debug { "Uploading #{source}, to #{userhost}:#{dest}" } unless source.is_a?(StringIO)
|
70
70
|
|
71
71
|
cp_conf = @target.transport_config['copy-command'] || ["scp", "-r"]
|
@@ -94,6 +94,27 @@ module Bolt
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
+
def download_file(source, dest, _download)
|
98
|
+
@logger.debug { "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.debug "Successfully downloaded #{userhost}:#{source} to #{dest}"
|
112
|
+
else
|
113
|
+
message = "Could not copy file to #{dest}: #{err}"
|
114
|
+
raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
97
118
|
def execute(command)
|
98
119
|
cmd_array = build_ssh_command(command)
|
99
120
|
Open3.popen3(*cmd_array)
|
@@ -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]
|
@@ -129,23 +138,23 @@ module Bolt
|
|
129
138
|
raise
|
130
139
|
end
|
131
140
|
|
132
|
-
def
|
141
|
+
def upload_file(source, destination)
|
133
142
|
@logger.debug { "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.debug { "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
|
@@ -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
@@ -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
|
data/lib/bolt/version.rb
CHANGED