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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.docker/Dockerfile +6 -0
  3. data/.docker/ubuntu_setup.sh +22 -0
  4. data/.github/dependabot.yml +16 -0
  5. data/.github/release-drafter.yml +25 -0
  6. data/.github/workflows/ci.yml +98 -0
  7. data/.github/workflows/push.yml +12 -0
  8. data/.gitignore +0 -1
  9. data/.rubocop.yml +63 -0
  10. data/.rubocop_todo.yml +630 -0
  11. data/CHANGELOG.md +1 -769
  12. data/CONTRIBUTING.md +2 -2
  13. data/Dangerfile +1 -1
  14. data/EXAMPLES.md +35 -13
  15. data/Gemfile +0 -12
  16. data/README.md +35 -17
  17. data/RELEASING.md +4 -5
  18. data/Rakefile +3 -10
  19. data/docker-compose.yml +8 -0
  20. data/examples/simple_connection.rb +9 -0
  21. data/lib/sshkit/backends/abstract.rb +9 -6
  22. data/lib/sshkit/backends/connection_pool/cache.rb +12 -5
  23. data/lib/sshkit/backends/connection_pool.rb +8 -4
  24. data/lib/sshkit/backends/netssh/known_hosts.rb +8 -8
  25. data/lib/sshkit/backends/netssh/scp_transfer.rb +26 -0
  26. data/lib/sshkit/backends/netssh/sftp_transfer.rb +46 -0
  27. data/lib/sshkit/backends/netssh.rb +38 -8
  28. data/lib/sshkit/command.rb +18 -13
  29. data/lib/sshkit/deprecation_logger.rb +2 -0
  30. data/lib/sshkit/host.rb +31 -4
  31. data/lib/sshkit/version.rb +1 -1
  32. data/sshkit.gemspec +6 -1
  33. data/test/functional/backends/netssh_transfer_tests.rb +83 -0
  34. data/test/functional/backends/test_local.rb +18 -0
  35. data/test/functional/backends/test_netssh.rb +24 -79
  36. data/test/functional/backends/test_netssh_scp.rb +23 -0
  37. data/test/functional/backends/test_netssh_sftp.rb +23 -0
  38. data/test/helper.rb +5 -43
  39. data/test/support/docker_wrapper.rb +71 -0
  40. data/test/unit/backends/test_abstract.rb +13 -1
  41. data/test/unit/backends/test_netssh.rb +55 -0
  42. data/test/unit/formatters/test_pretty.rb +1 -1
  43. data/test/unit/test_command.rb +32 -7
  44. data/test/unit/test_command_map.rb +8 -8
  45. data/test/unit/test_deprecation_logger.rb +1 -1
  46. data/test/unit/test_host.rb +44 -0
  47. metadata +58 -18
  48. data/.travis.yml +0 -14
  49. data/Vagrantfile +0 -15
  50. data/test/boxes.json +0 -17
  51. data/test/functional/test_ssh_server_comes_up_for_functional_tests.rb +0 -24
  52. data/test/support/vagrant_wrapper.rb +0 -55
@@ -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
- sprintf("cd #{options[:in]} && %s", yield)
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
- return yield unless environment_hash.any?
165
- "( export #{environment_string} ; #{yield} )"
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
- "sudo -u #{options[:user]} #{environment_string + " " unless environment_string.empty?}-- sh -c '#{yield}'"
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
- sprintf("( nohup %s > /dev/null & )", yield)
175
+ "( nohup #{yield} > /dev/null & )"
176
176
  end
177
177
 
178
178
  def umask(&_block)
179
179
  return yield unless SSHKit.config.umask
180
- sprintf("umask #{SSHKit.config.umask} && %s", yield)
180
+ "umask #{SSHKit.config.umask} && #{yield}"
181
181
  end
182
182
 
183
183
  def group(&_block)
184
184
  return yield unless options[:group]
185
- %Q(sg #{options[:group]} -c "#{yield}")
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
 
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module SSHKit
2
4
  class DeprecationLogger
3
5
  def initialize(out)
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(/[a-fA-F0-9:]+:\d+/)
181
+ host_string.match(IPV6_REGEX)
157
182
  end
158
183
 
159
184
  def port
160
- @host_string.split(':').last.to_i
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.gsub!(/\[|\]/, '')
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,
@@ -1,3 +1,3 @@
1
1
  module SSHKit
2
- VERSION = "1.18.0".freeze
2
+ VERSION = "1.23.0".freeze
3
3
  end
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.1.0')
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
- VagrantWrapper.hosts['one']
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 '/usr/bin/env touch restart.txt' )\n"
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: :admin do
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 admin -c \"/usr/bin/env touch restart.txt\"'\n"
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
- verify_host_opt = if Net::SSH::Version::MAJOR >= 5
100
- { verify_host_key: :always }
101
- else
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(a_host) do |host|
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, *verify_host_opt.keys, :known_hosts, :logger, :password_prompt].sort, host_ssh_options.keys.sort
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 verify_host_opt.values.first, host_ssh_options[verify_host_opt.keys.first]
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/setup'
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
- unless VagrantWrapper.running?
34
- warn "Vagrant VMs are not running. Please, start it manually with `vagrant up`"
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
- # Make the .ssh directory, change the ownership and the
55
- ssh.exec!("sudo mkdir -p /home/#{username}/.ssh")
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