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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42ea735e4b1e6b51eb99c2699ecc11579229fffe3db99cec9afe4e819f466bf0
4
- data.tar.gz: 74af7dc3101e5bb9853449f5ffc1f3de4a0ad0a3d19f5554540e3eff3e4f99d7
3
+ metadata.gz: ab4c2342f826356f08f688cb17f29323b90c4a7ecc85836b1ceaf323c34ab13d
4
+ data.tar.gz: 1126ee211260588192c298847ce2a00b43c356d25751a13c6268f324b822d8a9
5
5
  SHA512:
6
- metadata.gz: 1d78e3c1a8e8c875b5eef3f3951540817e82cd4ed07e84ddec648f5b6b85d9c38b09493ab476d5a2e728b44d62e56d1d5b5cadd5359c422371f9a1d8773e3b7c
7
- data.tar.gz: 5f7cdad196d09216c388d8ba95feb65f1299c3fc5deda5ebf7adbf95630621592011f1ebb64fe0405017aa6ebc5a5a73e42852e3b4d90cb865585a08f2ff3031
6
+ metadata.gz: a6321a43251bc31e117660e049e53b35dec7154119a9fac2dce0e4eb855f8acc6b5539cbfe696e53457f8f6784eb8eebfa95a71c79e7b1a3a45b093d9e89e5ee
7
+ data.tar.gz: 41d021df58a4d4cd5f88762d29491fd71c603a97f31ac939d34825a42a27d1576dce5410cd6da7384833bb98fc107df1dfdaa4ca379d226b4f6417171ea2057f
@@ -0,0 +1,6 @@
1
+ FROM ubuntu:22.04
2
+ WORKDIR /provision
3
+ COPY ./ubuntu_setup.sh ./
4
+ RUN ./ubuntu_setup.sh
5
+ EXPOSE 22
6
+ CMD ["/usr/sbin/sshd", "-D"]
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ export DEBIAN_FRONTEND=noninteractive
6
+ apt -y update
7
+
8
+ # Create `deployer` user that can sudo without a password
9
+ apt-get -y install sudo
10
+ adduser --disabled-password deployer < /dev/null
11
+ echo "deployer:topsecret" | chpasswd
12
+ echo "deployer ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
13
+
14
+ # Install and configure sshd
15
+ apt-get -y install openssh-server
16
+ {
17
+ echo "Port 22"
18
+ echo "PasswordAuthentication yes"
19
+ echo "ChallengeResponseAuthentication no"
20
+ } >> /etc/ssh/sshd_config
21
+ mkdir /var/run/sshd
22
+ chmod 0755 /var/run/sshd
@@ -1,5 +1,5 @@
1
- name-template: "$NEXT_PATCH_VERSION"
2
- tag-template: "v$NEXT_PATCH_VERSION"
1
+ name-template: "$RESOLVED_VERSION"
2
+ tag-template: "v$RESOLVED_VERSION"
3
3
  categories:
4
4
  - title: "⚠️ Breaking Changes"
5
5
  label: "⚠️ Breaking"
@@ -11,7 +11,15 @@ categories:
11
11
  label: "📚 Docs"
12
12
  - title: "🏠 Housekeeping"
13
13
  label: "🏠 Housekeeping"
14
+ version-resolver:
15
+ minor:
16
+ labels:
17
+ - "⚠️ Breaking"
18
+ - "✨ Feature"
19
+ default: patch
14
20
  change-template: "- $TITLE (#$NUMBER) @$AUTHOR"
15
21
  no-changes-template: "- No changes"
16
22
  template: |
17
23
  $CHANGES
24
+
25
+ **Full Changelog:** https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
@@ -1,16 +1,16 @@
1
1
  name: test on CI
2
2
  on:
3
3
  push:
4
- branches: [ master ]
4
+ branches: [master]
5
5
  pull_request:
6
6
  jobs:
7
7
  test:
8
8
  runs-on: ubuntu-latest
9
9
  strategy:
10
10
  matrix:
11
- ruby: [ "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "head" ]
11
+ ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "3.4", "head"]
12
12
  steps:
