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,579 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2014, 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 "../../kitchen"
|
|
19
|
+
|
|
20
|
+
require "fileutils" unless defined?(FileUtils)
|
|
21
|
+
require "net/ssh" unless defined?(Net::SSH)
|
|
22
|
+
require "net/ssh/gateway"
|
|
23
|
+
require "net/ssh/proxy/http"
|
|
24
|
+
require "net/scp"
|
|
25
|
+
require "timeout" unless defined?(Timeout)
|
|
26
|
+
require "benchmark" unless defined?(Benchmark)
|
|
27
|
+
|
|
28
|
+
module Kitchen
|
|
29
|
+
module Transport
|
|
30
|
+
# Wrapped exception for any internally raised SSH-related errors.
|
|
31
|
+
#
|
|
32
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
33
|
+
class SshFailed < TransportFailed; end
|
|
34
|
+
|
|
35
|
+
# A Transport which uses the SSH protocol to execute commands and transfer
|
|
36
|
+
# files.
|
|
37
|
+
#
|
|
38
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
39
|
+
class Ssh < Kitchen::Transport::Base
|
|
40
|
+
kitchen_transport_api_version 1
|
|
41
|
+
|
|
42
|
+
plugin_version Kitchen::VERSION
|
|
43
|
+
|
|
44
|
+
default_config :port, 22
|
|
45
|
+
default_config :username, "root"
|
|
46
|
+
default_config :keepalive, true
|
|
47
|
+
default_config :keepalive_interval, 60
|
|
48
|
+
default_config :keepalive_maxcount, 3
|
|
49
|
+
# needs to be one less than the configured sshd_config MaxSessions
|
|
50
|
+
default_config :max_ssh_sessions, 9
|
|
51
|
+
default_config :connection_timeout, 15
|
|
52
|
+
default_config :connection_retries, 5
|
|
53
|
+
default_config :connection_retry_sleep, 1
|
|
54
|
+
default_config :max_wait_until_ready, 600
|
|
55
|
+
|
|
56
|
+
default_config :ssh_gateway, nil
|
|
57
|
+
default_config :ssh_gateway_port, 22
|
|
58
|
+
default_config :ssh_gateway_username, nil
|
|
59
|
+
|
|
60
|
+
default_config :ssh_http_proxy, nil
|
|
61
|
+
default_config :ssh_http_proxy_port, nil
|
|
62
|
+
default_config :ssh_http_proxy_user, nil
|
|
63
|
+
default_config :ssh_http_proxy_password, nil
|
|
64
|
+
|
|
65
|
+
default_config :ssh_key, nil
|
|
66
|
+
expand_path_for :ssh_key
|
|
67
|
+
|
|
68
|
+
# compression disabled by default for speed
|
|
69
|
+
default_config :compression, false
|
|
70
|
+
required_config :compression
|
|
71
|
+
|
|
72
|
+
default_config :compression_level do |transport|
|
|
73
|
+
transport[:compression] == false ? 0 : 6
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def finalize_config!(instance)
|
|
77
|
+
super
|
|
78
|
+
|
|
79
|
+
# zlib was never a valid value and breaks in net-ssh >= 2.10
|
|
80
|
+
# TODO: remove these backwards compatiable casts in 2.0
|
|
81
|
+
case config[:compression]
|
|
82
|
+
when "zlib"
|
|
83
|
+
config[:compression] = "zlib@openssh.com"
|
|
84
|
+
when "none"
|
|
85
|
+
config[:compression] = false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# (see Base#connection)
|
|
92
|
+
def connection(state, &block)
|
|
93
|
+
options = connection_options(config.to_hash.merge(state))
|
|
94
|
+
|
|
95
|
+
if @connection && @connection_options == options
|
|
96
|
+
reuse_connection(&block)
|
|
97
|
+
else
|
|
98
|
+
create_new_connection(options, &block)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# (see Base#cleanup!)
|
|
103
|
+
def cleanup!
|
|
104
|
+
if @connection
|
|
105
|
+
logger.debug("[SSH] shutting previous connection #{@connection}")
|
|
106
|
+
@connection.close
|
|
107
|
+
@connection = @connection_options = nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# A Connection instance can be generated and re-generated, given new
|
|
112
|
+
# connection details such as connection port, hostname, credentials, etc.
|
|
113
|
+
# This object is responsible for carrying out the actions on the remote
|
|
114
|
+
# host such as executing commands, transferring files, etc.
|
|
115
|
+
#
|
|
116
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
117
|
+
class Connection < Kitchen::Transport::Base::Connection
|
|
118
|
+
# (see Base::Connection#initialize)
|
|
119
|
+
def initialize(config = {})
|
|
120
|
+
super(config)
|
|
121
|
+
@session = nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# (see Base::Connection#close)
|
|
125
|
+
def close
|
|
126
|
+
return if @session.nil?
|
|
127
|
+
|
|
128
|
+
logger.debug("[SSH] closing connection to #{self}")
|
|
129
|
+
session.close
|
|
130
|
+
ensure
|
|
131
|
+
@session = nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# (see Base::Connection#execute)
|
|
135
|
+
def execute(command)
|
|
136
|
+
return if command.nil?
|
|
137
|
+
|
|
138
|
+
logger.debug("[SSH] #{self} (#{command})")
|
|
139
|
+
exit_code = execute_with_exit_code(command)
|
|
140
|
+
|
|
141
|
+
if exit_code != 0
|
|
142
|
+
raise Transport::SshFailed.new(
|
|
143
|
+
"SSH exited (#{exit_code}) for command: [#{command}]",
|
|
144
|
+
exit_code
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
rescue Net::SSH::Exception => ex
|
|
148
|
+
raise SshFailed, "SSH command failed (#{ex.message})"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# (see Base::Connection#login_command)
|
|
152
|
+
def login_command
|
|
153
|
+
args = %w{ -o UserKnownHostsFile=/dev/null }
|
|
154
|
+
args += %w{ -o StrictHostKeyChecking=no }
|
|
155
|
+
args += %w{ -o IdentitiesOnly=yes } if options[:keys]
|
|
156
|
+
args += %W{ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} }
|
|
157
|
+
if options.key?(:forward_agent)
|
|
158
|
+
args += %W{ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} }
|
|
159
|
+
end
|
|
160
|
+
if ssh_gateway
|
|
161
|
+
gateway_command = "ssh -q #{ssh_gateway_username}@#{ssh_gateway} nc #{hostname} #{port}"
|
|
162
|
+
args += %W{ -o ProxyCommand=#{gateway_command} -p #{ssh_gateway_port} }
|
|
163
|
+
end
|
|
164
|
+
Array(options[:keys]).each { |ssh_key| args += %W{ -i #{ssh_key} } }
|
|
165
|
+
args += %W{ -p #{port} }
|
|
166
|
+
args += %W{ #{username}@#{hostname} }
|
|
167
|
+
|
|
168
|
+
LoginCommand.new("ssh", args)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# (see Base::Connection#upload)
|
|
172
|
+
def upload(locals, remote)
|
|
173
|
+
logger.debug("TIMING: scp async upload (Kitchen::Transport::Ssh)")
|
|
174
|
+
elapsed = Benchmark.measure do
|
|
175
|
+
waits = []
|
|
176
|
+
Array(locals).map do |local|
|
|
177
|
+
opts = File.directory?(local) ? { recursive: true } : {}
|
|
178
|
+
|
|
179
|
+
waits.push session.scp.upload(local, remote, opts) do |_ch, name, sent, total|
|
|
180
|
+
logger.debug("Async Uploaded #{name} (#{total} bytes)") if sent == total
|
|
181
|
+
end
|
|
182
|
+
waits.shift.wait while waits.length >= max_ssh_sessions
|
|
183
|
+
end
|
|
184
|
+
waits.each(&:wait)
|
|
185
|
+
end
|
|
186
|
+
delta = Util.duration(elapsed.real)
|
|
187
|
+
logger.debug("TIMING: scp async upload (Kitchen::Transport::Ssh) took #{delta}")
|
|
188
|
+
rescue Net::SSH::Exception => ex
|
|
189
|
+
raise SshFailed, "SCP upload failed (#{ex.message})"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# (see Base::Connection#download)
|
|
193
|
+
def download(remotes, local)
|
|
194
|
+
# ensure the parent dir of the local target exists
|
|
195
|
+
FileUtils.mkdir_p(File.dirname(local))
|
|
196
|
+
|
|
197
|
+
Array(remotes).each do |file|
|
|
198
|
+
logger.debug("Attempting to download '#{file}' as file")
|
|
199
|
+
session.scp.download!(file, local)
|
|
200
|
+
rescue Net::SCP::Error
|
|
201
|
+
begin
|
|
202
|
+
logger.debug("Attempting to download '#{file}' as directory")
|
|
203
|
+
session.scp.download!(file, local, recursive: true)
|
|
204
|
+
rescue Net::SCP::Error
|
|
205
|
+
logger.warn(
|
|
206
|
+
"SCP download failed for file or directory '#{file}', perhaps it does not exist?"
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
rescue Net::SSH::Exception => ex
|
|
211
|
+
raise SshFailed, "SCP download failed (#{ex.message})"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# (see Base::Connection#wait_until_ready)
|
|
215
|
+
def wait_until_ready
|
|
216
|
+
delay = 3
|
|
217
|
+
session(
|
|
218
|
+
retries: max_wait_until_ready / delay,
|
|
219
|
+
delay: delay,
|
|
220
|
+
message: "Waiting for SSH service on #{hostname}:#{port}, " \
|
|
221
|
+
"retrying in #{delay} seconds"
|
|
222
|
+
)
|
|
223
|
+
execute(PING_COMMAND.dup)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def rsyn_chef_repo(sandbox_path, root_path)
|
|
227
|
+
ssh_key = options[:keys].first
|
|
228
|
+
user = options[:user]
|
|
229
|
+
port = options[:port]
|
|
230
|
+
cmd = "rsync -av -e 'ssh -o StrictHostKeyChecking=no -p #{port} -i #{ssh_key}' #{sandbox_path}/ #{user}@#{hostname}:#{root_path}"
|
|
231
|
+
puts("rsync cmd is #{cmd}")
|
|
232
|
+
`#{cmd}`
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
private
|
|
237
|
+
|
|
238
|
+
PING_COMMAND = "echo '[SSH] Established'".freeze
|
|
239
|
+
|
|
240
|
+
RESCUE_EXCEPTIONS_ON_ESTABLISH = [
|
|
241
|
+
Errno::EACCES, Errno::EALREADY, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
|
242
|
+
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::EPIPE,
|
|
243
|
+
Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout,
|
|
244
|
+
Net::SSH::Proxy::ConnectError, Timeout::Error
|
|
245
|
+
].freeze
|
|
246
|
+
|
|
247
|
+
# @return [Integer] cap on number of parallel ssh sessions we can use
|
|
248
|
+
# @api private
|
|
249
|
+
attr_reader :max_ssh_sessions
|
|
250
|
+
|
|
251
|
+
# @return [Integer] how many times to retry when failing to execute
|
|
252
|
+
# a command or transfer files
|
|
253
|
+
# @api private
|
|
254
|
+
attr_reader :connection_retries
|
|
255
|
+
|
|
256
|
+
# @return [Float] how many seconds to wait before attempting a retry
|
|
257
|
+
# when failing to execute a command or transfer files
|
|
258
|
+
# @api private
|
|
259
|
+
attr_reader :connection_retry_sleep
|
|
260
|
+
|
|
261
|
+
# @return [String] the hostname or IP address of the remote SSH host
|
|
262
|
+
# @api private
|
|
263
|
+
attr_reader :hostname
|
|
264
|
+
|
|
265
|
+
# @return [Integer] how many times to retry when invoking
|
|
266
|
+
# `#wait_until_ready` before failing
|
|
267
|
+
# @api private
|
|
268
|
+
attr_reader :max_wait_until_ready
|
|
269
|
+
|
|
270
|
+
# @return [String] the username to use when connecting to the remote
|
|
271
|
+
# SSH host
|
|
272
|
+
# @api private
|
|
273
|
+
attr_reader :username
|
|
274
|
+
|
|
275
|
+
# @return [Integer] the TCP port number to use when connecting to the
|
|
276
|
+
# remote SSH host
|
|
277
|
+
# @api private
|
|
278
|
+
attr_reader :port
|
|
279
|
+
|
|
280
|
+
# @return [String] The ssh gateway to use when connecting to the
|
|
281
|
+
# remote SSH host
|
|
282
|
+
# @api private
|
|
283
|
+
attr_reader :ssh_gateway
|
|
284
|
+
|
|
285
|
+
# @return [String] The username to use when using an ssh gateway
|
|
286
|
+
# @api private
|
|
287
|
+
attr_reader :ssh_gateway_username
|
|
288
|
+
|
|
289
|
+
# @return [Integer] The port to use when using an ssh gateway
|
|
290
|
+
# @api private
|
|
291
|
+
attr_reader :ssh_gateway_port
|
|
292
|
+
|
|
293
|
+
# @return [String] The kitchen ssh proxy to use when connecting to the
|
|
294
|
+
# remote SSH host via http proxy
|
|
295
|
+
# @api private
|
|
296
|
+
attr_reader :ssh_http_proxy
|
|
297
|
+
|
|
298
|
+
# @return [Integer] The port to use when using an kitchen ssh proxy
|
|
299
|
+
# remote SSH host via http proxy
|
|
300
|
+
# @api private
|
|
301
|
+
attr_reader :ssh_http_proxy_port
|
|
302
|
+
|
|
303
|
+
# @return [String] The username to use when using an kitchen ssh proxy
|
|
304
|
+
# remote SSH host via http proxy
|
|
305
|
+
# @api private
|
|
306
|
+
attr_reader :ssh_http_proxy_user
|
|
307
|
+
|
|
308
|
+
# @return [String] The password to use when using an kitchen ssh proxy
|
|
309
|
+
# remote SSH host via http proxy
|
|
310
|
+
# @api private
|
|
311
|
+
attr_reader :ssh_http_proxy_password
|
|
312
|
+
|
|
313
|
+
# Establish an SSH session on the remote host using a gateway host.
|
|
314
|
+
#
|
|
315
|
+
# @param opts [Hash] retry options
|
|
316
|
+
# @option opts [Integer] :retries the number of times to retry before
|
|
317
|
+
# failing
|
|
318
|
+
# @option opts [Float] :delay the number of seconds to wait until
|
|
319
|
+
# attempting a retry
|
|
320
|
+
# @option opts [String] :message an optional message to be logged on
|
|
321
|
+
# debug (overriding the default) when a rescuable exception is raised
|
|
322
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
323
|
+
# @api private
|
|
324
|
+
def establish_connection_via_gateway(opts)
|
|
325
|
+
retry_connection(opts) do
|
|
326
|
+
gateway_options = options.merge(port: ssh_gateway_port)
|
|
327
|
+
Net::SSH::Gateway.new(ssh_gateway,
|
|
328
|
+
ssh_gateway_username, gateway_options).ssh(hostname, username, options)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def rsync_private(dirs_to_copy, root_path)
|
|
333
|
+
puts("inside rsync_private with dirs_to_copy => #{dirs_to_copy} and root_path => #{root_path}")
|
|
334
|
+
puts(" via #{ssh_gateway_username}@#{ssh_gateway}:#{ssh_gateway_port}")
|
|
335
|
+
puts("options are #{options}ssh_gateway is #{ssh_gateway} and port is #{ssh_gateway_port}")
|
|
336
|
+
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Establish an SSH session on the remote host.
|
|
340
|
+
#
|
|
341
|
+
# @param opts [Hash] retry options
|
|
342
|
+
# @option opts [Integer] :retries the number of times to retry before
|
|
343
|
+
# failing
|
|
344
|
+
# @option opts [Float] :delay the number of seconds to wait until
|
|
345
|
+
# attempting a retry
|
|
346
|
+
# @option opts [String] :message an optional message to be logged on
|
|
347
|
+
# debug (overriding the default) when a rescuable exception is raised
|
|
348
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
349
|
+
# @api private
|
|
350
|
+
def establish_connection(opts)
|
|
351
|
+
retry_connection(opts) do
|
|
352
|
+
Net::SSH.start(hostname, username, options)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Connect to a host executing passed block and properly handling retries.
|
|
357
|
+
#
|
|
358
|
+
# @param opts [Hash] retry options
|
|
359
|
+
# @option opts [Integer] :retries the number of times to retry before
|
|
360
|
+
# failing
|
|
361
|
+
# @option opts [Float] :delay the number of seconds to wait until
|
|
362
|
+
# attempting a retry
|
|
363
|
+
# @option opts [String] :message an optional message to be logged on
|
|
364
|
+
# debug (overriding the default) when a rescuable exception is raised
|
|
365
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
366
|
+
# @api private
|
|
367
|
+
def retry_connection(opts)
|
|
368
|
+
log_msg = "[SSH] opening connection to #{self}"
|
|
369
|
+
log_msg += " via #{ssh_gateway_username}@#{ssh_gateway}:#{ssh_gateway_port}" if ssh_gateway
|
|
370
|
+
logger.debug(log_msg)
|
|
371
|
+
yield
|
|
372
|
+
rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e
|
|
373
|
+
if (opts[:retries] -= 1) > 0
|
|
374
|
+
message = if opts[:message]
|
|
375
|
+
logger.debug("[SSH] connection failed (#{e.inspect})")
|
|
376
|
+
opts[:message]
|
|
377
|
+
else
|
|
378
|
+
"[SSH] connection failed, retrying in #{opts[:delay]} seconds " \
|
|
379
|
+
"(#{e.inspect})"
|
|
380
|
+
end
|
|
381
|
+
logger.info(message)
|
|
382
|
+
sleep(opts[:delay])
|
|
383
|
+
retry
|
|
384
|
+
else
|
|
385
|
+
logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
|
|
386
|
+
raise SshFailed, "SSH session could not be established"
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Execute a remote command over SSH and return the command's exit code.
|
|
391
|
+
#
|
|
392
|
+
# @param command [String] command string to execute
|
|
393
|
+
# @return [Integer] the exit code of the command
|
|
394
|
+
# @api private
|
|
395
|
+
def execute_with_exit_code(command)
|
|
396
|
+
exit_code = nil
|
|
397
|
+
session.open_channel do |channel|
|
|
398
|
+
channel.request_pty
|
|
399
|
+
|
|
400
|
+
channel.exec(command) do |_ch, _success|
|
|
401
|
+
channel.on_data do |_ch, data|
|
|
402
|
+
logger << data
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
channel.on_extended_data do |_ch, _type, data|
|
|
406
|
+
logger << data
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
channel.on_request("exit-status") do |_ch, data|
|
|
410
|
+
exit_code = data.read_long
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
session.loop
|
|
415
|
+
exit_code
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# (see Base::Connection#init_options)
|
|
419
|
+
def init_options(options)
|
|
420
|
+
super
|
|
421
|
+
@username = @options.delete(:username)
|
|
422
|
+
@hostname = @options.delete(:hostname)
|
|
423
|
+
@port = @options[:port] # don't delete from options
|
|
424
|
+
@connection_retries = @options.delete(:connection_retries)
|
|
425
|
+
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
|
|
426
|
+
@max_ssh_sessions = @options.delete(:max_ssh_sessions)
|
|
427
|
+
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
|
428
|
+
@ssh_gateway = @options.delete(:ssh_gateway)
|
|
429
|
+
@ssh_gateway_username = @options.delete(:ssh_gateway_username)
|
|
430
|
+
@ssh_gateway_port = @options.delete(:ssh_gateway_port)
|
|
431
|
+
@ssh_http_proxy = @options.delete(:ssh_http_proxy)
|
|
432
|
+
@ssh_http_proxy_user = @options.delete(:ssh_http_proxy_user)
|
|
433
|
+
@ssh_http_proxy_password = @options.delete(:ssh_http_proxy_password)
|
|
434
|
+
@ssh_http_proxy_port = @options.delete(:ssh_http_proxy_port)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Returns a connection session, or establishes one when invoked the
|
|
438
|
+
# first time.
|
|
439
|
+
#
|
|
440
|
+
# @param retry_options [Hash] retry options for the initial connection
|
|
441
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
442
|
+
# @api private
|
|
443
|
+
def session(retry_options = {})
|
|
444
|
+
if ssh_gateway
|
|
445
|
+
@session ||= establish_connection_via_gateway({
|
|
446
|
+
retries: connection_retries.to_i,
|
|
447
|
+
delay: connection_retry_sleep.to_i,
|
|
448
|
+
}.merge(retry_options))
|
|
449
|
+
else
|
|
450
|
+
@session ||= establish_connection({
|
|
451
|
+
retries: connection_retries.to_i,
|
|
452
|
+
delay: connection_retry_sleep.to_i,
|
|
453
|
+
}.merge(retry_options))
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# String representation of object, reporting its connection details and
|
|
458
|
+
# configuration.
|
|
459
|
+
#
|
|
460
|
+
# @api private
|
|
461
|
+
def to_s
|
|
462
|
+
"#{username}@#{hostname}<#{options.inspect}>"
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
private
|
|
467
|
+
|
|
468
|
+
# Builds the hash of options needed by the Connection object on
|
|
469
|
+
# construction.
|
|
470
|
+
#
|
|
471
|
+
# @param data [Hash] merged configuration and mutable state data
|
|
472
|
+
# @return [Hash] hash of connection options
|
|
473
|
+
# @api private
|
|
474
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
475
|
+
def connection_options(data)
|
|
476
|
+
opts = {
|
|
477
|
+
logger: logger,
|
|
478
|
+
user_known_hosts_file: "/dev/null",
|
|
479
|
+
hostname: data[:hostname],
|
|
480
|
+
port: data[:port],
|
|
481
|
+
username: data[:username],
|
|
482
|
+
compression: data[:compression],
|
|
483
|
+
compression_level: data[:compression_level],
|
|
484
|
+
keepalive: data[:keepalive],
|
|
485
|
+
keepalive_interval: data[:keepalive_interval],
|
|
486
|
+
keepalive_maxcount: data[:keepalive_maxcount],
|
|
487
|
+
timeout: data[:connection_timeout],
|
|
488
|
+
connection_retries: data[:connection_retries],
|
|
489
|
+
connection_retry_sleep: data[:connection_retry_sleep],
|
|
490
|
+
max_ssh_sessions: data[:max_ssh_sessions],
|
|
491
|
+
max_wait_until_ready: data[:max_wait_until_ready],
|
|
492
|
+
ssh_gateway: data[:ssh_gateway],
|
|
493
|
+
ssh_gateway_username: data[:ssh_gateway_username],
|
|
494
|
+
ssh_gateway_port: data[:ssh_gateway_port],
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if data[:ssh_key] && !data[:password]
|
|
498
|
+
opts[:keys_only] = true
|
|
499
|
+
opts[:keys] = Array(data[:ssh_key])
|
|
500
|
+
opts[:auth_methods] = ["publickey"]
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
if data[:ssh_http_proxy]
|
|
504
|
+
options_http_proxy = {}
|
|
505
|
+
options_http_proxy[:user] = data[:ssh_http_proxy_user]
|
|
506
|
+
options_http_proxy[:password] = data[:ssh_http_proxy_password]
|
|
507
|
+
opts[:proxy] = Net::SSH::Proxy::HTTP.new(data[:ssh_http_proxy], data[:ssh_http_proxy_port], options_http_proxy)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
if data[:ssh_key_only]
|
|
511
|
+
opts[:auth_methods] = ["publickey"]
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
opts[:password] = data[:password] if data.key?(:password)
|
|
515
|
+
opts[:forward_agent] = data[:forward_agent] if data.key?(:forward_agent)
|
|
516
|
+
opts[:verbose] = data[:verbose].to_sym if data.key?(:verbose)
|
|
517
|
+
|
|
518
|
+
# disable host key verification. The hash key and value to use
|
|
519
|
+
# depend on the version of net-ssh in use
|
|
520
|
+
opts[verify_host_key_option] = verify_host_key_value
|
|
521
|
+
|
|
522
|
+
opts
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
#
|
|
526
|
+
# Returns the correct host-key-verification option key to use depending
|
|
527
|
+
# on what version of net-ssh is in use. In net-ssh <= 4.1, the supported
|
|
528
|
+
# parameter is `paranoid` but in 4.2, it became `verify_host_key`
|
|
529
|
+
#
|
|
530
|
+
# `verify_host_key` does not work in <= 4.1, and `paranoid` throws
|
|
531
|
+
# deprecation warnings in >= 4.2.
|
|
532
|
+
#
|
|
533
|
+
# While the "right thing" to do would be to pin train's dependency on
|
|
534
|
+
# net-ssh to ~> 4.2, this will prevent InSpec from being used in
|
|
535
|
+
# Chef v12 because of it pinning to a v3 of net-ssh.
|
|
536
|
+
#
|
|
537
|
+
def verify_host_key_option
|
|
538
|
+
current_net_ssh = Net::SSH::Version::CURRENT
|
|
539
|
+
new_option_version = Net::SSH::Version[4, 2, 0]
|
|
540
|
+
|
|
541
|
+
current_net_ssh >= new_option_version ? :verify_host_key : :paranoid
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
#
|
|
545
|
+
# Returns the correct host-key-verification option value to use depending
|
|
546
|
+
# on what version of net-ssh is in use. In net-ssh <= 5, the supported
|
|
547
|
+
# parameter is false but in 5.0, it became `:never`
|
|
548
|
+
#
|
|
549
|
+
def verify_host_key_value
|
|
550
|
+
current_net_ssh = Net::SSH::Version::CURRENT
|
|
551
|
+
new_option_version = Net::SSH::Version[5, 0, 0]
|
|
552
|
+
|
|
553
|
+
current_net_ssh >= new_option_version ? :never : false
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Creates a new SSH Connection instance and save it for potential future
|
|
557
|
+
# reuse.
|
|
558
|
+
#
|
|
559
|
+
# @param options [Hash] conneciton options
|
|
560
|
+
# @return [Ssh::Connection] an SSH Connection instance
|
|
561
|
+
# @api private
|
|
562
|
+
def create_new_connection(options, &block)
|
|
563
|
+
cleanup!
|
|
564
|
+
@connection_options = options
|
|
565
|
+
@connection = Kitchen::Transport::Ssh::Connection.new(options, &block)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# Return the last saved SSH connection instance.
|
|
569
|
+
#
|
|
570
|
+
# @return [Ssh::Connection] an SSH Connection instance
|
|
571
|
+
# @api private
|
|
572
|
+
def reuse_connection
|
|
573
|
+
logger.debug("[SSH] reusing existing connection #{@connection}")
|
|
574
|
+
yield @connection if block_given?
|
|
575
|
+
@connection
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
end
|