sshkit 1.18.0 → 1.23.0
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/.docker/Dockerfile +6 -0
- data/.docker/ubuntu_setup.sh +22 -0
- data/.github/dependabot.yml +16 -0
- data/.github/release-drafter.yml +25 -0
- data/.github/workflows/ci.yml +98 -0
- data/.github/workflows/push.yml +12 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +63 -0
- data/.rubocop_todo.yml +630 -0
- data/CHANGELOG.md +1 -769
- data/CONTRIBUTING.md +2 -2
- data/Dangerfile +1 -1
- data/EXAMPLES.md +35 -13
- data/Gemfile +0 -12
- data/README.md +35 -17
- data/RELEASING.md +4 -5
- data/Rakefile +3 -10
- data/docker-compose.yml +8 -0
- data/examples/simple_connection.rb +9 -0
- data/lib/sshkit/backends/abstract.rb +9 -6
- data/lib/sshkit/backends/connection_pool/cache.rb +12 -5
- data/lib/sshkit/backends/connection_pool.rb +8 -4
- data/lib/sshkit/backends/netssh/known_hosts.rb +8 -8
- data/lib/sshkit/backends/netssh/scp_transfer.rb +26 -0
- data/lib/sshkit/backends/netssh/sftp_transfer.rb +46 -0
- data/lib/sshkit/backends/netssh.rb +38 -8
- data/lib/sshkit/command.rb +18 -13
- data/lib/sshkit/deprecation_logger.rb +2 -0
- data/lib/sshkit/host.rb +31 -4
- data/lib/sshkit/version.rb +1 -1
- data/sshkit.gemspec +6 -1
- data/test/functional/backends/netssh_transfer_tests.rb +83 -0
- data/test/functional/backends/test_local.rb +18 -0
- data/test/functional/backends/test_netssh.rb +24 -79
- data/test/functional/backends/test_netssh_scp.rb +23 -0
- data/test/functional/backends/test_netssh_sftp.rb +23 -0
- data/test/helper.rb +5 -43
- data/test/support/docker_wrapper.rb +71 -0
- data/test/unit/backends/test_abstract.rb +13 -1
- data/test/unit/backends/test_netssh.rb +55 -0
- data/test/unit/formatters/test_pretty.rb +1 -1
- data/test/unit/test_command.rb +32 -7
- data/test/unit/test_command_map.rb +8 -8
- data/test/unit/test_deprecation_logger.rb +1 -1
- data/test/unit/test_host.rb +44 -0
- metadata +58 -18
- data/.travis.yml +0 -14
- data/Vagrantfile +0 -15
- data/test/boxes.json +0 -17
- data/test/functional/test_ssh_server_comes_up_for_functional_tests.rb +0 -24
- data/test/support/vagrant_wrapper.rb +0 -55
data/lib/sshkit/command.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'digest/sha1'
|
2
2
|
require 'securerandom'
|
3
|
+
require 'shellwords'
|
3
4
|
|
4
5
|
# @author Lee Hambley
|
5
6
|
module SSHKit
|
@@ -9,7 +10,7 @@ module SSHKit
|
|
9
10
|
|
10
11
|
Failed = Class.new(SSHKit::StandardError)
|
11
12
|
|
12
|
-
attr_reader :command, :args, :options, :started_at, :started, :exit_status, :full_stdout, :full_stderr
|
13
|
+
attr_reader :command, :args, :options, :started_at, :started, :exit_status, :full_stdout, :full_stderr, :uuid
|
13
14
|
|
14
15
|
# Initialize a new Command object
|
15
16
|
#
|
@@ -25,6 +26,7 @@ module SSHKit
|
|
25
26
|
@args = args
|
26
27
|
@options.symbolize_keys!
|
27
28
|
@stdout, @stderr, @full_stdout, @full_stderr = String.new, String.new, String.new, String.new
|
29
|
+
@uuid = Digest::SHA1.hexdigest(SecureRandom.random_bytes(10))[0..7]
|
28
30
|
end
|
29
31
|
|
30
32
|
def complete?
|
@@ -41,10 +43,6 @@ module SSHKit
|
|
41
43
|
@started = new_started
|
42
44
|
end
|
43
45
|
|
44
|
-
def uuid
|
45
|
-
@uuid ||= Digest::SHA1.hexdigest(SecureRandom.random_bytes(10))[0..7]
|
46
|
-
end
|
47
|
-
|
48
46
|
def success?
|
49
47
|
exit_status.nil? ? false : exit_status.to_i == 0
|
50
48
|
end
|
@@ -145,7 +143,7 @@ module SSHKit
|
|
145
143
|
|
146
144
|
def within(&_block)
|
147
145
|
return yield unless options[:in]
|
148
|
-
|
146
|
+
"cd #{self.class.shellescape_except_tilde(options[:in])} && #{yield}"
|
149
147
|
end
|
150
148
|
|
151
149
|
def environment_hash
|
@@ -161,28 +159,30 @@ module SSHKit
|
|
161
159
|
end
|
162
160
|
|
163
161
|
def with(&_block)
|
164
|
-
|
165
|
-
|
162
|
+
env_string = environment_string
|
163
|
+
return yield if env_string.empty?
|
164
|
+
"( export #{env_string} ; #{yield} )"
|
166
165
|
end
|
167
166
|
|
168
167
|
def user(&_block)
|
169
168
|
return yield unless options[:user]
|
170
|
-
|
169
|
+
env_string = environment_string
|
170
|
+
"sudo -u #{options[:user].to_s.shellescape} #{env_string + " " unless env_string.empty?}-- sh -c #{yield.shellescape}"
|
171
171
|
end
|
172
172
|
|
173
173
|
def in_background(&_block)
|
174
174
|
return yield unless options[:run_in_background]
|
175
|
-
|
175
|
+
"( nohup #{yield} > /dev/null & )"
|
176
176
|
end
|
177
177
|
|
178
178
|
def umask(&_block)
|
179
179
|
return yield unless SSHKit.config.umask
|
180
|
-
|
180
|
+
"umask #{SSHKit.config.umask} && #{yield}"
|
181
181
|
end
|
182
182
|
|
183
183
|
def group(&_block)
|
184
184
|
return yield unless options[:group]
|
185
|
-
|
185
|
+
"sg #{options[:group].to_s.shellescape} -c #{yield.shellescape}"
|
186
186
|
# We could also use the so-called heredoc format perhaps:
|
187
187
|
#"newgrp #{options[:group]} <<EOC \\\"%s\\\" EOC" % %Q{#{yield}}
|
188
188
|
end
|
@@ -219,6 +219,11 @@ module SSHKit
|
|
219
219
|
end
|
220
220
|
end
|
221
221
|
|
222
|
+
# allow using home directory but escape everything else like spaces etc
|
223
|
+
def self.shellescape_except_tilde(file)
|
224
|
+
file.shellescape.gsub("\\~", "~")
|
225
|
+
end
|
226
|
+
|
222
227
|
private
|
223
228
|
|
224
229
|
def default_options
|
@@ -234,7 +239,7 @@ module SSHKit
|
|
234
239
|
|
235
240
|
def call_interaction_handler(stream_name, data, channel)
|
236
241
|
interaction_handler = options[:interaction_handler]
|
237
|
-
interaction_handler = MappingInteractionHandler.new(interaction_handler) if interaction_handler.kind_of?(Hash)
|
242
|
+
interaction_handler = MappingInteractionHandler.new(interaction_handler) if interaction_handler.kind_of?(Hash) or interaction_handler.kind_of?(Proc)
|
238
243
|
interaction_handler.on_data(self, stream_name, data, channel) if interaction_handler.respond_to?(:on_data)
|
239
244
|
end
|
240
245
|
|
data/lib/sshkit/host.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ostruct'
|
2
|
+
require 'resolv'
|
2
3
|
|
3
4
|
module SSHKit
|
4
5
|
|
@@ -7,6 +8,7 @@ module SSHKit
|
|
7
8
|
class Host
|
8
9
|
|
9
10
|
attr_accessor :password, :hostname, :port, :user, :ssh_options
|
11
|
+
attr_reader :transfer_method
|
10
12
|
|
11
13
|
def key=(new_key)
|
12
14
|
@keys = [new_key]
|
@@ -41,6 +43,12 @@ module SSHKit
|
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
46
|
+
def transfer_method=(method)
|
47
|
+
Backend::Netssh.assert_valid_transfer_method!(method) unless method.nil?
|
48
|
+
|
49
|
+
@transfer_method = method
|
50
|
+
end
|
51
|
+
|
44
52
|
def local?
|
45
53
|
@local
|
46
54
|
end
|
@@ -115,6 +123,22 @@ module SSHKit
|
|
115
123
|
|
116
124
|
end
|
117
125
|
|
126
|
+
# @private
|
127
|
+
# :nodoc:
|
128
|
+
class IPv6HostParser < SimpleHostParser
|
129
|
+
def self.suitable?(host_string)
|
130
|
+
host_string.match(Resolv::IPv6::Regex)
|
131
|
+
end
|
132
|
+
|
133
|
+
def port
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
def hostname
|
138
|
+
@host_string.match(Resolv::IPv6::Regex)[0]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
118
142
|
class HostWithPortParser < SimpleHostParser
|
119
143
|
|
120
144
|
def self.suitable?(host_string)
|
@@ -151,18 +175,20 @@ module SSHKit
|
|
151
175
|
# @private
|
152
176
|
# :nodoc:
|
153
177
|
class IPv6HostWithPortParser < SimpleHostParser
|
178
|
+
IPV6_REGEX = /\[([a-fA-F0-9:]+)\](?:\:(\d+))?/
|
154
179
|
|
155
180
|
def self.suitable?(host_string)
|
156
|
-
host_string.match(
|
181
|
+
host_string.match(IPV6_REGEX)
|
157
182
|
end
|
158
183
|
|
159
184
|
def port
|
160
|
-
@host_string.
|
185
|
+
prt = @host_string.match(IPV6_REGEX)[2]
|
186
|
+
prt = prt.to_i unless prt.nil?
|
187
|
+
prt
|
161
188
|
end
|
162
189
|
|
163
190
|
def hostname
|
164
|
-
@host_string.
|
165
|
-
@host_string.split(':')[0..-2].join(':')
|
191
|
+
@host_string.match(IPV6_REGEX)[1]
|
166
192
|
end
|
167
193
|
|
168
194
|
end
|
@@ -183,6 +209,7 @@ module SSHKit
|
|
183
209
|
|
184
210
|
PARSERS = [
|
185
211
|
SimpleHostParser,
|
212
|
+
IPv6HostParser,
|
186
213
|
HostWithPortParser,
|
187
214
|
HostWithUsernameAndPortParser,
|
188
215
|
IPv6HostWithPortParser,
|
data/lib/sshkit/version.rb
CHANGED
data/sshkit.gemspec
CHANGED
@@ -9,6 +9,9 @@ Gem::Specification.new do |gem|
|
|
9
9
|
gem.description = %q{A comprehensive toolkit for remotely running commands in a structured manner on groups of servers.}
|
10
10
|
gem.homepage = "http://github.com/capistrano/sshkit"
|
11
11
|
gem.license = "MIT"
|
12
|
+
gem.metadata = {
|
13
|
+
"changelog_uri" => "https://github.com/capistrano/sshkit/releases"
|
14
|
+
}
|
12
15
|
|
13
16
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
17
|
gem.files = `git ls-files`.split("\n")
|
@@ -17,13 +20,15 @@ Gem::Specification.new do |gem|
|
|
17
20
|
gem.require_paths = ["lib"]
|
18
21
|
gem.version = SSHKit::VERSION
|
19
22
|
|
23
|
+
gem.add_runtime_dependency('base64') if RUBY_VERSION >= "2.4"
|
20
24
|
gem.add_runtime_dependency('net-ssh', '>= 2.8.0')
|
21
25
|
gem.add_runtime_dependency('net-scp', '>= 1.1.2')
|
26
|
+
gem.add_runtime_dependency('net-sftp', '>= 2.1.2')
|
22
27
|
|
23
28
|
gem.add_development_dependency('danger')
|
24
29
|
gem.add_development_dependency('minitest', '>= 5.0.0')
|
25
30
|
gem.add_development_dependency('minitest-reporters')
|
26
|
-
gem.add_development_dependency('rainbow', '~> 2.
|
31
|
+
gem.add_development_dependency('rainbow', '~> 2.2.2')
|
27
32
|
gem.add_development_dependency('rake')
|
28
33
|
gem.add_development_dependency('rubocop', "~> 0.49.1")
|
29
34
|
gem.add_development_dependency('mocha')
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module SSHKit
|
4
|
+
module Backend
|
5
|
+
module NetsshTransferTests
|
6
|
+
def setup
|
7
|
+
super
|
8
|
+
@output = String.new
|
9
|
+
SSHKit.config.output_verbosity = :debug
|
10
|
+
SSHKit.config.output = SSHKit::Formatter::SimpleText.new(@output)
|
11
|
+
end
|
12
|
+
|
13
|
+
def a_host
|
14
|
+
DockerWrapper.host
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_upload_and_then_capture_file_contents
|
18
|
+
actual_file_contents = ""
|
19
|
+
file_name = File.join("/tmp", SecureRandom.uuid)
|
20
|
+
File.open file_name, 'w+' do |f|
|
21
|
+
f.write "Some Content\nWith a newline and trailing spaces \n "
|
22
|
+
end
|
23
|
+
Netssh.new(a_host) do
|
24
|
+
upload!(file_name, file_name)
|
25
|
+
actual_file_contents = capture(:cat, file_name, strip: false)
|
26
|
+
end.run
|
27
|
+
assert_equal "Some Content\nWith a newline and trailing spaces \n ", actual_file_contents
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_upload_within
|
31
|
+
file_name = SecureRandom.uuid
|
32
|
+
file_contents = "Some Content"
|
33
|
+
dir_name = SecureRandom.uuid
|
34
|
+
actual_file_contents = ""
|
35
|
+
Netssh.new(a_host) do |_host|
|
36
|
+
within("/tmp") do
|
37
|
+
execute :mkdir, "-p", dir_name
|
38
|
+
within(dir_name) do
|
39
|
+
upload!(StringIO.new(file_contents), file_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
actual_file_contents = capture(:cat, "/tmp/#{dir_name}/#{file_name}", strip: false)
|
43
|
+
end.run
|
44
|
+
assert_equal file_contents, actual_file_contents
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_upload_string_io
|
48
|
+
file_contents = ""
|
49
|
+
Netssh.new(a_host) do |_host|
|
50
|
+
file_name = File.join("/tmp", SecureRandom.uuid)
|
51
|
+
upload!(StringIO.new('example_io'), file_name)
|
52
|
+
file_contents = download!(file_name)
|
53
|
+
end.run
|
54
|
+
assert_equal "example_io", file_contents
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_upload_large_file
|
58
|
+
size = 25
|
59
|
+
fills = SecureRandom.random_bytes(1024*1024)
|
60
|
+
file_name = "/tmp/file-#{size}.txt"
|
61
|
+
File.open(file_name, 'wb') do |f|
|
62
|
+
(size).times {f.write(fills) }
|
63
|
+
end
|
64
|
+
file_contents = ""
|
65
|
+
Netssh.new(a_host) do
|
66
|
+
upload!(file_name, file_name)
|
67
|
+
file_contents = download!(file_name)
|
68
|
+
end.run
|
69
|
+
assert_equal File.open(file_name, 'rb').read, file_contents
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_upload_via_pathname
|
73
|
+
file_contents = ""
|
74
|
+
Netssh.new(a_host) do |_host|
|
75
|
+
file_name = Pathname.new(File.join("/tmp", SecureRandom.uuid))
|
76
|
+
upload!(StringIO.new('example_io'), file_name)
|
77
|
+
file_contents = download!(file_name)
|
78
|
+
end.run
|
79
|
+
assert_equal "example_io", file_contents
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -99,6 +99,24 @@ module SSHKit
|
|
99
99
|
end.run
|
100
100
|
assert_equal("Enter Data\nCaptured SOME DATA", captured_command_result)
|
101
101
|
end
|
102
|
+
|
103
|
+
def test_interaction_handler_with_proc
|
104
|
+
captured_command_result = nil
|
105
|
+
Local.new do
|
106
|
+
command = 'echo Enter Data; read the_data; echo Captured $the_data;'
|
107
|
+
captured_command_result = capture(command, interaction_handler:
|
108
|
+
lambda { |data|
|
109
|
+
case data
|
110
|
+
when "Enter Data\n"
|
111
|
+
"SOME DATA\n"
|
112
|
+
when "Captured SOME DATA\n"
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
}
|
116
|
+
)
|
117
|
+
end.run
|
118
|
+
assert_equal("Enter Data\nCaptured SOME DATA", captured_command_result)
|
119
|
+
end
|
102
120
|
end
|
103
121
|
end
|
104
122
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'securerandom'
|
3
2
|
require 'benchmark'
|
4
3
|
|
5
4
|
module SSHKit
|
@@ -16,7 +15,7 @@ module SSHKit
|
|
16
15
|
end
|
17
16
|
|
18
17
|
def a_host
|
19
|
-
|
18
|
+
DockerWrapper.host
|
20
19
|
end
|
21
20
|
|
22
21
|
def test_simple_netssh
|
@@ -38,7 +37,7 @@ module SSHKit
|
|
38
37
|
"Command: /usr/bin/env ls -l\n",
|
39
38
|
"Command: if test ! -d /tmp; then echo \"Directory does not exist '/tmp'\" 1>&2; false; fi\n",
|
40
39
|
"Command: if ! sudo -u root whoami > /dev/null; then echo \"You cannot switch to user 'root' using sudo, please check the sudoers file\" 1>&2; false; fi\n",
|
41
|
-
"Command: cd /tmp && ( export RAILS_ENV=\"production\" ; sudo -u root RAILS_ENV=\"production\" -- sh -c
|
40
|
+
"Command: cd /tmp && ( export RAILS_ENV=\"production\" ; sudo -u root RAILS_ENV=\"production\" -- sh -c /usr/bin/env\\ touch\\ restart.txt )\n"
|
42
41
|
], command_lines
|
43
42
|
end
|
44
43
|
|
@@ -75,14 +74,14 @@ module SSHKit
|
|
75
74
|
|
76
75
|
def test_group_netssh
|
77
76
|
Netssh.new(a_host) do
|
78
|
-
as user: :root, group: :
|
77
|
+
as user: :root, group: :root do
|
79
78
|
execute :touch, 'restart.txt'
|
80
79
|
end
|
81
80
|
end.run
|
82
81
|
command_lines = @output.lines.select { |line| line.start_with?('Command:') }
|
83
82
|
assert_equal [
|
84
83
|
"Command: if ! sudo -u root whoami > /dev/null; then echo \"You cannot switch to user 'root' using sudo, please check the sudoers file\" 1>&2; false; fi\n",
|
85
|
-
"Command: sudo -u root -- sh -c
|
84
|
+
"Command: sudo -u root -- sh -c sg\\ root\\ -c\\ /usr/bin/env\\\\\\ touch\\\\\\ restart.txt\n"
|
86
85
|
], command_lines
|
87
86
|
end
|
88
87
|
|
@@ -96,21 +95,18 @@ module SSHKit
|
|
96
95
|
end
|
97
96
|
|
98
97
|
def test_ssh_option_merge
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
{ paranoid: true }
|
103
|
-
end
|
104
|
-
a_host.ssh_options = verify_host_opt
|
98
|
+
keepalive_opt = { keepalive: true }
|
99
|
+
test_host = a_host.dup
|
100
|
+
test_host.ssh_options = keepalive_opt
|
105
101
|
host_ssh_options = {}
|
106
102
|
SSHKit::Backend::Netssh.config.ssh_options = { forward_agent: false }
|
107
|
-
Netssh.new(
|
103
|
+
Netssh.new(test_host) do |host|
|
108
104
|
capture(:uname)
|
109
105
|
host_ssh_options = host.ssh_options
|
110
106
|
end.run
|
111
|
-
assert_equal [:forward_agent, *
|
107
|
+
assert_equal [:forward_agent, *keepalive_opt.keys, :known_hosts, :logger, :password_prompt].sort, host_ssh_options.keys.sort
|
112
108
|
assert_equal false, host_ssh_options[:forward_agent]
|
113
|
-
assert_equal
|
109
|
+
assert_equal keepalive_opt.values.first, host_ssh_options[keepalive_opt.keys.first]
|
114
110
|
assert_instance_of SSHKit::Backend::Netssh::KnownHosts, host_ssh_options[:known_hosts]
|
115
111
|
end
|
116
112
|
|
@@ -139,71 +135,6 @@ module SSHKit
|
|
139
135
|
end.run
|
140
136
|
end
|
141
137
|
|
142
|
-
def test_upload_and_then_capture_file_contents
|
143
|
-
actual_file_contents = ""
|
144
|
-
file_name = File.join("/tmp", SecureRandom.uuid)
|
145
|
-
File.open file_name, 'w+' do |f|
|
146
|
-
f.write "Some Content\nWith a newline and trailing spaces \n "
|
147
|
-
end
|
148
|
-
Netssh.new(a_host) do
|
149
|
-
upload!(file_name, file_name)
|
150
|
-
actual_file_contents = capture(:cat, file_name, strip: false)
|
151
|
-
end.run
|
152
|
-
assert_equal "Some Content\nWith a newline and trailing spaces \n ", actual_file_contents
|
153
|
-
end
|
154
|
-
|
155
|
-
def test_upload_within
|
156
|
-
file_name = SecureRandom.uuid
|
157
|
-
file_contents = "Some Content"
|
158
|
-
dir_name = SecureRandom.uuid
|
159
|
-
actual_file_contents = ""
|
160
|
-
Netssh.new(a_host) do |_host|
|
161
|
-
within("/tmp") do
|
162
|
-
execute :mkdir, "-p", dir_name
|
163
|
-
within(dir_name) do
|
164
|
-
upload!(StringIO.new(file_contents), file_name)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
actual_file_contents = capture(:cat, "/tmp/#{dir_name}/#{file_name}", strip: false)
|
168
|
-
end.run
|
169
|
-
assert_equal file_contents, actual_file_contents
|
170
|
-
end
|
171
|
-
|
172
|
-
def test_upload_string_io
|
173
|
-
file_contents = ""
|
174
|
-
Netssh.new(a_host) do |_host|
|
175
|
-
file_name = File.join("/tmp", SecureRandom.uuid)
|
176
|
-
upload!(StringIO.new('example_io'), file_name)
|
177
|
-
file_contents = download!(file_name)
|
178
|
-
end.run
|
179
|
-
assert_equal "example_io", file_contents
|
180
|
-
end
|
181
|
-
|
182
|
-
def test_upload_large_file
|
183
|
-
size = 25
|
184
|
-
fills = SecureRandom.random_bytes(1024*1024)
|
185
|
-
file_name = "/tmp/file-#{size}.txt"
|
186
|
-
File.open(file_name, 'w') do |f|
|
187
|
-
(size).times {f.write(fills) }
|
188
|
-
end
|
189
|
-
file_contents = ""
|
190
|
-
Netssh.new(a_host) do
|
191
|
-
upload!(file_name, file_name)
|
192
|
-
file_contents = download!(file_name)
|
193
|
-
end.run
|
194
|
-
assert_equal File.open(file_name).read, file_contents
|
195
|
-
end
|
196
|
-
|
197
|
-
def test_upload_via_pathname
|
198
|
-
file_contents = ""
|
199
|
-
Netssh.new(a_host) do |_host|
|
200
|
-
file_name = Pathname.new(File.join("/tmp", SecureRandom.uuid))
|
201
|
-
upload!(StringIO.new('example_io'), file_name)
|
202
|
-
file_contents = download!(file_name)
|
203
|
-
end.run
|
204
|
-
assert_equal "example_io", file_contents
|
205
|
-
end
|
206
|
-
|
207
138
|
def test_interaction_handler
|
208
139
|
captured_command_result = nil
|
209
140
|
Netssh.new(a_host) do
|
@@ -215,6 +146,20 @@ module SSHKit
|
|
215
146
|
end.run
|
216
147
|
assert_equal("Enter Data\nCaptured SOME DATA", captured_command_result)
|
217
148
|
end
|
149
|
+
|
150
|
+
def test_connection_pool_keepalive
|
151
|
+
# ensure we enable connection pool
|
152
|
+
SSHKit::Backend::Netssh.pool.idle_timeout = 10
|
153
|
+
Netssh.new(a_host) do |_host|
|
154
|
+
test :false
|
155
|
+
end.run
|
156
|
+
sleep 2.5
|
157
|
+
captured_command_result = nil
|
158
|
+
Netssh.new(a_host) do |_host|
|
159
|
+
captured_command_result = capture(:echo, 'some_value')
|
160
|
+
end.run
|
161
|
+
assert_equal "some_value", captured_command_result
|
162
|
+
end
|
218
163
|
end
|
219
164
|
|
220
165
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require_relative 'netssh_transfer_tests'
|
3
|
+
|
4
|
+
module SSHKit
|
5
|
+
module Backend
|
6
|
+
class TestNetsshScp < FunctionalTest
|
7
|
+
include NetsshTransferTests
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
SSHKit::Backend::Netssh.configure do |ssh|
|
12
|
+
ssh.transfer_method = :scp
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_scp_implementation_is_used
|
17
|
+
Netssh.new(a_host).send(:with_transfer, nil) do |transfer|
|
18
|
+
assert_instance_of Netssh::ScpTransfer, transfer
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require_relative 'netssh_transfer_tests'
|
3
|
+
|
4
|
+
module SSHKit
|
5
|
+
module Backend
|
6
|
+
class TestNetsshSftp < FunctionalTest
|
7
|
+
include NetsshTransferTests
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
SSHKit::Backend::Netssh.configure do |ssh|
|
12
|
+
ssh.transfer_method = :sftp
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_sftp_implementation_is_used
|
17
|
+
Netssh.new(a_host).send(:with_transfer, nil) do |transfer|
|
18
|
+
assert_instance_of Netssh::SftpTransfer, transfer
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/test/helper.rb
CHANGED
@@ -3,7 +3,7 @@ require 'bundler/setup'
|
|
3
3
|
require 'tempfile'
|
4
4
|
require 'minitest/autorun'
|
5
5
|
require 'minitest/reporters'
|
6
|
-
require 'mocha/
|
6
|
+
require 'mocha/minitest'
|
7
7
|
require 'stringio'
|
8
8
|
require 'json'
|
9
9
|
|
@@ -28,51 +28,13 @@ class UnitTest < Minitest::Test
|
|
28
28
|
end
|
29
29
|
|
30
30
|
class FunctionalTest < Minitest::Test
|
31
|
-
|
32
31
|
def setup
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def create_user_with_key(username, password = :secret)
|
41
|
-
username, password = username.to_s, password.to_s
|
42
|
-
|
43
|
-
keys = VagrantWrapper.hosts.collect do |_name, host|
|
44
|
-
Net::SSH.start(host.hostname, host.user, port: host.port, password: host.password) do |ssh|
|
45
|
-
|
46
|
-
# Remove the user, make it again, force-generate a key for him
|
47
|
-
# short keys save us a few microseconds
|
48
|
-
ssh.exec!("sudo userdel -rf #{username}; true") # The `rescue nil` of the shell world
|
49
|
-
ssh.exec!("sudo useradd -m #{username}")
|
50
|
-
ssh.exec!("sudo echo y | ssh-keygen -b 1024 -f #{username} -N ''")
|
51
|
-
ssh.exec!("sudo chown vagrant:vagrant #{username}*")
|
52
|
-
ssh.exec!("sudo echo #{username}:#{password} | chpasswd")
|
32
|
+
require_relative "support/docker_wrapper"
|
33
|
+
return if DockerWrapper.running?
|
53
34
|
|
54
|
-
|
55
|
-
|
56
|
-
ssh.exec!("sudo chown #{username}:#{username} /home/#{username}/.ssh")
|
57
|
-
ssh.exec!("sudo chmod 700 /home/#{username}/.ssh")
|
58
|
-
|
59
|
-
# Move the key to authorized keys and chown and chmod it
|
60
|
-
ssh.exec!("sudo cat #{username}.pub > /home/#{username}/.ssh/authorized_keys")
|
61
|
-
ssh.exec!("sudo chown #{username}:#{username} /home/#{username}/.ssh/authorized_keys")
|
62
|
-
ssh.exec!("sudo chmod 600 /home/#{username}/.ssh/authorized_keys")
|
63
|
-
|
64
|
-
key = ssh.exec!("cat /home/vagrant/#{username}")
|
65
|
-
|
66
|
-
# Clean Up Files
|
67
|
-
ssh.exec!("sudo rm #{username} #{username}.pub")
|
68
|
-
|
69
|
-
key
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
Hash[VagrantWrapper.hosts.collect { |n, _h| n.to_sym }.zip(keys)]
|
35
|
+
DockerWrapper.start
|
36
|
+
DockerWrapper.wait_for_ssh_server
|
74
37
|
end
|
75
|
-
|
76
38
|
end
|
77
39
|
|
78
40
|
#
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
Minitest.after_run do
|
4
|
+
DockerWrapper.stop if DockerWrapper.running?
|
5
|
+
end
|
6
|
+
|
7
|
+
module DockerWrapper
|
8
|
+
SSH_SERVER_PORT = 2122
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def host
|
12
|
+
SSHKit::Host.new(
|
13
|
+
user: "deployer",
|
14
|
+
hostname: "localhost",
|
15
|
+
port: SSH_SERVER_PORT,
|
16
|
+
password: "topsecret",
|
17
|
+
ssh_options: host_verify_options
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def running?
|
22
|
+
out, status = run_compose_command("ps --status running", false)
|
23
|
+
status.success? && out.include?("ssh_server")
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
run_compose_command("up -d")
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
run_compose_command("down")
|
32
|
+
end
|
33
|
+
|
34
|
+
def wait_for_ssh_server(retries=3)
|
35
|
+
Socket.tcp("localhost", SSH_SERVER_PORT, connect_timeout: 1).close
|
36
|
+
sleep(1)
|
37
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
|
38
|
+
retries -= 1
|
39
|
+
sleep(2) && retry if retries.positive?
|
40
|
+
raise
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def run_compose_command(command, echo=true)
|
46
|
+
$stderr.puts "[docker compose] #{command}" if echo
|
47
|
+
Open3.popen2e("docker compose #{command}") do |stdin, outerr, wait_thread|
|
48
|
+
stdin.close
|
49
|
+
output = Thread.new { capture_stream(outerr, echo) }
|
50
|
+
[output.value, wait_thread.value]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def capture_stream(stream, echo=true)
|
55
|
+
buffer = String.new
|
56
|
+
while (line = stream.gets)
|
57
|
+
buffer << line
|
58
|
+
$stderr.puts("[docker compose] #{line}") if echo
|
59
|
+
end
|
60
|
+
buffer
|
61
|
+
end
|
62
|
+
|
63
|
+
def host_verify_options
|
64
|
+
if Net::SSH::Version::MAJOR >= 5
|
65
|
+
{ verify_host_key: :never }
|
66
|
+
else
|
67
|
+
{ paranoid: false }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|