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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.docker/Dockerfile +6 -0
  3. data/.docker/ubuntu_setup.sh +22 -0
  4. data/.github/release-drafter.yml +10 -2
  5. data/.github/workflows/ci.yml +33 -20
  6. data/.github/workflows/push.yml +1 -1
  7. data/.gitignore +0 -1
  8. data/.rubocop.yml +6 -6
  9. data/.rubocop_todo.yml +3 -10
  10. data/CONTRIBUTING.md +2 -2
  11. data/EXAMPLES.md +25 -13
  12. data/Gemfile +0 -5
  13. data/README.md +2 -1
  14. data/RELEASING.md +2 -2
  15. data/Rakefile +0 -4
  16. data/docker-compose.yml +8 -0
  17. data/lib/sshkit/backends/abstract.rb +3 -4
  18. data/lib/sshkit/backends/connection_pool/cache.rb +2 -2
  19. data/lib/sshkit/backends/connection_pool.rb +0 -1
  20. data/lib/sshkit/backends/netssh/known_hosts.rb +8 -8
  21. data/lib/sshkit/backends/netssh/scp_transfer.rb +26 -0
  22. data/lib/sshkit/backends/netssh/sftp_transfer.rb +46 -0
  23. data/lib/sshkit/backends/netssh.rb +36 -7
  24. data/lib/sshkit/color.rb +2 -2
  25. data/lib/sshkit/host.rb +25 -0
  26. data/lib/sshkit/runners/parallel.rb +0 -2
  27. data/lib/sshkit/version.rb +1 -1
  28. data/sshkit.gemspec +6 -2
  29. data/test/functional/backends/netssh_transfer_tests.rb +83 -0
  30. data/test/functional/backends/test_netssh.rb +5 -71
  31. data/test/functional/backends/test_netssh_scp.rb +23 -0
  32. data/test/functional/backends/test_netssh_sftp.rb +23 -0
  33. data/test/helper.rb +4 -42
  34. data/test/support/docker_wrapper.rb +71 -0
  35. data/test/unit/backends/test_abstract.rb +57 -2
  36. data/test/unit/backends/test_netssh.rb +48 -0
  37. data/test/unit/test_command.rb +2 -1
  38. data/test/unit/test_command_map.rb +8 -8
  39. data/test/unit/test_configuration.rb +2 -1
  40. data/test/unit/test_deprecation_logger.rb +1 -1
  41. data/test/unit/test_host.rb +39 -0
  42. metadata +74 -29
  43. data/Vagrantfile +0 -20
  44. data/test/boxes.json +0 -17
  45. data/test/functional/test_ssh_server_comes_up_for_functional_tests.rb +0 -24
  46. 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
- with_ssh do |ssh|
68
- ssh.scp.upload!(local, remote, options, &summarizer)
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
- with_ssh do |ssh|
76
- ssh.scp.download!(remote, local, options, &summarizer)
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
- result << COLOR_CODES.fetch(color).to_s
48
- result << ";49m#{string}\e[0m"
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,
@@ -1,5 +1,3 @@
1
- require 'thread'
2
-
3
1
  module SSHKit
4
2
 
5
3
  module Runner
@@ -1,3 +1,3 @@
1
1
  module SSHKit
2
- VERSION = "1.21.4".freeze
2
+ VERSION = "1.25.0".freeze
3
3
  end
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.49.1")
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
- VagrantWrapper.hosts['one']
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: :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
 
@@ -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
- 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
@@ -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 `block in test_background_logs_deprecation_warnings'\)\n/, lines[1])
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