test-kitchen 1.7.0 → 1.7.1.dev
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 +4 -4
- data/.cane +8 -8
- data/.gitattributes +3 -0
- data/.github/ISSUE_TEMPLATE.md +55 -55
- data/.gitignore +28 -28
- data/.kitchen.ci.yml +23 -23
- data/.kitchen.proxy.yml +27 -27
- data/.rubocop.yml +3 -3
- data/.travis.yml +70 -70
- data/.yardopts +3 -3
- data/Berksfile +3 -3
- data/CHANGELOG.md +1090 -1083
- data/CONTRIBUTING.md +14 -14
- data/Gemfile +19 -19
- data/Gemfile.proxy_tests +4 -4
- data/Guardfile +42 -42
- data/LICENSE +15 -15
- data/MAINTAINERS.md +23 -23
- data/README.md +135 -135
- data/Rakefile +61 -61
- data/appveyor.yml +44 -44
- data/features/kitchen_action_commands.feature +164 -164
- data/features/kitchen_command.feature +16 -16
- data/features/kitchen_console_command.feature +34 -34
- data/features/kitchen_defaults.feature +38 -38
- data/features/kitchen_diagnose_command.feature +96 -96
- data/features/kitchen_driver_create_command.feature +64 -64
- data/features/kitchen_driver_discover_command.feature +25 -25
- data/features/kitchen_help_command.feature +16 -16
- data/features/kitchen_init_command.feature +274 -274
- data/features/kitchen_list_command.feature +104 -104
- data/features/kitchen_login_command.feature +62 -62
- data/features/kitchen_sink_command.feature +30 -30
- data/features/kitchen_test_command.feature +88 -88
- data/features/step_definitions/gem_steps.rb +36 -36
- data/features/step_definitions/git_steps.rb +5 -5
- data/features/step_definitions/output_steps.rb +5 -5
- data/features/support/env.rb +75 -75
- data/lib/kitchen.rb +150 -150
- data/lib/kitchen/base64_stream.rb +55 -55
- data/lib/kitchen/cli.rb +419 -419
- data/lib/kitchen/collection.rb +55 -55
- data/lib/kitchen/color.rb +65 -65
- data/lib/kitchen/command.rb +185 -185
- data/lib/kitchen/command/action.rb +45 -45
- data/lib/kitchen/command/console.rb +58 -58
- data/lib/kitchen/command/diagnose.rb +92 -92
- data/lib/kitchen/command/driver_discover.rb +105 -105
- data/lib/kitchen/command/exec.rb +41 -41
- data/lib/kitchen/command/list.rb +119 -119
- data/lib/kitchen/command/login.rb +43 -43
- data/lib/kitchen/command/sink.rb +54 -54
- data/lib/kitchen/command/test.rb +51 -51
- data/lib/kitchen/config.rb +322 -322
- data/lib/kitchen/configurable.rb +529 -529
- data/lib/kitchen/data_munger.rb +959 -959
- data/lib/kitchen/diagnostic.rb +141 -141
- data/lib/kitchen/driver.rb +56 -56
- data/lib/kitchen/driver/base.rb +134 -134
- data/lib/kitchen/driver/dummy.rb +108 -108
- data/lib/kitchen/driver/proxy.rb +72 -72
- data/lib/kitchen/driver/ssh_base.rb +357 -357
- data/lib/kitchen/errors.rb +229 -229
- data/lib/kitchen/generator/driver_create.rb +177 -177
- data/lib/kitchen/generator/init.rb +296 -296
- data/lib/kitchen/instance.rb +662 -662
- data/lib/kitchen/lazy_hash.rb +142 -142
- data/lib/kitchen/loader/yaml.rb +349 -349
- data/lib/kitchen/logger.rb +423 -423
- data/lib/kitchen/logging.rb +56 -56
- data/lib/kitchen/login_command.rb +52 -52
- data/lib/kitchen/metadata_chopper.rb +52 -52
- data/lib/kitchen/platform.rb +67 -67
- data/lib/kitchen/provisioner.rb +54 -54
- data/lib/kitchen/provisioner/base.rb +236 -236
- data/lib/kitchen/provisioner/chef/berkshelf.rb +114 -114
- data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -322
- data/lib/kitchen/provisioner/chef/librarian.rb +112 -112
- data/lib/kitchen/provisioner/chef_apply.rb +124 -124
- data/lib/kitchen/provisioner/chef_base.rb +341 -341
- data/lib/kitchen/provisioner/chef_solo.rb +88 -88
- data/lib/kitchen/provisioner/chef_zero.rb +245 -245
- data/lib/kitchen/provisioner/dummy.rb +79 -79
- data/lib/kitchen/provisioner/shell.rb +138 -138
- data/lib/kitchen/rake_tasks.rb +63 -63
- data/lib/kitchen/shell_out.rb +93 -93
- data/lib/kitchen/ssh.rb +276 -276
- data/lib/kitchen/state_file.rb +120 -120
- data/lib/kitchen/suite.rb +51 -51
- data/lib/kitchen/thor_tasks.rb +66 -66
- data/lib/kitchen/transport.rb +54 -54
- data/lib/kitchen/transport/base.rb +176 -176
- data/lib/kitchen/transport/dummy.rb +79 -79
- data/lib/kitchen/transport/ssh.rb +364 -364
- data/lib/kitchen/transport/winrm.rb +486 -486
- data/lib/kitchen/util.rb +147 -147
- data/lib/kitchen/verifier.rb +55 -55
- data/lib/kitchen/verifier/base.rb +235 -235
- data/lib/kitchen/verifier/busser.rb +277 -277
- data/lib/kitchen/verifier/dummy.rb +79 -79
- data/lib/kitchen/verifier/shell.rb +101 -101
- data/lib/kitchen/version.rb +21 -21
- data/lib/vendor/hash_recursive_merge.rb +82 -82
- data/spec/kitchen/base64_stream_spec.rb +77 -77
- data/spec/kitchen/cli_spec.rb +56 -56
- data/spec/kitchen/collection_spec.rb +80 -80
- data/spec/kitchen/color_spec.rb +54 -54
- data/spec/kitchen/config_spec.rb +408 -408
- data/spec/kitchen/configurable_spec.rb +1095 -1095
- data/spec/kitchen/data_munger_spec.rb +2694 -2694
- data/spec/kitchen/diagnostic_spec.rb +129 -129
- data/spec/kitchen/driver/base_spec.rb +121 -121
- data/spec/kitchen/driver/dummy_spec.rb +199 -199
- data/spec/kitchen/driver/proxy_spec.rb +138 -138
- data/spec/kitchen/driver/ssh_base_spec.rb +1115 -1115
- data/spec/kitchen/driver_spec.rb +112 -112
- data/spec/kitchen/errors_spec.rb +309 -309
- data/spec/kitchen/instance_spec.rb +1419 -1419
- data/spec/kitchen/lazy_hash_spec.rb +117 -117
- data/spec/kitchen/loader/yaml_spec.rb +774 -774
- data/spec/kitchen/logger_spec.rb +429 -429
- data/spec/kitchen/logging_spec.rb +59 -59
- data/spec/kitchen/login_command_spec.rb +68 -68
- data/spec/kitchen/metadata_chopper_spec.rb +82 -82
- data/spec/kitchen/platform_spec.rb +89 -89
- data/spec/kitchen/provisioner/base_spec.rb +386 -386
- data/spec/kitchen/provisioner/chef_apply_spec.rb +136 -136
- data/spec/kitchen/provisioner/chef_base_spec.rb +1161 -1161
- data/spec/kitchen/provisioner/chef_solo_spec.rb +557 -557
- data/spec/kitchen/provisioner/chef_zero_spec.rb +1001 -1001
- data/spec/kitchen/provisioner/dummy_spec.rb +99 -99
- data/spec/kitchen/provisioner/shell_spec.rb +566 -566
- data/spec/kitchen/provisioner_spec.rb +107 -107
- data/spec/kitchen/shell_out_spec.rb +150 -150
- data/spec/kitchen/ssh_spec.rb +693 -693
- data/spec/kitchen/state_file_spec.rb +129 -129
- data/spec/kitchen/suite_spec.rb +62 -62
- data/spec/kitchen/transport/base_spec.rb +89 -89
- data/spec/kitchen/transport/ssh_spec.rb +1255 -1255
- data/spec/kitchen/transport/winrm_spec.rb +1143 -1143
- data/spec/kitchen/transport_spec.rb +112 -112
- data/spec/kitchen/util_spec.rb +165 -165
- data/spec/kitchen/verifier/base_spec.rb +362 -362
- data/spec/kitchen/verifier/busser_spec.rb +610 -610
- data/spec/kitchen/verifier/dummy_spec.rb +99 -99
- data/spec/kitchen/verifier/shell_spec.rb +160 -160
- data/spec/kitchen/verifier_spec.rb +120 -120
- data/spec/kitchen_spec.rb +114 -114
- data/spec/spec_helper.rb +85 -85
- data/spec/support/powershell_max_size_spec.rb +40 -40
- data/support/busser_install_command.ps1 +14 -14
- data/support/busser_install_command.sh +14 -14
- data/support/chef-client-zero.rb +77 -77
- data/support/chef_base_init_command.ps1 +18 -18
- data/support/chef_base_init_command.sh +2 -2
- data/support/chef_base_install_command.ps1 +85 -85
- data/support/chef_base_install_command.sh +229 -229
- data/support/chef_zero_prepare_command_legacy.ps1 +9 -9
- data/support/chef_zero_prepare_command_legacy.sh +10 -10
- data/support/download_helpers.sh +109 -109
- data/support/dummy-validation.pem +27 -27
- data/templates/driver/CHANGELOG.md.erb +3 -3
- data/templates/driver/Gemfile.erb +3 -3
- data/templates/driver/README.md.erb +64 -64
- data/templates/driver/Rakefile.erb +21 -21
- data/templates/driver/driver.rb.erb +23 -23
- data/templates/driver/gemspec.erb +29 -29
- data/templates/driver/gitignore.erb +17 -17
- data/templates/driver/license_apachev2.erb +15 -15
- data/templates/driver/license_lgplv3.erb +16 -16
- data/templates/driver/license_mit.erb +22 -22
- data/templates/driver/license_reserved.erb +5 -5
- data/templates/driver/tailor.erb +4 -4
- data/templates/driver/travis.yml.erb +11 -11
- data/templates/driver/version.rb.erb +12 -12
- data/templates/init/chefignore.erb +1 -1
- data/templates/init/kitchen.yml.erb +18 -18
- data/test-kitchen.gemspec +62 -62
- data/test/integration/default/default_spec.rb +3 -3
- data/testing_windows.md +37 -37
- metadata +5 -4
data/lib/kitchen/shell_out.rb
CHANGED
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2012, Fletcher Nichol
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
# you may not use this file except in compliance with the License.
|
|
9
|
-
# You may obtain a copy of the License at
|
|
10
|
-
#
|
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
#
|
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
# See the License for the specific language governing permissions and
|
|
17
|
-
# limitations under the License.
|
|
18
|
-
|
|
19
|
-
require "mixlib/shellout"
|
|
20
|
-
|
|
21
|
-
module Kitchen
|
|
22
|
-
|
|
23
|
-
# Mixin that wraps a command shell out invocation, providing a #run_command
|
|
24
|
-
# method.
|
|
25
|
-
#
|
|
26
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
27
|
-
module ShellOut
|
|
28
|
-
|
|
29
|
-
# Wrapped exception for any interally raised shell out commands.
|
|
30
|
-
class ShellCommandFailed < TransientFailure; end
|
|
31
|
-
|
|
32
|
-
# Executes a command in a subshell on the local running system.
|
|
33
|
-
#
|
|
34
|
-
# @param cmd [String] command to be executed locally
|
|
35
|
-
# @param options [Hash] additional configuration of command
|
|
36
|
-
# @option options [TrueClass, FalseClass] :use_sudo whether or not to use
|
|
37
|
-
# sudo
|
|
38
|
-
# @option options [String] :sudo_command custom sudo command to use.
|
|
39
|
-
# Default is "sudo -E".
|
|
40
|
-
# @option options [String] :log_subject used in the output or log header
|
|
41
|
-
# for clarity and context. Default is "local".
|
|
42
|
-
# @option options [String] :cwd the directory to chdir to before running
|
|
43
|
-
# the command
|
|
44
|
-
# @option options [Hash] :environment a Hash of environment variables to
|
|
45
|
-
# set before the command is run. By default, the environment will
|
|
46
|
-
# *always* be set to `'LC_ALL' => 'C'` to prevent issues with multibyte
|
|
47
|
-
# characters in Ruby 1.8. To avoid this, use :environment => nil for
|
|
48
|
-
# *no* extra environment settings, or
|
|
49
|
-
# `:environment => {'LC_ALL'=>nil, ...}` to set other environment
|
|
50
|
-
# settings without changing the locale.
|
|
51
|
-
# @option options [Integer] :timeout Numeric value for the number of
|
|
52
|
-
# seconds to wait on the child process before raising an Exception.
|
|
53
|
-
# This is calculated as the total amount of time that ShellOut waited on
|
|
54
|
-
# the child process without receiving any output (i.e., IO.select
|
|
55
|
-
# returned nil). Default is 60000 seconds. Note: the stdlib Timeout
|
|
56
|
-
# library is not used.
|
|
57
|
-
# @return [String] the standard output of the command as a String
|
|
58
|
-
# @raise [ShellCommandFailed] if the command fails
|
|
59
|
-
# @raise [Error] for all other unexpected exceptions
|
|
60
|
-
def run_command(cmd, options = {})
|
|
61
|
-
if options.fetch(:use_sudo, false)
|
|
62
|
-
cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}"
|
|
63
|
-
end
|
|
64
|
-
subject = "[#{options.fetch(:log_subject, "local")} command]"
|
|
65
|
-
|
|
66
|
-
debug("#{subject} BEGIN (#{cmd})")
|
|
67
|
-
sh = Mixlib::ShellOut.new(cmd, shell_opts(options))
|
|
68
|
-
sh.run_command
|
|
69
|
-
debug("#{subject} END #{Util.duration(sh.execution_time)}")
|
|
70
|
-
sh.error!
|
|
71
|
-
sh.stdout
|
|
72
|
-
rescue Mixlib::ShellOut::ShellCommandFailed => ex
|
|
73
|
-
raise ShellCommandFailed, ex.message
|
|
74
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
75
|
-
error.extend(Kitchen::Error)
|
|
76
|
-
raise
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
# Returns a hash of MixLib::ShellOut options for the command.
|
|
82
|
-
#
|
|
83
|
-
# @param options [Hash] a Hash of options
|
|
84
|
-
# @return [Hash] a new Hash of options, filterd and merged with defaults
|
|
85
|
-
# @api private
|
|
86
|
-
def shell_opts(options)
|
|
87
|
-
filtered_opts = options.reject do |key, _value|
|
|
88
|
-
[:use_sudo, :sudo_command, :log_subject, :quiet].include?(key)
|
|
89
|
-
end
|
|
90
|
-
{ :live_stream => logger, :timeout => 60000 }.merge(filtered_opts)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2012, Fletcher Nichol
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
|
|
19
|
+
require "mixlib/shellout"
|
|
20
|
+
|
|
21
|
+
module Kitchen
|
|
22
|
+
|
|
23
|
+
# Mixin that wraps a command shell out invocation, providing a #run_command
|
|
24
|
+
# method.
|
|
25
|
+
#
|
|
26
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
27
|
+
module ShellOut
|
|
28
|
+
|
|
29
|
+
# Wrapped exception for any interally raised shell out commands.
|
|
30
|
+
class ShellCommandFailed < TransientFailure; end
|
|
31
|
+
|
|
32
|
+
# Executes a command in a subshell on the local running system.
|
|
33
|
+
#
|
|
34
|
+
# @param cmd [String] command to be executed locally
|
|
35
|
+
# @param options [Hash] additional configuration of command
|
|
36
|
+
# @option options [TrueClass, FalseClass] :use_sudo whether or not to use
|
|
37
|
+
# sudo
|
|
38
|
+
# @option options [String] :sudo_command custom sudo command to use.
|
|
39
|
+
# Default is "sudo -E".
|
|
40
|
+
# @option options [String] :log_subject used in the output or log header
|
|
41
|
+
# for clarity and context. Default is "local".
|
|
42
|
+
# @option options [String] :cwd the directory to chdir to before running
|
|
43
|
+
# the command
|
|
44
|
+
# @option options [Hash] :environment a Hash of environment variables to
|
|
45
|
+
# set before the command is run. By default, the environment will
|
|
46
|
+
# *always* be set to `'LC_ALL' => 'C'` to prevent issues with multibyte
|
|
47
|
+
# characters in Ruby 1.8. To avoid this, use :environment => nil for
|
|
48
|
+
# *no* extra environment settings, or
|
|
49
|
+
# `:environment => {'LC_ALL'=>nil, ...}` to set other environment
|
|
50
|
+
# settings without changing the locale.
|
|
51
|
+
# @option options [Integer] :timeout Numeric value for the number of
|
|
52
|
+
# seconds to wait on the child process before raising an Exception.
|
|
53
|
+
# This is calculated as the total amount of time that ShellOut waited on
|
|
54
|
+
# the child process without receiving any output (i.e., IO.select
|
|
55
|
+
# returned nil). Default is 60000 seconds. Note: the stdlib Timeout
|
|
56
|
+
# library is not used.
|
|
57
|
+
# @return [String] the standard output of the command as a String
|
|
58
|
+
# @raise [ShellCommandFailed] if the command fails
|
|
59
|
+
# @raise [Error] for all other unexpected exceptions
|
|
60
|
+
def run_command(cmd, options = {})
|
|
61
|
+
if options.fetch(:use_sudo, false)
|
|
62
|
+
cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}"
|
|
63
|
+
end
|
|
64
|
+
subject = "[#{options.fetch(:log_subject, "local")} command]"
|
|
65
|
+
|
|
66
|
+
debug("#{subject} BEGIN (#{cmd})")
|
|
67
|
+
sh = Mixlib::ShellOut.new(cmd, shell_opts(options))
|
|
68
|
+
sh.run_command
|
|
69
|
+
debug("#{subject} END #{Util.duration(sh.execution_time)}")
|
|
70
|
+
sh.error!
|
|
71
|
+
sh.stdout
|
|
72
|
+
rescue Mixlib::ShellOut::ShellCommandFailed => ex
|
|
73
|
+
raise ShellCommandFailed, ex.message
|
|
74
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
75
|
+
error.extend(Kitchen::Error)
|
|
76
|
+
raise
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Returns a hash of MixLib::ShellOut options for the command.
|
|
82
|
+
#
|
|
83
|
+
# @param options [Hash] a Hash of options
|
|
84
|
+
# @return [Hash] a new Hash of options, filterd and merged with defaults
|
|
85
|
+
# @api private
|
|
86
|
+
def shell_opts(options)
|
|
87
|
+
filtered_opts = options.reject do |key, _value|
|
|
88
|
+
[:use_sudo, :sudo_command, :log_subject, :quiet].include?(key)
|
|
89
|
+
end
|
|
90
|
+
{ :live_stream => logger, :timeout => 60000 }.merge(filtered_opts)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/kitchen/ssh.rb
CHANGED
|
@@ -1,276 +1,276 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2013, Fletcher Nichol
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
# you may not use this file except in compliance with the License.
|
|
9
|
-
# You may obtain a copy of the License at
|
|
10
|
-
#
|
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
#
|
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
# See the License for the specific language governing permissions and
|
|
17
|
-
# limitations under the License.
|
|
18
|
-
|
|
19
|
-
require "logger"
|
|
20
|
-
require "net/ssh"
|
|
21
|
-
require "net/scp"
|
|
22
|
-
require "socket"
|
|
23
|
-
|
|
24
|
-
require "kitchen/errors"
|
|
25
|
-
require "kitchen/login_command"
|
|
26
|
-
|
|
27
|
-
module Kitchen
|
|
28
|
-
|
|
29
|
-
# Wrapped exception for any internally raised SSH-related errors.
|
|
30
|
-
#
|
|
31
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
32
|
-
class SSHFailed < TransientFailure; end
|
|
33
|
-
|
|
34
|
-
# Class to help establish SSH connections, issue remote commands, and
|
|
35
|
-
# transfer files between a local system and remote node.
|
|
36
|
-
#
|
|
37
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
38
|
-
class SSH
|
|
39
|
-
|
|
40
|
-
# Constructs a new SSH object.
|
|
41
|
-
#
|
|
42
|
-
# @example basic usage
|
|
43
|
-
#
|
|
44
|
-
# ssh = Kitchen::SSH.new("remote.example.com", "root")
|
|
45
|
-
# ssh.exec("sudo apt-get update")
|
|
46
|
-
# ssh.upload!("/tmp/data.txt", "/var/lib/data.txt")
|
|
47
|
-
# ssh.shutdown
|
|
48
|
-
#
|
|
49
|
-
# @example block usage
|
|
50
|
-
#
|
|
51
|
-
# Kitchen::SSH.new("remote.example.com", "root") do |ssh|
|
|
52
|
-
# ssh.exec("sudo apt-get update")
|
|
53
|
-
# ssh.upload!("/tmp/data.txt", "/var/lib/data.txt")
|
|
54
|
-
# end
|
|
55
|
-
#
|
|
56
|
-
# @param hostname [String] the remote hostname (IP address, FQDN, etc.)
|
|
57
|
-
# @param username [String] the username for the remote host
|
|
58
|
-
# @param options [Hash] configuration options
|
|
59
|
-
# @option options [Logger] :logger the logger to use
|
|
60
|
-
# (default: `::Logger.new(STDOUT)`)
|
|
61
|
-
# @yield [self] if a block is given then the constructed object yields
|
|
62
|
-
# itself and calls `#shutdown` at the end, closing the remote connection
|
|
63
|
-
def initialize(hostname, username, options = {})
|
|
64
|
-
@hostname = hostname
|
|
65
|
-
@username = username
|
|
66
|
-
@options = options.dup
|
|
67
|
-
@logger = @options.delete(:logger) || ::Logger.new(STDOUT)
|
|
68
|
-
|
|
69
|
-
if block_given?
|
|
70
|
-
yield self
|
|
71
|
-
shutdown
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Execute a command on the remote host.
|
|
76
|
-
#
|
|
77
|
-
# @param cmd [String] command string to execute
|
|
78
|
-
# @raise [SSHFailed] if the command does not exit with a 0 code
|
|
79
|
-
def exec(cmd)
|
|
80
|
-
logger.debug("[SSH] #{self} (#{cmd})")
|
|
81
|
-
exit_code = exec_with_exit(cmd)
|
|
82
|
-
|
|
83
|
-
if exit_code != 0
|
|
84
|
-
raise SSHFailed, "SSH exited (#{exit_code}) for command: [#{cmd}]"
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Uploads a local file to remote host.
|
|
89
|
-
#
|
|
90
|
-
# @param local [String] path to local file
|
|
91
|
-
# @param remote [String] path to remote file destination
|
|
92
|
-
# @param options [Hash] configuration options that are passed to
|
|
93
|
-
# `Net::SCP.upload`
|
|
94
|
-
# @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload
|
|
95
|
-
def upload!(local, remote, options = {}, &progress)
|
|
96
|
-
if progress.nil?
|
|
97
|
-
progress = lambda { |_ch, name, sent, total|
|
|
98
|
-
if sent == total
|
|
99
|
-
logger.debug("Uploaded #{name} (#{total} bytes)")
|
|
100
|
-
end
|
|
101
|
-
}
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
session.scp.upload!(local, remote, options, &progress)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Uploads a recursive directory to remote host.
|
|
108
|
-
#
|
|
109
|
-
# @param local [String] path to local file or directory
|
|
110
|
-
# @param remote [String] path to remote file destination
|
|
111
|
-
# @param options [Hash] configuration options that are passed to
|
|
112
|
-
# `Net::SCP.upload`
|
|
113
|
-
# @option options [true,false] :recursive recursive copy (default: `true`)
|
|
114
|
-
# @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload
|
|
115
|
-
def upload_path!(local, remote, options = {}, &progress)
|
|
116
|
-
options = { :recursive => true }.merge(options)
|
|
117
|
-
|
|
118
|
-
upload!(local, remote, options, &progress)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Shuts down the session connection, if it is still active.
|
|
122
|
-
def shutdown
|
|
123
|
-
return if @session.nil?
|
|
124
|
-
|
|
125
|
-
logger.debug("[SSH] closing connection to #{self}")
|
|
126
|
-
session.shutdown!
|
|
127
|
-
ensure
|
|
128
|
-
@session = nil
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Blocks until the remote host's SSH TCP port is listening.
|
|
132
|
-
def wait
|
|
133
|
-
logger.info("Waiting for #{hostname}:#{port}...") until test_ssh
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Builds a LoginCommand which can be used to open an interactive session
|
|
137
|
-
# on the remote host.
|
|
138
|
-
#
|
|
139
|
-
# @return [LoginCommand] the login command
|
|
140
|
-
def login_command
|
|
141
|
-
args = %W[ -o UserKnownHostsFile=/dev/null ]
|
|
142
|
-
args += %W[ -o StrictHostKeyChecking=no ]
|
|
143
|
-
args += %W[ -o IdentitiesOnly=yes ] if options[:keys]
|
|
144
|
-
args += %W[ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} ]
|
|
145
|
-
if options.key?(:forward_agent)
|
|
146
|
-
args += %W[ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} ]
|
|
147
|
-
end
|
|
148
|
-
Array(options[:keys]).each { |ssh_key| args += %W[ -i #{ssh_key} ] }
|
|
149
|
-
args += %W[ -p #{port} ]
|
|
150
|
-
args += %W[ #{username}@#{hostname} ]
|
|
151
|
-
|
|
152
|
-
LoginCommand.new("ssh", args)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
private
|
|
156
|
-
|
|
157
|
-
# TCP socket exceptions
|
|
158
|
-
SOCKET_EXCEPTIONS = [
|
|
159
|
-
SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
|
160
|
-
Errno::ENETUNREACH, IOError
|
|
161
|
-
]
|
|
162
|
-
|
|
163
|
-
# @return [String] the remote hostname
|
|
164
|
-
# @api private
|
|
165
|
-
attr_reader :hostname
|
|
166
|
-
|
|
167
|
-
# @return [String] the username for the remote host
|
|
168
|
-
# @api private
|
|
169
|
-
attr_reader :username
|
|
170
|
-
|
|
171
|
-
# @return [Hash] SSH options, passed to `Net::SSH.start`
|
|
172
|
-
attr_reader :options
|
|
173
|
-
|
|
174
|
-
# @return [Logger] the logger to use
|
|
175
|
-
# @api private
|
|
176
|
-
attr_reader :logger
|
|
177
|
-
|
|
178
|
-
# Builds the Net::SSH session connection or returns the existing one if
|
|
179
|
-
# built.
|
|
180
|
-
#
|
|
181
|
-
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
182
|
-
# @api private
|
|
183
|
-
def session
|
|
184
|
-
@session ||= establish_connection
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Establish a connection session to the remote host.
|
|
188
|
-
#
|
|
189
|
-
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
190
|
-
# @api private
|
|
191
|
-
def establish_connection
|
|
192
|
-
rescue_exceptions = [
|
|
193
|
-
Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
|
|
194
|
-
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
|
|
195
|
-
Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout
|
|
196
|
-
]
|
|
197
|
-
retries = options[:ssh_retries] || 3
|
|
198
|
-
|
|
199
|
-
begin
|
|
200
|
-
logger.debug("[SSH] opening connection to #{self}")
|
|
201
|
-
Net::SSH.start(hostname, username, options)
|
|
202
|
-
rescue *rescue_exceptions => e
|
|
203
|
-
retries -= 1
|
|
204
|
-
if retries > 0
|
|
205
|
-
logger.info("[SSH] connection failed, retrying (#{e.inspect})")
|
|
206
|
-
sleep options[:ssh_timeout] || 1
|
|
207
|
-
retry
|
|
208
|
-
else
|
|
209
|
-
logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
|
|
210
|
-
raise
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# String representation of object, reporting its connection details and
|
|
216
|
-
# configuration.
|
|
217
|
-
#
|
|
218
|
-
# @api private
|
|
219
|
-
def to_s
|
|
220
|
-
"#{username}@#{hostname}:#{port}<#{options.inspect}>"
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# @return [Integer] SSH port (default: 22)
|
|
224
|
-
# @api private
|
|
225
|
-
def port
|
|
226
|
-
options.fetch(:port, 22)
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
# Execute a remote command and return the command's exit code.
|
|
230
|
-
#
|
|
231
|
-
# @param cmd [String] command string to execute
|
|
232
|
-
# @return [Integer] the exit code of the command
|
|
233
|
-
# @api private
|
|
234
|
-
def exec_with_exit(cmd)
|
|
235
|
-
exit_code = nil
|
|
236
|
-
session.open_channel do |channel|
|
|
237
|
-
|
|
238
|
-
channel.request_pty
|
|
239
|
-
|
|
240
|
-
channel.exec(cmd) do |_ch, _success|
|
|
241
|
-
|
|
242
|
-
channel.on_data do |_ch, data|
|
|
243
|
-
logger << data
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
channel.on_extended_data do |_ch, _type, data|
|
|
247
|
-
logger << data
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
channel.on_request("exit-status") do |_ch, data|
|
|
251
|
-
exit_code = data.read_long
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
session.loop
|
|
256
|
-
exit_code
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
# Test a remote TCP socket (presumably SSH) for connectivity.
|
|
260
|
-
#
|
|
261
|
-
# @return [true,false] a truthy value if the socket is ready and false
|
|
262
|
-
# otherwise
|
|
263
|
-
# @api private
|
|
264
|
-
def test_ssh
|
|
265
|
-
socket = TCPSocket.new(hostname, port)
|
|
266
|
-
IO.select([socket], nil, nil, 5)
|
|
267
|
-
rescue *SOCKET_EXCEPTIONS
|
|
268
|
-
sleep options[:ssh_timeout] || 2
|
|
269
|
-
false
|
|
270
|
-
rescue Errno::EPERM, Errno::ETIMEDOUT
|
|
271
|
-
false
|
|
272
|
-
ensure
|
|
273
|
-
socket && socket.close
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2013, Fletcher Nichol
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
|
|
19
|
+
require "logger"
|
|
20
|
+
require "net/ssh"
|
|
21
|
+
require "net/scp"
|
|
22
|
+
require "socket"
|
|
23
|
+
|
|
24
|
+
require "kitchen/errors"
|
|
25
|
+
require "kitchen/login_command"
|
|
26
|
+
|
|
27
|
+
module Kitchen
|
|
28
|
+
|
|
29
|
+
# Wrapped exception for any internally raised SSH-related errors.
|
|
30
|
+
#
|
|
31
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
32
|
+
class SSHFailed < TransientFailure; end
|
|
33
|
+
|
|
34
|
+
# Class to help establish SSH connections, issue remote commands, and
|
|
35
|
+
# transfer files between a local system and remote node.
|
|
36
|
+
#
|
|
37
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
38
|
+
class SSH
|
|
39
|
+
|
|
40
|
+
# Constructs a new SSH object.
|
|
41
|
+
#
|
|
42
|
+
# @example basic usage
|
|
43
|
+
#
|
|
44
|
+
# ssh = Kitchen::SSH.new("remote.example.com", "root")
|
|
45
|
+
# ssh.exec("sudo apt-get update")
|
|
46
|
+
# ssh.upload!("/tmp/data.txt", "/var/lib/data.txt")
|
|
47
|
+
# ssh.shutdown
|
|
48
|
+
#
|
|
49
|
+
# @example block usage
|
|
50
|
+
#
|
|
51
|
+
# Kitchen::SSH.new("remote.example.com", "root") do |ssh|
|
|
52
|
+
# ssh.exec("sudo apt-get update")
|
|
53
|
+
# ssh.upload!("/tmp/data.txt", "/var/lib/data.txt")
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# @param hostname [String] the remote hostname (IP address, FQDN, etc.)
|
|
57
|
+
# @param username [String] the username for the remote host
|
|
58
|
+
# @param options [Hash] configuration options
|
|
59
|
+
# @option options [Logger] :logger the logger to use
|
|
60
|
+
# (default: `::Logger.new(STDOUT)`)
|
|
61
|
+
# @yield [self] if a block is given then the constructed object yields
|
|
62
|
+
# itself and calls `#shutdown` at the end, closing the remote connection
|
|
63
|
+
def initialize(hostname, username, options = {})
|
|
64
|
+
@hostname = hostname
|
|
65
|
+
@username = username
|
|
66
|
+
@options = options.dup
|
|
67
|
+
@logger = @options.delete(:logger) || ::Logger.new(STDOUT)
|
|
68
|
+
|
|
69
|
+
if block_given?
|
|
70
|
+
yield self
|
|
71
|
+
shutdown
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Execute a command on the remote host.
|
|
76
|
+
#
|
|
77
|
+
# @param cmd [String] command string to execute
|
|
78
|
+
# @raise [SSHFailed] if the command does not exit with a 0 code
|
|
79
|
+
def exec(cmd)
|
|
80
|
+
logger.debug("[SSH] #{self} (#{cmd})")
|
|
81
|
+
exit_code = exec_with_exit(cmd)
|
|
82
|
+
|
|
83
|
+
if exit_code != 0
|
|
84
|
+
raise SSHFailed, "SSH exited (#{exit_code}) for command: [#{cmd}]"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Uploads a local file to remote host.
|
|
89
|
+
#
|
|
90
|
+
# @param local [String] path to local file
|
|
91
|
+
# @param remote [String] path to remote file destination
|
|
92
|
+
# @param options [Hash] configuration options that are passed to
|
|
93
|
+
# `Net::SCP.upload`
|
|
94
|
+
# @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload
|
|
95
|
+
def upload!(local, remote, options = {}, &progress)
|
|
96
|
+
if progress.nil?
|
|
97
|
+
progress = lambda { |_ch, name, sent, total|
|
|
98
|
+
if sent == total
|
|
99
|
+
logger.debug("Uploaded #{name} (#{total} bytes)")
|
|
100
|
+
end
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
session.scp.upload!(local, remote, options, &progress)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Uploads a recursive directory to remote host.
|
|
108
|
+
#
|
|
109
|
+
# @param local [String] path to local file or directory
|
|
110
|
+
# @param remote [String] path to remote file destination
|
|
111
|
+
# @param options [Hash] configuration options that are passed to
|
|
112
|
+
# `Net::SCP.upload`
|
|
113
|
+
# @option options [true,false] :recursive recursive copy (default: `true`)
|
|
114
|
+
# @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload
|
|
115
|
+
def upload_path!(local, remote, options = {}, &progress)
|
|
116
|
+
options = { :recursive => true }.merge(options)
|
|
117
|
+
|
|
118
|
+
upload!(local, remote, options, &progress)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Shuts down the session connection, if it is still active.
|
|
122
|
+
def shutdown
|
|
123
|
+
return if @session.nil?
|
|
124
|
+
|
|
125
|
+
logger.debug("[SSH] closing connection to #{self}")
|
|
126
|
+
session.shutdown!
|
|
127
|
+
ensure
|
|
128
|
+
@session = nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Blocks until the remote host's SSH TCP port is listening.
|
|
132
|
+
def wait
|
|
133
|
+
logger.info("Waiting for #{hostname}:#{port}...") until test_ssh
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Builds a LoginCommand which can be used to open an interactive session
|
|
137
|
+
# on the remote host.
|
|
138
|
+
#
|
|
139
|
+
# @return [LoginCommand] the login command
|
|
140
|
+
def login_command
|
|
141
|
+
args = %W[ -o UserKnownHostsFile=/dev/null ]
|
|
142
|
+
args += %W[ -o StrictHostKeyChecking=no ]
|
|
143
|
+
args += %W[ -o IdentitiesOnly=yes ] if options[:keys]
|
|
144
|
+
args += %W[ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} ]
|
|
145
|
+
if options.key?(:forward_agent)
|
|
146
|
+
args += %W[ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} ]
|
|
147
|
+
end
|
|
148
|
+
Array(options[:keys]).each { |ssh_key| args += %W[ -i #{ssh_key} ] }
|
|
149
|
+
args += %W[ -p #{port} ]
|
|
150
|
+
args += %W[ #{username}@#{hostname} ]
|
|
151
|
+
|
|
152
|
+
LoginCommand.new("ssh", args)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
# TCP socket exceptions
|
|
158
|
+
SOCKET_EXCEPTIONS = [
|
|
159
|
+
SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
|
160
|
+
Errno::ENETUNREACH, IOError
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
# @return [String] the remote hostname
|
|
164
|
+
# @api private
|
|
165
|
+
attr_reader :hostname
|
|
166
|
+
|
|
167
|
+
# @return [String] the username for the remote host
|
|
168
|
+
# @api private
|
|
169
|
+
attr_reader :username
|
|
170
|
+
|
|
171
|
+
# @return [Hash] SSH options, passed to `Net::SSH.start`
|
|
172
|
+
attr_reader :options
|
|
173
|
+
|
|
174
|
+
# @return [Logger] the logger to use
|
|
175
|
+
# @api private
|
|
176
|
+
attr_reader :logger
|
|
177
|
+
|
|
178
|
+
# Builds the Net::SSH session connection or returns the existing one if
|
|
179
|
+
# built.
|
|
180
|
+
#
|
|
181
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
182
|
+
# @api private
|
|
183
|
+
def session
|
|
184
|
+
@session ||= establish_connection
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Establish a connection session to the remote host.
|
|
188
|
+
#
|
|
189
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
190
|
+
# @api private
|
|
191
|
+
def establish_connection
|
|
192
|
+
rescue_exceptions = [
|
|
193
|
+
Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
|
|
194
|
+
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
|
|
195
|
+
Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout
|
|
196
|
+
]
|
|
197
|
+
retries = options[:ssh_retries] || 3
|
|
198
|
+
|
|
199
|
+
begin
|
|
200
|
+
logger.debug("[SSH] opening connection to #{self}")
|
|
201
|
+
Net::SSH.start(hostname, username, options)
|
|
202
|
+
rescue *rescue_exceptions => e
|
|
203
|
+
retries -= 1
|
|
204
|
+
if retries > 0
|
|
205
|
+
logger.info("[SSH] connection failed, retrying (#{e.inspect})")
|
|
206
|
+
sleep options[:ssh_timeout] || 1
|
|
207
|
+
retry
|
|
208
|
+
else
|
|
209
|
+
logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
|
|
210
|
+
raise
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# String representation of object, reporting its connection details and
|
|
216
|
+
# configuration.
|
|
217
|
+
#
|
|
218
|
+
# @api private
|
|
219
|
+
def to_s
|
|
220
|
+
"#{username}@#{hostname}:#{port}<#{options.inspect}>"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# @return [Integer] SSH port (default: 22)
|
|
224
|
+
# @api private
|
|
225
|
+
def port
|
|
226
|
+
options.fetch(:port, 22)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Execute a remote command and return the command's exit code.
|
|
230
|
+
#
|
|
231
|
+
# @param cmd [String] command string to execute
|
|
232
|
+
# @return [Integer] the exit code of the command
|
|
233
|
+
# @api private
|
|
234
|
+
def exec_with_exit(cmd)
|
|
235
|
+
exit_code = nil
|
|
236
|
+
session.open_channel do |channel|
|
|
237
|
+
|
|
238
|
+
channel.request_pty
|
|
239
|
+
|
|
240
|
+
channel.exec(cmd) do |_ch, _success|
|
|
241
|
+
|
|
242
|
+
channel.on_data do |_ch, data|
|
|
243
|
+
logger << data
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
channel.on_extended_data do |_ch, _type, data|
|
|
247
|
+
logger << data
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
channel.on_request("exit-status") do |_ch, data|
|
|
251
|
+
exit_code = data.read_long
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
session.loop
|
|
256
|
+
exit_code
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Test a remote TCP socket (presumably SSH) for connectivity.
|
|
260
|
+
#
|
|
261
|
+
# @return [true,false] a truthy value if the socket is ready and false
|
|
262
|
+
# otherwise
|
|
263
|
+
# @api private
|
|
264
|
+
def test_ssh
|
|
265
|
+
socket = TCPSocket.new(hostname, port)
|
|
266
|
+
IO.select([socket], nil, nil, 5)
|
|
267
|
+
rescue *SOCKET_EXCEPTIONS
|
|
268
|
+
sleep options[:ssh_timeout] || 2
|
|
269
|
+
false
|
|
270
|
+
rescue Errno::EPERM, Errno::ETIMEDOUT
|
|
271
|
+
false
|
|
272
|
+
ensure
|
|
273
|
+
socket && socket.close
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|