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
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Salim Afiune (<salim@afiunemaya.com.mx>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2013, Salim Afiune
|
|
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 "kitchen"
|
|
20
|
-
|
|
21
|
-
module Kitchen
|
|
22
|
-
|
|
23
|
-
module Transport
|
|
24
|
-
|
|
25
|
-
# Dummy transport for Kitchen. This transport does nothing but report what would
|
|
26
|
-
# happen if this transport did anything of consequence. As a result it may
|
|
27
|
-
# be a useful transport to use when debugging or developing new features or
|
|
28
|
-
# plugins.
|
|
29
|
-
class Dummy < Kitchen::Transport::Base
|
|
30
|
-
|
|
31
|
-
kitchen_transport_api_version 1
|
|
32
|
-
|
|
33
|
-
plugin_version Kitchen::VERSION
|
|
34
|
-
|
|
35
|
-
default_config :sleep, 1
|
|
36
|
-
default_config :random_exit_code, 0
|
|
37
|
-
|
|
38
|
-
def connection(state, &block)
|
|
39
|
-
options = config.to_hash.merge(state)
|
|
40
|
-
Kitchen::Transport::Dummy::Connection.new(options, &block)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# TODO: comment
|
|
44
|
-
class Connection < Kitchen::Transport::Base::Connection
|
|
45
|
-
|
|
46
|
-
# (see Base#execute)
|
|
47
|
-
def execute(command)
|
|
48
|
-
report(:execute, command)
|
|
49
|
-
if options[:random_exit_code] != 0
|
|
50
|
-
info("Dummy exited (#{exit_code}) for command: [#{command}]")
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def upload(locals, remote)
|
|
55
|
-
report(:upload, "#{locals.inspect} => #{remote}")
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
private
|
|
59
|
-
|
|
60
|
-
# Report what action is taking place, sleeping if so configured, and
|
|
61
|
-
# possibly fail randomly.
|
|
62
|
-
#
|
|
63
|
-
# @param action [Symbol] the action currently taking place
|
|
64
|
-
# @param state [Hash] the state hash
|
|
65
|
-
# @api private
|
|
66
|
-
def report(action, msg = "")
|
|
67
|
-
what = action.capitalize
|
|
68
|
-
info("[Dummy] #{what} #{msg} on Transport=Dummy")
|
|
69
|
-
sleep_if_set
|
|
70
|
-
debug("[Dummy] #{what} #{msg} completed (#{options[:sleep]}s).")
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def sleep_if_set
|
|
74
|
-
sleep(options[:sleep].to_f) if options[:sleep].to_f > 0.0
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Salim Afiune (<salim@afiunemaya.com.mx>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2013, Salim Afiune
|
|
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 "kitchen"
|
|
20
|
+
|
|
21
|
+
module Kitchen
|
|
22
|
+
|
|
23
|
+
module Transport
|
|
24
|
+
|
|
25
|
+
# Dummy transport for Kitchen. This transport does nothing but report what would
|
|
26
|
+
# happen if this transport did anything of consequence. As a result it may
|
|
27
|
+
# be a useful transport to use when debugging or developing new features or
|
|
28
|
+
# plugins.
|
|
29
|
+
class Dummy < Kitchen::Transport::Base
|
|
30
|
+
|
|
31
|
+
kitchen_transport_api_version 1
|
|
32
|
+
|
|
33
|
+
plugin_version Kitchen::VERSION
|
|
34
|
+
|
|
35
|
+
default_config :sleep, 1
|
|
36
|
+
default_config :random_exit_code, 0
|
|
37
|
+
|
|
38
|
+
def connection(state, &block)
|
|
39
|
+
options = config.to_hash.merge(state)
|
|
40
|
+
Kitchen::Transport::Dummy::Connection.new(options, &block)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# TODO: comment
|
|
44
|
+
class Connection < Kitchen::Transport::Base::Connection
|
|
45
|
+
|
|
46
|
+
# (see Base#execute)
|
|
47
|
+
def execute(command)
|
|
48
|
+
report(:execute, command)
|
|
49
|
+
if options[:random_exit_code] != 0
|
|
50
|
+
info("Dummy exited (#{exit_code}) for command: [#{command}]")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def upload(locals, remote)
|
|
55
|
+
report(:upload, "#{locals.inspect} => #{remote}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Report what action is taking place, sleeping if so configured, and
|
|
61
|
+
# possibly fail randomly.
|
|
62
|
+
#
|
|
63
|
+
# @param action [Symbol] the action currently taking place
|
|
64
|
+
# @param state [Hash] the state hash
|
|
65
|
+
# @api private
|
|
66
|
+
def report(action, msg = "")
|
|
67
|
+
what = action.capitalize
|
|
68
|
+
info("[Dummy] #{what} #{msg} on Transport=Dummy")
|
|
69
|
+
sleep_if_set
|
|
70
|
+
debug("[Dummy] #{what} #{msg} completed (#{options[:sleep]}s).")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def sleep_if_set
|
|
74
|
+
sleep(options[:sleep].to_f) if options[:sleep].to_f > 0.0
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,364 +1,364 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2014, 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 "kitchen"
|
|
20
|
-
|
|
21
|
-
require "net/ssh"
|
|
22
|
-
require "net/scp"
|
|
23
|
-
require "timeout"
|
|
24
|
-
|
|
25
|
-
module Kitchen
|
|
26
|
-
|
|
27
|
-
module Transport
|
|
28
|
-
|
|
29
|
-
# Wrapped exception for any internally raised SSH-related errors.
|
|
30
|
-
#
|
|
31
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
32
|
-
class SshFailed < TransportFailed; end
|
|
33
|
-
|
|
34
|
-
# A Transport which uses the SSH protocol to execute commands and transfer
|
|
35
|
-
# files.
|
|
36
|
-
#
|
|
37
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
38
|
-
class Ssh < Kitchen::Transport::Base
|
|
39
|
-
|
|
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 :connection_timeout, 15
|
|
49
|
-
default_config :connection_retries, 5
|
|
50
|
-
default_config :connection_retry_sleep, 1
|
|
51
|
-
default_config :max_wait_until_ready, 600
|
|
52
|
-
|
|
53
|
-
default_config :ssh_key, nil
|
|
54
|
-
expand_path_for :ssh_key
|
|
55
|
-
|
|
56
|
-
default_config :compression, true
|
|
57
|
-
required_config :compression
|
|
58
|
-
|
|
59
|
-
default_config :compression_level do |transport|
|
|
60
|
-
transport[:compression] == false ? 0 : 6
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def finalize_config!(instance)
|
|
64
|
-
super
|
|
65
|
-
|
|
66
|
-
# zlib was never a valid value and breaks in net-ssh >= 2.10
|
|
67
|
-
# TODO: remove these backwards compatiable casts in 2.0
|
|
68
|
-
case config[:compression]
|
|
69
|
-
when "zlib"
|
|
70
|
-
config[:compression] = "zlib@openssh.com"
|
|
71
|
-
when "none"
|
|
72
|
-
config[:compression] = false
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
self
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# (see Base#connection)
|
|
79
|
-
def connection(state, &block)
|
|
80
|
-
options = connection_options(config.to_hash.merge(state))
|
|
81
|
-
|
|
82
|
-
if @connection && @connection_options == options
|
|
83
|
-
reuse_connection(&block)
|
|
84
|
-
else
|
|
85
|
-
create_new_connection(options, &block)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# (see Base#cleanup!)
|
|
90
|
-
def cleanup!
|
|
91
|
-
if @connection
|
|
92
|
-
logger.debug("[SSH] shutting previous connection #{@connection}")
|
|
93
|
-
@connection.close
|
|
94
|
-
@connection = @connection_options = nil
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# A Connection instance can be generated and re-generated, given new
|
|
99
|
-
# connection details such as connection port, hostname, credentials, etc.
|
|
100
|
-
# This object is responsible for carrying out the actions on the remote
|
|
101
|
-
# host such as executing commands, transferring files, etc.
|
|
102
|
-
#
|
|
103
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
104
|
-
class Connection < Kitchen::Transport::Base::Connection
|
|
105
|
-
|
|
106
|
-
# (see Base::Connection#close)
|
|
107
|
-
def close
|
|
108
|
-
return if @session.nil?
|
|
109
|
-
|
|
110
|
-
logger.debug("[SSH] closing connection to #{self}")
|
|
111
|
-
session.close
|
|
112
|
-
ensure
|
|
113
|
-
@session = nil
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# (see Base::Connection#execute)
|
|
117
|
-
def execute(command)
|
|
118
|
-
return if command.nil?
|
|
119
|
-
logger.debug("[SSH] #{self} (#{command})")
|
|
120
|
-
exit_code = execute_with_exit_code(command)
|
|
121
|
-
|
|
122
|
-
if exit_code != 0
|
|
123
|
-
raise Transport::SshFailed,
|
|
124
|
-
"SSH exited (#{exit_code}) for command: [#{command}]"
|
|
125
|
-
end
|
|
126
|
-
rescue Net::SSH::Exception => ex
|
|
127
|
-
raise SshFailed, "SSH command failed (#{ex.message})"
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# (see Base::Connection#login_command)
|
|
131
|
-
def login_command
|
|
132
|
-
args = %W[ -o UserKnownHostsFile=/dev/null ]
|
|
133
|
-
args += %W[ -o StrictHostKeyChecking=no ]
|
|
134
|
-
args += %W[ -o IdentitiesOnly=yes ] if options[:keys]
|
|
135
|
-
args += %W[ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} ]
|
|
136
|
-
if options.key?(:forward_agent)
|
|
137
|
-
args += %W[ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} ]
|
|
138
|
-
end
|
|
139
|
-
Array(options[:keys]).each { |ssh_key| args += %W[ -i #{ssh_key} ] }
|
|
140
|
-
args += %W[ -p #{port} ]
|
|
141
|
-
args += %W[ #{username}@#{hostname} ]
|
|
142
|
-
|
|
143
|
-
LoginCommand.new("ssh", args)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# (see Base::Connection#upload)
|
|
147
|
-
def upload(locals, remote)
|
|
148
|
-
Array(locals).each do |local|
|
|
149
|
-
opts = File.directory?(local) ? { :recursive => true } : {}
|
|
150
|
-
|
|
151
|
-
session.scp.upload!(local, remote, opts) do |_ch, name, sent, total|
|
|
152
|
-
logger.debug("Uploaded #{name} (#{total} bytes)") if sent == total
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
rescue Net::SSH::Exception => ex
|
|
156
|
-
raise SshFailed, "SCP upload failed (#{ex.message})"
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# (see Base::Connection#wait_until_ready)
|
|
160
|
-
def wait_until_ready
|
|
161
|
-
delay = 3
|
|
162
|
-
session(
|
|
163
|
-
:retries => max_wait_until_ready / delay,
|
|
164
|
-
:delay => delay,
|
|
165
|
-
:message => "Waiting for SSH service on #{hostname}:#{port}, " \
|
|
166
|
-
"retrying in #{delay} seconds"
|
|
167
|
-
)
|
|
168
|
-
execute(PING_COMMAND.dup)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
private
|
|
172
|
-
|
|
173
|
-
PING_COMMAND = "echo '[SSH] Established'".freeze
|
|
174
|
-
|
|
175
|
-
RESCUE_EXCEPTIONS_ON_ESTABLISH = [
|
|
176
|
-
Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
|
177
|
-
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
|
|
178
|
-
Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout,
|
|
179
|
-
Timeout::Error
|
|
180
|
-
].freeze
|
|
181
|
-
|
|
182
|
-
# @return [Integer] how many times to retry when failing to execute
|
|
183
|
-
# a command or transfer files
|
|
184
|
-
# @api private
|
|
185
|
-
attr_reader :connection_retries
|
|
186
|
-
|
|
187
|
-
# @return [Float] how many seconds to wait before attempting a retry
|
|
188
|
-
# when failing to execute a command or transfer files
|
|
189
|
-
# @api private
|
|
190
|
-
attr_reader :connection_retry_sleep
|
|
191
|
-
|
|
192
|
-
# @return [String] the hostname or IP address of the remote SSH host
|
|
193
|
-
# @api private
|
|
194
|
-
attr_reader :hostname
|
|
195
|
-
|
|
196
|
-
# @return [Integer] how many times to retry when invoking
|
|
197
|
-
# `#wait_until_ready` before failing
|
|
198
|
-
# @api private
|
|
199
|
-
attr_reader :max_wait_until_ready
|
|
200
|
-
|
|
201
|
-
# @return [String] the username to use when connecting to the remote
|
|
202
|
-
# SSH host
|
|
203
|
-
# @api private
|
|
204
|
-
attr_reader :username
|
|
205
|
-
|
|
206
|
-
# @return [Integer] the TCP port number to use when connecting to the
|
|
207
|
-
# remote SSH host
|
|
208
|
-
# @api private
|
|
209
|
-
attr_reader :port
|
|
210
|
-
|
|
211
|
-
# Establish an SSH session on the remote host.
|
|
212
|
-
#
|
|
213
|
-
# @param opts [Hash] retry options
|
|
214
|
-
# @option opts [Integer] :retries the number of times to retry before
|
|
215
|
-
# failing
|
|
216
|
-
# @option opts [Float] :delay the number of seconds to wait until
|
|
217
|
-
# attempting a retry
|
|
218
|
-
# @option opts [String] :message an optional message to be logged on
|
|
219
|
-
# debug (overriding the default) when a rescuable exception is raised
|
|
220
|
-
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
221
|
-
# @api private
|
|
222
|
-
def establish_connection(opts)
|
|
223
|
-
logger.debug("[SSH] opening connection to #{self}")
|
|
224
|
-
Net::SSH.start(hostname, username, options)
|
|
225
|
-
rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e
|
|
226
|
-
if (opts[:retries] -= 1) > 0
|
|
227
|
-
message = if opts[:message]
|
|
228
|
-
logger.debug("[SSH] connection failed (#{e.inspect})")
|
|
229
|
-
opts[:message]
|
|
230
|
-
else
|
|
231
|
-
"[SSH] connection failed, retrying in #{opts[:delay]} seconds " \
|
|
232
|
-
"(#{e.inspect})"
|
|
233
|
-
end
|
|
234
|
-
logger.info(message)
|
|
235
|
-
sleep(opts[:delay])
|
|
236
|
-
retry
|
|
237
|
-
else
|
|
238
|
-
logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
|
|
239
|
-
raise SshFailed, "SSH session could not be established"
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
# Execute a remote command over SSH and return the command's exit code.
|
|
244
|
-
#
|
|
245
|
-
# @param command [String] command string to execute
|
|
246
|
-
# @return [Integer] the exit code of the command
|
|
247
|
-
# @api private
|
|
248
|
-
def execute_with_exit_code(command)
|
|
249
|
-
exit_code = nil
|
|
250
|
-
session.open_channel do |channel|
|
|
251
|
-
|
|
252
|
-
channel.request_pty
|
|
253
|
-
|
|
254
|
-
channel.exec(command) do |_ch, _success|
|
|
255
|
-
|
|
256
|
-
channel.on_data do |_ch, data|
|
|
257
|
-
logger << data
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
channel.on_extended_data do |_ch, _type, data|
|
|
261
|
-
logger << data
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
channel.on_request("exit-status") do |_ch, data|
|
|
265
|
-
exit_code = data.read_long
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
end
|
|
269
|
-
session.loop
|
|
270
|
-
exit_code
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
# (see Base::Connection#init_options)
|
|
274
|
-
def init_options(options)
|
|
275
|
-
super
|
|
276
|
-
@username = @options.delete(:username)
|
|
277
|
-
@hostname = @options.delete(:hostname)
|
|
278
|
-
@port = @options[:port] # don't delete from options
|
|
279
|
-
@connection_retries = @options.delete(:connection_retries)
|
|
280
|
-
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
|
|
281
|
-
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
# Returns a connection session, or establishes one when invoked the
|
|
285
|
-
# first time.
|
|
286
|
-
#
|
|
287
|
-
# @param retry_options [Hash] retry options for the initial connection
|
|
288
|
-
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
289
|
-
# @api private
|
|
290
|
-
def session(retry_options = {})
|
|
291
|
-
@session ||= establish_connection({
|
|
292
|
-
:retries => connection_retries.to_i,
|
|
293
|
-
:delay => connection_retry_sleep.to_i
|
|
294
|
-
}.merge(retry_options))
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
# String representation of object, reporting its connection details and
|
|
298
|
-
# configuration.
|
|
299
|
-
#
|
|
300
|
-
# @api private
|
|
301
|
-
def to_s
|
|
302
|
-
"#{username}@#{hostname}<#{options.inspect}>"
|
|
303
|
-
end
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
private
|
|
307
|
-
|
|
308
|
-
# Builds the hash of options needed by the Connection object on
|
|
309
|
-
# construction.
|
|
310
|
-
#
|
|
311
|
-
# @param data [Hash] merged configuration and mutable state data
|
|
312
|
-
# @return [Hash] hash of connection options
|
|
313
|
-
# @api private
|
|
314
|
-
def connection_options(data) # rubocop:disable Metrics/MethodLength
|
|
315
|
-
opts = {
|
|
316
|
-
:logger => logger,
|
|
317
|
-
:user_known_hosts_file => "/dev/null",
|
|
318
|
-
:paranoid => false,
|
|
319
|
-
:hostname => data[:hostname],
|
|
320
|
-
:port => data[:port],
|
|
321
|
-
:username => data[:username],
|
|
322
|
-
:compression => data[:compression],
|
|
323
|
-
:compression_level => data[:compression_level],
|
|
324
|
-
:keepalive => data[:keepalive],
|
|
325
|
-
:keepalive_interval => data[:keepalive_interval],
|
|
326
|
-
:timeout => data[:connection_timeout],
|
|
327
|
-
:connection_retries => data[:connection_retries],
|
|
328
|
-
:connection_retry_sleep => data[:connection_retry_sleep],
|
|
329
|
-
:max_wait_until_ready => data[:max_wait_until_ready]
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
opts[:keys_only] = true if data[:ssh_key]
|
|
333
|
-
opts[:keys] = Array(data[:ssh_key]) if data[:ssh_key]
|
|
334
|
-
opts[:auth_methods] = ["publickey"] if data[:ssh_key]
|
|
335
|
-
opts[:password] = data[:password] if data.key?(:password)
|
|
336
|
-
opts[:forward_agent] = data[:forward_agent] if data.key?(:forward_agent)
|
|
337
|
-
|
|
338
|
-
opts
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
# Creates a new SSH Connection instance and save it for potential future
|
|
342
|
-
# reuse.
|
|
343
|
-
#
|
|
344
|
-
# @param options [Hash] conneciton options
|
|
345
|
-
# @return [Ssh::Connection] an SSH Connection instance
|
|
346
|
-
# @api private
|
|
347
|
-
def create_new_connection(options, &block)
|
|
348
|
-
cleanup!
|
|
349
|
-
@connection_options = options
|
|
350
|
-
@connection = Kitchen::Transport::Ssh::Connection.new(options, &block)
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# Return the last saved SSH connection instance.
|
|
354
|
-
#
|
|
355
|
-
# @return [Ssh::Connection] an SSH Connection instance
|
|
356
|
-
# @api private
|
|
357
|
-
def reuse_connection
|
|
358
|
-
logger.debug("[SSH] reusing existing connection #{@connection}")
|
|
359
|
-
yield @connection if block_given?
|
|
360
|
-
@connection
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
end
|
|
364
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2014, 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 "kitchen"
|
|
20
|
+
|
|
21
|
+
require "net/ssh"
|
|
22
|
+
require "net/scp"
|
|
23
|
+
require "timeout"
|
|
24
|
+
|
|
25
|
+
module Kitchen
|
|
26
|
+
|
|
27
|
+
module Transport
|
|
28
|
+
|
|
29
|
+
# Wrapped exception for any internally raised SSH-related errors.
|
|
30
|
+
#
|
|
31
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
32
|
+
class SshFailed < TransportFailed; end
|
|
33
|
+
|
|
34
|
+
# A Transport which uses the SSH protocol to execute commands and transfer
|
|
35
|
+
# files.
|
|
36
|
+
#
|
|
37
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
38
|
+
class Ssh < Kitchen::Transport::Base
|
|
39
|
+
|
|
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 :connection_timeout, 15
|
|
49
|
+
default_config :connection_retries, 5
|
|
50
|
+
default_config :connection_retry_sleep, 1
|
|
51
|
+
default_config :max_wait_until_ready, 600
|
|
52
|
+
|
|
53
|
+
default_config :ssh_key, nil
|
|
54
|
+
expand_path_for :ssh_key
|
|
55
|
+
|
|
56
|
+
default_config :compression, true
|
|
57
|
+
required_config :compression
|
|
58
|
+
|
|
59
|
+
default_config :compression_level do |transport|
|
|
60
|
+
transport[:compression] == false ? 0 : 6
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def finalize_config!(instance)
|
|
64
|
+
super
|
|
65
|
+
|
|
66
|
+
# zlib was never a valid value and breaks in net-ssh >= 2.10
|
|
67
|
+
# TODO: remove these backwards compatiable casts in 2.0
|
|
68
|
+
case config[:compression]
|
|
69
|
+
when "zlib"
|
|
70
|
+
config[:compression] = "zlib@openssh.com"
|
|
71
|
+
when "none"
|
|
72
|
+
config[:compression] = false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# (see Base#connection)
|
|
79
|
+
def connection(state, &block)
|
|
80
|
+
options = connection_options(config.to_hash.merge(state))
|
|
81
|
+
|
|
82
|
+
if @connection && @connection_options == options
|
|
83
|
+
reuse_connection(&block)
|
|
84
|
+
else
|
|
85
|
+
create_new_connection(options, &block)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# (see Base#cleanup!)
|
|
90
|
+
def cleanup!
|
|
91
|
+
if @connection
|
|
92
|
+
logger.debug("[SSH] shutting previous connection #{@connection}")
|
|
93
|
+
@connection.close
|
|
94
|
+
@connection = @connection_options = nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# A Connection instance can be generated and re-generated, given new
|
|
99
|
+
# connection details such as connection port, hostname, credentials, etc.
|
|
100
|
+
# This object is responsible for carrying out the actions on the remote
|
|
101
|
+
# host such as executing commands, transferring files, etc.
|
|
102
|
+
#
|
|
103
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
104
|
+
class Connection < Kitchen::Transport::Base::Connection
|
|
105
|
+
|
|
106
|
+
# (see Base::Connection#close)
|
|
107
|
+
def close
|
|
108
|
+
return if @session.nil?
|
|
109
|
+
|
|
110
|
+
logger.debug("[SSH] closing connection to #{self}")
|
|
111
|
+
session.close
|
|
112
|
+
ensure
|
|
113
|
+
@session = nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# (see Base::Connection#execute)
|
|
117
|
+
def execute(command)
|
|
118
|
+
return if command.nil?
|
|
119
|
+
logger.debug("[SSH] #{self} (#{command})")
|
|
120
|
+
exit_code = execute_with_exit_code(command)
|
|
121
|
+
|
|
122
|
+
if exit_code != 0
|
|
123
|
+
raise Transport::SshFailed,
|
|
124
|
+
"SSH exited (#{exit_code}) for command: [#{command}]"
|
|
125
|
+
end
|
|
126
|
+
rescue Net::SSH::Exception => ex
|
|
127
|
+
raise SshFailed, "SSH command failed (#{ex.message})"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# (see Base::Connection#login_command)
|
|
131
|
+
def login_command
|
|
132
|
+
args = %W[ -o UserKnownHostsFile=/dev/null ]
|
|
133
|
+
args += %W[ -o StrictHostKeyChecking=no ]
|
|
134
|
+
args += %W[ -o IdentitiesOnly=yes ] if options[:keys]
|
|
135
|
+
args += %W[ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} ]
|
|
136
|
+
if options.key?(:forward_agent)
|
|
137
|
+
args += %W[ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} ]
|
|
138
|
+
end
|
|
139
|
+
Array(options[:keys]).each { |ssh_key| args += %W[ -i #{ssh_key} ] }
|
|
140
|
+
args += %W[ -p #{port} ]
|
|
141
|
+
args += %W[ #{username}@#{hostname} ]
|
|
142
|
+
|
|
143
|
+
LoginCommand.new("ssh", args)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# (see Base::Connection#upload)
|
|
147
|
+
def upload(locals, remote)
|
|
148
|
+
Array(locals).each do |local|
|
|
149
|
+
opts = File.directory?(local) ? { :recursive => true } : {}
|
|
150
|
+
|
|
151
|
+
session.scp.upload!(local, remote, opts) do |_ch, name, sent, total|
|
|
152
|
+
logger.debug("Uploaded #{name} (#{total} bytes)") if sent == total
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
rescue Net::SSH::Exception => ex
|
|
156
|
+
raise SshFailed, "SCP upload failed (#{ex.message})"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# (see Base::Connection#wait_until_ready)
|
|
160
|
+
def wait_until_ready
|
|
161
|
+
delay = 3
|
|
162
|
+
session(
|
|
163
|
+
:retries => max_wait_until_ready / delay,
|
|
164
|
+
:delay => delay,
|
|
165
|
+
:message => "Waiting for SSH service on #{hostname}:#{port}, " \
|
|
166
|
+
"retrying in #{delay} seconds"
|
|
167
|
+
)
|
|
168
|
+
execute(PING_COMMAND.dup)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
PING_COMMAND = "echo '[SSH] Established'".freeze
|
|
174
|
+
|
|
175
|
+
RESCUE_EXCEPTIONS_ON_ESTABLISH = [
|
|
176
|
+
Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
|
177
|
+
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
|
|
178
|
+
Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout,
|
|
179
|
+
Timeout::Error
|
|
180
|
+
].freeze
|
|
181
|
+
|
|
182
|
+
# @return [Integer] how many times to retry when failing to execute
|
|
183
|
+
# a command or transfer files
|
|
184
|
+
# @api private
|
|
185
|
+
attr_reader :connection_retries
|
|
186
|
+
|
|
187
|
+
# @return [Float] how many seconds to wait before attempting a retry
|
|
188
|
+
# when failing to execute a command or transfer files
|
|
189
|
+
# @api private
|
|
190
|
+
attr_reader :connection_retry_sleep
|
|
191
|
+
|
|
192
|
+
# @return [String] the hostname or IP address of the remote SSH host
|
|
193
|
+
# @api private
|
|
194
|
+
attr_reader :hostname
|
|
195
|
+
|
|
196
|
+
# @return [Integer] how many times to retry when invoking
|
|
197
|
+
# `#wait_until_ready` before failing
|
|
198
|
+
# @api private
|
|
199
|
+
attr_reader :max_wait_until_ready
|
|
200
|
+
|
|
201
|
+
# @return [String] the username to use when connecting to the remote
|
|
202
|
+
# SSH host
|
|
203
|
+
# @api private
|
|
204
|
+
attr_reader :username
|
|
205
|
+
|
|
206
|
+
# @return [Integer] the TCP port number to use when connecting to the
|
|
207
|
+
# remote SSH host
|
|
208
|
+
# @api private
|
|
209
|
+
attr_reader :port
|
|
210
|
+
|
|
211
|
+
# Establish an SSH session on the remote host.
|
|
212
|
+
#
|
|
213
|
+
# @param opts [Hash] retry options
|
|
214
|
+
# @option opts [Integer] :retries the number of times to retry before
|
|
215
|
+
# failing
|
|
216
|
+
# @option opts [Float] :delay the number of seconds to wait until
|
|
217
|
+
# attempting a retry
|
|
218
|
+
# @option opts [String] :message an optional message to be logged on
|
|
219
|
+
# debug (overriding the default) when a rescuable exception is raised
|
|
220
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
221
|
+
# @api private
|
|
222
|
+
def establish_connection(opts)
|
|
223
|
+
logger.debug("[SSH] opening connection to #{self}")
|
|
224
|
+
Net::SSH.start(hostname, username, options)
|
|
225
|
+
rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e
|
|
226
|
+
if (opts[:retries] -= 1) > 0
|
|
227
|
+
message = if opts[:message]
|
|
228
|
+
logger.debug("[SSH] connection failed (#{e.inspect})")
|
|
229
|
+
opts[:message]
|
|
230
|
+
else
|
|
231
|
+
"[SSH] connection failed, retrying in #{opts[:delay]} seconds " \
|
|
232
|
+
"(#{e.inspect})"
|
|
233
|
+
end
|
|
234
|
+
logger.info(message)
|
|
235
|
+
sleep(opts[:delay])
|
|
236
|
+
retry
|
|
237
|
+
else
|
|
238
|
+
logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
|
|
239
|
+
raise SshFailed, "SSH session could not be established"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Execute a remote command over SSH and return the command's exit code.
|
|
244
|
+
#
|
|
245
|
+
# @param command [String] command string to execute
|
|
246
|
+
# @return [Integer] the exit code of the command
|
|
247
|
+
# @api private
|
|
248
|
+
def execute_with_exit_code(command)
|
|
249
|
+
exit_code = nil
|
|
250
|
+
session.open_channel do |channel|
|
|
251
|
+
|
|
252
|
+
channel.request_pty
|
|
253
|
+
|
|
254
|
+
channel.exec(command) do |_ch, _success|
|
|
255
|
+
|
|
256
|
+
channel.on_data do |_ch, data|
|
|
257
|
+
logger << data
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
channel.on_extended_data do |_ch, _type, data|
|
|
261
|
+
logger << data
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
channel.on_request("exit-status") do |_ch, data|
|
|
265
|
+
exit_code = data.read_long
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
session.loop
|
|
270
|
+
exit_code
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# (see Base::Connection#init_options)
|
|
274
|
+
def init_options(options)
|
|
275
|
+
super
|
|
276
|
+
@username = @options.delete(:username)
|
|
277
|
+
@hostname = @options.delete(:hostname)
|
|
278
|
+
@port = @options[:port] # don't delete from options
|
|
279
|
+
@connection_retries = @options.delete(:connection_retries)
|
|
280
|
+
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
|
|
281
|
+
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Returns a connection session, or establishes one when invoked the
|
|
285
|
+
# first time.
|
|
286
|
+
#
|
|
287
|
+
# @param retry_options [Hash] retry options for the initial connection
|
|
288
|
+
# @return [Net::SSH::Connection::Session] the SSH connection session
|
|
289
|
+
# @api private
|
|
290
|
+
def session(retry_options = {})
|
|
291
|
+
@session ||= establish_connection({
|
|
292
|
+
:retries => connection_retries.to_i,
|
|
293
|
+
:delay => connection_retry_sleep.to_i
|
|
294
|
+
}.merge(retry_options))
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# String representation of object, reporting its connection details and
|
|
298
|
+
# configuration.
|
|
299
|
+
#
|
|
300
|
+
# @api private
|
|
301
|
+
def to_s
|
|
302
|
+
"#{username}@#{hostname}<#{options.inspect}>"
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
private
|
|
307
|
+
|
|
308
|
+
# Builds the hash of options needed by the Connection object on
|
|
309
|
+
# construction.
|
|
310
|
+
#
|
|
311
|
+
# @param data [Hash] merged configuration and mutable state data
|
|
312
|
+
# @return [Hash] hash of connection options
|
|
313
|
+
# @api private
|
|
314
|
+
def connection_options(data) # rubocop:disable Metrics/MethodLength
|
|
315
|
+
opts = {
|
|
316
|
+
:logger => logger,
|
|
317
|
+
:user_known_hosts_file => "/dev/null",
|
|
318
|
+
:paranoid => false,
|
|
319
|
+
:hostname => data[:hostname],
|
|
320
|
+
:port => data[:port],
|
|
321
|
+
:username => data[:username],
|
|
322
|
+
:compression => data[:compression],
|
|
323
|
+
:compression_level => data[:compression_level],
|
|
324
|
+
:keepalive => data[:keepalive],
|
|
325
|
+
:keepalive_interval => data[:keepalive_interval],
|
|
326
|
+
:timeout => data[:connection_timeout],
|
|
327
|
+
:connection_retries => data[:connection_retries],
|
|
328
|
+
:connection_retry_sleep => data[:connection_retry_sleep],
|
|
329
|
+
:max_wait_until_ready => data[:max_wait_until_ready]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
opts[:keys_only] = true if data[:ssh_key]
|
|
333
|
+
opts[:keys] = Array(data[:ssh_key]) if data[:ssh_key]
|
|
334
|
+
opts[:auth_methods] = ["publickey"] if data[:ssh_key]
|
|
335
|
+
opts[:password] = data[:password] if data.key?(:password)
|
|
336
|
+
opts[:forward_agent] = data[:forward_agent] if data.key?(:forward_agent)
|
|
337
|
+
|
|
338
|
+
opts
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Creates a new SSH Connection instance and save it for potential future
|
|
342
|
+
# reuse.
|
|
343
|
+
#
|
|
344
|
+
# @param options [Hash] conneciton options
|
|
345
|
+
# @return [Ssh::Connection] an SSH Connection instance
|
|
346
|
+
# @api private
|
|
347
|
+
def create_new_connection(options, &block)
|
|
348
|
+
cleanup!
|
|
349
|
+
@connection_options = options
|
|
350
|
+
@connection = Kitchen::Transport::Ssh::Connection.new(options, &block)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Return the last saved SSH connection instance.
|
|
354
|
+
#
|
|
355
|
+
# @return [Ssh::Connection] an SSH Connection instance
|
|
356
|
+
# @api private
|
|
357
|
+
def reuse_connection
|
|
358
|
+
logger.debug("[SSH] reusing existing connection #{@connection}")
|
|
359
|
+
yield @connection if block_given?
|
|
360
|
+
@connection
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|