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