test-kitchen-rsync 3.0.0.pre.1
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/Gemfile +21 -0
- data/LICENSE +15 -0
- data/Rakefile +53 -0
- data/bin/zl-kitchen +11 -0
- data/lib/kitchen/base64_stream.rb +48 -0
- data/lib/kitchen/chef_utils_wiring.rb +40 -0
- data/lib/kitchen/cli.rb +413 -0
- data/lib/kitchen/collection.rb +52 -0
- data/lib/kitchen/color.rb +63 -0
- data/lib/kitchen/command/action.rb +41 -0
- data/lib/kitchen/command/console.rb +54 -0
- data/lib/kitchen/command/diagnose.rb +84 -0
- data/lib/kitchen/command/doctor.rb +39 -0
- data/lib/kitchen/command/exec.rb +37 -0
- data/lib/kitchen/command/list.rb +148 -0
- data/lib/kitchen/command/login.rb +39 -0
- data/lib/kitchen/command/package.rb +32 -0
- data/lib/kitchen/command/sink.rb +50 -0
- data/lib/kitchen/command/test.rb +47 -0
- data/lib/kitchen/command.rb +207 -0
- data/lib/kitchen/config.rb +344 -0
- data/lib/kitchen/configurable.rb +616 -0
- data/lib/kitchen/data_munger.rb +1024 -0
- data/lib/kitchen/diagnostic.rb +138 -0
- data/lib/kitchen/driver/base.rb +133 -0
- data/lib/kitchen/driver/dummy.rb +105 -0
- data/lib/kitchen/driver/exec.rb +70 -0
- data/lib/kitchen/driver/proxy.rb +70 -0
- data/lib/kitchen/driver/ssh_base.rb +351 -0
- data/lib/kitchen/driver.rb +40 -0
- data/lib/kitchen/errors.rb +243 -0
- data/lib/kitchen/generator/init.rb +254 -0
- data/lib/kitchen/instance.rb +726 -0
- data/lib/kitchen/lazy_hash.rb +148 -0
- data/lib/kitchen/lifecycle_hook/base.rb +78 -0
- data/lib/kitchen/lifecycle_hook/local.rb +53 -0
- data/lib/kitchen/lifecycle_hook/remote.rb +39 -0
- data/lib/kitchen/lifecycle_hooks.rb +92 -0
- data/lib/kitchen/loader/yaml.rb +377 -0
- data/lib/kitchen/logger.rb +422 -0
- data/lib/kitchen/logging.rb +52 -0
- data/lib/kitchen/login_command.rb +49 -0
- data/lib/kitchen/metadata_chopper.rb +49 -0
- data/lib/kitchen/platform.rb +64 -0
- data/lib/kitchen/plugin.rb +76 -0
- data/lib/kitchen/plugin_base.rb +60 -0
- data/lib/kitchen/provisioner/base.rb +269 -0
- data/lib/kitchen/provisioner/chef/berkshelf.rb +116 -0
- data/lib/kitchen/provisioner/chef/common_sandbox.rb +350 -0
- data/lib/kitchen/provisioner/chef/policyfile.rb +163 -0
- data/lib/kitchen/provisioner/chef_apply.rb +121 -0
- data/lib/kitchen/provisioner/chef_base.rb +705 -0
- data/lib/kitchen/provisioner/chef_infra.rb +167 -0
- data/lib/kitchen/provisioner/chef_solo.rb +82 -0
- data/lib/kitchen/provisioner/chef_zero.rb +12 -0
- data/lib/kitchen/provisioner/dummy.rb +75 -0
- data/lib/kitchen/provisioner/shell.rb +157 -0
- data/lib/kitchen/provisioner.rb +42 -0
- data/lib/kitchen/rake_tasks.rb +80 -0
- data/lib/kitchen/shell_out.rb +90 -0
- data/lib/kitchen/ssh.rb +289 -0
- data/lib/kitchen/state_file.rb +112 -0
- data/lib/kitchen/suite.rb +48 -0
- data/lib/kitchen/thor_tasks.rb +63 -0
- data/lib/kitchen/transport/base.rb +236 -0
- data/lib/kitchen/transport/dummy.rb +78 -0
- data/lib/kitchen/transport/exec.rb +145 -0
- data/lib/kitchen/transport/ssh.rb +579 -0
- data/lib/kitchen/transport/winrm.rb +546 -0
- data/lib/kitchen/transport.rb +40 -0
- data/lib/kitchen/util.rb +229 -0
- data/lib/kitchen/verifier/base.rb +243 -0
- data/lib/kitchen/verifier/busser.rb +275 -0
- data/lib/kitchen/verifier/dummy.rb +75 -0
- data/lib/kitchen/verifier/shell.rb +99 -0
- data/lib/kitchen/verifier.rb +39 -0
- data/lib/kitchen/version.rb +20 -0
- data/lib/kitchen/which.rb +26 -0
- data/lib/kitchen.rb +152 -0
- data/lib/vendor/hash_recursive_merge.rb +79 -0
- data/support/busser_install_command.ps1 +14 -0
- data/support/busser_install_command.sh +21 -0
- data/support/chef-client-fail-if-update-handler.rb +15 -0
- data/support/chef_base_init_command.ps1 +18 -0
- data/support/chef_base_init_command.sh +1 -0
- data/support/chef_base_install_command.ps1 +85 -0
- data/support/chef_base_install_command.sh +229 -0
- data/support/download_helpers.sh +109 -0
- data/support/dummy-validation.pem +27 -0
- data/templates/driver/CHANGELOG.md.erb +3 -0
- data/templates/driver/Gemfile.erb +3 -0
- data/templates/driver/README.md.erb +64 -0
- data/templates/driver/Rakefile.erb +21 -0
- data/templates/driver/driver.rb.erb +23 -0
- data/templates/driver/gemspec.erb +29 -0
- data/templates/driver/gitignore.erb +17 -0
- data/templates/driver/license_apachev2.erb +15 -0
- data/templates/driver/license_lgplv3.erb +16 -0
- data/templates/driver/license_mit.erb +22 -0
- data/templates/driver/license_reserved.erb +5 -0
- data/templates/driver/tailor.erb +4 -0
- data/templates/driver/travis.yml.erb +11 -0
- data/templates/driver/version.rb.erb +12 -0
- data/templates/init/chefignore.erb +2 -0
- data/templates/init/kitchen.yml.erb +18 -0
- data/test-kitchen.gemspec +52 -0
- metadata +528 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2012, Fletcher Nichol
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
require "thor/util"
|
|
19
|
+
|
|
20
|
+
require_relative "../lazy_hash"
|
|
21
|
+
require_relative "../plugin_base"
|
|
22
|
+
require "benchmark" unless defined?(Benchmark)
|
|
23
|
+
|
|
24
|
+
module Kitchen
|
|
25
|
+
module Driver
|
|
26
|
+
# Legacy base class for a driver that uses SSH to communication with an
|
|
27
|
+
# instance. This class has been updated to use the Instance's Transport to
|
|
28
|
+
# issue commands and transfer files and no longer uses the `Kitchen:SSH`
|
|
29
|
+
# class directly.
|
|
30
|
+
#
|
|
31
|
+
# **NOTE:** Authors of new Drivers are encouraged to inherit from
|
|
32
|
+
# `Kitchen::Driver::Base` instead and existing Driver authors are
|
|
33
|
+
# encouraged to update their Driver class to inherit from
|
|
34
|
+
# `Kitchen::Driver::SSHBase`.
|
|
35
|
+
#
|
|
36
|
+
# A subclass must implement the following methods:
|
|
37
|
+
# * #create(state)
|
|
38
|
+
# * #destroy(state)
|
|
39
|
+
#
|
|
40
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
41
|
+
# @deprecated While all possible effort has been made to preserve the
|
|
42
|
+
# original behavior of this class, future improvements to the Driver,
|
|
43
|
+
# Transport, and Verifier subsystems may not be picked up in these
|
|
44
|
+
# Drivers. When legacy Driver::SSHBase support is removed, this class
|
|
45
|
+
# will no longer be available.
|
|
46
|
+
class SSHBase < Kitchen::Plugin::Base
|
|
47
|
+
include ShellOut
|
|
48
|
+
include Configurable
|
|
49
|
+
include Logging
|
|
50
|
+
|
|
51
|
+
default_config :sudo, true
|
|
52
|
+
default_config :port, 22
|
|
53
|
+
# needs to be one less than the configured sshd_config MaxSessions
|
|
54
|
+
default_config :max_ssh_sessions, 9
|
|
55
|
+
|
|
56
|
+
# Creates a new Driver object using the provided configuration data
|
|
57
|
+
# which will be merged with any default configuration.
|
|
58
|
+
#
|
|
59
|
+
# @param config [Hash] provided driver configuration
|
|
60
|
+
def initialize(config = {})
|
|
61
|
+
init_config(config)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# (see Base#create)
|
|
65
|
+
def create(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
66
|
+
raise ClientError, "#{self.class}#create must be implemented"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# (see Base#converge)
|
|
70
|
+
def converge(state) # rubocop:disable Metrics/AbcSize
|
|
71
|
+
provisioner = instance.provisioner
|
|
72
|
+
provisioner.create_sandbox
|
|
73
|
+
sandbox_dirs = Util.list_directory(provisioner.sandbox_path)
|
|
74
|
+
|
|
75
|
+
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
76
|
+
conn.execute(env_cmd(provisioner.install_command))
|
|
77
|
+
conn.execute(env_cmd(provisioner.init_command))
|
|
78
|
+
info("Transferring files to #{instance.to_str}")
|
|
79
|
+
conn.upload(sandbox_dirs, provisioner[:root_path])
|
|
80
|
+
debug("Transfer complete")
|
|
81
|
+
conn.execute(env_cmd(provisioner.prepare_command))
|
|
82
|
+
conn.execute(env_cmd(provisioner.run_command))
|
|
83
|
+
info("Downloading files from #{instance.to_str}")
|
|
84
|
+
provisioner[:downloads].to_h.each do |remotes, local|
|
|
85
|
+
debug("Downloading #{Array(remotes).join(", ")} to #{local}")
|
|
86
|
+
conn.download(remotes, local)
|
|
87
|
+
end
|
|
88
|
+
debug("Download complete")
|
|
89
|
+
end
|
|
90
|
+
rescue Kitchen::Transport::TransportFailed => ex
|
|
91
|
+
raise ActionFailed, ex.message
|
|
92
|
+
ensure
|
|
93
|
+
instance.provisioner.cleanup_sandbox
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# (see Base#setup)
|
|
97
|
+
def setup(state)
|
|
98
|
+
verifier = instance.verifier
|
|
99
|
+
|
|
100
|
+
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
101
|
+
conn.execute(env_cmd(verifier.install_command))
|
|
102
|
+
end
|
|
103
|
+
rescue Kitchen::Transport::TransportFailed => ex
|
|
104
|
+
raise ActionFailed, ex.message
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# (see Base#verify)
|
|
108
|
+
def verify(state) # rubocop:disable Metrics/AbcSize
|
|
109
|
+
verifier = instance.verifier
|
|
110
|
+
verifier.create_sandbox
|
|
111
|
+
sandbox_dirs = Util.list_directory(verifier.sandbox_path)
|
|
112
|
+
|
|
113
|
+
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
114
|
+
conn.execute(env_cmd(verifier.init_command))
|
|
115
|
+
info("Transferring files to #{instance.to_str}")
|
|
116
|
+
conn.upload(sandbox_dirs, verifier[:root_path])
|
|
117
|
+
debug("Transfer complete")
|
|
118
|
+
conn.execute(env_cmd(verifier.prepare_command))
|
|
119
|
+
conn.execute(env_cmd(verifier.run_command))
|
|
120
|
+
end
|
|
121
|
+
rescue Kitchen::Transport::TransportFailed => ex
|
|
122
|
+
raise ActionFailed, ex.message
|
|
123
|
+
ensure
|
|
124
|
+
instance.verifier.cleanup_sandbox
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# (see Base#destroy)
|
|
128
|
+
def destroy(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
129
|
+
raise ClientError, "#{self.class}#destroy must be implemented"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def legacy_state(state)
|
|
133
|
+
backcompat_merged_state(state)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Package an instance.
|
|
137
|
+
#
|
|
138
|
+
# (see Base#package)
|
|
139
|
+
def package(state); end
|
|
140
|
+
|
|
141
|
+
# (see Base#login_command)
|
|
142
|
+
def login_command(state)
|
|
143
|
+
instance.transport.connection(backcompat_merged_state(state))
|
|
144
|
+
.login_command
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Executes an arbitrary command on an instance over an SSH connection.
|
|
148
|
+
#
|
|
149
|
+
# @param state [Hash] mutable instance and driver state
|
|
150
|
+
# @param command [String] the command to be executed
|
|
151
|
+
# @raise [ActionFailed] if the command could not be successfully completed
|
|
152
|
+
def remote_command(state, command)
|
|
153
|
+
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
154
|
+
conn.execute(env_cmd(command))
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# **(Deprecated)** Executes a remote command over SSH.
|
|
159
|
+
#
|
|
160
|
+
# @param ssh_args [Array] ssh arguments
|
|
161
|
+
# @param command [String] remote command to invoke
|
|
162
|
+
# @deprecated This method should no longer be called directly and exists
|
|
163
|
+
# to support very old drivers. This will be removed in the future.
|
|
164
|
+
def ssh(ssh_args, command)
|
|
165
|
+
pseudo_state = { hostname: ssh_args[0], username: ssh_args[1] }
|
|
166
|
+
pseudo_state.merge!(ssh_args[2])
|
|
167
|
+
connection_state = backcompat_merged_state(pseudo_state)
|
|
168
|
+
|
|
169
|
+
instance.transport.connection(connection_state) do |conn|
|
|
170
|
+
conn.execute(env_cmd(command))
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Performs whatever tests that may be required to ensure that this driver
|
|
175
|
+
# will be able to function in the current environment. This may involve
|
|
176
|
+
# checking for the presence of certain directories, software installed,
|
|
177
|
+
# etc.
|
|
178
|
+
#
|
|
179
|
+
# @raise [UserError] if the driver will not be able to perform or if a
|
|
180
|
+
# documented dependency is missing from the system
|
|
181
|
+
def verify_dependencies; end
|
|
182
|
+
|
|
183
|
+
# Cache directory that a driver could implement to inform the provisioner
|
|
184
|
+
# that it can leverage it internally
|
|
185
|
+
#
|
|
186
|
+
# @return path [String] a path of the cache directory
|
|
187
|
+
def cache_directory; end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
def backcompat_merged_state(state)
|
|
192
|
+
driver_ssh_keys = %w{
|
|
193
|
+
forward_agent hostname password port ssh_key username
|
|
194
|
+
}.map(&:to_sym)
|
|
195
|
+
config.select { |key, _| driver_ssh_keys.include?(key) }.rmerge(state)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Builds arguments for constructing a `Kitchen::SSH` instance.
|
|
199
|
+
#
|
|
200
|
+
# @param state [Hash] state hash
|
|
201
|
+
# @return [Array] SSH constructor arguments
|
|
202
|
+
# @api private
|
|
203
|
+
def build_ssh_args(state)
|
|
204
|
+
combined = config.to_hash.merge(state)
|
|
205
|
+
|
|
206
|
+
opts = {}
|
|
207
|
+
opts[:user_known_hosts_file] = "/dev/null"
|
|
208
|
+
opts[:verify_host_key] = false
|
|
209
|
+
opts[:keys_only] = true if combined[:ssh_key]
|
|
210
|
+
opts[:password] = combined[:password] if combined[:password]
|
|
211
|
+
opts[:forward_agent] = combined[:forward_agent] if combined.key? :forward_agent
|
|
212
|
+
opts[:port] = combined[:port] if combined[:port]
|
|
213
|
+
opts[:keys] = Array(combined[:ssh_key]) if combined[:ssh_key]
|
|
214
|
+
opts[:logger] = logger
|
|
215
|
+
|
|
216
|
+
[combined[:hostname], combined[:username], opts]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Adds http, https and ftp proxy environment variables to a command, if
|
|
220
|
+
# set in configuration data or on local workstation.
|
|
221
|
+
#
|
|
222
|
+
# @param cmd [String] command string
|
|
223
|
+
# @return [String] command string
|
|
224
|
+
# @api private
|
|
225
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
|
226
|
+
def env_cmd(cmd)
|
|
227
|
+
return if cmd.nil?
|
|
228
|
+
|
|
229
|
+
env = "env"
|
|
230
|
+
http_proxy = config[:http_proxy] || ENV["http_proxy"] ||
|
|
231
|
+
ENV["HTTP_PROXY"]
|
|
232
|
+
https_proxy = config[:https_proxy] || ENV["https_proxy"] ||
|
|
233
|
+
ENV["HTTPS_PROXY"]
|
|
234
|
+
ftp_proxy = config[:ftp_proxy] || ENV["ftp_proxy"] ||
|
|
235
|
+
ENV["FTP_PROXY"]
|
|
236
|
+
no_proxy = if (!config[:http_proxy] && http_proxy) ||
|
|
237
|
+
(!config[:https_proxy] && https_proxy) ||
|
|
238
|
+
(!config[:ftp_proxy] && ftp_proxy)
|
|
239
|
+
ENV["no_proxy"] || ENV["NO_PROXY"]
|
|
240
|
+
end
|
|
241
|
+
env << " http_proxy=#{http_proxy}" if http_proxy
|
|
242
|
+
env << " https_proxy=#{https_proxy}" if https_proxy
|
|
243
|
+
env << " ftp_proxy=#{ftp_proxy}" if ftp_proxy
|
|
244
|
+
env << " no_proxy=#{no_proxy}" if no_proxy
|
|
245
|
+
|
|
246
|
+
env == "env" ? cmd : "#{env} #{cmd}"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Executes a remote command over SSH.
|
|
250
|
+
#
|
|
251
|
+
# @param command [String] remove command to run
|
|
252
|
+
# @param connection [Kitchen::SSH] an SSH connection
|
|
253
|
+
# @raise [ActionFailed] if an exception occurs
|
|
254
|
+
# @api private
|
|
255
|
+
def run_remote(command, connection)
|
|
256
|
+
return if command.nil?
|
|
257
|
+
|
|
258
|
+
connection.exec(env_cmd(command))
|
|
259
|
+
rescue SSHFailed, Net::SSH::Exception => ex
|
|
260
|
+
raise ActionFailed, ex.message
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Transfers one or more local paths over SSH.
|
|
264
|
+
#
|
|
265
|
+
# @param locals [Array<String>] array of local paths
|
|
266
|
+
# @param remote [String] remote destination path
|
|
267
|
+
# @param connection [Kitchen::SSH] an SSH connection
|
|
268
|
+
# @raise [ActionFailed] if an exception occurs
|
|
269
|
+
# @api private
|
|
270
|
+
def transfer_path(locals, remote, connection)
|
|
271
|
+
return if locals.nil? || Array(locals).empty?
|
|
272
|
+
|
|
273
|
+
info("Transferring files to #{instance.to_str}")
|
|
274
|
+
debug("TIMING: scp asynch upload (Kitchen::Driver::SSHBase)")
|
|
275
|
+
elapsed = Benchmark.measure do
|
|
276
|
+
transfer_path_async(locals, remote, connection)
|
|
277
|
+
end
|
|
278
|
+
delta = Util.duration(elapsed.real)
|
|
279
|
+
debug("TIMING: scp async upload (Kitchen::Driver::SSHBase) took #{delta}")
|
|
280
|
+
debug("Transfer complete")
|
|
281
|
+
rescue SSHFailed, Net::SSH::Exception => ex
|
|
282
|
+
raise ActionFailed, ex.message
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def transfer_path_async(locals, remote, connection)
|
|
286
|
+
waits = []
|
|
287
|
+
locals.map do |local|
|
|
288
|
+
waits.push connection.upload_path(local, remote)
|
|
289
|
+
waits.shift.wait while waits.length >= config[:max_ssh_sessions]
|
|
290
|
+
end
|
|
291
|
+
waits.each(&:wait)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Blocks until a TCP socket is available where a remote SSH server
|
|
295
|
+
# should be listening.
|
|
296
|
+
#
|
|
297
|
+
# @param hostname [String] remote SSH server host
|
|
298
|
+
# @param username [String] SSH username (default: `nil`)
|
|
299
|
+
# @param options [Hash] configuration hash (default: `{}`)
|
|
300
|
+
# @api private
|
|
301
|
+
def wait_for_sshd(hostname, username = nil, options = {})
|
|
302
|
+
pseudo_state = { hostname: hostname }
|
|
303
|
+
pseudo_state[:username] = username if username
|
|
304
|
+
pseudo_state.merge!(options)
|
|
305
|
+
|
|
306
|
+
instance.transport.connection(backcompat_merged_state(pseudo_state))
|
|
307
|
+
.wait_until_ready
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Intercepts any bare #puts calls in subclasses and issues an INFO log
|
|
311
|
+
# event instead.
|
|
312
|
+
#
|
|
313
|
+
# @param msg [String] message string
|
|
314
|
+
def puts(msg)
|
|
315
|
+
info(msg)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Intercepts any bare #print calls in subclasses and issues an INFO log
|
|
319
|
+
# event instead.
|
|
320
|
+
#
|
|
321
|
+
# @param msg [String] message string
|
|
322
|
+
def print(msg)
|
|
323
|
+
info(msg)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Delegates to Kitchen::ShellOut.run_command, overriding some default
|
|
327
|
+
# options:
|
|
328
|
+
#
|
|
329
|
+
# * `:use_sudo` defaults to the value of `config[:use_sudo]` in the
|
|
330
|
+
# Driver object
|
|
331
|
+
# * `:log_subject` defaults to a String representation of the Driver's
|
|
332
|
+
# class name
|
|
333
|
+
#
|
|
334
|
+
# @see ShellOut#run_command
|
|
335
|
+
def run_command(cmd, options = {})
|
|
336
|
+
base_options = {
|
|
337
|
+
use_sudo: config[:use_sudo],
|
|
338
|
+
log_subject: Thor::Util.snake_case(self.class.to_s),
|
|
339
|
+
}.merge(options)
|
|
340
|
+
super(cmd, base_options)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Returns the Busser object associated with the driver.
|
|
344
|
+
#
|
|
345
|
+
# @return [Busser] a busser
|
|
346
|
+
def busser
|
|
347
|
+
instance.verifier
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2012, Fletcher Nichol
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
require_relative "plugin"
|
|
19
|
+
|
|
20
|
+
module Kitchen
|
|
21
|
+
# A driver is responsible for carrying out the lifecycle activities of an
|
|
22
|
+
# instance, such as creating and destroying an instance.
|
|
23
|
+
#
|
|
24
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
25
|
+
module Driver
|
|
26
|
+
# Default driver plugin to use
|
|
27
|
+
DEFAULT_PLUGIN = "dummy".freeze
|
|
28
|
+
|
|
29
|
+
# Returns an instance of a driver given a plugin type string.
|
|
30
|
+
#
|
|
31
|
+
# @param plugin [String] a driver plugin type, which will be constantized
|
|
32
|
+
# @param config [Hash] a configuration hash to initialize the driver
|
|
33
|
+
# @return [Driver::Base] a driver instance
|
|
34
|
+
# @raise [ClientError] if a driver instance could not be created
|
|
35
|
+
# @raise [UserError] if the driver's dependencies could not be met
|
|
36
|
+
def self.for_plugin(plugin, config)
|
|
37
|
+
Kitchen::Plugin.load(self, plugin, config)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2013, Fletcher Nichol
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
require "English"
|
|
19
|
+
|
|
20
|
+
module Kitchen
|
|
21
|
+
# All Kitchen errors and exceptions.
|
|
22
|
+
#
|
|
23
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
24
|
+
module Error
|
|
25
|
+
# Creates an array of strings, representing a formatted exception,
|
|
26
|
+
# containing backtrace and nested exception info as necessary, that can
|
|
27
|
+
# be viewed by a human.
|
|
28
|
+
#
|
|
29
|
+
# For example:
|
|
30
|
+
#
|
|
31
|
+
# ------Exception-------
|
|
32
|
+
# Class: Kitchen::StandardError
|
|
33
|
+
# Message: Failure starting the party
|
|
34
|
+
# ---Nested Exception---
|
|
35
|
+
# Class: IOError
|
|
36
|
+
# Message: not enough directories for a party
|
|
37
|
+
# ------Backtrace-------
|
|
38
|
+
# nil
|
|
39
|
+
# ----------------------
|
|
40
|
+
#
|
|
41
|
+
# @param exception [::StandardError] an exception
|
|
42
|
+
# @return [Array<String>] a formatted message
|
|
43
|
+
def self.formatted_trace(exception, title = "Exception")
|
|
44
|
+
arr = formatted_exception(exception, title).dup
|
|
45
|
+
arr += formatted_backtrace(exception)
|
|
46
|
+
|
|
47
|
+
if exception.respond_to?(:original) && exception.original
|
|
48
|
+
arr += if exception.original.is_a? Array
|
|
49
|
+
exception.original.map do |composite_exception|
|
|
50
|
+
formatted_trace(composite_exception, "Composite Exception").flatten
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
[
|
|
54
|
+
formatted_exception(exception.original, "Nested Exception"),
|
|
55
|
+
formatted_backtrace(exception),
|
|
56
|
+
].flatten
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
arr.flatten
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.formatted_backtrace(exception)
|
|
63
|
+
if exception.backtrace.nil?
|
|
64
|
+
[]
|
|
65
|
+
else
|
|
66
|
+
[
|
|
67
|
+
"Backtrace".center(22, "-"),
|
|
68
|
+
exception.backtrace,
|
|
69
|
+
"End Backtrace".center(22, "-"),
|
|
70
|
+
]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Creates an array of strings, representing a formatted exception that
|
|
75
|
+
# can be viewed by a human. Thanks to MiniTest for the inspiration
|
|
76
|
+
# upon which this output has been designed.
|
|
77
|
+
#
|
|
78
|
+
# For example:
|
|
79
|
+
#
|
|
80
|
+
# ------Exception-------
|
|
81
|
+
# Class: Kitchen::StandardError
|
|
82
|
+
# Message: I have failed you
|
|
83
|
+
# ----------------------
|
|
84
|
+
#
|
|
85
|
+
# @param exception [::StandardError] an exception
|
|
86
|
+
# @param title [String] a custom title for the message
|
|
87
|
+
# (default: `"Exception"`)
|
|
88
|
+
# @return [Array<String>] a formatted message
|
|
89
|
+
def self.formatted_exception(exception, title = "Exception")
|
|
90
|
+
[
|
|
91
|
+
title.center(22, "-"),
|
|
92
|
+
"Class: #{exception.class}",
|
|
93
|
+
"Message: #{exception.message}",
|
|
94
|
+
"".center(22, "-"),
|
|
95
|
+
]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Base exception class from which all Kitchen exceptions derive. This class
|
|
100
|
+
# nests an exception when this class is re-raised from a rescue block.
|
|
101
|
+
class StandardError < ::StandardError
|
|
102
|
+
include Error
|
|
103
|
+
|
|
104
|
+
# @return [::StandardError] the original (wrapped) exception
|
|
105
|
+
attr_reader :original
|
|
106
|
+
|
|
107
|
+
# Creates a new StandardError exception which optionally wraps an original
|
|
108
|
+
# exception if given or detected by checking the `$!` global variable.
|
|
109
|
+
#
|
|
110
|
+
# @param msg [String] exception message
|
|
111
|
+
# @param original [::StandardError] an original exception which will be
|
|
112
|
+
# wrapped (default: `$ERROR_INFO`)
|
|
113
|
+
def initialize(msg, original = $ERROR_INFO)
|
|
114
|
+
super(msg)
|
|
115
|
+
@original = original
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Base exception class for all exceptions that are caused by user input
|
|
120
|
+
# errors.
|
|
121
|
+
class UserError < StandardError; end
|
|
122
|
+
|
|
123
|
+
# Base exception class for all exceptions that are caused by incorrect use
|
|
124
|
+
# of an API.
|
|
125
|
+
class ClientError < StandardError; end
|
|
126
|
+
|
|
127
|
+
# Base exception class for exceptions that are caused by external library
|
|
128
|
+
# failures which may be temporary.
|
|
129
|
+
class TransientFailure < StandardError; end
|
|
130
|
+
|
|
131
|
+
# Exception class for any exceptions raised when performing an instance
|
|
132
|
+
# action.
|
|
133
|
+
class ActionFailed < TransientFailure; end
|
|
134
|
+
|
|
135
|
+
# Exception class capturing what caused an instance to die.
|
|
136
|
+
class InstanceFailure < TransientFailure; end
|
|
137
|
+
|
|
138
|
+
# Yields to a code block in order to consistently emit a useful crash/error
|
|
139
|
+
# message and exit appropriately. There are two primary failure conditions:
|
|
140
|
+
# an expected instance failure, and any other unexpected failures.
|
|
141
|
+
#
|
|
142
|
+
# **Note** This method may call `Kernel.exit` so may not return if the
|
|
143
|
+
# yielded code block raises an exception.
|
|
144
|
+
#
|
|
145
|
+
# ## Instance Failure
|
|
146
|
+
#
|
|
147
|
+
# This is an expected failure scenario which could happen if an instance
|
|
148
|
+
# couldn't be created, a Chef run didn't successfully converge, a
|
|
149
|
+
# post-convergence test suite failed, etc. In other words, you can count on
|
|
150
|
+
# encountering these failures all the time--this is Kitchen's worldview:
|
|
151
|
+
# crash early and often. In this case a cleanly formatted exception is
|
|
152
|
+
# written to `STDERR` and the exception message is written to
|
|
153
|
+
# the common Kitchen file logger.
|
|
154
|
+
#
|
|
155
|
+
# ## Unexpected Failure
|
|
156
|
+
#
|
|
157
|
+
# All other forms of `Kitchen::Error` exceptions are considered unexpected
|
|
158
|
+
# or unplanned exceptions, typically from user configuration errors, driver
|
|
159
|
+
# or provisioner coding issues or bugs, or internal code issues. Given
|
|
160
|
+
# a stable release of Kitchen and a solid set of drivers and provisioners,
|
|
161
|
+
# the most likely cause of this is user configuration error originating in
|
|
162
|
+
# the `.kitchen.yml` setup. For this reason, the exception is written to
|
|
163
|
+
# `STDERR`, a full formatted exception trace is written to the common
|
|
164
|
+
# Kitchen file logger, and a message is displayed on `STDERR` to the user
|
|
165
|
+
# informing them to check the log files and check their configuration with
|
|
166
|
+
# the `kitchen diagnose` subcommand.
|
|
167
|
+
#
|
|
168
|
+
# @raise [SystemExit] if an exception is raised in the yielded block
|
|
169
|
+
def self.with_friendly_errors
|
|
170
|
+
yield
|
|
171
|
+
rescue Kitchen::InstanceFailure => e
|
|
172
|
+
Kitchen.mutex.synchronize do
|
|
173
|
+
handle_instance_failure(e)
|
|
174
|
+
end
|
|
175
|
+
exit 10
|
|
176
|
+
rescue Kitchen::Error => e
|
|
177
|
+
Kitchen.mutex.synchronize do
|
|
178
|
+
handle_error(e)
|
|
179
|
+
end
|
|
180
|
+
exit 20
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Writes an array of lines to the common Kitchen logger's file device at the
|
|
184
|
+
# given severity level. If the Kitchen logger is set to debug severity, then
|
|
185
|
+
# the array of lines will also be written to the console output.
|
|
186
|
+
#
|
|
187
|
+
# @param level [Symbol,String] the desired log level
|
|
188
|
+
# @param lines [Array<String>] an array of strings to log
|
|
189
|
+
# @api private
|
|
190
|
+
def self.file_log(level, lines)
|
|
191
|
+
Array(lines).each do |line|
|
|
192
|
+
if Kitchen.logger.debug?
|
|
193
|
+
Kitchen.logger.debug(line)
|
|
194
|
+
else
|
|
195
|
+
Kitchen.logger.logdev && Kitchen.logger.logdev.public_send(level, line)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Writes an array of lines to the `STDERR` device.
|
|
201
|
+
#
|
|
202
|
+
# @param lines [Array<String>] an array of strings to log
|
|
203
|
+
# @api private
|
|
204
|
+
def self.stderr_log(lines)
|
|
205
|
+
Array(lines).map { |line| ">>>>>> #{line}" }.each do |line|
|
|
206
|
+
line = Color.colorize(line, :red) if Kitchen.tty?
|
|
207
|
+
$stderr.puts(line)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Writes an array of lines to the common Kitchen debugger with debug
|
|
212
|
+
# severity.
|
|
213
|
+
#
|
|
214
|
+
# @param lines [Array<String>] an array of strings to log
|
|
215
|
+
# @api private
|
|
216
|
+
def self.debug_log(lines)
|
|
217
|
+
Array(lines).each { |line| Kitchen.logger.debug(line) }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Handles an instance failure exception.
|
|
221
|
+
#
|
|
222
|
+
# @param e [StandardError] an exception to handle
|
|
223
|
+
# @see Kitchen.with_friendly_errors
|
|
224
|
+
# @api private
|
|
225
|
+
def self.handle_instance_failure(e)
|
|
226
|
+
stderr_log(e.message.split(/\s{2,}/))
|
|
227
|
+
stderr_log(Error.formatted_exception(e.original))
|
|
228
|
+
file_log(:error, e.message.split(/\s{2,}/).first)
|
|
229
|
+
debug_log(Error.formatted_trace(e))
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Handles an unexpected failure exception.
|
|
233
|
+
#
|
|
234
|
+
# @param e [StandardError] an exception to handle
|
|
235
|
+
# @see Kitchen.with_friendly_errors
|
|
236
|
+
# @api private
|
|
237
|
+
def self.handle_error(e)
|
|
238
|
+
stderr_log(Error.formatted_exception(e))
|
|
239
|
+
stderr_log("Please see .kitchen/logs/kitchen.log for more details")
|
|
240
|
+
stderr_log("Also try running `kitchen diagnose --all` for configuration\n")
|
|
241
|
+
file_log(:error, Error.formatted_trace(e))
|
|
242
|
+
end
|
|
243
|
+
end
|