bfs-sftp 0.9.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: 1c4dfdb49d9d05b759223cabb87be952c6dfae65f966eabe4cf0662ad26dea2d
4
+ data.tar.gz: 950c37378a426f30b038b67cf4e9c48d252801714f2f161e7fa23556a96a8585
5
+ SHA512:
6
+ metadata.gz: a481be112d9b2873a303675fb33e33dbb993678f889f0ca501fd5cab6cbc7f39c5cc41b1c4818187e5672e652beac6cc07276b19621175eb548f251be45577ac
7
+ data.tar.gz: 511f81ecb71ff42f0f6a987af1871bc56960e50b187c9e352fe824d3e624534d2a2c91c76d600d24675a114c190c5785955e3fdf91d986afa39beafd128a6985
data/bfs-sftp.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'bfs-sftp'
3
+ s.version = File.read(File.expand_path('../.version', __dir__)).strip
4
+ s.platform = Gem::Platform::RUBY
5
+
6
+ s.licenses = ['Apache-2.0']
7
+ s.summary = 'SFTP adapter for bfs'
8
+ s.description = 'https://github.com/bsm/bfs.rb'
9
+
10
+ s.authors = ['Dimitrij Denissenko']
11
+ s.email = 'dimitrij@blacksquaremedia.com'
12
+ s.homepage = 'https://github.com/bsm/bfs.rb'
13
+
14
+ s.executables = []
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- spec/*`.split("\n")
17
+ s.require_paths = ['lib']
18
+ s.required_ruby_version = '>= 2.6.0'
19
+
20
+ s.add_dependency 'bfs', s.version
21
+ s.add_dependency 'net-sftp'
22
+ end
@@ -0,0 +1,143 @@
1
+ require 'bfs'
2
+ require 'net/sftp'
3
+
4
+ module BFS
5
+ module Bucket
6
+ # SFTP buckets are operating on SFTP connections.
7
+ class SFTP < Abstract
8
+ StatusCodes = Net::SFTP::Constants::StatusCodes
9
+
10
+ # Initializes a new bucket
11
+ # @param [String] host the host name
12
+ # @param [Hash] opts options
13
+ # @option opts [Integer] :port custom port. Default: 22.
14
+ # @option opts [String] :user user name for login.
15
+ # @option opts [String] :password password for login.
16
+ # @option opts [String] :prefix optional prefix.
17
+ # @option opts [Boolean] :compression use compression.
18
+ # @option opts [Boolean] :keepalive use keepalive.
19
+ # @option opts [Integer] :keepalive_interval interval if keepalive enabled. Default: 300.
20
+ # @option opts [Array<String>] :keys an array of file names of private keys to use for publickey and hostbased authentication.
21
+ # @option opts [Boolean|Symbol] :verify_host_key specifying how strict host-key verification should be, either false, true, :very, or :secure.
22
+ def initialize(host, prefix: nil, **opts)
23
+ super(**opts)
24
+
25
+ @prefix = prefix
26
+ @session = Net::SSH.start(host, nil, **opts.slice(*Net::SSH::VALID_OPTIONS), non_interactive: true)
27
+
28
+ if @prefix # rubocop:disable Style/GuardClause
29
+ @prefix = "#{norm_path(@prefix)}/"
30
+ mkdir_p @prefix
31
+ end
32
+ end
33
+
34
+ # Lists the contents of a bucket using a glob pattern
35
+ def ls(pattern = '**/*', **_opts)
36
+ Enumerator.new do |acc|
37
+ walk(pattern) {|path, _| acc << path }
38
+ end
39
+ end
40
+
41
+ # Iterates over the contents of a bucket using a glob pattern
42
+ def glob(pattern = '**/*', **_opts)
43
+ Enumerator.new do |acc|
44
+ walk(pattern) do |path, attrs|
45
+ acc << file_info(path, attrs)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Info returns the object info
51
+ def info(path, **_opts)
52
+ full = full_path(path)
53
+ path = norm_path(path)
54
+ attrs = @session.sftp.stat!(full)
55
+ raise BFS::FileNotFound, path unless attrs.file?
56
+
57
+ file_info path, attrs
58
+ rescue Net::SFTP::StatusException => e
59
+ raise BFS::FileNotFound, path if e.code == StatusCodes::FX_NO_SUCH_FILE
60
+
61
+ raise
62
+ end
63
+
64
+ # Creates a new file and opens it for writing
65
+ # @option opts [String|Encoding] :encoding Custom file encoding.
66
+ # @option opts [Integer] :perm Custom file permission, default: 0600.
67
+ def create(path, encoding: self.encoding, perm: self.perm, **opts, &block)
68
+ full = full_path(path)
69
+
70
+ opts[:preserve] = true if perm && !opts.key?(:preserve)
71
+ BFS::Writer.new(path, encoding: encoding, perm: perm) do |temp_path|
72
+ mkdir_p File.dirname(full)
73
+ @session.sftp.upload!(temp_path, full, **opts)
74
+ end.perform(&block)
75
+ end
76
+
77
+ # Opens an existing file for reading
78
+ def open(path, encoding: self.encoding, tempdir: nil, **_opts, &block)
79
+ full = full_path(path)
80
+ temp = Tempfile.new(File.basename(path), tempdir, encoding: encoding)
81
+ temp.close
82
+
83
+ @session.sftp.download!(full, temp.path)
84
+ File.open(temp.path, encoding: encoding, &block)
85
+ rescue Net::SFTP::StatusException => e
86
+ raise BFS::FileNotFound, path if e.code == StatusCodes::FX_NO_SUCH_FILE
87
+
88
+ raise
89
+ end
90
+
91
+ # Deletes a file.
92
+ def rm(path, **_opts)
93
+ full = full_path(path)
94
+ @session.sftp.remove!(full)
95
+ rescue Net::SFTP::StatusException => e
96
+ raise unless e.code == StatusCodes::FX_NO_SUCH_FILE
97
+ end
98
+
99
+ # Closes the underlying connection
100
+ def close
101
+ @session.close unless @session.closed?
102
+ end
103
+
104
+ private
105
+
106
+ def file_info(path, attrs)
107
+ BFS::FileInfo.new(path: path, size: attrs.size.to_i, mtime: Time.at(attrs.mtime.to_i), mode: BFS.norm_mode(attrs.permissions))
108
+ end
109
+
110
+ def walk(pattern)
111
+ @session.sftp.dir.glob(@prefix || '/', pattern) do |ent|
112
+ next unless ent.file?
113
+
114
+ path = norm_path(ent.name)
115
+ yield(path, ent.attributes)
116
+ end
117
+ end
118
+
119
+ def mkdir_p(path)
120
+ parts = path.split('/').reject(&:empty?)
121
+ cmds = (0...parts.size).map do |i|
122
+ @session.sftp.mkdir parts[0..i].join('/')
123
+ end
124
+ cmds.each do |req|
125
+ req.wait
126
+ next if req.response.code <= StatusCodes::FX_FAILURE
127
+
128
+ raise Net::SFTP::StatusException, req.response
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ BFS.register('sftp') do |url, opts, block|
136
+ prefix = BFS.norm_path(opts[:prefix] || url.path)
137
+ opts[:prefix] = prefix unless prefix.empty?
138
+ opts[:user] ||= CGI.unescape(url.user) if url.user
139
+ opts[:password] ||= CGI.unescape(url.password) if url.password
140
+ opts[:port] ||= url.port if url.port
141
+
142
+ BFS::Bucket::SFTP.open(url.host, **opts, &block)
143
+ end
data/lib/bfs/sftp.rb ADDED
@@ -0,0 +1 @@
1
+ require 'bfs/bucket/sftp'
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe BFS::Bucket::SFTP, sftp: true do
4
+ subject do
5
+ described_class.new '127.0.0.1', port: 7023, user: 'sftp', password: 'DockerPassSFTP', prefix: SecureRandom.uuid
6
+ end
7
+
8
+ it_behaves_like 'a bucket', content_type: false, metadata: false
9
+
10
+ it 'resolves from URL' do
11
+ bucket = BFS.resolve('sftp://sftp:DockerPassSFTP@127.0.0.1:7023')
12
+ expect(bucket).to be_instance_of(described_class)
13
+ bucket.close
14
+
15
+ bucket = BFS.resolve('sftp://sftp:DockerPassSFTP@127.0.0.1:7023/a/b/')
16
+ expect(bucket).to be_instance_of(described_class)
17
+ bucket.close
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bfs-sftp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Dimitrij Denissenko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bfs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-sftp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: https://github.com/bsm/bfs.rb
42
+ email: dimitrij@blacksquaremedia.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - bfs-sftp.gemspec
48
+ - lib/bfs/bucket/sftp.rb
49
+ - lib/bfs/sftp.rb
50
+ - spec/bfs/bucket/sftp_spec.rb
51
+ homepage: https://github.com/bsm/bfs.rb
52
+ licenses:
53
+ - Apache-2.0
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 2.6.0
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubygems_version: 3.2.15
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: SFTP adapter for bfs
74
+ test_files:
75
+ - spec/bfs/bucket/sftp_spec.rb