activestorage-sftp 0.1.1 → 0.2.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 +4 -4
- data/README.md +33 -1
- data/activestorage-sftp.gemspec +2 -2
- data/lib/active_storage/service/sftp_service.rb +82 -37
- data/lib/active_storage/sftp.rb +3 -0
- data/lib/active_storage/sftp/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86f61c010b69d4972dbd32fd3eeec9ad415d5051
|
4
|
+
data.tar.gz: 3d82a1f0208e565c98a791506c8c11619faa7a4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ccd930d3aa5c2cd6f26dd6476b64000e935e1984f01536efd7abd46191f587ed48defdeac2f74efe3ee7155d1a45fe79ce14034613ac3b6c84002192bbca61c
|
7
|
+
data.tar.gz: cd1c9b1f1983508558e833af339105e2089adb47e23f70b4903c775beac5d75a2bc67c06d844908c15fafebfa183debce548b0df38d60b05f5459e83d81d286b
|
data/README.md
CHANGED
@@ -20,8 +20,8 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
|
24
23
|
Each application server saves blobs to file server through SFTP:
|
24
|
+
|
25
25
|
```yml
|
26
26
|
# config/storage.yml
|
27
27
|
sftp:
|
@@ -30,7 +30,9 @@ sftp:
|
|
30
30
|
root: /var/www/proj/shared/storage
|
31
31
|
host: file.intranet
|
32
32
|
public_host: https://file.internet
|
33
|
+
password: <%= ENV['PASSWORD'] %> # optional
|
33
34
|
```
|
35
|
+
|
34
36
|
File server serves blobs using DiskService:
|
35
37
|
```yml
|
36
38
|
# config/storage.yml
|
@@ -54,6 +56,36 @@ sftp:
|
|
54
56
|
host: secure.backup
|
55
57
|
```
|
56
58
|
|
59
|
+
### Further configuration options:
|
60
|
+
|
61
|
+
#### use_public_url: Generate plain ("dumb") URLs of upload server
|
62
|
+
|
63
|
+
By default the generated URLs will include parameters for `content_disposition`, expiration hints etc. A generated blobs URL might thus look like:
|
64
|
+
|
65
|
+
https://publichost/PATH/rails/active_storage/disk/hash-hash/name.JPG?content_type=image%2Fjpeg&disposition=inline%3B+filename%3D
|
66
|
+
|
67
|
+
If you prefer simple URLs like
|
68
|
+
|
69
|
+
https://publichost/PATH/hash
|
70
|
+
|
71
|
+
you can set a configuration option:
|
72
|
+
|
73
|
+
```yml
|
74
|
+
# config/storage.yml
|
75
|
+
sftp:
|
76
|
+
simple_public_urls: true # defaults to false
|
77
|
+
```
|
78
|
+
|
79
|
+
|
80
|
+
#### verify_via_http_get: Faster existence verification via HTTP GET
|
81
|
+
|
82
|
+
The default way of verifying that a blob does exist is to login to the sftp server and stat() the relevant file. This is done e.g. before re-transforming and uploading an image variant. While other "caching" solutions exist to speed up that process, a simple and efficient way of verifying the existence of a file is to query the relevant server with an HTTP HEAD request. Depending on the setup this might not always be a viable way, so it can be switched on with a configuration option.
|
83
|
+
|
84
|
+
```yml
|
85
|
+
# config/storage.yml
|
86
|
+
sftp:
|
87
|
+
verify_via_http_get: true # defaults to false
|
88
|
+
```
|
57
89
|
|
58
90
|
## Contributing
|
59
91
|
|
data/activestorage-sftp.gemspec
CHANGED
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ["lib"]
|
25
25
|
|
26
|
-
spec.add_dependency "rails", "
|
27
|
-
spec.add_dependency "net-sftp", "
|
26
|
+
spec.add_dependency "rails", ">= 5.2.0"
|
27
|
+
spec.add_dependency "net-sftp", ">= 2.1.2"
|
28
28
|
|
29
29
|
spec.add_development_dependency "bundler", "~> 1.17"
|
30
30
|
spec.add_development_dependency "rake", "~> 10.0"
|
@@ -11,12 +11,15 @@ module ActiveStorage
|
|
11
11
|
|
12
12
|
attr_reader :host, :user, :root, :public_host, :public_root
|
13
13
|
|
14
|
-
def initialize(host:, user:, public_host: nil, root: './', public_root: nil)
|
14
|
+
def initialize(host:, user:, public_host: nil, root: './', public_root: './', password: nil, simple_public_urls: false, verify_via_http_get: false)
|
15
15
|
@host = host
|
16
16
|
@user = user
|
17
17
|
@root = root
|
18
18
|
@public_host = public_host
|
19
19
|
@public_root = public_root
|
20
|
+
@password = password
|
21
|
+
@simple_public_urls = simple_public_urls
|
22
|
+
@verify_via_http_get = verify_via_http_get
|
20
23
|
end
|
21
24
|
|
22
25
|
def upload(key, io, checksum: nil, **)
|
@@ -116,37 +119,76 @@ module ActiveStorage
|
|
116
119
|
def exist?(key)
|
117
120
|
instrument :exist, key: key do |payload|
|
118
121
|
answer = false
|
119
|
-
|
120
|
-
|
122
|
+
|
123
|
+
if @verify_via_http_get
|
124
|
+
uri = URI([public_host, relative_folder_for(key), key].join('/'))
|
125
|
+
request = Net::HTTP.new uri.host
|
126
|
+
response = request.request_head uri.path
|
127
|
+
|
128
|
+
answer = (response.code.to_i == 200)
|
129
|
+
else
|
130
|
+
through_sftp do |sftp|
|
131
|
+
# TODO Probably adviseable to let some more exceptions go through
|
132
|
+
begin
|
133
|
+
sftp.stat!(path_for(key)) do |response|
|
134
|
+
answer = response.ok?
|
135
|
+
end
|
136
|
+
rescue Net::SFTP::StatusException => e
|
137
|
+
answer = false
|
138
|
+
end
|
139
|
+
end
|
121
140
|
end
|
141
|
+
|
122
142
|
payload[:exist] = answer
|
123
143
|
answer
|
124
144
|
end
|
125
145
|
end
|
126
146
|
|
127
147
|
def url(key, expires_in:, filename:, disposition:, content_type:)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
148
|
+
if @simple_public_urls
|
149
|
+
public_url(key)
|
150
|
+
else
|
151
|
+
classic_url(key,
|
152
|
+
expires_in: expires_in,
|
153
|
+
filename: filename,
|
154
|
+
disposition: disposition,
|
155
|
+
content_type: content_type)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def classic_url(key, expires_in:, filename:, disposition:, content_type:)
|
160
|
+
instrument :url, key: key do |payload|
|
161
|
+
raise NotConfigured, "public_host not defined." unless public_host
|
162
|
+
content_disposition = content_disposition_with(type: disposition, filename: filename)
|
163
|
+
verified_key_with_expiration = ActiveStorage.verifier.generate(
|
164
|
+
{
|
165
|
+
key: key,
|
166
|
+
disposition: content_disposition,
|
167
|
+
content_type: content_type
|
168
|
+
},
|
169
|
+
{
|
170
|
+
expires_in: expires_in,
|
171
|
+
purpose: :blob_key
|
172
|
+
}
|
173
|
+
)
|
174
|
+
|
175
|
+
generated_url = url_helpers.rails_disk_service_url(verified_key_with_expiration,
|
176
|
+
host: public_host,
|
177
|
+
disposition: content_disposition,
|
178
|
+
content_type: content_type,
|
179
|
+
filename: filename
|
180
|
+
)
|
181
|
+
payload[:url] = generated_url
|
182
|
+
generated_url
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def public_url(key)
|
187
|
+
instrument :url, key: key do |payload|
|
188
|
+
raise NotConfigured, "public_host not defined." unless public_host
|
189
|
+
generated_url = File.join(public_host, public_root, path_for(key), key)
|
190
|
+
payload[:url] = generated_url
|
191
|
+
generated_url
|
150
192
|
end
|
151
193
|
end
|
152
194
|
|
@@ -183,7 +225,8 @@ module ActiveStorage
|
|
183
225
|
|
184
226
|
protected
|
185
227
|
def through_sftp(&block)
|
186
|
-
|
228
|
+
opts = @password.present? ? {password: @password} : {}
|
229
|
+
Net::SFTP.start(@host, @user, opts) do |sftp|
|
187
230
|
block.call(sftp)
|
188
231
|
end
|
189
232
|
end
|
@@ -197,18 +240,20 @@ module ActiveStorage
|
|
197
240
|
end
|
198
241
|
|
199
242
|
def mkdir_for(key)
|
243
|
+
mkdir_p_for(path_for key)
|
244
|
+
end
|
245
|
+
|
246
|
+
def mkdir_p_for(abs_path)
|
200
247
|
through_sftp do |sftp|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
rescue => e
|
211
|
-
sftp.mkdir!(sub_folder)
|
248
|
+
base_path = ''
|
249
|
+
abs_path.split('/')[0...-1].each do |path|
|
250
|
+
sub_folder = File.join(base_path, path)
|
251
|
+
begin
|
252
|
+
sftp.opendir!(sub_folder)
|
253
|
+
rescue => e
|
254
|
+
sftp.mkdir!(sub_folder)
|
255
|
+
end
|
256
|
+
base_path = sub_folder
|
212
257
|
end
|
213
258
|
end
|
214
259
|
end
|
data/lib/active_storage/sftp.rb
CHANGED
metadata
CHANGED
@@ -1,41 +1,41 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activestorage-sftp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- treenewbee
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 5.2.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 5.2.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: net-sftp
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 2.1.2
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 2.1.2
|
41
41
|
- !ruby/object:Gem::Dependency
|