sftp_wrapper 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e2e0e228c73646e79c09685f1b70acc11cf5f65ac18c5c2fd37441f3b75c5b2a
4
+ data.tar.gz: 0b76e0a5149bbc6d2e79c942121aacc5dcb83069647d95347824777812c452f1
5
+ SHA512:
6
+ metadata.gz: a66da813d9e6a2519afb0bd9fe2b0c5d40433ca7b63025cf7a53bda52bd3afe40e7230513c081feda33d8d9169c7fdca472ea4ae3a7de702f6045b2c1da07935
7
+ data.tar.gz: 6741c93b50de825182c507c62c45b412083094ee355f3f93529d5438ca9b08ed3638dd96905d649258dc88527fa6f37827618d89d5e3a6221932ff87e9a64f57
@@ -0,0 +1,76 @@
1
+ name: Continuous Integration
2
+ on: [push]
3
+ jobs:
4
+ build:
5
+ name: Test
6
+ runs-on: ${{ matrix.os }}
7
+ strategy:
8
+ matrix:
9
+ os:
10
+ - ubuntu-latest
11
+ ruby:
12
+ - "2.3"
13
+ - "2.4"
14
+ - "2.5"
15
+ - "2.6"
16
+ services:
17
+ sftp:
18
+ image: atmoz/sftp:alpine
19
+ ports:
20
+ - "2222:22"
21
+ env:
22
+ SFTP_USERS: "test:pass:::upload"
23
+ steps:
24
+ - uses: actions/checkout@v1
25
+ - name: Use Ruby ${{ matrix.ruby_version }}
26
+ uses: actions/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby }}
29
+ architecture: 'x64'
30
+ - name: install curl (enabled SFTP support)
31
+ run: |
32
+ sudo apt purge curl
33
+ sudo apt update
34
+ sudo apt install -y libssh2-1-dev libssl-dev ca-certificates
35
+ sudo apt install -y binutils g++ make
36
+ cd /tmp
37
+ wget https://curl.haxx.se/download/curl-7.65.3.tar.gz
38
+ tar xzvf curl-7.65.3.tar.gz
39
+ cd curl-7.65.3
40
+ ./configure --disable-libcurl-option --disable-shared --with-libssh2 --with-ssl
41
+ make
42
+ sudo make install
43
+ - name: bundle install, run test
44
+ run: |
45
+ gem install bundler -v1.17 --force --no-document
46
+ bundle install
47
+ bundle exec rake
48
+ release:
49
+ name: Release Gem
50
+ needs: build
51
+ runs-on: ubuntu-latest
52
+ steps:
53
+ - uses: actions/checkout@master
54
+ - name: Setup Ruby
55
+ uses: actions/setup-ruby@v1
56
+ with:
57
+ ruby-version: "2.6"
58
+ architecture: "x64"
59
+ - name: Setup RubyGems
60
+ run: |
61
+ # NOTE: use rubygems v3.0.5+ to works GEM_HOST_API_KEY.
62
+ gem update --system
63
+ # NOTE: set `--force` option to avoid error: `"bundle" from bundler conflicts with ...`
64
+ gem install bundler -v1.17 --force --no-document
65
+ - name: Build Gem
66
+ run: |
67
+ bundle
68
+ bundle exec rake build
69
+ bundle exec rake install
70
+ - name: Push Gem
71
+ if: startsWith(github.event.ref, 'refs/tags/v')
72
+ env:
73
+ GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
74
+ run: |
75
+ gem -v
76
+ gem push pkg/sftp_wrapper-*.gem
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ Metrics/BlockLength:
5
+ Exclude:
6
+ - "test/**/*_test.rb"
7
+ - "*.gemspec"
8
+
9
+ Metrics/ClassLength:
10
+ Exclude:
11
+ - "test/**/*_test.rb"
12
+
13
+ Metrics/LineLength:
14
+ Max: 120
15
+ Exclude:
16
+ - sftp_wrapper.gemspec
17
+
18
+ Style/TrailingCommaInHashLiteral:
19
+ EnforcedStyleForMultiline: consistent_comma
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ Changelog
2
+ ====
3
+
4
+ [v0.1.0](https://github.com/koshigoe/sftp_wrapper/releases/tag/v0.1.0)
5
+ ----
6
+
7
+ The first release.
8
+
9
+ - Implement wrapper for `sftp` (OpenSSH)
10
+ - Implement wrapper for `curl`
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in sftp_wrapper.gemspec
8
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Masataka SUZUKI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # SftpWrapper
2
+
3
+ The wrapper for sftp CLI.
4
+
5
+ _**NOTE: This is an experimental wrapper to download faster than [net-sftp](https://github.com/net-ssh/net-sftp).**_
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'sftp_wrapper'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install sftp_wrapper
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ require 'sftp_wrapper'
27
+
28
+ SftpWrapper::OpenSSH.new('localhost', 2222, 'test', 'pass').download('/upload/file', './file')
29
+ SftpWrapper::Curl.new('localhost', 2222, 'test', 'pass').download('/upload/file', './file')
30
+ ```
31
+
32
+ ## Development
33
+
34
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
35
+
36
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
+
38
+ ## Contributing
39
+
40
+ Bug reports and pull requests are welcome on GitHub at https://github.com/koshigoe/sftp_wrapper.
41
+
42
+ ## License
43
+
44
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ end
11
+
12
+ task default: :test
data/benchmark/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ gem 'net-sftp'
8
+ gem 'sftp_wrapper', path: '..'
@@ -0,0 +1,21 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ sftp_wrapper (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ net-sftp (2.1.2)
10
+ net-ssh (>= 2.6.5)
11
+ net-ssh (5.2.0)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ net-sftp
18
+ sftp_wrapper!
19
+
20
+ BUNDLED WITH
21
+ 1.17.2
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+ require 'net/sftp'
5
+ require 'sftp_wrapper'
6
+ require 'tempfile'
7
+
8
+ data = ' ' * (1024 * 1024 * 100)
9
+
10
+ ssh_options = {
11
+ auth_methods: %w[password],
12
+ config: false,
13
+ non_interactive: true,
14
+ user_known_hosts_file: IO::NULL,
15
+ verify_host_key: :never,
16
+ port: 2222,
17
+ password: 'pass',
18
+ }
19
+
20
+ Tempfile.create('temp') do |temp|
21
+ temp.write(data)
22
+ temp.close
23
+
24
+ Net::SFTP.start('localhost', 'test', ssh_options) do |session|
25
+ session.upload!(temp.path, '/upload/benchmark-download')
26
+ end
27
+ end
28
+
29
+ Benchmark.bm(16) do |x|
30
+ x.report('OpenSSH sftp CLI') do
31
+ Tempfile.create('temp') do |temp|
32
+ temp.close
33
+
34
+ wrapper = SftpWrapper::OpenSSH.new('localhost', 2222, 'test', 'pass')
35
+ wrapper.download('/upload/benchmark-download', 'downloaded')
36
+ end
37
+ end
38
+
39
+ x.report('curl CLI') do
40
+ Tempfile.create('temp') do |temp|
41
+ temp.close
42
+
43
+ wrapper = SftpWrapper::Curl.new('localhost', 2222, 'test', 'pass')
44
+ wrapper.download('/upload/benchmark-download', 'downloaded')
45
+ end
46
+ end
47
+
48
+ x.report('net-sftp') do
49
+ Tempfile.create('temp') do |temp|
50
+ temp.close
51
+
52
+ Net::SFTP.start('localhost', 'test', ssh_options) do |session|
53
+ session.download!('/upload/benchmark-download', temp.path)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ __END__
60
+
61
+ $ ruby -v
62
+ ruby 2.6.4p104 (2019-08-28 revision 67798) [x86_64-linux]
63
+ $ bundle exec ruby benchmark_download.rb
64
+ user system total real
65
+ OpenSSH sftp CLI 0.000382 0.003365 0.003747 ( 3.044737)
66
+ curl CLI 0.015933 0.000000 2.115820 ( 3.375170)
67
+ net-sftp 6.210004 0.744365 6.954369 ( 10.792968)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+ require 'net/sftp'
5
+ require 'sftp_wrapper'
6
+ require 'tempfile'
7
+
8
+ data = ' ' * (1024 * 1024 * 100)
9
+
10
+ Tempfile.create('temp') do |temp|
11
+ temp.write(data)
12
+ temp.close
13
+
14
+ Benchmark.bm(16) do |x|
15
+ x.report('OpenSSH sftp CLI') do
16
+ wrapper = SftpWrapper::OpenSSH.new('localhost', 2222, 'test', 'pass')
17
+ wrapper.upload(temp.path, '/upload/benchmark-download')
18
+ end
19
+
20
+ x.report('curl CLI') do
21
+ wrapper = SftpWrapper::Curl.new('localhost', 2222, 'test', 'pass')
22
+ wrapper.upload(temp.path, '/upload/benchmark-download')
23
+ end
24
+
25
+ x.report('net-sftp') do
26
+ ssh_options = {
27
+ auth_methods: %w[password],
28
+ config: false,
29
+ non_interactive: true,
30
+ user_known_hosts_file: IO::NULL,
31
+ verify_host_key: :never,
32
+ port: 2222,
33
+ password: 'pass',
34
+ }
35
+ Net::SFTP.start('localhost', 'test', ssh_options) do |session|
36
+ session.upload!(temp.path, '/upload/benchmark-upload')
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ __END__
43
+
44
+ $ ruby -v
45
+ ruby 2.6.4p104 (2019-08-28 revision 67798) [x86_64-linux]
46
+ $ bundle exec ruby benchmark_upload.rb
47
+ user system total real
48
+ OpenSSH sftp CLI 0.004058 0.000321 0.004379 ( 1.749079)
49
+ curl CLI 0.007233 0.003560 0.676544 ( 2.184746)
50
+ net-sftp 0.852571 0.099383 0.951954 ( 2.413606)
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'sftp_wrapper'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
9
+ docker-compose up -d
@@ -0,0 +1,9 @@
1
+ version: '3'
2
+
3
+ services:
4
+ sftp:
5
+ image: atmoz/sftp:alpine
6
+ container_name: sftp
7
+ ports:
8
+ - "2222:22"
9
+ command: "test:pass:::upload"
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'erb'
5
+ require 'open3'
6
+
7
+ module SftpWrapper
8
+ # The wrapper for curl.
9
+ class Curl
10
+ attr_reader :host, :port, :username, :password, :curl_command, :curl_options
11
+
12
+ # lookup table of curl errors.
13
+ CURL_ERRORS = {
14
+ # Failed to connect to host.
15
+ 7 => SftpWrapper::Errors::ConnectionError,
16
+ # The user name, password, or similar was not accepted and curl failed to log in.
17
+ 67 => SftpWrapper::Errors::AuthenticationFailure,
18
+ }.freeze
19
+
20
+ # Initialize SFTP wrapper.
21
+ #
22
+ # @param host [String] host address of SFTP server
23
+ # @param port [Integer] port number of SFTP server
24
+ # @param username [String] user name of SFTP server
25
+ # @param password [String] password of SFTP server
26
+ # @param curl_options [Hash] curl options. (e.g. --connect-timeout 1 => connect_timeout: 1)
27
+ #
28
+ def initialize(host, port, username, password, curl_options = {})
29
+ @host = host
30
+ @port = port
31
+ @username = username
32
+ @password = password
33
+ @curl_command = curl_options.key?(:command_path) ? curl_options.delete(:command_path) : 'curl'
34
+ @curl_options = curl_options
35
+ end
36
+
37
+ # Get remote file.
38
+ #
39
+ # @param source [String] source file path
40
+ # @param destination [String] destination path
41
+ # @raise [SftpWrapper::Errors::ConnectionError]
42
+ # @raise [SftpWrapper::Errors::AuthenticationFailure]
43
+ # @raise [SftpWrapper::Errors::CommandError]
44
+ #
45
+ def download(source, destination)
46
+ userinfo = [username, password].map(&ERB::Util.method(:url_encode)).join(':')
47
+ uri = URI::Generic.build(scheme: 'sftp', userinfo: userinfo, host: host, port: port, path: source)
48
+ cmd = %W[#{curl_command} -s --insecure -o #{destination}] + build_curl_options + [uri.to_s]
49
+
50
+ execute(*cmd)
51
+ end
52
+
53
+ # Put local file.
54
+ #
55
+ # @param source [String] source file path
56
+ # @param destination [String] destination path
57
+ # @raise [SftpWrapper::Errors::ConnectionError]
58
+ # @raise [SftpWrapper::Errors::AuthenticationFailure]
59
+ # @raise [SftpWrapper::Errors::CommandError]
60
+ #
61
+ def upload(source, destination)
62
+ userinfo = [username, password].map(&ERB::Util.method(:url_encode)).join(':')
63
+ uri = URI::Generic.build(scheme: 'sftp', userinfo: userinfo, host: host, port: port, path: destination)
64
+ cmd = %W[#{curl_command} -s --insecure -T #{source}] + build_curl_options + [uri.to_s]
65
+
66
+ execute(*cmd)
67
+ end
68
+
69
+ private
70
+
71
+ def build_curl_options
72
+ curl_options.map { |k, v| ["--#{k.tr('_', '-')}", v] }.flatten
73
+ end
74
+
75
+ def execute(*cmd)
76
+ _, stderr, status = Open3.capture3(*cmd)
77
+
78
+ return if status.success?
79
+
80
+ exception_class = CURL_ERRORS[status.exitstatus] || SftpWrapper::Errors::CommandError
81
+
82
+ raise exception_class, "exit status #{status.exitstatus}: #{stderr}"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SftpWrapper
4
+ # Exceptions.
5
+ module Errors
6
+ # Base class for exceptions.
7
+ class Error < StandardError; end
8
+
9
+ # Can't establish connection.
10
+ class ConnectionError < Error; end
11
+
12
+ # Can't authenticate user.
13
+ class AuthenticationFailure < Error; end
14
+
15
+ # Command timed out.
16
+ class TimeoutError < Error; end
17
+
18
+ # Command failed.
19
+ class CommandError < Error; end
20
+ end
21
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pty'
4
+ require 'expect'
5
+ require 'shellwords'
6
+
7
+ module SftpWrapper
8
+ # The wrapper for OpenSSH's sftp command.
9
+ class OpenSSH
10
+ attr_reader :host,
11
+ :port,
12
+ :username,
13
+ :password,
14
+ :ssh_options,
15
+ :ssh_config,
16
+ :open_timeout,
17
+ :read_timeout,
18
+ :command_timeout,
19
+ :debug
20
+
21
+ # Default value of open timeout.
22
+ DEFAULT_OPEN_TIMEOUT = 10
23
+ # Default value of read timeout.
24
+ DEFAULT_READ_TIMEOUT = 10
25
+ # Default value of command timeout.
26
+ DEFAULT_COMMAND_TIMEOUT = 9_999_999
27
+ # Default value of ssh options
28
+ DEFAULT_SSH_OPTIONS = {
29
+ PasswordAuthentication: 'yes',
30
+ PreferredAuthentications: 'password',
31
+ PubkeyAuthentication: 'no',
32
+ NumberOfPasswordPrompts: 1,
33
+ StrictHostKeyChecking: 'no',
34
+ UserKnownHostsFile: '/dev/null',
35
+ TCPKeepAlive: 'yes',
36
+ ServerAliveInterval: 60,
37
+ ServerAliveCountMax: 3,
38
+ }.freeze
39
+ # Default value of ssh config file path.
40
+ DEFAULT_SSH_CONFIG = '/dev/null'
41
+ # pattern of password prompt.
42
+ PASSWORD_PROMPT_RE = / password: /.freeze
43
+ # sftp prompt.
44
+ SFTP_PROMPT = 'sftp> '
45
+ # pattern of sftp prompt.
46
+ SFTP_PROMPT_RE = /^#{SFTP_PROMPT}/.freeze
47
+
48
+ # Initialize SFTP wrapper.
49
+ #
50
+ # @param host [String] host address of SFTP server
51
+ # @param port [Integer] port number of SFTP server
52
+ # @param username [String] user name of SFTP server
53
+ # @param password [String] password of SFTP server
54
+ # @param ssh_options [Hash] SSH options (set as -o options)
55
+ # @param ssh_config [String] path of SSH config file (set as -F option unless nil)
56
+ # @param open_timeout [Integer, Float]
57
+ # @param read_timeout [Integer, Float]
58
+ # @param command_timeout [Integer, Float]
59
+ # @param debug [Boolean]
60
+ #
61
+ # rubocop:disable Metrics/ParameterLists
62
+ def initialize(host, port, username, password,
63
+ ssh_options: {},
64
+ ssh_config: DEFAULT_SSH_CONFIG,
65
+ open_timeout: DEFAULT_OPEN_TIMEOUT,
66
+ read_timeout: DEFAULT_READ_TIMEOUT,
67
+ command_timeout: DEFAULT_COMMAND_TIMEOUT,
68
+ debug: false)
69
+ @host = host
70
+ @port = port
71
+ @username = username
72
+ @password = password
73
+ @ssh_options = DEFAULT_SSH_OPTIONS.merge(ssh_options)
74
+ @ssh_config = ssh_config
75
+ @open_timeout = open_timeout
76
+ @read_timeout = read_timeout
77
+ @command_timeout = command_timeout
78
+ @debug = debug
79
+ end
80
+ # rubocop:enable Metrics/ParameterLists
81
+
82
+ # Get remote file.
83
+ #
84
+ # @param source [String] source file path
85
+ # @param destination [String] destination path
86
+ # @raise [SftpWrapper::Errors::ConnectionError]
87
+ # @raise [SftpWrapper::Errors::AuthenticationFailure]
88
+ # @raise [SftpWrapper::Errors::TimeoutError]
89
+ # @raise [SftpWrapper::Errors::CommandError]
90
+ #
91
+ def download(source, destination)
92
+ execute(%W[get #{source} #{destination}].shelljoin)
93
+ end
94
+
95
+ # Put local file.
96
+ #
97
+ # @param source [String] source file path
98
+ # @param destination [String] destination path
99
+ # @raise [SftpWrapper::Errors::ConnectionError]
100
+ # @raise [SftpWrapper::Errors::AuthenticationFailure]
101
+ # @raise [SftpWrapper::Errors::TimeoutError]
102
+ # @raise [SftpWrapper::Errors::CommandError]
103
+ #
104
+ def upload(source, destination)
105
+ execute(%W[put #{source} #{destination}].shelljoin)
106
+ end
107
+
108
+ private
109
+
110
+ # Execute sftp command.
111
+ #
112
+ # @param command [String] SFTP command
113
+ # @raise [SftpWrapper::Errors::ConnectionError]
114
+ # @raise [SftpWrapper::Errors::AuthenticationFailure]
115
+ # @raise [SftpWrapper::Errors::TimeoutError]
116
+ # @raise [SftpWrapper::Errors::CommandError]
117
+ #
118
+ def execute(command) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
119
+ cli = %w[sftp] + build_cli_args + [host]
120
+ PTY.getpty(cli.shelljoin) do |r, w, _pid|
121
+ w.sync = true
122
+
123
+ unless safe_expect(r, PASSWORD_PROMPT_RE, open_timeout)
124
+ raise SftpWrapper::Errors::ConnectionError, 'connection error.'
125
+ end
126
+
127
+ w.puts(password)
128
+
129
+ unless safe_expect(r, SFTP_PROMPT_RE, read_timeout)
130
+ raise SftpWrapper::Errors::AuthenticationFailure, 'authentication failure'
131
+ end
132
+
133
+ w.puts(command)
134
+
135
+ res = safe_expect(r, SFTP_PROMPT_RE, command_timeout)
136
+ raise SftpWrapper::Errors::TimeoutError, 'command timed out' unless res
137
+
138
+ skip = command.bytesize + 2
139
+ error_message = res[0].byteslice(skip..-1).chomp.sub(/#{SFTP_PROMPT}\z/, '').chomp
140
+ raise SftpWrapper::Errors::CommandError, error_message unless error_message.empty?
141
+
142
+ w.puts('quit')
143
+ end
144
+ end
145
+
146
+ def build_cli_args
147
+ args = []
148
+ args << '-q'
149
+ args += %W[-F #{ssh_config}] if ssh_config
150
+ args += %W[-P #{port} -o User=#{username}]
151
+ args += ssh_options.map { |k, v| %W[-o #{k}=#{v}] }.flatten
152
+ args
153
+ end
154
+
155
+ # @see https://github.com/ruby/ruby/blob/13b692200dba1056fa9033f2c64c43453f6d6a98/ext/pty/pty.c#L716-L723
156
+ def safe_expect(io, pattern, timeout)
157
+ expect_verbose = $expect_verbose # rubocop:disable Style/GlobalVars
158
+ $expect_verbose = debug # rubocop:disable Style/GlobalVars
159
+ io.expect(pattern, timeout)
160
+ rescue Errno::EIO
161
+ nil
162
+ ensure
163
+ $expect_verbose = expect_verbose # rubocop:disable Style/GlobalVars
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SftpWrapper
4
+ # version number.
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The wrapper for SFTP client CLI.
4
+ module SftpWrapper
5
+ autoload :Errors, 'sftp_wrapper/errors'
6
+ autoload :OpenSSH, 'sftp_wrapper/open_ssh'
7
+ autoload :Curl, 'sftp_wrapper/curl'
8
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'sftp_wrapper/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'sftp_wrapper'
9
+ spec.version = SftpWrapper::VERSION
10
+ spec.authors = ['Masataka SUZUKI']
11
+ spec.email = ['koshigoeb@gmail.com']
12
+
13
+ spec.summary = 'The wrapper for sftp CLI.'
14
+ spec.description = 'The wrapper for sftp CLI.'
15
+ spec.homepage = 'https://github.com/koshigoe/sftp_wrapper'
16
+ spec.license = 'MIT'
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = spec.homepage
23
+ spec.metadata['changelog_uri'] = 'https://github.com/koshigoe/sftp_wrapper/CHANGELOG.md'
24
+ else
25
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
26
+ 'public gem pushes.'
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
+ end
34
+ spec.bindir = 'exe'
35
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ['lib']
37
+
38
+ spec.required_ruby_version = '>= 2.3.0'
39
+
40
+ spec.add_development_dependency 'bundler', '~> 1.17'
41
+ spec.add_development_dependency 'minitest', '~> 5.0'
42
+ spec.add_development_dependency 'net-sftp', '~> 2.1'
43
+ spec.add_development_dependency 'rake', '~> 10.0'
44
+ spec.add_development_dependency 'rubocop', '~> 0.74'
45
+ spec.add_development_dependency 'simplecov', '~> 0.17'
46
+ spec.add_development_dependency 'yard', '~> 0.9'
47
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sftp_wrapper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Masataka SUZUKI
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-09-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: net-sftp
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.74'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.74'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.17'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.17'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ description: The wrapper for sftp CLI.
112
+ email:
113
+ - koshigoeb@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".github/workflows/continuous-integration-workflow.yml"
119
+ - ".gitignore"
120
+ - ".rubocop.yml"
121
+ - CHANGELOG.md
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - benchmark/Gemfile
127
+ - benchmark/Gemfile.lock
128
+ - benchmark/benchmark_download.rb
129
+ - benchmark/benchmark_upload.rb
130
+ - bin/console
131
+ - bin/setup
132
+ - docker-compose.yml
133
+ - lib/sftp_wrapper.rb
134
+ - lib/sftp_wrapper/curl.rb
135
+ - lib/sftp_wrapper/errors.rb
136
+ - lib/sftp_wrapper/open_ssh.rb
137
+ - lib/sftp_wrapper/version.rb
138
+ - sftp_wrapper.gemspec
139
+ homepage: https://github.com/koshigoe/sftp_wrapper
140
+ licenses:
141
+ - MIT
142
+ metadata:
143
+ homepage_uri: https://github.com/koshigoe/sftp_wrapper
144
+ source_code_uri: https://github.com/koshigoe/sftp_wrapper
145
+ changelog_uri: https://github.com/koshigoe/sftp_wrapper/CHANGELOG.md
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: 2.3.0
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubygems_version: 3.0.6
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: The wrapper for sftp CLI.
165
+ test_files: []