sshkit 1.18.0 → 1.23.0

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