sftp_wrapper 0.1.0

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