test-kitchen-rsync 3.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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