13
- - uses: actions/checkout@v3
13
+ - uses: actions/checkout@v6
14
14
  - name: Set up Ruby
15
15
  uses: ruby/setup-ruby@v1
16
16
  with:
@@ -18,11 +18,25 @@ jobs:
18
18
  bundler-cache: true
19
19
  - name: Run tests
20
20
  run: bundle exec rake test:units
21
+ env:
22
+ RUBYOPT: ${{ startsWith(matrix.ruby, 'head') && '--enable=frozen-string-literal' || '' }}
23
+
24
+ test-all:
25
+ runs-on: ubuntu-latest
26
+ needs: test
27
+ if: always()
28
+ steps:
29
+ - name: All tests ok
30
+ if: ${{ !(contains(needs.*.result, 'failure')) }}
31
+ run: exit 0
32
+ - name: Some tests failed
33
+ if: ${{ contains(needs.*.result, 'failure') }}
34
+ run: exit 1
21
35
 
22
36
  rubocop:
23
37
  runs-on: ubuntu-latest
24
38
  steps:
25
- - uses: actions/checkout@v3
39
+ - uses: actions/checkout@v6
26
40
  - name: Set up Ruby
27
41
  uses: ruby/setup-ruby@v1
28
42
  with:
@@ -32,29 +46,28 @@ jobs:
32
46
  run: bundle exec rake lint
33
47
 
34
48
  functional:
35
- runs-on: macos-10.15
49
+ runs-on: ubuntu-latest
36
50
  strategy:
37
51
  matrix:
