sshkit 1.21.4 → 1.25.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/release-drafter.yml +10 -2
- data/.github/workflows/ci.yml +33 -20
- data/.github/workflows/push.yml +1 -1
- data/.gitignore +0 -1
- data/.rubocop.yml +6 -6
- data/.rubocop_todo.yml +3 -10
- data/CONTRIBUTING.md +2 -2
- data/EXAMPLES.md +25 -13
- data/Gemfile +0 -5
- data/README.md +2 -1
- data/RELEASING.md +2 -2
- data/Rakefile +0 -4
- data/docker-compose.yml +8 -0
- data/lib/sshkit/backends/abstract.rb +3 -4
- data/lib/sshkit/backends/connection_pool/cache.rb +2 -2
- data/lib/sshkit/backends/connection_pool.rb +0 -1
- 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 +36 -7
- data/lib/sshkit/color.rb +2 -2
- data/lib/sshkit/host.rb +25 -0
- data/lib/sshkit/runners/parallel.rb +0 -2
- data/lib/sshkit/version.rb +1 -1
- data/sshkit.gemspec +6 -2
- data/test/functional/backends/netssh_transfer_tests.rb +83 -0
- data/test/functional/backends/test_netssh.rb +5 -71
- data/test/functional/backends/test_netssh_scp.rb +23 -0
- data/test/functional/backends/test_netssh_sftp.rb +23 -0
- data/test/helper.rb +4 -42
- data/test/support/docker_wrapper.rb +71 -0
- data/test/unit/backends/test_abstract.rb +57 -2
- data/test/unit/backends/test_netssh.rb +48 -0
- data/test/unit/test_command.rb +2 -1
- data/test/unit/test_command_map.rb +8 -8
- data/test/unit/test_configuration.rb +2 -1
- data/test/unit/test_deprecation_logger.rb +1 -1
- data/test/unit/test_host.rb +39 -0
- metadata +74 -29
- data/Vagrantfile +0 -20
- 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 -64
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
require 'English'
|
|
2
2
|
require 'strscan'
|
|
3
|
-
require 'mutex_m'
|
|
4
3
|
require 'net/ssh'
|
|
5
|
-
require 'net/scp'
|
|
6
4
|
|
|
7
5
|
module Net
|
|
8
6
|
module SSH
|
|
@@ -23,10 +21,27 @@ module SSHKit
|
|
|
23
21
|
module Backend
|
|
24
22
|
|
|
25
23
|
class Netssh < Abstract
|
|
24
|
+
def self.assert_valid_transfer_method!(method)
|
|
25
|
+
return if [:scp, :sftp].include?(method)
|
|
26
|
+
|
|
27
|
+
raise ArgumentError, "#{method.inspect} is not a valid transfer method. Supported methods are :scp, :sftp."
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
class Configuration
|
|
27
31
|
attr_accessor :connection_timeout, :pty
|
|
32
|
+
attr_reader :transfer_method
|
|
28
33
|
attr_writer :ssh_options
|
|
29
34
|
|
|
35
|
+
def initialize
|
|
36
|
+
self.transfer_method = :scp
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def transfer_method=(method)
|
|
40
|
+
Netssh.assert_valid_transfer_method!(method)
|
|
41
|
+
|
|
42
|
+
@transfer_method = method
|
|
43
|
+
end
|
|
44
|
+
|
|
30
45
|
def ssh_options
|
|
31
46
|
default_options.merge(@ssh_options ||= {})
|
|
32
47
|
end
|
|
@@ -64,16 +79,16 @@ module SSHKit
|
|
|
64
79
|
def upload!(local, remote, options = {})
|
|
65
80
|
summarizer = transfer_summarizer('Uploading', options)
|
|
66
81
|
remote = File.join(pwd_path, remote) unless remote.to_s.start_with?("/") || pwd_path.nil?
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
with_transfer(summarizer) do |transfer|
|
|
83
|
+
transfer.upload!(local, remote, options)
|
|
69
84
|
end
|
|
70
85
|
end
|
|
71
86
|
|
|
72
87
|
def download!(remote, local=nil, options = {})
|
|
73
88
|
summarizer = transfer_summarizer('Downloading', options)
|
|
74
89
|
remote = File.join(pwd_path, remote) unless remote.to_s.start_with?("/") || pwd_path.nil?
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
with_transfer(summarizer) do |transfer|
|
|
91
|
+
transfer.download!(remote, local, options)
|
|
77
92
|
end
|
|
78
93
|
end
|
|
79
94
|
|
|
@@ -105,7 +120,7 @@ module SSHKit
|
|
|
105
120
|
last_percentage = nil
|
|
106
121
|
proc do |_ch, name, transferred, total|
|
|
107
122
|
percentage = (transferred.to_f * 100 / total.to_f)
|
|
108
|
-
unless percentage.nan?
|
|
123
|
+
unless percentage.nan? || percentage.infinite?
|
|
109
124
|
message = "#{action} #{name} #{percentage.round(2)}%"
|
|
110
125
|
percentage_r = (percentage / log_percent).truncate * log_percent
|
|
111
126
|
if percentage_r > 0 && (last_name != name || last_percentage != percentage_r)
|
|
@@ -183,6 +198,20 @@ module SSHKit
|
|
|
183
198
|
)
|
|
184
199
|
end
|
|
185
200
|
|
|
201
|
+
def with_transfer(summarizer)
|
|
202
|
+
transfer_method = host.transfer_method || self.class.config.transfer_method
|
|
203
|
+
transfer_class = if transfer_method == :sftp
|
|
204
|
+
require_relative "netssh/sftp_transfer"
|
|
205
|
+
SftpTransfer
|
|
206
|
+
else
|
|
207
|
+
require_relative "netssh/scp_transfer"
|
|
208
|
+
ScpTransfer
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
with_ssh do |ssh|
|
|
212
|
+
yield(transfer_class.new(ssh, summarizer))
|
|
213
|
+
end
|
|
214
|
+
end
|
|
186
215
|
end
|
|
187
216
|
end
|
|
188
217
|
|
data/lib/sshkit/color.rb
CHANGED
|
@@ -44,8 +44,8 @@ module SSHKit
|
|
|
44
44
|
return string unless COLOR_CODES.key?(color)
|
|
45
45
|
|
|
46
46
|
result = mode == :bold ? "\e[1;" : "\e[0;"
|
|
47
|
-
|
|
48
|
-
result
|
|
47
|
+
|
|
48
|
+
"#{result}#{COLOR_CODES.fetch(color)};49m#{string}\e[0m"
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Returns `true` if the underlying output is a tty, or if the SSHKIT_COLOR
|
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)
|
|
@@ -185,6 +209,7 @@ module SSHKit
|
|
|
185
209
|
|
|
186
210
|
PARSERS = [
|
|
187
211
|
SimpleHostParser,
|
|
212
|
+
IPv6HostParser,
|
|
188
213
|
HostWithPortParser,
|
|
189
214
|
HostWithUsernameAndPortParser,
|
|
190
215
|
IPv6HostWithPortParser,
|
data/lib/sshkit/version.rb
CHANGED
data/sshkit.gemspec
CHANGED
|
@@ -19,16 +19,20 @@ Gem::Specification.new do |gem|
|
|
|
19
19
|
gem.name = "sshkit"
|
|
20
20
|
gem.require_paths = ["lib"]
|
|
21
21
|
gem.version = SSHKit::VERSION
|
|
22
|
+
gem.required_ruby_version = ">= 2.5"
|
|
22
23
|
|
|
24
|
+
gem.add_runtime_dependency('base64')
|
|
25
|
+
gem.add_runtime_dependency('logger')
|
|
23
26
|
gem.add_runtime_dependency('net-ssh', '>= 2.8.0')
|
|
24
27
|
gem.add_runtime_dependency('net-scp', '>= 1.1.2')
|
|
28
|
+
gem.add_runtime_dependency('net-sftp', '>= 2.1.2')
|
|
29
|
+
gem.add_runtime_dependency('ostruct')
|
|
25
30
|
|
|
26
31
|
gem.add_development_dependency('danger')
|
|
27
32
|
gem.add_development_dependency('minitest', '>= 5.0.0')
|
|
28
33
|
gem.add_development_dependency('minitest-reporters')
|
|
29
|
-
gem.add_development_dependency('rainbow', '~> 2.2.2')
|
|
30
34
|
gem.add_development_dependency('rake')
|
|
31
|
-
gem.add_development_dependency('rubocop', "~> 0.
|
|
35
|
+
gem.add_development_dependency('rubocop', "~> 0.52.0")
|
|
32
36
|
gem.add_development_dependency('mocha')
|
|
33
37
|
|
|
34
38
|
gem.add_development_dependency('bcrypt_pbkdf')
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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 sg\\
|
|
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
|
|
|
@@ -132,75 +131,10 @@ module SSHKit
|
|
|
132
131
|
|
|
133
132
|
def test_test_does_not_raise_on_non_zero_exit_status
|
|
134
133
|
Netssh.new(a_host) do |_host|
|
|
135
|
-
test :false
|
|
134
|
+
test :false # rubocop:disable Lint/BooleanSymbol
|
|
136
135
|
end.run
|
|
137
136
|
end
|
|
138
137
|
|
|
139
|
-
def test_upload_and_then_capture_file_contents
|
|
140
|
-
actual_file_contents = ""
|
|
141
|
-
file_name = File.join("/tmp", SecureRandom.uuid)
|
|
142
|
-
File.open file_name, 'w+' do |f|
|
|
143
|
-
f.write "Some Content\nWith a newline and trailing spaces \n "
|
|
144
|
-
end
|
|
145
|
-
Netssh.new(a_host) do
|
|
146
|
-
upload!(file_name, file_name)
|
|
147
|
-
actual_file_contents = capture(:cat, file_name, strip: false)
|
|
148
|
-
end.run
|
|
149
|
-
assert_equal "Some Content\nWith a newline and trailing spaces \n ", actual_file_contents
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def test_upload_within
|
|
153
|
-
file_name = SecureRandom.uuid
|
|
154
|
-
file_contents = "Some Content"
|
|
155
|
-
dir_name = SecureRandom.uuid
|
|
156
|
-
actual_file_contents = ""
|
|
157
|
-
Netssh.new(a_host) do |_host|
|
|
158
|
-
within("/tmp") do
|
|
159
|
-
execute :mkdir, "-p", dir_name
|
|
160
|
-
within(dir_name) do
|
|
161
|
-
upload!(StringIO.new(file_contents), file_name)
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
actual_file_contents = capture(:cat, "/tmp/#{dir_name}/#{file_name}", strip: false)
|
|
165
|
-
end.run
|
|
166
|
-
assert_equal file_contents, actual_file_contents
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def test_upload_string_io
|
|
170
|
-
file_contents = ""
|
|
171
|
-
Netssh.new(a_host) do |_host|
|
|
172
|
-
file_name = File.join("/tmp", SecureRandom.uuid)
|
|
173
|
-
upload!(StringIO.new('example_io'), file_name)
|
|
174
|
-
file_contents = download!(file_name)
|
|
175
|
-
end.run
|
|
176
|
-
assert_equal "example_io", file_contents
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def test_upload_large_file
|
|
180
|
-
size = 25
|
|
181
|
-
fills = SecureRandom.random_bytes(1024*1024)
|
|
182
|
-
file_name = "/tmp/file-#{size}.txt"
|
|
183
|
-
File.open(file_name, 'wb') do |f|
|
|
184
|
-
(size).times {f.write(fills) }
|
|
185
|
-
end
|
|
186
|
-
file_contents = ""
|
|
187
|
-
Netssh.new(a_host) do
|
|
188
|
-
upload!(file_name, file_name)
|
|
189
|
-
file_contents = download!(file_name)
|
|
190
|
-
end.run
|
|
191
|
-
assert_equal File.open(file_name, 'rb').read, file_contents
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def test_upload_via_pathname
|
|
195
|
-
file_contents = ""
|
|
196
|
-
Netssh.new(a_host) do |_host|
|
|
197
|
-
file_name = Pathname.new(File.join("/tmp", SecureRandom.uuid))
|
|
198
|
-
upload!(StringIO.new('example_io'), file_name)
|
|
199
|
-
file_contents = download!(file_name)
|
|
200
|
-
end.run
|
|
201
|
-
assert_equal "example_io", file_contents
|
|
202
|
-
end
|
|
203
|
-
|
|
204
138
|
def test_interaction_handler
|
|
205
139
|
captured_command_result = nil
|
|
206
140
|
Netssh.new(a_host) do
|
|
@@ -217,7 +151,7 @@ module SSHKit
|
|
|
217
151
|
# ensure we enable connection pool
|
|
218
152
|
SSHKit::Backend::Netssh.pool.idle_timeout = 10
|
|
219
153
|
Netssh.new(a_host) do |_host|
|
|
220
|
-
test :false
|
|
154
|
+
test :false # rubocop:disable Lint/BooleanSymbol
|
|
221
155
|
end.run
|
|
222
156
|
sleep 2.5
|
|
223
157
|
captured_command_result = nil
|
|
@@ -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
|
@@ -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
|
|
@@ -111,8 +111,63 @@ module SSHKit
|
|
|
111
111
|
assert_equal 'cd ~/foo && /usr/bin/env cat file', backend.executed_command.to_command
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
def test_as_properly_clears
|
|
115
|
+
backend = ExampleBackend.new do
|
|
116
|
+
as :root do
|
|
117
|
+
execute :cat, 'file', :strip => false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
execute :cat, 'file', :strip => false
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
backend.run
|
|
124
|
+
|
|
125
|
+
assert_equal '/usr/bin/env cat file', backend.executed_command.to_command
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_as_root
|
|
129
|
+
backend = ExampleBackend.new do
|
|
130
|
+
as :root do
|
|
131
|
+
execute :cat, 'file', :strip => false
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
backend.run
|
|
136
|
+
|
|
137
|
+
assert_equal 'sudo -u root -- sh -c /usr/bin/env\\ cat\\ file', backend.executed_command.to_command
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_nested_as
|
|
141
|
+
backend = ExampleBackend.new do
|
|
142
|
+
as :root do
|
|
143
|
+
as :other_user do
|
|
144
|
+
execute :cat, 'file', :strip => false
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
backend.run
|
|
150
|
+
|
|
151
|
+
assert_equal 'sudo -u other_user -- sh -c /usr/bin/env\\ cat\\ file', backend.executed_command.to_command
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_nested_as_properly_clears
|
|
155
|
+
backend = ExampleBackend.new do
|
|
156
|
+
as :root do
|
|
157
|
+
as :other_user do
|
|
158
|
+
execute :cat, 'file', :strip => false
|
|
159
|
+
end
|
|
160
|
+
execute :cat, 'file', :strip => false
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
backend.run
|
|
165
|
+
|
|
166
|
+
assert_equal 'sudo -u root -- sh -c /usr/bin/env\\ cat\\ file', backend.executed_command.to_command
|
|
167
|
+
end
|
|
168
|
+
|
|
114
169
|
def test_background_logs_deprecation_warnings
|
|
115
|
-
deprecation_out = ''
|
|
170
|
+
deprecation_out = +''
|
|
116
171
|
SSHKit.config.deprecation_output = deprecation_out
|
|
117
172
|
|
|
118
173
|
ExampleBackend.new do
|
|
@@ -124,7 +179,7 @@ module SSHKit
|
|
|
124
179
|
assert_equal 2, lines.length
|
|
125
180
|
|
|
126
181
|
assert_equal("[Deprecated] The background method is deprecated. Blame badly behaved pseudo-daemons!\n", lines[0])
|
|
127
|
-
assert_match(/ \(Called from.*test_abstract.rb:\d+:in
|
|
182
|
+
assert_match(/ \(Called from.*test_abstract.rb:\d+:in .block in .*test_background_logs_deprecation_warnings.\)\n/, lines[1])
|
|
128
183
|
end
|
|
129
184
|
|
|
130
185
|
def test_calling_abstract_with_undefined_execute_command_raises_exception
|