bfs-scp 0.4.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2249f61d6b64e9816ec5a54ca7141ceff09bc9f290ddb7bdebd040b695e5a339
4
+ data.tar.gz: 81c282a260b33d36c24f906c0fee161cc5594ddef47ac2c35fbd1c9786df864e
5
+ SHA512:
6
+ metadata.gz: 78f6577a9b1a5e8617291f73f705539a3a3496b54cc9ed0c1c300dd83b140c7d566803f66f1ad145cc635fda1fe946678dc1c8622a1300e773459aa592dec888
7
+ data.tar.gz: cc8b355b35164f3ec1dbcc3c4abcd2dbf4a9922e6bd89b2588546d0c449656b9f84122d3ae9165728238d8b64697d9c7e990ffb6c7c517fd888fbc8f1a06a718
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'bfs-scp'
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 = 'SCP/SSH 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.4.0'
19
+
20
+ s.add_dependency 'bfs', s.version
21
+ s.add_dependency 'net-scp'
22
+ end
@@ -0,0 +1,190 @@
1
+ require 'bfs'
2
+ require 'net/scp'
3
+ require 'cgi'
4
+ require 'shellwords'
5
+
6
+ module BFS
7
+ module Bucket
8
+ # SCP buckets are operating on SCP/SSH connections.
9
+ class SCP < Abstract
10
+ class CommandError < RuntimeError
11
+ attr_reader :status
12
+
13
+ def initialize(cmd, status, extra=nil)
14
+ @status = status
15
+ super ["Command '#{cmd}' exited with status #{status}", extra].join(': ')
16
+ end
17
+ end
18
+
19
+ # Initializes a new bucket
20
+ # @param [String] host the host name
21
+ # @param [Hash] opts options
22
+ # @option opts [Integer] :port custom port. Default: 22.
23
+ # @option opts [String] :user user name for login.
24
+ # @option opts [String] :password password for login.
25
+ # @option opts [String] :prefix optional prefix.
26
+ # @option opts [Boolean] :compression use compression.
27
+ # @option opts [Boolean] :keepalive use keepalive.
28
+ # @option opts [Integer] :keepalive_interval interval if keepalive enabled. Default: 300.
29
+ # @option opts [Array<String>] :keys an array of file names of private keys to use for publickey and hostbased authentication.
30
+ # @option opts [Boolean|Symbol] :verify_host_key specifying how strict host-key verification should be, either false, true, :very, or :secure.
31
+ def initialize(host, opts={})
32
+ opts = opts.dup
33
+ opts.keys.each do |key|
34
+ val = opts.delete(key)
35
+ opts[key.to_sym] = val unless val.nil?
36
+ end
37
+ super(opts)
38
+
39
+ @prefix = opts.delete(:prefix)
40
+ @client = Net::SCP.start(host, nil, opts)
41
+
42
+ if @prefix # rubocop:disable Style/GuardClause
43
+ @prefix = norm_path(@prefix) + '/'
44
+ mkdir_p(@prefix)
45
+ end
46
+ end
47
+
48
+ # Lists the contents of a bucket using a glob pattern
49
+ def ls(pattern='**/*', _opts={})
50
+ prefix = @prefix || '.'
51
+ Enumerator.new do |y|
52
+ sh! 'find', prefix, '-type', 'f' do |out|
53
+ out.each_line do |line|
54
+ path = trim_prefix(line.strip)
55
+ y << path if File.fnmatch?(pattern, path, File::FNM_PATHNAME)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ # Info returns the object info
62
+ def info(path, _opts={})
63
+ full = full_path(path)
64
+ path = norm_path(path)
65
+ out = sh! 'stat', '-c', '%s;%Z', full
66
+
67
+ size, epoch = out.strip.split(';', 2).map(&:to_i)
68
+ BFS::FileInfo.new(path, size, Time.at(epoch))
69
+ rescue CommandError => e
70
+ e.status == 1 ? raise(BFS::FileNotFound, path) : raise
71
+ end
72
+
73
+ # Creates a new file and opens it for writing
74
+ def create(path, opts={}, &block)
75
+ full = full_path(path)
76
+ enc = opts.delete(:encoding) || @encoding
77
+ temp = BFS::TempWriter.new(path, encoding: enc) do |temp_path|
78
+ mkdir_p File.dirname(full)
79
+ @client.upload!(temp_path, full)
80
+ end
81
+ return temp unless block
82
+
83
+ begin
84
+ yield temp
85
+ ensure
86
+ temp.close
87
+ end
88
+ end
89
+
90
+ # Opens an existing file for reading
91
+ def open(path, opts={}, &block)
92
+ full = full_path(path)
93
+ enc = opts.delete(:encoding) || @encoding
94
+ temp = Tempfile.new(File.basename(path), encoding: enc)
95
+ temp.close
96
+
97
+ @client.download!(full, temp.path)
98
+ File.open(temp.path, encoding: enc, &block)
99
+ rescue Net::SCP::Error
100
+ raise BFS::FileNotFound, path
101
+ end
102
+
103
+ # Deletes a file.
104
+ def rm(path, _opts={})
105
+ path = full_path(path)
106
+ sh! 'rm', '-f', path
107
+ end
108
+
109
+ # Copies src to dst
110
+ #
111
+ # @param [String] src The source path.
112
+ # @param [String] dst The destination path.
113
+ def cp(src, dst, _opts={})
114
+ full_src = full_path(src)
115
+ full_dst = full_path(dst)
116
+
117
+ mkdir_p File.dirname(full_dst)
118
+ sh! 'cp', '-a', '-f', full_src, full_dst
119
+ rescue CommandError => e
120
+ e.status == 1 ? raise(BFS::FileNotFound, src) : raise
121
+ end
122
+
123
+ # Moves src to dst
124
+ #
125
+ # @param [String] src The source path.
126
+ # @param [String] dst The destination path.
127
+ def mv(src, dst, _opts={})
128
+ full_src = full_path(src)
129
+ full_dst = full_path(dst)
130
+
131
+ mkdir_p File.dirname(full_dst)
132
+ sh! 'mv', '-f', full_src, full_dst
133
+ rescue CommandError => e
134
+ e.status == 1 ? raise(BFS::FileNotFound, src) : raise
135
+ end
136
+
137
+ # Closes the underlying connection
138
+ def close
139
+ @client.session.close unless @client.session.closed?
140
+ end
141
+
142
+ private
143
+
144
+ def mkdir_p(path)
145
+ sh! 'mkdir', '-p', path
146
+ end
147
+
148
+ def sh!(*cmd) # rubocop:disable Metrics/MethodLength
149
+ stdout = ''
150
+ stderr = nil
151
+ status = 0
152
+ cmdstr = cmd.map {|x| Shellwords.escape(x) }.join(' ')
153
+
154
+ @client.session.open_channel do |ch|
155
+ ch.exec(cmdstr) do |_, _success|
156
+ ch.on_data do |_, data|
157
+ if block_given?
158
+ yield data
159
+ else
160
+ stdout += data
161
+ end
162
+ end
163
+ ch.on_extended_data do |_, _, data|
164
+ stderr = data
165
+ end
166
+ ch.on_request('exit-status') do |_, buf|
167
+ status = buf.read_long
168
+ end
169
+ end
170
+ end
171
+ @client.session.loop
172
+ raise CommandError.new(cmdstr, status, stderr) unless status.zero?
173
+
174
+ stdout
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ BFS.register('scp', 'ssh') do |url|
181
+ opts = {}
182
+ CGI.parse(url.query.to_s).each do |key, values|
183
+ opts[key.to_sym] = values.first
184
+ end
185
+ opts[:user] ||= CGI.unescape(url.user) if url.user
186
+ opts[:password] ||= CGI.unescape(url.password) if url.password
187
+ opts[:port] ||= url.port if url.port
188
+
189
+ BFS::Bucket::SCP.new url.host, opts
190
+ end
@@ -0,0 +1 @@
1
+ require 'bfs/bucket/scp'
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ sandbox = { host: '127.0.0.1', opts: { port: 7022, user: 'root', password: 'root' } }.freeze
4
+ run_spec = \
5
+ begin
6
+ Net::SCP.start sandbox[:host], nil, sandbox[:opts].merge(timeout: 1) do |scp|
7
+ scp.session.exec!('hostname')
8
+ end
9
+ true
10
+ rescue Net::SSH::Exception => e
11
+ warn "WARNING: unable to run #{File.basename __FILE__}: #{e.message}"
12
+ false
13
+ end
14
+
15
+ RSpec.describe BFS::Bucket::SCP, if: run_spec do
16
+ subject { described_class.new sandbox[:host], sandbox[:opts].merge(prefix: SecureRandom.uuid) }
17
+ after { subject.close }
18
+
19
+ it_behaves_like 'a bucket',
20
+ content_type: false,
21
+ metadata: false
22
+
23
+ it 'should resolve from URL' do
24
+ bucket = BFS.resolve('scp://root:root@127.0.0.1:7022')
25
+ expect(bucket).to be_instance_of(described_class)
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bfs-scp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.2
5
+ platform: ruby
6
+ authors:
7
+ - Dimitrij Denissenko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-09-18 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.4.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.4.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-scp
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-scp.gemspec
48
+ - lib/bfs/bucket/scp.rb
49
+ - lib/bfs/scp.rb
50
+ - spec/bfs/bucket/scp_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.4.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.0.3
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: SCP/SSH adapter for bfs
74
+ test_files:
75
+ - spec/bfs/bucket/scp_spec.rb