bfs-sftp 0.9.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: 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