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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +21 -0
  3. data/LICENSE +15 -0
  4. data/Rakefile +53 -0
  5. data/bin/zl-kitchen +11 -0
  6. data/lib/kitchen/base64_stream.rb +48 -0
  7. data/lib/kitchen/chef_utils_wiring.rb +40 -0
  8. data/lib/kitchen/cli.rb +413 -0
  9. data/lib/kitchen/collection.rb +52 -0
  10. data/lib/kitchen/color.rb +63 -0
  11. data/lib/kitchen/command/action.rb +41 -0
  12. data/lib/kitchen/command/console.rb +54 -0
  13. data/lib/kitchen/command/diagnose.rb +84 -0
  14. data/lib/kitchen/command/doctor.rb +39 -0
  15. data/lib/kitchen/command/exec.rb +37 -0
  16. data/lib/kitchen/command/list.rb +148 -0
  17. data/lib/kitchen/command/login.rb +39 -0
  18. data/lib/kitchen/command/package.rb +32 -0
  19. data/lib/kitchen/command/sink.rb +50 -0
  20. data/lib/kitchen/command/test.rb +47 -0
  21. data/lib/kitchen/command.rb +207 -0
  22. data/lib/kitchen/config.rb +344 -0
  23. data/lib/kitchen/configurable.rb +616 -0
  24. data/lib/kitchen/data_munger.rb +1024 -0
  25. data/lib/kitchen/diagnostic.rb +138 -0
  26. data/lib/kitchen/driver/base.rb +133 -0
  27. data/lib/kitchen/driver/dummy.rb +105 -0
  28. data/lib/kitchen/driver/exec.rb +70 -0
  29. data/lib/kitchen/driver/proxy.rb +70 -0
  30. data/lib/kitchen/driver/ssh_base.rb +351 -0
  31. data/lib/kitchen/driver.rb +40 -0
  32. data/lib/kitchen/errors.rb +243 -0
  33. data/lib/kitchen/generator/init.rb +254 -0
  34. data/lib/kitchen/instance.rb +726 -0
  35. data/lib/kitchen/lazy_hash.rb +148 -0
  36. data/lib/kitchen/lifecycle_hook/base.rb +78 -0
  37. data/lib/kitchen/lifecycle_hook/local.rb +53 -0
  38. data/lib/kitchen/lifecycle_hook/remote.rb +39 -0
  39. data/lib/kitchen/lifecycle_hooks.rb +92 -0
  40. data/lib/kitchen/loader/yaml.rb +377 -0
  41. data/lib/kitchen/logger.rb +422 -0
  42. data/lib/kitchen/logging.rb +52 -0
  43. data/lib/kitchen/login_command.rb +49 -0
  44. data/lib/kitchen/metadata_chopper.rb +49 -0
  45. data/lib/kitchen/platform.rb +64 -0
  46. data/lib/kitchen/plugin.rb +76 -0
  47. data/lib/kitchen/plugin_base.rb +60 -0
  48. data/lib/kitchen/provisioner/base.rb +269 -0
  49. data/lib/kitchen/provisioner/chef/berkshelf.rb +116 -0
  50. data/lib/kitchen/provisioner/chef/common_sandbox.rb +350 -0
  51. data/lib/kitchen/provisioner/chef/policyfile.rb +163 -0
  52. data/lib/kitchen/provisioner/chef_apply.rb +121 -0
  53. data/lib/kitchen/provisioner/chef_base.rb +705 -0
  54. data/lib/kitchen/provisioner/chef_infra.rb +167 -0
  55. data/lib/kitchen/provisioner/chef_solo.rb +82 -0
  56. data/lib/kitchen/provisioner/chef_zero.rb +12 -0
  57. data/lib/kitchen/provisioner/dummy.rb +75 -0
  58. data/lib/kitchen/provisioner/shell.rb +157 -0
  59. data/lib/kitchen/provisioner.rb +42 -0
  60. data/lib/kitchen/rake_tasks.rb +80 -0
  61. data/lib/kitchen/shell_out.rb +90 -0
  62. data/lib/kitchen/ssh.rb +289 -0
  63. data/lib/kitchen/state_file.rb +112 -0
  64. data/lib/kitchen/suite.rb +48 -0
  65. data/lib/kitchen/thor_tasks.rb +63 -0
  66. data/lib/kitchen/transport/base.rb +236 -0
  67. data/lib/kitchen/transport/dummy.rb +78 -0
  68. data/lib/kitchen/transport/exec.rb +145 -0
  69. data/lib/kitchen/transport/ssh.rb +579 -0
  70. data/lib/kitchen/transport/winrm.rb +546 -0
  71. data/lib/kitchen/transport.rb +40 -0
  72. data/lib/kitchen/util.rb +229 -0
  73. data/lib/kitchen/verifier/base.rb +243 -0
  74. data/lib/kitchen/verifier/busser.rb +275 -0
  75. data/lib/kitchen/verifier/dummy.rb +75 -0
  76. data/lib/kitchen/verifier/shell.rb +99 -0
  77. data/lib/kitchen/verifier.rb +39 -0
  78. data/lib/kitchen/version.rb +20 -0
  79. data/lib/kitchen/which.rb +26 -0
  80. data/lib/kitchen.rb +152 -0
  81. data/lib/vendor/hash_recursive_merge.rb +79 -0
  82. data/support/busser_install_command.ps1 +14 -0
  83. data/support/busser_install_command.sh +21 -0
  84. data/support/chef-client-fail-if-update-handler.rb +15 -0
  85. data/support/chef_base_init_command.ps1 +18 -0
  86. data/support/chef_base_init_command.sh +1 -0
  87. data/support/chef_base_install_command.ps1 +85 -0
  88. data/support/chef_base_install_command.sh +229 -0
  89. data/support/download_helpers.sh +109 -0
  90. data/support/dummy-validation.pem +27 -0
  91. data/templates/driver/CHANGELOG.md.erb +3 -0
  92. data/templates/driver/Gemfile.erb +3 -0
  93. data/templates/driver/README.md.erb +64 -0
  94. data/templates/driver/Rakefile.erb +21 -0
  95. data/templates/driver/driver.rb.erb +23 -0
  96. data/templates/driver/gemspec.erb +29 -0
  97. data/templates/driver/gitignore.erb +17 -0
  98. data/templates/driver/license_apachev2.erb +15 -0
  99. data/templates/driver/license_lgplv3.erb +16 -0
  100. data/templates/driver/license_mit.erb +22 -0
  101. data/templates/driver/license_reserved.erb +5 -0
  102. data/templates/driver/tailor.erb +4 -0
  103. data/templates/driver/travis.yml.erb +11 -0
  104. data/templates/driver/version.rb.erb +12 -0
  105. data/templates/init/chefignore.erb +2 -0
  106. data/templates/init/kitchen.yml.erb +18 -0
  107. data/test-kitchen.gemspec +52 -0
  108. 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