sshkit 1.21.5 → 1.23.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +19 -34
- data/.github/workflows/push.yml +1 -1
- data/.gitignore +0 -1
- data/.rubocop_todo.yml +0 -7
- data/CONTRIBUTING.md +2 -2
- data/EXAMPLES.md +25 -13
- data/README.md +2 -1
- data/RELEASING.md +1 -1
- data/Rakefile +0 -4
- data/docker-compose.yml +8 -0
- data/lib/sshkit/backends/connection_pool/cache.rb +2 -2
- 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/host.rb +25 -0
- data/lib/sshkit/version.rb +1 -1
- data/sshkit.gemspec +2 -0
- data/test/functional/backends/netssh_transfer_tests.rb +83 -0
- data/test/functional/backends/test_netssh.rb +1 -67
- 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 +1 -1
- data/test/unit/backends/test_netssh.rb +48 -0
- data/test/unit/test_command_map.rb +8 -8
- data/test/unit/test_deprecation_logger.rb +1 -1
- data/test/unit/test_host.rb +39 -0
- metadata +44 -10
- data/Vagrantfile +0 -24
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f40ad7a1382ef707a259094c945692f08c44392e0bba7c37fe4099e748301a5e
|
4
|
+
data.tar.gz: 72f3fea395eaaa0036dd701c04692e75c379867465cffaa4ac6cdf1c4f647813
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab28074eba7cb9bdbfcbca9857f5e74fc89ed06acdf872148fcb7329b45754a714c658dacd879fead076de3bbd2541d84bcfd8ef66369007a1444b3edc2b3ac3
|
7
|
+
data.tar.gz: a6d0deb01db101ba2a41dcfcd861a10c7604b17ebe5e25981eebe6d74a0b5c9a9ea345d2dcbb45a4ba30952b571b6736f3bdcd7721d7d8fe684db772cc723700
|
data/.docker/Dockerfile
ADDED
@@ -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
|
data/.github/release-drafter.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
name-template: "$
|
2
|
-
tag-template: "v$
|
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
|
data/.github/workflows/ci.yml
CHANGED
@@ -8,9 +8,21 @@ jobs:
|
|
8
8
|
runs-on: ubuntu-latest
|
9
9
|
strategy:
|
10
10
|
matrix:
|
11
|
-
ruby:
|
11
|
+
ruby:
|
12
|
+
[
|
13
|
+
"2.3",
|
14
|
+
"2.4",
|
15
|
+
"2.5",
|
16
|
+
"2.6",
|
17
|
+
"2.7",
|
18
|
+
"3.0",
|
19
|
+
"3.1",
|
20
|
+
"3.2",
|
21
|
+
"3.3",
|
22
|
+
"head",
|
23
|
+
]
|
12
24
|
steps:
|
13
|
-
- uses: actions/checkout@
|
25
|
+
- uses: actions/checkout@v4
|
14
26
|
- name: Set up Ruby
|
15
27
|
uses: ruby/setup-ruby@v1
|
16
28
|
with:
|
@@ -25,7 +37,7 @@ jobs:
|
|
25
37
|
matrix:
|
26
38
|
ruby: ["2.0", "2.1", "2.2"]
|
27
39
|
steps:
|
28
|
-
- uses: actions/checkout@
|
40
|
+
- uses: actions/checkout@v4
|
29
41
|
- name: Set up Ruby
|
30
42
|
uses: ruby/setup-ruby@v1
|
31
43
|
with:
|
@@ -49,7 +61,7 @@ jobs:
|
|
49
61
|
rubocop:
|
50
62
|
runs-on: ubuntu-latest
|
51
63
|
steps:
|
52
|
-
- uses: actions/checkout@
|
64
|
+
- uses: actions/checkout@v4
|
53
65
|
- name: Set up Ruby
|
54
66
|
uses: ruby/setup-ruby@v1
|
55
67
|
with:
|
@@ -59,44 +71,17 @@ jobs:
|
|
59
71
|
run: bundle exec rake lint
|
60
72
|
|
61
73
|
functional:
|
62
|
-
runs-on:
|
74
|
+
runs-on: ubuntu-latest
|
63
75
|
strategy:
|
64
76
|
matrix:
|
65
|
-
ruby:
|
66
|
-
[
|
67
|
-
"2.0",
|
68
|
-
"2.1",
|
69
|
-
"2.2",
|
70
|
-
"2.3",
|
71
|
-
"2.4",
|
72
|
-
"2.5",
|
73
|
-
"2.6",
|
74
|
-
"2.7",
|
75
|
-
"3.0",
|
76
|
-
"3.1",
|
77
|
-
"3.2",
|
78
|
-
"head",
|
79
|
-
]
|
77
|
+
ruby: ["2.0", "ruby"]
|
80
78
|
steps:
|
81
|
-
- uses: actions/checkout@
|
82
|
-
|
83
|
-
- name: Cache Vagrant boxes
|
84
|
-
uses: actions/cache@v3
|
85
|
-
with:
|
86
|
-
path: ~/.vagrant.d/boxes
|
87
|
-
key: ${{ runner.os }}-vagrant-v2-${{ hashFiles('Vagrantfile') }}
|
88
|
-
restore-keys: |
|
89
|
-
${{ runner.os }}-vagrant-v2-
|
90
|
-
|
91
|
-
- name: Run vagrant up
|
92
|
-
run: vagrant up
|
93
|
-
|
79
|
+
- uses: actions/checkout@v4
|
94
80
|
- name: Set up Ruby
|
95
81
|
uses: ruby/setup-ruby@v1
|
96
82
|
with:
|
97
83
|
ruby-version: ${{ matrix.ruby }}
|
98
84
|
bundler-cache: true
|
99
|
-
|
100
85
|
- name: Run functional tests
|
101
86
|
run: bundle exec rake test:functional
|
102
87
|
|
data/.github/workflows/push.yml
CHANGED
data/.gitignore
CHANGED
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'
|
@@ -445,12 +444,6 @@ Style/MethodName:
|
|
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.
|
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
|
28
|
-
[
|
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::
|
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.
|
250
|
+
SSHKit.config.use_format :pretty
|
239
251
|
|
240
252
|
# Text with no coloring
|
241
|
-
SSHKit.config.
|
253
|
+
SSHKit.config.use_format :simpletext
|
242
254
|
|
243
255
|
# Red / Green dots for each completed step
|
244
|
-
SSHKit.config.
|
256
|
+
SSHKit.config.use_format :dot
|
245
257
|
|
246
258
|
# No output
|
247
|
-
SSHKit.config.
|
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
|
-
|
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.
|
278
|
+
SSHKit.config.use_format :myformatter
|
267
279
|
|
268
280
|
# Or configure the output directly
|
269
281
|
SSHKit.config.output = MyFormatter.new($stdout)
|
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
@@ -9,7 +9,7 @@
|
|
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 [
|
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']
|
data/docker-compose.yml
ADDED
@@ -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,
|
40
|
-
return if (first_expires_at.nil? || fresh?(first_expires_at))
|
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,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
|
@@ -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/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
@@ -20,8 +20,10 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.require_paths = ["lib"]
|
21
21
|
gem.version = SSHKit::VERSION
|
22
22
|
|
23
|
+
gem.add_runtime_dependency('base64') if RUBY_VERSION >= "2.4"
|
23
24
|
gem.add_runtime_dependency('net-ssh', '>= 2.8.0')
|
24
25
|
gem.add_runtime_dependency('net-scp', '>= 1.1.2')
|
26
|
+
gem.add_runtime_dependency('net-sftp', '>= 2.1.2')
|
25
27
|
|
26
28
|
gem.add_development_dependency('danger')
|
27
29
|
gem.add_development_dependency('minitest', '>= 5.0.0')
|