38
- ruby: [ "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "head" ]
52
+ ruby: ["2.5", "ruby"]
39
53
  steps:
40
- - uses: actions/checkout@v3
41
-
42
- - name: Cache Vagrant boxes
43
- uses: actions/cache@v2
44
- with:
45
- path: ~/.vagrant.d/boxes
46
- key: ${{ runner.os }}-vagrant-${{ hashFiles('Vagrantfile') }}
47
- restore-keys: |
48
- ${{ runner.os }}-vagrant-
49
-
50
- - name: Run vagrant up
51
- run: vagrant up
52
-
54
+ - uses: actions/checkout@v6
53
55
  - name: Set up Ruby
54
56
  uses: ruby/setup-ruby@v1
55
57
  with:
56
58
  ruby-version: ${{ matrix.ruby }}
57
59
  bundler-cache: true
58
-
59
60
  - name: Run functional tests
60
61
  run: bundle exec rake test:functional
62
+
63
+ functional-all:
64
+ runs-on: ubuntu-latest
65
+ needs: [functional]
66
+ if: always()
67
+ steps:
68
+ - name: All tests ok
69
+ if: ${{ !(contains(needs.*.result, 'failure')) }}
70
+ run: exit 0
71
+ - name: Some tests failed
72
+ if: ${{ contains(needs.*.result, 'failure') }}
73
+ run: exit 1
@@ -7,6 +7,6 @@ jobs:
7
7
  steps:
8
8
  - uses: actions/checkout@master
9
9
  - name: Draft Release
10
- uses: toolmantim/release-drafter@v5.22.0
10
+ uses: toolmantim/release-drafter@v6.1.0
11
11
  env:
12
12
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
data/.gitignore CHANGED
@@ -3,6 +3,5 @@
3
3
  bin/rake
4
4
  .bundle
5
5
  .yardoc
6
- .vagrant*
7
6
  test/tmp
8
7
  Gemfile.lock
data/.rubocop.yml CHANGED
@@ -3,7 +3,7 @@ inherit_from: .rubocop_todo.yml
3
3
  AllCops:
4
4
  DisplayCopNames: true
5
5
  DisplayStyleGuide: true
6
- TargetRubyVersion: 2.0
6
+ TargetRubyVersion: 2.5
7
7
 
8
8
  Lint/AmbiguousBlockAssociation:
9
9
  Enabled: false
@@ -18,12 +18,12 @@ Style/ClassAndModuleChildren:
18
18
  Enabled: false
19
19
  Style/DoubleNegation:
20
20
  Enabled: false
21
- Style/FileName:
21
+ Naming/FileName:
22
22
  Exclude:
23
23
  - "Dangerfile"
24
- Style/IndentHeredoc:
24
+ Layout/IndentHeredoc:
25
25
  Enabled: false
26
- Style/SpaceAroundEqualsInParameterDefault:
26
+ Layout/SpaceAroundEqualsInParameterDefault:
27
27
  EnforcedStyle: no_space
28
28
  Style/StringLiterals:
29
29
  EnforcedStyle: double_quotes
@@ -47,7 +47,7 @@ Metrics/CyclomaticComplexity:
47
47
  Enabled: false
48
48
  Metrics/MethodLength:
49
49
  Enabled: false
50
- Style/PredicateName:
50
+ Naming/PredicateName:
51
51
  Enabled: false
52
52
  Metrics/LineLength:
53
53
  Enabled: false
@@ -59,5 +59,5 @@ Metrics/ClassLength:
59
59
  Enabled: false
60
60
  Metrics/ModuleLength:
61
61
  Enabled: false
62
- Style/AccessorMethodName:
62
+ Naming/AccessorMethodName:
63
63
  Enabled: false
data/.rubocop_todo.yml CHANGED
@@ -102,7 +102,6 @@ Layout/IndentHash:
102
102
  Exclude:
103
103
  - 'test/functional/backends/test_local.rb'
104
104
  - 'test/functional/backends/test_netssh.rb'
105
- - 'test/support/vagrant_wrapper.rb'
106
105
  - 'test/unit/formatters/test_custom.rb'
107
106
  - 'test/unit/formatters/test_pretty.rb'
108
107
  - 'test/unit/test_mapping_interaction_handler.rb'
@@ -440,17 +439,11 @@ Style/MethodDefParentheses:
440
439
  # Offense count: 3
441
440
  # Configuration parameters: SupportedStyles.
442
441
  # SupportedStyles: snake_case, camelCase
443
- Style/MethodName:
442
+ Naming/MethodName:
444
443
  EnforcedStyle: snake_case
445
444
  Exclude:
446
445
  - 'test/unit/test_color.rb'
447
446
 
448
- # Offense count: 1
449
- # Cop supports --auto-correct.
450
- Style/MutableConstant:
451
- Exclude:
452
- - 'Vagrantfile'
453
-
454
447
  # Offense count: 1
455
448
  # Cop supports --auto-correct.
456
449
  # Configuration parameters: Strict.
@@ -467,7 +460,7 @@ Style/NumericPredicate:
467
460
  - 'lib/sshkit/command.rb'
468
461
 
469
462
  # Offense count: 1
470
- Style/OpMethod:
463
+ Naming/BinaryOperatorParameterName:
471
464
  Exclude:
472
465
  - 'lib/sshkit/host.rb'
473
466
 
@@ -618,7 +611,7 @@ Style/UnneededPercentQ:
618
611
  # Offense count: 1
619
612
  # Configuration parameters: EnforcedStyle, SupportedStyles.
620
613
  # SupportedStyles: snake_case, normalcase, non_integer
621
- Style/VariableNumber:
614
+ Naming/VariableNumber:
622
615
  Exclude:
623
616
  - 'test/unit/backends/test_connection_pool.rb'
624
617
 
data/CONTRIBUTING.md CHANGED
@@ -24,8 +24,8 @@ using unsupported features.
24
24
 
25
25
  ## Tests
26
26
 
27
- SSHKit has a unit test suite and a functional test suite. Some functional tests run against
28
- [Vagrant](https://www.vagrantup.com/) VMs. If possible, you should make sure that the
27
+ SSHKit has a unit test suite and a functional test suite. Some functional tests run using
28
+ [Docker](https://docs.docker.com/get-docker/). If possible, you should make sure that the
29
29
  tests pass for each commit by running `rake` in the sshkit directory. This is in case we
30
30
  need to cherry pick commits or rebase. You should ensure the tests pass, (preferably on
31
31
  the minimum and maximum ruby version), before creating a PR.
data/EXAMPLES.md CHANGED
@@ -121,9 +121,6 @@ on hosts do |host|
121
121
  end
122
122
  ```
123
123
 
124
- **Note:** The `upload!()` method doesn't honor the values of `as()` etc, this
125
- will be improved as the library matures, but we're not there yet.
126
-
127
124
  ## Upload a file from a stream
128
125
 
129
126
  ```ruby
@@ -148,9 +145,6 @@ end
148
145
  This spares one from having to figure out the correct escaping sequences for
149
146
  something like "echo(:cat, '...?...', '> /etc/sudoers.d/yolo')".
150
147
 
151
- **Note:** The `upload!()` method doesn't honor the values of `within()`, `as()`
152
- etc, this will be improved as the library matures, but we're not there yet.
153
-
154
148
  ## Upload a directory of files
155
149
 
156
150
  ```ruby
@@ -160,7 +154,25 @@ end
160
154
  ```
161
155
 
162
156
  In this case the `recursive: true` option mirrors the same options which are
163
- available to [`Net::{SCP,SFTP}`](http://net-ssh.github.io/net-scp/).
157
+ available to [`Net::SCP`](https://github.com/net-ssh/net-scp) and
158
+ [`Net::SFTP`](https://github.com/net-ssh/net-sftp).
159
+
160
+ ## Set the upload/download method (SCP or SFTP).
161
+
162
+ SSHKit can use SCP or SFTP for file transfers. The default is SCP, but this can be changed to SFTP per host:
163
+
164
+ ```ruby
165
+ host = SSHKit::Host.new('user@example.com')
166
+ host.transfer_method = :sftp
167
+ ```
168
+
169
+ or globally:
170
+
171
+ ```ruby
172
+ SSHKit::Backend::Netssh.configure do |ssh|
173
+ ssh.transfer_method = :sftp
174
+ end
175
+ ```
164
176
 
165
177
  ## Setting global SSH options
166
178
 
@@ -235,16 +247,16 @@ end
235
247
 
236
248
  ```ruby
237
249
  # The default format is pretty, which outputs colored text
238
- SSHKit.config.format = :pretty
250
+ SSHKit.config.use_format :pretty
239
251
 
240
252
  # Text with no coloring
241
- SSHKit.config.format = :simpletext
253
+ SSHKit.config.use_format :simpletext
242
254
 
243
255
  # Red / Green dots for each completed step
244
- SSHKit.config.format = :dot
256
+ SSHKit.config.use_format :dot
245
257
 
246
258
  # No output
247
- SSHKit.config.format = :blackhole
259
+ SSHKit.config.use_format :blackhole
248
260
  ```
249
261
 
250
262
  ## Implement a dirt-simple formatter class
@@ -254,7 +266,7 @@ module SSHKit
254
266
  module Formatter
255
267
  class MyFormatter < SSHKit::Formatter::Abstract
256
268
  def write(obj)
257
- case obj.is_a? SSHKit::Command
269
+ if obj.is_a? SSHKit::Command
258
270
  # Do something here, see the SSHKit::Command documentation
259
271
  end
260
272
  end
@@ -263,7 +275,7 @@ module SSHKit
263
275
  end
264
276
 
265
277
  # If your formatter is defined in the SSHKit::Formatter module configure with the format option:
266
- SSHKit.config.format = :myformatter
278
+ SSHKit.config.use_format :myformatter
267
279
 
268
280
  # Or configure the output directly
269
281
  SSHKit.config.output = MyFormatter.new($stdout)
data/Gemfile CHANGED
@@ -1,8 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
-
5
- # public_suffix 3+ requires ruby 2.1+
6
- if Gem::Requirement.new('< 2.1').satisfied_by?(Gem::Version.new(RUBY_VERSION))
7
- gem 'public_suffix', '< 3'
8
- end
data/README.md CHANGED
@@ -68,7 +68,8 @@ you can pass the `strip: false` option: `capture(:ls, '-l', strip: false)`
68
68
  ### Transferring files
69
69
 
70
70
  All backends also support the `upload!` and `download!` methods for transferring files.
71
- For the remote backend, the file is transferred with scp.
71
+ For the remote backend, the file is transferred with scp by default, but sftp is also
72
+ supported. See [EXAMPLES.md](EXAMPLES.md) for details.
72
73
 
73
74
  ```ruby
74
75
  on '1.example.com' do
data/RELEASING.md CHANGED
@@ -4,12 +4,12 @@
4
4
 
5
5
  * You must have commit rights to the SSHKit repository.
6
6
  * You must have push rights for the sshkit gem on rubygems.org.
7
- * You must be using Ruby >= 2.1.0.
7
+ * You must be using Ruby >= 2.5.0.
8
8
 
9
9
  ## How to release
10
10
 
11
11
  1. Run `bundle install` to make sure that you have all the gems necessary for testing and releasing.
12
- 2. **Ensure the tests are passing by running `rake test`.** If functional tests fail, ensure you have [Vagrant](https://www.vagrantup.com) installed and have started it with `vagrant up`.
12
+ 2. **Ensure the tests are passing by running `rake test`.** If functional tests fail, ensure you have [Docker installed](https://docs.docker.com/get-docker/) and running.
13
13
  3. Determine which would be the correct next version number according to [semver](http://semver.org/).
14
14
  4. Update the version in `./lib/sshkit/version.rb`.
15
15
  5. Commit the `version.rb` change with a message like "Preparing vX.Y.Z"
data/Rakefile CHANGED
@@ -21,10 +21,6 @@ namespace :test do
21
21
 
22
22
  end
23
23
 
24
- Rake::Task["test:functional"].enhance do
25
- warn "Remember there are still some VMs running, kill them with `vagrant halt` if you are finished using them."
26
- end
27
-
28
24
  desc 'Run RuboCop lint checks'
29
25
  RuboCop::RakeTask.new(:lint) do |task|
30
26
  task.options = ['--lint']
@@ -0,0 +1,8 @@
1
+ name: sshkit
2
+
3
+ services:
4
+ ssh_server:
5
+ build:
6
+ context: .docker
7
+ ports:
8
+ - "2122:22"
@@ -103,6 +103,7 @@ module SSHKit
103
103
  end
104
104
 
105
105
  def as(who, &_block)
106
+ who_old = [@user, @group]
106
107
  if who.is_a? Hash
107
108
  @user = who[:user] || who["user"]
108
109
  @group = who[:group] || who["group"]
@@ -118,8 +119,7 @@ module SSHKit
118
119
  EOTEST
119
120
  yield
120
121
  ensure
121
- remove_instance_variable(:@user)
122
- remove_instance_variable(:@group)
122
+ @user, @group = *who_old
123
123
  end
124
124
 
125
125
  class << self
@@ -135,8 +135,7 @@ module SSHKit
135
135
  # Backends which extend the Abstract backend should implement the following methods:
136
136
  def upload!(_local, _remote, _options = {}) raise MethodUnavailableError end
137
137
  def download!(_remote, _local=nil, _options = {}) raise MethodUnavailableError end
138
- def execute_command(_cmd) raise MethodUnavailableError end
139
- private :execute_command # Can inline after Ruby 2.1
138
+ private def execute_command(_cmd) raise MethodUnavailableError end
140
139
 
141
140
  private
142
141
 
@@ -36,8 +36,8 @@ class SSHKit::Backend::ConnectionPool::Cache
36
36
  def evict
37
37
  # Peek at the first connection to see if it is still fresh. If so, we can
38
38
  # return right away without needing to use `synchronize`.
39
- first_expires_at, first_conn = connections.first
40
- return if (first_expires_at.nil? || fresh?(first_expires_at)) && !closed?(first_conn)
39
+ first_expires_at, _first_conn = connections.first
40
+ return if (first_expires_at.nil? || fresh?(first_expires_at))
41
41
 
42
42
  connections.synchronize do
43
43
  fresh, stale = connections.partition do |expires_at, conn|
@@ -1,5 +1,4 @@
1
1
  require "monitor"
2
- require "thread"
3
2
 
4
3
  # Since we call to_s on new connection arguments and use that as a cache key, we
5
4
  # need to make sure the memory address of the object is not used as part of the
@@ -1,3 +1,5 @@
1
+ require "base64"
2
+
1
3
  module SSHKit
2
4
 
3
5
  module Backend
@@ -5,12 +7,11 @@ module SSHKit
5
7
  class Netssh < Abstract
6
8
 
7
9
  class KnownHostsKeys
8
- include Mutex_m
9
-
10
10
  def initialize(path)
11
11
  super()
12
12
  @path = File.expand_path(path)
13
13
  @hosts_keys = nil
14
+ @mutex = Mutex.new
14
15
  end
15
16
 
16
17
  def keys_for(hostlist)
@@ -44,7 +45,7 @@ module SSHKit
44
45
  end
45
46
 
46
47
  def parse_file
47
- synchronize do
48
+ @mutex.synchronize do
48
49
  return if hosts_keys && hosts_hashes
49
50
 
50
51
  unless File.readable?(path)
@@ -110,11 +111,10 @@ module SSHKit
110
111
  end
111
112
 
112
113
  class KnownHosts
113
- include Mutex_m
114
-
115
114
  def initialize
116
115
  super()
117
116
  @files = {}
117
+ @mutex = Mutex.new
118
118
  end
119
119
 
120
120
  def search_for(host, options = {})
@@ -126,13 +126,13 @@ module SSHKit
126
126
 
127
127
  def add(*args)
128
128
  ::Net::SSH::KnownHosts.add(*args)
129
- synchronize { @files = {} }
129
+ @mutex.synchronize { @files = {} }
130
130
  end
131
131
 
132
132
  private
133
133
 
134
134
  def known_hosts_file(path)
135
- @files[path] || synchronize { @files[path] ||= KnownHostsKeys.new(path) }
135
+ @files[path] || @mutex.synchronize { @files[path] ||= KnownHostsKeys.new(path) }
136
136
  end
137
137
  end
138
138
 
@@ -140,4 +140,4 @@ module SSHKit
140
140
 
141
141
  end
142
142
 
143
- end
143
+ end
@@ -0,0 +1,26 @@
1
+ require "net/scp"
2
+
3
+ module SSHKit
4
+ module Backend
5
+ class Netssh < Abstract
6
+ class ScpTransfer
7
+ def initialize(ssh, summarizer)
8
+ @ssh = ssh
9
+ @summarizer = summarizer
10
+ end
11
+
12
+ def upload!(local, remote, options)
13
+ ssh.scp.upload!(local, remote, options, &summarizer)
14
+ end
15
+
16
+ def download!(remote, local, options)
17
+ ssh.scp.download!(remote, local, options, &summarizer)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :ssh, :summarizer
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ require "net/sftp"
2
+
3
+ module SSHKit
4
+ module Backend
5
+ class Netssh < Abstract
6
+ class SftpTransfer
7
+ def initialize(ssh, summarizer)
8
+ @ssh = ssh
9
+ @summarizer = summarizer
10
+ end
11
+
12
+ def upload!(local, remote, options)
13
+ options = { progress: self }.merge(options || {})
14
+ ssh.sftp.connect!
15
+ ssh.sftp.upload!(local, remote, options)
16
+ ensure
17
+ ssh.sftp.close_channel
18
+ end
19
+
20
+ def download!(remote, local, options)
21
+ options = { progress: self }.merge(options || {})
22
+ destination = local ? local : StringIO.new.tap { |io| io.set_encoding('BINARY') }
23
+
24
+ ssh.sftp.connect!
25
+ ssh.sftp.download!(remote, destination, options)
26
+ local ? true : destination.string
27
+ ensure
28
+ ssh.sftp.close_channel
29
+ end
30
+
31
+ def on_get(download, entry, offset, data)
32
+ entry.size ||= download.sftp.file.open(entry.remote) { |file| file.stat.size }
33
+ summarizer.call(nil, entry.remote, offset + data.bytesize, entry.size)
34
+ end
35
+
36
+ def on_put(_upload, file, offset, data)
37
+ summarizer.call(nil, file.local, offset + data.bytesize, file.size)
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :ssh, :summarizer
43
+ end
44
+ end
45
+ end
46
+ end